api: flesh tests for mood and user's crud
Pedro Lucas Porcellis porcellis@eletrotupi.com · 10 months ago
56ae9d8272c35d408c95fb441f727bf93b0507d3 Browse filesParents
ba9ebeeChanged files
8
Additions
+1829
Deletions
-17
api/tests/mood.test.js
1 1
import test from "ava";
2 2
import request from "supertest";
3 3
import { createApp } from "../src/create-app.js";
4 4
import { setupTestDb, cleanupTestDb } from "./test-helper.js";
5 5
31 31
test.serial("Registrar humor", async (t) => {
32 32
const res = await request(app).post("/mood").send({
33 33
user_id: userId,
34 34
stress_level: 3,
35 35
anxiety_level: 4,
36 +
energy_level: 5,
36 37
title: "Bad day",
37 38
description: "Stressado com a faculdade",
39 +
recorded_at: new Date().toISOString()
38 40
});
39 41
40 42
t.is(res.status, 201);
41 43
t.truthy(res.body.id);
42 44
});
43 45
44 46
test.serial("Listar humores do usuário", async (t) => {
45 -
const res = await request(app).get(`/mood/${userId}`);
47 +
const res = await request(app).get(`/mood/user/${userId}`);
46 48
t.is(res.status, 200);
47 -
t.true(Array.isArray(res.body));
49 +
t.true(Array.isArray(res.body.moods));
48 50
});
49 51
50 52
test.serial("Falha ao registrar humor sem stress_level", async (t) => {
51 53
const res = await request(app).post("/mood").send({
52 54
user_id: userId,
53 55
anxiety_level: 4,
56 +
energy_level: 5,
54 57
title: "Test",
58 +
recorded_at: new Date().toISOString()
55 59
});
56 60
57 61
t.is(res.status, 400);
58 62
t.is(res.body.error, "Campos faltantes");
59 63
});
65 69
// Create first mood entry
66 70
const res1 = await request(app).post("/mood").send({
67 71
user_id: userId,
68 72
stress_level: 3,
69 73
anxiety_level: 4,
74 +
energy_level: 6,
70 75
title: "Morning",
71 76
description: "Morning mood",
77 +
recorded_at: new Date().toISOString()
72 78
});
73 79
t.is(res1.status, 201);
74 80
75 81
// Create second mood entry
76 82
const res2 = await request(app).post("/mood").send({
77 83
user_id: userId,
78 84
stress_level: 2,
79 85
anxiety_level: 2,
86 +
energy_level: 8,
80 87
title: "Evening",
81 88
description: "Evening mood",
89 +
recorded_at: new Date().toISOString()
82 90
});
83 91
t.is(res2.status, 201);
84 92
85 -
const res = await request(app).get(`/mood/${userId}`);
93 +
const res = await request(app).get(`/mood/user/${userId}`);
86 94
t.is(res.status, 200);
87 -
t.true(Array.isArray(res.body));
88 -
t.is(res.body.length, 2);
95 +
t.true(Array.isArray(res.body.moods));
96 +
t.is(res.body.moods.length, 2);
89 97
});
90 98
/dev/null → api/tests/mood/creation.test.js
1 +
import test from "ava";
2 +
import request from "supertest";
3 +
import { createApp } from "../../src/create-app.js";
4 +
import { setupTestDb, cleanupTestDb } from "../test-helper.js";
5 +
6 +
let app;
7 +
let db;
8 +
let userId;
9 +
10 +
test.before(() => {
11 +
db = setupTestDb();
12 +
app = createApp(db);
13 +
});
14 +
15 +
test.beforeEach(async () => {
16 +
cleanupTestDb();
17 +
18 +
// Create a test user with unique email
19 +
const uniqueEmail = `mood_${Date.now()}_${Math.random()}@example.com`;
20 +
const res = await request(app)
21 +
.post("/users")
22 +
.send({
23 +
email: uniqueEmail,
24 +
password: "senha123",
25 +
first_name: "Mood",
26 +
last_name: "Tester"
27 +
});
28 +
29 +
if (res.status !== 201) {
30 +
throw new Error(`Failed to create test user: ${JSON.stringify(res.body)}`);
31 +
}
32 +
33 +
userId = res.body.id;
34 +
});
35 +
36 +
test("Criar registro de humor com todos os campos obrigatórios", async (t) => {
37 +
const now = new Date().toISOString();
38 +
39 +
const res = await request(app)
40 +
.post("/mood")
41 +
.send({
42 +
user_id: userId,
43 +
stress_level: 3,
44 +
anxiety_level: 4,
45 +
energy_level: 7,
46 +
recorded_at: now
47 +
});
48 +
49 +
t.is(res.status, 201);
50 +
t.truthy(res.body.id);
51 +
});
52 +
53 +
test("Criar registro de humor com avaliação e descrição", async (t) => {
54 +
const now = new Date().toISOString();
55 +
56 +
const res = await request(app)
57 +
.post("/mood")
58 +
.send({
59 +
user_id: userId,
60 +
rating: 8,
61 +
stress_level: 2,
62 +
anxiety_level: 3,
63 +
energy_level: 9,
64 +
title: "Dia produtivo",
65 +
description: "Consegui terminar todas as tarefas",
66 +
recorded_at: now
67 +
});
68 +
69 +
t.is(res.status, 201);
70 +
71 +
// Verify data was saved correctly
72 +
const mood = db.prepare("SELECT * FROM mood WHERE id = ?").get(res.body.id);
73 +
t.is(mood.rating, 8);
74 +
t.is(mood.stress_level, 2);
75 +
t.is(mood.title, "Dia produtivo");
76 +
});
77 +
78 +
test("Criar registro de humor com componentes emocionais", async (t) => {
79 +
const now = new Date().toISOString();
80 +
81 +
const res = await request(app)
82 +
.post("/mood")
83 +
.send({
84 +
user_id: userId,
85 +
stress_level: 5,
86 +
anxiety_level: 6,
87 +
energy_level: 4,
88 +
recorded_at: now,
89 +
mood_components: [
90 +
{ emotion: "joy", intensity: 7 },
91 +
{ emotion: "anxiety", intensity: 6 },
92 +
{ emotion: "trust", intensity: 8 }
93 +
]
94 +
});
95 +
96 +
t.is(res.status, 201);
97 +
98 +
// Verify components were saved
99 +
const components = db.prepare("SELECT * FROM mood_components WHERE mood_id = ?").all(res.body.id);
100 +
t.is(components.length, 3);
101 +
102 +
const emotions = components.map(c => c.emotion);
103 +
t.true(emotions.includes('joy'));
104 +
t.true(emotions.includes('anxiety'));
105 +
t.true(emotions.includes('trust'));
106 +
});
107 +
108 +
test("Componentes emocionais devem aceitar maiúsculas e converter para minúsculas", async (t) => {
109 +
const now = new Date().toISOString();
110 +
111 +
const res = await request(app)
112 +
.post("/mood")
113 +
.send({
114 +
user_id: userId,
115 +
stress_level: 5,
116 +
anxiety_level: 6,
117 +
energy_level: 4,
118 +
recorded_at: now,
119 +
mood_components: [
120 +
{ emotion: "JOY", intensity: 7 },
121 +
{ emotion: "AnXiEtY", intensity: 6 }
122 +
]
123 +
});
124 +
125 +
t.is(res.status, 201);
126 +
127 +
// Verify emotions were lowercased
128 +
const components = db.prepare("SELECT emotion FROM mood_components WHERE mood_id = ? ORDER BY emotion ASC").all(res.body.id);
129 +
t.is(components[0].emotion, 'anxiety');
130 +
t.is(components[1].emotion, 'joy');
131 +
});
132 +
133 +
test("Falha ao criar registro de humor sem ID de usuário", async (t) => {
134 +
const now = new Date().toISOString();
135 +
136 +
const res = await request(app)
137 +
.post("/mood")
138 +
.send({
139 +
stress_level: 3,
140 +
anxiety_level: 4,
141 +
energy_level: 7,
142 +
recorded_at: now
143 +
});
144 +
145 +
t.is(res.status, 400);
146 +
t.is(res.body.error, "Campos faltantes");
147 +
t.true(res.body.missing.includes('user_id'));
148 +
});
149 +
150 +
test("Falha ao criar registro de humor sem nível de stress", async (t) => {
151 +
const now = new Date().toISOString();
152 +
153 +
const res = await request(app)
154 +
.post("/mood")
155 +
.send({
156 +
user_id: userId,
157 +
anxiety_level: 4,
158 +
energy_level: 7,
159 +
recorded_at: now
160 +
});
161 +
162 +
t.is(res.status, 400);
163 +
t.is(res.body.error, "Campos faltantes");
164 +
t.true(res.body.missing.includes('stress_level'));
165 +
});
166 +
167 +
test("Falha ao criar registro de humor sem nível de ansiedade", async (t) => {
168 +
const now = new Date().toISOString();
169 +
170 +
const res = await request(app)
171 +
.post("/mood")
172 +
.send({
173 +
user_id: userId,
174 +
stress_level: 3,
175 +
energy_level: 7,
176 +
recorded_at: now
177 +
});
178 +
179 +
t.is(res.status, 400);
180 +
t.is(res.body.error, "Campos faltantes");
181 +
t.true(res.body.missing.includes('anxiety_level'));
182 +
});
183 +
184 +
test("Falha ao criar registro de humor sem nível de energia", async (t) => {
185 +
const now = new Date().toISOString();
186 +
187 +
const res = await request(app)
188 +
.post("/mood")
189 +
.send({
190 +
user_id: userId,
191 +
stress_level: 3,
192 +
anxiety_level: 4,
193 +
recorded_at: now
194 +
});
195 +
196 +
t.is(res.status, 400);
197 +
t.is(res.body.error, "Campos faltantes");
198 +
t.true(res.body.missing.includes('energy_level'));
199 +
});
200 +
201 +
test("Falha ao criar registro de humor sem data de registro", async (t) => {
202 +
const res = await request(app)
203 +
.post("/mood")
204 +
.send({
205 +
user_id: userId,
206 +
stress_level: 3,
207 +
anxiety_level: 4,
208 +
energy_level: 7
209 +
});
210 +
211 +
t.is(res.status, 400);
212 +
t.is(res.body.error, "Campos faltantes");
213 +
t.true(res.body.missing.includes('recorded_at'));
214 +
});
215 +
216 +
test("Falha ao criar registro de humor com nível de estresse inválido (menor que 1)", async (t) => {
217 +
const now = new Date().toISOString();
218 +
219 +
const res = await request(app)
220 +
.post("/mood")
221 +
.send({
222 +
user_id: userId,
223 +
stress_level: 0,
224 +
anxiety_level: 4,
225 +
energy_level: 7,
226 +
recorded_at: now
227 +
});
228 +
229 +
t.is(res.status, 400);
230 +
t.is(res.body.error, "stress_level deve ser entre 1 e 10");
231 +
});
232 +
233 +
test("Falha ao criar registro de humor com nível de estresse inválido (maior que 10)", async (t) => {
234 +
const now = new Date().toISOString();
235 +
236 +
const res = await request(app)
237 +
.post("/mood")
238 +
.send({
239 +
user_id: userId,
240 +
stress_level: 11,
241 +
anxiety_level: 4,
242 +
energy_level: 7,
243 +
recorded_at: now
244 +
});
245 +
246 +
t.is(res.status, 400);
247 +
t.is(res.body.error, "stress_level deve ser entre 1 e 10");
248 +
});
249 +
250 +
test("Falha ao criar registro de humor com nível de ansiedade inválido", async (t) => {
251 +
const now = new Date().toISOString();
252 +
253 +
const res = await request(app)
254 +
.post("/mood")
255 +
.send({
256 +
user_id: userId,
257 +
stress_level: 3,
258 +
anxiety_level: 15,
259 +
energy_level: 7,
260 +
recorded_at: now
261 +
});
262 +
263 +
t.is(res.status, 400);
264 +
t.is(res.body.error, "anxiety_level deve ser entre 1 e 10");
265 +
});
266 +
267 +
test("Falha ao criar registro de humor com nível de energia inválido", async (t) => {
268 +
const now = new Date().toISOString();
269 +
270 +
const res = await request(app)
271 +
.post("/mood")
272 +
.send({
273 +
user_id: userId,
274 +
stress_level: 3,
275 +
anxiety_level: 4,
276 +
energy_level: -5,
277 +
recorded_at: now
278 +
});
279 +
280 +
t.is(res.status, 400);
281 +
t.is(res.body.error, "energy_level deve ser entre 1 e 10");
282 +
});
283 +
284 +
test("Falha ao criar registro de humor com avaliação inválido", async (t) => {
285 +
const now = new Date().toISOString();
286 +
287 +
const res = await request(app)
288 +
.post("/mood")
289 +
.send({
290 +
user_id: userId,
291 +
rating: 20,
292 +
stress_level: 3,
293 +
anxiety_level: 4,
294 +
energy_level: 7,
295 +
recorded_at: now
296 +
});
297 +
298 +
t.is(res.status, 400);
299 +
t.is(res.body.error, "rating deve ser entre 1 e 10");
300 +
});
301 +
302 +
test("Falha ao criar registro de humor com data inválida", async (t) => {
303 +
const res = await request(app)
304 +
.post("/mood")
305 +
.send({
306 +
user_id: userId,
307 +
stress_level: 3,
308 +
anxiety_level: 4,
309 +
energy_level: 7,
310 +
recorded_at: "data-invalida"
311 +
});
312 +
313 +
t.is(res.status, 400);
314 +
t.is(res.body.error, "recorded_at deve ser uma data válida");
315 +
});
316 +
317 +
test("Falha ao criar registro de humor com data no futuro", async (t) => {
318 +
const futureDate = new Date();
319 +
futureDate.setDate(futureDate.getDate() + 1);
320 +
321 +
const res = await request(app)
322 +
.post("/mood")
323 +
.send({
324 +
user_id: userId,
325 +
stress_level: 3,
326 +
anxiety_level: 4,
327 +
energy_level: 7,
328 +
recorded_at: futureDate.toISOString()
329 +
});
330 +
331 +
t.is(res.status, 400);
332 +
t.is(res.body.error, "recorded_at não pode ser no futuro");
333 +
});
334 +
335 +
test("Falha ao criar registro de humor com emoção inválida", async (t) => {
336 +
const now = new Date().toISOString();
337 +
338 +
const res = await request(app)
339 +
.post("/mood")
340 +
.send({
341 +
user_id: userId,
342 +
stress_level: 3,
343 +
anxiety_level: 4,
344 +
energy_level: 7,
345 +
recorded_at: now,
346 +
mood_components: [
347 +
{ emotion: "invalid_emotion", intensity: 5 }
348 +
]
349 +
});
350 +
351 +
t.is(res.status, 400);
352 +
t.regex(res.body.error, /Invalid emotion/);
353 +
});
354 +
355 +
test("Falha ao criar registro de humor com intensidade inválida (menor que 1)", async (t) => {
356 +
const now = new Date().toISOString();
357 +
358 +
const res = await request(app)
359 +
.post("/mood")
360 +
.send({
361 +
user_id: userId,
362 +
stress_level: 3,
363 +
anxiety_level: 4,
364 +
energy_level: 7,
365 +
recorded_at: now,
366 +
mood_components: [
367 +
{ emotion: "joy", intensity: 0 }
368 +
]
369 +
});
370 +
371 +
t.is(res.status, 400);
372 +
t.regex(res.body.error, /Invalid intensity/);
373 +
});
374 +
375 +
test("Falha ao criar registro de humor com intensidade inválida (maior que 10)", async (t) => {
376 +
const now = new Date().toISOString();
377 +
378 +
const res = await request(app)
379 +
.post("/mood")
380 +
.send({
381 +
user_id: userId,
382 +
stress_level: 3,
383 +
anxiety_level: 4,
384 +
energy_level: 7,
385 +
recorded_at: now,
386 +
mood_components: [
387 +
{ emotion: "sad", intensity: 11 }
388 +
]
389 +
});
390 +
391 +
t.is(res.status, 400);
392 +
t.regex(res.body.error, /Invalid intensity/);
393 +
});
394 +
395 +
test("Falha ao criar registro de humor com emoções duplicadas", async (t) => {
396 +
const now = new Date().toISOString();
397 +
398 +
const res = await request(app)
399 +
.post("/mood")
400 +
.send({
401 +
user_id: userId,
402 +
stress_level: 3,
403 +
anxiety_level: 4,
404 +
energy_level: 7,
405 +
recorded_at: now,
406 +
mood_components: [
407 +
{ emotion: "joy", intensity: 7 },
408 +
{ emotion: "joy", intensity: 5 }
409 +
]
410 +
});
411 +
412 +
t.is(res.status, 400);
413 +
t.regex(res.body.error, /Duplicate emotion/);
414 +
});
415 +
416 +
test("Falha ao criar registro de humor com componente sem emoção de referencia", async (t) => {
417 +
const now = new Date().toISOString();
418 +
419 +
const res = await request(app)
420 +
.post("/mood")
421 +
.send({
422 +
user_id: userId,
423 +
stress_level: 3,
424 +
anxiety_level: 4,
425 +
energy_level: 7,
426 +
recorded_at: now,
427 +
mood_components: [
428 +
{ intensity: 5 }
429 +
]
430 +
});
431 +
432 +
t.is(res.status, 400);
433 +
t.regex(res.body.error, /must have emotion and intensity/);
434 +
});
435 +
436 +
test("Falha ao criar registro de humor com componente sem intensidade", async (t) => {
437 +
const now = new Date().toISOString();
438 +
439 +
const res = await request(app)
440 +
.post("/mood")
441 +
.send({
442 +
user_id: userId,
443 +
stress_level: 3,
444 +
anxiety_level: 4,
445 +
energy_level: 7,
446 +
recorded_at: now,
447 +
mood_components: [
448 +
{ emotion: "joy" }
449 +
]
450 +
});
451 +
452 +
t.is(res.status, 400);
453 +
t.regex(res.body.error, /must have emotion and intensity/);
454 +
});
455 +
456 +
test("Criar registro de humor com lista vazia de componentes deve funcionar", async (t) => {
457 +
const now = new Date().toISOString();
458 +
459 +
const res = await request(app)
460 +
.post("/mood")
461 +
.send({
462 +
user_id: userId,
463 +
stress_level: 3,
464 +
anxiety_level: 4,
465 +
energy_level: 7,
466 +
recorded_at: now,
467 +
mood_components: []
468 +
});
469 +
470 +
t.is(res.status, 201);
471 +
472 +
// Verify no components were created
473 +
const components = db.prepare("SELECT * FROM mood_components WHERE mood_id = ?").all(res.body.id);
474 +
t.is(components.length, 0);
475 +
});
476 +
477 +
test("Descrição e título devem ser sanitizados (truncados em 500 caracteres)", async (t) => {
478 +
const now = new Date().toISOString();
479 +
const longText = "A".repeat(600);
480 +
481 +
const res = await request(app)
482 +
.post("/mood")
483 +
.send({
484 +
user_id: userId,
485 +
stress_level: 3,
486 +
anxiety_level: 4,
487 +
energy_level: 7,
488 +
recorded_at: now,
489 +
title: longText,
490 +
description: longText
491 +
});
492 +
493 +
t.is(res.status, 201);
494 +
495 +
const mood = db.prepare("SELECT title, description FROM mood WHERE id = ?").get(res.body.id);
496 +
t.is(mood.title.length, 500);
497 +
t.is(mood.description.length, 500);
498 +
});
/dev/null → api/tests/mood/queries.test.js
1 +
import test from "ava";
2 +
import request from "supertest";
3 +
import { createApp } from "../../src/create-app.js";
4 +
import { setupTestDb, cleanupTestDb } from "../test-helper.js";
5 +
6 +
let app;
7 +
let db;
8 +
let userId;
9 +
let moodIds = [];
10 +
11 +
test.before(() => {
12 +
db = setupTestDb();
13 +
app = createApp(db);
14 +
});
15 +
16 +
test.beforeEach(async () => {
17 +
cleanupTestDb();
18 +
moodIds = [];
19 +
20 +
// Create a test user with unique email
21 +
const uniqueEmail = `mood_${Date.now()}_${Math.random()}@example.com`;
22 +
const userRes = await request(app)
23 +
.post("/users")
24 +
.send({
25 +
email: uniqueEmail,
26 +
password: "senha123",
27 +
first_name: "Mood",
28 +
last_name: "Tester"
29 +
});
30 +
31 +
if (userRes.status !== 201) {
32 +
throw new Error(`Failed to create test user: ${JSON.stringify(userRes.body)}`);
33 +
}
34 +
35 +
userId = userRes.body.id;
36 +
37 +
// Create multiple moods for testing
38 +
const dates = [
39 +
new Date('2024-01-15T10:00:00Z'),
40 +
new Date('2024-01-20T14:30:00Z'),
41 +
new Date('2024-02-01T08:00:00Z'),
42 +
new Date('2024-02-15T16:45:00Z'),
43 +
new Date('2024-03-01T12:00:00Z')
44 +
];
45 +
46 +
for (let i = 0; i < dates.length; i++) {
47 +
const moodRes = await request(app)
48 +
.post("/mood")
49 +
.send({
50 +
user_id: userId,
51 +
rating: 5 + i,
52 +
stress_level: 3 + i,
53 +
anxiety_level: 2 + i,
54 +
energy_level: 6 - i,
55 +
title: `Mood ${i + 1}`,
56 +
recorded_at: dates[i].toISOString(),
57 +
mood_components: i % 2 === 0 ? [
58 +
{ emotion: "joy", intensity: 5 + i },
59 +
{ emotion: "trust", intensity: 4 + i }
60 +
] : []
61 +
});
62 +
moodIds.push(moodRes.body.id);
63 +
}
64 +
});
65 +
66 +
test.serial("Obter mood específico por ID", async (t) => {
67 +
const res = await request(app).get(`/mood/${moodIds[0]}`);
68 +
69 +
t.is(res.status, 200);
70 +
t.is(Number(res.body.id), Number(moodIds[0]));
71 +
t.is(Number(res.body.user_id), Number(userId));
72 +
t.is(res.body.title, "Mood 1");
73 +
t.truthy(res.body.first_name);
74 +
t.truthy(res.body.last_name);
75 +
t.true(Array.isArray(res.body.mood_components));
76 +
});
77 +
78 +
test.serial("Mood deve incluir componentes emocionais", async (t) => {
79 +
const res = await request(app).get(`/mood/${moodIds[0]}`);
80 +
81 +
t.is(res.status, 200);
82 +
t.is(res.body.mood_components.length, 2);
83 +
84 +
const emotions = res.body.mood_components.map(c => c.emotion);
85 +
t.true(emotions.includes('joy'));
86 +
t.true(emotions.includes('trust'));
87 +
});
88 +
89 +
test.serial("Mood deve incluir estatísticas emocionais quando houver componentes", async (t) => {
90 +
const res = await request(app).get(`/mood/${moodIds[0]}`);
91 +
92 +
t.is(res.status, 200);
93 +
t.truthy(res.body.emotion_stats);
94 +
t.truthy(res.body.emotion_stats.dominant);
95 +
t.truthy(res.body.emotion_stats.average);
96 +
t.truthy(res.body.emotion_stats.breakdown);
97 +
});
98 +
99 +
test.serial("Falha ao obter mood inexistente", async (t) => {
100 +
const res = await request(app).get("/mood/99999");
101 +
102 +
t.is(res.status, 404);
103 +
t.is(res.body.error, "Humor não encontrado");
104 +
});
105 +
106 +
test.serial("Obter todos os moods de um usuário", async (t) => {
107 +
const res = await request(app).get(`/mood/user/${userId}`);
108 +
109 +
t.is(res.status, 200);
110 +
t.truthy(res.body.moods);
111 +
t.is(res.body.moods.length, 5);
112 +
t.truthy(res.body.pagination);
113 +
t.is(res.body.pagination.total, 5);
114 +
});
115 +
116 +
test.serial("Moods devem estar ordenados por data (mais recente primeiro)", async (t) => {
117 +
const res = await request(app).get(`/mood/user/${userId}`);
118 +
119 +
t.is(res.status, 200);
120 +
121 +
const dates = res.body.moods.map(m => new Date(m.recorded_at));
122 +
for (let i = 1; i < dates.length; i++) {
123 +
t.true(dates[i - 1] >= dates[i]);
124 +
}
125 +
});
126 +
127 +
test.serial("Filtrar registros de humor por intervalo de datas", async (t) => {
128 +
const res = await request(app).get(`/mood/user/${userId}`)
129 +
.query({
130 +
start_date: '2024-01-01',
131 +
end_date: '2024-01-31'
132 +
});
133 +
134 +
t.is(res.status, 200);
135 +
t.is(res.body.moods.length, 2); // Only January moods
136 +
});
137 +
138 +
test.serial("Filtrar registros de humor apenas por data de início", async (t) => {
139 +
const res = await request(app).get(`/mood/user/${userId}`)
140 +
.query({
141 +
start_date: '2024-02-01'
142 +
});
143 +
144 +
t.is(res.status, 200);
145 +
t.is(res.body.moods.length, 3); // Feb and March moods
146 +
});
147 +
148 +
test.serial("Filtrar registros de humor apenas por data de fim", async (t) => {
149 +
const res = await request(app).get(`/mood/user/${userId}`)
150 +
.query({
151 +
end_date: '2024-01-31'
152 +
});
153 +
154 +
t.is(res.status, 200);
155 +
t.is(res.body.moods.length, 2); // Only January moods
156 +
});
157 +
158 +
test.serial("Paginação de moods", async (t) => {
159 +
const res = await request(app).get(`/mood/user/${userId}`)
160 +
.query({
161 +
page: 2,
162 +
limit: 2
163 +
});
164 +
165 +
t.is(res.status, 200);
166 +
t.is(res.body.moods.length, 2);
167 +
t.is(res.body.pagination.page, 2);
168 +
t.is(res.body.pagination.limit, 2);
169 +
t.is(res.body.pagination.total_pages, 3);
170 +
});
171 +
172 +
test.serial("Paginação com limite máximo de 100", async (t) => {
173 +
const res = await request(app).get(`/mood/user/${userId}`)
174 +
.query({
175 +
limit: 200
176 +
});
177 +
178 +
t.is(res.status, 200);
179 +
t.is(res.body.pagination.limit, 100); // Should be capped at 100
180 +
});
181 +
182 +
test.serial("Falha ao filtrar com intervalo de datas inválido", async (t) => {
183 +
const res = await request(app).get(`/mood/user/${userId}`)
184 +
.query({
185 +
start_date: '2024-02-01',
186 +
end_date: '2024-01-01' // End before start
187 +
});
188 +
189 +
t.is(res.status, 400);
190 +
t.is(res.body.error, "Intervalo de datas inválido");
191 +
});
192 +
193 +
test.serial("Falha ao filtrar com data de início inválida", async (t) => {
194 +
const res = await request(app).get(`/mood/user/${userId}`)
195 +
.query({
196 +
start_date: 'data-invalida'
197 +
});
198 +
199 +
t.is(res.status, 400);
200 +
t.is(res.body.error, "Data de início inválida");
201 +
});
202 +
203 +
test.serial("Falha ao filtrar com data de fim inválida", async (t) => {
204 +
const res = await request(app).get(`/mood/user/${userId}`)
205 +
.query({
206 +
end_date: 'data-invalida'
207 +
});
208 +
209 +
t.is(res.status, 400);
210 +
t.is(res.body.error, "Data de fim inválida");
211 +
});
212 +
213 +
test.serial("Obter estatísticas de mood do usuário", async (t) => {
214 +
const res = await request(app).get(`/mood/user/${userId}/stats`);
215 +
216 +
t.is(res.status, 200);
217 +
t.truthy(res.body.overall);
218 +
t.is(res.body.overall.total_entries, 5);
219 +
t.truthy(res.body.overall.avg_rating);
220 +
t.truthy(res.body.overall.avg_stress);
221 +
t.truthy(res.body.overall.avg_anxiety);
222 +
t.truthy(res.body.overall.avg_energy);
223 +
t.truthy(res.body.emotions);
224 +
t.truthy(res.body.trends);
225 +
});
226 +
227 +
test.serial("Estatísticas devem incluir frequência de emoções", async (t) => {
228 +
const res = await request(app).get(`/mood/user/${userId}/stats`);
229 +
230 +
t.is(res.status, 200);
231 +
t.true(Array.isArray(res.body.emotions));
232 +
233 +
// We created 3 moods with components
234 +
const joyStats = res.body.emotions.find(e => e.emotion === 'joy');
235 +
t.truthy(joyStats);
236 +
t.is(joyStats.frequency, 3);
237 +
});
238 +
239 +
test.serial("Estatísticas com filtro de data", async (t) => {
240 +
const res = await request(app).get(`/mood/user/${userId}/stats`)
241 +
.query({
242 +
start_date: '2024-01-01',
243 +
end_date: '2024-01-31'
244 +
});
245 +
246 +
t.is(res.status, 200);
247 +
t.is(res.body.overall.total_entries, 2); // Only January
248 +
t.is(res.body.date_range.start, '2024-01-01');
249 +
t.is(res.body.date_range.end, '2024-01-31');
250 +
});
251 +
252 +
test.serial("Estatísticas devem incluir tendências por dia da semana", async (t) => {
253 +
const res = await request(app).get(`/mood/user/${userId}/stats`);
254 +
255 +
t.is(res.status, 200);
256 +
t.true(Array.isArray(res.body.trends.by_day_of_week));
257 +
258 +
const hasDay = res.body.trends.by_day_of_week.some(d =>
259 +
['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].includes(d.day_of_week)
260 +
);
261 +
t.true(hasDay);
262 +
});
263 +
264 +
test.serial("Usuário sem moods deve retornar lista vazia", async (t) => {
265 +
// Create another user without moods
266 +
const newUserRes = await request(app)
267 +
.post("/users")
268 +
.send({
269 +
email: "nomood@example.com",
270 +
password: "senha123",
271 +
first_name: "No",
272 +
last_name: "Mood"
273 +
});
274 +
275 +
const res = await request(app).get(`/mood/user/${newUserRes.body.id}`);
276 +
277 +
t.is(res.status, 200);
278 +
t.is(res.body.moods.length, 0);
279 +
t.is(res.body.pagination.total, 0);
280 +
});
281 +
282 +
test.serial("Estatísticas de usuário sem moods", async (t) => {
283 +
// Create another user without moods
284 +
const newUserRes = await request(app)
285 +
.post("/users")
286 +
.send({
287 +
email: "nostats@example.com",
288 +
password: "senha123",
289 +
first_name: "No",
290 +
last_name: "Stats"
291 +
});
292 +
293 +
const res = await request(app).get(`/mood/user/${newUserRes.body.id}/stats`);
294 +
295 +
t.is(res.status, 200);
296 +
t.is(res.body.overall.total_entries, 0);
297 +
t.is(res.body.emotions.length, 0);
298 +
});
/dev/null → api/tests/mood/updates.test.js
1 +
import test from "ava";
2 +
import request from "supertest";
3 +
import { createApp } from "../../src/create-app.js";
4 +
import { setupTestDb, cleanupTestDb } from "../test-helper.js";
5 +
6 +
let app;
7 +
let db;
8 +
let userId;
9 +
let moodId;
10 +
11 +
test.before(() => {
12 +
db = setupTestDb();
13 +
app = createApp(db);
14 +
});
15 +
16 +
test.beforeEach(async () => {
17 +
cleanupTestDb();
18 +
19 +
// Create a test user with unique email
20 +
const uniqueEmail = `mood_${Date.now()}_${Math.random()}@example.com`;
21 +
const userRes = await request(app)
22 +
.post("/users")
23 +
.send({
24 +
email: uniqueEmail,
25 +
password: "senha123",
26 +
first_name: "Mood",
27 +
last_name: "Tester"
28 +
});
29 +
30 +
if (userRes.status !== 201) {
31 +
throw new Error(`Failed to create test user: ${JSON.stringify(userRes.body)}`);
32 +
}
33 +
34 +
userId = userRes.body.id;
35 +
36 +
// Create a mood to update
37 +
const moodRes = await request(app)
38 +
.post("/mood")
39 +
.send({
40 +
user_id: userId,
41 +
rating: 5,
42 +
stress_level: 4,
43 +
anxiety_level: 3,
44 +
energy_level: 7,
45 +
title: "Original mood",
46 +
description: "Original description",
47 +
recorded_at: new Date().toISOString(),
48 +
mood_components: [
49 +
{ emotion: "joy", intensity: 6 },
50 +
{ emotion: "trust", intensity: 7 }
51 +
]
52 +
});
53 +
54 +
moodId = moodRes.body.id;
55 +
});
56 +
57 +
test.serial("Atualizar rating do mood", async (t) => {
58 +
const res = await request(app)
59 +
.put(`/mood/${moodId}`)
60 +
.send({
61 +
rating: 8
62 +
});
63 +
64 +
t.is(res.status, 200);
65 +
t.true(res.body.success);
66 +
67 +
// Verify update
68 +
const mood = db.prepare("SELECT rating FROM mood WHERE id = ?").get(moodId);
69 +
t.is(mood.rating, 8);
70 +
});
71 +
72 +
test.serial("Atualizar múltiplos níveis do mood", async (t) => {
73 +
const res = await request(app)
74 +
.put(`/mood/${moodId}`)
75 +
.send({
76 +
stress_level: 2,
77 +
anxiety_level: 1,
78 +
energy_level: 9
79 +
});
80 +
81 +
t.is(res.status, 200);
82 +
t.true(res.body.success);
83 +
84 +
// Verify updates
85 +
const mood = db.prepare("SELECT stress_level, anxiety_level, energy_level FROM mood WHERE id = ?").get(moodId);
86 +
t.is(mood.stress_level, 2);
87 +
t.is(mood.anxiety_level, 1);
88 +
t.is(mood.energy_level, 9);
89 +
});
90 +
91 +
test.serial("Atualizar título e descrição do mood", async (t) => {
92 +
const res = await request(app)
93 +
.put(`/mood/${moodId}`)
94 +
.send({
95 +
title: "Novo título",
96 +
description: "Nova descrição detalhada"
97 +
});
98 +
99 +
t.is(res.status, 200);
100 +
t.true(res.body.success);
101 +
102 +
// Verify updates
103 +
const mood = db.prepare("SELECT title, description FROM mood WHERE id = ?").get(moodId);
104 +
t.is(mood.title, "Novo título");
105 +
t.is(mood.description, "Nova descrição detalhada");
106 +
});
107 +
108 +
test.serial("Atualizar data do mood", async (t) => {
109 +
const newDate = new Date('2024-01-15T10:00:00Z');
110 +
111 +
const res = await request(app)
112 +
.put(`/mood/${moodId}`)
113 +
.send({
114 +
recorded_at: newDate.toISOString()
115 +
});
116 +
117 +
t.is(res.status, 200);
118 +
t.true(res.body.success);
119 +
120 +
// Verify update
121 +
const mood = db.prepare("SELECT recorded_at FROM mood WHERE id = ?").get(moodId);
122 +
t.truthy(mood.recorded_at.includes('2024-01-15'));
123 +
});
124 +
125 +
test.serial("Atualizar componentes emocionais do mood", async (t) => {
126 +
const res = await request(app)
127 +
.put(`/mood/${moodId}`)
128 +
.send({
129 +
mood_components: [
130 +
{ emotion: "sad", intensity: 4 },
131 +
{ emotion: "fear", intensity: 3 },
132 +
{ emotion: "angry", intensity: 2 }
133 +
]
134 +
});
135 +
136 +
t.is(res.status, 200);
137 +
t.true(res.body.success);
138 +
139 +
// Verify old components were replaced
140 +
const components = db.prepare("SELECT emotion, intensity FROM mood_components WHERE mood_id = ? ORDER BY emotion").all(moodId);
141 +
t.is(components.length, 3);
142 +
143 +
const emotions = components.map(c => c.emotion);
144 +
t.true(emotions.includes('sad'));
145 +
t.true(emotions.includes('fear'));
146 +
t.true(emotions.includes('angry'));
147 +
t.false(emotions.includes('joy')); // Old emotion should be gone
148 +
t.false(emotions.includes('trust')); // Old emotion should be gone
149 +
});
150 +
151 +
test.serial("Remover todos os componentes emocionais enviando array vazio", async (t) => {
152 +
const res = await request(app)
153 +
.put(`/mood/${moodId}`)
154 +
.send({
155 +
mood_components: []
156 +
});
157 +
158 +
t.is(res.status, 200);
159 +
t.true(res.body.success);
160 +
161 +
// Verify components were deleted
162 +
const components = db.prepare("SELECT * FROM mood_components WHERE mood_id = ?").all(moodId);
163 +
t.is(components.length, 0);
164 +
});
165 +
166 +
test.serial("Atualização deve modificar a coluna de atualização", async (t) => {
167 +
// Get original updated_at
168 +
const originalMood = db.prepare("SELECT updated_at FROM mood WHERE id = ?")
169 +
.get(moodId);
170 +
171 +
// Wait a bit to ensure timestamp difference
172 +
await new Promise(resolve => setTimeout(resolve, 1100));
173 +
174 +
const res = await request(app)
175 +
.put(`/mood/${moodId}`)
176 +
.send({
177 +
rating: 9
178 +
});
179 +
180 +
t.is(res.status, 200);
181 +
182 +
// Verify updated_at changed
183 +
const updatedMood = db.prepare("SELECT updated_at FROM mood WHERE id = ?")
184 +
.get(moodId);
185 +
186 +
t.not(updatedMood.updated_at, originalMood.updated_at);
187 +
});
188 +
189 +
test.serial("Falha ao atualizar humor inexistente", async (t) => {
190 +
const res = await request(app)
191 +
.put("/mood/99999")
192 +
.send({
193 +
rating: 8
194 +
});
195 +
196 +
t.is(res.status, 404);
197 +
t.is(res.body.error, "Humor não encontrado");
198 +
});
199 +
200 +
test.serial("Falha ao atualizar com avaliação inválida", async (t) => {
201 +
const res = await request(app)
202 +
.put(`/mood/${moodId}`)
203 +
.send({
204 +
rating: 15
205 +
});
206 +
207 +
t.is(res.status, 400);
208 +
t.is(res.body.error, "rating deve ser entre 1 e 10");
209 +
});
210 +
211 +
test.serial("Falha ao atualizar com nível de estresse inválido", async (t) => {
212 +
const res = await request(app)
213 +
.put(`/mood/${moodId}`)
214 +
.send({
215 +
stress_level: 0
216 +
});
217 +
218 +
t.is(res.status, 400);
219 +
t.is(res.body.error, "stress_level deve ser entre 1 e 10");
220 +
});
221 +
222 +
test.serial("Falha ao atualizar com nível de ansiedade inválido", async (t) => {
223 +
const res = await request(app)
224 +
.put(`/mood/${moodId}`)
225 +
.send({
226 +
anxiety_level: 11
227 +
});
228 +
229 +
t.is(res.status, 400);
230 +
t.is(res.body.error, "anxiety_level deve ser entre 1 e 10");
231 +
});
232 +
233 +
test.serial("Falha ao atualizar com nível de energia inválida", async (t) => {
234 +
const res = await request(app)
235 +
.put(`/mood/${moodId}`)
236 +
.send({
237 +
energy_level: -1
238 +
});
239 +
240 +
t.is(res.status, 400);
241 +
t.is(res.body.error, "energy_level deve ser entre 1 e 10");
242 +
});
243 +
244 +
test.serial("Falha ao atualizar com data inválida", async (t) => {
245 +
const res = await request(app)
246 +
.put(`/mood/${moodId}`)
247 +
.send({
248 +
recorded_at: "data-invalida"
249 +
});
250 +
251 +
t.is(res.status, 400);
252 +
t.is(res.body.error, "recorded_at deve ser uma data válida");
253 +
});
254 +
255 +
test.serial("Falha ao atualizar com data no futuro", async (t) => {
256 +
const futureDate = new Date();
257 +
futureDate.setDate(futureDate.getDate() + 1);
258 +
259 +
const res = await request(app)
260 +
.put(`/mood/${moodId}`)
261 +
.send({
262 +
recorded_at: futureDate.toISOString()
263 +
});
264 +
265 +
t.is(res.status, 400);
266 +
t.is(res.body.error, "recorded_at não pode ser no futuro");
267 +
});
268 +
269 +
test.serial("Falha ao atualizar com emoção inválida nos componentes", async (t) => {
270 +
const res = await request(app)
271 +
.put(`/mood/${moodId}`)
272 +
.send({
273 +
mood_components: [
274 +
{ emotion: "invalid", intensity: 5 }
275 +
]
276 +
});
277 +
278 +
t.is(res.status, 400);
279 +
t.regex(res.body.error, /Invalid emotion/);
280 +
});
281 +
282 +
test.serial("Falha ao atualizar com emoções duplicadas", async (t) => {
283 +
const res = await request(app)
284 +
.put(`/mood/${moodId}`)
285 +
.send({
286 +
mood_components: [
287 +
{ emotion: "joy", intensity: 5 },
288 +
{ emotion: "joy", intensity: 7 }
289 +
]
290 +
});
291 +
292 +
t.is(res.status, 400);
293 +
t.regex(res.body.error, /Duplicate emotion/);
294 +
});
295 +
296 +
test.serial("Deletar humor existente", async (t) => {
297 +
const res = await request(app).delete(`/mood/${moodId}`);
298 +
299 +
t.is(res.status, 200);
300 +
t.true(res.body.success);
301 +
t.is(Number(res.body.deleted), Number(moodId));
302 +
303 +
// Verify mood is deleted
304 +
const mood = db.prepare("SELECT * FROM mood WHERE id = ?").get(moodId);
305 +
t.falsy(mood);
306 +
});
307 +
308 +
test.serial("Deletar humor deve deletar seus componentes (em cascata)", async (t) => {
309 +
// Verify components exist
310 +
const componentsBefore = db.prepare("SELECT * FROM mood_components WHERE mood_id = ?").all(moodId);
311 +
t.true(componentsBefore.length > 0);
312 +
313 +
const res = await request(app).delete(`/mood/${moodId}`);
314 +
t.is(res.status, 200);
315 +
316 +
// Verify components are deleted
317 +
const componentsAfter = db.prepare("SELECT * FROM mood_components WHERE mood_id = ?").all(moodId);
318 +
t.is(componentsAfter.length, 0);
319 +
});
320 +
321 +
test.serial("Falha ao deletar mood inexistente", async (t) => {
322 +
const res = await request(app).delete("/mood/99999");
323 +
324 +
t.is(res.status, 404);
325 +
t.is(res.body.error, "Humor não encontrado");
326 +
});
327 +
328 +
test.serial("Atualização parcial mantém valores não alterados", async (t) => {
329 +
// Get original values
330 +
const originalMood = db.prepare("SELECT * FROM mood WHERE id = ?").get(moodId);
331 +
332 +
// Update only rating
333 +
const res = await request(app)
334 +
.put(`/mood/${moodId}`)
335 +
.send({
336 +
rating: 10
337 +
});
338 +
339 +
t.is(res.status, 200);
340 +
341 +
// Verify only rating changed, others remained
342 +
const updatedMood = db.prepare("SELECT * FROM mood WHERE id = ?").get(moodId);
343 +
t.is(updatedMood.rating, 10);
344 +
t.is(updatedMood.stress_level, originalMood.stress_level);
345 +
t.is(updatedMood.anxiety_level, originalMood.anxiety_level);
346 +
t.is(updatedMood.energy_level, originalMood.energy_level);
347 +
t.is(updatedMood.title, originalMood.title);
348 +
t.is(updatedMood.description, originalMood.description);
349 +
});
api/tests/test-helper.js
1 1
import { setupTestDatabase } from '../src/setup-db.js';
2 2
3 -
// Store the test database globally
4 -
let testDb;
3 +
// Store a single test database instance
4 +
let globalTestDb = null;
5 5
6 -
// Replace the default db connection with test database
6 +
// Setup the test database
7 7
export function setupTestDb() {
8 -
testDb = setupTestDatabase();
9 -
return testDb;
8 +
if (!globalTestDb) {
9 +
globalTestDb = setupTestDatabase();
10 +
}
11 +
return globalTestDb;
10 12
}
11 13
12 14
// Get the test database
13 15
export function getTestDb() {
14 -
return testDb;
16 +
return globalTestDb;
15 17
}
16 18
19 +
// Cleanup test database
17 20
export function cleanupTestDb() {
18 -
// Clear all tables
19 -
if (testDb) {
20 -
testDb.exec(`
21 -
DELETE FROM mood;
22 -
DELETE FROM users;
23 -
`);
21 +
if (globalTestDb) {
22 +
try {
23 +
// Delete in correct order due to foreign keys
24 +
globalTestDb.exec(`
25 +
DELETE FROM mood_components;
26 +
DELETE FROM mood;
27 +
DELETE FROM users;
28 +
`);
29 +
} catch (err) {
30 +
console.error('Error cleaning test DB:', err);
31 +
}
24 32
}
25 -
} 33 +
}
/dev/null → api/tests/users/authentication.test.js
1 +
import test from "ava";
2 +
import request from "supertest";
3 +
import bcrypt from "bcryptjs";
4 +
import { createApp } from "../../src/create-app.js";
5 +
import { setupTestDb, cleanupTestDb } from "../test-helper.js";
6 +
7 +
let app;
8 +
let db;
9 +
10 +
test.before(() => {
11 +
db = setupTestDb();
12 +
app = createApp(db);
13 +
});
14 +
15 +
test.beforeEach(() => {
16 +
cleanupTestDb();
17 +
18 +
// Create a test user for login tests
19 +
const hashedPassword = bcrypt.hashSync("senha123", 10);
20 +
db.prepare(`
21 +
INSERT INTO users (email, password, first_name, last_name, timezone)
22 +
VALUES (?, ?, ?, ?, ?)
23 +
`).run("teste@example.com", hashedPassword, "Teste", "User", "UTC");
24 +
});
25 +
26 +
test("Login bem-sucedido com credenciais corretas", async (t) => {
27 +
const res = await request(app)
28 +
.post("/users/login")
29 +
.send({
30 +
email: "teste@example.com",
31 +
password: "senha123"
32 +
});
33 +
34 +
t.is(res.status, 200);
35 +
t.true(res.body.success);
36 +
t.truthy(res.body.user);
37 +
t.is(res.body.user.email, "teste@example.com");
38 +
t.is(res.body.user.first_name, "Teste");
39 +
t.is(res.body.user.last_name, "User");
40 +
t.falsy(res.body.user.password); // Password should not be in response
41 +
});
42 +
43 +
test("Login com email em maiúsculas deve funcionar", async (t) => {
44 +
const res = await request(app)
45 +
.post("/users/login")
46 +
.send({
47 +
email: "TESTE@EXAMPLE.COM",
48 +
password: "senha123"
49 +
});
50 +
51 +
t.is(res.status, 200);
52 +
t.true(res.body.success);
53 +
});
54 +
55 +
test("Falha no login com senha incorreta", async (t) => {
56 +
const res = await request(app)
57 +
.post("/users/login")
58 +
.send({
59 +
email: "teste@example.com",
60 +
password: "senhaerrada"
61 +
});
62 +
63 +
t.is(res.status, 401);
64 +
t.is(res.body.error, "Credenciais inválidas");
65 +
});
66 +
67 +
test("Falha no login com email não cadastrado", async (t) => {
68 +
const res = await request(app)
69 +
.post("/users/login")
70 +
.send({
71 +
email: "naocadastrado@example.com",
72 +
password: "senha123"
73 +
});
74 +
75 +
t.is(res.status, 401);
76 +
t.is(res.body.error, "Credenciais inválidas");
77 +
});
78 +
79 +
test("Falha no login sem email", async (t) => {
80 +
const res = await request(app)
81 +
.post("/users/login")
82 +
.send({
83 +
password: "senha123"
84 +
});
85 +
86 +
t.is(res.status, 400);
87 +
t.is(res.body.error, "Email e senha são obrigatórios");
88 +
});
89 +
90 +
test("Falha no login sem senha", async (t) => {
91 +
const res = await request(app)
92 +
.post("/users/login")
93 +
.send({
94 +
email: "teste@example.com"
95 +
});
96 +
97 +
t.is(res.status, 400);
98 +
t.is(res.body.error, "Email e senha são obrigatórios");
99 +
});
100 +
101 +
test("Falha no login sem credenciais", async (t) => {
102 +
const res = await request(app)
103 +
.post("/users/login")
104 +
.send({});
105 +
106 +
t.is(res.status, 400);
107 +
t.is(res.body.error, "Email e senha são obrigatórios");
108 +
});
109 +
110 +
test("Login não deve retornar a senha do usuário", async (t) => {
111 +
const res = await request(app)
112 +
.post("/users/login")
113 +
.send({
114 +
email: "teste@example.com",
115 +
password: "senha123"
116 +
});
117 +
118 +
t.is(res.status, 200);
119 +
t.falsy(res.body.user.password);
120 +
t.is(typeof res.body.user.password, 'undefined');
121 +
});
122 +
123 +
test("Login deve retornar informações completas do usuário", async (t) => {
124 +
const res = await request(app)
125 +
.post("/users/login")
126 +
.send({
127 +
email: "teste@example.com",
128 +
password: "senha123"
129 +
});
130 +
131 +
t.is(res.status, 200);
132 +
t.truthy(res.body.user.id);
133 +
t.is(res.body.user.email, "teste@example.com");
134 +
t.is(res.body.user.first_name, "Teste");
135 +
t.is(res.body.user.last_name, "User");
136 +
t.is(res.body.user.timezone, "UTC");
137 +
});
138 +
139 +
test("Login com espaços em branco deve ser tratado corretamente", async (t) => {
140 +
const res = await request(app)
141 +
.post("/users/login")
142 +
.send({
143 +
email: " teste@example.com ",
144 +
password: "senha123"
145 +
});
146 +
147 +
t.is(res.status, 401); // Should fail as email with spaces doesn't match
148 +
});/dev/null → api/tests/users/creation.test.js
1 +
import test from "ava";
2 +
import request from "supertest";
3 +
import { createApp } from "../../src/create-app.js";
4 +
import { setupTestDb, cleanupTestDb } from "../test-helper.js";
5 +
6 +
let app;
7 +
let db;
8 +
9 +
test.before(() => {
10 +
db = setupTestDb();
11 +
app = createApp(db);
12 +
});
13 +
14 +
test.beforeEach(() => {
15 +
cleanupTestDb();
16 +
});
17 +
18 +
test("Criar usuário com todos os campos válidos", async (t) => {
19 +
const res = await request(app)
20 +
.post("/users")
21 +
.send({
22 +
email: "usuario@example.com",
23 +
password: "senha123",
24 +
first_name: "João",
25 +
last_name: "Silva",
26 +
timezone: "America/Sao_Paulo"
27 +
});
28 +
29 +
t.is(res.status, 201);
30 +
t.truthy(res.body.id);
31 +
});
32 +
33 +
test("Criar usuário com timezone padrão (UTC)", async (t) => {
34 +
const res = await request(app)
35 +
.post("/users")
36 +
.send({
37 +
email: "utc@example.com",
38 +
password: "senha123",
39 +
first_name: "Maria",
40 +
last_name: "Santos"
41 +
});
42 +
43 +
t.is(res.status, 201);
44 +
45 +
// Verify timezone was set to UTC
46 +
const user = db.prepare("SELECT timezone FROM users WHERE id = ?").get(res.body.id);
47 +
t.is(user.timezone, 'UTC');
48 +
});
49 +
50 +
test("Falha ao criar usuário sem email", async (t) => {
51 +
const res = await request(app)
52 +
.post("/users")
53 +
.send({
54 +
password: "senha123",
55 +
first_name: "João",
56 +
last_name: "Silva"
57 +
});
58 +
59 +
t.is(res.status, 400);
60 +
t.is(res.body.error, "Campos faltantes");
61 +
t.true(res.body.missing.includes('email'));
62 +
});
63 +
64 +
test("Falha ao criar usuário sem senha", async (t) => {
65 +
const res = await request(app)
66 +
.post("/users")
67 +
.send({
68 +
email: "usuario@example.com",
69 +
first_name: "João",
70 +
last_name: "Silva"
71 +
});
72 +
73 +
t.is(res.status, 400);
74 +
t.is(res.body.error, "Campos faltantes");
75 +
t.true(res.body.missing.includes('password'));
76 +
});
77 +
78 +
test("Falha ao criar usuário sem primeiro nome", async (t) => {
79 +
const res = await request(app)
80 +
.post("/users")
81 +
.send({
82 +
email: "usuario@example.com",
83 +
password: "senha123",
84 +
last_name: "Silva"
85 +
});
86 +
87 +
t.is(res.status, 400);
88 +
t.is(res.body.error, "Campos faltantes");
89 +
t.true(res.body.missing.includes('first_name'));
90 +
});
91 +
92 +
test("Falha ao criar usuário sem último nome", async (t) => {
93 +
const res = await request(app)
94 +
.post("/users")
95 +
.send({
96 +
email: "usuario@example.com",
97 +
password: "senha123",
98 +
first_name: "João"
99 +
});
100 +
101 +
t.is(res.status, 400);
102 +
t.is(res.body.error, "Campos faltantes");
103 +
t.true(res.body.missing.includes('last_name'));
104 +
});
105 +
106 +
test("Falha ao criar usuário com múltiplos campos faltantes", async (t) => {
107 +
const res = await request(app)
108 +
.post("/users")
109 +
.send({
110 +
first_name: "João"
111 +
});
112 +
113 +
t.is(res.status, 400);
114 +
t.is(res.body.error, "Campos faltantes");
115 +
t.true(res.body.missing.includes('email'));
116 +
t.true(res.body.missing.includes('password'));
117 +
t.true(res.body.missing.includes('last_name'));
118 +
});
119 +
120 +
test("Falha ao criar usuário com email inválido (sem @)", async (t) => {
121 +
const res = await request(app)
122 +
.post("/users")
123 +
.send({
124 +
email: "emailinvalido",
125 +
password: "senha123",
126 +
first_name: "João",
127 +
last_name: "Silva"
128 +
});
129 +
130 +
t.is(res.status, 400);
131 +
t.is(res.body.error, "Email inválido");
132 +
});
133 +
134 +
test("Falha ao criar usuário com email inválido (sem domínio)", async (t) => {
135 +
const res = await request(app)
136 +
.post("/users")
137 +
.send({
138 +
email: "usuario@",
139 +
password: "senha123",
140 +
first_name: "João",
141 +
last_name: "Silva"
142 +
});
143 +
144 +
t.is(res.status, 400);
145 +
t.is(res.body.error, "Email inválido");
146 +
});
147 +
148 +
test("Falha ao criar usuário com email inválido (formato incorreto)", async (t) => {
149 +
const res = await request(app)
150 +
.post("/users")
151 +
.send({
152 +
email: "@example.com",
153 +
password: "senha123",
154 +
first_name: "João",
155 +
last_name: "Silva"
156 +
});
157 +
158 +
t.is(res.status, 400);
159 +
t.is(res.body.error, "Email inválido");
160 +
});
161 +
162 +
test("Falha ao criar usuário com senha muito curta (menos de 6 caracteres)", async (t) => {
163 +
const res = await request(app)
164 +
.post("/users")
165 +
.send({
166 +
email: "usuario@example.com",
167 +
password: "123",
168 +
first_name: "João",
169 +
last_name: "Silva"
170 +
});
171 +
172 +
t.is(res.status, 400);
173 +
t.is(res.body.error, "Senha deve ter pelo menos 6 caracteres");
174 +
});
175 +
176 +
test("Falha ao criar usuário com senha vazia", async (t) => {
177 +
const res = await request(app)
178 +
.post("/users")
179 +
.send({
180 +
email: "usuario@example.com",
181 +
password: "",
182 +
first_name: "João",
183 +
last_name: "Silva"
184 +
});
185 +
186 +
t.is(res.status, 400);
187 +
t.is(res.body.error, "Campos faltantes");
188 +
});
189 +
190 +
test("Falha ao criar usuário com email duplicado", async (t) => {
191 +
// Create first user
192 +
await request(app)
193 +
.post("/users")
194 +
.send({
195 +
email: "duplicado@example.com",
196 +
password: "senha123",
197 +
first_name: "João",
198 +
last_name: "Silva"
199 +
});
200 +
201 +
// Try to create with same email
202 +
const res = await request(app)
203 +
.post("/users")
204 +
.send({
205 +
email: "duplicado@example.com",
206 +
password: "outrasenha",
207 +
first_name: "Maria",
208 +
last_name: "Santos"
209 +
});
210 +
211 +
t.is(res.status, 400);
212 +
t.is(res.body.error, "Email já cadastrado");
213 +
});
214 +
215 +
test("Email deve ser case-insensitive para duplicação", async (t) => {
216 +
// Create with lowercase
217 +
await request(app)
218 +
.post("/users")
219 +
.send({
220 +
email: "usuario@example.com",
221 +
password: "senha123",
222 +
first_name: "João",
223 +
last_name: "Silva"
224 +
});
225 +
226 +
// Try with uppercase
227 +
const res = await request(app)
228 +
.post("/users")
229 +
.send({
230 +
email: "USUARIO@EXAMPLE.COM",
231 +
password: "senha123",
232 +
first_name: "Maria",
233 +
last_name: "Santos"
234 +
});
235 +
236 +
t.is(res.status, 400);
237 +
t.is(res.body.error, "Email já cadastrado");
238 +
});
239 +
240 +
test("Sanitização de strings longas no nome", async (t) => {
241 +
const longName = "A".repeat(600); // 600 characters
242 +
243 +
const res = await request(app)
244 +
.post("/users")
245 +
.send({
246 +
email: "longname@example.com",
247 +
password: "senha123",
248 +
first_name: longName,
249 +
last_name: longName
250 +
});
251 +
252 +
t.is(res.status, 201);
253 +
254 +
// Check that name was truncated to 500 chars
255 +
const user = db.prepare("SELECT first_name, last_name FROM users WHERE id = ?").get(res.body.id);
256 +
t.is(user.first_name.length, 500);
257 +
t.is(user.last_name.length, 500);
258 +
});/dev/null → api/tests/users/profile.test.js
1 +
import test from "ava";
2 +
import request from "supertest";
3 +
import { createApp } from "../../src/create-app.js";
4 +
import { setupTestDb, cleanupTestDb } from "../test-helper.js";
5 +
6 +
let app;
7 +
let db;
8 +
let userId;
9 +
10 +
test.before(() => {
11 +
db = setupTestDb();
12 +
app = createApp(db);
13 +
});
14 +
15 +
let testEmail;
16 +
17 +
test.serial.beforeEach(async () => {
18 +
cleanupTestDb();
19 +
20 +
// Create a test user with unique email
21 +
testEmail = `perfil_${Date.now()}_${Math.random()}@example.com`;
22 +
const res = await request(app)
23 +
.post("/users")
24 +
.send({
25 +
email: testEmail,
26 +
password: "senha123",
27 +
first_name: "Perfil",
28 +
last_name: "Teste",
29 +
timezone: "America/Sao_Paulo"
30 +
});
31 +
32 +
if (res.status !== 201) {
33 +
throw new Error(`Failed to create test user: ${JSON.stringify(res.body)}`);
34 +
}
35 +
36 +
userId = res.body.id;
37 +
});
38 +
39 +
test.serial("Obter perfil de usuário existente", async (t) => {
40 +
const res = await request(app).get(`/users/${userId}`);
41 +
42 +
t.is(res.status, 200);
43 +
t.is(Number(res.body.id), Number(userId));
44 +
t.is(res.body.email, testEmail);
45 +
t.is(res.body.first_name, "Perfil");
46 +
t.is(res.body.last_name, "Teste");
47 +
t.is(res.body.timezone, "America/Sao_Paulo");
48 +
t.truthy(res.body.created_at);
49 +
t.truthy(res.body.updated_at);
50 +
t.is(typeof res.body.total_moods, 'number');
51 +
t.falsy(res.body.password); // Should not include password
52 +
});
53 +
54 +
test.serial("Perfil deve incluir contagem de registros de humor", async (t) => {
55 +
// Add some moods
56 +
const now = new Date().toISOString();
57 +
58 +
await db.prepare(`
59 +
INSERT INTO mood (user_id, rating, stress_level, anxiety_level, energy_level, recorded_at)
60 +
VALUES (?, ?, ?, ?, ?, ?)
61 +
`).run(userId, 7, 3, 4, 8, now);
62 +
63 +
await db.prepare(`
64 +
INSERT INTO mood (user_id, rating, stress_level, anxiety_level, energy_level, recorded_at)
65 +
VALUES (?, ?, ?, ?, ?, ?)
66 +
`).run(userId, 5, 6, 7, 4, now);
67 +
68 +
const res = await request(app).get(`/users/${userId}`);
69 +
70 +
t.is(res.status, 200);
71 +
t.is(res.body.total_moods, 2);
72 +
});
73 +
74 +
test.serial("Falha ao obter perfil de usuário inexistente", async (t) => {
75 +
const res = await request(app).get("/users/99999");
76 +
77 +
t.is(res.status, 404);
78 +
t.is(res.body.error, "Usuário não encontrado");
79 +
});
80 +
81 +
test.serial("Atualizar email do usuário", async (t) => {
82 +
const res = await request(app)
83 +
.put(`/users/${userId}`)
84 +
.send({
85 +
email: "novoemail@example.com"
86 +
});
87 +
88 +
t.is(res.status, 200);
89 +
t.true(res.body.success);
90 +
91 +
// Verify update
92 +
const user = db.prepare("SELECT email FROM users WHERE id = ?").get(userId);
93 +
t.is(user.email, "novoemail@example.com");
94 +
});
95 +
96 +
test.serial("Atualizar senha do usuário", async (t) => {
97 +
const res = await request(app)
98 +
.put(`/users/${userId}`)
99 +
.send({
100 +
password: "novasenha123"
101 +
});
102 +
103 +
t.is(res.status, 200);
104 +
t.true(res.body.success);
105 +
106 +
// Verify can login with new password
107 +
const loginRes = await request(app)
108 +
.post("/users/login")
109 +
.send({
110 +
email: testEmail,
111 +
password: "novasenha123"
112 +
});
113 +
114 +
t.is(loginRes.status, 200);
115 +
});
116 +
117 +
test.serial("Atualizar múltiplos campos do usuário", async (t) => {
118 +
const res = await request(app)
119 +
.put(`/users/${userId}`)
120 +
.send({
121 +
first_name: "NovoNome",
122 +
last_name: "NovoSobrenome",
123 +
timezone: "Europe/London"
124 +
});
125 +
126 +
t.is(res.status, 200);
127 +
t.true(res.body.success);
128 +
129 +
// Verify updates
130 +
const user = db.prepare("SELECT * FROM users WHERE id = ?").get(userId);
131 +
t.is(user.first_name, "NovoNome");
132 +
t.is(user.last_name, "NovoSobrenome");
133 +
t.is(user.timezone, "Europe/London");
134 +
});
135 +
136 +
test.serial("Falha ao atualizar com email inválido", async (t) => {
137 +
const res = await request(app)
138 +
.put(`/users/${userId}`)
139 +
.send({
140 +
email: "emailinvalido"
141 +
});
142 +
143 +
t.is(res.status, 400);
144 +
t.is(res.body.error, "Email inválido");
145 +
});
146 +
147 +
test.serial("Falha ao atualizar com senha muito curta", async (t) => {
148 +
const res = await request(app)
149 +
.put(`/users/${userId}`)
150 +
.send({
151 +
password: "123"
152 +
});
153 +
154 +
t.is(res.status, 400);
155 +
t.is(res.body.error, "Senha deve ter pelo menos 6 caracteres");
156 +
});
157 +
158 +
test.serial("Falha ao atualizar email para um já existente", async (t) => {
159 +
// Create another user
160 +
await request(app)
161 +
.post("/users")
162 +
.send({
163 +
email: "outro@example.com",
164 +
password: "senha123",
165 +
first_name: "Outro",
166 +
last_name: "Usuario"
167 +
});
168 +
169 +
// Try to update to existing email
170 +
const res = await request(app)
171 +
.put(`/users/${userId}`)
172 +
.send({
173 +
email: "outro@example.com"
174 +
});
175 +
176 +
t.is(res.status, 400);
177 +
t.is(res.body.error, "Email já está em uso");
178 +
});
179 +
180 +
test.serial("Falha ao atualizar usuário inexistente", async (t) => {
181 +
const res = await request(app)
182 +
.put("/users/99999")
183 +
.send({
184 +
first_name: "Novo"
185 +
});
186 +
187 +
t.is(res.status, 404);
188 +
t.is(res.body.error, "Usuário não encontrado");
189 +
});
190 +
191 +
test.serial("Falha ao atualizar sem campos", async (t) => {
192 +
const res = await request(app)
193 +
.put(`/users/${userId}`)
194 +
.send({});
195 +
196 +
t.is(res.status, 400);
197 +
t.is(res.body.error, "Nenhum campo para atualizar");
198 +
});
199 +
200 +
test.serial("Deletar usuário existente", async (t) => {
201 +
const res = await request(app).delete(`/users/${userId}`);
202 +
203 +
t.is(res.status, 200);
204 +
t.true(res.body.success);
205 +
t.is(Number(res.body.deleted), Number(userId));
206 +
207 +
// Verify user is deleted
208 +
const user = db.prepare("SELECT * FROM users WHERE id = ?").get(userId);
209 +
t.falsy(user);
210 +
});
211 +
212 +
test.serial("Deletar usuário deve deletar seus registros de humores (em cascata)", async (t) => {
213 +
// Add a mood
214 +
const now = new Date().toISOString();
215 +
const moodResult = db.prepare(`
216 +
INSERT INTO mood (user_id, rating, stress_level, anxiety_level, energy_level, recorded_at)
217 +
VALUES (?, ?, ?, ?, ?, ?)
218 +
`).run(userId, 7, 3, 4, 8, now);
219 +
220 +
const moodId = moodResult.lastInsertRowid;
221 +
222 +
// Add mood components
223 +
db.prepare(`
224 +
INSERT INTO mood_components (mood_id, emotion, intensity)
225 +
VALUES (?, ?, ?)
226 +
`).run(moodId, 'joy', 8);
227 +
228 +
// Delete user
229 +
const res = await request(app).delete(`/users/${userId}`);
230 +
t.is(res.status, 200);
231 +
232 +
// Verify moods are deleted
233 +
const mood = db.prepare("SELECT * FROM mood WHERE user_id = ?").get(userId);
234 +
t.falsy(mood);
235 +
236 +
// Verify mood components are deleted
237 +
const component = db.prepare("SELECT * FROM mood_components WHERE mood_id = ?").get(moodId);
238 +
t.falsy(component);
239 +
});
240 +
241 +
test.serial("Falha ao deletar usuário inexistente", async (t) => {
242 +
const res = await request(app).delete("/users/99999");
243 +
244 +
t.is(res.status, 404);
245 +
t.is(res.body.error, "Usuário não encontrado");
246 +
});