-
-
Notifications
You must be signed in to change notification settings - Fork 135
fix(ci): stop shard 5/6 flakes from PostgREST upstream errors #2808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
02c41ba
fix(ci): stop shard 5/6 flakes from PostgREST upstream errors
riderx 81e8a75
fix(ci): harden shard5 PostgREST flake fix against CI fallout
riderx 314eb72
fix(auth): coerce postgres apikey id to number for self-update checks
riderx 51d685f
merge(main): sync fix/shard5-postgrest-upstream-flakes with origin/main
riderx c4fde99
test(unit): align countDevices mock with updatedAt options from main
riderx ca3039f
test(unit): drop regex HTML strip mock flagged by CodeQL
riderx c93238b
fix(ci): seed org API-key fixtures via SQL to avoid PostgREST flakes
riderx ce8a0a1
fix(ci): store UUID API keys in SQL seed helper for auth middleware
riderx 8378c53
fix(auth): rethrow checkKey upstream 503 and auth against primary
riderx 1a288aa
fix(ci): always seed UUID secrets for hashed API keys too
riderx 9a1fddc
fix(ci): seed organization-api and audit-logs fixtures via SQL
riderx bf6aa7a
fix(ci): include actor user id in audit-logs seed error
riderx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| /** | ||
| * Reproduce backend shard 5/6 PostgREST/Kong upstream flakes without Vitest retries. | ||
| * | ||
| * CI evidence: | ||
| * - stats createAppVersions via PostgREST → "An invalid response was received from the upstream server" | ||
| * - organization-api capgkey auth via PostgREST → intermittent 401 invalid_apikey | ||
| * | ||
| * This script forces the upstream failure mode by pausing the local PostgREST | ||
| * container during the old-path hammer, then proves direct SQL still succeeds. | ||
| * | ||
| * Usage (Supabase must be running): | ||
| * bun run supabase:with-env -- bun scripts/repro-shard5-postgrest-flakes.ts | ||
| */ | ||
| import { randomUUID } from 'node:crypto' | ||
| import { spawnSync } from 'node:child_process' | ||
| import { createClient } from '@supabase/supabase-js' | ||
| import pg from 'pg' | ||
|
|
||
| const SUPABASE_URL = process.env.SUPABASE_URL | ||
| const SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY | ||
| ?? process.env.SUPABASE_SERVICE_ROLE_KEY | ||
| ?? process.env.SERVICE_ROLE_KEY | ||
| const DB_URL = process.env.SUPABASE_DB_URL ?? process.env.DB_URL | ||
|
|
||
| if (!SUPABASE_URL || !SERVICE_KEY || !DB_URL) { | ||
| console.error('Missing SUPABASE_URL / service key / DB URL. Use: bun run supabase:with-env -- bun scripts/repro-shard5-postgrest-flakes.ts') | ||
| process.exit(2) | ||
| } | ||
|
|
||
| const ORG_ID = '046a36ac-e03c-4590-9257-bd6c9dba9ee8' | ||
| const USER_ID = '6aa76066-55ef-4238-ade6-0b32334a4097' | ||
| const PARALLEL = Number(process.env.REPRO_PARALLEL ?? 20) | ||
| const REQUEST_TIMEOUT_MS = Number(process.env.REPRO_TIMEOUT_MS ?? 2000) | ||
|
|
||
| const supabase = createClient(SUPABASE_URL, SERVICE_KEY, { | ||
| auth: { persistSession: false }, | ||
| global: { | ||
| fetch: (input, init) => fetch(input, { | ||
| ...init, | ||
| signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), | ||
| }), | ||
| }, | ||
| }) | ||
| const pool = new pg.Pool({ connectionString: DB_URL, max: 10 }) | ||
|
|
||
| function findPostgrestContainer(): string | null { | ||
| const listed = spawnSync('docker', ['ps', '--format', '{{.Names}}'], { encoding: 'utf8' }) | ||
| if (listed.status !== 0) | ||
| return null | ||
| const names = listed.stdout.split('\n').map(s => s.trim()).filter(Boolean) | ||
| return names.find(name => name.includes('rest') && name.includes('capgo')) | ||
| ?? names.find(name => name.includes('rest')) | ||
| ?? null | ||
| } | ||
|
|
||
| function dockerAction(action: 'pause' | 'unpause', container: string) { | ||
| const result = spawnSync('docker', [action, container], { encoding: 'utf8' }) | ||
| if (result.status !== 0) { | ||
| throw new Error(`docker ${action} ${container} failed: ${result.stderr || result.stdout}`) | ||
| } | ||
| } | ||
|
|
||
| async function ensureApp(appId: string) { | ||
| await pool.query( | ||
| `INSERT INTO public.apps (app_id, icon_url, name, owner_org, user_id) | ||
| VALUES ($1, '', $1, $2::uuid, $3::uuid) | ||
| ON CONFLICT (app_id) DO NOTHING`, | ||
| [appId, ORG_ID, USER_ID], | ||
| ) | ||
| } | ||
|
|
||
| async function cleanupApp(appId: string) { | ||
| await pool.query('DELETE FROM public.app_versions WHERE app_id = $1', [appId]) | ||
| await pool.query('DELETE FROM public.apps WHERE app_id = $1', [appId]) | ||
| } | ||
|
|
||
| async function postgrestCreateVersion(appId: string, version: string) { | ||
| try { | ||
| const { data, error } = await supabase.from('app_versions').upsert({ | ||
| app_id: appId, | ||
| name: version, | ||
| owner_org: ORG_ID, | ||
| }, { | ||
| onConflict: 'app_id,name', | ||
| }).select('id,name').single() | ||
| if (error || !data) | ||
| return { ok: false as const, error: error?.message ?? 'no data' } | ||
| return { ok: true as const } | ||
| } | ||
| catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error) | ||
| return { ok: false as const, error: message } | ||
| } | ||
| } | ||
|
|
||
| async function sqlCreateVersion(appId: string, version: string) { | ||
| const result = await pool.query( | ||
|
riderx marked this conversation as resolved.
|
||
| `INSERT INTO public.app_versions (app_id, name, owner_org) | ||
| VALUES ($1, $2, $3::uuid) | ||
| ON CONFLICT (name, app_id) DO UPDATE SET updated_at = now() | ||
| RETURNING id, name`, | ||
| [appId, version, ORG_ID], | ||
| ) | ||
| if (!result.rows[0]) | ||
| return { ok: false as const, error: 'no data' } | ||
| return { ok: true as const } | ||
| } | ||
|
riderx marked this conversation as resolved.
|
||
|
|
||
| async function runBatch( | ||
| label: string, | ||
| fn: (appId: string, version: string) => Promise<{ ok: boolean, error?: string }>, | ||
| ) { | ||
| const appId = `com.repro.shard5.${randomUUID().slice(0, 8)}` | ||
| await ensureApp(appId) | ||
| const results = await Promise.all( | ||
| Array.from({ length: PARALLEL }, (_, i) => { | ||
| const version = `1.0.0-repro-${i}-${randomUUID().slice(0, 8)}` | ||
| return fn(appId, version) | ||
| }), | ||
| ) | ||
| const failures = results.filter(r => !r.ok) | ||
| const errors = new Map<string, number>() | ||
| for (const failure of failures) { | ||
| const key = failure.error ?? 'unknown' | ||
| errors.set(key, (errors.get(key) ?? 0) + 1) | ||
| } | ||
| console.log(`\n[${label}] failures=${failures.length}/${PARALLEL} (${((failures.length / PARALLEL) * 100).toFixed(1)}%)`) | ||
| for (const [message, count] of [...errors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5)) | ||
| console.log(` - ${count}x ${message}`) | ||
| await cleanupApp(appId) | ||
| return failures.length | ||
| } | ||
|
|
||
| const container = findPostgrestContainer() | ||
| if (!container) { | ||
| console.error('Could not find local Postgrest docker container') | ||
| process.exit(2) | ||
| } | ||
| console.log(`Using PostgREST container: ${container}`) | ||
|
|
||
| let postgrestFailures = 0 | ||
| try { | ||
| dockerAction('pause', container) | ||
| await Bun.sleep(300) | ||
| postgrestFailures = await runBatch('PostgREST upsert while upstream paused (old path)', postgrestCreateVersion) | ||
| } | ||
| finally { | ||
| try { | ||
| dockerAction('unpause', container) | ||
| } | ||
| catch { | ||
| // already running | ||
| } | ||
| await Bun.sleep(300) | ||
| } | ||
|
riderx marked this conversation as resolved.
|
||
|
|
||
| const sqlFailures = await runBatch('Direct SQL upsert (fixed path)', sqlCreateVersion) | ||
|
|
||
| await pool.end() | ||
|
|
||
| if (postgrestFailures < PARALLEL) { | ||
| console.error('\nExpected PostgREST path to fail 100% while upstream is paused.') | ||
| process.exit(1) | ||
| } | ||
|
|
||
| if (sqlFailures > 0) { | ||
| console.error('\nSQL path failed — fix is incomplete.') | ||
| process.exit(1) | ||
| } | ||
|
|
||
| console.log('\nRepro locked: PostgREST path fails 100% on upstream outage; direct SQL stays green.') | ||
| process.exit(0) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.