import { describe, expect, it } from "vitest";
import { DB_IN_CLAUSE_MAX_ITEMS } from "@/lib/config";
import { chunkForInClause } from "@/lib/db/chunked";
/**
* 指示書AC 段階2 ★§E-5★ — `in` に入れる配列の長さに上限を設ける。
*
* ★上限値と、その値にした理由は `DB_IN_CLAUSE_MAX_ITEMS`(`lib/config/index.ts`)★。
* ★ここに数字を直書きしないこと★(`CLAUDE.md` §7)。
*/
describe("chunkForInClause(指示書AC §E-5)", () => {
it("★空配列は「塊が0個」★(★`[[]]` を返さない= `in: []` のクエリを撃たない★)", () => {
// ★`[[]]` を返すと、必ず0件と分かっているクエリが1本走って往復が無駄になる★
expect(chunkForInClause([])).toEqual([]);
});
it("上限以下なら1つの塊のまま", () => {
const ids = Array.from({ length: DB_IN_CLAUSE_MAX_ITEMS }, (_, i) => i);
expect(chunkForInClause(ids)).toEqual([ids]);
});
it("上限を1つ超えたら2つに割れる", () => {
const ids = Array.from({ length: DB_IN_CLAUSE_MAX_ITEMS + 1 }, (_, i) => i);
const chunks = chunkForInClause(ids);
expect(chunks).toHaveLength(2);
expect(chunks[0]).toHaveLength(DB_IN_CLAUSE_MAX_ITEMS);
expect(chunks[1]).toHaveLength(1);
});
it("★分割しても要素は1つも落ちず、順序も変わらない★", () => {
const ids = Array.from({ length: 2_345 }, (_, i) => i);
expect(chunkForInClause(ids).flat()).toEqual(ids);
});
it("本番想定(31日 × 約360行/日 ≒ 11,000件)でもクエリ回数が有界", () => {
const ids = Array.from({ length: 11_000 }, (_, i) => i);
const chunks = chunkForInClause(ids);
expect(chunks).toHaveLength(Math.ceil(11_000 / DB_IN_CLAUSE_MAX_ITEMS));
// ★Prisma(mysql)の `maxBindValues` 65,535 を1本のクエリで超えない★
expect(Math.max(...chunks.map((c) => c.length))).toBeLessThan(65_535);
});
it("分割サイズが0以下なら例外(★無限ループに落ちるより気づけるほうを選ぶ★)", () => {
expect(() => chunkForInClause([1, 2, 3], 0)).toThrow();
expect(() => chunkForInClause([1, 2, 3], -1)).toThrow();
});
it("bigint の配列でも使える(★`clean_timetables.id` は BigInt★)", () => {
expect(chunkForInClause([BigInt(1), BigInt(2), BigInt(3)], 2)).toEqual([[BigInt(1), BigInt(2)], [BigInt(3)]]);
});
});