eletrotupi / tcc/ commit / 56ae9d8

api: flesh tests for mood and user's crud

Pedro Lucas Porcellis porcellis@eletrotupi.com · 10 months ago 56ae9d8272c35d408c95fb441f727bf93b0507d3 Browse files
Parents ba9ebee
Changed files
8
Additions
+1829
Deletions
-17
api/tests/mood.test.js modified+13 -5
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 modified+21 -12
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 +
});