From c7a5c7be1f5a773688f40b2cbbb13967f28be33e Mon Sep 17 00:00:00 2001 From: BilalG1 Date: Tue, 26 May 2026 12:59:44 -0700 Subject: [PATCH] fix(ci): repair two pre-existing test failures on dev (#1488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both failures are pre-existing on `dev` (confirmed by checking the most recent dev run [26434368271](https://github.com/hexclave/stack-auth/actions/runs/26434368271) — same two annotations, same line numbers). Neither is caused by an open PR. ## Failure 1 — \`apps/backend/src/lib/redirect-urls.test.tsx:75\` \`\`\` AssertionError: expected false to be true \`\`\` The \`withHostedHandlerEnv\` helper set/cleared only the \`STACK_*\`-prefixed env vars. CI's [e2e-custom-base-port-api-tests.yaml:21](.github/workflows/e2e-custom-base-port-api-tests.yaml#L21) sets only the \`HEXCLAVE_*\`-prefixed sibling (\`NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67\`), and the dual-read shim in [packages/stack-shared/src/utils/env.tsx#L53-L55](packages/stack-shared/src/utils/env.tsx#L53-L55) prefers \`HEXCLAVE_*\` over \`STACK_*\`: \`\`\`ts const hexclaveName = getHexclaveEnvVarName(name); let value = (hexclaveName ? process.env[hexclaveName] : undefined) ?? process.env[name]; \`\`\` So \`getEnvVariable(\"NEXT_PUBLIC_STACK_PORT_PREFIX\", \"81\")\` returned \`\"67\"\` instead of the test's \`\"92\"\`, the template resolved to port \`6709\` instead of \`9209\`, and the assertion at line 75 failed. **Fix:** mirror every \`STACK_*\` key managed by the helper to its \`HEXCLAVE_*\` sibling. The dual-read then resolves to the test-controlled value regardless of which key it checks first. ## Failure 2 — \`apps/backend/prisma/migrations/20260526060000_nullable_oauth_access_token_expires_at/tests/nullable-expires-at.ts:58\` \`\`\` PostgresError: null value in column \"updatedAt\" of relation \"OAuthAccessToken\" violates not-null constraint \`\`\` The migration test's raw INSERT omits \`\"updatedAt\"\`. The Prisma model declares \`updatedAt DateTime @updatedAt\` with no \`@default(now())\`, so the DB column is \`NOT NULL\` with no default — Prisma populates it at the ORM layer on insert, but this test bypasses Prisma via \`postgres.js\`. **Fix:** add the \`\"updatedAt\"\` column to the INSERT, set to \`NOW()\`, with a comment noting why raw SQL must set it explicitly. ## Verification - **Failure 1, before fix:** ran \`NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67 pnpm test run apps/backend/src/lib/redirect-urls.test.tsx\` locally → reproduces the exact line-75 assertion failure from CI. - **Failure 1, after fix:** same command → 33/33 pass. - **Failure 2:** local reproduction requires the migration-test postgres harness; the fix is one column matching how every other raw SQL insert in this repo handles \`@updatedAt\` fields. CI on this branch will confirm. --- ## Summary by cubic Fixes two failing tests on dev CI by aligning env var handling in redirect URL tests and by setting the missing updatedAt in a migration test. Restores green CI with no runtime changes. - **Bug Fixes** - Redirect URL tests: `withHostedHandlerEnv` now mirrors `STACK_*` values to their `HEXCLAVE_*` siblings and restores both, so `getEnvVariable` reads the test-controlled values even when CI sets only `HEXCLAVE_*` (e.g. `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX`). - Migration test: the raw insert into `OAuthAccessToken` now sets `"updatedAt" = NOW()` since `Prisma`’s `@updatedAt` isn’t applied when using `postgres.js` and the column is NOT NULL. Written for commit 75c8e4343ec9b6ac0bc2a4d1457e89d97b1173f6. Summary will update on new commits. Review in cubic --- .../tests/nullable-expires-at.ts | 9 +++- apps/backend/src/lib/redirect-urls.test.tsx | 47 +++++++++++-------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/apps/backend/prisma/migrations/20260526060000_nullable_oauth_access_token_expires_at/tests/nullable-expires-at.ts b/apps/backend/prisma/migrations/20260526060000_nullable_oauth_access_token_expires_at/tests/nullable-expires-at.ts index c127b775d..9a4e4f6b4 100644 --- a/apps/backend/prisma/migrations/20260526060000_nullable_oauth_access_token_expires_at/tests/nullable-expires-at.ts +++ b/apps/backend/prisma/migrations/20260526060000_nullable_oauth_access_token_expires_at/tests/nullable-expires-at.ts @@ -55,6 +55,9 @@ export const postMigration = async (sql: Sql, ctx: Awaited { callback: () => T, ): T => { const processEnv = Reflect.get(process, "env"); - const oldHostedHandlerUrlTemplate = Reflect.get(processEnv, "NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE"); - const oldHostedHandlerDomainSuffix = Reflect.get(processEnv, "NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX"); - const oldStackPortPrefix = Reflect.get(processEnv, "NEXT_PUBLIC_STACK_PORT_PREFIX"); - try { - for (const [key, value] of Object.entries({ - NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE: values.hostedHandlerUrlTemplate, - NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX: values.hostedHandlerDomainSuffix, - NEXT_PUBLIC_STACK_PORT_PREFIX: values.stackPortPrefix, - })) { + // Hexclave rebrand: getEnvVariable() in stack-shared/utils/env.tsx prefers the + // HEXCLAVE_*-prefixed sibling of each STACK_* var. CI sets only the HEXCLAVE_* + // variant (e.g. NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX), so writing only the STACK_* + // key here would be silently overridden. Mirror every STACK_* key to its + // HEXCLAVE_* sibling so both representations resolve to the same value. + const stackKeys = [ + "NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE", + "NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX", + "NEXT_PUBLIC_STACK_PORT_PREFIX", + ] as const; + const hexclaveOf = (name: string) => name.replace("STACK_", "HEXCLAVE_"); + const allKeys = [...stackKeys, ...stackKeys.map(hexclaveOf)]; + const oldValues = Object.fromEntries(allKeys.map((k) => [k, Reflect.get(processEnv, k)])); + const newValues: Record = { + NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE: values.hostedHandlerUrlTemplate, + NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX: values.hostedHandlerDomainSuffix, + NEXT_PUBLIC_STACK_PORT_PREFIX: values.stackPortPrefix, + }; + for (const stackKey of stackKeys) { + newValues[hexclaveOf(stackKey)] = newValues[stackKey]; + } + const applyValues = (entries: Record) => { + for (const [key, value] of Object.entries(entries)) { if (value == null) { Reflect.deleteProperty(processEnv, key); } else { Reflect.set(processEnv, key, value); } } + }; + try { + applyValues(newValues); return callback(); } finally { - for (const [key, value] of Object.entries({ - NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE: oldHostedHandlerUrlTemplate, - NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX: oldHostedHandlerDomainSuffix, - NEXT_PUBLIC_STACK_PORT_PREFIX: oldStackPortPrefix, - })) { - if (value == null) { - Reflect.deleteProperty(processEnv, key); - } else { - Reflect.set(processEnv, key, value); - } - } + applyValues(oldValues); } };