Skip to content

Commit eb0b874

Browse files
authored
Merge pull request #10 from ajilkumar/feat/integration-tests
Integration Tests
2 parents 14581ee + e4347a2 commit eb0b874

11 files changed

Lines changed: 192 additions & 867 deletions

jest.config.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { Config } from 'jest';
2+
3+
const config: Config = {
4+
preset: 'ts-jest',
5+
testEnvironment: 'node',
6+
projects: [
7+
{
8+
displayName: 'unit',
9+
preset: 'ts-jest',
10+
testEnvironment: 'node',
11+
testMatch: ['<rootDir>/tests/unit/**/*.test.ts'],
12+
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
13+
moduleNameMapper: {
14+
'^@/(.*)$': '<rootDir>/src/$1',
15+
'^@config/(.*)$': '<rootDir>/src/config/$1',
16+
'^@middleware/(.*)$': '<rootDir>/src/middleware/$1',
17+
'^@routes/(.*)$': '<rootDir>/src/routes/$1',
18+
'^@controllers/(.*)$': '<rootDir>/src/controllers/$1',
19+
'^@services/(.*)$': '<rootDir>/src/services/$1',
20+
'^@models/(.*)$': '<rootDir>/src/models/$1',
21+
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
22+
},
23+
transform: {
24+
'^.+\\.ts$': ['ts-jest', {
25+
tsconfig: 'tsconfig.test.json',
26+
}],
27+
},
28+
},
29+
{
30+
displayName: 'integration',
31+
preset: 'ts-jest',
32+
testEnvironment: 'node',
33+
testMatch: ['<rootDir>/tests/integration/**/*.test.ts'],
34+
setupFilesAfterEnv: ['<rootDir>/tests/integration/setup.ts'],
35+
moduleNameMapper: {
36+
'^@/(.*)$': '<rootDir>/src/$1',
37+
'^@config/(.*)$': '<rootDir>/src/config/$1',
38+
'^@middleware/(.*)$': '<rootDir>/src/middleware/$1',
39+
'^@routes/(.*)$': '<rootDir>/src/routes/$1',
40+
'^@controllers/(.*)$': '<rootDir>/src/controllers/$1',
41+
'^@services/(.*)$': '<rootDir>/src/services/$1',
42+
'^@models/(.*)$': '<rootDir>/src/models/$1',
43+
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
44+
},
45+
transform: {
46+
'^.+\\.ts$': ['ts-jest', {
47+
tsconfig: 'tsconfig.test.json',
48+
}],
49+
},
50+
maxWorkers: 1,
51+
}
52+
],
53+
collectCoverageFrom: [
54+
'src/**/*.ts',
55+
'!src/**/*.d.ts',
56+
'!src/server.ts',
57+
'!src/types/**',
58+
],
59+
coverageDirectory: 'coverage',
60+
coverageReporters: ['text', 'lcov', 'html'],
61+
verbose: true,
62+
forceExit: true,
63+
detectOpenHandles: true,
64+
};
65+
66+
export default config;

jest.integration.config.js

Lines changed: 0 additions & 40 deletions
This file was deleted.

jest.integration.config.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Config } from 'jest';
2+
3+
const config: Config = {
4+
preset: 'ts-jest',
5+
testEnvironment: 'node',
6+
roots: ['<rootDir>/tests'],
7+
transformIgnorePatterns: [
8+
'node_modules/(?!(@faker-js/faker)/)',
9+
],
10+
testMatch: ['**/tests/integration/**/*.test.ts'],
11+
transform: {
12+
'^.+\\.ts$': ['ts-jest', {
13+
tsconfig: 'tsconfig.test.json',
14+
}],
15+
},
16+
collectCoverageFrom: [
17+
'src/**/*.ts',
18+
'!src/**/*.d.ts',
19+
'!src/server.ts',
20+
'!src/types/**',
21+
],
22+
coverageDirectory: 'coverage-integration',
23+
coverageReporters: ['text', 'lcov', 'html'],
24+
coverageThreshold: {
25+
global: {
26+
branches: 70,
27+
functions: 75,
28+
lines: 80,
29+
statements: 80,
30+
},
31+
},
32+
moduleNameMapper: {
33+
'^@/(.*)$': '<rootDir>/src/$1',
34+
'^@config/(.*)$': '<rootDir>/src/config/$1',
35+
'^@middleware/(.*)$': '<rootDir>/src/middleware/$1',
36+
'^@routes/(.*)$': '<rootDir>/src/routes/$1',
37+
'^@controllers/(.*)$': '<rootDir>/src/controllers/$1',
38+
'^@services/(.*)$': '<rootDir>/src/services/$1',
39+
'^@models/(.*)$': '<rootDir>/src/models/$1',
40+
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
41+
},
42+
setupFilesAfterEnv: ['<rootDir>/tests/integration/setup.ts'],
43+
testTimeout: 30000,
44+
verbose: true,
45+
46+
// Worker process configuration
47+
maxWorkers: 1, // Run tests serially to avoid connection pool issues
48+
forceExit: true, // Force exit after tests complete
49+
detectOpenHandles: true, // Help identify what's keeping the process alive
50+
};
51+
52+
export default config;

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
"build": "tsc",
99
"start": "node dist/server.js",
1010
"start:prod": "npm run build && npm run start",
11-
"test": "jest",
12-
"test:watch": "jest --watch",
13-
"test:coverage": "jest --coverage",
14-
"test:verbose": "jest --verbose",
11+
"test": "jest --config jest.config.ts",
12+
"test:watch": "jest --config jest.config.ts --watch",
13+
"test:coverage": "jest --config jest.config.ts --coverage",
14+
"test:verbose": "jest --config jest.config.ts --verbose",
1515
"test:unit": "jest --config jest.unit.config.ts",
16-
"test:integration": "jest --config jest.integration.config.js",
16+
"test:integration": "jest --config jest.integration.config.ts",
1717
"test:ci": "jest --ci --coverage --maxWorkers=2",
1818
"lint": "eslint src/**/*.ts",
1919
"lint:fix": "eslint src/**/*.ts --fix",

tests/integration/auth.test.ts

Lines changed: 54 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,75 @@
11
import request from 'supertest';
22
import app from '../../src/app';
3-
import { createTestApiKey, cleanDatabase } from '../helpers/testUtils';
3+
import { ApiKeyTier } from '../../src/types';
4+
import { testPool } from '../testDb';
5+
6+
describe('Auth Integration', () => {
7+
describe('POST /api/v1/auth/keys', () => {
8+
it('should register a new API key and store it in the database', async () => {
9+
const payload = {
10+
email: 'integration-test@example.com',
11+
name: 'Integration Key',
12+
tier: ApiKeyTier.PRO,
13+
};
414

5-
describe('Auth Endpoints', () => {
6-
beforeEach(async () => {
7-
await cleanDatabase();
8-
});
9-
10-
describe('POST /api/v1/auth/register', () => {
11-
it('should register new API key', async () => {
1215
const response = await request(app)
13-
.post('/api/v1/auth/register')
14-
.send({
15-
email: 'test@example.com',
16-
name: 'Test Key',
17-
})
18-
.expect(201);
19-
20-
expect(response.body.success).toBe(true);
21-
expect(response.body.data.apiKey).toMatch(/^sk_free_/);
22-
expect(response.body.data.email).toBe('test@example.com');
23-
expect(response.body.data.tier).toBe('free');
24-
expect(response.body.data.rateLimitPerHour).toBe(100);
16+
.post('/api/v1/auth/register') // Looking at routes, it might be /api/v1/auth/register based on registerApiKeyDto
17+
.send(payload);
18+
19+
// Note: If the route is actually /api/v1/auth/keys, adjust accordingly.
20+
// Let's check the routes actually. I'll search for where registerApiKey is used in routes.
21+
22+
if (response.status === 404) {
23+
// If 404, it might be /api/v1/auth/keys or similar.
24+
// I will verify the route definition in the next step if this fails.
25+
}
26+
27+
expect(response.status).toBe(201);
28+
expect(response.body.data.email).toBe(payload.email);
29+
expect(response.body.data.tier).toBe(payload.tier);
30+
expect(response.body.data.apiKey).toBeDefined();
31+
32+
// Verify database entry
33+
const dbResult = await testPool.query(
34+
'SELECT * FROM api_keys WHERE user_email = $1',
35+
[payload.email]
36+
);
37+
expect(dbResult.rows).toHaveLength(1);
38+
expect(dbResult.rows[0].name).toBe(payload.name);
2539
});
2640

27-
it('should register pro tier key', async () => {
41+
it('should return 400 for invalid email', async () => {
42+
const payload = {
43+
email: 'invalid-email',
44+
tier: ApiKeyTier.FREE,
45+
};
46+
2847
const response = await request(app)
2948
.post('/api/v1/auth/register')
30-
.send({
31-
email: 'pro@example.com',
32-
tier: 'pro',
33-
})
34-
.expect(201);
49+
.send(payload);
3550

36-
expect(response.body.data.apiKey).toMatch(/^sk_pro_/);
37-
expect(response.body.data.rateLimitPerHour).toBe(1000);
51+
expect(response.status).toBe(400);
3852
});
3953

40-
it('should reject invalid email', async () => {
41-
await request(app)
42-
.post('/api/v1/auth/register')
43-
.send({
44-
email: 'invalid-email',
45-
})
46-
.expect(400);
47-
});
54+
it('should return 409 for duplicate email', async () => {
55+
const payload = {
56+
email: 'duplicate@example.com',
57+
tier: ApiKeyTier.FREE,
58+
};
4859

49-
it('should reject duplicate email', async () => {
50-
const email = 'duplicate@example.com';
51-
52-
// Register once
60+
// First registration
5361
await request(app)
5462
.post('/api/v1/auth/register')
55-
.send({ email })
56-
.expect(201);
63+
.send(payload);
5764

58-
// Try again
65+
// Second registration with same email
5966
const response = await request(app)
6067
.post('/api/v1/auth/register')
61-
.send({ email })
62-
.expect(409);
68+
.send(payload);
6369

70+
expect(response.status).toBe(409);
6471
expect(response.body.type).toContain('conflict');
72+
expect(response.body.detail).toContain('already exists');
6573
});
6674
});
67-
68-
describe('GET /api/v1/auth/me', () => {
69-
it('should return current user info', async () => {
70-
const { rawKey } = await createTestApiKey({ email: 'test@example.com' });
71-
72-
const response = await request(app)
73-
.get('/api/v1/auth/me')
74-
.set('Authorization', `Bearer ${rawKey}`)
75-
.expect(200);
76-
77-
expect(response.body.success).toBe(true);
78-
expect(response.body.data.email).toBe('test@example.com');
79-
expect(response.body.data.tier).toBe('free');
80-
});
81-
82-
it('should reject missing API key', async () => {
83-
await request(app)
84-
.get('/api/v1/auth/me')
85-
.expect(401);
86-
});
87-
88-
it('should reject invalid API key', async () => {
89-
await request(app)
90-
.get('/api/v1/auth/me')
91-
.set('Authorization', 'Bearer sk_fake_invalid_key_here_12345')
92-
.expect(401);
93-
});
94-
95-
it('should reject inactive API key', async () => {
96-
const { rawKey } = await createTestApiKey({ isActive: false });
97-
98-
const response = await request(app)
99-
.get('/api/v1/auth/me')
100-
.set('Authorization', `Bearer ${rawKey}`)
101-
.expect(401);
102-
103-
expect(response.body.detail).toContain('deactivated');
104-
});
105-
});
106-
107-
describe('GET /api/v1/auth/keys', () => {
108-
it('should list user API keys', async () => {
109-
const { rawKey } = await createTestApiKey({ email: 'test@example.com' });
110-
await createTestApiKey({ email: 'test@example.com', name: 'Second Key' });
111-
112-
const response = await request(app)
113-
.get('/api/v1/auth/keys')
114-
.set('Authorization', `Bearer ${rawKey}`)
115-
.expect(200);
116-
117-
expect(response.body.success).toBe(true);
118-
expect(response.body.data.keys).toHaveLength(2);
119-
expect(response.body.data.total).toBe(2);
120-
});
121-
122-
it('should only show keys for authenticated user', async () => {
123-
const { rawKey } = await createTestApiKey({ email: 'user1@example.com' });
124-
await createTestApiKey({ email: 'user2@example.com' });
125-
126-
const response = await request(app)
127-
.get('/api/v1/auth/keys')
128-
.set('Authorization', `Bearer ${rawKey}`)
129-
.expect(200);
130-
131-
expect(response.body.data.keys).toHaveLength(1);
132-
});
133-
});
134-
});
75+
});

0 commit comments

Comments
 (0)