Newer
Older
client_dwatch / app / api / errors / __tests__ / route.fixtures.test.ts
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 },
    });
  });
});