Import normalizer: offline tool to normalize the raw archive spreadsheets #663

Merged
marcel merged 172 commits from docs/import-migration into main 2026-05-28 15:05:51 +02:00
Showing only changes of commit 22603a4b04 - Show all commits

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 });
});
});