fix(backend): Testen DatabaseLearningPathProvider en LearningPathService gerepareerd na refactoring.

This commit is contained in:
Gerald Schmittinger 2025-04-16 09:08:23 +02:00
parent 1815371a7b
commit c624e36680
6 changed files with 163 additions and 204 deletions

View file

@ -2,167 +2,84 @@ import { beforeAll, describe, expect, it } from 'vitest';
import { LearningObject } from '../../../src/entities/content/learning-object.entity.js';
import { setupTestApp } from '../../setup-tests.js';
import { LearningPath } from '../../../src/entities/content/learning-path.entity.js';
import {
getAssignmentRepository,
getClassRepository,
getGroupRepository,
getLearningObjectRepository,
getLearningPathRepository,
getStudentRepository,
getSubmissionRepository,
} from '../../../src/data/repositories.js';
import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.js';
import learningPathExample from '../../test-assets/learning-paths/pn-werking-example.js';
import { getSubmissionRepository } from '../../../src/data/repositories.js';
import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider.js';
import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.js';
import learningObjectService from '../../../src/services/learning-objects/learning-object-service.js';
import { Language } from '@dwengo-1/common/util/language';
import {
ConditionTestLearningPathAndLearningObjects,
createConditionTestLearningPathAndLearningObjects,
} from '../../test-assets/learning-paths/test-conditions-example.js';
import { Student } from '../../../src/entities/users/student.entity.js';
import { LearningObjectNode, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content';
const STUDENT_A_USERNAME = 'student_a';
const STUDENT_B_USERNAME = 'student_b';
const CLASS_NAME = 'test_class';
async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> {
const learningObjectRepo = getLearningObjectRepository();
const learningPathRepo = getLearningPathRepository();
const learningObject = learningObjectExample.createLearningObject();
const learningPath = learningPathExample.createLearningPath();
await learningObjectRepo.save(learningObject);
await learningPathRepo.save(learningPath);
return { learningObject, learningPath };
}
async function initPersonalizationTestData(): Promise<{
learningContent: ConditionTestLearningPathAndLearningObjects;
studentA: Student;
studentB: Student;
}> {
const studentRepo = getStudentRepository();
const classRepo = getClassRepository();
const assignmentRepo = getAssignmentRepository();
const groupRepo = getGroupRepository();
const submissionRepo = getSubmissionRepository();
const learningPathRepo = getLearningPathRepository();
const learningObjectRepo = getLearningObjectRepository();
const learningContent = createConditionTestLearningPathAndLearningObjects();
await learningObjectRepo.save(learningContent.branchingObject);
await learningObjectRepo.save(learningContent.finalObject);
await learningObjectRepo.save(learningContent.extraExerciseObject);
await learningPathRepo.save(learningContent.learningPath);
// Create students
const studentA = studentRepo.create({
username: STUDENT_A_USERNAME,
firstName: 'Aron',
lastName: 'Student',
});
await studentRepo.save(studentA);
const studentB = studentRepo.create({
username: STUDENT_B_USERNAME,
firstName: 'Bill',
lastName: 'Student',
});
await studentRepo.save(studentB);
// Create class for students
const testClass = classRepo.create({
classId: CLASS_NAME,
displayName: 'Test class',
});
await classRepo.save(testClass);
// Create assignment for students and assign them to different groups
const assignment = assignmentRepo.create({
id: 0,
title: 'Test assignment',
description: 'Test description',
learningPathHruid: learningContent.learningPath.hruid,
learningPathLanguage: learningContent.learningPath.language,
within: testClass,
});
const groupA = groupRepo.create({
groupNumber: 0,
members: [studentA],
assignment,
});
await groupRepo.save(groupA);
const groupB = groupRepo.create({
groupNumber: 1,
members: [studentB],
assignment,
});
await groupRepo.save(groupB);
// Let each of the students make a submission in his own group.
const submissionA = submissionRepo.create({
learningObjectHruid: learningContent.branchingObject.hruid,
learningObjectLanguage: learningContent.branchingObject.language,
learningObjectVersion: learningContent.branchingObject.version,
onBehalfOf: groupA,
submitter: studentA,
submissionTime: new Date(),
content: '[0]',
});
await submissionRepo.save(submissionA);
const submissionB = submissionRepo.create({
learningObjectHruid: learningContent.branchingObject.hruid,
learningObjectLanguage: learningContent.branchingObject.language,
learningObjectVersion: learningContent.branchingObject.version,
onBehalfOf: groupA,
submitter: studentB,
submissionTime: new Date(),
content: '[1]',
});
await submissionRepo.save(submissionB);
return {
learningContent: learningContent,
studentA: studentA,
studentB: studentB,
};
}
LearningObjectNode,
LearningPathResponse
} from '@dwengo-1/common/interfaces/learning-content';
import {
testLearningObject01, testLearningObjectEssayQuestion,
testLearningObjectMultipleChoice
} from "../../test_assets/content/learning-objects.testdata";
import {testLearningPathWithConditions} from "../../test_assets/content/learning-paths.testdata";
import {mapToLearningPath} from "../../../src/services/learning-paths/learning-path-service";
import {getTestGroup01, getTestGroup02} from "../../test_assets/assignments/groups.testdata";
import { Group } from '../../../src/entities/assignments/group.entity.js';
import {RequiredEntityData} from "@mikro-orm/core";
function expectBranchingObjectNode(
result: LearningPathResponse,
persTestData: {
learningContent: ConditionTestLearningPathAndLearningObjects;
studentA: Student;
studentB: Student;
}
result: LearningPathResponse
): LearningObjectNode {
const branchingObjectMatches = result.data![0].nodes.filter(
(it) => it.learningobject_hruid === persTestData.learningContent.branchingObject.hruid
(it) => it.learningobject_hruid === testLearningObjectMultipleChoice.hruid
);
expect(branchingObjectMatches.length).toBe(1);
return branchingObjectMatches[0];
}
describe('DatabaseLearningPathProvider', () => {
let example: { learningObject: LearningObject; learningPath: LearningPath };
let persTestData: { learningContent: ConditionTestLearningPathAndLearningObjects; studentA: Student; studentB: Student };
let testLearningPath: LearningPath;
let branchingLearningObject: RequiredEntityData<LearningObject>;
let extraExerciseLearningObject: RequiredEntityData<LearningObject>;
let finalLearningObject: RequiredEntityData<LearningObject>;
let groupA: Group;
let groupB: Group;
beforeAll(async () => {
await setupTestApp();
example = await initExampleData();
persTestData = await initPersonalizationTestData();
testLearningPath = mapToLearningPath(testLearningPathWithConditions, []);
branchingLearningObject = testLearningObjectMultipleChoice;
extraExerciseLearningObject = testLearningObject01;
finalLearningObject = testLearningObjectEssayQuestion;
groupA = getTestGroup01();
groupB = getTestGroup02();
// Place different submissions for group A and B.
const submissionRepo = getSubmissionRepository();
const submissionA = submissionRepo.create({
learningObjectHruid: branchingLearningObject.hruid,
learningObjectLanguage: branchingLearningObject.language,
learningObjectVersion: branchingLearningObject.version,
content: "[0]",
onBehalfOf: groupA,
submissionTime: new Date(),
submitter: groupA.members[0]
});
await submissionRepo.save(submissionA);
const submissionB = submissionRepo.create({
learningObjectHruid: branchingLearningObject.hruid,
learningObjectLanguage: branchingLearningObject.language,
learningObjectVersion: branchingLearningObject.version,
content: "[1]",
onBehalfOf: groupB,
submissionTime: new Date(),
submitter: groupB.members[0]
});
await submissionRepo.save(submissionB);
});
describe('fetchLearningPaths', () => {
it('returns the learning path correctly', async () => {
const result = await databaseLearningPathProvider.fetchLearningPaths(
[example.learningPath.hruid],
example.learningPath.language,
[testLearningPath.hruid],
testLearningPath.language as Language,
'the source'
);
expect(result.success).toBe(true);
@ -170,7 +87,7 @@ describe('DatabaseLearningPathProvider', () => {
const learningObjectsOnPath = (
await Promise.all(
example.learningPath.nodes.map(async (node) =>
testLearningPath.nodes.map(async (node) =>
learningObjectService.getLearningObjectById({
hruid: node.learningObjectHruid,
version: node.version,
@ -180,49 +97,65 @@ describe('DatabaseLearningPathProvider', () => {
)
).filter((it) => it !== null);
expectToBeCorrectLearningPath(result.data![0], example.learningPath, learningObjectsOnPath);
expectToBeCorrectLearningPath(
result.data![0],
mapToLearningPath(testLearningPathWithConditions, []),
learningObjectsOnPath
);
});
it('returns the correct personalized learning path', async () => {
// For student A:
let result = await databaseLearningPathProvider.fetchLearningPaths(
[persTestData.learningContent.learningPath.hruid],
persTestData.learningContent.learningPath.language,
[testLearningPath.hruid],
testLearningPath.language,
'the source',
{ type: 'student', student: persTestData.studentA }
groupA
);
expect(result.success).toBeTruthy();
expect(result.data?.length).toBe(1);
// There should be exactly one branching object
let branchingObject = expectBranchingObjectNode(result, persTestData);
let branchingObject = expectBranchingObjectNode(result);
expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.finalObject.hruid).length).toBe(0); // StudentA picked the first option, therefore, there should be no direct path to the final object.
expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.extraExerciseObject.hruid).length).toBe(
1
); // There should however be a path to the extra exercise object.
expect(
branchingObject.transitions.filter(
it => it.next.hruid === finalLearningObject.hruid
).length
).toBe(0); // StudentA picked the first option, therefore, there should be no direct path to the final object.
expect(
branchingObject.transitions.filter(
it => it.next.hruid === extraExerciseLearningObject.hruid
).length
).toBe(1); // There should however be a path to the extra exercise object.
// For student B:
result = await databaseLearningPathProvider.fetchLearningPaths(
[persTestData.learningContent.learningPath.hruid],
persTestData.learningContent.learningPath.language,
[testLearningPath.hruid],
testLearningPath.language,
'the source',
{ type: 'student', student: persTestData.studentB }
groupB
);
expect(result.success).toBeTruthy();
expect(result.data?.length).toBe(1);
// There should still be exactly one branching object
branchingObject = expectBranchingObjectNode(result, persTestData);
branchingObject = expectBranchingObjectNode(result);
// However, now the student picks the other option.
expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.finalObject.hruid).length).toBe(1); // StudentB picked the second option, therefore, there should be a direct path to the final object.
expect(branchingObject.transitions.filter((it) => it.next.hruid === persTestData.learningContent.extraExerciseObject.hruid).length).toBe(
0
); // There should not be a path anymore to the extra exercise object.
expect(
branchingObject.transitions.filter(
(it) => it.next.hruid === finalLearningObject.hruid
).length
).toBe(1); // StudentB picked the second option, therefore, there should be a direct path to the final object.
expect(
branchingObject.transitions.filter(
(it) => it.next.hruid === extraExerciseLearningObject.hruid
).length
).toBe(0); // There should not be a path anymore to the extra exercise object.
});
it('returns a non-successful response if a non-existing learning path is queried', async () => {
const result = await databaseLearningPathProvider.fetchLearningPaths(
[example.learningPath.hruid],
[testLearningPath.hruid],
Language.Abkhazian, // Wrong language
'the source'
);
@ -234,26 +167,26 @@ describe('DatabaseLearningPathProvider', () => {
describe('searchLearningPaths', () => {
it('returns the correct learning path when queried with a substring of its title', async () => {
const result = await databaseLearningPathProvider.searchLearningPaths(
example.learningPath.title.substring(2, 6),
example.learningPath.language
testLearningPath.title.substring(2, 6),
testLearningPath.language
);
expect(result.length).toBe(1);
expect(result[0].title).toBe(example.learningPath.title);
expect(result[0].description).toBe(example.learningPath.description);
expect(result[0].title).toBe(testLearningPath.title);
expect(result[0].description).toBe(testLearningPath.description);
});
it('returns the correct learning path when queried with a substring of the description', async () => {
const result = await databaseLearningPathProvider.searchLearningPaths(
example.learningPath.description.substring(5, 12),
example.learningPath.language
testLearningPath.description.substring(5, 12),
testLearningPath.language
);
expect(result.length).toBe(1);
expect(result[0].title).toBe(example.learningPath.title);
expect(result[0].description).toBe(example.learningPath.description);
expect(result[0].title).toBe(testLearningPath.title);
expect(result[0].description).toBe(testLearningPath.description);
});
it('returns an empty result when queried with a text which is not a substring of the title or the description of a learning path', async () => {
const result = await databaseLearningPathProvider.searchLearningPaths(
'substring which does not occur in the title or the description of a learning object',
example.learningPath.language
testLearningPath.language
);
expect(result.length).toBe(0);
});