'use strict'; /** * Integration tests: /api/practice — прогресс ученика в ИИ-тренажёре (Фаза 0). * Covers: auth-only (401); correct создаёт строку; wrong не растит solved, но * растит attempts и обнуляет серию; серия из MASTERY_STREAK → mastered; * прогресс per-user; валидация входа (400). */ const { describe, it, before } = require('node:test'); const assert = require('node:assert/strict'); const { app, inject, getToken, cleanup } = require('./setup'); // Mount /api/practice on the shared test app (setup.js не монтирует новые роуты). app.use('/api/practice', require('../src/routes/practice')); const { after } = require('node:test'); after(() => cleanup()); const SKILL = 'linear-basic'; describe('/api/practice progress', () => { let token; before(async () => { token = (await getToken('student')).token; }); it('GET /progress requires auth (401)', async () => { const res = await inject('GET', '/api/practice/progress', null, null); assert.equal(res.status, 401, `got ${res.status}`); }); it('POST /attempt requires auth (401)', async () => { const res = await inject('POST', '/api/practice/attempt', { skill: SKILL, correct: true }, null); assert.equal(res.status, 401, `got ${res.status}`); }); it('correct attempt creates a row (solved=1, streak=1)', async () => { const res = await inject('POST', '/api/practice/attempt', { skill: SKILL, correct: true }, token); assert.equal(res.status, 200, `got ${res.status}`); assert.equal(res.body.ok, true); assert.equal(res.body.progress.skill, SKILL); assert.equal(res.body.progress.solved, 1); assert.equal(res.body.progress.attempts, 1); assert.equal(res.body.progress.cur_streak, 1); assert.equal(res.body.progress.best_streak, 1); assert.equal(res.body.progress.mastered, 0); }); it('GET /progress lists the row', async () => { const res = await inject('GET', '/api/practice/progress', null, token); assert.equal(res.status, 200, `got ${res.status}`); assert.ok(Array.isArray(res.body.progress)); const row = res.body.progress.find(r => r.skill === SKILL); assert.ok(row, 'skill row present'); assert.equal(row.solved, 1); }); it('wrong attempt: attempts++, solved unchanged, streak resets to 0', async () => { const res = await inject('POST', '/api/practice/attempt', { skill: SKILL, correct: false }, token); assert.equal(res.status, 200, `got ${res.status}`); assert.equal(res.body.progress.solved, 1, 'solved unchanged'); assert.equal(res.body.progress.attempts, 2, 'attempts incremented'); assert.equal(res.body.progress.cur_streak, 0, 'streak reset'); assert.equal(res.body.progress.best_streak, 1, 'best streak kept'); }); it('streak of 5 correct → mastered=1 (and stays mastered after a miss)', async () => { const sk = 'mastery-skill'; let last; for (let i = 0; i < 5; i++) { last = await inject('POST', '/api/practice/attempt', { skill: sk, correct: true }, token); } assert.equal(last.body.progress.cur_streak, 5); assert.equal(last.body.progress.best_streak, 5); assert.equal(last.body.progress.mastered, 1, 'mastered after 5 in a row'); const miss = await inject('POST', '/api/practice/attempt', { skill: sk, correct: false }, token); assert.equal(miss.body.progress.cur_streak, 0, 'streak reset on miss'); assert.equal(miss.body.progress.mastered, 1, 'mastered is sticky'); }); it('progress is per-user (другой ученик начинает с нуля)', async () => { const other = (await getToken('student')).token; const res = await inject('POST', '/api/practice/attempt', { skill: SKILL, correct: true }, other); assert.equal(res.status, 200, `got ${res.status}`); assert.equal(res.body.progress.attempts, 1, 'fresh user has attempts=1'); assert.equal(res.body.progress.solved, 1); }); it('validation: missing skill → 400', async () => { const res = await inject('POST', '/api/practice/attempt', { correct: true }, token); assert.equal(res.status, 400, `got ${res.status}`); }); it('validation: correct not boolean → 400', async () => { const res = await inject('POST', '/api/practice/attempt', { skill: SKILL, correct: 'yes' }, token); assert.equal(res.status, 400, `got ${res.status}`); }); it('validation: skill too long → 400', async () => { const res = await inject('POST', '/api/practice/attempt', { skill: 'x'.repeat(200), correct: true }, token); assert.equal(res.status, 400, `got ${res.status}`); }); });