feat(scheduler): persist recurring daily starts
Add a local-calendar daily start scheduler with pending receipts, per-day deduplication, DST-safe target calculation, missed-day recovery, and retryable account/start failure outcomes. Persist and validate the daily rule, expose its next target to the renderer, preserve legacy one-time schedules until a daily rule is saved, and gate boot auto-resume on recorded active-run evidence. Wire boot, settings, account, suspend, resume, interval, and shutdown lifecycle handling with focused RED/GREEN coverage for calendar, persistence, renderer, and controller boundaries.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { defaultSettings } from "../src/main/constants";
|
||||
import { createRendererSettings } from "../src/main/renderer-state";
|
||||
import { validateRendererSettingsUpdate } from "../src/main/renderer-settings";
|
||||
@@ -73,4 +73,54 @@ describe("renderer settings validation", () => {
|
||||
expect(projected.archivePasswordListConfigured).toBe(true);
|
||||
expect(JSON.stringify(projected)).not.toContain(password);
|
||||
});
|
||||
|
||||
it("projects daily start state and accepts only editable calendar controls", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date(2026, 7, 22, 9, 0));
|
||||
try {
|
||||
const current = {
|
||||
...defaultSettings(),
|
||||
dailyStartEnabled: true,
|
||||
dailyStartMinuteOfDay: 18 * 60 + 45,
|
||||
dailyStartFirstLocalDate: "2026-08-23",
|
||||
dailyStartLastHandledLocalDate: "2026-08-22",
|
||||
dailyStartPendingLocalDate: "",
|
||||
dailyStartLastOutcome: "started" as const
|
||||
};
|
||||
const projected = createRendererSettings(current);
|
||||
|
||||
expect(projected).toMatchObject({
|
||||
dailyStartEnabled: true,
|
||||
dailyStartMinuteOfDay: 18 * 60 + 45,
|
||||
dailyStartFirstLocalDate: "2026-08-23",
|
||||
dailyStartLastHandledLocalDate: "2026-08-22",
|
||||
dailyStartPendingLocalDate: "",
|
||||
dailyStartLastOutcome: "started"
|
||||
});
|
||||
expect(projected.nextDailyStartEpochMs).toBe(new Date(2026, 7, 23, 18, 45, 0, 0).getTime());
|
||||
expect(validateRendererSettingsUpdate({
|
||||
dailyStartEnabled: true,
|
||||
dailyStartMinuteOfDay: 7 * 60 + 30,
|
||||
dailyStartFirstLocalDate: "2026-08-24",
|
||||
dailyStartLastHandledLocalDate: "2026-08-23",
|
||||
dailyStartPendingLocalDate: "2026-08-24",
|
||||
dailyStartLastOutcome: "missed",
|
||||
nextDailyStartEpochMs: 123
|
||||
}, current)).toEqual({
|
||||
dailyStartEnabled: true,
|
||||
dailyStartMinuteOfDay: 7 * 60 + 30,
|
||||
dailyStartFirstLocalDate: "2026-08-24"
|
||||
});
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects invalid editable daily calendar values at the renderer boundary", () => {
|
||||
const current = defaultSettings();
|
||||
|
||||
expect(() => validateRendererSettingsUpdate({ dailyStartMinuteOfDay: 1_440 }, current)).toThrow("Settings-Payload ist ungültig");
|
||||
expect(() => validateRendererSettingsUpdate({ dailyStartMinuteOfDay: 12.5 }, current)).toThrow("Settings-Payload ist ungültig");
|
||||
expect(() => validateRendererSettingsUpdate({ dailyStartFirstLocalDate: "2026-02-29" }, current)).toThrow("Settings-Payload ist ungültig");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user