chore(backup): polish — boundary test, roundtrip lock-in, precompute today
Code reviewer minor nitpicks: - Add test for inkling-2026-02-30.sqlite (locks roundtrip-validation contract) - Add test for weekly window inclusive at oldest boundary - Precompute today=startOfDayUtc(now) once outside the loop, pass to helpers No behavior change; tests added cover existing semantics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,17 +24,15 @@ function startOfDayUtc(d: Date): Date {
|
||||
return x;
|
||||
}
|
||||
|
||||
function isWithinDailyWindow(fileDate: Date, now: Date): boolean {
|
||||
const today = startOfDayUtc(now);
|
||||
function isWithinDailyWindow(fileDate: Date, today: Date): boolean {
|
||||
const oldest = new Date(today.getTime() - (DAILY_WINDOW_DAYS - 1) * ONE_DAY_MS);
|
||||
return fileDate >= oldest && fileDate <= today;
|
||||
}
|
||||
|
||||
function isWithinWeeklyWindow(fileDate: Date, now: Date): boolean {
|
||||
function isWithinWeeklyWindow(fileDate: Date, today: Date): boolean {
|
||||
// UTC-based Monday detection. UTCDay: 0=Sun, 1=Mon..6=Sat
|
||||
if (fileDate.getUTCDay() !== 1) return false;
|
||||
const today = startOfDayUtc(now);
|
||||
// Weekly window: anchor on the most recent Monday on/before `now`, then reach back
|
||||
// Weekly window: anchor on the most recent Monday on/before `today`, then reach back
|
||||
// WEEKLY_WINDOW_PRIOR_MONDAYS * 7 days from that anchor. Effective semantic:
|
||||
// up to 5 distinct Mondays kept (the anchor Monday + 4 prior). The plan's commit
|
||||
// header says "4 weekly Mondays" but the plan's test case at
|
||||
@@ -47,9 +45,8 @@ function isWithinWeeklyWindow(fileDate: Date, now: Date): boolean {
|
||||
return fileDate >= oldest && fileDate <= today;
|
||||
}
|
||||
|
||||
function isWithinMonthlyWindow(fileDate: Date, now: Date): boolean {
|
||||
function isWithinMonthlyWindow(fileDate: Date, today: Date): boolean {
|
||||
if (fileDate.getUTCDate() !== 1) return false;
|
||||
const today = startOfDayUtc(now);
|
||||
// months ago: difference in calendar months
|
||||
const monthsAgo =
|
||||
(today.getUTCFullYear() - fileDate.getUTCFullYear()) * 12 +
|
||||
@@ -60,18 +57,19 @@ function isWithinMonthlyWindow(fileDate: Date, now: Date): boolean {
|
||||
export function applyGfsRetention(filenames: string[], now: Date): RetentionResult {
|
||||
const keep: string[] = [];
|
||||
const remove: string[] = [];
|
||||
const today = startOfDayUtc(now);
|
||||
for (const name of filenames) {
|
||||
const iso = parseBackupFilename(name);
|
||||
if (iso === null) continue; // unrecognized — ignore (no-op)
|
||||
const fileDate = new Date(iso + 'T00:00:00Z');
|
||||
if (fileDate > startOfDayUtc(now)) {
|
||||
if (fileDate > today) {
|
||||
keep.push(name); // future-dated — clock skew safety
|
||||
continue;
|
||||
}
|
||||
const survives =
|
||||
isWithinDailyWindow(fileDate, now) ||
|
||||
isWithinWeeklyWindow(fileDate, now) ||
|
||||
isWithinMonthlyWindow(fileDate, now);
|
||||
isWithinDailyWindow(fileDate, today) ||
|
||||
isWithinWeeklyWindow(fileDate, today) ||
|
||||
isWithinMonthlyWindow(fileDate, today);
|
||||
if (survives) keep.push(name);
|
||||
else remove.push(name);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ describe('parseBackupFilename', () => {
|
||||
expect(parseBackupFilename('inkling-2026-13-99.sqlite')).toBeNull();
|
||||
expect(parseBackupFilename('.last-snapshot')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for date that JS would silently coerce (roundtrip lock-in)', () => {
|
||||
// Without the toISOString roundtrip check, JS coerces 2026-02-30 to 2026-03-02.
|
||||
// This test locks in the roundtrip-validation contract.
|
||||
expect(parseBackupFilename('inkling-2026-02-30.sqlite')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyGfsRetention', () => {
|
||||
@@ -52,6 +58,14 @@ describe('applyGfsRetention', () => {
|
||||
expect(r.remove).toContain('inkling-2026-03-09.sqlite');
|
||||
});
|
||||
|
||||
it('weekly window is inclusive at oldest boundary', () => {
|
||||
// 2026-03-23 is exactly 4*7 days before anchor Monday 2026-04-20.
|
||||
// Locks in the boundary semantic explicitly.
|
||||
const r = applyGfsRetention(names('2026-03-23', '2026-03-16'), NOW);
|
||||
expect(r.keep).toContain('inkling-2026-03-23.sqlite');
|
||||
expect(r.remove).toContain('inkling-2026-03-16.sqlite');
|
||||
});
|
||||
|
||||
it('keeps month-firsts within last 6 months', () => {
|
||||
// Last 6 month-firsts from 2026-04-26: 2026-04-01, 2026-03-01, 2026-02-01,
|
||||
// 2026-01-01, 2025-12-01, 2025-11-01
|
||||
|
||||
Reference in New Issue
Block a user