166 lines
5.7 KiB
JavaScript
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);
|
|
});
|
|
}); |