Fix PocketBase date format - use YYYY-MM-DD instead of ISO
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
PocketBase filters don't accept ISO format with T separator (causes 400). Changed both garmin-sync storage and today route query to use simple YYYY-MM-DD format, matching the working /api/history pattern. TDD approach: wrote failing tests first, then implemented the fix. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -414,18 +414,17 @@ describe("POST /api/cron/garmin-sync", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets date to today's ISO date string", async () => {
|
it("sets date to YYYY-MM-DD format string", async () => {
|
||||||
mockUsers = [createMockUser()];
|
mockUsers = [createMockUser()];
|
||||||
// Full ISO format for consistent date comparison in queries
|
// Simple YYYY-MM-DD format for PocketBase date field compatibility
|
||||||
const todayDate = new Date();
|
// PocketBase filters don't accept ISO format with T separator
|
||||||
todayDate.setUTCHours(0, 0, 0, 0);
|
const today = new Date().toISOString().split("T")[0];
|
||||||
const todayISO = todayDate.toISOString();
|
|
||||||
|
|
||||||
await POST(createMockRequest(`Bearer ${validSecret}`));
|
await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||||
|
|
||||||
expect(mockPbCreate).toHaveBeenCalledWith(
|
expect(mockPbCreate).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
date: todayISO,
|
date: today,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -91,13 +91,9 @@ export async function POST(request: Request) {
|
|||||||
(u) => u.garminConnected && u.garminTokenExpiresAt && u.lastPeriodDate,
|
(u) => u.garminConnected && u.garminTokenExpiresAt && u.lastPeriodDate,
|
||||||
);
|
);
|
||||||
|
|
||||||
// YYYY-MM-DD format for Garmin API calls
|
// YYYY-MM-DD format for both Garmin API calls and PocketBase storage
|
||||||
|
// PocketBase date filters don't accept ISO format with T separator
|
||||||
const today = new Date().toISOString().split("T")[0];
|
const today = new Date().toISOString().split("T")[0];
|
||||||
// Full ISO format for PocketBase date field storage
|
|
||||||
// This ensures consistent date comparison in queries
|
|
||||||
const todayDate = new Date();
|
|
||||||
todayDate.setUTCHours(0, 0, 0, 0);
|
|
||||||
const todayISO = todayDate.toISOString();
|
|
||||||
|
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
const userSyncStartTime = Date.now();
|
const userSyncStartTime = Date.now();
|
||||||
@@ -214,10 +210,10 @@ export async function POST(request: Request) {
|
|||||||
// Store default value 100 for body battery when Garmin returns null.
|
// Store default value 100 for body battery when Garmin returns null.
|
||||||
// This prevents PocketBase's number field null-to-0 coercion from
|
// This prevents PocketBase's number field null-to-0 coercion from
|
||||||
// causing the dashboard to display 0 instead of a meaningful value.
|
// causing the dashboard to display 0 instead of a meaningful value.
|
||||||
// Use full ISO date format for consistent date comparison in queries
|
// Use YYYY-MM-DD format for PocketBase date field compatibility
|
||||||
await pb.collection("dailyLogs").create({
|
await pb.collection("dailyLogs").create({
|
||||||
user: user.id,
|
user: user.id,
|
||||||
date: todayISO,
|
date: today,
|
||||||
cycleDay,
|
cycleDay,
|
||||||
phase,
|
phase,
|
||||||
bodyBatteryCurrent: bodyBattery.current ?? 100,
|
bodyBatteryCurrent: bodyBattery.current ?? 100,
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ let currentMockUser: User | null = null;
|
|||||||
// Module-level variable to control mock daily log in tests
|
// Module-level variable to control mock daily log in tests
|
||||||
let currentMockDailyLog: DailyLog | null = null;
|
let currentMockDailyLog: DailyLog | null = null;
|
||||||
|
|
||||||
|
// Track the filter string passed to getFirstListItem
|
||||||
|
let lastDailyLogFilter: string | null = null;
|
||||||
|
|
||||||
// Create mock PocketBase client
|
// Create mock PocketBase client
|
||||||
const mockPb = {
|
const mockPb = {
|
||||||
collection: vi.fn((collectionName: string) => ({
|
collection: vi.fn((collectionName: string) => ({
|
||||||
@@ -30,7 +33,11 @@ const mockPb = {
|
|||||||
}
|
}
|
||||||
throw new Error("Record not found");
|
throw new Error("Record not found");
|
||||||
}),
|
}),
|
||||||
getFirstListItem: vi.fn(async () => {
|
getFirstListItem: vi.fn(async (filter: string) => {
|
||||||
|
// Capture the filter for testing
|
||||||
|
if (collectionName === "dailyLogs") {
|
||||||
|
lastDailyLogFilter = filter;
|
||||||
|
}
|
||||||
if (!currentMockDailyLog) {
|
if (!currentMockDailyLog) {
|
||||||
const error = new Error("No DailyLog found");
|
const error = new Error("No DailyLog found");
|
||||||
(error as { status?: number }).status = 404;
|
(error as { status?: number }).status = 404;
|
||||||
@@ -100,6 +107,7 @@ describe("GET /api/today", () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
currentMockUser = null;
|
currentMockUser = null;
|
||||||
currentMockDailyLog = null;
|
currentMockDailyLog = null;
|
||||||
|
lastDailyLogFilter = null;
|
||||||
// Mock current date to 2025-01-10 for predictable testing
|
// Mock current date to 2025-01-10 for predictable testing
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
vi.setSystemTime(new Date("2025-01-10T12:00:00Z"));
|
vi.setSystemTime(new Date("2025-01-10T12:00:00Z"));
|
||||||
@@ -508,6 +516,24 @@ describe("GET /api/today", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("dailyLog query", () => {
|
||||||
|
it("queries dailyLogs with YYYY-MM-DD date format using contains operator", async () => {
|
||||||
|
// PocketBase filters don't accept ISO format with T separator
|
||||||
|
// Must use simple YYYY-MM-DD with ~ contains operator
|
||||||
|
currentMockUser = createMockUser();
|
||||||
|
currentMockDailyLog = createMockDailyLog();
|
||||||
|
|
||||||
|
await GET(mockRequest);
|
||||||
|
|
||||||
|
// Verify filter uses YYYY-MM-DD format (2025-01-10) not ISO format
|
||||||
|
// The filter should use ~ contains operator, not >= range
|
||||||
|
expect(lastDailyLogFilter).toBeDefined();
|
||||||
|
expect(lastDailyLogFilter).toContain('date~"2025-01-10"');
|
||||||
|
// Should NOT contain ISO format with T separator
|
||||||
|
expect(lastDailyLogFilter).not.toContain("T");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("biometrics data", () => {
|
describe("biometrics data", () => {
|
||||||
it("returns biometrics from daily log when available", async () => {
|
it("returns biometrics from daily log when available", async () => {
|
||||||
currentMockUser = createMockUser();
|
currentMockUser = createMockUser();
|
||||||
|
|||||||
@@ -74,24 +74,16 @@ export const GET = withAuth(async (_request, user, pb) => {
|
|||||||
// Sort by created DESC to get the most recent record if multiple exist
|
// Sort by created DESC to get the most recent record if multiple exist
|
||||||
let biometrics = { ...DEFAULT_BIOMETRICS, phaseLimit };
|
let biometrics = { ...DEFAULT_BIOMETRICS, phaseLimit };
|
||||||
try {
|
try {
|
||||||
// Use date range query for proper date field comparison
|
// Use YYYY-MM-DD format with contains operator for PocketBase date field
|
||||||
// PocketBase date fields need >= and < operators, not string contains
|
// PocketBase filters don't accept ISO format with T separator
|
||||||
const todayStart = new Date();
|
const today = new Date().toISOString().split("T")[0];
|
||||||
todayStart.setUTCHours(0, 0, 0, 0);
|
|
||||||
const tomorrowStart = new Date(todayStart.getTime() + 86400000);
|
|
||||||
const todayISO = todayStart.toISOString();
|
|
||||||
const tomorrowISO = tomorrowStart.toISOString();
|
|
||||||
|
|
||||||
logger.info(
|
logger.info({ userId: user.id, date: today }, "Fetching dailyLog");
|
||||||
{ userId: user.id, todayISO, tomorrowISO },
|
|
||||||
"Fetching dailyLog",
|
|
||||||
);
|
|
||||||
const dailyLog = await pb
|
const dailyLog = await pb
|
||||||
.collection("dailyLogs")
|
.collection("dailyLogs")
|
||||||
.getFirstListItem<DailyLog>(
|
.getFirstListItem<DailyLog>(`user="${user.id}" && date~"${today}"`, {
|
||||||
`user="${user.id}" && date>="${todayISO}" && date<"${tomorrowISO}"`,
|
sort: "-created",
|
||||||
{ sort: "-created" },
|
});
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user