fix: unlock exploration areas on zone unlock, correct quest_eternal count, fix quest status filter in timers
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m6s
CI / Lint, Build & Test (pull_request) Failing after 1m13s

This commit is contained in:
2026-04-06 16:03:52 -07:00
committed by Naomi Carrigan
parent 51cbd75ec0
commit 85a3c65d28
5 changed files with 67 additions and 5 deletions
+2 -2
View File
@@ -334,8 +334,8 @@ export const defaultAchievements: Array<Achievement> = [
unlockedAt: null, unlockedAt: null,
}, },
{ {
condition: { amount: 112, type: "questsCompleted" }, condition: { amount: 122, type: "questsCompleted" },
description: "Complete all 112 quests across the known multiverse.", description: "Complete all 122 quests across the known multiverse.",
icon: "🌌", icon: "🌌",
id: "quest_eternal", id: "quest_eternal",
name: "Quest Eternal", name: "Quest Eternal",
+14
View File
@@ -18,6 +18,7 @@ import {
import { Hono } from "hono"; import { Hono } from "hono";
import { defaultBosses } from "../data/bosses.js"; import { defaultBosses } from "../data/bosses.js";
import { defaultEquipmentSets } from "../data/equipmentSets.js"; import { defaultEquipmentSets } from "../data/equipmentSets.js";
import { defaultExplorations } from "../data/explorations.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";
import { updateChallengeProgress } from "../services/dailyChallenges.js"; import { updateChallengeProgress } from "../services/dailyChallenges.js";
@@ -284,6 +285,19 @@ bossRouter.post("/challenge", async(context) => {
continue; continue;
} }
zone.status = "unlocked"; zone.status = "unlocked";
// Unlock exploration areas for the newly unlocked zone
for (const area of state.exploration?.areas ?? []) {
const areaDefinition = defaultExplorations.find((explorationArea) => {
return explorationArea.id === area.id;
});
// eslint-disable-next-line capitalized-comments -- v8 ignore
/* v8 ignore next 3 -- @preserve */
if (areaDefinition?.zoneId === zone.id && area.status === "locked") {
area.status = "available";
}
}
const updatedZoneBosses = state.bosses.filter((b) => { const updatedZoneBosses = state.bosses.filter((b) => {
return b.zoneId === zone.id; return b.zoneId === zone.id;
}); });
+3 -1
View File
@@ -36,10 +36,11 @@ const getQuestTimers = (
}> => { }> => {
return state.quests. return state.quests.
filter((quest) => { filter((quest) => {
return quest.status === "in_progress" && quest.startedAt !== undefined; return quest.status === "active" && quest.startedAt !== undefined;
}). }).
map((quest) => { map((quest) => {
const durationMs = quest.durationSeconds * 1000; const durationMs = quest.durationSeconds * 1000;
// eslint-disable-next-line capitalized-comments -- v8 ignore
/* v8 ignore next -- @preserve */ /* v8 ignore next -- @preserve */
const endsAt = (quest.startedAt ?? 0) + durationMs; const endsAt = (quest.startedAt ?? 0) + durationMs;
return { return {
@@ -71,6 +72,7 @@ const getExplorationTimers = (
return area.status === "in_progress" && area.endsAt !== undefined; return area.status === "in_progress" && area.endsAt !== undefined;
}). }).
map((area) => { map((area) => {
// eslint-disable-next-line capitalized-comments -- v8 ignore
/* v8 ignore next -- @preserve */ /* v8 ignore next -- @preserve */
const endsAt = area.endsAt ?? 0; const endsAt = area.endsAt ?? 0;
return { return {
+46
View File
@@ -294,6 +294,52 @@ describe("boss route", () => {
expect(body.won).toBe(true); expect(body.won).toBe(true);
}); });
it("handles zone unlock gracefully when exploration state is undefined", async () => {
const state = makeState({
bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"],
adventurers: [makeAdventurer()] as GameState["adventurers"],
zones: [{ id: "test_zone", status: "locked", unlockBossId: "test_boss", unlockQuestId: null }] as GameState["zones"],
quests: [],
exploration: undefined,
});
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
const res = await challenge({ bossId: "test_boss" });
expect(res.status).toBe(200);
const body = await res.json() as { won: boolean };
expect(body.won).toBe(true);
});
it("unlocks exploration areas when a zone is unlocked on boss defeat", async () => {
const state = makeState({
bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"],
adventurers: [makeAdventurer()] as GameState["adventurers"],
zones: [{ id: "test_zone", status: "locked", unlockBossId: "test_boss", unlockQuestId: null }] as GameState["zones"],
quests: [],
exploration: {
areas: [{ id: "test_area", status: "locked" as const }],
materials: [],
craftedRecipeIds: [],
craftedGoldMultiplier: 1,
craftedEssenceMultiplier: 1,
craftedClickMultiplier: 1,
craftedCombatMultiplier: 1,
},
});
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
let savedState: GameState | undefined;
vi.mocked(prisma.gameState.update).mockImplementationOnce(async (args) => {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Test assertion */
savedState = (args as { data: { state: GameState } }).data.state;
return {} as never;
});
const res = await challenge({ bossId: "test_boss" });
expect(res.status).toBe(200);
// Exploration area should remain locked — no matching defaultExploration for "test_area"
const area = savedState?.exploration?.areas.find((a) => a.id === "test_area");
expect(area?.status).toBe("locked");
});
it("returns 500 when the database throws", async () => { it("returns 500 when the database throws", async () => {
vi.mocked(prisma.gameState.findUnique).mockRejectedValueOnce(new Error("DB error")); vi.mocked(prisma.gameState.findUnique).mockRejectedValueOnce(new Error("DB error"));
const res = await challenge({ bossId: "test_boss" }); const res = await challenge({ bossId: "test_boss" });
+2 -2
View File
@@ -71,7 +71,7 @@ describe("timers route", () => {
{ {
id: "q1", id: "q1",
name: "Forest Patrol", name: "Forest Patrol",
status: "in_progress", status: "active",
startedAt: startedAt, startedAt: startedAt,
durationSeconds: 600, durationSeconds: 600,
}, },
@@ -110,7 +110,7 @@ describe("timers route", () => {
{ {
id: "q1", id: "q1",
name: "Old Quest", name: "Old Quest",
status: "in_progress", status: "active",
startedAt: startedAt, startedAt: startedAt,
durationSeconds: 600, durationSeconds: 600,
}, },