Skip to content

Commit 093eaf0

Browse files
authored
e2e: isolate external DB sync cleanup per suite (#1148)
Possible CI flake fix: track external DB sync cleanup per test suite to avoid cross-suite config resets.\n\n- Lint: pass\n- Typecheck: pass <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added external database synchronization, enabling data replication from the internal database to external PostgreSQL databases with incremental batching and sequence tracking. * Introduced background cron job execution system for scheduled tasks. * **Tests** * Added comprehensive test suites for external database synchronization scenarios including basic operations, advanced configurations, high-volume data transfers, and race condition handling. * **Chores** * Enhanced CI/CD workflows to support external database sync operations. * Updated Docker configuration and environment variables for improved testing infrastructure. * **Bug Fixes** * Increased test timeout threshold for session expiration tests to accommodate slower executions. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent ba1df26 commit 093eaf0

5 files changed

Lines changed: 61 additions & 17 deletions

File tree

apps/e2e/tests/backend/endpoints/api/v1/external-db-sync-advanced.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
POSTGRES_USER,
1010
TEST_TIMEOUT,
1111
TestDbManager,
12-
createProjectWithExternalDb,
12+
createProjectWithExternalDb as createProjectWithExternalDbRaw,
1313
verifyNotInExternalDb,
1414
waitForCondition,
1515
waitForSyncedData,
@@ -21,6 +21,16 @@ const COMPLEX_SEQUENCE_TIMEOUT = TEST_TIMEOUT * 2 + 30_000;
2121

2222
describe.sequential('External DB Sync - Advanced Tests', () => {
2323
let dbManager: TestDbManager;
24+
const createProjectWithExternalDb = (
25+
externalDatabases: any,
26+
projectOptions?: { display_name?: string, description?: string }
27+
) => {
28+
return createProjectWithExternalDbRaw(
29+
externalDatabases,
30+
projectOptions,
31+
{ projectTracker: dbManager.createdProjects }
32+
);
33+
};
2434

2535
beforeAll(async () => {
2636
dbManager = new TestDbManager();

apps/e2e/tests/backend/endpoints/api/v1/external-db-sync-basics.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { User, niceBackendFetch } from '../../../backend-helpers';
44
import {
55
TEST_TIMEOUT,
66
TestDbManager,
7-
createProjectWithExternalDb,
7+
createProjectWithExternalDb as createProjectWithExternalDbRaw,
88
verifyInExternalDb,
99
verifyNotInExternalDb,
1010
waitForCondition,
@@ -16,6 +16,16 @@ import {
1616
// Run tests sequentially to avoid concurrency issues with shared backend state
1717
describe.sequential('External DB Sync - Basic Tests', () => {
1818
let dbManager: TestDbManager;
19+
const createProjectWithExternalDb = (
20+
externalDatabases: any,
21+
projectOptions?: { display_name?: string, description?: string }
22+
) => {
23+
return createProjectWithExternalDbRaw(
24+
externalDatabases,
25+
projectOptions,
26+
{ projectTracker: dbManager.createdProjects }
27+
);
28+
};
1929

2030
beforeAll(async () => {
2131
dbManager = new TestDbManager();

apps/e2e/tests/backend/endpoints/api/v1/external-db-sync-high-volume.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,24 @@ import {
88
POSTGRES_PASSWORD,
99
POSTGRES_USER,
1010
TestDbManager,
11-
createProjectWithExternalDb,
11+
createProjectWithExternalDb as createProjectWithExternalDbRaw,
1212
waitForCondition,
1313
waitForTable,
1414
} from './external-db-sync-utils';
1515

1616
// Run tests sequentially to avoid concurrency issues with shared backend state
1717
describe.sequential('External DB Sync - High Volume Tests', () => {
1818
let dbManager: TestDbManager;
19+
const createProjectWithExternalDb = (
20+
externalDatabases: any,
21+
projectOptions?: { display_name?: string, description?: string }
22+
) => {
23+
return createProjectWithExternalDbRaw(
24+
externalDatabases,
25+
projectOptions,
26+
{ projectTracker: dbManager.createdProjects }
27+
);
28+
};
1929

2030
beforeAll(async () => {
2131
dbManager = new TestDbManager();

apps/e2e/tests/backend/endpoints/api/v1/external-db-sync-race.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
POSTGRES_USER,
1010
TEST_TIMEOUT,
1111
TestDbManager,
12-
createProjectWithExternalDb,
12+
createProjectWithExternalDb as createProjectWithExternalDbRaw,
1313
forceExternalDbSync,
1414
waitForCondition,
1515
waitForSyncedDeletion,
@@ -20,6 +20,16 @@ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
2020

2121
describe.sequential('External DB Sync - Race Condition Tests', () => {
2222
let dbManager: TestDbManager;
23+
const createProjectWithExternalDb = (
24+
externalDatabases: any,
25+
projectOptions?: { display_name?: string, description?: string }
26+
) => {
27+
return createProjectWithExternalDbRaw(
28+
externalDatabases,
29+
projectOptions,
30+
{ projectTracker: dbManager.createdProjects }
31+
);
32+
};
2333

2434
beforeAll(async () => {
2535
dbManager = new TestDbManager();

apps/e2e/tests/backend/endpoints/api/v1/external-db-sync-utils.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,10 @@ const CLIENT_CONFIG: Partial<ClientConfig> = {
3434
};
3535

3636
// Track all projects created with external DB configs for cleanup
37-
type ProjectContext = {
37+
export type ProjectContext = {
3838
projectId: string,
3939
superSecretAdminKey: string,
4040
};
41-
const createdProjects: ProjectContext[] = [];
4241

4342
/**
4443
* Helper class to manage external test databases
@@ -47,6 +46,7 @@ export class TestDbManager {
4746
private setupClient: Client | null = null;
4847
private databases: Map<string, Client> = new Map();
4948
private databaseNames: Set<string> = new Set();
49+
public readonly createdProjects: ProjectContext[] = [];
5050

5151
async init() {
5252
this.setupClient = new Client({
@@ -81,7 +81,8 @@ export class TestDbManager {
8181

8282
async cleanup() {
8383
// First, clean up all project configs to stop the sync cron from trying to connect
84-
await cleanupAllProjectConfigs();
84+
await cleanupProjectConfigs(this.createdProjects);
85+
this.createdProjects.length = 0;
8586

8687
// Close all tracked database clients
8788
const closePromises = Array.from(this.databases.values()).map(async (client) => {
@@ -321,7 +322,11 @@ export async function countUsersInExternalDb(client: Client): Promise<number> {
321322
* Helper to create a project and update its config with external DB settings.
322323
* Tracks the project for cleanup later.
323324
*/
324-
export async function createProjectWithExternalDb(externalDatabases: any, projectOptions?: { display_name?: string, description?: string }) {
325+
export async function createProjectWithExternalDb(
326+
externalDatabases: any,
327+
projectOptions?: { display_name?: string, description?: string },
328+
options?: { projectTracker?: ProjectContext[] }
329+
) {
325330
const project = await Project.createAndSwitch(projectOptions);
326331
const { projectKeys } = await InternalApiKey.createAndSetProjectKeys(project.adminAccessToken);
327332
if (!projectKeys.superSecretAdminKey) {
@@ -332,10 +337,12 @@ export async function createProjectWithExternalDb(externalDatabases: any, projec
332337
});
333338

334339
// Track this project for cleanup
335-
createdProjects.push({
336-
projectId: project.projectId,
337-
superSecretAdminKey: projectKeys.superSecretAdminKey,
338-
});
340+
if (options?.projectTracker) {
341+
options.projectTracker.push({
342+
projectId: project.projectId,
343+
superSecretAdminKey: projectKeys.superSecretAdminKey,
344+
});
345+
}
339346

340347
return project;
341348
}
@@ -356,8 +363,8 @@ export async function cleanupProjectExternalDb() {
356363
* Note: This function makes direct HTTP calls instead of using backendContext
357364
* because it runs in afterAll, which is outside the test context.
358365
*/
359-
export async function cleanupAllProjectConfigs() {
360-
for (const project of createdProjects) {
366+
export async function cleanupProjectConfigs(projects: ProjectContext[]) {
367+
for (const project of projects) {
361368
try {
362369
// Make direct HTTP call to clear the external DB config
363370
await niceFetch(new URL('/api/latest/internal/config/override', STACK_BACKEND_BASE_URL), {
@@ -377,7 +384,4 @@ export async function cleanupAllProjectConfigs() {
377384
console.warn(`Failed to cleanup project ${project.projectId}:`, err);
378385
}
379386
}
380-
381-
// Clear the tracked projects
382-
createdProjects.length = 0;
383387
}

0 commit comments

Comments
 (0)