forked from wangziqi/gongxue-base
feat: enforce practice access controls
This commit is contained in:
@@ -28,6 +28,8 @@ const ids = {
|
||||
practiceBlueprintRandom: '00000000-0000-0000-0000-000000000617',
|
||||
practiceBlueprintMock: '00000000-0000-0000-0000-000000000618',
|
||||
question: '00000000-0000-0000-0000-000000000401',
|
||||
questionTwo: '00000000-0000-0000-0000-000000000403',
|
||||
questionThree: '00000000-0000-0000-0000-000000000405',
|
||||
vocabularyUnit: '00000000-0000-0000-0000-000000000811',
|
||||
vocabularyWord: '00000000-0000-0000-0000-000000000812',
|
||||
video: '00000000-0000-0000-0000-000000000821',
|
||||
@@ -565,28 +567,80 @@ async function testCatalogAndLearning() {
|
||||
});
|
||||
assert.ok(questionsByNode.items?.some(item => item.id === ids.question), 'catalog questions should filter by contentNodeId');
|
||||
|
||||
const session = await request('/api/learning/practice-sessions', {
|
||||
method: 'POST',
|
||||
body: { userId: USER_ID, mode: 'chapter', targetType: 'category', targetId: ids.question },
|
||||
});
|
||||
assert.ok(session.item?.id, 'practice session should be created');
|
||||
|
||||
const sequentialSession = await request('/api/learning/practice-sessions', {
|
||||
const freeLogin = await loginBySms('13800000008');
|
||||
const freeSession = await request('/api/learning/practice-sessions', {
|
||||
userId: false,
|
||||
headers: { authorization: `Bearer ${freeLogin.session.token}` },
|
||||
method: 'POST',
|
||||
body: {
|
||||
userId: USER_ID,
|
||||
mode: 'sequential',
|
||||
collectionId: ids.questionCollection,
|
||||
questionLimit: 5,
|
||||
},
|
||||
});
|
||||
assert.equal(sequentialSession.item?.collectionId, ids.questionCollection, 'collection session should bind collection');
|
||||
assert.ok(sequentialSession.item?.questionIds?.includes(ids.question), 'collection session should snapshot question ids');
|
||||
assert.equal(freeSession.item?.collectionId, ids.questionCollection, 'collection session should bind collection');
|
||||
assert.equal(freeSession.item?.accessMode, 'free', 'non-SVIP user should use free quota');
|
||||
assert.equal(freeSession.item?.consumedFreeQuota, 2, 'free practice should consume configured daily quota');
|
||||
assert.equal(freeSession.item?.questionCount, 2, 'free practice should be truncated to configured free limit');
|
||||
assert.equal(freeSession.item?.accessSnapshot?.truncated, true, 'free practice access snapshot should mark truncation');
|
||||
assert.ok(freeSession.item?.questionIds?.includes(ids.question), 'free session should snapshot first question');
|
||||
|
||||
const nodeSession = await request('/api/learning/practice-sessions', {
|
||||
const freeLimitReached = await request('/api/learning/practice-sessions', {
|
||||
userId: false,
|
||||
headers: { authorization: `Bearer ${freeLogin.session.token}` },
|
||||
method: 'POST',
|
||||
body: {
|
||||
userId: USER_ID,
|
||||
mode: 'sequential',
|
||||
collectionId: ids.questionCollection,
|
||||
questionLimit: 5,
|
||||
},
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(freeLimitReached.code, 'PRACTICE_FREE_LIMIT_REACHED', 'second free session should be blocked after quota is exhausted');
|
||||
|
||||
const forbiddenAnswer = await request('/api/learning/answers', {
|
||||
userId: false,
|
||||
headers: { authorization: `Bearer ${freeLogin.session.token}` },
|
||||
method: 'POST',
|
||||
body: {
|
||||
questionId: ids.questionThree,
|
||||
selectedOptions: ['1'],
|
||||
practiceSessionId: freeSession.item.id,
|
||||
},
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(forbiddenAnswer.code, 'PRACTICE_SESSION_QUESTION_FORBIDDEN', 'answers outside the session snapshot should be rejected');
|
||||
|
||||
const answer = await request('/api/learning/answers', {
|
||||
userId: false,
|
||||
headers: { authorization: `Bearer ${freeLogin.session.token}` },
|
||||
method: 'POST',
|
||||
body: {
|
||||
questionId: ids.question,
|
||||
selectedOptions: ['0'],
|
||||
practiceSessionId: freeSession.item.id,
|
||||
},
|
||||
});
|
||||
assert.equal(answer.item?.isCorrect, false, 'wrong answer should be judged false');
|
||||
|
||||
const staffSequentialSession = await request('/api/learning/practice-sessions', {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
mode: 'sequential',
|
||||
collectionId: ids.questionCollection,
|
||||
questionLimit: 5,
|
||||
},
|
||||
});
|
||||
assert.equal(staffSequentialSession.item?.accessMode, 'staff', 'tenant staff should bypass free practice quota');
|
||||
assert.ok(staffSequentialSession.item?.questionIds?.includes(ids.questionThree), 'staff session should receive the full collection');
|
||||
|
||||
const nodeSession = await request('/api/learning/practice-sessions', {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
mode: 'random',
|
||||
contentNodeId: ids.contentNodeProfessional,
|
||||
questionLimit: 5,
|
||||
@@ -596,9 +650,10 @@ async function testCatalogAndLearning() {
|
||||
assert.ok(nodeSession.item?.questionIds?.includes(ids.question), 'node session should include descendant questions');
|
||||
|
||||
const mockSession = await request('/api/learning/practice-sessions', {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: {
|
||||
userId: USER_ID,
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
blueprintId: ids.practiceBlueprintMock,
|
||||
},
|
||||
});
|
||||
@@ -608,18 +663,11 @@ async function testCatalogAndLearning() {
|
||||
assert.equal(Number(mockSession.item?.totalScore), 100, 'mock session should inherit total score');
|
||||
assert.ok(mockSession.item?.questionIds?.includes(ids.question), 'mock session should snapshot assembled questions');
|
||||
|
||||
const answer = await request('/api/learning/answers', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
userId: USER_ID,
|
||||
questionId: ids.question,
|
||||
selectedOptions: ['0'],
|
||||
practiceSessionId: session.item.id,
|
||||
},
|
||||
const wrong = await request('/api/learning/wrong-questions', {
|
||||
userId: false,
|
||||
headers: { authorization: `Bearer ${freeLogin.session.token}` },
|
||||
query: { status: 'all' },
|
||||
});
|
||||
assert.equal(answer.item?.isCorrect, false, 'wrong answer should be judged false');
|
||||
|
||||
const wrong = await request('/api/learning/wrong-questions', { query: { status: 'all' } });
|
||||
assert.ok(wrong.items?.some(item => item.questionId === ids.question), 'wrong book should include smoke question');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user