test(backend): Testen voor DatabaseLearningPathProvider.fetchLearningPaths afgewerkt
Hierbij optredende problemen opgelost.
This commit is contained in:
		
							parent
							
								
									1f9e9ed70a
								
							
						
					
					
						commit
						7018a8822d
					
				
					 10 changed files with 139 additions and 32 deletions
				
			
		|  | @ -7,21 +7,30 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj | ||||||
|     public findByIdentifier( |     public findByIdentifier( | ||||||
|         identifier: LearningObjectIdentifier |         identifier: LearningObjectIdentifier | ||||||
|     ): Promise<LearningObject | null> { |     ): Promise<LearningObject | null> { | ||||||
|         return this.findOne({ |         return this.findOne( | ||||||
|             hruid: identifier.hruid, |             { | ||||||
|             language: identifier.language, |                 hruid: identifier.hruid, | ||||||
|             version: identifier.version, |                 language: identifier.language, | ||||||
|         }); |                 version: identifier.version, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 populate: ["keywords"] | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findLatestByHruidAndLanguage(hruid: string, language: Language) { |     public findLatestByHruidAndLanguage(hruid: string, language: Language) { | ||||||
|         return this.findOne({ |         return this.findOne( | ||||||
|             hruid: hruid, |             { | ||||||
|             language: language |                 hruid: hruid, | ||||||
|         }, { |                 language: language | ||||||
|             orderBy: { |             }, | ||||||
|                 version: "DESC" |             { | ||||||
|  |                 populate: ["keywords"], | ||||||
|  |                 orderBy: { | ||||||
|  |                     version: "DESC" | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         }); |         ); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,7 +7,10 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath> | ||||||
|         hruid: string, |         hruid: string, | ||||||
|         language: Language |         language: Language | ||||||
|     ): Promise<LearningPath | null> { |     ): Promise<LearningPath | null> { | ||||||
|         return this.findOne({ hruid: hruid, language: language }); |         return this.findOne( | ||||||
|  |             { hruid: hruid, language: language }, | ||||||
|  |             { populate: ["nodes", "nodes.transitions"] } | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -25,8 +28,8 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath> | ||||||
|                     { title: { $like: `%${query}%`} }, |                     { title: { $like: `%${query}%`} }, | ||||||
|                     { description: { $like: `%${query}%`} } |                     { description: { $like: `%${query}%`} } | ||||||
|                 ] |                 ] | ||||||
|             } |             }, | ||||||
|  |             populate: ["nodes", "nodes.transitions"] | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     // This repository is read-only for now since creating own learning object is an extension feature.
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -33,6 +33,8 @@ import { LearningPath } from '../entities/content/learning-path.entity.js'; | ||||||
| import { LearningPathRepository } from './content/learning-path-repository.js'; | import { LearningPathRepository } from './content/learning-path-repository.js'; | ||||||
| import { AttachmentRepository } from './content/attachment-repository.js'; | import { AttachmentRepository } from './content/attachment-repository.js'; | ||||||
| import { Attachment } from '../entities/content/attachment.entity.js'; | import { Attachment } from '../entities/content/attachment.entity.js'; | ||||||
|  | import {LearningPathNode} from "../entities/content/learning-path-node.entity"; | ||||||
|  | import {LearningPathTransition} from "../entities/content/learning-path-transition.entity"; | ||||||
| 
 | 
 | ||||||
| let entityManager: EntityManager | undefined; | let entityManager: EntityManager | undefined; | ||||||
| 
 | 
 | ||||||
|  | @ -113,6 +115,8 @@ export const getLearningPathRepository = repositoryGetter< | ||||||
|     LearningPath, |     LearningPath, | ||||||
|     LearningPathRepository |     LearningPathRepository | ||||||
| >(LearningPath); | >(LearningPath); | ||||||
|  | export const getLearningPathNodeRepository = repositoryGetter(LearningPathNode); | ||||||
|  | export const getLearningPathTransitionRepository = repositoryGetter(LearningPathTransition); | ||||||
| export const getAttachmentRepository = repositoryGetter< | export const getAttachmentRepository = repositoryGetter< | ||||||
|     Attachment, |     Attachment, | ||||||
|     AttachmentRepository |     AttachmentRepository | ||||||
|  |  | ||||||
|  | @ -5,10 +5,11 @@ import {LearningPathTransition} from "./learning-path-transition.entity"; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
| export class LearningPathNode { | export class LearningPathNode { | ||||||
|  | 
 | ||||||
|     @ManyToOne({ entity: () => LearningPath, primary: true }) |     @ManyToOne({ entity: () => LearningPath, primary: true }) | ||||||
|     learningPath!: LearningPath; |     learningPath!: LearningPath; | ||||||
| 
 | 
 | ||||||
|     @PrimaryKey({ type: "numeric", autoincrement: true }) |     @PrimaryKey({ type: "integer", autoincrement: true }) | ||||||
|     nodeNumber!: number; |     nodeNumber!: number; | ||||||
| 
 | 
 | ||||||
|     @Property({ type: 'string' }) |     @Property({ type: 'string' }) | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import {LearningPathNode} from "./learning-path-node.entity"; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
| export class LearningPathTransition { | export class LearningPathTransition { | ||||||
|     @ManyToOne({entity: () => LearningPathNode }) |     @ManyToOne({entity: () => LearningPathNode, primary: true }) | ||||||
|     node!: LearningPathNode; |     node!: LearningPathNode; | ||||||
| 
 | 
 | ||||||
|     @PrimaryKey({ type: 'numeric' }) |     @PrimaryKey({ type: 'numeric' }) | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ export interface LearningObjectNode { | ||||||
|     _id: string; |     _id: string; | ||||||
|     learningobject_hruid: string; |     learningobject_hruid: string; | ||||||
|     version: number; |     version: number; | ||||||
|     language: string; |     language: Language; | ||||||
|     start_node?: boolean; |     start_node?: boolean; | ||||||
|     transitions: Transition[]; |     transitions: Transition[]; | ||||||
|     created_at: string; |     created_at: string; | ||||||
|  | @ -88,7 +88,7 @@ export interface FilteredLearningObject { | ||||||
|     version: number; |     version: number; | ||||||
|     title: string; |     title: string; | ||||||
|     htmlUrl: string; |     htmlUrl: string; | ||||||
|     language: string; |     language: Language; | ||||||
|     difficulty: number; |     difficulty: number; | ||||||
|     estimatedTime: number; |     estimatedTime: number; | ||||||
|     available: boolean; |     available: boolean; | ||||||
|  |  | ||||||
|  | @ -6,6 +6,10 @@ import {getLearningObjectRepository, getLearningPathRepository} from "../../../s | ||||||
| import learningObjectExample from "../../test-assets/learning-objects/pn_werkingnotebooks/pn-werkingnotebooks-example"; | import learningObjectExample from "../../test-assets/learning-objects/pn_werkingnotebooks/pn-werkingnotebooks-example"; | ||||||
| import learningPathExample from "../../test-assets/learning-paths/pn-werking-example" | import learningPathExample from "../../test-assets/learning-paths/pn-werking-example" | ||||||
| import databaseLearningPathProvider from "../../../src/services/learning-paths/database-learning-path-provider"; | import databaseLearningPathProvider from "../../../src/services/learning-paths/database-learning-path-provider"; | ||||||
|  | import {expectToBeCorrectLearningPath} from "../../test-utils/expectations"; | ||||||
|  | import {LearningObjectRepository} from "../../../src/data/content/learning-object-repository"; | ||||||
|  | import learningObjectService from "../../../src/services/learning-objects/learning-object-service"; | ||||||
|  | import {Language} from "../../../src/entities/content/language"; | ||||||
| 
 | 
 | ||||||
| async function initExampleData(): Promise<{ learningObject: LearningObject, learningPath: LearningPath }> { | async function initExampleData(): Promise<{ learningObject: LearningObject, learningPath: LearningPath }> { | ||||||
|     const learningObjectRepo = getLearningObjectRepository(); |     const learningObjectRepo = getLearningObjectRepository(); | ||||||
|  | @ -18,15 +22,17 @@ async function initExampleData(): Promise<{ learningObject: LearningObject, lear | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| describe("DatabaseLearningPathProvider", () => { | describe("DatabaseLearningPathProvider", () => { | ||||||
|  |     let learningObjectRepo: LearningObjectRepository; | ||||||
|     let example: {learningObject: LearningObject, learningPath: LearningPath}; |     let example: {learningObject: LearningObject, learningPath: LearningPath}; | ||||||
| 
 | 
 | ||||||
|     beforeAll(async () => { |     beforeAll(async () => { | ||||||
|         await setupTestApp(); |         await setupTestApp(); | ||||||
|         example = await initExampleData(); |         example = await initExampleData(); | ||||||
|  |         learningObjectRepo = getLearningObjectRepository(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe("fetchLearningPaths", () => { |     describe("fetchLearningPaths", () => { | ||||||
|         it("returns the learning path correctly", () => { |         it("returns the learning path correctly", async () => { | ||||||
|             const result = await databaseLearningPathProvider.fetchLearningPaths( |             const result = await databaseLearningPathProvider.fetchLearningPaths( | ||||||
|                 [example.learningPath.hruid], |                 [example.learningPath.hruid], | ||||||
|                 example.learningPath.language, |                 example.learningPath.language, | ||||||
|  | @ -34,7 +40,26 @@ describe("DatabaseLearningPathProvider", () => { | ||||||
|             ); |             ); | ||||||
|             expect(result.success).toBe(true); |             expect(result.success).toBe(true); | ||||||
|             expect(result.data?.length).toBe(1); |             expect(result.data?.length).toBe(1); | ||||||
|             expect(result.data) | 
 | ||||||
|         }) |             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 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); | ||||||
|  |         }); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,21 +1,28 @@ | ||||||
| import {Language} from "../../../src/entities/content/language"; | import {Language} from "../../../src/entities/content/language"; | ||||||
| import {LearningPathTransition} from "../../../src/entities/content/learning-path-transition.entity"; | import {LearningPathTransition} from "../../../src/entities/content/learning-path-transition.entity"; | ||||||
| import {LearningPathNode} from "../../../src/entities/content/learning-path-node.entity"; | import {LearningPathNode} from "../../../src/entities/content/learning-path-node.entity"; | ||||||
|  | import {LearningPath} from "../../../src/entities/content/learning-path.entity"; | ||||||
| 
 | 
 | ||||||
| export function createLearningPathTransition(condition: string | null, to: LearningPathNode) { | export function createLearningPathTransition(node: LearningPathNode, transitionNumber: number, condition: string | null, to: LearningPathNode) { | ||||||
|     let trans = new LearningPathTransition(); |     let trans = new LearningPathTransition(); | ||||||
|  |     trans.node = node; | ||||||
|  |     trans.transitionNumber = transitionNumber; | ||||||
|     trans.condition = condition || "true"; |     trans.condition = condition || "true"; | ||||||
|     trans.next = to; |     trans.next = to; | ||||||
|     return trans; |     return trans; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function createLearningPathNode( | export function createLearningPathNode( | ||||||
|  |     learningPath: LearningPath, | ||||||
|  |     nodeNumber: number, | ||||||
|     learningObjectHruid: string, |     learningObjectHruid: string, | ||||||
|     version: number, |     version: number, | ||||||
|     language: Language, |     language: Language, | ||||||
|     startNode: boolean |     startNode: boolean | ||||||
| ) { | ) { | ||||||
|     let node = new LearningPathNode(); |     let node = new LearningPathNode(); | ||||||
|  |     node.learningPath = learningPath; | ||||||
|  |     node.nodeNumber = nodeNumber; | ||||||
|     node.learningObjectHruid = learningObjectHruid; |     node.learningObjectHruid = learningObjectHruid; | ||||||
|     node.version = version; |     node.version = version; | ||||||
|     node.language = language; |     node.language = language; | ||||||
|  |  | ||||||
|  | @ -1,16 +1,17 @@ | ||||||
| import {LearningPath, LearningPathNode} from "../../../src/entities/content/learning-path.entity"; | import {LearningPath} from "../../../src/entities/content/learning-path.entity"; | ||||||
| import {Language} from "../../../src/entities/content/language"; | import {Language} from "../../../src/entities/content/language"; | ||||||
| import {EnvVars, getEnvVar} from "../../../src/util/envvars"; | import {EnvVars, getEnvVar} from "../../../src/util/envvars"; | ||||||
| import {createLearningPathNode, createLearningPathTransition} from "./learning-path-utils"; | import {createLearningPathNode, createLearningPathTransition} from "./learning-path-utils"; | ||||||
|  | import {LearningPathNode} from "../../../src/entities/content/learning-path-node.entity"; | ||||||
| 
 | 
 | ||||||
| function createNodes(): LearningPathNode[] { | function createNodes(learningPath: LearningPath): LearningPathNode[] { | ||||||
|     let nodes = [ |     let nodes = [ | ||||||
|         createLearningPathNode("u_pn_werkingnotebooks", 3, Language.Dutch, true), |         createLearningPathNode(learningPath, 0, "u_pn_werkingnotebooks", 3, Language.Dutch, true), | ||||||
|         createLearningPathNode("pn_werkingnotebooks2", 3, Language.Dutch, false), |         createLearningPathNode(learningPath, 1, "pn_werkingnotebooks2", 3, Language.Dutch, false), | ||||||
|         createLearningPathNode("pn_werkingnotebooks3", 3, Language.Dutch, false), |         createLearningPathNode(learningPath, 2, "pn_werkingnotebooks3", 3, Language.Dutch, false), | ||||||
|     ]; |     ]; | ||||||
|     nodes[0].transitions.push(createLearningPathTransition("true", nodes[1])); |     nodes[0].transitions.push(createLearningPathTransition(nodes[0], 0, "true", nodes[1])); | ||||||
|     nodes[1].transitions.push(createLearningPathTransition("true", nodes[2])); |     nodes[1].transitions.push(createLearningPathTransition(nodes[1], 0, "true", nodes[2])); | ||||||
|     return nodes; |     return nodes; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -21,7 +22,7 @@ const example: LearningPathExample = { | ||||||
|         path.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}pn_werking`; |         path.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}pn_werking`; | ||||||
|         path.title = "Werken met notebooks"; |         path.title = "Werken met notebooks"; | ||||||
|         path.description = "Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?"; |         path.description = "Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?"; | ||||||
|         path.nodes = createNodes(); |         path.nodes = createNodes(path); | ||||||
|         return path; |         return path; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -99,9 +99,66 @@ export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearni | ||||||
|  * |  * | ||||||
|  * @param learningPath The learning path returned by the retriever, service or endpoint |  * @param learningPath The learning path returned by the retriever, service or endpoint | ||||||
|  * @param expectedEntity The expected entity |  * @param expectedEntity The expected entity | ||||||
|  |  * @param learningObjectsOnPath The learning objects on LearningPath. Necessary since some information in | ||||||
|  |  *                              the learning path returned from the API endpoint | ||||||
|  */ |  */ | ||||||
| export function expectToBeCorrectLearningPath(learningPath: LearningPath, expectedEntity: LearningPathEntity) { | export function expectToBeCorrectLearningPath( | ||||||
|  |     learningPath: LearningPath, | ||||||
|  |     expectedEntity: LearningPathEntity, | ||||||
|  |     learningObjectsOnPath: FilteredLearningObject[] | ||||||
|  | ) { | ||||||
|     expect(learningPath.hruid).toEqual(expectedEntity.hruid); |     expect(learningPath.hruid).toEqual(expectedEntity.hruid); | ||||||
|     expect(learningPath.language).toEqual(expectedEntity.language); |     expect(learningPath.language).toEqual(expectedEntity.language); | ||||||
|     expect(learningPath.description).toEqual(expectedEntity.description); |     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 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)); | ||||||
|  | 
 | ||||||
|  |     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} | ||||||
|  |         ]) | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     for (let node of learningPath.nodes) { | ||||||
|  |         const nodeKey = { | ||||||
|  |             learningObjectHruid: node.learningobject_hruid, | ||||||
|  |             language: node.language, | ||||||
|  |             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] | ||||||
|  |         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)) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger