Skip to content

Commit 161455b

Browse files
test(account): cobertura de /api/user/data (PATCH favs, historial, settings+loc)
1 parent c39bf28 commit 161455b

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

backend/tests/userdata.test.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* userdata.test.js — Pruebas de integración de /api/user/data
3+
* (createUserDataRouter) con un Supabase fake en memoria que simula
4+
* la tabla user_data (favorites/settings/history como JSON strings).
5+
*
6+
* Cubre: PATCH de favoritos (true agrega / false elimina), historial con
7+
* cap y validación, settings con región "loc" y sanitización.
8+
*/
9+
'use strict';
10+
11+
const { test } = require('node:test');
12+
const assert = require('node:assert');
13+
14+
function makeApp() {
15+
const express = require('express');
16+
const { createUserDataRouter } = require('../modules/userdata');
17+
let row = null;
18+
const supabase = {
19+
from() {
20+
return {
21+
select() { return sel; },
22+
upsert(obj) { row = obj; return { error: null }; },
23+
};
24+
},
25+
};
26+
const sel = {
27+
eq() { return this; },
28+
limit() { return this; },
29+
async maybeSingle() { return { data: row, error: null }; },
30+
};
31+
const app = express();
32+
app.use(express.json());
33+
app.use((req, res, next) => { req.authUser = { id: 'u-test', email: 't@x.dev' }; next(); });
34+
app.use('/api/user', createUserDataRouter(supabase));
35+
const state = { getRow: () => row };
36+
return { app, state };
37+
}
38+
39+
function request(app, method, path, body, token) {
40+
const http = require('http');
41+
const server = app.listen(0);
42+
const port = server.address().port;
43+
return new Promise((resolve, reject) => {
44+
const payload = body ? JSON.stringify(body) : null;
45+
const req = http.request({ host: '127.0.0.1', port, path, method, headers: {
46+
'Content-Type': 'application/json',
47+
...(token ? { Authorization: 'Bearer ' + token } : {}),
48+
} }, (res) => {
49+
let data = '';
50+
res.on('data', (d) => { data += d; });
51+
res.on('end', () => { server.close(); resolve({ status: res.statusCode, body: JSON.parse(data) }); });
52+
});
53+
req.on('error', reject);
54+
if (payload) req.write(payload);
55+
req.end();
56+
});
57+
}
58+
59+
test('PATCH de favoritos: true agrega, false elimina, ausentes no cambian', async () => {
60+
const { app, state } = makeApp();
61+
let r = await request(app, 'PUT', '/api/user/data', { favorites: { pixa: true, pixb: true } });
62+
assert.equal(r.status, 200);
63+
assert.deepEqual(r.body.data.favorites, { pixa: true, pixb: true });
64+
assert.deepEqual(JSON.parse(state.getRow().favorites), { pixa: true, pixb: true });
65+
66+
r = await request(app, 'PUT', '/api/user/data', { favorites: { pixa: false } });
67+
assert.equal(r.status, 200);
68+
// pixa se elimina; pixb se preserva (no fue mencionado)
69+
assert.deepEqual(JSON.parse(state.getRow().favorites), { pixb: true });
70+
71+
r = await request(app, 'GET', '/api/user/data', null);
72+
assert.equal(r.status, 200);
73+
assert.deepEqual(r.body.data.favorites, { pixb: true });
74+
});
75+
76+
test('historial: reemplaza campo, valida shape y cap de 40', async () => {
77+
const { app } = makeApp();
78+
// input válido con 45 ítems (40 válidos únicos + duplicados fuera de cap)
79+
const h = [];
80+
for (let i = 0; i < 46; i++) h.push({ id: 'tool' + (i % 45), ts: i });
81+
let r = await request(app, 'PUT', '/api/user/data', { history: h });
82+
assert.equal(r.status, 200);
83+
assert.equal(r.body.data.history.length, 40);
84+
85+
r = await request(app, 'PUT', '/api/user/data', { history: 'x' });
86+
assert.equal(r.status, 400);
87+
88+
r = await request(app, 'PUT', '/api/user/data', { history: [{ id: 'a/b', ts: 1 }] });
89+
assert.equal(r.status, 200);
90+
assert.equal(r.body.data.history.length, 0);
91+
});
92+
93+
test('settings: primitivos + region loc; objetos anidados se descartan', async () => {
94+
const { app, state } = makeApp();
95+
const r = await request(app, 'PUT', '/api/user/data', {
96+
settings: {
97+
theme: 'dark',
98+
font: 'md',
99+
lang: 'es',
100+
loc: { lat: 14.6, lon: -90.5, city: 'Ciudad de Guatemala', country: 'Guatemala', src: 'search' },
101+
nested: { x: 1 },
102+
arr: [1, 2],
103+
},
104+
});
105+
assert.equal(r.status, 200);
106+
const saved = JSON.parse(state.getRow().settings);
107+
assert.equal(saved.theme, 'dark');
108+
assert.deepEqual(saved.loc, { lat: 14.6, lon: -90.5, city: 'Ciudad de Guatemala', country: 'Guatemala', src: 'search' });
109+
assert.equal(saved.nested, undefined);
110+
});
111+
112+
test('sin payload → 400; tipos inválidos → 400', async () => {
113+
const { app } = makeApp();
114+
let r = await request(app, 'PUT', '/api/user/data', {});
115+
assert.equal(r.status, 400);
116+
r = await request(app, 'PUT', '/api/user/data', { favorites: [1, 2] });
117+
assert.equal(r.status, 400);
118+
r = await request(app, 'PUT', '/api/user/data', { settings: 'nope' });
119+
assert.equal(r.status, 400);
120+
});

0 commit comments

Comments
 (0)