generated from nhcarrigan/template
feat: vampire syncNewContent injection and grant-eternal-sovereignty debug endpoint
Adds vampire parity with existing goddess debug tooling: - `SyncNewContentResponse` type gains vampire count fields - `syncNewContent` injects missing vampire content arrays for players who have entered the vampire realm (achievements, bosses, equipment, exploration areas, quests, thralls, upgrades, zones) - `/grant-eternal-sovereignty` debug endpoint mirrors `/grant-apotheosis`, setting `vampire.eternalSovereignty.count = 1` for saves that need it - Full test coverage for all new paths in debug.spec.ts
This commit is contained in:
@@ -1206,6 +1206,198 @@ describe("debug route", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /sync-new-content — vampire injection", () => {
|
||||
const syncNewContent = () =>
|
||||
app.fetch(new Request("http://localhost/debug/sync-new-content", { method: "POST" }));
|
||||
|
||||
const makeVampireState = (): NonNullable<GameState["vampire"]> => ({
|
||||
achievements: [],
|
||||
awakening: { count: 0, purchasedUpgradeIds: [], soulShards: 0, soulShardsBloodMultiplier: 1, soulShardsCombatMultiplier: 1, soulShardsMetaMultiplier: 1, soulShardsSiringIchorMultiplier: 1, soulShardsSiringThresholdMultiplier: 1 },
|
||||
baseClickPower: 1,
|
||||
bosses: [],
|
||||
equipment: [],
|
||||
eternalSovereignty: { count: 0 },
|
||||
exploration: { areas: [], craftedBloodMultiplier: 1, craftedCombatMultiplier: 1, craftedIchorMultiplier: 1, craftedRecipeIds: [], materials: [] },
|
||||
lastTickAt: 0,
|
||||
lifetimeBloodEarned: 0,
|
||||
lifetimeBossesDefeated: 0,
|
||||
lifetimeQuestsCompleted: 0,
|
||||
quests: [],
|
||||
siring: { count: 0, ichor: 0, ichorBloodMultiplier: 1, ichorCombatMultiplier: 1, ichorThrallsMultiplier: 1, purchasedUpgradeIds: [] },
|
||||
thralls: [],
|
||||
totalBloodEarned: 0,
|
||||
upgrades: [],
|
||||
zones: [],
|
||||
});
|
||||
|
||||
it("injects vampire content arrays when state.vampire exists with empty arrays", async () => {
|
||||
const state = makeState({ vampire: makeVampireState() });
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
const res = await syncNewContent();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { vampireAchievementsAdded: number; vampireBossesAdded: number; vampireQuestsAdded: number; vampireZonesAdded: number };
|
||||
expect(body.vampireAchievementsAdded).toBeGreaterThan(0);
|
||||
expect(body.vampireBossesAdded).toBeGreaterThan(0);
|
||||
expect(body.vampireQuestsAdded).toBeGreaterThan(0);
|
||||
expect(body.vampireZonesAdded).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("returns zero vampire counts when state.vampire is undefined", async () => {
|
||||
const state = makeState();
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
const res = await syncNewContent();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { vampireAchievementsAdded: number; vampireBossesAdded: number; vampireEquipmentAdded: number; vampireExplorationAreasAdded: number; vampireQuestsAdded: number; vampireThrallsAdded: number; vampireUpgradesAdded: number; vampireZonesAdded: number };
|
||||
expect(body.vampireAchievementsAdded).toBe(0);
|
||||
expect(body.vampireBossesAdded).toBe(0);
|
||||
expect(body.vampireEquipmentAdded).toBe(0);
|
||||
expect(body.vampireExplorationAreasAdded).toBe(0);
|
||||
expect(body.vampireQuestsAdded).toBe(0);
|
||||
expect(body.vampireThrallsAdded).toBe(0);
|
||||
expect(body.vampireUpgradesAdded).toBe(0);
|
||||
expect(body.vampireZonesAdded).toBe(0);
|
||||
});
|
||||
|
||||
it("injects vampire exploration areas when vampire has no areas", async () => {
|
||||
const state = makeState({ vampire: makeVampireState() });
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
const res = await syncNewContent();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { vampireExplorationAreasAdded: number };
|
||||
expect(body.vampireExplorationAreasAdded).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /grant-eternal-sovereignty", () => {
|
||||
const grantEternalSovereignty = () =>
|
||||
app.fetch(new Request("http://localhost/debug/grant-eternal-sovereignty", { method: "POST" }));
|
||||
|
||||
it("returns 404 when no save is found", async () => {
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce(null);
|
||||
const res = await grantEternalSovereignty();
|
||||
expect(res.status).toBe(404);
|
||||
const body = await res.json() as { error: string };
|
||||
expect(body.error).toContain("No save found");
|
||||
});
|
||||
|
||||
it("returns 200 with unchanged state when eternalSovereignty count is already >= 1", async () => {
|
||||
const vampire: NonNullable<GameState["vampire"]> = {
|
||||
achievements: [],
|
||||
awakening: { count: 0, purchasedUpgradeIds: [], soulShards: 0, soulShardsBloodMultiplier: 1, soulShardsCombatMultiplier: 1, soulShardsMetaMultiplier: 1, soulShardsSiringIchorMultiplier: 1, soulShardsSiringThresholdMultiplier: 1 },
|
||||
baseClickPower: 1,
|
||||
bosses: [],
|
||||
equipment: [],
|
||||
eternalSovereignty: { count: 1 },
|
||||
exploration: { areas: [], craftedBloodMultiplier: 1, craftedCombatMultiplier: 1, craftedIchorMultiplier: 1, craftedRecipeIds: [], materials: [] },
|
||||
lastTickAt: 0,
|
||||
lifetimeBloodEarned: 0,
|
||||
lifetimeBossesDefeated: 0,
|
||||
lifetimeQuestsCompleted: 0,
|
||||
quests: [],
|
||||
siring: { count: 0, ichor: 0, ichorBloodMultiplier: 1, ichorCombatMultiplier: 1, ichorThrallsMultiplier: 1, purchasedUpgradeIds: [] },
|
||||
thralls: [],
|
||||
totalBloodEarned: 0,
|
||||
upgrades: [],
|
||||
zones: [],
|
||||
};
|
||||
const state = makeState({ vampire });
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
const res = await grantEternalSovereignty();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { state: GameState };
|
||||
expect(body.state.vampire?.eternalSovereignty.count).toBe(1);
|
||||
expect(vi.mocked(prisma.gameState.update)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns 200 and grants eternal sovereignty when not yet granted", async () => {
|
||||
const state = makeState();
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
const res = await grantEternalSovereignty();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { state: GameState };
|
||||
expect(body.state.vampire?.eternalSovereignty.count).toBe(1);
|
||||
});
|
||||
|
||||
it("returns 200 and sets eternalSovereignty when vampire exists with count 0", async () => {
|
||||
const vampire: NonNullable<GameState["vampire"]> = {
|
||||
achievements: [],
|
||||
awakening: { count: 0, purchasedUpgradeIds: [], soulShards: 0, soulShardsBloodMultiplier: 1, soulShardsCombatMultiplier: 1, soulShardsMetaMultiplier: 1, soulShardsSiringIchorMultiplier: 1, soulShardsSiringThresholdMultiplier: 1 },
|
||||
baseClickPower: 1,
|
||||
bosses: [],
|
||||
equipment: [],
|
||||
eternalSovereignty: { count: 0 },
|
||||
exploration: { areas: [], craftedBloodMultiplier: 1, craftedCombatMultiplier: 1, craftedIchorMultiplier: 1, craftedRecipeIds: [], materials: [] },
|
||||
lastTickAt: 0,
|
||||
lifetimeBloodEarned: 0,
|
||||
lifetimeBossesDefeated: 0,
|
||||
lifetimeQuestsCompleted: 0,
|
||||
quests: [],
|
||||
siring: { count: 0, ichor: 0, ichorBloodMultiplier: 1, ichorCombatMultiplier: 1, ichorThrallsMultiplier: 1, purchasedUpgradeIds: [] },
|
||||
thralls: [],
|
||||
totalBloodEarned: 0,
|
||||
upgrades: [],
|
||||
zones: [],
|
||||
};
|
||||
const state = makeState({ vampire });
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
const res = await grantEternalSovereignty();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { state: GameState };
|
||||
expect(body.state.vampire?.eternalSovereignty.count).toBe(1);
|
||||
});
|
||||
|
||||
it("returns 200 with HMAC signature when ANTI_CHEAT_SECRET is set", async () => {
|
||||
process.env.ANTI_CHEAT_SECRET = "test_secret";
|
||||
const vampire: NonNullable<GameState["vampire"]> = {
|
||||
achievements: [],
|
||||
awakening: { count: 0, purchasedUpgradeIds: [], soulShards: 0, soulShardsBloodMultiplier: 1, soulShardsCombatMultiplier: 1, soulShardsMetaMultiplier: 1, soulShardsSiringIchorMultiplier: 1, soulShardsSiringThresholdMultiplier: 1 },
|
||||
baseClickPower: 1,
|
||||
bosses: [],
|
||||
equipment: [],
|
||||
eternalSovereignty: { count: 1 },
|
||||
exploration: { areas: [], craftedBloodMultiplier: 1, craftedCombatMultiplier: 1, craftedIchorMultiplier: 1, craftedRecipeIds: [], materials: [] },
|
||||
lastTickAt: 0,
|
||||
lifetimeBloodEarned: 0,
|
||||
lifetimeBossesDefeated: 0,
|
||||
lifetimeQuestsCompleted: 0,
|
||||
quests: [],
|
||||
siring: { count: 0, ichor: 0, ichorBloodMultiplier: 1, ichorCombatMultiplier: 1, ichorThrallsMultiplier: 1, purchasedUpgradeIds: [] },
|
||||
thralls: [],
|
||||
totalBloodEarned: 0,
|
||||
upgrades: [],
|
||||
zones: [],
|
||||
};
|
||||
const state = makeState({ vampire });
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
const res = await grantEternalSovereignty();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { signature: string | undefined };
|
||||
expect(body.signature).toBeDefined();
|
||||
delete process.env.ANTI_CHEAT_SECRET;
|
||||
});
|
||||
|
||||
it("returns 500 when DB throws an Error", async () => {
|
||||
vi.mocked(prisma.gameState.findUnique).mockRejectedValueOnce(new Error("DB error"));
|
||||
const res = await grantEternalSovereignty();
|
||||
expect(res.status).toBe(500);
|
||||
const body = await res.json() as { error: string };
|
||||
expect(body.error).toContain("Internal server error");
|
||||
});
|
||||
|
||||
it("returns 500 when DB throws a non-Error value", async () => {
|
||||
vi.mocked(prisma.gameState.findUnique).mockRejectedValueOnce("raw error");
|
||||
const res = await grantEternalSovereignty();
|
||||
expect(res.status).toBe(500);
|
||||
const body = await res.json() as { error: string };
|
||||
expect(body.error).toContain("Internal server error");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /grant-apotheosis", () => {
|
||||
const grantApotheosis = () =>
|
||||
app.fetch(new Request("http://localhost/debug/grant-apotheosis", { method: "POST" }));
|
||||
|
||||
Reference in New Issue
Block a user