import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { PATCH } from "@/app/api/errors/[error_id]/status/route";
import { GET } from "@/app/api/ds-alerts/route";
import { resetFixtureErrorReportOverrides } from "@/lib/db/fixture-error-report-overrides";
import type { DsAlertSummary } from "@/lib/ds-alerts/query";
import type { DsAlert, SummaryListResponse } from "@/lib/types";
/**
* `GET /ds-alerts` → `PATCH /errors/{error_id}/status` → `GET /ds-alerts`(`USE_FIXTURES=true`。指示書AX 段階3-4)。
*
* ★D-Sec アラートは `errorReportRows` の `D-S_CONTROL_FAILURE` 行から作る★(D13)ので、
* エラー一覧の PATCH で書いた対応状況が D-Sec にも出る(U4。上書き表を共有)。
*
* ★★なぜ別ファイルなのか★★ — `lib/db/index.ts` の `db` はモジュール読み込み時に束縛されるので、
* 同じファイルで true/false の両方は試せない(`app/api/errors/__tests__/route.fixtures.test.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";
/** 出る7行(`FX-ERR-10`・`12`〜`17`)。未解消3(10・12・16)・対応中2(13・17)・解消2(14・15) */
const VISIBLE_DS_ALERT_IDS = [
"FX-ERR-10",
"FX-ERR-12",
"FX-ERR-13",
"FX-ERR-14",
"FX-ERR-15",
"FX-ERR-16",
"FX-ERR-17",
];
beforeEach(() => {
resetFixtureErrorReportOverrides();
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date(NOW_ISO));
});
afterEach(() => {
vi.useRealTimers();
resetFixtureErrorReportOverrides();
});
async function list(qs = ""): Promise<SummaryListResponse<DsAlert, DsAlertSummary>> {
const res = await GET(new Request(`http://localhost/api/ds-alerts${qs ? `?${qs}` : ""}`));
expect(res.status).toBe(200);
return (await res.json()) as SummaryListResponse<DsAlert, DsAlertSummary>;
}
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 /ds-alerts(USE_FIXTURES=true)", () => {
it("★既定(status=open)で5件・status=all で7件★", async () => {
const def = await list();
expect(def.meta.total).toBe(5);
expect(def.items.map((i) => i.id).sort()).toEqual(["FX-ERR-10", "FX-ERR-12", "FX-ERR-13", "FX-ERR-16", "FX-ERR-17"]);
const all = await list("status=all");
expect(all.meta.total).toBe(7);
expect(all.items.map((i) => i.id).sort()).toEqual(VISIBLE_DS_ALERT_IDS);
});
it("★summary は {unresolved:3, in_progress:2, resolved:2}(status に依らない)★", async () => {
for (const qs of ["", "status=all", "status=resolved"]) {
expect((await list(qs)).summary).toEqual({ unresolved: 3, in_progress: 2, resolved: 2 });
}
});
it("★SECURITY_ON(FX-ERR-08)・NOT_STARTED(FX-ERR-11)・削除済みの清掃エリア(FX-ERR-19)は出ない★", async () => {
const ids = (await list("status=all")).items.map((i) => i.id);
expect(ids).not.toContain("FX-ERR-08");
expect(ids).not.toContain("FX-ERR-11");
expect(ids).not.toContain("FX-ERR-19");
});
});
describe("PATCH /errors → GET /ds-alerts(USE_FIXTURES=true)", () => {
it("★FX-ERR-10 を解消にすると status=open から消え、summary.resolved が3になる★", async () => {
expect((await patch("FX-ERR-10", { status: "resolved" })).status).toBe(200);
const open = await list();
expect(open.items.map((i) => i.id)).not.toContain("FX-ERR-10");
expect(open.meta.total).toBe(4);
expect(open.summary).toEqual({ unresolved: 2, in_progress: 2, resolved: 3 });
const resolved = await list("status=resolved");
expect(resolved.items.find((i) => i.id === "FX-ERR-10")).toMatchObject({
resolution_status: "resolved",
responded_at: NOW_ISO,
resolved_at: NOW_ISO,
});
});
});