Files
web-cad/backend/tests/users.test.ts
T
Leopoldadmin 1bcfaffdd4 fix: replace hardcoded localhost:3001 with relative API URL + add canvas mock and integration test
- Fix frontend Network error: API_BASE now defaults to empty string (same-origin)
- Fix 3 files: api.ts, AuthContext.tsx, Dashboard.tsx
- Add Canvas 2D context mock in tests/setup.ts for jsdom
- Fix -0 vs +0 in ZoomPanController.getViewport()
- Add IntegrationWorkflow.test.ts (35 tests, full CAD workflow)
2026-06-26 17:48:24 +02:00

188 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Users API Tests List / Get / Update / Delete (password_hash stripping)
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import type { FastifyInstance } from 'fastify';
import { SqliteAdapter } from '../src/database/SqliteAdapter.js';
import { createServer } from '../src/server.js';
describe('Users API', () => {
let app: FastifyInstance;
let db: SqliteAdapter;
let testUserId: string;
beforeAll(async () => {
db = new SqliteAdapter(':memory:');
await db.init();
app = await createServer({ db, port: 0 });
await app.ready();
// Create a test user directly via DB (bypassing AuthService)
const user = db.createUser({
email: 'test-admin@example.com',
password_hash: 'fake-hash-for-testing',
name: 'Test Admin',
role: 'admin',
});
testUserId = user.id;
});
afterAll(async () => {
await app.close();
db.close();
});
// ─── List ───────────────────────────────────────────
describe('GET /api/users', () => {
it('should list all users', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/users',
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBeGreaterThanOrEqual(2); // default user + test user
});
it('should strip password_hash from list responses', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/users',
});
const body = JSON.parse(response.body);
for (const user of body) {
expect(user.password_hash).toBeUndefined();
}
});
});
// ─── Get ────────────────────────────────────────────
describe('GET /api/users/:id', () => {
it('should get a single user', async () => {
const response = await app.inject({
method: 'GET',
url: `/api/users/${testUserId}`,
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.id).toBe(testUserId);
expect(body.email).toBe('test-admin@example.com');
expect(body.name).toBe('Test Admin');
expect(body.role).toBe('admin');
});
it('should strip password_hash from single user response', async () => {
const response = await app.inject({
method: 'GET',
url: `/api/users/${testUserId}`,
});
const body = JSON.parse(response.body);
expect(body.password_hash).toBeUndefined();
});
it('should return 404 for non-existent user', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/users/non-existent-id',
});
expect(response.statusCode).toBe(404);
});
});
// ─── Update ─────────────────────────────────────────
describe('PATCH /api/users/:id', () => {
it('should update user name', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/users/${testUserId}`,
payload: { name: 'Updated Name' },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.name).toBe('Updated Name');
expect(body.id).toBe(testUserId);
});
it('should update user role', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/users/${testUserId}`,
payload: { role: 'planer' },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.role).toBe('planer');
});
it('should strip password_hash from update response', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/users/${testUserId}`,
payload: { name: 'Another Name' },
});
const body = JSON.parse(response.body);
expect(body.password_hash).toBeUndefined();
});
it('should not update password_hash via PATCH endpoint', async () => {
const response = await app.inject({
method: 'PATCH',
url: `/api/users/${testUserId}`,
payload: { password_hash: 'hacked-hash' },
});
expect(response.statusCode).toBe(200);
// Verify the password_hash was NOT changed in DB
const dbUser = db.getUser(testUserId);
expect(dbUser!.password_hash).toBe('fake-hash-for-testing');
});
it('should return 404 for non-existent user update', async () => {
const response = await app.inject({
method: 'PATCH',
url: '/api/users/non-existent-id',
payload: { name: 'New Name' },
});
expect(response.statusCode).toBe(404);
});
});
// ─── Delete ─────────────────────────────────────────
describe('DELETE /api/users/:id', () => {
it('should delete a user', async () => {
// Create a user to delete
const user = db.createUser({
email: 'delete-me@example.com',
password_hash: 'temp-hash',
name: 'Delete Me',
role: 'planer',
});
const response = await app.inject({
method: 'DELETE',
url: `/api/users/${user.id}`,
});
expect(response.statusCode).toBe(204);
// Verify it's gone
const getRes = await app.inject({
method: 'GET',
url: `/api/users/${user.id}`,
});
expect(getRes.statusCode).toBe(404);
});
it('should return 404 for non-existent user delete', async () => {
const response = await app.inject({
method: 'DELETE',
url: '/api/users/non-existent-id',
});
expect(response.statusCode).toBe(404);
});
});
});