Files
buzz-sheet/lib/guides/queries.test.ts
T
gunshiz 9580407a90
CI / Verify (push) Successful in 1m56s
CI / Build immutable images and deploy (push) Successful in 3m12s
perf : cache
2026-10-08 23:21:34 +07:00

219 lines
10 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({ reads: [] as unknown[], selections: [] as unknown[], rows: new Map<unknown, unknown[]>() }));
vi.mock("server-only", () => ({}));
vi.mock("@/lib/media/storage", () => ({ publicMediaUrl: (key: string) => `/media/${key}` }));
vi.mock("@/db", () => ({
getDb: () => ({
select: (columns?: unknown) => {
mocks.selections.push(columns);
let table: unknown;
const query = {
from(value: unknown) { table = value; return query; },
innerJoin() { return query; },
leftJoin() { return query; },
where() { return query; },
orderBy() { return query; },
limit() { return query; },
then(resolve: (rows: unknown[]) => unknown) {
mocks.reads.push(table);
return Promise.resolve(mocks.rows.get(table) ?? []).then(resolve);
},
};
return query;
},
}),
}));
import {
artifactConditions, artifactProfiles, constellationConditions, extraSectionRows,
guides, guideSections, teamCompositions, teamConditions, teamMembers, weaponConditions,
catalogCharacters, catalogMaterials, guidePosterBleeding, media, weaponRecommendations, artifactRecommendations,
} from "@/db/schema";
import { getActiveCatalog, getCatalogOptions, getAdminGuideContent, getPublicGuide, listAdminTeamImportsForCharacter } from "./queries";
beforeEach(() => {
mocks.reads.length = 0;
mocks.selections.length = 0;
mocks.rows.clear();
mocks.rows.set(guides, [{
guide: { id: "guide", slug: "test", posterArtMediaId: null },
cover: { objectKey: "cover", status: "ready" },
character: { key: "test", imageKey: "character", constellations: [], talents: [], ascension: [], leveling: [] },
}]);
mocks.rows.set(guideSections, [
{ id: "extra-a", slug: "extra-a", kind: "extra" },
{ id: "extra-b", slug: "extra-b", kind: "extra" },
]);
});
describe("admin section reads", () => {
it.each([
["weapon", weaponConditions],
["artifact", artifactConditions],
["constellations", constellationConditions],
["team", teamConditions],
])("loads only the %s section's conditions", async (section, expectedTable) => {
const result = await getAdminGuideContent("test", section);
expect(result).not.toBeNull();
expect(mocks.reads).toContain(expectedTable);
for (const table of [weaponConditions, artifactConditions, constellationConditions, teamConditions]) {
if (table !== expectedTable) expect(mocks.reads).not.toContain(table);
}
expect(mocks.reads).not.toContain(extraSectionRows);
expect(mocks.reads).not.toContain(catalogMaterials);
expect(mocks.reads).not.toContain(guidePosterBleeding);
});
it("loads just the selected extra section", async () => {
const result = await getAdminGuideContent("test", "extra-b");
expect(result?.extras.map((extra) => extra.id)).toEqual(["extra-b"]);
expect(mocks.reads).toContain(extraSectionRows);
expect(mocks.reads).not.toContain(weaponConditions);
expect(mocks.reads).not.toContain(teamConditions);
});
it("preserves full-guide reads for existing preview and validation callers", async () => {
const result = await getAdminGuideContent("test");
for (const table of [weaponConditions, artifactConditions, constellationConditions, teamConditions, extraSectionRows]) {
expect(mocks.reads).toContain(table);
}
expect(result?.extras).toHaveLength(2);
});
it("resolves condition icons from one batched media read", async () => {
const iconMediaId = "550e8400-e29b-41d4-a716-446655440000";
mocks.rows.set(weaponConditions, [{ id: "weapon-condition", iconMediaId }]);
mocks.rows.set(artifactConditions, [{ id: "artifact-condition", iconMediaId }]);
mocks.rows.set(constellationConditions, [{ id: "constellation-condition", iconMediaId }]);
mocks.rows.set(teamConditions, [{ id: "team-condition", iconMediaId }]);
mocks.rows.set(artifactProfiles, [{
sands: [], goblet: [], circlet: [], substats: [], targets: [],
recommendedConditions: [{ name: "Recommended", note: null, iconMediaId, sands: [], goblet: [], circlet: [], substats: [] }],
targetConditions: [{ name: "Target", note: null, iconMediaId, rows: [] }],
}]);
mocks.rows.set(media, [{ id: iconMediaId, objectKey: "condition-icon.png" }]);
const result = await getAdminGuideContent("test");
expect(result?.weaponGroups[0].iconUrl).toBe("/media/condition-icon.png");
expect(result?.artifactGroups[0].iconUrl).toBe("/media/condition-icon.png");
expect(result?.constellationGroups[0].iconUrl).toBe("/media/condition-icon.png");
expect(result?.teamGroups[0].iconUrl).toBe("/media/condition-icon.png");
expect(result?.artifactProfile?.recommendedConditions[0].iconUrl).toBe("/media/condition-icon.png");
expect(result?.artifactProfile?.targetConditions[0].iconUrl).toBe("/media/condition-icon.png");
expect(mocks.reads.filter((table) => table === media)).toHaveLength(1);
});
});
describe("character material metadata", () => {
it.each(["catalog", "overview"] as const)(
"preserves ascension and leveling metadata in the %s read",
async (read) => {
const character = {
key: "test", imageKey: "character", constellations: [], talents: [],
ascension: [{ type: "ascension", items: [{ key: "known", amount: 2 }, { key: "missing/key", amount: 3 }] }],
leveling: [{ type: "leveling", items: [{ key: "missing/key", amount: 5 }, { key: "known", amount: 7 }] }],
};
const original = structuredClone(character);
mocks.rows.set(catalogCharacters, [character]);
mocks.rows.set(guides, [{
guide: { id: "guide", slug: "test", posterArtMediaId: null },
cover: { objectKey: "cover", status: "ready" },
character,
}]);
mocks.rows.set(catalogMaterials, [{
key: "known", name: "Known material", description: "Description",
rarity: 4, type: "upgrade", imageKey: "material.webp",
}]);
const result = read === "catalog"
? (await getActiveCatalog())?.characters[0]
: (await getAdminGuideContent("test", "overview"))?.character;
const known = {
key: "known", name: "Known material", description: "Description",
rarity: 4, type: "upgrade", imageUrl: "/media/material.webp",
lunarisUrl: "https://lunaris.moe/material/known", available: true,
};
const missing = {
key: "missing/key", name: "missing/key", description: null,
rarity: null, type: null, imageUrl: null,
lunarisUrl: "https://lunaris.moe/material/missing%2Fkey", available: false,
};
expect(result?.ascension).toEqual([{ type: "ascension", items: [{ ...known, amount: 2 }, { ...missing, amount: 3 }] }]);
expect(result?.leveling).toEqual([{ type: "leveling", items: [{ ...missing, amount: 5 }, { ...known, amount: 7 }] }]);
expect(mocks.reads.filter((table) => table === catalogMaterials)).toHaveLength(1);
expect(character).toEqual(original);
},
);
});
describe("team import lookup", () => {
it("hydrates matching teams and source labels with one member read", async () => {
mocks.rows.set(teamCompositions, [{
id: "team-1", title: "Example", rotationSeconds: "20", combo: "E > Q",
guideName: "Other Guide", conditionName: "Boss",
}]);
mocks.rows.set(teamMembers, [{
member: {
teamId: "team-1", characterKey: "character-1", weaponKey: "weapon-1",
artifactKey: "artifact-1", secondArtifactKey: null,
constellation: "2", refinement: "1", damage: "100",
},
characterName: "Character One",
}]);
const result = await listAdminTeamImportsForCharacter("current-guide", "character-1");
expect(result).toEqual([{
id: "team-1", title: "Example", rotationSeconds: "20", combo: "E > Q",
guideName: "Other Guide", conditionName: "Boss",
members: [{
characterKey: "character-1", characterName: "Character One", weaponKey: "weapon-1",
artifactKey: "artifact-1", secondArtifactKey: null,
constellation: "2", refinement: "1", damage: "100",
}],
}]);
expect(mocks.reads).toEqual([teamCompositions, teamMembers]);
});
});
describe("public overview build summary", () => {
it("loads small recommendation reads only for published sections", async () => {
mocks.rows.set(guideSections, [{ section: { id: "weapon", slug: "weapon", kind: "weapon" }, populated: true }]);
mocks.rows.set(weaponRecommendations, [
{ name: "First", rarity: 5, refinement: "1", conditionId: "first" },
{ name: "Four-star", rarity: 4, refinement: "5", conditionId: "first" },
{ name: "Different condition", rarity: 4, refinement: "1", conditionId: "second" },
]);
const result = await getPublicGuide("test");
expect(result?.buildSummary?.weapon?.name).toBe("First");
expect(result?.buildSummary?.fourStarWeapon?.name).toBe("Four-star");
expect(mocks.reads).not.toContain(artifactProfiles);
expect(mocks.reads).not.toContain(artifactRecommendations);
expect(mocks.reads).not.toContain(teamMembers);
});
it("does not add overview recommendation reads to admin editing", async () => {
await getAdminGuideContent("test", "overview");
expect(mocks.reads).not.toContain(weaponRecommendations);
expect(mocks.reads).not.toContain(artifactProfiles);
});
it("does not disclose recommendations from hidden sections", async () => {
mocks.rows.set(guideSections, []);
mocks.rows.set(weaponRecommendations, [{ name: "Hidden", conditionId: "hidden", rarity: 5, refinement: "1" }]);
const result = await getPublicGuide("test");
expect(result?.buildSummary?.weapon).toBeNull();
expect(mocks.reads).not.toContain(weaponRecommendations);
});
});
describe("catalog picker reads", () => {
it("selects only character picker and export artwork fields", async () => {
mocks.rows.set(catalogCharacters, [{ key: "test", name: "Test", imageKey: "avatar", coopImageKey: "splash" }]);
const catalog = await getCatalogOptions();
expect(catalog?.characters[0].splashImageUrl).toBe("/media/splash");
expect(mocks.reads).not.toContain(catalogMaterials);
expect(Object.keys(mocks.selections[0] as object).sort()).toEqual([
"key", "name", "rarity", "weaponType", "element", "imageKey", "elementIconKey", "coopImageKey",
].sort());
});
});