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:
@@ -26,11 +26,23 @@ import { defaultGoddessExplorationAreas } from "../data/goddessExplorations.js";
|
|||||||
import { defaultGoddessQuests } from "../data/goddessQuests.js";
|
import { defaultGoddessQuests } from "../data/goddessQuests.js";
|
||||||
import { defaultGoddessUpgrades } from "../data/goddessUpgrades.js";
|
import { defaultGoddessUpgrades } from "../data/goddessUpgrades.js";
|
||||||
import { defaultGoddessZones } from "../data/goddessZones.js";
|
import { defaultGoddessZones } from "../data/goddessZones.js";
|
||||||
import { initialGameState, initialGoddessState } from "../data/initialState.js";
|
import {
|
||||||
|
initialGameState,
|
||||||
|
initialGoddessState,
|
||||||
|
initialVampireState,
|
||||||
|
} from "../data/initialState.js";
|
||||||
import { defaultQuests } from "../data/quests.js";
|
import { defaultQuests } from "../data/quests.js";
|
||||||
import { defaultRecipes } from "../data/recipes.js";
|
import { defaultRecipes } from "../data/recipes.js";
|
||||||
import { currentSchemaVersion } from "../data/schemaVersion.js";
|
import { currentSchemaVersion } from "../data/schemaVersion.js";
|
||||||
import { defaultUpgrades } from "../data/upgrades.js";
|
import { defaultUpgrades } from "../data/upgrades.js";
|
||||||
|
import { defaultVampireAchievements } from "../data/vampireAchievements.js";
|
||||||
|
import { defaultVampireBosses } from "../data/vampireBosses.js";
|
||||||
|
import { defaultVampireEquipment } from "../data/vampireEquipment.js";
|
||||||
|
import { defaultVampireExplorationAreas } from "../data/vampireExplorations.js";
|
||||||
|
import { defaultVampireQuests } from "../data/vampireQuests.js";
|
||||||
|
import { defaultVampireThralls } from "../data/vampireThralls.js";
|
||||||
|
import { defaultVampireUpgrades } from "../data/vampireUpgrades.js";
|
||||||
|
import { defaultVampireZones } from "../data/vampireZones.js";
|
||||||
import { defaultZones } from "../data/zones.js";
|
import { defaultZones } from "../data/zones.js";
|
||||||
import { prisma } from "../db/client.js";
|
import { prisma } from "../db/client.js";
|
||||||
import { authMiddleware } from "../middleware/auth.js";
|
import { authMiddleware } from "../middleware/auth.js";
|
||||||
@@ -602,6 +614,33 @@ const injectMissingGoddessExplorationAreas = (state: GameState): number => {
|
|||||||
return added;
|
return added;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects any vampire exploration areas from the defaults that are missing from
|
||||||
|
* the player's vampire exploration state, seeding each new area as locked.
|
||||||
|
* @param state - The player's current game state (mutated in place).
|
||||||
|
* @returns The number of vampire exploration areas that were added.
|
||||||
|
*/
|
||||||
|
const injectMissingVampireExplorationAreas = (state: GameState): number => {
|
||||||
|
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
||||||
|
/* v8 ignore next 3 -- @preserve */
|
||||||
|
if (state.vampire === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const existingIds = new Set(state.vampire.exploration.areas.map((area) => {
|
||||||
|
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
||||||
|
/* v8 ignore next -- @preserve */
|
||||||
|
return area.id;
|
||||||
|
}));
|
||||||
|
let added = 0;
|
||||||
|
for (const area of defaultVampireExplorationAreas) {
|
||||||
|
if (!existingIds.has(area.id)) {
|
||||||
|
state.vampire.exploration.areas.push({ id: area.id, status: "locked" });
|
||||||
|
added = added + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return added;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patches rewards on existing quests whose reward lists have grown since the
|
* Patches rewards on existing quests whose reward lists have grown since the
|
||||||
* save was created (e.g. A new upgrade added as a reward to an old quest).
|
* save was created (e.g. A new upgrade added as a reward to an old quest).
|
||||||
@@ -1030,6 +1069,14 @@ const syncNewContent = (
|
|||||||
questsPatched: number;
|
questsPatched: number;
|
||||||
upgradesAdded: number;
|
upgradesAdded: number;
|
||||||
upgradesPatched: number;
|
upgradesPatched: number;
|
||||||
|
vampireAchievementsAdded: number;
|
||||||
|
vampireBossesAdded: number;
|
||||||
|
vampireEquipmentAdded: number;
|
||||||
|
vampireExplorationAreasAdded: number;
|
||||||
|
vampireQuestsAdded: number;
|
||||||
|
vampireThrallsAdded: number;
|
||||||
|
vampireUpgradesAdded: number;
|
||||||
|
vampireZonesAdded: number;
|
||||||
zonesAdded: number;
|
zonesAdded: number;
|
||||||
zonesPatched: number;
|
zonesPatched: number;
|
||||||
} => {
|
} => {
|
||||||
@@ -1079,6 +1126,33 @@ const syncNewContent = (
|
|||||||
= injectMissingEntries(state.goddess.zones, defaultGoddessZones);
|
= injectMissingEntries(state.goddess.zones, defaultGoddessZones);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject missing vampire content for players who have entered the Vampire realm
|
||||||
|
let vampireAchievementsAdded = 0;
|
||||||
|
let vampireBossesAdded = 0;
|
||||||
|
let vampireEquipmentAdded = 0;
|
||||||
|
let vampireExplorationAreasAdded = 0;
|
||||||
|
let vampireQuestsAdded = 0;
|
||||||
|
let vampireThrallsAdded = 0;
|
||||||
|
let vampireUpgradesAdded = 0;
|
||||||
|
let vampireZonesAdded = 0;
|
||||||
|
if (state.vampire) {
|
||||||
|
vampireAchievementsAdded
|
||||||
|
= injectMissingEntries(state.vampire.achievements, defaultVampireAchievements);
|
||||||
|
vampireBossesAdded
|
||||||
|
= injectMissingEntries(state.vampire.bosses, defaultVampireBosses);
|
||||||
|
vampireEquipmentAdded
|
||||||
|
= injectMissingEntries(state.vampire.equipment, defaultVampireEquipment);
|
||||||
|
vampireExplorationAreasAdded = injectMissingVampireExplorationAreas(state);
|
||||||
|
vampireQuestsAdded
|
||||||
|
= injectMissingEntries(state.vampire.quests, defaultVampireQuests);
|
||||||
|
vampireThrallsAdded
|
||||||
|
= injectMissingEntries(state.vampire.thralls, defaultVampireThralls);
|
||||||
|
vampireUpgradesAdded
|
||||||
|
= injectMissingEntries(state.vampire.upgrades, defaultVampireUpgrades);
|
||||||
|
vampireZonesAdded
|
||||||
|
= injectMissingEntries(state.vampire.zones, defaultVampireZones);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
achievementsAdded,
|
achievementsAdded,
|
||||||
achievementsPatched,
|
achievementsPatched,
|
||||||
@@ -1104,6 +1178,14 @@ const syncNewContent = (
|
|||||||
questsPatched,
|
questsPatched,
|
||||||
upgradesAdded,
|
upgradesAdded,
|
||||||
upgradesPatched,
|
upgradesPatched,
|
||||||
|
vampireAchievementsAdded,
|
||||||
|
vampireBossesAdded,
|
||||||
|
vampireEquipmentAdded,
|
||||||
|
vampireExplorationAreasAdded,
|
||||||
|
vampireQuestsAdded,
|
||||||
|
vampireThrallsAdded,
|
||||||
|
vampireUpgradesAdded,
|
||||||
|
vampireZonesAdded,
|
||||||
zonesAdded,
|
zonesAdded,
|
||||||
zonesPatched,
|
zonesPatched,
|
||||||
};
|
};
|
||||||
@@ -1213,6 +1295,14 @@ debugRouter.post("/sync-new-content", async(context) => {
|
|||||||
questsPatched,
|
questsPatched,
|
||||||
upgradesAdded,
|
upgradesAdded,
|
||||||
upgradesPatched,
|
upgradesPatched,
|
||||||
|
vampireAchievementsAdded,
|
||||||
|
vampireBossesAdded,
|
||||||
|
vampireEquipmentAdded,
|
||||||
|
vampireExplorationAreasAdded,
|
||||||
|
vampireQuestsAdded,
|
||||||
|
vampireThrallsAdded,
|
||||||
|
vampireUpgradesAdded,
|
||||||
|
vampireZonesAdded,
|
||||||
zonesAdded,
|
zonesAdded,
|
||||||
zonesPatched,
|
zonesPatched,
|
||||||
} = syncNewContent(state);
|
} = syncNewContent(state);
|
||||||
@@ -1257,6 +1347,14 @@ debugRouter.post("/sync-new-content", async(context) => {
|
|||||||
state,
|
state,
|
||||||
upgradesAdded,
|
upgradesAdded,
|
||||||
upgradesPatched,
|
upgradesPatched,
|
||||||
|
vampireAchievementsAdded,
|
||||||
|
vampireBossesAdded,
|
||||||
|
vampireEquipmentAdded,
|
||||||
|
vampireExplorationAreasAdded,
|
||||||
|
vampireQuestsAdded,
|
||||||
|
vampireThrallsAdded,
|
||||||
|
vampireUpgradesAdded,
|
||||||
|
vampireZonesAdded,
|
||||||
zonesAdded,
|
zonesAdded,
|
||||||
zonesPatched,
|
zonesPatched,
|
||||||
});
|
});
|
||||||
@@ -1329,6 +1427,65 @@ debugRouter.post("/grant-apotheosis", async(context) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debugRouter.post("/grant-eternal-sovereignty", async(context) => {
|
||||||
|
try {
|
||||||
|
const discordId = context.get("discordId");
|
||||||
|
|
||||||
|
const record = await prisma.gameState.findUnique({ where: { discordId } });
|
||||||
|
if (!record) {
|
||||||
|
return context.json({ error: "No save found" }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Prisma returns JsonValue; double-cast required */
|
||||||
|
const state = record.state as unknown as GameState;
|
||||||
|
|
||||||
|
const updatedState: GameState
|
||||||
|
= (state.vampire?.eternalSovereignty.count ?? 0) >= 1
|
||||||
|
? state
|
||||||
|
: {
|
||||||
|
...state,
|
||||||
|
vampire: state.vampire
|
||||||
|
? { ...state.vampire, eternalSovereignty: { count: 1 } }
|
||||||
|
: { ...initialVampireState(), eternalSovereignty: { count: 1 } },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (updatedState !== state) {
|
||||||
|
const now = Date.now();
|
||||||
|
await prisma.gameState.update({
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Prisma requires object */
|
||||||
|
data: { state: updatedState as object, updatedAt: now },
|
||||||
|
where: { discordId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = process.env.ANTI_CHEAT_SECRET;
|
||||||
|
const signature
|
||||||
|
= secret === undefined
|
||||||
|
? undefined
|
||||||
|
: computeHmac(JSON.stringify(updatedState), secret);
|
||||||
|
|
||||||
|
return context.json({
|
||||||
|
currentSchemaVersion: currentSchemaVersion,
|
||||||
|
loginBonus: null,
|
||||||
|
loginStreak: 0,
|
||||||
|
offlineEssence: 0,
|
||||||
|
offlineGold: 0,
|
||||||
|
offlineSeconds: 0,
|
||||||
|
schemaOutdated: false,
|
||||||
|
signature: signature,
|
||||||
|
state: updatedState,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
void logger.error(
|
||||||
|
"debug_grant_eternal_sovereignty",
|
||||||
|
error instanceof Error
|
||||||
|
? error
|
||||||
|
: new Error(String(error)),
|
||||||
|
);
|
||||||
|
return context.json({ error: "Internal server error" }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
debugRouter.post("/hard-reset", async(context) => {
|
debugRouter.post("/hard-reset", async(context) => {
|
||||||
try {
|
try {
|
||||||
const discordId = context.get("discordId");
|
const discordId = context.get("discordId");
|
||||||
|
|||||||
@@ -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", () => {
|
describe("POST /grant-apotheosis", () => {
|
||||||
const grantApotheosis = () =>
|
const grantApotheosis = () =>
|
||||||
app.fetch(new Request("http://localhost/debug/grant-apotheosis", { method: "POST" }));
|
app.fetch(new Request("http://localhost/debug/grant-apotheosis", { method: "POST" }));
|
||||||
|
|||||||
@@ -604,6 +604,46 @@ interface SyncNewContentResponse {
|
|||||||
*/
|
*/
|
||||||
goddessZonesAdded: number;
|
goddessZonesAdded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of vampire achievements added to the save.
|
||||||
|
*/
|
||||||
|
vampireAchievementsAdded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of vampire bosses added to the save.
|
||||||
|
*/
|
||||||
|
vampireBossesAdded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of vampire thralls added to the save.
|
||||||
|
*/
|
||||||
|
vampireThrallsAdded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of vampire equipment items added to the save.
|
||||||
|
*/
|
||||||
|
vampireEquipmentAdded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of vampire exploration areas added to the save.
|
||||||
|
*/
|
||||||
|
vampireExplorationAreasAdded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of vampire quests added to the save.
|
||||||
|
*/
|
||||||
|
vampireQuestsAdded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of vampire upgrades added to the save.
|
||||||
|
*/
|
||||||
|
vampireUpgradesAdded: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of vampire zones added to the save.
|
||||||
|
*/
|
||||||
|
vampireZonesAdded: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HMAC-SHA256 signature of the updated state for anti-cheat chain continuity.
|
* HMAC-SHA256 signature of the updated state for anti-cheat chain continuity.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user