Skip to content

Commit 8a30958

Browse files
committed
test: add article e2e tests with CRUD
operations - Add comprehensive e2e tests for article controller - Test create, read, update, delete operations - Include authorization and validation error scenarios - Fix article controller to return NO_CONTENT for delete
1 parent bab461a commit 8a30958

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

src/article/controllers/article.controller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Controller,
55
Delete,
66
Get,
7+
HttpCode,
78
HttpStatus,
89
Param,
910
Patch,
@@ -141,6 +142,7 @@ export class ArticleController {
141142
}
142143

143144
@Delete(':id')
145+
@HttpCode(HttpStatus.NO_CONTENT)
144146
@ApiOperation({
145147
summary: 'Delete article by id API',
146148
})

test/article/article.e2e-spec.ts

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { HttpStatus, INestApplication, ValidationPipe } from '@nestjs/common';
2+
import { Test } from '@nestjs/testing';
3+
import request from 'supertest';
4+
5+
import { AppModule } from '../../src/app.module';
6+
import { ArticleOutput } from '../../src/article/dtos/article-output.dto';
7+
import { AuthTokenOutput } from '../../src/auth/dtos/auth-token-output.dto';
8+
import { UserOutput } from '../../src/user/dtos/user-output.dto';
9+
import {
10+
closeDBAfterTest,
11+
createDBEntities,
12+
resetDBBeforeTest,
13+
seedAdminUser,
14+
} from '../test-utils';
15+
16+
describe('ArticleController (e2e)', () => {
17+
let app: INestApplication;
18+
let adminUser: UserOutput;
19+
let authTokenForAdmin: AuthTokenOutput;
20+
21+
beforeAll(async () => {
22+
await resetDBBeforeTest();
23+
await createDBEntities();
24+
25+
const moduleRef = await Test.createTestingModule({
26+
imports: [AppModule],
27+
}).compile();
28+
29+
app = moduleRef.createNestApplication();
30+
app.useGlobalPipes(new ValidationPipe());
31+
await app.init();
32+
33+
({ adminUser, authTokenForAdmin } = await seedAdminUser(app));
34+
});
35+
36+
const createArticleInput = {
37+
title: 'Test Article Title',
38+
post: 'This is a test article content for e2e testing.',
39+
};
40+
41+
const updateArticleInput = {
42+
title: 'Updated Test Article Title',
43+
post: 'This is the updated test article content.',
44+
};
45+
46+
let createdArticle: ArticleOutput;
47+
48+
describe('Create article', () => {
49+
it('successfully creates an article', async () => {
50+
const response = await request(app.getHttpServer())
51+
.post('/articles')
52+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
53+
.send(createArticleInput)
54+
.expect(HttpStatus.CREATED);
55+
56+
createdArticle = response.body.data;
57+
expect(createdArticle.title).toEqual(createArticleInput.title);
58+
expect(createdArticle.post).toEqual(createArticleInput.post);
59+
expect(createdArticle.author.id).toEqual(adminUser.id);
60+
expect(createdArticle.author.name).toEqual(adminUser.name);
61+
expect(createdArticle.id).toBeDefined();
62+
expect(createdArticle.createdAt).toBeDefined();
63+
expect(createdArticle.updatedAt).toBeDefined();
64+
});
65+
66+
it('Unauthorized error when BearerToken is not provided', async () => {
67+
return request(app.getHttpServer())
68+
.post('/articles')
69+
.send(createArticleInput)
70+
.expect(HttpStatus.UNAUTHORIZED);
71+
});
72+
73+
it('validation error when title is missing', async () => {
74+
const invalidInput = { post: 'Content without title' };
75+
return request(app.getHttpServer())
76+
.post('/articles')
77+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
78+
.send(invalidInput)
79+
.expect(HttpStatus.BAD_REQUEST);
80+
});
81+
});
82+
83+
describe('Get article', () => {
84+
it('successfully gets an article by id', async () => {
85+
return request(app.getHttpServer())
86+
.get(`/articles/${createdArticle.id}`)
87+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
88+
.expect(HttpStatus.OK)
89+
.expect((res) => {
90+
expect(res.body.data.id).toEqual(createdArticle.id);
91+
expect(res.body.data.title).toEqual(createdArticle.title);
92+
expect(res.body.data.post).toEqual(createdArticle.post);
93+
});
94+
});
95+
96+
it('throws NOT_FOUND when article doesnt exist', () => {
97+
return request(app.getHttpServer())
98+
.get('/articles/99999')
99+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
100+
.expect(HttpStatus.NOT_FOUND);
101+
});
102+
103+
it('Unauthorized error when BearerToken is not provided', async () => {
104+
return request(app.getHttpServer())
105+
.get(`/articles/${createdArticle.id}`)
106+
.expect(HttpStatus.UNAUTHORIZED);
107+
});
108+
});
109+
110+
describe('Get all articles', () => {
111+
it('successfully gets all articles', async () => {
112+
return request(app.getHttpServer())
113+
.get('/articles')
114+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
115+
.expect(HttpStatus.OK)
116+
.expect((res) => {
117+
expect(res.body.data).toBeInstanceOf(Array);
118+
expect(res.body.data.length).toBeGreaterThan(0);
119+
expect(res.body.meta.count).toBeGreaterThan(0);
120+
});
121+
});
122+
});
123+
124+
describe('Update article', () => {
125+
it('successfully updates an article', async () => {
126+
return request(app.getHttpServer())
127+
.patch(`/articles/${createdArticle.id}`)
128+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
129+
.send(updateArticleInput)
130+
.expect(HttpStatus.OK)
131+
.expect((res) => {
132+
expect(res.body.data.id).toEqual(createdArticle.id);
133+
expect(res.body.data.title).toEqual(updateArticleInput.title);
134+
expect(res.body.data.post).toEqual(updateArticleInput.post);
135+
expect(res.body.data.updatedAt).not.toEqual(createdArticle.updatedAt);
136+
});
137+
});
138+
139+
it('throws NOT_FOUND when article doesnt exist', () => {
140+
return request(app.getHttpServer())
141+
.patch('/articles/99999')
142+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
143+
.send(updateArticleInput)
144+
.expect(HttpStatus.NOT_FOUND);
145+
});
146+
147+
it('Unauthorized error when BearerToken is not provided', async () => {
148+
return request(app.getHttpServer())
149+
.patch(`/articles/${createdArticle.id}`)
150+
.send(updateArticleInput)
151+
.expect(HttpStatus.UNAUTHORIZED);
152+
});
153+
});
154+
155+
describe('Delete article', () => {
156+
it('successfully deletes an article', async () => {
157+
return request(app.getHttpServer())
158+
.delete(`/articles/${createdArticle.id}`)
159+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
160+
.expect(HttpStatus.NO_CONTENT);
161+
});
162+
163+
it('throws NOT_FOUND when trying to delete non-existent article', () => {
164+
return request(app.getHttpServer())
165+
.delete('/articles/99999')
166+
.set('Authorization', 'Bearer ' + authTokenForAdmin.accessToken)
167+
.expect(HttpStatus.NOT_FOUND);
168+
});
169+
170+
it('Unauthorized error when BearerToken is not provided', async () => {
171+
return request(app.getHttpServer())
172+
.delete('/articles/12345')
173+
.expect(HttpStatus.UNAUTHORIZED);
174+
});
175+
});
176+
177+
afterAll(async () => {
178+
await app.close();
179+
await closeDBAfterTest();
180+
});
181+
});

0 commit comments

Comments
 (0)