1. Increase garminOauth1Token and garminOauth2Token max length from 5000 to 20000 characters to accommodate encrypted OAuth tokens. Add logic to update existing field constraints in addUserFields(). 2. Fix flaky pocketbase-harness e2e test by adding retry logic with exponential backoff to createAdminUser() and createTestUser(). Handles SQLite database lock during PocketBase startup migrations. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
184 lines
5.8 KiB
TypeScript
184 lines
5.8 KiB
TypeScript
// ABOUTME: Tests for the database setup script that creates PocketBase collections.
|
|
// ABOUTME: Verifies collection definitions and setup logic without hitting real PocketBase.
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import {
|
|
DAILY_LOGS_COLLECTION,
|
|
getExistingCollectionNames,
|
|
getMissingCollections,
|
|
PERIOD_LOGS_COLLECTION,
|
|
USER_CUSTOM_FIELDS,
|
|
} from "./setup-db";
|
|
|
|
describe("PERIOD_LOGS_COLLECTION", () => {
|
|
it("has correct name", () => {
|
|
expect(PERIOD_LOGS_COLLECTION.name).toBe("period_logs");
|
|
});
|
|
|
|
it("has required fields", () => {
|
|
const fieldNames = PERIOD_LOGS_COLLECTION.fields.map((f) => f.name);
|
|
expect(fieldNames).toContain("user");
|
|
expect(fieldNames).toContain("startDate");
|
|
expect(fieldNames).toContain("predictedDate");
|
|
});
|
|
|
|
it("has user field as relation to users", () => {
|
|
const userField = PERIOD_LOGS_COLLECTION.fields.find(
|
|
(f) => f.name === "user",
|
|
);
|
|
expect(userField?.type).toBe("relation");
|
|
expect(userField?.collectionId).toBe("users");
|
|
expect(userField?.maxSelect).toBe(1);
|
|
expect(userField?.cascadeDelete).toBe(true);
|
|
});
|
|
|
|
it("has startDate as required date", () => {
|
|
const startDateField = PERIOD_LOGS_COLLECTION.fields.find(
|
|
(f) => f.name === "startDate",
|
|
);
|
|
expect(startDateField?.type).toBe("date");
|
|
expect(startDateField?.required).toBe(true);
|
|
});
|
|
|
|
it("has predictedDate as optional date", () => {
|
|
const predictedDateField = PERIOD_LOGS_COLLECTION.fields.find(
|
|
(f) => f.name === "predictedDate",
|
|
);
|
|
expect(predictedDateField?.type).toBe("date");
|
|
expect(predictedDateField?.required).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("DAILY_LOGS_COLLECTION", () => {
|
|
it("has correct name", () => {
|
|
expect(DAILY_LOGS_COLLECTION.name).toBe("dailyLogs");
|
|
});
|
|
|
|
it("has all required fields", () => {
|
|
const fieldNames = DAILY_LOGS_COLLECTION.fields.map((f) => f.name);
|
|
|
|
// Core fields
|
|
expect(fieldNames).toContain("user");
|
|
expect(fieldNames).toContain("date");
|
|
expect(fieldNames).toContain("cycleDay");
|
|
expect(fieldNames).toContain("phase");
|
|
|
|
// Garmin biometric fields
|
|
expect(fieldNames).toContain("bodyBatteryCurrent");
|
|
expect(fieldNames).toContain("bodyBatteryYesterdayLow");
|
|
expect(fieldNames).toContain("hrvStatus");
|
|
expect(fieldNames).toContain("weekIntensityMinutes");
|
|
|
|
// Decision fields
|
|
expect(fieldNames).toContain("phaseLimit");
|
|
expect(fieldNames).toContain("remainingMinutes");
|
|
expect(fieldNames).toContain("trainingDecision");
|
|
expect(fieldNames).toContain("decisionReason");
|
|
expect(fieldNames).toContain("notificationSentAt");
|
|
});
|
|
|
|
it("has user field as relation to users", () => {
|
|
const userField = DAILY_LOGS_COLLECTION.fields.find(
|
|
(f) => f.name === "user",
|
|
);
|
|
expect(userField?.type).toBe("relation");
|
|
expect(userField?.collectionId).toBe("users");
|
|
expect(userField?.maxSelect).toBe(1);
|
|
expect(userField?.cascadeDelete).toBe(true);
|
|
});
|
|
|
|
it("has trainingDecision as required text", () => {
|
|
const field = DAILY_LOGS_COLLECTION.fields.find(
|
|
(f) => f.name === "trainingDecision",
|
|
);
|
|
expect(field?.type).toBe("text");
|
|
expect(field?.required).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("getExistingCollectionNames", () => {
|
|
it("extracts collection names from PocketBase response", async () => {
|
|
const mockPb = {
|
|
collections: {
|
|
getFullList: vi
|
|
.fn()
|
|
.mockResolvedValue([
|
|
{ name: "users" },
|
|
{ name: "period_logs" },
|
|
{ name: "_superusers" },
|
|
]),
|
|
},
|
|
};
|
|
|
|
// biome-ignore lint/suspicious/noExplicitAny: test mock
|
|
const names = await getExistingCollectionNames(mockPb as any);
|
|
|
|
expect(names).toEqual(["users", "period_logs", "_superusers"]);
|
|
});
|
|
|
|
it("returns empty array when no collections exist", async () => {
|
|
const mockPb = {
|
|
collections: {
|
|
getFullList: vi.fn().mockResolvedValue([]),
|
|
},
|
|
};
|
|
|
|
// biome-ignore lint/suspicious/noExplicitAny: test mock
|
|
const names = await getExistingCollectionNames(mockPb as any);
|
|
|
|
expect(names).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("getMissingCollections", () => {
|
|
it("returns both collections when none exist", () => {
|
|
const existing = ["users"];
|
|
const missing = getMissingCollections(existing);
|
|
|
|
expect(missing).toHaveLength(2);
|
|
expect(missing.map((c) => c.name)).toContain("period_logs");
|
|
expect(missing.map((c) => c.name)).toContain("dailyLogs");
|
|
});
|
|
|
|
it("returns only dailyLogs when period_logs exists", () => {
|
|
const existing = ["users", "period_logs"];
|
|
const missing = getMissingCollections(existing);
|
|
|
|
expect(missing).toHaveLength(1);
|
|
expect(missing[0].name).toBe("dailyLogs");
|
|
});
|
|
|
|
it("returns only period_logs when dailyLogs exists", () => {
|
|
const existing = ["users", "dailyLogs"];
|
|
const missing = getMissingCollections(existing);
|
|
|
|
expect(missing).toHaveLength(1);
|
|
expect(missing[0].name).toBe("period_logs");
|
|
});
|
|
|
|
it("returns empty array when all collections exist", () => {
|
|
const existing = ["users", "period_logs", "dailyLogs"];
|
|
const missing = getMissingCollections(existing);
|
|
|
|
expect(missing).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("USER_CUSTOM_FIELDS garmin token max lengths", () => {
|
|
it("should have sufficient max length for garminOauth2Token field", () => {
|
|
const oauth2Field = USER_CUSTOM_FIELDS.find(
|
|
(f) => f.name === "garminOauth2Token",
|
|
);
|
|
expect(oauth2Field).toBeDefined();
|
|
expect(oauth2Field?.max).toBeGreaterThanOrEqual(10000);
|
|
});
|
|
|
|
it("should have sufficient max length for garminOauth1Token field", () => {
|
|
const oauth1Field = USER_CUSTOM_FIELDS.find(
|
|
(f) => f.name === "garminOauth1Token",
|
|
);
|
|
expect(oauth1Field).toBeDefined();
|
|
expect(oauth1Field?.max).toBeGreaterThanOrEqual(10000);
|
|
});
|
|
});
|