Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ test/**/*.js
test/**/*.js.map
coverage/
!test/preprocessor.js
!test/integration/setup/*.js
mydb.sql

.ebextensions
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ module.exports = {
// forceCoverageMatch: [],

// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: './src/setupTests',
globalSetup: './test/integration/setup/globalSetup.js',
setupFilesAfterEnv: ['./test/unit/lib/setup.ts'],
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
globalTeardown: './test/integration/setup/globalTeardown.js',
Comment on lines +56 to +59
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

globalSetup/globalTeardown are now configured in the base Jest config, which means they will run for unit tests and any Jest invocation that uses jest.config.js (e.g., nps test.unit.run, nps test.coverage). This introduces an unexpected Postgres dependency for unit tests and can create/drop the test database even when running only unit tests. Move these hooks into jest.integration.config.js (or a dedicated integration-only config) and keep the base/unit config DB-free.

Copilot uses AI. Check for mistakes.

// A set of global variables that need to be available in all test environments
globals: {
Expand Down
6 changes: 6 additions & 0 deletions packages/backend/jest.integration.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const base = require('./jest.config');

module.exports = {
...base,
testMatch: ['**/test/integration/**/*.test.[jt]s?(x)'],
};
Comment on lines +3 to +6
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If globalSetup/globalTeardown are moved out of the base Jest config to avoid impacting unit tests, add them here in the integration config so only integration runs create/drop the test DB.

Copilot uses AI. Check for mistakes.
4 changes: 2 additions & 2 deletions packages/backend/package-scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ module.exports = {
run: {
default: {
// -i. Run all tests serially in the current process, rather than creating a worker pool of child processes that run tests. This can be useful for debugging.
script: 'cross-env NODE_ENV=test jest --runInBand --testPathPattern=integration -i',
script: 'cross-env NODE_ENV=test jest --runInBand --config jest.integration.config.js -i',
hiddenFromHelp: true,
},
watch: {
script: 'cross-env NODE_ENV=test jest --runInBand --watch --testPathPattern=integration -i',
script: 'cross-env NODE_ENV=test jest --runInBand --watch --config jest.integration.config.js -i',
hiddenFromHelp: true,
},
},
Expand Down
123 changes: 0 additions & 123 deletions packages/backend/src/config.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export default async function LogOperations(): Promise<void> {
const settingService = Container.get<SettingService>(SettingService);
const analyticsService = Container.get<AnalyticsService>(AnalyticsService);
const queryService = Container.get<QueryService>(QueryService);
const emailAddress = 'vivekfitkariwala@gmail.com';

const user = await userService.upsertUser(systemUser as any, new UpgradeLogger());

Expand Down Expand Up @@ -258,7 +257,7 @@ export default async function LogOperations(): Promise<void> {

await experimentService.update(experimentObject as any, user, new UpgradeLogger());

await analyticsService.exportCSVData(experimentObject.id, emailAddress, new UpgradeLogger());
await analyticsService.exportCSVData(experimentObject.id, systemUser.email, new UpgradeLogger());

// log data here
let experimentUserDoc = await experimentUserService.getOriginalUserDoc(experimentUsers[0].id, new UpgradeLogger());
Expand Down Expand Up @@ -337,7 +336,7 @@ export default async function LogOperations(): Promise<void> {
new UpgradeLogger()
);

await analyticsService.exportCSVData(experimentObject.id, emailAddress, new UpgradeLogger());
await analyticsService.exportCSVData(experimentObject.id, systemUser.email, new UpgradeLogger());
experimentUserDoc = await experimentUserService.getOriginalUserDoc(experimentUsers[2].id, new UpgradeLogger());
await experimentAssignmentService.dataLog(
{ ...experimentUserDoc, requestedUserId: experimentUsers[2].id },
Expand Down
6 changes: 3 additions & 3 deletions packages/backend/test/integration/mockData/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { User } from '../../../../src/api/models/User';
export const systemUser: Partial<User> = {
email: 'vivekfitkariwala@gmail.com',
firstName: 'Vivek',
lastName: 'Fitkariwala',
email: 'mockUser@mockmail.com',
firstName: 'Mock',
lastName: 'User',
imageUrl: 'https://res.cloudinary.com/dk-find-out/image/upload/q_80,w_1920,f_auto/A-Alamy-BXWK5E_vvmkuf.jpg',
};
24 changes: 24 additions & 0 deletions packages/backend/test/integration/setup/globalSetup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { Client } = require('pg');
const dotenv = require('dotenv');
const path = require('path');

module.exports = async function globalSetup() {
dotenv.config({ path: path.join(process.cwd(), '.env.test') });
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using dotenv.config({ path: path.join(process.cwd(), '.env.test') }) makes setup dependent on the process working directory (e.g., running Jest from the repo root with --config packages/backend/jest.integration.config.js won’t find the env file). Prefer resolving relative to this file (via __dirname) or Jest’s <rootDir> so the env file path is stable across local/CI execution contexts.

Suggested change
dotenv.config({ path: path.join(process.cwd(), '.env.test') });
dotenv.config({ path: path.join(__dirname, '../../../.env.test') });

Copilot uses AI. Check for mistakes.

const testDb = process.env.TYPEORM_DATABASE;
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TYPEORM_DATABASE is used to determine which database to create, but it isn’t validated. Add guards to ensure it’s defined, not postgres, and matches a safe identifier pattern (e.g. /^[A-Za-z0-9_]+$/) before it’s later interpolated into CREATE DATABASE "${testDb}", to avoid targeting/dropping the wrong DB and to prevent identifier-injection via env vars.

Suggested change
const testDb = process.env.TYPEORM_DATABASE;
const testDb = process.env.TYPEORM_DATABASE;
const dbNamePattern = /^[A-Za-z0-9_]+$/;
if (!testDb) {
throw new Error('TYPEORM_DATABASE environment variable must be defined for test setup');
}
if (testDb.toLowerCase() === 'postgres') {
throw new Error('TYPEORM_DATABASE must not be set to "postgres" for test setup');
}
if (!dbNamePattern.test(testDb)) {
throw new Error('TYPEORM_DATABASE contains invalid characters; only A-Za-z0-9_ are allowed');
}

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current repo .env.test, TYPEORM_DATABASE is set to postgres, so this setup won’t create a separate test DB and teardown will attempt to drop postgres (which will error and is unsafe). Consider failing fast here when TYPEORM_DATABASE is postgres and instructing developers/CI to use a dedicated test DB name (e.g. upgrade_test).

Suggested change
const testDb = process.env.TYPEORM_DATABASE;
const testDb = process.env.TYPEORM_DATABASE;
if (!testDb) {
throw new Error(
'TYPEORM_DATABASE is not set in .env.test. Please configure a dedicated test database name, e.g. "upgrade_test".',
);
}
if (testDb === 'postgres') {
throw new Error(
'Refusing to use "postgres" as the test database. Please set TYPEORM_DATABASE in .env.test to a dedicated test database name, e.g. "upgrade_test".',
);
}

Copilot uses AI. Check for mistakes.
const client = new Client({
host: process.env.TYPEORM_HOST,
port: parseInt(process.env.TYPEORM_PORT, 10),
user: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: 'postgres',
});

await client.connect();
const result = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [testDb]);
if (result.rowCount === 0) {
await client.query(`CREATE DATABASE "${testDb}"`);
console.log(`Created test database: ${testDb}`);
}
Comment on lines +18 to +22
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a previous test run exits before globalTeardown (e.g., SIGKILL/CI timeout), the test DB will remain and globalSetup will currently reuse it without resetting state (it only creates if missing). To guarantee a clean run, consider dropping/recreating the DB in setup (after terminating connections) or otherwise clearing schema/data deterministically.

Suggested change
const result = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [testDb]);
if (result.rowCount === 0) {
await client.query(`CREATE DATABASE "${testDb}"`);
console.log(`Created test database: ${testDb}`);
}
// Ensure no active connections to the test database from previous runs
await client.query(
'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()',
[testDb],
);
// Drop and recreate the test database to guarantee a clean state
await client.query(`DROP DATABASE IF EXISTS "${testDb}"`);
await client.query(`CREATE DATABASE "${testDb}"`);
console.log(`Recreated test database: ${testDb}`);

Copilot uses AI. Check for mistakes.
await client.end();
};
26 changes: 26 additions & 0 deletions packages/backend/test/integration/setup/globalTeardown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { Client } = require('pg');
const dotenv = require('dotenv');
const path = require('path');

module.exports = async function globalTeardown() {
dotenv.config({ path: path.join(process.cwd(), '.env.test') });
Comment on lines +4 to +6
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like setup, teardown relies on process.cwd() to locate .env.test, which can break when Jest is invoked from a different working directory. Resolve the env file path relative to the repo/workspace instead of the current process CWD to make teardown reliable in CI and when running from the monorepo root.

Suggested change
module.exports = async function globalTeardown() {
dotenv.config({ path: path.join(process.cwd(), '.env.test') });
const fs = require('fs');
function findEnvTest(startDir) {
let dir = startDir;
while (true) {
const candidate = path.join(dir, '.env.test');
if (fs.existsSync(candidate)) {
return candidate;
}
const parent = path.dirname(dir);
if (parent === dir) {
break;
}
dir = parent;
}
return null;
}
module.exports = async function globalTeardown() {
const envPath = findEnvTest(__dirname);
if (envPath) {
dotenv.config({ path: envPath });
} else {
dotenv.config();
}

Copilot uses AI. Check for mistakes.

const testDb = process.env.TYPEORM_DATABASE;
const client = new Client({
host: process.env.TYPEORM_HOST,
port: parseInt(process.env.TYPEORM_PORT, 10),
user: process.env.TYPEORM_USERNAME,
Comment on lines +8 to +12
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TYPEORM_DATABASE is later interpolated into DROP DATABASE IF EXISTS "${testDb}" without validation. Add the same safety checks as in setup (defined, not postgres, safe identifier) before running termination/drop queries so a misconfigured env can’t drop an unintended database.

Copilot uses AI. Check for mistakes.
password: process.env.TYPEORM_PASSWORD,
database: 'postgres',
});

await client.connect();
// Terminate active connections so the DB can be dropped
await client.query(
`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()`,
[testDb]
);
await client.query(`DROP DATABASE IF EXISTS "${testDb}"`);
console.log(`Dropped test database: ${testDb}`);
await client.end();
};
Loading