Fix email timing and show fallback data when Garmin sync pending
All checks were successful
Deploy / deploy (push) Successful in 2m31s
All checks were successful
Deploy / deploy (push) Successful in 2m31s
- Add 15-minute notification granularity (*/15 cron) so users get emails at their configured time instead of rounding to the nearest hour - Add DailyLog fallback to most recent when today's log doesn't exist, preventing 100/100/Unknown default values before morning sync - Show "Last synced" indicator when displaying stale data - Change Garmin sync to 6-hour intervals (0,6,12,18 UTC) to ensure data is available before European morning notifications Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -205,6 +205,112 @@ describe("POST /api/cron/notifications", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Quarter-hour time matching", () => {
|
||||
it("sends notification at exact 15-minute slot (07:15)", async () => {
|
||||
// Current time is 07:15 UTC
|
||||
vi.setSystemTime(new Date("2025-01-15T07:15:00Z"));
|
||||
mockUsers = [
|
||||
createMockUser({ notificationTime: "07:15", timezone: "UTC" }),
|
||||
];
|
||||
mockDailyLogs = [createMockDailyLog()];
|
||||
|
||||
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockSendDailyEmail).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rounds down notification time to nearest 15-minute slot (07:10 -> 07:00)", async () => {
|
||||
// Current time is 07:00 UTC
|
||||
vi.setSystemTime(new Date("2025-01-15T07:00:00Z"));
|
||||
// User set 07:10, which rounds down to 07:00 slot
|
||||
mockUsers = [
|
||||
createMockUser({ notificationTime: "07:10", timezone: "UTC" }),
|
||||
];
|
||||
mockDailyLogs = [createMockDailyLog()];
|
||||
|
||||
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockSendDailyEmail).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rounds down notification time (07:29 -> 07:15)", async () => {
|
||||
// Current time is 07:15 UTC
|
||||
vi.setSystemTime(new Date("2025-01-15T07:15:00Z"));
|
||||
// User set 07:29, which rounds down to 07:15 slot
|
||||
mockUsers = [
|
||||
createMockUser({ notificationTime: "07:29", timezone: "UTC" }),
|
||||
];
|
||||
mockDailyLogs = [createMockDailyLog()];
|
||||
|
||||
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockSendDailyEmail).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not send notification when minute slot does not match", async () => {
|
||||
// Current time is 07:00 UTC
|
||||
vi.setSystemTime(new Date("2025-01-15T07:00:00Z"));
|
||||
// User wants 07:15, but current slot is 07:00
|
||||
mockUsers = [
|
||||
createMockUser({ notificationTime: "07:15", timezone: "UTC" }),
|
||||
];
|
||||
mockDailyLogs = [createMockDailyLog()];
|
||||
|
||||
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockSendDailyEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles 30-minute slot correctly", async () => {
|
||||
// Current time is 07:30 UTC
|
||||
vi.setSystemTime(new Date("2025-01-15T07:30:00Z"));
|
||||
mockUsers = [
|
||||
createMockUser({ notificationTime: "07:30", timezone: "UTC" }),
|
||||
];
|
||||
mockDailyLogs = [createMockDailyLog()];
|
||||
|
||||
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockSendDailyEmail).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles 45-minute slot correctly", async () => {
|
||||
// Current time is 07:45 UTC
|
||||
vi.setSystemTime(new Date("2025-01-15T07:45:00Z"));
|
||||
mockUsers = [
|
||||
createMockUser({ notificationTime: "07:45", timezone: "UTC" }),
|
||||
];
|
||||
mockDailyLogs = [createMockDailyLog()];
|
||||
|
||||
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockSendDailyEmail).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles timezone with 15-minute matching", async () => {
|
||||
// Current time is 07:15 UTC = 02:15 America/New_York (EST is UTC-5)
|
||||
vi.setSystemTime(new Date("2025-01-15T07:15:00Z"));
|
||||
mockUsers = [
|
||||
createMockUser({
|
||||
notificationTime: "02:15",
|
||||
timezone: "America/New_York",
|
||||
}),
|
||||
];
|
||||
mockDailyLogs = [createMockDailyLog()];
|
||||
|
||||
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockSendDailyEmail).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("DailyLog handling", () => {
|
||||
it("does not send notification if no DailyLog exists for today", async () => {
|
||||
mockUsers = [
|
||||
|
||||
Reference in New Issue
Block a user