LMS-BGN/backend/tests/server.test.js

166 lines
5.7 KiB
JavaScript

const request = require('supertest');
const app = require('../src/server');
describe('Health', () => {
it('GET /health returns ok', async () => {
const res = await request(app).get('/health');
expect(res.status).toBe(200);
expect(res.body.status).toBe('ok');
expect(res.body.service).toBe('lms-bgn-backend');
});
});
describe('Epik 1 - Courses', () => {
it('GET /api/courses returns paged list', async () => {
const res = await request(app).get('/api/courses?page=1&pageSize=20');
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('page');
expect(res.body).toHaveProperty('pageSize');
expect(res.body).toHaveProperty('total');
expect(Array.isArray(res.body.items)).toBe(true);
});
it('GET /api/courses/:courseId returns detail', async () => {
const res = await request(app).get('/api/courses/c1');
expect(res.status).toBe(200);
expect(res.body.id).toBe('c1');
expect(res.body).toHaveProperty('modules');
});
it('GET /api/courses/:courseId returns 404 for unknown', async () => {
const res = await request(app).get('/api/courses/unknown');
expect(res.status).toBe(404);
});
it('POST /api/modules/:moduleId/progress validates progressPercent', async () => {
const res = await request(app)
.post('/api/modules/m1/progress')
.send({ progressPercent: 50 });
expect(res.status).toBe(202);
expect(res.body.status).toBe('accepted');
});
});
describe('Epik 1 - Assignments', () => {
it('POST /api/assignments/:assignmentId/submission requires idempotencyKey', async () => {
const res = await request(app)
.post('/api/assignments/a1/submission')
.send({ content: 'Answer' });
expect(res.status).toBe(400);
});
it('POST /api/assignments/:assignmentId/submission creates submission', async () => {
const res = await request(app)
.post('/api/assignments/a1/submission')
.send({ content: 'Answer', idempotencyKey: 'abc' });
expect(res.status).toBe(201);
expect(res.body.status).toBe('created');
});
it('POST /api/assignments/:assignmentId/submission returns 409 on duplicate idempotency', async () => {
await request(app)
.post('/api/assignments/a1/submission')
.send({ content: 'Answer', idempotencyKey: 'dup-key' });
const res = await request(app)
.post('/api/assignments/a1/submission')
.send({ content: 'Answer', idempotencyKey: 'dup-key' });
expect(res.status).toBe(409);
});
});
describe('Epik 2 - Exam Session', () => {
let sessionId;
it('POST /api/exam-session requires fields', async () => {
const res = await request(app)
.post('/api/exam-session')
.set('Authorization', 'Bearer test-token')
.send({});
expect(res.status).toBe(400);
});
it('POST /api/exam-session creates session', async () => {
const res = await request(app)
.post('/api/exam-session')
.set('Authorization', 'Bearer test-token')
.send({ examId: 'exam-001', userId: 'u1' });
expect(res.status).toBe(201);
expect(res.body).toHaveProperty('sessionId');
sessionId = res.body.sessionId;
});
it('POST /api/exam-session/:sessionId/score scores answers', async () => {
const res = await request(app)
.post(`/api/exam-session/${sessionId}/score`)
.set('Authorization', 'Bearer test-token')
.send({
idempotencyKey: 'score-1',
mode: 'batch',
answers: [
{ questionId: 'q1', choiceIndex: 0 },
{ questionId: 'q2', choiceIndex: 2 }
]
});
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('totalScore');
expect(res.body).toHaveProperty('totalQuestions');
expect(res.body).toHaveProperty('correctAnswers');
});
it('POST /api/exam-session/:sessionId/score returns 409 on duplicate idempotency', async () => {
const res = await request(app)
.post(`/api/exam-session/${sessionId}/score`)
.set('Authorization', 'Bearer test-token')
.send({ idempotencyKey: 'score-1', answers: [] });
expect(res.status).toBe(409);
});
it('GET /api/exam-session/:sessionId/summary returns summary', async () => {
const res = await request(app)
.get(`/api/exam-session/${sessionId}/summary`)
.set('Authorization', 'Bearer test-token');
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('examId');
expect(res.body).toHaveProperty('answers');
});
});
describe('Epik 2 - Exams listing', () => {
it('GET /api/exams returns paged list', async () => {
const res = await request(app).get('/api/exams?page=1&pageSize=20');
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('items');
});
it('GET /api/exams filters by q', async () => {
const res = await request(app).get('/api/exams?page=1&pageSize=20&q=HACCP');
expect(res.status).toBe(200);
expect(Array.isArray(res.body.items)).toBe(true);
});
});
describe('Epik 2 - Certificates', () => {
it('POST /api/certificates/:certificateId/resend requires fields', async () => {
let res = await request(app)
.post('/api/certificates/abc/resend')
.send({ idempotencyKey: 'id1' });
expect(res.status).toBe(400);
res = await request(app)
.post('/api/certificates/abc/resend')
.send({ recipientEmail: 'x@y.com' });
expect(res.status).toBe(400);
});
it('POST /api/certificates/:certificateId/resend accepts and throttles', async () => {
const res1 = await request(app)
.post('/api/certificates/abc/resend')
.send({ recipientEmail: 'x@y.com', idempotencyKey: 'cert-1' });
expect(res1.status).toBe(202);
const res2 = await request(app)
.post('/api/certificates/abc/resend')
.send({ recipientEmail: 'x@y.com', idempotencyKey: 'cert-1' });
expect(res2.status).toBe(429);
});
});