diff --git a/frontend/src/routes/persons/review/page.server.spec.ts b/frontend/src/routes/persons/review/page.server.spec.ts index 85898add..93260ddc 100644 --- a/frontend/src/routes/persons/review/page.server.spec.ts +++ b/frontend/src/routes/persons/review/page.server.spec.ts @@ -45,8 +45,39 @@ function runAction( } as any); } -describe('persons/review confirm action — WRITE_ALL guard', () => { - it('returns fail(403) when the user lacks WRITE_ALL', async () => { +describe('persons/review confirm action', () => { + 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 fd = new FormData(); 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', () => { - it('returns fail(403) when the user lacks WRITE_ALL', async () => { +describe('persons/review delete action', () => { + 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 fd = new FormData(); 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', () => { - it('returns fail(403) when the user lacks WRITE_ALL', async () => { +describe('persons/review merge action', () => { + 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 fd = new FormData(); 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', () => { - it('returns fail(403) when the user lacks WRITE_ALL', async () => { +describe('persons/review rename action', () => { + 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 fd = new FormData(); fd.append('id', 'p-1'); @@ -98,18 +250,3 @@ describe('persons/review rename action — WRITE_ALL guard', () => { 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 }); - }); -});