import { describe, expect, it } from "vitest";
import { db, USE_FIXTURES } from "@/lib/db";
import { resolveFacilityIdsByMasters } from "@/lib/facilities/resolve-by-master";
import { customers, facilities, groups } from "@/lib/fixtures";
/**
* 分類マスタの fixtures(`groups.ts` / `customers.ts`)の不変条件(指示書AK 段階1)。
*
* ------------------------------------------------------------------------
* ★★何を守るためのテストか★★
* ------------------------------------------------------------------------
* - ★fixtures 側を `notImplemented` にしないこと★(落とし穴9)—
* ★`USE_FIXTURES=true` で画面が落ちる★。ここで実際に `db.groups()` を呼んで確かめる
* - ★1グループ1顧客にしないこと★(落とし穴10)—
* ★3階層のUIを fixtures で確かめられなくなる★
* - ★「グループ0」を作らないこと★(落とし穴3)
*
* ★テスト実行時は `USE_FIXTURES` の既定(= true)で走る★
* (`lib/db/use-fixtures.ts`: `process.env.USE_FIXTURES !== "false"`)。
*/
describe("fixtures: 分類マスタ", () => {
it("db.groups() / db.customers() が fixtures で動く(notImplemented にしない)", async () => {
expect(USE_FIXTURES).toBe(true);
await expect(db.groups()).resolves.toEqual(groups);
await expect(db.customers()).resolves.toEqual(customers);
});
it("★複数グループ・複数顧客がある★(3階層のUIを確かめられること)", () => {
expect(groups.length).toBeGreaterThanOrEqual(2);
expect(customers.length).toBeGreaterThanOrEqual(2);
// ★同一グループに2社★ … グループ単位の集計が「1社に定まらない」側(段階6 の 6-7)
const byGroup = new Map<number | null, number>();
for (const c of customers) {
byGroup.set(c.group_id, (byGroup.get(c.group_id) ?? 0) + 1);
}
expect([...byGroup.values()].some((n) => n >= 2)).toBe(true);
// ★傘下1社のグループ★ … 422 にならない側(6-6)
expect([...byGroup.values()].some((n) => n === 1)).toBe(true);
// ★ホールディングスなしの屋号★(`group_id: null`)
expect(customers.some((c) => c.group_id === null)).toBe(true);
});
it("★id=0 のグループが無い/group_id に 0 を書いていない★(D-011)", () => {
expect(groups.some((g) => g.id === 0)).toBe(false);
expect(customers.some((c) => c.group_id === 0)).toBe(false);
});
it("id が一意で、customers.group_id は実在するグループを指す", () => {
expect(new Set(groups.map((g) => g.id)).size).toBe(groups.length);
expect(new Set(customers.map((c) => c.id)).size).toBe(customers.length);
const ids = new Set(groups.map((g) => g.id));
for (const c of customers) {
if (c.group_id !== null) expect(ids.has(c.group_id)).toBe(true);
}
});
/**
* ★所属は `Facility` 自身が持つ★(指示書AK ★段階5★。段階1〜4 の
* `customerIdByFacilityId` は削除済み)。★`customers` / `groups` との整合をここで固定する★ —
* 片方だけ直すと ★屋号で絞ったときだけ施設が消える★という気づけない壊れ方をする。
*/
it("施設12件すべてが、実在する屋号に1つだけ割り当たっている", () => {
const customerById = new Map(customers.map((c) => [c.id, c]));
const groupNameById = new Map(groups.map((g) => [g.id, g.group_name]));
for (const f of facilities) {
const customer = customerById.get(f.customer_id);
expect(customer, `施設 ${f.id} の屋号(${f.customer_id}) が実在しない`).toBeDefined();
// ★屋号名・ホールディングスは `customers` / `groups` の写しであること★
expect(f.customer_name).toBe(customer?.customer_name);
expect(f.group_id).toBe(customer?.group_id);
// ★`group_id` が null なら名前も null★(「グループ0」を作らない。落とし穴3)
expect(f.group_name).toBe(
f.group_id === null ? null : (groupNameById.get(f.group_id) ?? null)
);
}
// ★1顧客に寄せていないこと★(複数の屋号に散らす。落とし穴10)
expect(new Set(facilities.map((f) => f.customer_id)).size).toBeGreaterThanOrEqual(2);
// ★ホールディングスなしの施設がある★(段階5 で `group_name: null` を通すケース)
expect(facilities.some((f) => f.group_id === null)).toBe(true);
});
});
/**
* 段階3(施設IDへの解決)が ★`USE_FIXTURES=true` でも動くこと★(指示書AK §6-3 / 落とし穴9)。
*
* ★★純粋関数の単体テストは `lib/facilities/__tests__/resolve-by-master.test.ts`★★。
* ここで確かめるのは ★fixtures 側のデータ取得(`db.facilityGroupIndex()`)が
* `notImplemented` でないこと★ と、★Prisma 版と同じ契約で解決できること★。
*/
describe("fixtures: 分類 → 施設IDの解決(指示書AK 段階3)", () => {
it("db.facilityGroupIndex() が fixtures で動く(notImplemented にしない)", async () => {
expect(USE_FIXTURES).toBe(true);
const refs = await db.facilityGroupIndex();
// ★施設を間引かないこと★(`deleted_at` の概念が無いので全件。罠7)
expect(refs).toHaveLength(facilities.length);
const customerIdByFacilityId = new Map(facilities.map((f) => [f.id, f.customer_id]));
for (const ref of refs) {
expect(customerIdByFacilityId.get(ref.id)).toBe(ref.customer_id);
}
// ★`group_id` は API の形(0 を漏らさない。落とし穴3)★
expect(refs.some((r) => r.group_id === 0)).toBe(false);
expect(refs.some((r) => r.group_id === null)).toBe(true);
});
it("★どちらも未指定なら DB を読まずに `null`★(条件なし)", async () => {
await expect(
resolveFacilityIdsByMasters({ groupId: null, customerId: null })
).resolves.toBeNull();
});
it("★屋号で絞ると、その屋号の施設だけが返る★", async () => {
const expected = facilities
.filter((f) => f.customer_id === 2)
.map((f) => f.id)
.sort((a, b) => a - b);
const ids = await resolveFacilityIdsByMasters({ groupId: null, customerId: 2 });
expect(ids).not.toBeNull();
expect([...(ids as number[])].sort((a, b) => a - b)).toEqual(expected);
});
it("★ホールディングスで絞ると、傘下の複数屋号の施設が全部返る★", async () => {
const customerIds = new Set(
customers.filter((c) => c.group_id === 1).map((c) => c.id)
);
// ★傘下が2社あることを前提にしたケース★(fixtures がそうなっている。落とし穴10)
expect(customerIds.size).toBeGreaterThanOrEqual(2);
const expected = facilities
.filter((f) => customerIds.has(f.customer_id))
.map((f) => f.id)
.sort((a, b) => a - b);
const ids = await resolveFacilityIdsByMasters({ groupId: 1, customerId: null });
expect([...(ids as number[])].sort((a, b) => a - b)).toEqual(expected);
});
it("★★実在しないIDは `[]`(0件ヒット)★★。★`null` にしないこと★", async () => {
await expect(
resolveFacilityIdsByMasters({ groupId: null, customerId: 9999 })
).resolves.toEqual([]);
});
});