fix(12): satisfy CI fast-checks — lint unused vars, typed contract-test body, prettier
CI / changes (pull_request) Successful in 3s
CI / fast-checks (pull_request) Successful in 2m16s
CI / api (pull_request) Failing after 1m37s
CI / harness (pull_request) Failing after 1h3m45s
CI / security (pull_request) Failing after 11s
CI / gate (pull_request) Failing after 1s
CI / changes (pull_request) Successful in 3s
CI / fast-checks (pull_request) Successful in 2m16s
CI / api (pull_request) Failing after 1m37s
CI / harness (pull_request) Failing after 1h3m45s
CI / security (pull_request) Failing after 11s
CI / gate (pull_request) Failing after 1s
- Remove unused 'res'/'container' assignments (no-unused-vars) - setupClient.contract.test.ts: typed parseSentBody helper + non-async json mock (no-unsafe-*/require-await) - Prettier format 7 setup files Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
f485b38324
commit
a193bc8236
@@ -89,15 +89,21 @@ export async function oidcConfigFallbackMiddleware(c: Context, next: Next): Prom
|
||||
// container is already running, the in-process value is stale until restart.
|
||||
// A container restart is required to pick up any changed OIDC config values.
|
||||
if (key === 'oidc_issuer') {
|
||||
console.info('[oidcFallback] Writing OIDC_ISSUER from app_config — container restart required to update this value');
|
||||
console.info(
|
||||
'[oidcFallback] Writing OIDC_ISSUER from app_config — container restart required to update this value',
|
||||
);
|
||||
process.env.OIDC_ISSUER = row.value;
|
||||
}
|
||||
if (key === 'oidc_client_id') {
|
||||
console.info('[oidcFallback] Writing OIDC_CLIENT_ID from app_config — container restart required to update this value');
|
||||
console.info(
|
||||
'[oidcFallback] Writing OIDC_CLIENT_ID from app_config — container restart required to update this value',
|
||||
);
|
||||
process.env.OIDC_CLIENT_ID = row.value;
|
||||
}
|
||||
if (key === 'app_external_url') {
|
||||
console.info('[oidcFallback] Writing OIDC_AUTH_EXTERNAL_URL from app_config — container restart required to update this value');
|
||||
console.info(
|
||||
'[oidcFallback] Writing OIDC_AUTH_EXTERNAL_URL from app_config — container restart required to update this value',
|
||||
);
|
||||
process.env.OIDC_AUTH_EXTERNAL_URL = row.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,10 @@ setupRouter.post('/validate/db', async (c) => {
|
||||
await db.execute(sql`SELECT 1`);
|
||||
return c.json({ ok: true }, 200);
|
||||
} catch (err) {
|
||||
console.error('[setup/validate/db] DB round-trip failed:', err instanceof Error ? err.message : String(err));
|
||||
console.error(
|
||||
'[setup/validate/db] DB round-trip failed:',
|
||||
err instanceof Error ? err.message : String(err),
|
||||
);
|
||||
return c.json({ ok: false, error: 'DB unavailable' }, 503);
|
||||
}
|
||||
});
|
||||
@@ -217,7 +220,10 @@ setupRouter.post('/validate/vapid', async (c) => {
|
||||
const publicKey = process.env.VAPID_PUBLIC_KEY ?? '';
|
||||
|
||||
if (!privateKey || !publicKey) {
|
||||
return c.json({ ok: false, error: 'VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY env vars must be set' }, 400);
|
||||
return c.json(
|
||||
{ ok: false, error: 'VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY env vars must be set' },
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
// Gap 2: assert the operator-submitted public key matches the env public key BEFORE
|
||||
@@ -230,11 +236,14 @@ setupRouter.post('/validate/vapid', async (c) => {
|
||||
|
||||
const submittedPublicKey = submittedRow?.value;
|
||||
if (!submittedPublicKey || submittedPublicKey !== publicKey) {
|
||||
return c.json({
|
||||
ok: false,
|
||||
error:
|
||||
'VAPID public key does not match the configured key pair. Paste the exact VAPID_PUBLIC_KEY printed by `npm run generate-secrets`.',
|
||||
}, 400);
|
||||
return c.json(
|
||||
{
|
||||
ok: false,
|
||||
error:
|
||||
'VAPID public key does not match the configured key pair. Paste the exact VAPID_PUBLIC_KEY printed by `npm run generate-secrets`.',
|
||||
},
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -248,10 +257,13 @@ setupRouter.post('/validate/vapid', async (c) => {
|
||||
);
|
||||
return c.json({ ok: true }, 200);
|
||||
} catch (err) {
|
||||
return c.json({
|
||||
ok: false,
|
||||
error: err instanceof Error ? err.message : 'VAPID validation failed',
|
||||
}, 400);
|
||||
return c.json(
|
||||
{
|
||||
ok: false,
|
||||
error: err instanceof Error ? err.message : 'VAPID validation failed',
|
||||
},
|
||||
400,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -298,7 +310,9 @@ setupRouter.post('/credential', zValidator('json', credentialSchema, noEchoHook)
|
||||
)) as unknown as [{ count: string | number }[], unknown];
|
||||
const unclaimedCount = Number(countRows[0][0]?.count ?? 0);
|
||||
if (unclaimedCount > 0) {
|
||||
throw Object.assign(new Error('An unclaimed user already exists'), { code: 'DUPLICATE_UNCLAIMED' });
|
||||
throw Object.assign(new Error('An unclaimed user already exists'), {
|
||||
code: 'DUPLICATE_UNCLAIMED',
|
||||
});
|
||||
}
|
||||
|
||||
// Step 1: Assign color (first unused from palette, or round-robin fallback)
|
||||
@@ -322,20 +336,22 @@ setupRouter.post('/credential', zValidator('json', credentialSchema, noEchoHook)
|
||||
})
|
||||
.$returningId();
|
||||
|
||||
const [row] = await tx
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, inserted.id))
|
||||
.limit(1);
|
||||
const [row] = await tx.select().from(users).where(eq(users.id, inserted.id)).limit(1);
|
||||
|
||||
return row;
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof Error && (err as NodeJS.ErrnoException & { code?: string }).code === 'DUPLICATE_UNCLAIMED') {
|
||||
if (
|
||||
err instanceof Error &&
|
||||
(err as NodeJS.ErrnoException & { code?: string }).code === 'DUPLICATE_UNCLAIMED'
|
||||
) {
|
||||
// A concurrent request already created the unclaimed admin row
|
||||
return c.json({ error: 'Setup already in progress' }, 409);
|
||||
}
|
||||
console.error('[setup/POST /credential] Transaction error:', err instanceof Error ? err.message : String(err));
|
||||
console.error(
|
||||
'[setup/POST /credential] Transaction error:',
|
||||
err instanceof Error ? err.message : String(err),
|
||||
);
|
||||
return c.json({ error: 'Service unavailable' }, 503);
|
||||
}
|
||||
|
||||
@@ -353,12 +369,7 @@ setupRouter.post('/credential', zValidator('json', credentialSchema, noEchoHook)
|
||||
// Step 3: Validate + encrypt + store the credential via the shared helper (D-09)
|
||||
// The user row MUST exist before this call (Pitfall 5 — FK constraint).
|
||||
try {
|
||||
await validateEncryptAndStoreCredential(
|
||||
localUser.id,
|
||||
fastmailEmail,
|
||||
appPassword,
|
||||
'caldav',
|
||||
);
|
||||
await validateEncryptAndStoreCredential(localUser.id, fastmailEmail, appPassword, 'caldav');
|
||||
} catch (err) {
|
||||
// Roll back the local user insert on credential failure to avoid orphaned rows
|
||||
await db.delete(users).where(eq(users.id, localUser.id));
|
||||
|
||||
@@ -203,7 +203,8 @@ async function seedCredential(userId: number): Promise<void> {
|
||||
// Env and DB cleanup between tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const VAPID_PUBLIC_KEY = 'BKO9RLPqxNQ7GOLHsQ5kFXqMkfJBfElkd5h9ECQ0gRu7R5SJP6Ct5GkRuPxqY1u0UVY84_z7JGJsLhO-wChk_sE';
|
||||
const VAPID_PUBLIC_KEY =
|
||||
'BKO9RLPqxNQ7GOLHsQ5kFXqMkfJBfElkd5h9ECQ0gRu7R5SJP6Ct5GkRuPxqY1u0UVY84_z7JGJsLhO-wChk_sE';
|
||||
const VAPID_PRIVATE_KEY = 'QEkqsrxLqpv_ynpCECWFLYlOxCEGd-_O5u0AXzY_qEY';
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -223,9 +224,7 @@ beforeEach(async () => {
|
||||
afterEach(async () => {
|
||||
// Clean up seeded rows between tests
|
||||
await db.delete(memberCredentials);
|
||||
await db
|
||||
.delete(users)
|
||||
.where(eq(users.oidcIss, 'https://auth.test.setup'));
|
||||
await db.delete(users).where(eq(users.oidcIss, 'https://auth.test.setup'));
|
||||
// Delete local (wizard-created) users with null oidcIss
|
||||
// We identify them by displayName prefix for safety
|
||||
// Use raw delete of unclaimed users (the test-seeded ones)
|
||||
@@ -554,7 +553,7 @@ describe('POST /api/setup/credential', () => {
|
||||
displayName: 'OIDC User With claimed=false',
|
||||
color: '#4A90D9',
|
||||
isAdmin: false,
|
||||
claimed: false, // legacy/hypothetical — oidcIss is NOT NULL
|
||||
claimed: false, // legacy/hypothetical — oidcIss is NOT NULL
|
||||
});
|
||||
|
||||
mockValidateCredentialShouldThrow = false;
|
||||
@@ -632,9 +631,8 @@ describe('/api/setup/* — 423 when effectively configured (D-10 effective-confi
|
||||
process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY;
|
||||
|
||||
const app = await getApp();
|
||||
const res = await app.fetch(jsonRequest('GET', '/api/setup/status'));
|
||||
// status route always returns the status — but guard kicks in on mutation routes
|
||||
// Test a mutation route to confirm 423
|
||||
// The /status route always returns the status; the guard kicks in on mutation
|
||||
// routes. Test a mutation route to confirm 423.
|
||||
const completeRes = await app.fetch(jsonRequest('POST', '/api/setup/complete'));
|
||||
expect(completeRes.status).toBe(423);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user