Fix Garmin token storage and flaky e2e test
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>
This commit is contained in:
@@ -83,19 +83,52 @@ async function waitForReady(url: string, timeoutMs = 30000): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the admin superuser using the PocketBase CLI.
|
||||
* Sleeps for the specified number of milliseconds.
|
||||
*/
|
||||
function createAdminUser(
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the admin superuser using the PocketBase CLI.
|
||||
* Retries on database lock errors since PocketBase may still be running migrations.
|
||||
*/
|
||||
async function createAdminUser(
|
||||
dataDir: string,
|
||||
email: string,
|
||||
password: string,
|
||||
): void {
|
||||
execSync(
|
||||
`pocketbase superuser upsert ${email} ${password} --dir=${dataDir}`,
|
||||
{
|
||||
stdio: "pipe",
|
||||
},
|
||||
);
|
||||
maxRetries = 5,
|
||||
): Promise<void> {
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
try {
|
||||
execSync(
|
||||
`pocketbase superuser upsert ${email} ${password} --dir=${dataDir}`,
|
||||
{
|
||||
stdio: "pipe",
|
||||
},
|
||||
);
|
||||
return;
|
||||
} catch (err) {
|
||||
lastError = err as Error;
|
||||
const errorMsg = String(lastError.message || lastError);
|
||||
|
||||
// Retry on database lock errors
|
||||
if (
|
||||
errorMsg.includes("database is locked") ||
|
||||
errorMsg.includes("SQLITE_BUSY")
|
||||
) {
|
||||
await sleep(100 * (attempt + 1)); // Exponential backoff: 100ms, 200ms, 300ms...
|
||||
continue;
|
||||
}
|
||||
|
||||
// For other errors, throw immediately
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,6 +219,41 @@ async function setupCollections(pb: PocketBase): Promise<void> {
|
||||
await setupApiRules(pb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries an async operation with exponential backoff.
|
||||
*/
|
||||
async function retryAsync<T>(
|
||||
operation: () => Promise<T>,
|
||||
maxRetries = 5,
|
||||
baseDelayMs = 100,
|
||||
): Promise<T> {
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (err) {
|
||||
lastError = err as Error;
|
||||
const errorMsg = String(lastError.message || lastError);
|
||||
|
||||
// Retry on transient errors (database busy, connection issues)
|
||||
if (
|
||||
errorMsg.includes("database is locked") ||
|
||||
errorMsg.includes("SQLITE_BUSY") ||
|
||||
errorMsg.includes("Failed to create record")
|
||||
) {
|
||||
await sleep(baseDelayMs * (attempt + 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// For other errors, throw immediately
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the test user with period data.
|
||||
*/
|
||||
@@ -199,24 +267,28 @@ async function createTestUser(
|
||||
fourteenDaysAgo.setDate(fourteenDaysAgo.getDate() - 14);
|
||||
const lastPeriodDate = fourteenDaysAgo.toISOString().split("T")[0];
|
||||
|
||||
// Create the test user
|
||||
const user = await pb.collection("users").create({
|
||||
email,
|
||||
password,
|
||||
passwordConfirm: password,
|
||||
emailVisibility: true,
|
||||
verified: true,
|
||||
lastPeriodDate,
|
||||
cycleLength: 28,
|
||||
notificationTime: "07:00",
|
||||
timezone: "UTC",
|
||||
});
|
||||
// Create the test user (with retry for transient errors)
|
||||
const user = await retryAsync(() =>
|
||||
pb.collection("users").create({
|
||||
email,
|
||||
password,
|
||||
passwordConfirm: password,
|
||||
emailVisibility: true,
|
||||
verified: true,
|
||||
lastPeriodDate,
|
||||
cycleLength: 28,
|
||||
notificationTime: "07:00",
|
||||
timezone: "UTC",
|
||||
}),
|
||||
);
|
||||
|
||||
// Create a period log entry
|
||||
await pb.collection("period_logs").create({
|
||||
user: user.id,
|
||||
startDate: lastPeriodDate,
|
||||
});
|
||||
// Create a period log entry (with retry for transient errors)
|
||||
await retryAsync(() =>
|
||||
pb.collection("period_logs").create({
|
||||
user: user.id,
|
||||
startDate: lastPeriodDate,
|
||||
}),
|
||||
);
|
||||
|
||||
return user.id;
|
||||
}
|
||||
@@ -254,8 +326,8 @@ export async function start(
|
||||
// Wait for PocketBase to be ready
|
||||
await waitForReady(url);
|
||||
|
||||
// Create admin user via CLI
|
||||
createAdminUser(dataDir, config.adminEmail, config.adminPassword);
|
||||
// Create admin user via CLI (with retry for database lock during migrations)
|
||||
await createAdminUser(dataDir, config.adminEmail, config.adminPassword);
|
||||
|
||||
// Connect to PocketBase as admin
|
||||
const pb = new PocketBase(url);
|
||||
|
||||
Reference in New Issue
Block a user