feat(11-01): PATCH /api/threads/:id/candidates/reorder route + tests
- Import reorderCandidatesSchema and reorderCandidates into threads route
- Add PATCH /:id/candidates/reorder route with Zod validation
- Returns 200 + { success: true } on active thread, 400 on resolved thread
- Add 5 route tests: success, order persists, resolved guard, empty array, missing field
This commit is contained in:
@@ -234,6 +234,119 @@ describe("Thread Routes", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("PATCH /api/threads/:id/candidates/reorder", () => {
|
||||
it("with valid orderedIds returns 200 + { success: true }", async () => {
|
||||
const thread = await createThreadViaAPI(app, "Reorder Test");
|
||||
const c1 = await createCandidateViaAPI(app, thread.id, {
|
||||
name: "Candidate A",
|
||||
categoryId: 1,
|
||||
});
|
||||
const c2 = await createCandidateViaAPI(app, thread.id, {
|
||||
name: "Candidate B",
|
||||
categoryId: 1,
|
||||
});
|
||||
|
||||
const res = await app.request(
|
||||
`/api/threads/${thread.id}/candidates/reorder`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ orderedIds: [c2.id, c1.id] }),
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.success).toBe(true);
|
||||
});
|
||||
|
||||
it("after PATCH reorder, GET thread returns candidates in the new order", async () => {
|
||||
const thread = await createThreadViaAPI(app, "Order Verify");
|
||||
const c1 = await createCandidateViaAPI(app, thread.id, {
|
||||
name: "First",
|
||||
categoryId: 1,
|
||||
});
|
||||
const c2 = await createCandidateViaAPI(app, thread.id, {
|
||||
name: "Second",
|
||||
categoryId: 1,
|
||||
});
|
||||
const c3 = await createCandidateViaAPI(app, thread.id, {
|
||||
name: "Third",
|
||||
categoryId: 1,
|
||||
});
|
||||
|
||||
// Reverse the order
|
||||
await app.request(`/api/threads/${thread.id}/candidates/reorder`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ orderedIds: [c3.id, c2.id, c1.id] }),
|
||||
});
|
||||
|
||||
const res = await app.request(`/api/threads/${thread.id}`);
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.candidates[0].id).toBe(c3.id);
|
||||
expect(body.candidates[1].id).toBe(c2.id);
|
||||
expect(body.candidates[2].id).toBe(c1.id);
|
||||
});
|
||||
|
||||
it("on a resolved thread returns 400", async () => {
|
||||
const thread = await createThreadViaAPI(app, "Resolved Thread");
|
||||
const candidate = await createCandidateViaAPI(app, thread.id, {
|
||||
name: "Winner",
|
||||
categoryId: 1,
|
||||
});
|
||||
|
||||
// Resolve the thread first
|
||||
await app.request(`/api/threads/${thread.id}/resolve`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ candidateId: candidate.id }),
|
||||
});
|
||||
|
||||
const res = await app.request(
|
||||
`/api/threads/${thread.id}/candidates/reorder`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ orderedIds: [candidate.id] }),
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("with invalid body (empty orderedIds) returns 400", async () => {
|
||||
const thread = await createThreadViaAPI(app, "Invalid Body");
|
||||
|
||||
const res = await app.request(
|
||||
`/api/threads/${thread.id}/candidates/reorder`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ orderedIds: [] }),
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("with missing orderedIds field returns 400", async () => {
|
||||
const thread = await createThreadViaAPI(app, "Missing Field");
|
||||
|
||||
const res = await app.request(
|
||||
`/api/threads/${thread.id}/candidates/reorder`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({}),
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/threads/:id/resolve", () => {
|
||||
it("with valid candidateId returns 200 + created item", async () => {
|
||||
const thread = await createThreadViaAPI(app, "Tent Decision");
|
||||
|
||||
Reference in New Issue
Block a user