Merge remote-tracking branch 'origin/feature/own-learning-objects' into feature/own-learning-objects
# Conflicts: # backend/src/services/learning-objects/database-learning-object-provider.ts # backend/tests/services/learning-objects/database-learning-object-provider.test.ts # backend/tests/test-utils/expectations.ts
This commit is contained in:
		
						commit
						9f28e4ed17
					
				
					 84 changed files with 874 additions and 1048 deletions
				
			
		|  | @ -1,63 +1,62 @@ | |||
| import {AssertionError} from "node:assert"; | ||||
| import {LearningObject} from "../../src/entities/content/learning-object.entity"; | ||||
| import {FilteredLearningObject, LearningPath} from "../../src/interfaces/learning-content"; | ||||
| import {LearningPath as LearningPathEntity} from "../../src/entities/content/learning-path.entity" | ||||
| import { expect } from "vitest"; | ||||
| import { AssertionError } from 'node:assert'; | ||||
| import { LearningObject } from '../../src/entities/content/learning-object.entity'; | ||||
| import { FilteredLearningObject, LearningPath } from '../../src/interfaces/learning-content'; | ||||
| import { LearningPath as LearningPathEntity } from '../../src/entities/content/learning-path.entity'; | ||||
| import { expect } from 'vitest'; | ||||
| 
 | ||||
| // Ignored properties because they belang for example to the class, not to the entity itself.
 | ||||
| const IGNORE_PROPERTIES = ["parent"]; | ||||
| const IGNORE_PROPERTIES = ['parent']; | ||||
| 
 | ||||
| /** | ||||
|  * Checks if the actual entity from the database conforms to the entity that was added previously. | ||||
|  * @param actual The actual entity retrieved from the database | ||||
|  * @param expected The (previously added) entity we would expect to retrieve | ||||
|  */ | ||||
| export function expectToBeCorrectEntity<T extends object>( | ||||
|     actual: {entity: T, name?: string}, | ||||
|     expected: {entity: T, name?: string} | ||||
| ): void { | ||||
| export function expectToBeCorrectEntity<T extends object>(actual: { entity: T; name?: string }, expected: { entity: T; name?: string }): void { | ||||
|     if (!actual.name) { | ||||
|         actual.name = "actual"; | ||||
|         actual.name = 'actual'; | ||||
|     } | ||||
|     if (!expected.name) { | ||||
|         expected.name = "expected"; | ||||
|         expected.name = 'expected'; | ||||
|     } | ||||
|     for (let property in expected.entity) { | ||||
|     for (const property in expected.entity) { | ||||
|         if ( | ||||
|             property !in IGNORE_PROPERTIES | ||||
|             && expected.entity[property] !== undefined // If we don't expect a certain value for a property, we assume it can be filled in by the database however it wants.
 | ||||
|             && typeof expected.entity[property] !== "function" // Functions obviously are not persisted via the database
 | ||||
|             property! in IGNORE_PROPERTIES && | ||||
|             expected.entity[property] !== undefined && // If we don't expect a certain value for a property, we assume it can be filled in by the database however it wants.
 | ||||
|             typeof expected.entity[property] !== 'function' // Functions obviously are not persisted via the database
 | ||||
|         ) { | ||||
|             if (!actual.entity.hasOwnProperty(property)) { | ||||
|                 throw new AssertionError({ | ||||
|                     message: `${expected.name} has defined property ${property}, but ${actual.name} is missing it.` | ||||
|                     message: `${expected.name} has defined property ${property}, but ${actual.name} is missing it.`, | ||||
|                 }); | ||||
|             } | ||||
|             if (typeof expected.entity[property] === "boolean") { // Sometimes, booleans get represented by numbers 0 and 1 in the objects actual from the database.
 | ||||
|                 if (!!expected.entity[property] !== !!actual.entity[property]) { | ||||
|             if (typeof expected.entity[property] === 'boolean') { | ||||
|                 // Sometimes, booleans get represented by numbers 0 and 1 in the objects actual from the database.
 | ||||
|                 if (Boolean(expected.entity[property]) !== Boolean(actual.entity[property])) { | ||||
|                     throw new AssertionError({ | ||||
|                         message: `${property} was ${expected.entity[property]} in ${expected.name},
 | ||||
|                         but ${actual.entity[property]} (${!!expected.entity[property]}) in ${actual.name}` | ||||
|                         but ${actual.entity[property]} (${Boolean(expected.entity[property])}) in ${actual.name}`,
 | ||||
|                     }); | ||||
|                 } | ||||
|             } else if (typeof expected.entity[property] !== typeof actual.entity[property]) { | ||||
|                 throw new AssertionError({ | ||||
|                     message: `${property} has type ${typeof expected.entity[property]} in ${expected.name}, but type ${typeof actual.entity[property]} in ${actual.name}.` | ||||
|                     message: `${property} has type ${typeof expected.entity[property]} in ${expected.name}, but type ${typeof actual.entity[property]} in ${actual.name}.`, | ||||
|                 }); | ||||
|             } else if (typeof expected.entity[property] === "object") { | ||||
|             } else if (typeof expected.entity[property] === 'object') { | ||||
|                 expectToBeCorrectEntity( | ||||
|                     { | ||||
|                         name: actual.name + "." + property, | ||||
|                         entity: actual.entity[property] as object | ||||
|                     }, { | ||||
|                         name: expected.name + "." + property, | ||||
|                         entity: expected.entity[property] as object | ||||
|                         name: actual.name + '.' + property, | ||||
|                         entity: actual.entity[property] as object, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: expected.name + '.' + property, | ||||
|                         entity: expected.entity[property] as object, | ||||
|                     } | ||||
|                 ); | ||||
|             } else { | ||||
|                 if (expected.entity[property] !== actual.entity[property]) { | ||||
|                     throw new AssertionError({ | ||||
|                         message: `${property} was ${expected.entity[property]} in ${expected.name}, but ${actual.entity[property]} in ${actual.name}` | ||||
|                         message: `${property} was ${expected.entity[property]} in ${expected.name}, but ${actual.entity[property]} in ${actual.name}`, | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|  | @ -78,7 +77,7 @@ export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearni | |||
|     expect(filtered.key).toEqual(original.hruid); | ||||
|     expect(filtered.targetAges).toEqual(original.targetAges); | ||||
|     expect(filtered.title).toEqual(original.title); | ||||
|     expect(!!filtered.teacherExclusive).toEqual(!!original.teacherExclusive) // !!: Workaround: MikroORM with SQLite returns 0 and 1 instead of booleans.
 | ||||
|     expect(Boolean(filtered.teacherExclusive)).toEqual(original.teacherExclusive); // !!: Workaround: MikroORM with SQLite returns 0 and 1 instead of booleans.
 | ||||
|     expect(filtered.skosConcepts).toEqual(original.skosConcepts); | ||||
|     expect(filtered.estimatedTime).toEqual(original.estimatedTime); | ||||
|     expect(filtered.educationalGoals).toEqual(original.educationalGoals); | ||||
|  | @ -112,10 +111,10 @@ export function expectToBeCorrectLearningPath( | |||
|     expect(learningPath.description).toEqual(expectedEntity.description); | ||||
|     expect(learningPath.title).toEqual(expectedEntity.title); | ||||
| 
 | ||||
|     const keywords = new Set(learningObjectsOnPath.flatMap(it => it.keywords || [])); | ||||
|     expect(new Set(learningPath.keywords.split(' '))).toEqual(keywords) | ||||
|     const keywords = new Set(learningObjectsOnPath.flatMap((it) => it.keywords || [])); | ||||
|     expect(new Set(learningPath.keywords.split(' '))).toEqual(keywords); | ||||
| 
 | ||||
|     const targetAges = new Set(learningObjectsOnPath.flatMap(it => it.targetAges || [])); | ||||
|     const targetAges = new Set(learningObjectsOnPath.flatMap((it) => it.targetAges || [])); | ||||
|     expect(new Set(learningPath.target_ages)).toEqual(targetAges); | ||||
|     expect(learningPath.min_age).toEqual(Math.min(...targetAges)); | ||||
|     expect(learningPath.max_age).toEqual(Math.max(...targetAges)); | ||||
|  | @ -123,42 +122,29 @@ export function expectToBeCorrectLearningPath( | |||
|     expect(learningPath.num_nodes).toEqual(expectedEntity.nodes.length); | ||||
|     expect(learningPath.image || null).toEqual(expectedEntity.image); | ||||
| 
 | ||||
|     let expectedLearningPathNodes = new Map( | ||||
|         expectedEntity.nodes.map(node => [ | ||||
|             {learningObjectHruid: node.learningObjectHruid, language: node.language, version: node.version}, | ||||
|             {startNode: node.startNode, transitions: node.transitions} | ||||
|     const expectedLearningPathNodes = new Map( | ||||
|         expectedEntity.nodes.map((node) => [ | ||||
|             { learningObjectHruid: node.learningObjectHruid, language: node.language, version: node.version }, | ||||
|             { startNode: node.startNode, transitions: node.transitions }, | ||||
|         ]) | ||||
|     ); | ||||
| 
 | ||||
|     for (let node of learningPath.nodes) { | ||||
|     for (const node of learningPath.nodes) { | ||||
|         const nodeKey = { | ||||
|             learningObjectHruid: node.learningobject_hruid, | ||||
|             language: node.language, | ||||
|             version: node.version | ||||
|             version: node.version, | ||||
|         }; | ||||
|         expect(expectedLearningPathNodes.keys()).toContainEqual(nodeKey); | ||||
|         let expectedNode = [...expectedLearningPathNodes.entries()] | ||||
|             .filter(([key, _]) => | ||||
|                 key.learningObjectHruid === nodeKey.learningObjectHruid | ||||
|                 && key.language === node.language | ||||
|                 && key.version === node.version | ||||
|             )[0][1] | ||||
|         const expectedNode = [...expectedLearningPathNodes.entries()].filter( | ||||
|             ([key, _]) => key.learningObjectHruid === nodeKey.learningObjectHruid && key.language === node.language && key.version === node.version | ||||
|         )[0][1]; | ||||
|         expect(node.start_node).toEqual(expectedNode?.startNode); | ||||
| 
 | ||||
|         expect( | ||||
|             new Set(node.transitions.map(it => it.next.hruid)) | ||||
|         ).toEqual( | ||||
|             new Set(expectedNode.transitions.map(it => it.next.learningObjectHruid)) | ||||
|         ); | ||||
|         expect( | ||||
|             new Set(node.transitions.map(it => it.next.language)) | ||||
|         ).toEqual( | ||||
|             new Set(expectedNode.transitions.map(it => it.next.language)) | ||||
|         ); | ||||
|         expect( | ||||
|             new Set(node.transitions.map(it => it.next.version)) | ||||
|         ).toEqual( | ||||
|             new Set(expectedNode.transitions.map(it => it.next.version)) | ||||
|         expect(new Set(node.transitions.map((it) => it.next.hruid))).toEqual( | ||||
|             new Set(expectedNode.transitions.map((it) => it.next.learningObjectHruid)) | ||||
|         ); | ||||
|         expect(new Set(node.transitions.map((it) => it.next.language))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.language))); | ||||
|         expect(new Set(node.transitions.map((it) => it.next.version))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.version))); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import fs from "fs"; | ||||
| import path from "node:path"; | ||||
| import fs from 'fs'; | ||||
| import path from 'node:path'; | ||||
| 
 | ||||
| /** | ||||
|  * Load the asset at the given path. | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger