Account Modal Bugfix (#3687)

## Description:

Fix null stat values from LEFT JOIN causing Zod validation failure on
player profiles

https://github.com/openfrontio/infra/pull/316 switched playerStats from
innerJoin to leftJoin so that sessions with no stats row (games that
ended instantly on spawn) are still counted in wins/losses/total.


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

w.o.n
This commit is contained in:
Ryan
2026-04-15 23:14:11 +01:00
committed by GitHub
parent 3a49b9a794
commit f32994fbc7
2 changed files with 30 additions and 0 deletions
+1
View File
@@ -94,6 +94,7 @@ export const OTHER_INDEX_LOST = 3; // Structures/warships destroyed/captured by
export const OTHER_INDEX_UPGRADE = 4; // Structures upgraded
export const BigIntStringSchema = z.preprocess((val) => {
if (val === null) return 0n;
if (typeof val === "string" && /^-?\d+$/.test(val)) return BigInt(val);
if (typeof val === "bigint") return val;
return val;
+29
View File
@@ -1,3 +1,4 @@
import { PlayerStatsLeafSchema } from "../src/core/ApiSchemas";
import { PlayerStatsSchema } from "../src/core/StatsSchemas";
function testPlayerSchema(
@@ -45,4 +46,32 @@ describe("StatsSchema", () => {
testPlayerSchema("{", false, true);
testPlayerSchema("{}}", false, true);
});
test("null array elements coerce to 0n (LEFT JOIN rows with no stats)", () => {
// Postgres SUM() over all-NULL rows returns NULL. These should parse as 0n.
testPlayerSchema(
'{"attacks":[null,null,null],"betrayals":null,"gold":[null,null,null,null,null,null]}',
);
});
});
describe("PlayerStatsLeafSchema", () => {
test("null stat values coerce to 0n", () => {
const result = PlayerStatsLeafSchema.safeParse({
wins: "0",
losses: "1",
total: "1",
stats: { attacks: [null, null, null], betrayals: null },
});
expect(result.success).toBe(true);
});
test("missing required field (wins) still fails — undefined is not coerced", () => {
const result = PlayerStatsLeafSchema.safeParse({
losses: "1",
total: "1",
stats: {},
});
expect(result.success).toBe(false);
});
});