From 90c9e6b4284f18278c49d08cd5174ac72888c2c7 Mon Sep 17 00:00:00 2001 From: Mateus Boergeson Date: Sat, 29 Aug 2026 14:30:27 +0200 Subject: [PATCH 1/2] fix(result): keep incomplete test xp on zen results --- backend/src/api/controllers/result.ts | 47 ++++++++++++++------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/backend/src/api/controllers/result.ts b/backend/src/api/controllers/result.ts index 1353f4e3a1a1..005ed31ef34d 100644 --- a/backend/src/api/controllers/result.ts +++ b/backend/src/api/controllers/result.ts @@ -734,7 +734,7 @@ async function calculateXp( funboxBonus: funboxBonusConfiguration, } = xpConfiguration; - if (mode === "zen" || !enabled) { + if (!enabled) { return { xp: 0, }; @@ -742,6 +742,30 @@ async function calculateXp( const breakdown: XpBreakdown = {}; + let incompleteXp = 0; + if (incompleteTests !== undefined && incompleteTests.length > 0) { + incompleteTests.forEach((it: { acc: number; seconds: number }) => { + let mod = (it.acc - 50) / 50; + if (mod < 0) mod = 0; + incompleteXp += Math.round(it.seconds * mod); + }); + breakdown.incomplete = incompleteXp; + } else if (incompleteTestSeconds && incompleteTestSeconds > 0) { + incompleteXp = Math.round(incompleteTestSeconds); + breakdown.incomplete = incompleteXp; + } + + if (gainMultiplier !== 1) { + breakdown.configMultiplier = gainMultiplier; + } + + if (mode === "zen") { + return { + xp: Math.round(incompleteXp * gainMultiplier), + breakdown, + }; + } + const baseXp = Math.round((testDuration - afkDuration) * 2); breakdown.base = baseXp; @@ -807,19 +831,6 @@ async function calculateXp( } } - let incompleteXp = 0; - if (incompleteTests !== undefined && incompleteTests.length > 0) { - incompleteTests.forEach((it: { acc: number; seconds: number }) => { - let mod = (it.acc - 50) / 50; - if (mod < 0) mod = 0; - incompleteXp += Math.round(it.seconds * mod); - }); - breakdown.incomplete = incompleteXp; - } else if (incompleteTestSeconds && incompleteTestSeconds > 0) { - incompleteXp = Math.round(incompleteTestSeconds); - breakdown.incomplete = incompleteXp; - } - const accuracyModifier = (acc - 50) / 50; let dailyBonus = 0; @@ -844,14 +855,6 @@ async function calculateXp( const totalXp = Math.round((xpAfterAccuracy + incompleteXp) * gainMultiplier) + dailyBonus; - if (gainMultiplier !== 1) { - // breakdown.push([ - // "configMultiplier", - // Math.round((xpAfterAccuracy + incompleteXp) * (gainMultiplier - 1)), - // ]); - breakdown.configMultiplier = gainMultiplier; - } - const isAwardingDailyBonus = dailyBonus > 0; return { From 3e84b1fdb51802369c1229c9373d988fa5c36e09 Mon Sep 17 00:00:00 2001 From: Mateus Boergeson Date: Sat, 29 Aug 2026 14:30:29 +0200 Subject: [PATCH 2/2] test(result): cover incomplete xp for zen --- .../__tests__/api/controllers/result.spec.ts | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/backend/__tests__/api/controllers/result.spec.ts b/backend/__tests__/api/controllers/result.spec.ts index 516f66549f3b..f035f276cd10 100644 --- a/backend/__tests__/api/controllers/result.spec.ts +++ b/backend/__tests__/api/controllers/result.spec.ts @@ -687,6 +687,28 @@ describe("result controller test", () => { 15.1 + 2 - 5, //duration + incompleteTestSeconds-afk ); }); + it("should keep incomplete xp for zen", async () => { + //GIVEN + await enableUsersXpGain(true, 1); + const completedEvent = buildCompletedEvent({ + mode: "zen", + mode2: "zen", + }); + + //WHEN + const { body } = await mockApp + .post("/results") + .set("Authorization", `Bearer ${uid}`) + .send({ + result: completedEvent, + }) + .expect(200); + + //THEN + expect(body.data.xp).toEqual(5); + expect(body.data.xpBreakdown).toEqual({ incomplete: 5 }); + expect(userIncrementXpMock).toHaveBeenCalledWith(uid, 5); + }); it("should fail if result saving is disabled", async () => { //GIVEN await enableResultsSaving(false); @@ -882,9 +904,17 @@ async function enableResultsSaving(enabled: boolean): Promise { mockConfig, ); } -async function enableUsersXpGain(enabled: boolean): Promise { +async function enableUsersXpGain( + enabled: boolean, + gainMultiplier = 0, +): Promise { const mockConfig = await configuration; - mockConfig.users.xp = { ...mockConfig.users.xp, enabled, funboxBonus: 1 }; + mockConfig.users.xp = { + ...mockConfig.users.xp, + enabled, + funboxBonus: 1, + gainMultiplier, + }; vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue( mockConfig,