import { describe, expect, it } from "vitest";
import {
CLEAN_AREA_ANOMALY_METRICS,
reportCleanAreaAnomalies,
} from "@/lib/clean-area/anomaly";
import type { DataAnomalyLogEvent } from "@/lib/logger/events";
/**
* 指示書AN ★段階3★ §2-5 / §6-3(★落とし穴2★)— 番人のログ。
*
* ★★静かに壊れる代わりにログに出す★★のが目的なので、
* ★「異常があるのに1行も出ない」★ことをテストで止める。
*/
function collect(): { events: DataAnomalyLogEvent[]; emit: (e: DataAnomalyLogEvent) => void } {
const events: DataAnomalyLogEvent[] = [];
return { events, emit: (event) => events.push(event) };
}
describe("CLEAN_AREA_ANOMALY_METRICS — ログの `metric` の綴りを固定する", () => {
// ★ログの `metric` は外部(監視・集計)との契約★。他のテストは定数を参照するので、
// ★綴りがうっかり変わっても1件も落ちない★。ここだけは文字列そのものを固定する。
// ★変えるときは、この名前で警告を拾っている監視が無いことを確かめてから★(D-151)。
it("重複検知は clean_task_reports の名前(2026-10-01 に旧テーブル名から改名)", () => {
expect(CLEAN_AREA_ANOMALY_METRICS.duplicatedTimetables).toBe(
"clean_task_report_rows_per_timetable"
);
});
it("施設マスタに無い facility_id", () => {
expect(CLEAN_AREA_ANOMALY_METRICS.unmatchedFacilities).toBe(
"clean_area_facility_ids_without_master"
);
});
});
describe("reportCleanAreaAnomalies", () => {
it("異常が無ければ1行も出さない(0件を毎回出すとログが埋まる)", () => {
const { events, emit } = collect();
reportCleanAreaAnomalies(
{
route: "/api/clean-area",
method: "GET",
duplicated_timetable_count: 0,
unmatched_facility_ids: [],
},
emit
);
expect(events).toEqual([]);
});
it("★★履歴が2行以上ある清掃があれば warn を出す★★(期待値0と実測だけを残す)", () => {
const { events, emit } = collect();
reportCleanAreaAnomalies(
{
route: "/api/clean-area",
method: "GET",
duplicated_timetable_count: 3,
unmatched_facility_ids: [],
},
emit
);
expect(events).toEqual([
{
event: "data.anomaly",
route: "/api/clean-area",
method: "GET",
metric: CLEAN_AREA_ANOMALY_METRICS.duplicatedTimetables,
expected: 0,
actual: 3,
},
]);
});
it("施設マスタに無い `facility_id` も同じ形で残す(★件数だけ。IDは載せない★)", () => {
const { events, emit } = collect();
reportCleanAreaAnomalies(
{
route: "/api/clean-area",
method: "GET",
duplicated_timetable_count: 0,
unmatched_facility_ids: [98, 99],
},
emit
);
expect(events).toHaveLength(1);
expect(events[0].metric).toBe(CLEAN_AREA_ANOMALY_METRICS.unmatchedFacilities);
expect(events[0].actual).toBe(2);
});
it("両方あれば2行出す(★片方で打ち切らない★)", () => {
const { events, emit } = collect();
reportCleanAreaAnomalies(
{
route: "/api/clean-area",
method: "GET",
duplicated_timetable_count: 1,
unmatched_facility_ids: [7],
},
emit
);
expect(events).toHaveLength(2);
});
it("★`metric` の文字列に原因の断定を書かない★(`docs/logging-design.md` §7)", () => {
// ★「TeamWorks の収集処理が二重登録している」のような推測を metric に埋め込まない★ —
// ★テーブル名と数え方だけ★(`clean_log_rows_vs_scheduled`(D-049)と同じ流儀)
for (const metric of Object.values(CLEAN_AREA_ANOMALY_METRICS)) {
expect(metric).toMatch(/^[a-z0-9_]+$/);
}
});
});