import { describe, expect, it } from "vitest";
import { dbDateRange } from "@/lib/db/date-range";
import { fromDbJst, jstIsoFromDb, toDbJst } from "@/lib/format/date";
import { parseDate } from "@/lib/clean-logs/query";
/**
* 指示書AC 段階2 ★D-2 / C-6★ — 日付境界の補正。
*
* ------------------------------------------------------------------------
* ★★このテストがある理由 — ローカルDBでは間違いを検出できないから★★
* ------------------------------------------------------------------------
* `clean_timetables.start_at` は ★日本時間の壁時計値★ が入った DATETIME(D-115)。
* 一方 `parseDate("2026-09-08")` が返すのは ★真の瞬間★(= `2026-09-07T15:00:00Z`)。
* ★そのまま `where` に渡すと9時間ずれます★。
*
* ★★ところがローカルDB(`dwatch_local`)の `start_at`(壁時計)は
* 0時:155 / 1時:185 / 2時:168 / 3時:30 で、★15時以降が0件★★★
* (2026-09-13 本作業の実測。指示書AC C-6 の実測とも一致)。
* ★9時間ずらしても日付が変わらない時間帯にしかデータが無い★ため、
* ★★補正を忘れてもローカルでは件数が1件も変わりません★★。
*
* ★★だから「壁時計 20:00 の清掃」を合成データで必ず通すこと★★(期待値表 #7)。
* ★このテストを消さないこと★。
*/
/**
* DB に入っているのと同じ「壁時計値としての `Date`」を作る
* (`lib/db/__tests__/consecutive-source.test.ts` と同じヘルパー)。
* ★`fromDbJst()` を通すと元の ISO に戻る★のが正しい状態。
*/
function dbWallClock(jstIso: string): Date {
return new Date(jstIso.replace("+09:00", "Z"));
}
/** Prisma の `start_at: { gte, lt }` と同じ判定(★DBがやることの写し★) */
function matchesRange(
startAt: Date,
range: { gte?: Date; lt?: Date }
): boolean {
if (range.gte && startAt.getTime() < range.gte.getTime()) return false;
if (range.lt && startAt.getTime() >= range.lt.getTime()) return false;
return true;
}
describe("dbDateRange(指示書AC 段階2 D-2 / C-6)", () => {
it("下限は `toDbJst()` を通した壁時計値になる(★補正を忘れると9時間ずれる★)", () => {
const range = dbDateRange("2026-09-08", null);
// ★指示書AC C-6 の実測値をそのまま固定する★
expect(parseDate("2026-09-08")?.toISOString()).toBe("2026-09-07T15:00:00.000Z");
expect(range.gte?.toISOString()).toBe("2026-09-08T00:00:00.000Z");
// ★★`parseDate()` の結果をそのまま渡していないこと★★
expect(range.gte?.getTime()).not.toBe(parseDate("2026-09-08")?.getTime());
expect(range.lt).toBeUndefined();
});
it("上限はその日を**含む**(=翌日 00:00 未満)", () => {
const range = dbDateRange(null, "2026-09-10");
expect(range.gte).toBeUndefined();
expect(range.lt?.toISOString()).toBe("2026-09-11T00:00:00.000Z");
});
it("どちらも `null` なら空オブジェクト(★呼び出し側は `start_at` のキーごと付けない★)", () => {
expect(dbDateRange(null, null)).toEqual({});
});
it("形式が不正な文字列は「指定なし」として扱う(★検証は `parseCleanLogQuery()` の仕事★)", () => {
// ★ここで422を返さないこと★ — 2箇所で検証すると片方だけ緩めたときに気づけない
expect(dbDateRange("2026-9-8", "")).toEqual({});
expect(dbDateRange("not-a-date", "2026-13-45")).toEqual({});
});
it("月をまたぐ範囲でも境界がずれない", () => {
const range = dbDateRange("2026-08-30", "2026-09-01");
expect(range.gte?.toISOString()).toBe("2026-08-30T00:00:00.000Z");
expect(range.lt?.toISOString()).toBe("2026-09-02T00:00:00.000Z");
});
describe("★#7 壁時計 20:00 の清掃★(★ローカルDBには1件も無い時間帯★)", () => {
/** ★9時間ずらすと日付が翌日に化ける行★ */
const evening = dbWallClock("2026-09-10T20:00:00+09:00");
it("その行が JST の 9/10 であることを前提として確認する", () => {
expect(jstIsoFromDb(evening)).toBe("2026-09-10T20:00:00+09:00");
});
it("`date_from = date_to = 2026-09-10` の範囲に**含まれる**", () => {
expect(matchesRange(evening, dbDateRange("2026-09-10", "2026-09-10"))).toBe(true);
});
it("前日・翌日だけを指定した範囲には**含まれない**", () => {
expect(matchesRange(evening, dbDateRange("2026-09-09", "2026-09-09"))).toBe(false);
expect(matchesRange(evening, dbDateRange("2026-09-11", "2026-09-11"))).toBe(false);
});
it("★★`toDbJst()` を忘れた実装なら、この行は 9/10 の範囲から落ちる★★(=検出できる)", () => {
// ★補正を忘れた実装★(`parseDate()` の結果をそのまま `where` に渡す)
const buggy = {
gte: parseDate("2026-09-10") as Date,
lt: new Date((parseDate("2026-09-10") as Date).getTime() + 86_400_000),
};
// ★9時間ずれるので 20:00 の行は範囲外になる(=画面から静かに消える)★
expect(matchesRange(evening, buggy)).toBe(false);
// ★深夜2時の行なら補正を忘れても落ちない=ローカルDBでは気づけない★
const midnight = dbWallClock("2026-09-10T02:00:00+09:00");
expect(matchesRange(midnight, buggy)).toBe(true);
expect(matchesRange(midnight, dbDateRange("2026-09-10", "2026-09-10"))).toBe(true);
});
});
it("境界ちょうど(00:00:00 と 23:59:59)が両端とも含まれる", () => {
const range = dbDateRange("2026-09-10", "2026-09-10");
expect(matchesRange(dbWallClock("2026-09-10T00:00:00+09:00"), range)).toBe(true);
expect(matchesRange(dbWallClock("2026-09-10T23:59:59+09:00"), range)).toBe(true);
expect(matchesRange(dbWallClock("2026-09-09T23:59:59+09:00"), range)).toBe(false);
expect(matchesRange(dbWallClock("2026-09-11T00:00:00+09:00"), range)).toBe(false);
});
it("`toDbJst()` / `fromDbJst()` の往復で元に戻る(★新しい変換を書いていないことの確認★)", () => {
const from = parseDate("2026-09-08") as Date;
expect(fromDbJst(toDbJst(from)).getTime()).toBe(from.getTime());
expect(dbDateRange("2026-09-08", null).gte?.getTime()).toBe(toDbJst(from).getTime());
});
});