Prevent negative heart recovery from future restore times
All checks were successful
CI/CD Pipeline / Unit Tests (push) Successful in 24s
CI/CD Pipeline / Build & Deploy Test (push) Has been skipped
CI/CD Pipeline / Build & Deploy Production (push) Successful in 1m31s

This commit is contained in:
Wang Zhuoxuan 2026-06-10 22:25:25 +08:00
parent c29599daaa
commit d967d11672
2 changed files with 20 additions and 1 deletions

View File

@ -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();
});
});
});

View File

@ -62,7 +62,8 @@ export async function getHearts(userId: string): Promise<HeartsInfo> {
// 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) {