fix(12): CR-01 remove extraneous providerType from postSetupCredential wire body

The server's credentialSchema does not declare providerType; it was being
silently stripped by Zod. Remove it from the request body and add a
contract test suite asserting the exact wire keys sent, mirroring the
existing BUG-1 tests for postSetupConfig.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lucas Berger
2026-06-15 16:14:18 -04:00
co-authored by Claude Sonnet 4.6
parent ef3e9810a9
commit 066b69f2be
2 changed files with 56 additions and 2 deletions
+1 -1
View File
@@ -671,9 +671,9 @@ export async function postSetupCredential(payload: SetupCredentialPayload): Prom
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
providerType: 'caldav',
fastmailEmail: payload.fastmailEmail,
appPassword: payload.appPassword,
// providerType omitted — not in credentialSchema; server hard-codes 'caldav'
}),
});
+55 -1
View File
@@ -20,7 +20,7 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { postSetupConfig, type SetupConfigPayload } from './client.js';
import { postSetupConfig, postSetupCredential, type SetupConfigPayload, type SetupCredentialPayload } from './client.js';
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -176,3 +176,57 @@ describe('postSetupConfig — error rendering (BUG 2 regression)', () => {
).resolves.toBeUndefined();
});
});
// ── CR-01: postSetupCredential wire body contract ─────────────────────────────
// Guards that postSetupCredential sends exactly { fastmailEmail, appPassword }
// matching the server's credentialSchema — no extra fields (e.g. providerType).
describe('postSetupCredential — payload contract (CR-01 regression)', () => {
let fetchSpy: ReturnType<typeof vi.fn>;
const VALID_CREDENTIAL_PAYLOAD: SetupCredentialPayload = {
fastmailEmail: 'user@fastmail.com',
appPassword: 'secret-app-password',
};
beforeEach(() => {
fetchSpy = vi.fn();
vi.stubGlobal('fetch', fetchSpy);
});
afterEach(() => {
vi.unstubAllGlobals();
});
it('sends exactly the 2 canonical fields matching credentialSchema', async () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupCredential(VALID_CREDENTIAL_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
expect(Object.keys(body).sort()).toEqual(['appPassword', 'fastmailEmail'].sort());
});
it('does NOT send providerType (not in credentialSchema; server hard-codes caldav)', async () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupCredential(VALID_CREDENTIAL_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
expect(body).not.toHaveProperty('providerType');
});
it('sends fastmailEmail matching the payload value', async () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupCredential(VALID_CREDENTIAL_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
expect(body).toHaveProperty('fastmailEmail', 'user@fastmail.com');
});
it('sends appPassword matching the payload value', async () => {
fetchSpy.mockResolvedValueOnce(mockFetchResponse(null, 200));
await postSetupCredential(VALID_CREDENTIAL_PAYLOAD);
const body = JSON.parse(fetchSpy.mock.calls[0][1].body as string);
expect(body).toHaveProperty('appPassword', 'secret-app-password');
});
});