add e2e with docker-compose support
This commit is contained in:
parent
7bf514b2aa
commit
bdf16a3189
6
.env.e2e
Normal file
6
.env.e2e
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=4401
|
||||||
|
DB_USERNAME=test
|
||||||
|
DB_PASSWORD=test
|
||||||
|
DB_DATABASE=test
|
||||||
|
JWT_SECRET=secret
|
||||||
11
environment/e2e/docker-compose.yaml
Normal file
11
environment/e2e/docker-compose.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:18
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: test
|
||||||
|
POSTGRES_PASSWORD: test
|
||||||
|
POSTGRES_DB: test
|
||||||
|
ports:
|
||||||
|
- '4401:5432'
|
||||||
1360
package-lock.json
generated
1360
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -52,6 +52,7 @@
|
|||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/passport-jwt": "^4.0.0",
|
"@types/passport-jwt": "^4.0.0",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-prettier": "^5.2.2",
|
"eslint-plugin-prettier": "^5.2.2",
|
||||||
@ -60,6 +61,7 @@
|
|||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
|
"testcontainers": "^10.9.0",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"ts-loader": "^9.5.2",
|
"ts-loader": "^9.5.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
|||||||
41
test/global-setup.ts
Normal file
41
test/global-setup.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { exec } from 'child_process';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { DockerComposeEnvironment, Wait } from 'testcontainers';
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
const composeFilePath = path.resolve(__dirname, '../environment/e2e');
|
||||||
|
|
||||||
|
const environment = await new DockerComposeEnvironment(
|
||||||
|
composeFilePath,
|
||||||
|
'docker-compose.yaml',
|
||||||
|
)
|
||||||
|
.withWaitStrategy('postgres_1', Wait.forHealthCheck())
|
||||||
|
.up();
|
||||||
|
|
||||||
|
// Store the environment details for teardown
|
||||||
|
(global as any).__TESTCONTAINERS_ENVIRONMENT__ = environment;
|
||||||
|
|
||||||
|
// Run migrations
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
console.info("running migration")
|
||||||
|
exec(
|
||||||
|
'env && npm run migration:run',
|
||||||
|
{ env: { ...process.env, ...readEnvFile() } },
|
||||||
|
(err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(stderr);
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
console.log(stdout);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function readEnvFile() {
|
||||||
|
const fs = require('fs');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
const envConfig = dotenv.parse(fs.readFileSync('.env.e2e'));
|
||||||
|
return envConfig;
|
||||||
|
}
|
||||||
10
test/global-teardown.ts
Normal file
10
test/global-teardown.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { DockerComposeEnvironment } from 'testcontainers';
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
const environment: DockerComposeEnvironment = (global as any)
|
||||||
|
.__TESTCONTAINERS_ENVIRONMENT__;
|
||||||
|
|
||||||
|
if (environment) {
|
||||||
|
await environment.down();
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -5,5 +5,7 @@
|
|||||||
"testRegex": ".e2e-spec.ts$",
|
"testRegex": ".e2e-spec.ts$",
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
}
|
},
|
||||||
|
"globalSetup": "<rootDir>/global-setup.ts",
|
||||||
|
"globalTeardown": "<rootDir>/global-teardown.ts"
|
||||||
}
|
}
|
||||||
|
|||||||
133
test/user.e2e-spec.ts
Normal file
133
test/user.e2e-spec.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { AppModule } from '../src/app.module';
|
||||||
|
import { CreateUserDto } from '../src/user/dto/create-user.dto';
|
||||||
|
import { Role } from '../src/auth/role.enum';
|
||||||
|
import { UserService } from '../src/user/user.service';
|
||||||
|
import { User } from '../src/entity/user';
|
||||||
|
import { UpdateUserDto } from '../src/user/dto/update-user.dto';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config({ path: '.env.e2e' });
|
||||||
|
|
||||||
|
describe('UserController (e2e)', () => {
|
||||||
|
let app: INestApplication;
|
||||||
|
let jwtToken: string;
|
||||||
|
let adminUserId: number;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
|
await app.init();
|
||||||
|
|
||||||
|
const userService = moduleFixture.get<UserService>(UserService);
|
||||||
|
const adminUser = await userService.create({
|
||||||
|
username: 'e2e_admin',
|
||||||
|
password: 'password',
|
||||||
|
roles: [Role.Admin],
|
||||||
|
});
|
||||||
|
adminUserId = adminUser.id;
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post('/auth/login')
|
||||||
|
.send({ username: 'e2e_admin', password: 'password' });
|
||||||
|
|
||||||
|
jwtToken = response.body.access_token;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const userService = app.get<UserService>(UserService);
|
||||||
|
await userService.remove(adminUserId);
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/users', () => {
|
||||||
|
it('(GET) should get all users', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.get('/users')
|
||||||
|
.set('Authorization', `Bearer ${jwtToken}`);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(Array.isArray(response.body)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(POST) should create a user', async () => {
|
||||||
|
const createUserDto: CreateUserDto = {
|
||||||
|
username: 'e2e_user',
|
||||||
|
password: 'password',
|
||||||
|
roles: [Role.User],
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post('/users')
|
||||||
|
.set('Authorization', `Bearer ${jwtToken}`)
|
||||||
|
.send(createUserDto);
|
||||||
|
|
||||||
|
expect(response.status).toBe(201);
|
||||||
|
expect(response.body.username).toEqual(createUserDto.username);
|
||||||
|
|
||||||
|
const userService = app.get<UserService>(UserService);
|
||||||
|
await userService.remove(response.body.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/users/:id', () => {
|
||||||
|
let user: User;
|
||||||
|
let userService: UserService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
userService = app.get<UserService>(UserService);
|
||||||
|
user = await userService.create({
|
||||||
|
username: 'e2e_test_user',
|
||||||
|
password: 'password',
|
||||||
|
roles: [Role.User],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const userExists = await userService.findOne(user.id);
|
||||||
|
if (userExists) {
|
||||||
|
await userService.remove(user.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(GET) should get a user by id', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.get(`/users/${user.id}`)
|
||||||
|
.set('Authorization', `Bearer ${jwtToken}`);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body.id).toEqual(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(PATCH) should update a user', async () => {
|
||||||
|
const updateUserDto: UpdateUserDto = {
|
||||||
|
username: 'e2e_updated_user',
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.patch(`/users/${user.id}`)
|
||||||
|
.set('Authorization', `Bearer ${jwtToken}`)
|
||||||
|
.send(updateUserDto);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body.username).toEqual(updateUserDto.username);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(DELETE) should delete a user', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.delete(`/users/${user.id}`)
|
||||||
|
.set('Authorization', `Bearer ${jwtToken}`);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
|
const deletedUser = await userService.findOne(user.id);
|
||||||
|
expect(deletedUser).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user