215 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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 {
 | |
|     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 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 '../../../src/entities/content/language.js';
 | |
| 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 '../../../src/interfaces/learning-content.js';
 | |
| 
 | |
| 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 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);
 | |
| 
 | |
|     console.log(await getSubmissionRepository().findAll({}));
 | |
| 
 | |
|     const studentA = studentRepo.create({
 | |
|         username: 'student_a',
 | |
|         firstName: 'Aron',
 | |
|         lastName: 'Student',
 | |
|     });
 | |
|     await studentRepo.save(studentA);
 | |
|     const submissionA = submissionRepo.create({
 | |
|         learningObjectHruid: learningContent.branchingObject.hruid,
 | |
|         learningObjectLanguage: learningContent.branchingObject.language,
 | |
|         learningObjectVersion: learningContent.branchingObject.version,
 | |
|         submitter: studentA,
 | |
|         submissionTime: new Date(),
 | |
|         content: '[0]',
 | |
|     });
 | |
|     await submissionRepo.save(submissionA);
 | |
| 
 | |
|     const studentB = studentRepo.create({
 | |
|         username: 'student_b',
 | |
|         firstName: 'Bill',
 | |
|         lastName: 'Student',
 | |
|     });
 | |
|     await studentRepo.save(studentB);
 | |
|     const submissionB = submissionRepo.create({
 | |
|         learningObjectHruid: learningContent.branchingObject.hruid,
 | |
|         learningObjectLanguage: learningContent.branchingObject.language,
 | |
|         learningObjectVersion: learningContent.branchingObject.version,
 | |
|         submitter: studentB,
 | |
|         submissionTime: new Date(),
 | |
|         content: '[1]',
 | |
|     });
 | |
|     await submissionRepo.save(submissionB);
 | |
| 
 | |
|     return {
 | |
|         learningContent: learningContent,
 | |
|         studentA: studentA,
 | |
|         studentB: studentB,
 | |
|     };
 | |
| }
 | |
| 
 | |
| function expectBranchingObjectNode(
 | |
|     result: LearningPathResponse,
 | |
|     persTestData: {
 | |
|         learningContent: ConditionTestLearningPathAndLearningObjects;
 | |
|         studentA: Student;
 | |
|         studentB: Student;
 | |
|     }
 | |
| ): LearningObjectNode {
 | |
|     const branchingObjectMatches = result.data![0].nodes.filter(
 | |
|         (it) => it.learningobject_hruid === persTestData.learningContent.branchingObject.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 };
 | |
| 
 | |
|     beforeAll(async () => {
 | |
|         await setupTestApp();
 | |
|         example = await initExampleData();
 | |
|         persTestData = await initPersonalizationTestData();
 | |
|     });
 | |
| 
 | |
|     describe('fetchLearningPaths', () => {
 | |
|         it('returns the learning path correctly', async () => {
 | |
|             const result = await databaseLearningPathProvider.fetchLearningPaths(
 | |
|                 [example.learningPath.hruid],
 | |
|                 example.learningPath.language,
 | |
|                 'the source'
 | |
|             );
 | |
|             expect(result.success).toBe(true);
 | |
|             expect(result.data?.length).toBe(1);
 | |
| 
 | |
|             const learningObjectsOnPath = (
 | |
|                 await Promise.all(
 | |
|                     example.learningPath.nodes.map((node) =>
 | |
|                         learningObjectService.getLearningObjectById({
 | |
|                             hruid: node.learningObjectHruid,
 | |
|                             version: node.version,
 | |
|                             language: node.language,
 | |
|                         })
 | |
|                     )
 | |
|                 )
 | |
|             ).filter((it) => it !== null);
 | |
| 
 | |
|             expectToBeCorrectLearningPath(result.data![0], example.learningPath, 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,
 | |
|                 'the source',
 | |
|                 { type: 'student', student: persTestData.studentA }
 | |
|             );
 | |
|             expect(result.success).toBeTruthy();
 | |
|             expect(result.data?.length).toBe(1);
 | |
| 
 | |
|             // There should be exactly one branching object
 | |
|             let branchingObject = expectBranchingObjectNode(result, persTestData);
 | |
| 
 | |
|             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.
 | |
| 
 | |
|             // For student B:
 | |
|             result = await databaseLearningPathProvider.fetchLearningPaths(
 | |
|                 [persTestData.learningContent.learningPath.hruid],
 | |
|                 persTestData.learningContent.learningPath.language,
 | |
|                 'the source',
 | |
|                 { type: 'student', student: persTestData.studentB }
 | |
|             );
 | |
|             expect(result.success).toBeTruthy();
 | |
|             expect(result.data?.length).toBe(1);
 | |
| 
 | |
|             // There should still be exactly one branching object
 | |
|             branchingObject = expectBranchingObjectNode(result, persTestData);
 | |
| 
 | |
|             // 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.
 | |
|         });
 | |
|         it('returns a non-successful response if a non-existing learning path is queried', async () => {
 | |
|             const result = await databaseLearningPathProvider.fetchLearningPaths(
 | |
|                 [example.learningPath.hruid],
 | |
|                 Language.Abkhazian, // Wrong language
 | |
|                 'the source'
 | |
|             );
 | |
| 
 | |
|             expect(result.success).toBe(false);
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     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
 | |
|             );
 | |
|             expect(result.length).toBe(1);
 | |
|             expect(result[0].title).toBe(example.learningPath.title);
 | |
|             expect(result[0].description).toBe(example.learningPath.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
 | |
|             );
 | |
|             expect(result.length).toBe(1);
 | |
|             expect(result[0].title).toBe(example.learningPath.title);
 | |
|             expect(result[0].description).toBe(example.learningPath.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
 | |
|             );
 | |
|             expect(result.length).toBe(0);
 | |
|         });
 | |
|     });
 | |
| });
 | 
