Reject unknown campaign battle reports

This commit is contained in:
2026-07-05 10:50:30 +09:00
parent a0ced53e8d
commit 118d668002
2 changed files with 53 additions and 7 deletions

View File

@@ -494,6 +494,37 @@ try {
`Expected malformed report fields to be normalized without discarding the report: ${JSON.stringify(malformedReport)}`
);
storage.clear();
storage.set(
campaignStorageKey,
JSON.stringify({
version: 1,
updatedAt: '2026-07-04T00:30:00.000Z',
step: 'fourth-camp',
firstBattleReport: {
battleId: 'unknown-battle-id',
battleTitle: 'Unknown Battle',
outcome: 'victory',
turnNumber: 4,
rewardGold: 120,
defeatedEnemies: 3,
totalEnemies: 5,
objectives: [],
units: [],
bonds: [],
itemRewards: [],
completedCampDialogues: [],
completedCampVisits: [],
createdAt: '2026-07-04T00:30:00.000Z'
}
})
);
const unknownReport = loadCampaignState();
assert(
unknownReport.step === 'fourth-camp' && unknownReport.firstBattleReport === undefined,
`Expected first battle report with unknown battle id to be discarded safely: ${JSON.stringify(unknownReport)}`
);
storage.clear();
storage.set(
campaignStorageKey,
@@ -552,6 +583,19 @@ try {
],
completedAt: '2026-07-04T01:00:00.000Z'
},
'unknown-battle-key': {
battleId: 'unknown-battle-id',
battleTitle: 'Unknown Battle',
outcome: 'victory',
rewardGold: 999,
itemRewards: [],
campaignRewards: { supplies: [], equipment: [], reputation: [], recruits: [], unlocks: [] },
objectives: [],
units: [],
bonds: [],
reserveTraining: [],
completedAt: '2026-07-04T02:00:00.000Z'
},
corrupted: 'not-a-settlement'
}
})

View File

@@ -950,9 +950,10 @@ function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettle
}
const settlement = value as Partial<CampaignBattleSettlement>;
const battleId = typeof settlement.battleId === 'string' ? settlement.battleId : '';
if (
typeof settlement.battleId !== 'string' ||
settlement.battleId.length === 0 ||
battleId.length === 0 ||
!(battleId in battleScenarios) ||
typeof settlement.battleTitle !== 'string' ||
settlement.battleTitle.length === 0 ||
(settlement.outcome !== 'victory' && settlement.outcome !== 'defeat') ||
@@ -963,7 +964,7 @@ function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettle
}
return {
battleId: settlement.battleId,
battleId,
battleTitle: settlement.battleTitle,
outcome: settlement.outcome,
rewardGold: normalizeNonNegativeInteger(settlement.rewardGold),
@@ -1112,9 +1113,10 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
return undefined;
}
const battleId = typeof report.battleId === 'string' ? report.battleId : '';
if (
typeof report.battleId !== 'string' ||
report.battleId.length === 0 ||
battleId.length === 0 ||
!(battleId in battleScenarios) ||
(report.outcome !== 'victory' && report.outcome !== 'defeat')
) {
return undefined;
@@ -1123,13 +1125,13 @@ function normalizeFirstBattleReport(report: unknown): FirstBattleReport | undefi
const battleTitle =
typeof report.battleTitle === 'string' && report.battleTitle.length > 0
? report.battleTitle
: battleScenarios[report.battleId as BattleScenarioId]?.title;
: battleScenarios[battleId as BattleScenarioId]?.title;
if (!battleTitle) {
return undefined;
}
return {
battleId: report.battleId,
battleId,
battleTitle,
outcome: report.outcome,
turnNumber: normalizeNonNegativeInteger(report.turnNumber),