Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion api/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const config = {
accessLogsEnabled: getBoolOrDefault(env.TR_ACCESS_LOGS_ENABLED, true),
authTokenExpires: parseInt(env.TR_AUTH_TOKEN_EXPIRES, 10) || 86400,
signupsEnabled: getBoolOrDefault(env.TR_SIGNUPS_ENABLED, true),
maxProjectsPerUser: parseInt(env.TR_MAX_PROJECTS_PER_USER, 10) || 100,
maxProjectsPerUser: getNumberOrDefault(env.TR_MAX_PROJECTS_PER_USER, 100),
defaultProjectPlan: env.TR_DEFAULT_PROJECT_PLAN || 'open-source',
autoMigrate: getBoolOrDefault(env.TR_DB_AUTOMIGRATE, true),
seedData: getBoolOrDefault(env.TR_SEED_DATA, true),
Expand Down
26 changes: 26 additions & 0 deletions api/test/project.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import './util';
import { createAndMigrateApp, signupTestUser, TestingUser } from './util';
import { config } from '../src/config';

describe('ProjectController (e2e)', () => {
let app: INestApplication;
Expand Down Expand Up @@ -224,6 +225,31 @@ describe('ProjectController (e2e)', () => {
.expect(429);
});

it('/api/v1/projects (POST) should not create a project if the project limit is zero', async () => {
const maxProjectsPerUser = config.maxProjectsPerUser;
config.maxProjectsPerUser = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Mutating the module-level config singleton mid-test works because Jest runs test cases within the same file sequentially, and the controller reads config.maxProjectsPerUser at request time. However, a future move to parallel test workers could leave the value permanently set to 0, causing every subsequent test in the file to reject all project creation requests. The try/finally pattern is the correct approach for plain object properties.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Regression test mutates in-memory config after the app is already bootstrapped, so it does not cover the original zero-limit config-loading bug.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api/test/project.e2e-spec.ts, line 230:

<comment>Regression test mutates in-memory config after the app is already bootstrapped, so it does not cover the original zero-limit config-loading bug.</comment>

<file context>
@@ -224,6 +225,31 @@ describe('ProjectController (e2e)', () => {
 
+  it('/api/v1/projects (POST) should not create a project if the project limit is zero', async () => {
+    const maxProjectsPerUser = config.maxProjectsPerUser;
+    config.maxProjectsPerUser = 0;
+
+    try {
</file context>

@omribz156 omribz156 May 20, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, good catch. I replaced that e2e singleton-mutation check with a config-loading unit test in api/src/config.spec.ts, setting TR_MAX_PROJECTS_PER_USER=0 before importing ./config. Verified with git diff --check; local Jest could not run because this checkout has no installed node_modules (cross-env missing), so I'll watch the remote CI run.


try {
await request(app.getHttpServer())
.post('/api/v1/projects')
.set('Authorization', `Bearer ${testingUser.accessToken}`)
.send({
name: 'My project',
})
.expect(429);

await request(app.getHttpServer())
.get('/api/v1/projects')
.set('Authorization', `Bearer ${testingUser.accessToken}`)
.expect(200)
.expect(res => {
expect(res.body.data).toHaveLength(0);
});
} finally {
config.maxProjectsPerUser = maxProjectsPerUser;
}
});

it('/api/v1/projects/:projectId (PATCH) should update a project', async () => {
let projectId;

Expand Down