feat(collection-confirm): add POST /api/v1/order-items/[id]/cost route

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
SJCoetz 2026-08-03 01:01:30 +02:00
parent e860611980
commit fada6f155f
2 changed files with 91 additions and 0 deletions

View file

@ -0,0 +1,65 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { NextRequest } from "next/server";
vi.mock("@/lib/server/collection-confirm-service", () => ({
updateOrderItemCost: vi.fn(),
}));
import { POST } from "./route";
import { updateOrderItemCost } from "@/lib/server/collection-confirm-service";
function makePostRequest(id: string, body: unknown) {
return new NextRequest(`http://localhost/api/v1/order-items/${id}/cost`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
}
describe("POST /api/v1/order-items/[id]/cost", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("calls updateOrderItemCost with the route id and body, returns the updated row", async () => {
const updatedRow = { id: "row-1", cost_price: 50 };
vi.mocked(updateOrderItemCost).mockResolvedValue(updatedRow as never);
const request = makePostRequest("row-1", {
cost_price: 50,
actor: { id: "u-1", name: "sj" },
});
const response = await POST(request, {
params: Promise.resolve({ id: "row-1" }),
});
const body = await response.json();
expect(updateOrderItemCost).toHaveBeenCalledWith("row-1", {
cost_price: 50,
actor: { id: "u-1", name: "sj" },
});
expect(response.status).toBe(200);
expect(body).toEqual(updatedRow);
});
it("returns a 500 with a generic error body and logs the original error", async () => {
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
vi.mocked(updateOrderItemCost).mockRejectedValue(new Error("PB down"));
const request = makePostRequest("row-1", {
cost_price: 50,
actor: { id: "u-1", name: "sj" },
});
const response = await POST(request, {
params: Promise.resolve({ id: "row-1" }),
});
expect(response.status).toBe(500);
const body = await response.json();
expect(body).toEqual({ error: "internal error" });
expect(errorSpy).toHaveBeenCalledWith(
"[POST /api/v1/order-items/[id]/cost]",
expect.any(Error),
);
errorSpy.mockRestore();
});
});

View file

@ -0,0 +1,26 @@
import { NextResponse } from "next/server";
import { updateOrderItemCost } from "@/lib/server/collection-confirm-service";
/**
* POST /api/v1/order-items/[id]/cost
*
* Thin wrapper around `updateOrderItemCost(id, { cost_price, actor })`
* from the collection-confirm service layer. The service is responsible
* for the prev-vs-new cost comparison and the audit-note append; this
* route just plumbs the HTTP body through and reports failures with a
* generic 500 (the real error is logged server-side).
*/
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> },
): Promise<NextResponse> {
try {
const { id } = await params;
const body = await request.json();
const updated = await updateOrderItemCost(id, body);
return NextResponse.json(updated);
} catch (error) {
console.error("[POST /api/v1/order-items/[id]/cost]", error);
return NextResponse.json({ error: "internal error" }, { status: 500 });
}
}