Skip to content
Merged
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
25 changes: 25 additions & 0 deletions .github/workflows/ts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: TypeScript CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
name: Test and Lint
runs-on: ubuntu-latest

steps:
- name: 🧾 Checkout repository
uses: actions/checkout@v4

- name: 📦 Install dependencies
run: yarn install --immutable

- name: 🔍 Lint with ESLint
run: yarn lint

- name: ✅ Run tests
run: yarn test
12 changes: 9 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ async function bootstrap() {
});

app.enableShutdownHooks();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);

const port = process.env.PORT ?? 3000
const port = process.env.PORT ?? 3000;

const config = new DocumentBuilder()
.setTitle('TypeScript NestJS Template')
Expand All @@ -27,4 +33,4 @@ async function bootstrap() {
await app.listen(port);
}

bootstrap();
void bootstrap();
6 changes: 3 additions & 3 deletions src/users/entities/user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface User {
id: number;
name: string;
email: string;
id: number;
name: string;
email: string;
}
10 changes: 7 additions & 3 deletions src/users/users.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Body, Controller, Get, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import {
ApiCreatedResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import type { User } from './entities/user.entity';

@ApiTags('User')
@Controller('/api/v1/users')
export class UsersController {
constructor(private readonly usersService: UsersService) {
}
constructor(private readonly usersService: UsersService) {}

@Post()
@ApiOperation({ summary: 'Create User' })
Expand Down
27 changes: 27 additions & 0 deletions src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';

describe('UsersService', () => {
let service: UsersService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();

service = module.get<UsersService>(UsersService);
});

it('should save a user', () => {
const dto: CreateUserDto = { name: 'Mario', email: 'mario@example.com' };
const user = service.saveUser(dto);
expect(user).toMatchObject(dto);
expect(user.id).toBeDefined();
});

it('should get all users empty', () => {
const all = service.getAllUsers();
expect(all).toEqual([]);
});
});
2 changes: 1 addition & 1 deletion src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from "./entities/user.entity";
import { User } from './entities/user.entity';

@Injectable()
export class UsersService {
Expand Down
25 changes: 0 additions & 25 deletions test/app.e2e-spec.ts

This file was deleted.

60 changes: 60 additions & 0 deletions test/users.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import request from 'supertest';
import { AppModule } from 'src/app.module';
import { User } from 'src/users/entities/user.entity';

describe('UsersModule (e2e)', () => {
let app: INestApplication;

beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();

app = moduleFixture.createNestApplication();
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
await app.init();
});

it('/api/v1/users (POST) should create a user', async () => {
const userDto = {
name: 'Simone',
email: 'simone@example.com',
};

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const res = await request(app.getHttpServer())
.post('/api/v1/users')
.send(userDto)
.expect(201);

const user = res.body as User;
expect(typeof user.id).toBe('number');
expect(user.name).toBe(userDto.name);
expect(user.email).toBe(userDto.email);
});

it('/api/v1/users (GET) should return all users', async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const res = await request(app.getHttpServer())
.get('/api/v1/users')
.expect(200);

const users = res.body as User[];
expect(Array.isArray(users)).toBe(true);
expect(users[0]).toHaveProperty('id');
expect(users[0]).toHaveProperty('name');
expect(users[0]).toHaveProperty('email');
});

afterAll(async () => {
await app.close();
});
});