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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ One of our main principals has been to keep the starter kit as lightweight as po
| Docker Ready | Dockerfile | Done |
| Devcontainer | - | Done |
| Auto-generated OpenAPI | - | Done |
| Auto-generated ChangeLog | - | WIP |
| Auto-generated ChangeLog | - | WIP. |

Apart from these features above, our start-kit comes loaded with a bunch of minor awesomeness like prettier integration, commit-linting husky hooks, package import sorting, SonarCloud github actions, docker-compose for database dependencies, etc. :D

Expand Down
2 changes: 2 additions & 0 deletions src/article/controllers/article.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Expand Down Expand Up @@ -141,6 +142,7 @@ export class ArticleController {
}

@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({
summary: 'Delete article by id API',
})
Expand Down
181 changes: 181 additions & 0 deletions test/article/article.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { HttpStatus, INestApplication, ValidationPipe } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';

import { AppModule } from '../../src/app.module';
import { ArticleOutput } from '../../src/article/dtos/article-output.dto';
import { AuthTokenOutput } from '../../src/auth/dtos/auth-token-output.dto';
import { UserOutput } from '../../src/user/dtos/user-output.dto';
import {
closeDBAfterTest,
createDBEntities,
resetDBBeforeTest,
seedAdminUser,
} from '../test-utils';

describe('ArticleController (e2e)', () => {
let app: INestApplication;
let adminUser: UserOutput;
let authTokenForAdmin: AuthTokenOutput;

beforeAll(async () => {
await resetDBBeforeTest();
await createDBEntities();

const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();

app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
await app.init();

({ adminUser, authTokenForAdmin } = await seedAdminUser(app));
});

const createArticleInput = {
title: 'Test Article Title',
post: 'This is a test article content for e2e testing.',
};

const updateArticleInput = {
title: 'Updated Test Article Title',
post: 'This is the updated test article content.',
};

let createdArticle: ArticleOutput;

describe('Create article', () => {
it('successfully creates an article', async () => {
const response = await request(app.getHttpServer())
.post('/articles')
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.send(createArticleInput)
.expect(HttpStatus.CREATED);

createdArticle = response.body.data;
expect(createdArticle.title).toEqual(createArticleInput.title);
expect(createdArticle.post).toEqual(createArticleInput.post);
expect(createdArticle.author.id).toEqual(adminUser.id);
expect(createdArticle.author.name).toEqual(adminUser.name);
expect(createdArticle.id).toBeDefined();
expect(createdArticle.createdAt).toBeDefined();
expect(createdArticle.updatedAt).toBeDefined();
});

it('Unauthorized error when BearerToken is not provided', async () => {
return request(app.getHttpServer())
.post('/articles')
.send(createArticleInput)
.expect(HttpStatus.UNAUTHORIZED);
});

it('validation error when title is missing', async () => {
const invalidInput = { post: 'Content without title' };
return request(app.getHttpServer())
.post('/articles')
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.send(invalidInput)
.expect(HttpStatus.BAD_REQUEST);
});
});

describe('Get article', () => {
it('successfully gets an article by id', async () => {
return request(app.getHttpServer())
.get(`/articles/${createdArticle.id}`)
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.expect(HttpStatus.OK)
.expect((res) => {
expect(res.body.data.id).toEqual(createdArticle.id);
expect(res.body.data.title).toEqual(createdArticle.title);
expect(res.body.data.post).toEqual(createdArticle.post);
});
});

it('throws NOT_FOUND when article doesnt exist', () => {
return request(app.getHttpServer())
.get('/articles/99999')
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.expect(HttpStatus.NOT_FOUND);
});

it('Unauthorized error when BearerToken is not provided', async () => {
return request(app.getHttpServer())
.get(`/articles/${createdArticle.id}`)
.expect(HttpStatus.UNAUTHORIZED);
});
});

describe('Get all articles', () => {
it('successfully gets all articles', async () => {
return request(app.getHttpServer())
.get('/articles')
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.expect(HttpStatus.OK)
.expect((res) => {
expect(res.body.data).toBeInstanceOf(Array);
expect(res.body.data.length).toBeGreaterThan(0);
expect(res.body.meta.count).toBeGreaterThan(0);
});
});
});

describe('Update article', () => {
it('successfully updates an article', async () => {
return request(app.getHttpServer())
.patch(`/articles/${createdArticle.id}`)
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.send(updateArticleInput)
.expect(HttpStatus.OK)
.expect((res) => {
expect(res.body.data.id).toEqual(createdArticle.id);
expect(res.body.data.title).toEqual(updateArticleInput.title);
expect(res.body.data.post).toEqual(updateArticleInput.post);
expect(res.body.data.updatedAt).not.toEqual(createdArticle.updatedAt);
});
});

it('throws NOT_FOUND when article doesnt exist', () => {
return request(app.getHttpServer())
.patch('/articles/99999')
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.send(updateArticleInput)
.expect(HttpStatus.NOT_FOUND);
});

it('Unauthorized error when BearerToken is not provided', async () => {
return request(app.getHttpServer())
.patch(`/articles/${createdArticle.id}`)
.send(updateArticleInput)
.expect(HttpStatus.UNAUTHORIZED);
});
});

describe('Delete article', () => {
it('successfully deletes an article', async () => {
return request(app.getHttpServer())
.delete(`/articles/${createdArticle.id}`)
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.expect(HttpStatus.NO_CONTENT);
});

it('throws NOT_FOUND when trying to delete non-existent article', () => {
return request(app.getHttpServer())
.delete('/articles/99999')
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
.expect(HttpStatus.NOT_FOUND);
});

it('Unauthorized error when BearerToken is not provided', async () => {
return request(app.getHttpServer())
.delete('/articles/12345')
.expect(HttpStatus.UNAUTHORIZED);
});
});

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