Newer
Older
client_dwatch / lib / db / __tests__ / robot-error-select.test.ts
/**
 * `robot_error_reports` の読み取りの `select` が `robot_id` / `robot` を含まないこと(指示書AU 段階0-4。§11 Q6)。
 *
 * ★先方の DDL では `robot_id` が NULL 可(TeamWorks 第11回 P-8 b)、Prisma のスキーマは NOT NULL のまま★。
 *   読まなければ NULL の行が入っても落ちない。経路ごとの「NULL の行を混ぜても落ちない」は
 *   各ルートのテスト(`app/api/{clean-logs,prechecks,prechecks/[id]/status,reports}/__tests__/route.test.ts`)。
 */
import { describe, expect, it } from "vitest";
import {
  ROBOT_ERROR_COUNT_SELECT,
  ROBOT_ERROR_DS_ALERT_SELECT,
  ROBOT_ERROR_LIST_KEY_SELECT,
  ROBOT_ERROR_LIST_ROW_SELECT,
  ROBOT_ERROR_PRECHECK_ROW_SELECT,
  ROBOT_ERROR_STATUS_SELECT,
  ROBOT_ERROR_TYPE_SELECT,
} from "@/lib/db/robot-error-select";
import { guardNullRobotId } from "./robot-error-null-guard";

const SELECTS: [string, Record<string, unknown>][] = [
  ["ROBOT_ERROR_COUNT_SELECT", ROBOT_ERROR_COUNT_SELECT],
  ["ROBOT_ERROR_STATUS_SELECT", ROBOT_ERROR_STATUS_SELECT],
  ["ROBOT_ERROR_TYPE_SELECT", ROBOT_ERROR_TYPE_SELECT],
  ["ROBOT_ERROR_PRECHECK_ROW_SELECT", ROBOT_ERROR_PRECHECK_ROW_SELECT],
  // エラー一覧(指示書AW D6): 1段目(キーだけ)と2段目・PATCH の前後の読み取り
  ["ROBOT_ERROR_LIST_KEY_SELECT", ROBOT_ERROR_LIST_KEY_SELECT],
  ["ROBOT_ERROR_LIST_ROW_SELECT", ROBOT_ERROR_LIST_ROW_SELECT],
  // D-Sec アラート(指示書AX 2-3。行は全部 robot_id NULL)
  ["ROBOT_ERROR_DS_ALERT_SELECT", ROBOT_ERROR_DS_ALERT_SELECT],
];

/** ★可視性(`isVisibleErrorRow()`。削除済みの施設・清掃エリアの行を外す。指示書AX U8)の判定に要る列を読む定数★ */
const VISIBILITY_SELECTS: [string, Record<string, unknown>][] = [
  ["ROBOT_ERROR_LIST_KEY_SELECT", ROBOT_ERROR_LIST_KEY_SELECT],
  ["ROBOT_ERROR_LIST_ROW_SELECT", ROBOT_ERROR_LIST_ROW_SELECT],
  ["ROBOT_ERROR_DS_ALERT_SELECT", ROBOT_ERROR_DS_ALERT_SELECT],
  // ★アラート(事前チェック異常)の行も可視性で外す(指示書BB U1・5-4)★
  ["ROBOT_ERROR_PRECHECK_ROW_SELECT", ROBOT_ERROR_PRECHECK_ROW_SELECT],
];

describe("robot_error_reports の select(指示書AU 段階0-4)", () => {
  it.each(SELECTS)("%s は robot_id も robot も含まない", (_name, select) => {
    expect(select).not.toHaveProperty("robot_id");
    expect(select).not.toHaveProperty("robot");
  });

  it.each(SELECTS)("%s は紐づけのキー clean_timetable_id を含む", (_name, select) => {
    expect(select.clean_timetable_id).toBe(true);
  });

  it.each(VISIBILITY_SELECTS)(
    "★%s は facility / section の deleted_at を含む(指示書AX U8。取り忘れを防ぐ)★",
    (_name, select) => {
      expect(select).toHaveProperty(["facility", "select", "deleted_at"], true);
      expect(select).toHaveProperty(["section", "select", "deleted_at"], true);
    }
  );
});

describe("error_list_id(エラー内容。指示書BD §5-3)", () => {
  it("★ROBOT_ERROR_LIST_ROW_SELECT(一覧の2段目・PATCH の前後の読み取り)は error_list_id を読む★", () => {
    expect(ROBOT_ERROR_LIST_ROW_SELECT.error_list_id).toBe(true);
  });

  it.each(SELECTS.filter(([name]) => name !== "ROBOT_ERROR_LIST_ROW_SELECT"))(
    "%s は error_list_id を読まない(エラー一覧の行だけが持つ)",
    (_name, select) => {
      expect(select).not.toHaveProperty("error_list_id");
    }
  );
});

describe("guardNullRobotId(スタブで Prisma の失敗を再現する)", () => {
  const nullRow = [{ error_id: "E1", robot_id: null }];

  it("select なし(全列)で NULL の行を読むと落ちる", () => {
    expect(() => guardNullRobotId(nullRow, { where: {} })).toThrow(/robot_id/);
    expect(() => guardNullRobotId(nullRow, { include: { facility: true } })).toThrow(/robot_id/);
  });

  it("select / include に robot_id・robot があると落ちる", () => {
    expect(() => guardNullRobotId(nullRow, { select: { robot_id: true } })).toThrow();
    expect(() => guardNullRobotId(nullRow, { select: { robot: true } })).toThrow();
    expect(() => guardNullRobotId(nullRow, { include: { robot: true } })).toThrow();
  });

  it("select で絞っていれば落ちない", () => {
    expect(() => guardNullRobotId(nullRow, { select: ROBOT_ERROR_PRECHECK_ROW_SELECT })).not.toThrow();
  });

  it("NULL の行が無ければ select なしでも落ちない(既存のテストを巻き込まない)", () => {
    expect(() => guardNullRobotId([{ robot_id: BigInt(1) }], undefined)).not.toThrow();
  });
});