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

- 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:
Lucas Berger
2026-06-16 16:53:14 -04:00
co-authored by Claude Opus 4.8
parent f485b38324
commit a193bc8236
7 changed files with 172 additions and 132 deletions
+12 -4
View File
@@ -636,7 +636,9 @@ export async function validateSetupDb(): Promise<void> {
if (res.status === 423) throw new SetupAlreadyLockedError();
if (!res.ok) {
throw new Error('Cannot reach the database. Check DB_HOST, DB_PORT, DB_USER, DB_PASSWORD in your Docker environment and try again.');
throw new Error(
'Cannot reach the database. Check DB_HOST, DB_PORT, DB_USER, DB_PASSWORD in your Docker environment and try again.',
);
}
}
@@ -650,7 +652,9 @@ export async function validateSetupOidc(): Promise<void> {
if (res.status === 423) throw new SetupAlreadyLockedError();
if (!res.ok) {
throw new Error('OIDC discovery failed. Check the issuer URL and that Authelia is reachable from the server.');
throw new Error(
'OIDC discovery failed. Check the issuer URL and that Authelia is reachable from the server.',
);
}
}
@@ -663,7 +667,9 @@ export async function validateSetupVapid(): Promise<void> {
if (res.status === 423) throw new SetupAlreadyLockedError();
if (!res.ok) {
throw new Error('VAPID validation failed. Check that your VAPID keys were generated with `npm run generate-secrets`.');
throw new Error(
'VAPID validation failed. Check that your VAPID keys were generated with `npm run generate-secrets`.',
);
}
}
@@ -686,7 +692,9 @@ export async function postSetupCredential(payload: SetupCredentialPayload): Prom
if (res.status === 423) throw new SetupAlreadyLockedError();
if (!res.ok) {
throw new Error("Invalid password — CalDAV validation failed. Check the scope is 'Calendars & Contacts (CalDAV)' and try again.");
throw new Error(
"Invalid password — CalDAV validation failed. Check the scope is 'Calendars & Contacts (CalDAV)' and try again.",
);
}
}
+41 -26
View File
@@ -20,7 +20,12 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { postSetupConfig, postSetupCredential, type SetupConfigPayload, type SetupCredentialPayload } from './client.js';
import {
postSetupConfig,
postSetupCredential,
type SetupConfigPayload,
type SetupCredentialPayload,
} from './client.js';
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -28,11 +33,21 @@ function mockFetchResponse(body: unknown, status = 200): Response {
return {
ok: status >= 200 && status < 300,
status,
json: async () => body,
clone: function () { return this as unknown as Response; },
json: () => Promise.resolve(body),
clone: function () {
return this as unknown as Response;
},
} as unknown as Response;
}
// Extract the JSON body sent on the first fetch call as a typed record, so the
// strict typed-lint rules (no-unsafe-*) are satisfied without scattering casts.
function parseSentBody(spy: ReturnType<typeof vi.fn>): Record<string, unknown> {
const calls = spy.mock.calls as unknown as Array<[unknown, RequestInit]>;
const init = calls[0][1];
return JSON.parse(init.body as string) as Record<string, unknown>;
}
// Build a canonical camelCase payload using the contract type.
// If SetupConfigPayload still has snake_case fields, TypeScript will error here
// (the interface and this test agree on the camelCase contract).
@@ -61,7 +76,7 @@ describe('postSetupConfig — payload contract (BUG 1 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupConfig(VALID_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(body).toHaveProperty('oidcIssuer', 'https://auth.example.com');
expect(body).not.toHaveProperty('oidc_issuer');
});
@@ -70,7 +85,7 @@ describe('postSetupConfig — payload contract (BUG 1 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupConfig(VALID_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(body).toHaveProperty('oidcClientId', 'familysync');
expect(body).not.toHaveProperty('oidc_client_id');
});
@@ -79,7 +94,7 @@ describe('postSetupConfig — payload contract (BUG 1 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupConfig(VALID_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(body).toHaveProperty('vapidPublicKey', 'BH_example_public_key');
expect(body).not.toHaveProperty('vapid_public_key');
});
@@ -88,7 +103,7 @@ describe('postSetupConfig — payload contract (BUG 1 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupConfig(VALID_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(body).toHaveProperty('appExternalUrl', 'https://familysync.example.com');
expect(body).not.toHaveProperty('app_url');
});
@@ -97,7 +112,7 @@ describe('postSetupConfig — payload contract (BUG 1 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupConfig(VALID_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(Object.keys(body).sort()).toEqual(
['appExternalUrl', 'oidcClientId', 'oidcIssuer', 'vapidPublicKey'].sort(),
);
@@ -125,15 +140,17 @@ describe('postSetupConfig — error rendering (BUG 2 regression)', () => {
error: {
name: 'ZodError',
issues: [
{ code: 'invalid_string', message: 'oidcIssuer must be an https URL', path: ['oidcIssuer'] },
{
code: 'invalid_string',
message: 'oidcIssuer must be an https URL',
path: ['oidcIssuer'],
},
],
},
};
fetchSpy.mockResolvedValueOnce(mockFetchResponse(zodErrorBody, 400));
await expect(
postSetupConfig(VALID_PAYLOAD),
).rejects.toSatisfy((err: Error) => {
await expect(postSetupConfig(VALID_PAYLOAD)).rejects.toSatisfy((err: Error) => {
// The error message must NOT be "[object Object]"
expect(err.message).not.toBe('[object Object]');
// The error message must be a non-empty string
@@ -149,31 +166,29 @@ describe('postSetupConfig — error rendering (BUG 2 regression)', () => {
error: {
name: 'ZodError',
issues: [
{ code: 'invalid_string', message: 'oidcIssuer must be an https URL', path: ['oidcIssuer'] },
{
code: 'invalid_string',
message: 'oidcIssuer must be an https URL',
path: ['oidcIssuer'],
},
],
},
};
fetchSpy.mockResolvedValueOnce(mockFetchResponse(zodErrorBody, 400));
await expect(
postSetupConfig(VALID_PAYLOAD),
).rejects.toThrow('oidcIssuer must be an https URL');
await expect(postSetupConfig(VALID_PAYLOAD)).rejects.toThrow('oidcIssuer must be an https URL');
});
it('throws a fallback status error when error body is empty', async () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse({}, 400));
await expect(
postSetupConfig(VALID_PAYLOAD),
).rejects.toThrow('400');
await expect(postSetupConfig(VALID_PAYLOAD)).rejects.toThrow('400');
});
it('returns void on 200 success', async () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await expect(
postSetupConfig(VALID_PAYLOAD),
).resolves.toBeUndefined();
await expect(postSetupConfig(VALID_PAYLOAD)).resolves.toBeUndefined();
});
});
@@ -202,7 +217,7 @@ describe('postSetupCredential — payload contract (CR-01 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupCredential(VALID_CREDENTIAL_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(Object.keys(body).sort()).toEqual(['appPassword', 'fastmailEmail'].sort());
});
@@ -210,7 +225,7 @@ describe('postSetupCredential — payload contract (CR-01 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupCredential(VALID_CREDENTIAL_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(body).not.toHaveProperty('providerType');
});
@@ -218,7 +233,7 @@ describe('postSetupCredential — payload contract (CR-01 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupCredential(VALID_CREDENTIAL_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(body).toHaveProperty('fastmailEmail', 'user@fastmail.com');
});
@@ -226,7 +241,7 @@ describe('postSetupCredential — payload contract (CR-01 regression)', () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupCredential(VALID_CREDENTIAL_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
const body = parseSentBody(fetchSpy);
expect(body).toHaveProperty('appPassword', 'secret-app-password');
});
});