import type { Database } from '../src/types/database.types' import { randomUUID } from 'node:crypto' import { createClient } from '@supabase/supabase-js' import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' import { executeSQL, getSupabaseClient, resetAndSeedAppDataStats, resetAppDataStats, SUPABASE_ANON_KEY, SUPABASE_BASE_URL, USER_EMAIL, USER_EMAIL_NONMEMBER, USER_ID, USER_PASSWORD, USER_PASSWORD_NONMEMBER, } from './test-utils.ts' const orgId = randomUUID() const staleAppId = `com.chart.refresh.stale.${randomUUID().slice(0, 8)}` const freshAppId = `com.chart.refresh.fresh.${randomUUID().slice(0, 8)}` function createAuthClient() { return createClient(SUPABASE_BASE_URL, SUPABASE_ANON_KEY, { auth: { persistSession: false, }, }) } async function clearCronStatAppMessages(appIds: string[]) { await executeSQL( `DELETE FROM pgmq.q_cron_stat_app WHERE message->'payload'->>'appId' = ANY($1::text[])`, [appIds], ) } async function countCronStatAppMessages(appId: string): Promise { const rows = await executeSQL( `SELECT COUNT(*)::integer AS count FROM pgmq.q_cron_stat_app WHERE message->'payload'->>'appId' = $1`, [appId], ) return rows[0]?.count ?? 0 } async function countStatsRefreshAuditLogs(): Promise { const rows = await executeSQL( `SELECT COUNT(*)::integer AS count FROM public.audit_logs WHERE org_id = $1::uuid AND operation = 'UPDATE' AND table_name IN ('apps', 'orgs') AND changed_fields && ARRAY['stats_refresh_requested_at', 'stats_updated_at']::text[] AND NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(changed_fields) AS changed_field(field_name) WHERE changed_field.field_name <> ALL(ARRAY['stats_refresh_requested_at', 'stats_updated_at', 'updated_at']::text[]) )`, [orgId], ) return rows[0]?.count ?? 0 } async function getAppRefreshState(appId: string) { const { data, error } = await getSupabaseClient() .from('app_stats_refresh_state') .select('stats_refresh_requested_at,stats_updated_at') .eq('app_id', appId) .single() if (error) throw error return data } async function getOrgRefreshState() { const [state] = await executeSQL<{ manual_refresh_requested_at: string | null stats_refresh_requested_at: string | null stats_updated_at: string | null }>(` SELECT manual_refresh_requested_at::text AS manual_refresh_requested_at, stats_refresh_requested_at::text AS stats_refresh_requested_at, stats_updated_at::text AS stats_updated_at FROM public.org_stats_refresh_state WHERE org_id = $1 `, [orgId]) return state } async function getLegacyOrgRefreshState() { const [state] = await executeSQL<{ last_stats_updated_at: string | null stats_refresh_requested_at: string | null stats_updated_at: string | null xmin: string }>(` SELECT xmin::text AS xmin, last_stats_updated_at::text AS last_stats_updated_at, stats_refresh_requested_at::text AS stats_refresh_requested_at, stats_updated_at::text AS stats_updated_at FROM public.orgs WHERE id = $1 `, [orgId]) return state } async function getDateRangeMetrics(scope: 'app' | 'org', metricDateText: string) { if (scope === 'app') { return getSupabaseClient().rpc('get_app_metrics', { p_app_id: staleAppId, p_end_date: metricDateText, p_org_id: orgId, p_start_date: metricDateText, }) } return getSupabaseClient().rpc('get_app_metrics', { end_date: metricDateText, org_id: orgId, start_date: metricDateText, }) } describe('chart refresh RPCs', () => { const authorizedClient = createAuthClient() const unauthorizedClient = createAuthClient() beforeAll(async () => { const { error: authorizedSignInError } = await authorizedClient.auth.signInWithPassword({ email: USER_EMAIL, password: USER_PASSWORD, }) expect(authorizedSignInError).toBeNull() const { error: unauthorizedSignInError } = await unauthorizedClient.auth.signInWithPassword({ email: USER_EMAIL_NONMEMBER, password: USER_PASSWORD_NONMEMBER, }) expect(unauthorizedSignInError).toBeNull() await getSupabaseClient().from('orgs').insert({ created_by: USER_ID, id: orgId, management_email: USER_EMAIL, name: `Chart Refresh Org ${orgId}`, updated_at: new Date().toISOString(), }).throwOnError() await getSupabaseClient().from('org_users').insert({ org_id: orgId, user_id: USER_ID, rbac_role_name: 'org_super_admin', }).throwOnError() await getSupabaseClient().from('apps').insert([ { app_id: staleAppId, created_at: new Date().toISOString(), icon_url: '', last_version: '1.0.0', name: 'Stale Chart Refresh App', owner_org: orgId, updated_at: new Date().toISOString(), user_id: USER_ID, }, { app_id: freshAppId, created_at: new Date().toISOString(), icon_url: '', last_version: '1.0.0', name: 'Fresh Chart Refresh App', owner_org: orgId, updated_at: new Date().toISOString(), user_id: USER_ID, }, ]).throwOnError() await resetAndSeedAppDataStats(staleAppId) }) beforeEach(async () => { await clearCronStatAppMessages([staleAppId, freshAppId]) await getSupabaseClient().from('app_metrics_cache').delete().eq('org_id', orgId).throwOnError() await getSupabaseClient().from('orgs').update({ stats_refresh_requested_at: null, stats_updated_at: null, }).eq('id', orgId).throwOnError() await getSupabaseClient().from('app_stats_refresh_state').update({ stats_refresh_requested_at: null, stats_updated_at: null, }).in('app_id', [staleAppId, freshAppId]).throwOnError() await executeSQL(` INSERT INTO public.org_stats_refresh_state ( org_id, manual_refresh_requested_at, stats_refresh_requested_at, stats_updated_at ) VALUES ($1, NULL, NULL, NULL) ON CONFLICT (org_id) DO UPDATE SET manual_refresh_requested_at = NULL, stats_refresh_requested_at = NULL, stats_updated_at = NULL `, [orgId]) await getSupabaseClient().from('audit_logs').delete().eq('org_id', orgId).throwOnError() }) afterAll(async () => { await clearCronStatAppMessages([staleAppId, freshAppId]) await resetAppDataStats(staleAppId) await getSupabaseClient().from('app_metrics_cache').delete().eq('org_id', orgId) await getSupabaseClient().from('apps').delete().in('app_id', [staleAppId, freshAppId]) await getSupabaseClient().from('org_users').delete().eq('org_id', orgId) await getSupabaseClient().from('orgs').delete().eq('id', orgId) await authorizedClient.auth.signOut() await unauthorizedClient.auth.signOut() }) it('queue_cron_stat_app_for_app only stamps refresh_requested_at when it enqueues work', async () => { await getSupabaseClient().from('app_stats_refresh_state').update({ stats_updated_at: new Date(Date.now() - 10 * 60 * 1000).toISOString(), }).eq('app_id', staleAppId).throwOnError() const { data: appBefore } = await getSupabaseClient() .from('apps') .select('updated_at') .eq('app_id', staleAppId) .single() await getSupabaseClient().rpc('queue_cron_stat_app_for_app', { p_app_id: staleAppId, p_org_id: orgId, }).throwOnError() const queuedState = await getAppRefreshState(staleAppId) expect(queuedState?.stats_refresh_requested_at).toBeTruthy() expect(await countCronStatAppMessages(staleAppId)).toBe(1) const { data: appAfter } = await getSupabaseClient() .from('apps') .select('updated_at') .eq('app_id', staleAppId) .single() expect(appAfter?.updated_at).toBe(appBefore?.updated_at) await getSupabaseClient().from('app_stats_refresh_state').update({ stats_refresh_requested_at: null, stats_updated_at: new Date().toISOString(), }).eq('app_id', freshAppId).throwOnError() await getSupabaseClient().rpc('queue_cron_stat_app_for_app', { p_app_id: freshAppId, p_org_id: orgId, }).throwOnError() const skippedState = await getAppRefreshState(freshAppId) expect(skippedState?.stats_refresh_requested_at).toBeNull() expect(await countCronStatAppMessages(freshAppId)).toBe(0) }) it('only returns app refresh state to users who can read the app', async () => { const { data: authorizedState, error: authorizedError } = await authorizedClient .rpc('get_app_stats_refresh_state', { p_app_id: staleAppId }) .single() expect(authorizedError).toBeNull() expect(authorizedState?.owner_org).toBe(orgId) const { data: unauthorizedState, error: unauthorizedError } = await unauthorizedClient .rpc('get_app_stats_refresh_state', { p_app_id: staleAppId }) .maybeSingle() expect(unauthorizedError).toBeNull() expect(unauthorizedState).toBeNull() }) it('request_app_chart_refresh queues once when stale and rejects users without access', async () => { await getSupabaseClient().from('app_stats_refresh_state').update({ stats_updated_at: new Date(Date.now() - 10 * 60 * 1000).toISOString(), }).eq('app_id', staleAppId).throwOnError() const { data: firstResponse, error: firstError } = await authorizedClient.rpc('request_app_chart_refresh', { app_id: staleAppId, }).single() expect(firstError).toBeNull() expect(firstResponse?.queued_app_ids).toEqual([staleAppId]) expect(firstResponse?.queued_count).toBe(1) expect(firstResponse?.skipped_count).toBe(0) expect(await countCronStatAppMessages(staleAppId)).toBe(1) expect(await countStatsRefreshAuditLogs()).toBe(0) const { data: secondResponse, error: secondError } = await authorizedClient.rpc('request_app_chart_refresh', { app_id: staleAppId, }).single() expect(secondError).toBeNull() expect(secondResponse?.queued_count).toBe(0) expect(secondResponse?.skipped_count).toBe(1) expect(await countCronStatAppMessages(staleAppId)).toBe(1) const { data: deniedData, error: deniedError } = await unauthorizedClient.rpc('request_app_chart_refresh', { app_id: staleAppId, }).single() expect(deniedData).toBeNull() expect(deniedError?.message).toContain('App access denied') }) it('request_org_chart_refresh stamps org refresh state and only queues stale apps', async () => { await getSupabaseClient().from('app_stats_refresh_state').update({ stats_updated_at: new Date(Date.now() - 10 * 60 * 1000).toISOString(), }).eq('app_id', staleAppId).throwOnError() await getSupabaseClient().from('app_stats_refresh_state').update({ stats_updated_at: new Date().toISOString(), }).eq('app_id', freshAppId).throwOnError() const legacyStateBefore = await getLegacyOrgRefreshState() const { data, error } = await authorizedClient.rpc('request_org_chart_refresh', { org_id: orgId, }).single() expect(error).toBeNull() expect(data?.queued_app_ids).toEqual([staleAppId]) expect(data?.queued_count).toBe(1) expect(data?.skipped_count).toBe(1) expect(data?.requested_at).toBeTruthy() const orgState = await getOrgRefreshState() expect(orgState?.manual_refresh_requested_at).toBeTruthy() expect(orgState?.stats_refresh_requested_at).toBeNull() expect(await getLegacyOrgRefreshState()).toEqual(legacyStateBefore) const staleState = await getAppRefreshState(staleAppId) const freshState = await getAppRefreshState(freshAppId) expect(staleState?.stats_refresh_requested_at).toBeTruthy() expect(freshState?.stats_refresh_requested_at).toBeNull() expect(await countCronStatAppMessages(staleAppId)).toBe(1) expect(await countCronStatAppMessages(freshAppId)).toBe(0) expect(await countStatsRefreshAuditLogs()).toBe(0) }) it('request_org_chart_refresh preserves the current org request marker when no apps are queued', async () => { const inProgressRequestedAt = new Date(Date.now() - 2 * 60 * 1000).toISOString() await executeSQL(` UPDATE public.org_stats_refresh_state SET manual_refresh_requested_at = $2 WHERE org_id = $1 `, [orgId, inProgressRequestedAt]) await getSupabaseClient().from('app_stats_refresh_state').update({ stats_refresh_requested_at: inProgressRequestedAt, stats_updated_at: new Date().toISOString(), }).in('app_id', [staleAppId, freshAppId]).throwOnError() const beforeOrgState = await getOrgRefreshState() const legacyStateBefore = await getLegacyOrgRefreshState() expect(beforeOrgState?.manual_refresh_requested_at).toBeTruthy() const { data, error } = await authorizedClient.rpc('request_org_chart_refresh', { org_id: orgId, }).single() expect(error).toBeNull() expect(data?.queued_app_ids).toEqual([]) expect(data?.queued_count).toBe(0) expect(data?.skipped_count).toBe(2) expect(new Date(`${data?.requested_at}Z`).toISOString()).toBe(inProgressRequestedAt) const afterOrgState = await getOrgRefreshState() expect(afterOrgState).toEqual(beforeOrgState) expect(await getLegacyOrgRefreshState()).toEqual(legacyStateBefore) expect(await countCronStatAppMessages(staleAppId)).toBe(0) expect(await countCronStatAppMessages(freshAppId)).toBe(0) }) it('returns org refresh state only to callers with org.read access', async () => { const updatedAt = new Date(Date.now() - 60_000).toISOString() const requestedAt = new Date().toISOString() await executeSQL(` UPDATE public.org_stats_refresh_state SET stats_updated_at = $2, manual_refresh_requested_at = $3 WHERE org_id = $1 `, [orgId, updatedAt, requestedAt]) const { data: authorizedState, error: authorizedError } = await authorizedClient .rpc('get_org_stats_refresh_state', { p_org_id: orgId }) .single() expect(authorizedError).toBeNull() expect(new Date(`${authorizedState?.stats_updated_at}Z`).toISOString()).toBe(updatedAt) expect(new Date(`${authorizedState?.stats_refresh_requested_at}Z`).toISOString()).toBe(requestedAt) const { data: unauthorizedState, error: unauthorizedError } = await unauthorizedClient .rpc('get_org_stats_refresh_state', { p_org_id: orgId }) .maybeSingle() expect(unauthorizedError).toBeNull() expect(unauthorizedState).toBeNull() const { error: directReadError } = await authorizedClient .from('org_stats_refresh_state') .select('org_id') .eq('org_id', orgId) expect(directReadError).not.toBeNull() }) it('keeps org refresh state private and outside logical replication publications', async () => { const [privileges] = await executeSQL<{ anon_select: boolean authenticated_select: boolean }>(` SELECT has_table_privilege('anon', 'public.org_stats_refresh_state', 'SELECT') AS anon_select, has_table_privilege('authenticated', 'public.org_stats_refresh_state', 'SELECT') AS authenticated_select `) const publicationRows = await executeSQL(` SELECT pubname FROM pg_catalog.pg_publication_tables WHERE schemaname = 'public' AND tablename = 'org_stats_refresh_state' `) expect(privileges).toEqual({ anon_select: false, authenticated_select: false, }) expect(publicationRows).toEqual([]) }) it('get_orgs_v7 returns state timestamps and falls back only when the state row is missing', async () => { const legacyUpdatedAt = new Date(Date.now() - 10 * 60 * 1000).toISOString() const legacyRequestedAt = new Date(Date.now() - 9 * 60 * 1000).toISOString() const stateUpdatedAt = new Date(Date.now() - 2 * 60 * 1000).toISOString() const stateRequestedAt = new Date(Date.now() - 60 * 1000).toISOString() await getSupabaseClient().from('orgs').update({ stats_refresh_requested_at: legacyRequestedAt, stats_updated_at: legacyUpdatedAt, }).eq('id', orgId).throwOnError() await executeSQL(` UPDATE public.org_stats_refresh_state SET stats_updated_at = $2, manual_refresh_requested_at = $3, stats_refresh_requested_at = $2 WHERE org_id = $1 `, [orgId, stateUpdatedAt, stateRequestedAt]) const { data: stateRows, error: stateError } = await authorizedClient.rpc('get_orgs_v7') expect(stateError).toBeNull() const stateOrg = stateRows?.find(org => org.gid === orgId) expect(new Date(`${stateOrg?.stats_updated_at}Z`).toISOString()).toBe(stateUpdatedAt) expect(new Date(`${stateOrg?.stats_refresh_requested_at}Z`).toISOString()).toBe(stateRequestedAt) const { data: stateUserRows, error: stateUserError } = await getSupabaseClient().rpc('get_orgs_v7', { userid: USER_ID, }) expect(stateUserError).toBeNull() const stateUserOrg = stateUserRows?.find(org => org.gid === orgId) expect(new Date(`${stateUserOrg?.stats_updated_at}Z`).toISOString()).toBe(stateUpdatedAt) expect(new Date(`${stateUserOrg?.stats_refresh_requested_at}Z`).toISOString()).toBe(stateRequestedAt) await executeSQL('DELETE FROM public.org_stats_refresh_state WHERE org_id = $1', [orgId]) const { data: fallbackRows, error: fallbackError } = await authorizedClient.rpc('get_orgs_v7') expect(fallbackError).toBeNull() const fallbackOrg = fallbackRows?.find(org => org.gid === orgId) expect(new Date(`${fallbackOrg?.stats_updated_at}Z`).toISOString()).toBe(legacyUpdatedAt) expect(new Date(`${fallbackOrg?.stats_refresh_requested_at}Z`).toISOString()).toBe(legacyRequestedAt) const { data: fallbackUserRows, error: fallbackUserError } = await getSupabaseClient().rpc('get_orgs_v7', { userid: USER_ID, }) expect(fallbackUserError).toBeNull() const fallbackUserOrg = fallbackUserRows?.find(org => org.gid === orgId) expect(new Date(`${fallbackUserOrg?.stats_updated_at}Z`).toISOString()).toBe(legacyUpdatedAt) expect(new Date(`${fallbackUserOrg?.stats_refresh_requested_at}Z`).toISOString()).toBe(legacyRequestedAt) }) it('preserves the exact get_orgs_v7 output contract for both overloads', async () => { const expectedColumns = [ 'gid', 'created_by', 'created_at', 'logo', 'website', 'name', 'role', 'is_invite', 'paying', 'trial_left', 'can_use_more', 'is_canceled', 'app_count', 'subscription_start', 'subscription_end', 'management_email', 'is_yearly', 'stats_updated_at', 'stats_refresh_requested_at', 'next_stats_update_at', 'credit_available', 'credit_total', 'credit_next_expiration', 'enforcing_2fa', '2fa_has_access', 'enforce_hashed_api_keys', 'password_policy_config', 'password_has_access', 'require_apikey_expiration', 'max_apikey_expiration_days', 'enforce_encrypted_bundles', 'required_encryption_key', ] const rows = await executeSQL<{ columns: string[], overload: string }>(` SELECT overload, array_agg(arg_name ORDER BY ordinality) AS columns FROM ( SELECT 'no_args' AS overload, args.arg_name, args.ordinality FROM pg_catalog.pg_proc proc JOIN LATERAL unnest(proc.proallargtypes, proc.proargmodes, proc.proargnames) WITH ORDINALITY AS args(type_oid, arg_mode, arg_name, ordinality) ON true WHERE proc.oid = 'public.get_orgs_v7()'::regprocedure AND args.arg_mode = 't' UNION ALL SELECT 'userid' AS overload, args.arg_name, args.ordinality FROM pg_catalog.pg_proc proc JOIN LATERAL unnest(proc.proallargtypes, proc.proargmodes, proc.proargnames) WITH ORDINALITY AS args(type_oid, arg_mode, arg_name, ordinality) ON true WHERE proc.oid = 'public.get_orgs_v7(uuid)'::regprocedure AND args.arg_mode = 't' ) output_args GROUP BY overload ORDER BY overload `) expect(rows.map(row => row.overload)).toEqual(['no_args', 'userid']) for (const row of rows) expect(row.columns).toEqual(expectedColumns) }) it.each(['org', 'app'] as const)('get_app_metrics %s date-range overload invalidates only after state completion advances', async (scope) => { const metricDate = new Date() metricDate.setHours(0, 0, 0, 0) const metricDateText = metricDate.toISOString().slice(0, 10) await getSupabaseClient().from('daily_mau').upsert({ app_id: staleAppId, date: metricDateText, mau: 5, }, { onConflict: 'app_id,date', }).throwOnError() await getSupabaseClient().from('daily_bandwidth').upsert({ app_id: staleAppId, bandwidth: 0, date: metricDateText, }, { onConflict: 'app_id,date', }).throwOnError() await getSupabaseClient().from('daily_storage').upsert({ app_id: staleAppId, date: metricDateText, storage: 0, }, { onConflict: 'app_id,date', }).throwOnError() const { data: initialMetrics, error: initialError } = await getDateRangeMetrics(scope, metricDateText) expect(initialError).toBeNull() const initialRow = initialMetrics?.find(row => row.app_id === staleAppId && row.date === metricDateText) expect(initialRow?.mau).toBe(5) const cacheRows = await executeSQL( 'SELECT cached_at FROM public.app_metrics_cache WHERE org_id = $1 LIMIT 1', [orgId], ) expect(cacheRows).toHaveLength(1) await getSupabaseClient().from('daily_mau').update({ mau: 9 }).eq('app_id', staleAppId).eq('date', metricDateText).throwOnError() const { data: cachedMetrics, error: cachedError } = await getDateRangeMetrics(scope, metricDateText) expect(cachedError).toBeNull() const cachedRow = cachedMetrics?.find(row => row.app_id === staleAppId && row.date === metricDateText) expect(cachedRow?.mau).toBe(5) const cacheAfterFreshRead = await executeSQL( 'SELECT cached_at FROM public.app_metrics_cache WHERE org_id = $1 LIMIT 1', [orgId], ) expect(cacheAfterFreshRead[0]?.cached_at).toEqual(cacheRows[0].cached_at) const refreshedAt = new Date(new Date(cacheRows[0].cached_at).getTime() + 60_000).toISOString() const legacyStateBefore = await getLegacyOrgRefreshState() await executeSQL(` UPDATE public.org_stats_refresh_state SET stats_updated_at = $2 WHERE org_id = $1 `, [orgId, refreshedAt]) const { data: refreshedMetrics, error: refreshedError } = await getDateRangeMetrics(scope, metricDateText) expect(refreshedError).toBeNull() const refreshedRow = refreshedMetrics?.find(row => row.app_id === staleAppId && row.date === metricDateText) expect(refreshedRow?.mau).toBe(9) expect(await getLegacyOrgRefreshState()).toEqual(legacyStateBefore) }) })