Skip to content

Commit e402914

Browse files
authored
Merge pull request #21 from jetbrains-academy/konstantin/deploy_lesson
Add a Deploy lesson
2 parents 92a5ef2 + cac4404 commit e402914

File tree

153 files changed

+7483
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

153 files changed

+7483
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
JWT_SECRET=testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestte==
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM node:20-alpine
2+
3+
WORKDIR /app
4+
5+
COPY package*.json ./
6+
RUN npm install
7+
COPY . .
8+
9+
EXPOSE 8000
10+
11+
CMD ["npm", "start"]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import request from 'supertest';
2+
import { httpServer } from '../src/index.js';
3+
import {dbReset} from "../src/data/dataServices.js";
4+
5+
describe('Authentication API', () => {
6+
7+
afterAll((done) => {
8+
httpServer.close(done);
9+
});
10+
11+
describe('POST /api/auth/register', () => {
12+
beforeEach(async() => {
13+
await dbReset();
14+
15+
});
16+
17+
it('should register a new user successfully', async () => {
18+
const response = await request(httpServer)
19+
.post('/api/auth/register')
20+
.send({
21+
username: 'testuser',
22+
password: 'password123'
23+
})
24+
.timeout(4000);
25+
26+
expect(response.status).toBe(201);
27+
expect(response.body).toHaveProperty('username', 'testuser');
28+
});
29+
30+
it('should not allow duplicate usernames', async () => {
31+
// Register first user
32+
await request(httpServer)
33+
.post('/api/auth/register')
34+
.send({
35+
username: 'testuser',
36+
password: 'password123'
37+
})
38+
.timeout(4000);
39+
40+
// Try to register the same username
41+
const response = await request(httpServer)
42+
.post('/api/auth/register')
43+
.send({
44+
username: 'testuser',
45+
password: 'differentpassword'
46+
})
47+
.timeout(4000);
48+
49+
expect(response.status).toBe(409);
50+
expect(response.body).toHaveProperty('message', 'Username already exists');
51+
});
52+
});
53+
54+
describe('POST /api/auth/login', () => {
55+
beforeAll(async () => {
56+
await dbReset();
57+
// Create a test user before login test
58+
await request(httpServer)
59+
.post('/api/auth/register')
60+
.send({
61+
username: 'testuser',
62+
password: 'password123'
63+
})
64+
.timeout(4000);
65+
});
66+
67+
it('should log in successfully with correct credentials', async () => {
68+
const response = await request(httpServer)
69+
.post('/api/auth/login')
70+
.send({
71+
username: 'testuser',
72+
password: 'password123'
73+
})
74+
.timeout(4000);
75+
76+
expect(response.status).toBe(200);
77+
expect(response.body).toHaveProperty('username', 'testuser');
78+
expect(response.body).toHaveProperty('token');
79+
});
80+
81+
it('should fail with incorrect password', async () => {
82+
const response = await request(httpServer)
83+
.post('/api/auth/login')
84+
.send({
85+
username: 'testuser',
86+
password: 'wrongpassword'
87+
})
88+
.timeout(4000);
89+
90+
expect(response.status).toBe(401);
91+
expect(response.body).toHaveProperty('message', 'Invalid username or password');
92+
});
93+
94+
it('should fail with non-existent username', async () => {
95+
const response = await request(httpServer)
96+
.post('/api/auth/login')
97+
.send({
98+
username: 'nonexistent',
99+
password: 'password123'
100+
})
101+
.timeout(4000);
102+
103+
expect(response.status).toBe(401);
104+
expect(response.body).toHaveProperty('message', 'Invalid username or password');
105+
});
106+
});
107+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {dbReset, userService, messageService} from '../src/data/dataServices.js';
2+
3+
describe('Test database-backed data layer', () => {
4+
beforeEach(async () => {
5+
await dbReset();
6+
});
7+
8+
describe('Test userService', () => {
9+
it('Test new user creation', async () => {
10+
const newUser = await userService.createUser('Tom', 'password123');
11+
expect(newUser.username).toBe('Tom');
12+
});
13+
14+
it('Test existing user creation', async () => {
15+
await userService.createUser('John', '1234567890');
16+
17+
await expect(userService.createUser('John', 'newPassword'))
18+
.rejects
19+
.toThrow('Username already exists');
20+
});
21+
22+
it('Test get existing user', async () => {
23+
const existingUser = await userService.createUser('Alice', 'mypassword');
24+
const fetchedUser = await userService.getUser('Alice');
25+
expect(fetchedUser.username).toEqual(existingUser.username);
26+
});
27+
28+
it('Test get non-existing user', async () => {
29+
const nonExistentUser = await userService.getUser('NonExistentUser');
30+
expect(nonExistentUser).toBeUndefined();
31+
});
32+
});
33+
34+
describe('Test messageService', () => {
35+
it('Test add new message', async () => {
36+
const message = await messageService.addMessage('Alice', 'Test message');
37+
expect(message.username).toBe('Alice');
38+
expect(message.content).toBe('Test message');
39+
expect(message.id).toBeDefined();
40+
});
41+
42+
it('Test get messages', async () => {
43+
await messageService.addMessage('Tom', 'Message 1');
44+
await messageService.addMessage('John', 'Message 2');
45+
46+
const messages = await messageService.getMessages();
47+
expect(messages.length).toBe(2);
48+
expect(messages[0].content).toBe('Message 1');
49+
expect(messages[1].content).toBe('Message 2');
50+
});
51+
52+
xit('Test delete the only message', async () => {
53+
const newMessage = await messageService.addMessage('Alice', 'Single message');
54+
const result = await messageService.deleteMessage(newMessage.id);
55+
expect(result).toBe(true);
56+
57+
const messages = await messageService.getMessages();
58+
expect(messages.length).toBe(0);
59+
});
60+
61+
xit('Test delete message with others', async () => {
62+
await messageService.addMessage('Alice', 'Message A');
63+
const messageToDelete = await messageService.addMessage('Alice', 'Message B');
64+
await messageService.addMessage('Alice', 'Message C');
65+
66+
const result = await messageService.deleteMessage(messageToDelete.id);
67+
expect(result).toBe(true);
68+
69+
const messages = await messageService.getMessages();
70+
expect(messages.length).toBe(2);
71+
expect(messages[0].content).toBe('Message A');
72+
expect(messages[1].content).toBe('Message C');
73+
});
74+
75+
xit('Test delete non-existing message', async () => {
76+
const result = await messageService.deleteMessage(999999);
77+
expect(result).toBe(false);
78+
});
79+
});
80+
});
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import request from 'supertest';
2+
import {httpServer} from '../src/index.js';
3+
import {dbReset} from '../src/data/dataServices.js';
4+
5+
describe('Messages API', () => {
6+
let authToken;
7+
const testUser = {
8+
username: 'User',
9+
password: '1234'
10+
};
11+
beforeAll(() => {
12+
});
13+
14+
afterAll((done) => {
15+
httpServer.close(done);
16+
});
17+
18+
beforeEach(async () => {
19+
// Clear data
20+
await dbReset();
21+
22+
// Register and login test user
23+
const registerResponse = await request(httpServer)
24+
.post('/api/auth/register')
25+
.send(testUser);
26+
27+
authToken = registerResponse.body.token;
28+
});
29+
30+
describe('Messages API - Unauthorized Access', () => {
31+
32+
it('should return 401 for POST /api/messages without a token', async () => {
33+
const response = await request(httpServer)
34+
.post('/api/messages')
35+
.send({ content: 'Unauthorized message' });
36+
37+
expect(response.status).toBe(401);
38+
expect(response.body).toHaveProperty('message');
39+
});
40+
41+
it('should return 401 for GET /api/messages without a token', async () => {
42+
const response = await request(httpServer)
43+
.get('/api/messages');
44+
45+
expect(response.status).toBe(401);
46+
expect(response.body).toHaveProperty('message');
47+
});
48+
49+
it('should return 401 for DELETE /api/messages/:id without a token', async () => {
50+
const response = await request(httpServer)
51+
.delete('/api/messages/1');
52+
53+
expect(response.status).toBe(401);
54+
expect(response.body).toHaveProperty('message');
55+
});
56+
});
57+
58+
59+
describe('POST /api/messages', () => {
60+
it('should create a new message with correct structure', async () => {
61+
const response = await request(httpServer)
62+
.post('/api/messages')
63+
.set('Authorization', `Bearer ${authToken}`)
64+
.send({ content: 'Hello, World!' })
65+
.timeout(4000);
66+
67+
expect(response.status).toBe(201);
68+
expect(response.body).toMatchObject({
69+
content: 'Hello, World!',
70+
username: 'User',
71+
id: expect.any(Number),
72+
});
73+
});
74+
75+
it('should fail without content', async () => {
76+
const response = await request(httpServer)
77+
.post('/api/messages')
78+
.set('Authorization', `Bearer ${authToken}`)
79+
.send()
80+
.timeout(4000);
81+
82+
expect(response.status).toBe(400);
83+
expect(response.body).toHaveProperty('message', 'Message content is required');
84+
});
85+
});
86+
87+
describe('GET /api/messages', () => {
88+
it('should get empty message list initially', async () => {
89+
const response = await request(httpServer)
90+
.get('/api/messages')
91+
.set('Authorization', `Bearer ${authToken}`)
92+
.timeout(4000);
93+
94+
expect(response.status).toBe(200);
95+
expect(response.body).toEqual([]);
96+
});
97+
98+
it('should get messages', async () => {
99+
// Add test messages
100+
for (let i = 0; i < 3; i++) {
101+
await request(httpServer)
102+
.post('/api/messages')
103+
.set('Authorization', `Bearer ${authToken}`)
104+
.send({ content: `Test message ${i}` })
105+
.timeout(4000);
106+
}
107+
108+
const response = await request(httpServer)
109+
.get('/api/messages')
110+
.set('Authorization', `Bearer ${authToken}`)
111+
.timeout(4000);
112+
113+
expect(response.status).toBe(200);
114+
expect(response.body).toHaveLength(3);
115+
expect(response.body[2].content).toBe('Test message 2');
116+
});
117+
});
118+
119+
describe('DELETE /api/messages/:id', () => {
120+
xit('should delete an existing message', async () => {
121+
// Create a message
122+
const createResponse = await request(httpServer)
123+
.post('/api/messages')
124+
.set('Authorization', `Bearer ${authToken}`)
125+
.send({ content: 'Test message' })
126+
.timeout(4000);
127+
128+
const messageId = createResponse.body.id;
129+
130+
// Delete the message
131+
const deleteResponse = await request(httpServer)
132+
.delete(`/api/messages/${messageId}`)
133+
.set('Authorization', `Bearer ${authToken}`)
134+
.timeout(4000);
135+
136+
expect(deleteResponse.status).toBe(204);
137+
138+
// Verify the message is deleted
139+
const getResponse = await request(httpServer)
140+
.get('/api/messages')
141+
.set('Authorization', `Bearer ${authToken}`)
142+
.timeout(4000);
143+
144+
expect(getResponse.body).toHaveLength(0);
145+
});
146+
147+
xit('should return 404 for non-existent message', async () => {
148+
const response = await request(httpServer)
149+
.delete('/api/messages/999999')
150+
.set('Authorization', `Bearer ${authToken}`)
151+
.timeout(4000);
152+
153+
expect(response.status).toBe(404);
154+
});
155+
156+
});
157+
});

0 commit comments

Comments
 (0)