test(persons): cover review form actions in server spec

Extend the WRITE_ALL-guard spec to a full matrix for each of the four
form actions (confirm, delete, merge, rename): happy path (backend 200),
required-field validation where applicable (merge without
targetPersonId, rename without lastName), backend 403, backend 404,
and the unauthorized guard from the previous commit. Mirrors the
shape of frontend/src/routes/persons/page.server.spec.ts.

18 tests, all green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-28 10:39:43 +02:00
parent 461a8b125d
commit 22603a4b04

View File

@@ -45,8 +45,39 @@ function runAction(
} as any); } as any);
} }
describe('persons/review confirm action — WRITE_ALL guard', () => { describe('persons/review confirm action', () => {
it('returns fail(403) when the user lacks WRITE_ALL', async () => { it('returns { success: true } on backend 200', async () => {
const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData();
fd.append('id', 'p-1');
const result = await runAction(actions.confirm, fd, writer);
expect(apiCall).toHaveBeenCalledOnce();
expect(result).toEqual({ success: true });
});
it('returns fail(403) on backend 403', async () => {
mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
const fd = new FormData();
fd.append('id', 'p-1');
const result = await runAction(actions.confirm, fd, writer);
expect(result).toMatchObject({ status: 403 });
});
it('returns fail(404) on backend 404', async () => {
mockApi({ ok: false, status: 404, error: { code: 'NOT_FOUND' } });
const fd = new FormData();
fd.append('id', 'p-missing');
const result = await runAction(actions.confirm, fd, writer);
expect(result).toMatchObject({ status: 404 });
});
it('returns fail(403) when the user lacks WRITE_ALL (server-side guard)', async () => {
const apiCall = mockApi({ ok: true, status: 200 }); const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData(); const fd = new FormData();
fd.append('id', 'p-1'); fd.append('id', 'p-1');
@@ -58,8 +89,39 @@ describe('persons/review confirm action — WRITE_ALL guard', () => {
}); });
}); });
describe('persons/review delete action — WRITE_ALL guard', () => { describe('persons/review delete action', () => {
it('returns fail(403) when the user lacks WRITE_ALL', async () => { it('returns { success: true } on backend 200', async () => {
const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData();
fd.append('id', 'p-1');
const result = await runAction(actions.delete, fd, writer);
expect(apiCall).toHaveBeenCalledOnce();
expect(result).toEqual({ success: true });
});
it('returns fail(403) on backend 403', async () => {
mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
const fd = new FormData();
fd.append('id', 'p-1');
const result = await runAction(actions.delete, fd, writer);
expect(result).toMatchObject({ status: 403 });
});
it('returns fail(404) on backend 404', async () => {
mockApi({ ok: false, status: 404, error: { code: 'NOT_FOUND' } });
const fd = new FormData();
fd.append('id', 'p-missing');
const result = await runAction(actions.delete, fd, writer);
expect(result).toMatchObject({ status: 404 });
});
it('returns fail(403) when the user lacks WRITE_ALL (server-side guard)', async () => {
const apiCall = mockApi({ ok: true, status: 200 }); const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData(); const fd = new FormData();
fd.append('id', 'p-1'); fd.append('id', 'p-1');
@@ -71,8 +133,53 @@ describe('persons/review delete action — WRITE_ALL guard', () => {
}); });
}); });
describe('persons/review merge action — WRITE_ALL guard', () => { describe('persons/review merge action', () => {
it('returns fail(403) when the user lacks WRITE_ALL', async () => { it('returns { success: true } on backend 200', async () => {
const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData();
fd.append('id', 'p-1');
fd.append('targetPersonId', 'p-2');
const result = await runAction(actions.merge, fd, writer);
expect(apiCall).toHaveBeenCalledOnce();
expect(result).toEqual({ success: true });
});
it('returns fail(400) when targetPersonId is missing', async () => {
const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData();
fd.append('id', 'p-1');
const result = await runAction(actions.merge, fd, writer);
expect(apiCall).not.toHaveBeenCalled();
expect(result).toMatchObject({ status: 400 });
});
it('returns fail(403) on backend 403', async () => {
mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
const fd = new FormData();
fd.append('id', 'p-1');
fd.append('targetPersonId', 'p-2');
const result = await runAction(actions.merge, fd, writer);
expect(result).toMatchObject({ status: 403 });
});
it('returns fail(404) on backend 404', async () => {
mockApi({ ok: false, status: 404, error: { code: 'NOT_FOUND' } });
const fd = new FormData();
fd.append('id', 'p-1');
fd.append('targetPersonId', 'p-missing');
const result = await runAction(actions.merge, fd, writer);
expect(result).toMatchObject({ status: 404 });
});
it('returns fail(403) when the user lacks WRITE_ALL (server-side guard)', async () => {
const apiCall = mockApi({ ok: true, status: 200 }); const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData(); const fd = new FormData();
fd.append('id', 'p-1'); fd.append('id', 'p-1');
@@ -85,8 +192,53 @@ describe('persons/review merge action — WRITE_ALL guard', () => {
}); });
}); });
describe('persons/review rename action — WRITE_ALL guard', () => { describe('persons/review rename action', () => {
it('returns fail(403) when the user lacks WRITE_ALL', async () => { it('returns { success: true } on backend 200', async () => {
const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData();
fd.append('id', 'p-1');
fd.append('lastName', 'Smith');
const result = await runAction(actions.rename, fd, writer);
expect(apiCall).toHaveBeenCalledOnce();
expect(result).toEqual({ success: true });
});
it('returns fail(400) when lastName is missing', async () => {
const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData();
fd.append('id', 'p-1');
const result = await runAction(actions.rename, fd, writer);
expect(apiCall).not.toHaveBeenCalled();
expect(result).toMatchObject({ status: 400 });
});
it('returns fail(403) on backend 403', async () => {
mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
const fd = new FormData();
fd.append('id', 'p-1');
fd.append('lastName', 'Smith');
const result = await runAction(actions.rename, fd, writer);
expect(result).toMatchObject({ status: 403 });
});
it('returns fail(404) on backend 404', async () => {
mockApi({ ok: false, status: 404, error: { code: 'NOT_FOUND' } });
const fd = new FormData();
fd.append('id', 'p-missing');
fd.append('lastName', 'Smith');
const result = await runAction(actions.rename, fd, writer);
expect(result).toMatchObject({ status: 404 });
});
it('returns fail(403) when the user lacks WRITE_ALL (server-side guard)', async () => {
const apiCall = mockApi({ ok: true, status: 200 }); const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData(); const fd = new FormData();
fd.append('id', 'p-1'); fd.append('id', 'p-1');
@@ -98,18 +250,3 @@ describe('persons/review rename action — WRITE_ALL guard', () => {
expect(result).toMatchObject({ status: 403 }); expect(result).toMatchObject({ status: 403 });
}); });
}); });
// Sanity: writers still pass through (no 403 from the guard). Full happy-path coverage lives
// in the action-by-action describe blocks added later.
describe('persons/review confirm action — writer passes guard', () => {
it('does NOT short-circuit with 403 when the user has WRITE_ALL', async () => {
const apiCall = mockApi({ ok: true, status: 200 });
const fd = new FormData();
fd.append('id', 'p-1');
const result = await runAction(actions.confirm, fd, writer);
expect(apiCall).toHaveBeenCalled();
expect(result).toEqual({ success: true });
});
});