diff --git a/src/__tests__/services/progress/hearts-service.test.ts b/src/__tests__/services/progress/hearts-service.test.ts index f26e0a3..70cb10e 100644 --- a/src/__tests__/services/progress/hearts-service.test.ts +++ b/src/__tests__/services/progress/hearts-service.test.ts @@ -165,5 +165,23 @@ describe('hearts-service', () => { expect(db.update).toHaveBeenCalledOnce(); expect(vi.mocked(db.update).mock.results[0]?.value.set).toHaveBeenCalledWith({ heartsRemaining: 0 }); }); + + it('does not turn hearts negative when last restore time is in the future', async () => { + vi.mocked(db.select).mockReturnValueOnce( + selectReturning([ + { + tier: 'free', + heartsRemaining: 0, + heartsLastRestore: new Date(Date.now() + 13 * 30 * 60_000).toISOString(), + }, + ]) as never, + ); + + const { getHearts } = await import('../../../services/progress/hearts-service.js'); + const result = await getHearts('user-1'); + + expect(result.remaining).toBe(0); + expect(db.update).not.toHaveBeenCalled(); + }); }); }); diff --git a/src/services/progress/hearts-service.ts b/src/services/progress/hearts-service.ts index 17081b9..c915cf2 100644 --- a/src/services/progress/hearts-service.ts +++ b/src/services/progress/hearts-service.ts @@ -62,7 +62,8 @@ export async function getHearts(userId: string): Promise { // Calculate auto-restore if (lastMs !== null && remaining < MAX_FREE_HEARTS) { const elapsed = Date.now() - lastMs; - const restored = Math.floor(elapsed / RESTORE_INTERVAL_MS); + // 服务器时间回拨或历史脏数据可能让 lastRestore 落在未来;恢复次数不能为负。 + const restored = Math.max(0, Math.floor(elapsed / RESTORE_INTERVAL_MS)); remaining = Math.min(remaining + restored, MAX_FREE_HEARTS); if (restored > 0) {