'use strict'; const request = require('supertest'); const app = require('../../src/main/app'); const { setDb } = require('../../src/db/knex'); const { createTestDb } = require('../helpers/testDb'); describe('Auth Routes', () => { let db; beforeEach(() => { db = createTestDb(); setDb(db); }); // ── POST /v1/auth/signup ──────────────────────────────────────────────────── describe('POST /v1/auth/signup', () => { it('creates a new user and returns token', async () => { const res = await request(app) .post('/v1/auth/signup') .send({ email: 'jane@example.com', password: 'password1', name: 'Jane Doe' }); expect(res.status).toBe(201); expect(res.body.user.email).toBe('jane@example.com'); expect(res.body.user.name).toBe('Jane Doe'); expect(res.body.token).toBeDefined(); expect(res.body.expires_at).toBeDefined(); // password_hash must never appear in response expect(res.body.user.password_hash).toBeUndefined(); }); it('lowercases email on storage', async () => { const res = await request(app) .post('/v1/auth/signup') .send({ email: 'JANE@EXAMPLE.COM', password: 'password1', name: 'Jane' }); expect(res.status).toBe(201); expect(res.body.user.email).toBe('jane@example.com'); }); it('returns 409 when email already registered', async () => { await request(app) .post('/v1/auth/signup') .send({ email: 'jane@example.com', password: 'password1', name: 'Jane' }); const res = await request(app) .post('/v1/auth/signup') .send({ email: 'jane@example.com', password: 'password1', name: 'Jane Again' }); expect(res.status).toBe(409); expect(res.body.code).toBe('CONFLICT'); }); it('returns 409 for case-insensitive duplicate email', async () => { await request(app) .post('/v1/auth/signup') .send({ email: 'jane@example.com', password: 'password1', name: 'Jane' }); const res = await request(app) .post('/v1/auth/signup') .send({ email: 'JANE@EXAMPLE.COM', password: 'password1', name: 'Jane' }); expect(res.status).toBe(409); }); it('returns 400 for invalid email', async () => { const res = await request(app) .post('/v1/auth/signup') .send({ email: 'not-an-email', password: 'password1', name: 'Jane' }); expect(res.status).toBe(400); expect(res.body.code).toBe('VALIDATION_ERROR'); }); it('returns 400 for password shorter than 8 chars', async () => { const res = await request(app) .post('/v1/auth/signup') .send({ email: 'jane@example.com', password: 'short', name: 'Jane' }); expect(res.status).toBe(400); }); it('returns 400 for non-alphanumeric password', async () => { const res = await request(app) .post('/v1/auth/signup') .send({ email: 'jane@example.com', password: 'p@ssword1', name: 'Jane' }); expect(res.status).toBe(400); }); it('returns 400 when name is missing', async () => { const res = await request(app) .post('/v1/auth/signup') .send({ email: 'jane@example.com', password: 'password1' }); expect(res.status).toBe(400); }); it('returns 400 when body is empty', async () => { const res = await request(app).post('/v1/auth/signup').send({}); expect(res.status).toBe(400); }); }); // ── POST /v1/auth/signin ──────────────────────────────────────────────────── describe('POST /v1/auth/signin', () => { beforeEach(async () => { await request(app) .post('/v1/auth/signup') .send({ email: 'jane@example.com', password: 'password1', name: 'Jane' }); }); it('returns token on valid credentials', async () => { const res = await request(app) .post('/v1/auth/signin') .send({ email: 'jane@example.com', password: 'password1' }); expect(res.status).toBe(200); expect(res.body.token).toBeDefined(); expect(res.body.user.email).toBe('jane@example.com'); }); it('is case-insensitive for email', async () => { const res = await request(app) .post('/v1/auth/signin') .send({ email: 'JANE@EXAMPLE.COM', password: 'password1' }); expect(res.status).toBe(200); }); it('returns 401 for wrong password', async () => { const res = await request(app) .post('/v1/auth/signin') .send({ email: 'jane@example.com', password: 'wrongpass' }); expect(res.status).toBe(401); expect(res.body.code).toBe('UNAUTHORIZED'); }); it('returns 401 for unknown email', async () => { const res = await request(app) .post('/v1/auth/signin') .send({ email: 'nobody@example.com', password: 'password1' }); expect(res.status).toBe(401); }); it('returns 400 when fields are missing', async () => { const res = await request(app) .post('/v1/auth/signin') .send({ email: 'jane@example.com' }); expect(res.status).toBe(400); }); }); // ── POST /v1/auth/password-reset ──────────────────────────────────────────── describe('POST /v1/auth/password-reset', () => { it('always returns 200 regardless of email existence', async () => { const res = await request(app) .post('/v1/auth/password-reset') .send({ email: 'nobody@example.com' }); expect(res.status).toBe(200); expect(res.body.message).toMatch(/reset link/i); }); it('returns 400 for invalid email', async () => { const res = await request(app) .post('/v1/auth/password-reset') .send({ email: 'not-an-email' }); expect(res.status).toBe(400); }); }); // ── DELETE /v1/auth/account ───────────────────────────────────────────────── describe('DELETE /v1/auth/account', () => { it('soft-deletes the authenticated user account', async () => { const signupRes = await request(app) .post('/v1/auth/signup') .send({ email: 'delete@example.com', password: 'password1', name: 'Delete Me' }); const token = signupRes.body.token; const res = await request(app) .delete('/v1/auth/account') .set('Authorization', `Bearer ${token}`); expect(res.status).toBe(204); // Verify account is soft-deleted const users = db.getTable('users'); const user = users.find((u) => u.email === 'delete@example.com'); expect(user.deleted_at).toBeDefined(); expect(user.deletion_scheduled_at).toBeDefined(); }); it('returns 401 without token', async () => { const res = await request(app).delete('/v1/auth/account'); expect(res.status).toBe(401); }); }); // ── POST /v1/auth/restore-account ────────────────────────────────────────── describe('POST /v1/auth/restore-account', () => { it('restores a soft-deleted account', async () => { const signupRes = await request(app) .post('/v1/auth/signup') .send({ email: 'restore@example.com', password: 'password1', name: 'Restore Me' }); const token = signupRes.body.token; await request(app) .delete('/v1/auth/account') .set('Authorization', `Bearer ${token}`); const res = await request(app) .post('/v1/auth/restore-account') .send({ email: 'restore@example.com', password: 'password1' }); expect(res.status).toBe(200); expect(res.body.user.deleted_at).toBeNull(); expect(res.body.message).toMatch(/restored/i); }); it('returns 401 for wrong credentials', async () => { const res = await request(app) .post('/v1/auth/restore-account') .send({ email: 'nobody@example.com', password: 'password1' }); expect(res.status).toBe(401); }); }); });