import { describe, expect, it } from "vitest";
import { filterCleanLogs, type CleanLogQuery } from "@/lib/clean-logs/query";
import { filterFixtureCleanLogs } from "@/lib/db/fixture-clean-logs";
import { cleanLogs } from "@/lib/fixtures";
import type { CleanLogDbQuery } from "@/lib/types";
/**
* 指示書AC 段階2 ★D-5★ — fixtures 版も同じ引数を受け取り、同じ結果を返す。
*
* ★★`USE_FIXTURES` の true/false で件数が変わってはいけない★★(指示書AC 禁止事項6)。
* ★★`null`(条件なし)と `[]`(0件ヒット)を同じ意味で扱わないこと★★(罠9 / 罠1)。
*
* ★fixtures の**既存の値**は変更していない★(禁止事項5)— ここは読むだけ。
*/
const ALL: CleanLogDbQuery = { id: null, date_from: null, date_to: null, facility_ids: null };
/** API ルート側の絞り込み(=規則の「正」)で同じ条件を書いたもの */
function memoryQuery(partial: Partial<CleanLogQuery>): CleanLogQuery {
return {
dateFrom: null,
dateTo: null,
facilityName: "",
model: "",
serial: "",
result: "all",
// ★絞り込まない(指示書AK 段階2 で足った2つ。`null` = 条件なし)★
groupId: null,
customerId: null,
page: 1,
perPage: 50,
...partial,
};
}
function jstDate(day: string): Date {
return new Date(`${day}T00:00:00+09:00`);
}
/** fixtures の中でデータが載っている日(`at()` が相対日で作るので実行日から決める) */
const days = [...new Set(cleanLogs.map((r) => r.scheduled_at.slice(0, 10)))].sort();
const middleDay = days[Math.floor(days.length / 2)];
describe("filterFixtureCleanLogs(指示書AC 段階2 D-5)", () => {
it("#1 範囲なし(全部 null)→ fixtures の全件", () => {
expect(filterFixtureCleanLogs(cleanLogs, ALL)).toHaveLength(cleanLogs.length);
});
it("#2 日付範囲 → ★API ルート側の `filterCleanLogs()` と完全一致★", () => {
const from = days[1];
const to = days[3];
const sql = filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: from,
date_to: to,
facility_ids: null,
});
const memory = filterCleanLogs(
[...cleanLogs],
memoryQuery({ dateFrom: jstDate(from), dateTo: jstDate(to) })
);
// ★件数だけでなく id の集合まで比べる★(指示書AC G-3)
expect(sql.map((r) => r.id)).toEqual(memory.map((r) => r.id));
expect(sql).toEqual(memory);
expect(sql.length).toBeGreaterThan(0);
});
it("#3 `date_from = date_to` → その日の行だけ", () => {
const rows = filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: middleDay,
date_to: middleDay,
facility_ids: null,
});
expect(rows.length).toBeGreaterThan(0);
expect([...new Set(rows.map((r) => r.scheduled_at.slice(0, 10)))]).toEqual([middleDay]);
});
it("#3 上限はその日を**含む**(★翌日 00:00 未満★)", () => {
const rows = filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: null,
date_to: middleDay,
facility_ids: null,
});
expect(rows.some((r) => r.scheduled_at.slice(0, 10) === middleDay)).toBe(true);
expect(rows.every((r) => r.scheduled_at.slice(0, 10) <= middleDay)).toBe(true);
});
it("#4 `date_from` がデータの最古日より前 → #1 と同じ件数", () => {
const rows = filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: "2000-01-01",
date_to: null,
facility_ids: null,
});
expect(rows).toHaveLength(cleanLogs.length);
});
it("#6 `facility_ids: [A, B]` → その2施設だけ(★メモリ絞り込みと一致★)", () => {
const ids = [...new Set(cleanLogs.map((r) => r.facility_id))].sort((a, b) => a - b);
const two = [ids[0], ids[1]];
const rows = filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: null,
date_to: null,
facility_ids: two,
});
expect(rows).toEqual(cleanLogs.filter((r) => two.includes(r.facility_id)));
expect(rows.length).toBeGreaterThan(0);
});
it("#6 1施設だけのケース", () => {
const id = cleanLogs[0].facility_id;
const rows = filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: null,
date_to: null,
facility_ids: [id],
});
expect(rows.every((r) => r.facility_id === id)).toBe(true);
expect(rows.length).toBeGreaterThan(0);
});
it("★#6b★ `facility_ids: []` → ★★0件★★(★`in: undefined` に化けていないことの検出。罠1★)", () => {
// ★★このテストが唯一の検出手段★★ —
// `[]` を「条件なし」と読んで全件返す実装にすると、Prisma 版(`in: []` = 0件)と
// 件数が食い違い、`USE_FIXTURES` の true/false で結果が変わる(禁止事項6)
expect(
filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: null,
date_to: null,
facility_ids: [],
})
).toEqual([]);
});
it("★#6c★ `facility_ids: null` → ★全件(#1 と同じ)★", () => {
expect(
filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: null,
date_to: null,
facility_ids: null,
})
).toHaveLength(cleanLogs.length);
});
it("存在しない施設IDだけを渡すと0件(★`[]` との区別がついている★)", () => {
expect(
filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: null,
date_to: null,
facility_ids: [999_999],
})
).toEqual([]);
});
it("日付と施設の AND が効く", () => {
const id = cleanLogs[0].facility_id;
const rows = filterFixtureCleanLogs(cleanLogs, {
id: null,
date_from: middleDay,
date_to: middleDay,
facility_ids: [id],
});
expect(
rows.every(
(r) => r.facility_id === id && r.scheduled_at.slice(0, 10) === middleDay
)
).toBe(true);
});
it("★★段階3: `id` を指定すると1件だけ★★(★Prisma 版の `where.id` と同じ意味★)", () => {
const target = cleanLogs[0];
const rows = filterFixtureCleanLogs(cleanLogs, { ...ALL, id: target.id });
expect(rows).toEqual([target]);
});
it("★★段階3: 存在しない `id` → 0件★★(★`null` に化けて全件になっていないことの検出★)", () => {
expect(filterFixtureCleanLogs(cleanLogs, { ...ALL, id: 999_999_999 })).toEqual([]);
});
it("★段階3: `id` は他の条件と AND で重なる★(★範囲外の `id` は引けない★)", () => {
const target = cleanLogs[0];
const otherDay = days.find((d) => d !== target.scheduled_at.slice(0, 10))!;
expect(
filterFixtureCleanLogs(cleanLogs, {
...ALL,
id: target.id,
date_from: otherDay,
date_to: otherDay,
})
).toEqual([]);
});
it("★入力の配列を壊さない(純粋関数)★", () => {
const before = cleanLogs.length;
filterFixtureCleanLogs(cleanLogs, { id: null, date_from: middleDay, date_to: middleDay, facility_ids: [] });
expect(cleanLogs).toHaveLength(before);
});
});