import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { PATCH } from "@/app/api/errors/[error_id]/status/route";
import { GET } from "@/app/api/errors/route";
import { resetFixtureErrorReportOverrides } from "@/lib/db/fixture-error-report-overrides";
import type { ErrorReportItem, ListResponse } from "@/lib/types";
/**
* `GET /errors` → `PATCH /errors/{error_id}/status` → `GET /errors`(`USE_FIXTURES=true`。指示書AW 段階4-4-3)。
*
* ★★なぜ別ファイルなのか★★ — `lib/db/index.ts` の `db` はモジュール読み込み時に束縛されるので、
* 同じファイルで true/false の両方は試せない(`app/api/prechecks/[id]/status/__tests__/route.fixtures.test.ts` と同じ)。
*
* fixtures 版は `lib/fixtures/error-reports.ts` を書き換えず、モジュール内の上書き表を持つ。
* ★テストごとに `resetFixtureErrorReportOverrides()` で空に戻す★。
*/
vi.mock("@/lib/db/use-fixtures", () => ({ USE_FIXTURES: true }));
vi.mock("@/lib/logger", async (importOriginal) => {
const actual = await importOriginal<typeof import("@/lib/logger")>();
return { ...actual, logEvent: vi.fn() };
});
const NOW_ISO = "2026-10-06T08:15:00+09:00";
beforeEach(() => {
resetFixtureErrorReportOverrides();
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date(NOW_ISO));
});
afterEach(() => {
vi.useRealTimers();
resetFixtureErrorReportOverrides();
});
async function list(qs = ""): Promise<ListResponse<ErrorReportItem>> {
const res = await GET(new Request(`http://localhost/api/errors${qs ? `?${qs}` : ""}`));
expect(res.status).toBe(200);
return (await res.json()) as ListResponse<ErrorReportItem>;
}
async function patch(id: string, body: unknown): Promise<Response> {
return PATCH(
new Request(`http://localhost/api/errors/${encodeURIComponent(id)}/status`, {
method: "PATCH",
body: JSON.stringify(body),
}),
{ params: Promise.resolve({ error_id: id }) }
);
}
describe("GET /errors(USE_FIXTURES=true)", () => {
it("★既定で FX-ERR-03 が出ない(10行)・include_resolved=true で11行★", async () => {
const def = await list();
expect(def.meta.total).toBe(10);
expect(def.items.map((i) => i.id)).not.toContain("FX-ERR-03");
const all = await list("include_resolved=true");
expect(all.meta.total).toBe(11);
expect(all.items).toHaveLength(11);
});
it("error_type=secur → FX-ERR-08 だけ", async () => {
expect((await list("error_type=secur")).items.map((i) => i.id)).toEqual(["FX-ERR-08"]);
});
});
describe("PATCH → GET(USE_FIXTURES=true)", () => {
it("★事前チェック以外の行(FX-ERR-08: robot_id NULL)に書け、GET に反映される★", async () => {
const res = await patch("FX-ERR-08", { status: "in_progress", note: "警備会社に確認" });
expect(res.status).toBe(200);
expect((await res.json()) as ErrorReportItem).toMatchObject({
id: "FX-ERR-08",
resolution_status: "in_progress",
responded_at: NOW_ISO,
note: "警備会社に確認",
});
const item = (await list()).items.find((i) => i.id === "FX-ERR-08");
expect(item).toMatchObject({ resolution_status: "in_progress", responded_at: NOW_ISO, note: "警備会社に確認" });
});
it("解消にすると既定の一覧から外れ、「解消済みも表示」では出る", async () => {
expect((await patch("FX-ERR-04", { status: "resolved" })).status).toBe(200);
expect((await list()).items.map((i) => i.id)).not.toContain("FX-ERR-04");
const all = await list("include_resolved=true");
expect(all.items.find((i) => i.id === "FX-ERR-04")).toMatchObject({
resolution_status: "resolved",
responded_at: NOW_ISO,
resolved_at: NOW_ISO,
});
});
it("解消 → 未解消で日時を消す(FX-ERR-03)", async () => {
const res = await patch("FX-ERR-03", { status: "unresolved" });
expect((await res.json()) as ErrorReportItem).toMatchObject({
resolution_status: "unresolved",
responded_at: null,
resolved_at: null,
});
expect((await list()).meta.total).toBe(11);
});
it.each([["存在しない id", "FX-ERR-99"], ["大小文字違い", "fx-err-01"]])("%s は404", async (_label, id) => {
const res = await patch(id, { status: "resolved" });
expect(res.status).toBe(404);
expect(await res.json()).toEqual({
error: { code: "not_found", message: "エラーが見つかりません", details: null },
});
});
});