test(ux): integration coverage for search + stats + due session
This commit is contained in:
90
packages/backend/src/tests/ux.integration.test.ts
Normal file
90
packages/backend/src/tests/ux.integration.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import request from 'supertest';
|
||||
import { createApp } from '../app.js';
|
||||
import { makeTestDb, createUserDirect } from './dbHelper.js';
|
||||
import { hashPassword } from '../services/auth/passwords.js';
|
||||
import { setMailerForTests, type Mailer } from '../services/auth/email.js';
|
||||
|
||||
class StubMailer implements Mailer { async send() {} }
|
||||
|
||||
async function login(app: ReturnType<typeof createApp>, email: string, password = 'secretpass') {
|
||||
const r = await request(app).post('/api/auth/login').send({ email, password });
|
||||
if (r.status !== 200) throw new Error(`login failed: ${r.status}`);
|
||||
const cookies = r.headers['set-cookie'] as unknown as string[];
|
||||
const csrf = cookies.find((c) => c.startsWith('flashcard_csrf='))!.split(';')[0]!.split('=')[1]!;
|
||||
return { cookies, csrf };
|
||||
}
|
||||
|
||||
async function makeUser(env: ReturnType<typeof makeTestDb>, email: string, role: 'user'|'sysadmin' = 'user') {
|
||||
return createUserDirect(env.db, {
|
||||
email, role, isActive: true,
|
||||
passwordHash: await hashPassword('secretpass'),
|
||||
emailVerifiedAt: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
}
|
||||
|
||||
let env: ReturnType<typeof makeTestDb>;
|
||||
let app: ReturnType<typeof createApp>;
|
||||
beforeEach(async () => {
|
||||
env = makeTestDb();
|
||||
setMailerForTests(new StubMailer());
|
||||
app = createApp(env.db);
|
||||
});
|
||||
|
||||
describe('UX integration', () => {
|
||||
it('GET /api/search filters by visibility', async () => {
|
||||
await makeUser(env, 'a@example.com');
|
||||
await makeUser(env, 'b@example.com');
|
||||
const aAuth = await login(app, 'a@example.com');
|
||||
await request(app).post('/api/lessons').set('Cookie', aAuth.cookies).set('x-csrf-token', aAuth.csrf)
|
||||
.send({ name: 'Spaans private' });
|
||||
const lShared = (await request(app).post('/api/lessons').set('Cookie', aAuth.cookies).set('x-csrf-token', aAuth.csrf)
|
||||
.send({ name: 'Spaans public' })).body;
|
||||
await request(app).patch(`/api/lessons/${lShared.id}/visibility`).set('Cookie', aAuth.cookies).set('x-csrf-token', aAuth.csrf)
|
||||
.send({ visibility: 'shared' });
|
||||
|
||||
const bAuth = await login(app, 'b@example.com');
|
||||
const r = await request(app).get('/api/search?q=spaans').set('Cookie', bAuth.cookies);
|
||||
expect(r.status).toBe(200);
|
||||
const names = r.body.lessons.map((l: { name: string }) => l.name);
|
||||
expect(names).toContain('Spaans public');
|
||||
expect(names).not.toContain('Spaans private');
|
||||
});
|
||||
|
||||
it('GET /api/stats/lessons-progress returns only roots', async () => {
|
||||
await makeUser(env, 'u@example.com');
|
||||
const uAuth = await login(app, 'u@example.com');
|
||||
const root = (await request(app).post('/api/lessons').set('Cookie', uAuth.cookies).set('x-csrf-token', uAuth.csrf)
|
||||
.send({ name: 'Root' })).body;
|
||||
await request(app).post('/api/lessons').set('Cookie', uAuth.cookies).set('x-csrf-token', uAuth.csrf)
|
||||
.send({ name: 'Child', parentId: root.id });
|
||||
|
||||
const r = await request(app).get('/api/stats/lessons-progress').set('Cookie', uAuth.cookies);
|
||||
expect(r.status).toBe(200);
|
||||
expect(r.body.rows).toHaveLength(1);
|
||||
expect(r.body.rows[0].name).toBe('Root');
|
||||
});
|
||||
|
||||
it('GET /api/stats/due returns counts', async () => {
|
||||
await makeUser(env, 'u@example.com');
|
||||
const uAuth = await login(app, 'u@example.com');
|
||||
const r = await request(app).get('/api/stats/due').set('Cookie', uAuth.cookies);
|
||||
expect(r.status).toBe(200);
|
||||
expect(r.body).toHaveProperty('overdue');
|
||||
expect(r.body).toHaveProperty('today');
|
||||
expect(r.body).toHaveProperty('tomorrow');
|
||||
expect(r.body).toHaveProperty('thisWeek');
|
||||
});
|
||||
|
||||
it('POST /api/sessions/due creates a session', async () => {
|
||||
await makeUser(env, 'u@example.com');
|
||||
const uAuth = await login(app, 'u@example.com');
|
||||
const lesson = (await request(app).post('/api/lessons').set('Cookie', uAuth.cookies).set('x-csrf-token', uAuth.csrf)
|
||||
.send({ name: 'L' })).body;
|
||||
await request(app).post(`/api/lessons/${lesson.id}/cards`).set('Cookie', uAuth.cookies).set('x-csrf-token', uAuth.csrf)
|
||||
.send({ question: 'q', answer: 'a' });
|
||||
const r = await request(app).post('/api/sessions/due').set('Cookie', uAuth.cookies).set('x-csrf-token', uAuth.csrf);
|
||||
expect(r.status).toBe(201);
|
||||
expect(r.body.session.id).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user