feat(api): add stock-purchase attachments delete route
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
a72c5ca0a7
commit
ccf04bc39f
2 changed files with 104 additions and 0 deletions
|
|
@ -0,0 +1,63 @@
|
|||
// @vitest-environment node
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/lib/server/stock-purchases-service", () => ({
|
||||
removeStockPurchaseAttachment: vi.fn(),
|
||||
}));
|
||||
|
||||
import { DELETE } from "./route";
|
||||
import { removeStockPurchaseAttachment } from "@/lib/server/stock-purchases-service";
|
||||
|
||||
const rmMock = removeStockPurchaseAttachment as unknown as ReturnType<
|
||||
typeof vi.fn
|
||||
>;
|
||||
|
||||
describe("DELETE /api/v1/stock-purchases/[id]/attachments/[filename]", () => {
|
||||
beforeEach(() => {
|
||||
rmMock.mockReset();
|
||||
});
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns 200 + remaining attachments on success", async () => {
|
||||
rmMock.mockResolvedValueOnce({ attachments: ["keep.pdf"] });
|
||||
const res = await DELETE(
|
||||
new Request("http://localhost/api/v1/stock-purchases/sp-1/attachments/drop.pdf", {
|
||||
method: "DELETE",
|
||||
}),
|
||||
{ params: Promise.resolve({ id: "sp-1", filename: "drop.pdf" }) },
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body).toEqual({ ok: true, attachments: ["keep.pdf"] });
|
||||
expect(rmMock).toHaveBeenCalledWith({
|
||||
purchaseId: "sp-1",
|
||||
token: "drop.pdf",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 404 when the token is not in the array", async () => {
|
||||
rmMock.mockRejectedValueOnce(new Error("Attachment not found: nope.pdf"));
|
||||
const res = await DELETE(
|
||||
new Request("http://localhost/api/v1/stock-purchases/sp-1/attachments/nope.pdf", {
|
||||
method: "DELETE",
|
||||
}),
|
||||
{ params: Promise.resolve({ id: "sp-1", filename: "nope.pdf" }) },
|
||||
);
|
||||
expect(res.status).toBe(404);
|
||||
const body = await res.json();
|
||||
expect(body.error).toMatch(/not found/i);
|
||||
});
|
||||
|
||||
it("returns 500 on other service errors", async () => {
|
||||
rmMock.mockRejectedValueOnce(new Error("PB unreachable"));
|
||||
const res = await DELETE(
|
||||
new Request("http://localhost/api/v1/stock-purchases/sp-1/attachments/drop.pdf", {
|
||||
method: "DELETE",
|
||||
}),
|
||||
{ params: Promise.resolve({ id: "sp-1", filename: "drop.pdf" }) },
|
||||
);
|
||||
expect(res.status).toBe(500);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { removeStockPurchaseAttachment } from "@/lib/server/stock-purchases-service";
|
||||
|
||||
/**
|
||||
* DELETE /api/v1/stock-purchases/[id]/attachments/[filename]
|
||||
*
|
||||
* Remove a single additional attachment token from a stock purchase.
|
||||
* Mirrors `/api/v1/purchase-orders/[id]/attachments/[filename]`. The
|
||||
* `filename` segment is the PB-stored token (the same string that
|
||||
* appears in `additional_attachments[]`), not the operator's original
|
||||
* filename. PB doesn't expose a per-file delete on a multi-file field
|
||||
* via the SDK, so the underlying helper filters the array client-side
|
||||
* and writes the rest back; the on-disk file is left in place (same
|
||||
* limitation as the PO route).
|
||||
*
|
||||
* Response:
|
||||
* 200 — { ok: true, attachments: [remaining tokens] }
|
||||
* 404 — { error: "Attachment not found: <token>" }
|
||||
* 500 — { error } on PB or service failure
|
||||
*/
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function DELETE(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; filename: string }> },
|
||||
) {
|
||||
const { id: purchaseId, filename: token } = await params;
|
||||
try {
|
||||
const { attachments } = await removeStockPurchaseAttachment({
|
||||
purchaseId,
|
||||
token,
|
||||
});
|
||||
return NextResponse.json({ ok: true, attachments }, { status: 200 });
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : "Delete failed";
|
||||
const status = /not found/i.test(msg) ? 404 : 500;
|
||||
return NextResponse.json({ error: msg }, { status });
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue