From 4092f1f617e3c1eb9e33ddc9f76de6d6787895b1 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Mon, 14 Apr 2025 17:14:43 +0200 Subject: [PATCH 01/31] refactor(backend): Personalisatie van leerpaden is enkel mogelijk voor groepen, niet voor individuele studenten. --- backend/src/controllers/learning-paths.ts | 28 +++++---- .../database-learning-path-provider.ts | 15 ++--- .../learning-path-personalization-util.ts | 62 ++----------------- .../learning-paths/learning-path-provider.ts | 6 +- .../learning-paths/learning-path-service.ts | 6 +- backend/tests/setup-tests.ts | 6 +- backend/tool/seed.ts | 2 +- frontend/package.json | 5 +- 8 files changed, 41 insertions(+), 89 deletions(-) diff --git a/backend/src/controllers/learning-paths.ts b/backend/src/controllers/learning-paths.ts index 0097d568..5a450299 100644 --- a/backend/src/controllers/learning-paths.ts +++ b/backend/src/controllers/learning-paths.ts @@ -3,13 +3,10 @@ import { themes } from '../data/themes.js'; import { FALLBACK_LANG } from '../config.js'; import learningPathService from '../services/learning-paths/learning-path-service.js'; import { Language } from '@dwengo-1/common/util/language'; -import { - PersonalizationTarget, - personalizedForGroup, - personalizedForStudent, -} from '../services/learning-paths/learning-path-personalization-util.js'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; +import {Group} from "../entities/assignments/group.entity"; +import {getGroupRepository} from "../data/repositories"; /** * Fetch learning paths based on query parameters. @@ -20,20 +17,25 @@ export async function getLearningPaths(req: Request, res: Response): Promise { +async function convertLearningPath(learningPath: LearningPathEntity, order: number, personalizedFor?: Group): Promise { // Fetch the corresponding learning object for each node since some parts of the expected response contains parts // With information which is not available in the LearningPathNodes themselves. const nodesToLearningObjects: Map = await getLearningObjectsForNodes(learningPath.nodes); @@ -89,10 +90,10 @@ async function convertLearningPath(learningPath: LearningPathEntity, order: numb async function convertNode( node: LearningPathNode, learningObject: FilteredLearningObject, - personalizedFor: PersonalizationTarget | undefined, + personalizedFor: Group | undefined, nodesToLearningObjects: Map ): Promise { - const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null; + const lastSubmission = personalizedFor ? await getLastSubmissionForGroup(node, personalizedFor) : null; const transitions = node.transitions .filter( (trans) => @@ -121,7 +122,7 @@ async function convertNode( */ async function convertNodes( nodesToLearningObjects: Map, - personalizedFor?: PersonalizationTarget + personalizedFor?: Group ): Promise { const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects) @@ -181,7 +182,7 @@ const databaseLearningPathProvider: LearningPathProvider = { hruids: string[], language: Language, source: string, - personalizedFor?: PersonalizationTarget + personalizedFor?: Group ): Promise { const learningPathRepo = getLearningPathRepository(); @@ -202,7 +203,7 @@ const databaseLearningPathProvider: LearningPathProvider = { /** * Search learning paths in the database using the given search string. */ - async searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise { + async searchLearningPaths(query: string, language: Language, personalizedFor?: Group): Promise { const learningPathRepo = getLearningPathRepository(); const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); diff --git a/backend/src/services/learning-paths/learning-path-personalization-util.ts b/backend/src/services/learning-paths/learning-path-personalization-util.ts index a9175d13..a10d5ead 100644 --- a/backend/src/services/learning-paths/learning-path-personalization-util.ts +++ b/backend/src/services/learning-paths/learning-path-personalization-util.ts @@ -1,76 +1,22 @@ import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js'; -import { Student } from '../../entities/users/student.entity.js'; import { Group } from '../../entities/assignments/group.entity.js'; import { Submission } from '../../entities/assignments/submission.entity.js'; -import { getClassRepository, getGroupRepository, getStudentRepository, getSubmissionRepository } from '../../data/repositories.js'; +import { getSubmissionRepository } from '../../data/repositories.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js'; import { JSONPath } from 'jsonpath-plus'; -export type PersonalizationTarget = { type: 'student'; student: Student } | { type: 'group'; group: Group }; - /** - * Shortcut function to easily create a PersonalizationTarget object for a student by his/her username. - * @param username Username of the student we want to generate a personalized learning path for. - * If there is no student with this username, return undefined. + * Returns the last submission for the learning object associated with the given node and for the group */ -export async function personalizedForStudent(username: string): Promise { - const student = await getStudentRepository().findByUsername(username); - if (student) { - return { - type: 'student', - student: student, - }; - } - return undefined; -} - -/** - * Shortcut function to easily create a PersonalizationTarget object for a group by class name, assignment number and - * group number. - * @param classId Id of the class in which this group was created - * @param assignmentNumber Number of the assignment for which this group was created - * @param groupNumber Number of the group for which we want to personalize the learning path. - */ -export async function personalizedForGroup( - classId: string, - assignmentNumber: number, - groupNumber: number -): Promise { - const clazz = await getClassRepository().findById(classId); - if (!clazz) { - return undefined; - } - const group = await getGroupRepository().findOne({ - assignment: { - within: clazz, - id: assignmentNumber, - }, - groupNumber: groupNumber, - }); - if (group) { - return { - type: 'group', - group: group, - }; - } - return undefined; -} - -/** - * Returns the last submission for the learning object associated with the given node and for the student or group - */ -export async function getLastSubmissionForCustomizationTarget(node: LearningPathNode, pathFor: PersonalizationTarget): Promise { +export async function getLastSubmissionForGroup(node: LearningPathNode, pathFor: Group): Promise { const submissionRepo = getSubmissionRepository(); const learningObjectId: LearningObjectIdentifier = { hruid: node.learningObjectHruid, language: node.language, version: node.version, }; - if (pathFor.type === 'group') { - return await submissionRepo.findMostRecentSubmissionForGroup(learningObjectId, pathFor.group); - } - return await submissionRepo.findMostRecentSubmissionForStudent(learningObjectId, pathFor.student); + return await submissionRepo.findMostRecentSubmissionForGroup(learningObjectId, pathFor); } /** diff --git a/backend/src/services/learning-paths/learning-path-provider.ts b/backend/src/services/learning-paths/learning-path-provider.ts index 3bf734e7..bc067a41 100644 --- a/backend/src/services/learning-paths/learning-path-provider.ts +++ b/backend/src/services/learning-paths/learning-path-provider.ts @@ -1,6 +1,6 @@ import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; -import { PersonalizationTarget } from './learning-path-personalization-util.js'; import { Language } from '@dwengo-1/common/util/language'; +import { Group } from "../../entities/assignments/group.entity"; /** * Generic interface for a service which provides access to learning paths from a data source. @@ -9,10 +9,10 @@ export interface LearningPathProvider { /** * Fetch the learning paths with the given hruids from the data source. */ - fetchLearningPaths(hruids: string[], language: Language, source: string, personalizedFor?: PersonalizationTarget): Promise; + fetchLearningPaths(hruids: string[], language: Language, source: string, personalizedFor?: Group): Promise; /** * Search learning paths in the data source using the given search string. */ - searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise; + searchLearningPaths(query: string, language: Language, personalizedFor?: Group): Promise; } diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index 0e4d2c5e..73315769 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -1,9 +1,9 @@ import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; import databaseLearningPathProvider from './database-learning-path-provider.js'; import { envVars, getEnvVar } from '../../util/envVars.js'; -import { PersonalizationTarget } from './learning-path-personalization-util.js'; import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; import { Language } from '@dwengo-1/common/util/language'; +import {Group} from "../../entities/assignments/group.entity"; const userContentPrefix = getEnvVar(envVars.UserContentPrefix); const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; @@ -23,7 +23,7 @@ const learningPathService = { hruids: string[], language: Language, source: string, - personalizedFor?: PersonalizationTarget + personalizedFor?: Group ): Promise { const userContentHruids = hruids.filter((hruid) => hruid.startsWith(userContentPrefix)); const nonUserContentHruids = hruids.filter((hruid) => !hruid.startsWith(userContentPrefix)); @@ -48,7 +48,7 @@ const learningPathService = { /** * Search learning paths in the data source using the given search string. */ - async searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise { + async searchLearningPaths(query: string, language: Language, personalizedFor?: Group): Promise { const providerResponses = await Promise.all( allProviders.map(async (provider) => provider.searchLearningPaths(query, language, personalizedFor)) ); diff --git a/backend/tests/setup-tests.ts b/backend/tests/setup-tests.ts index 5bd2fbd6..0d71b434 100644 --- a/backend/tests/setup-tests.ts +++ b/backend/tests/setup-tests.ts @@ -13,6 +13,8 @@ import { makeTestAttachments } from './test_assets/content/attachments.testdata. import { makeTestQuestions } from './test_assets/questions/questions.testdata.js'; import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; +import {Collection} from "@mikro-orm/core"; +import {Group} from "../src/entities/assignments/group.entity"; export async function setupTestApp(): Promise { dotenv.config({ path: '.env.test' }); @@ -28,8 +30,8 @@ export async function setupTestApp(): Promise { const assignments = makeTestAssignemnts(em, classes); const groups = makeTestGroups(em, students, assignments); - assignments[0].groups = groups.slice(0, 3); - assignments[1].groups = groups.slice(3, 4); + assignments[0].groups = new Collection(groups.slice(0, 3)); + assignments[1].groups = new Collection(groups.slice(3, 4)); const teacherInvitations = makeTestTeacherInvitations(em, teachers, classes); const classJoinRequests = makeTestClassJoinRequests(em, students, classes); diff --git a/backend/tool/seed.ts b/backend/tool/seed.ts index 3ded9379..f1742b69 100644 --- a/backend/tool/seed.ts +++ b/backend/tool/seed.ts @@ -15,7 +15,7 @@ import { makeTestStudents } from '../tests/test_assets/users/students.testdata.j import { makeTestTeachers } from '../tests/test_assets/users/teachers.testdata.js'; import { getLogger, Logger } from '../src/logging/initalize.js'; import { Collection } from '@mikro-orm/core'; -import { Group } from '../dist/entities/assignments/group.entity.js'; +import { Group } from '../src/entities/assignments/group.entity'; const logger: Logger = getLogger(); diff --git a/frontend/package.json b/frontend/package.json index b6bd5deb..4fbfb0cf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,10 +10,11 @@ "preview": "vite preview", "type-check": "vue-tsc --build", "format": "prettier --write src/", + "test:e2e": "playwright test", "format-check": "prettier --check src/", "lint": "eslint . --fix", - "test:unit": "vitest --run", - "test:e2e": "playwright test" + "pretest:unit": "tsx ../docs/api/generate.ts && npm run build", + "test:unit": "vitest --run" }, "dependencies": { "@tanstack/react-query": "^5.69.0", From 202cf4e33c7b9f1c9a213c9ae3f04d22e1708647 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Tue, 15 Apr 2025 23:43:30 +0200 Subject: [PATCH 02/31] refactor(backend): Streamlining van de testdata voor leerpaden en leerobjecten + integratie in seed Hierbij ook testdata functionaliteit toegevoegd om makkelijk nieuwe leerpaden aan te maken. --- .../data/content/learning-path-repository.ts | 33 ++ .../content/learning-path-node.entity.ts | 4 +- .../entities/content/learning-path.entity.ts | 4 +- .../database-learning-path-provider.ts | 5 +- .../learning-paths/learning-path-service.ts | 79 +++- backend/src/util/base64-buffer-conversion.ts | 12 + .../learning-object-repository.test.ts | 52 ++- ...xample-learning-object-with-attachments.ts | 10 - .../learning-object-example.d.ts | 8 - .../learning-objects/test-essay/content.txt | 2 - .../learning-paths/learning-path-example.d.ts | 3 - .../learning-paths/learning-path-utils.ts | 36 -- .../learning-paths/pn-werking-example.ts | 30 -- .../learning-paths/test-conditions-example.ts | 80 ---- backend/tests/test-utils/expectations.ts | 55 ++- backend/tests/test-utils/load-test-asset.ts | 2 +- .../dummy/dummy-learning-object-example.ts | 10 +- .../dummy/rendering.txt | 0 .../pn-werkingnotebooks/Knop.png | Bin .../pn-werkingnotebooks/content.md | 0 .../pn-werkingnotebooks/dwengo.png | Bin .../pn-werkingnotebooks-example.ts | 16 +- .../pn-werkingnotebooks/rendering.txt | 0 .../test-essay/content.txt | 2 + .../test-essay/rendering.txt | 2 +- .../test-essay/test-essay-example.ts | 10 +- .../test-multiple-choice/content.txt | 2 +- .../test-multiple-choice/rendering.txt | 2 +- .../test-multiple-choice-example.ts | 10 +- .../content/learning-objects.testdata.ts | 367 ++++++++++++------ .../content/learning-paths.testdata.ts | 327 +++++++++++----- common/src/interfaces/learning-content.ts | 21 +- 32 files changed, 691 insertions(+), 493 deletions(-) create mode 100644 backend/src/util/base64-buffer-conversion.ts delete mode 100644 backend/tests/test-assets/learning-objects/create-example-learning-object-with-attachments.ts delete mode 100644 backend/tests/test-assets/learning-objects/learning-object-example.d.ts delete mode 100644 backend/tests/test-assets/learning-objects/test-essay/content.txt delete mode 100644 backend/tests/test-assets/learning-paths/learning-path-example.d.ts delete mode 100644 backend/tests/test-assets/learning-paths/learning-path-utils.ts delete mode 100644 backend/tests/test-assets/learning-paths/pn-werking-example.ts delete mode 100644 backend/tests/test-assets/learning-paths/test-conditions-example.ts rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/dummy/dummy-learning-object-example.ts (76%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/dummy/rendering.txt (100%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/pn-werkingnotebooks/Knop.png (100%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/pn-werkingnotebooks/content.md (100%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/pn-werkingnotebooks/dwengo.png (100%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/pn-werkingnotebooks/pn-werkingnotebooks-example.ts (80%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/pn-werkingnotebooks/rendering.txt (100%) create mode 100644 backend/tests/test_assets/content/learning-object-resources/test-essay/content.txt rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/test-essay/rendering.txt (68%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/test-essay/test-essay-example.ts (72%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/test-multiple-choice/content.txt (62%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/test-multiple-choice/rendering.txt (95%) rename backend/tests/{test-assets/learning-objects => test_assets/content/learning-object-resources}/test-multiple-choice/test-multiple-choice-example.ts (73%) diff --git a/backend/src/data/content/learning-path-repository.ts b/backend/src/data/content/learning-path-repository.ts index 87035f21..9db7c361 100644 --- a/backend/src/data/content/learning-path-repository.ts +++ b/backend/src/data/content/learning-path-repository.ts @@ -1,6 +1,10 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { LearningPath } from '../../entities/content/learning-path.entity.js'; import { Language } from '@dwengo-1/common/util/language'; +import {LearningPathNode} from "../../entities/content/learning-path-node.entity"; +import {RequiredEntityData} from "@mikro-orm/core"; +import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity"; +import {EntityAlreadyExistsException} from "../../exceptions/entity-already-exists-exception"; export class LearningPathRepository extends DwengoEntityRepository { public async findByHruidAndLanguage(hruid: string, language: Language): Promise { @@ -23,4 +27,33 @@ export class LearningPathRepository extends DwengoEntityRepository populate: ['nodes', 'nodes.transitions'], }); } + + public createNode( + nodeData: RequiredEntityData + ): LearningPathNode { + return this.em.create(LearningPathNode, nodeData); + } + + public createTransition( + transitionData: RequiredEntityData + ): LearningPathTransition { + return this.em.create(LearningPathTransition, transitionData) + } + + public async saveLearningPathNodesAndTransitions( + path: LearningPath, + nodes: LearningPathNode[], + transitions: LearningPathTransition[], + options?: {preventOverwrite?: boolean} + ): Promise { + if (options?.preventOverwrite && (await this.findOne(path))) { + throw new EntityAlreadyExistsException( + "A learning path with this hruid/language combination already exists." + ); + } + const em = this.getEntityManager(); + await em.persistAndFlush(path); + await Promise.all(nodes.map(it => em.persistAndFlush(it))); + await Promise.all(transitions.map(it => em.persistAndFlush(it))); + } } diff --git a/backend/src/entities/content/learning-path-node.entity.ts b/backend/src/entities/content/learning-path-node.entity.ts index 3016c367..8ad2e1f8 100644 --- a/backend/src/entities/content/learning-path-node.entity.ts +++ b/backend/src/entities/content/learning-path-node.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel } from '@mikro-orm/core'; +import {Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel} from '@mikro-orm/core'; import { LearningPath } from './learning-path.entity.js'; import { LearningPathTransition } from './learning-path-transition.entity.js'; import { Language } from '@dwengo-1/common/util/language'; @@ -27,7 +27,7 @@ export class LearningPathNode { startNode!: boolean; @OneToMany({ entity: () => LearningPathTransition, mappedBy: 'node' }) - transitions: LearningPathTransition[] = []; + transitions: Collection = new Collection(this); @Property({ length: 3 }) createdAt: Date = new Date(); diff --git a/backend/src/entities/content/learning-path.entity.ts b/backend/src/entities/content/learning-path.entity.ts index 203af86d..57d095b8 100644 --- a/backend/src/entities/content/learning-path.entity.ts +++ b/backend/src/entities/content/learning-path.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; +import {Collection, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property} from '@mikro-orm/core'; import { Teacher } from '../users/teacher.entity.js'; import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; import { LearningPathNode } from './learning-path-node.entity.js'; @@ -25,5 +25,5 @@ export class LearningPath { image: Buffer | null = null; @OneToMany({ entity: () => LearningPathNode, mappedBy: 'learningPath' }) - nodes: LearningPathNode[] = []; + nodes: Collection = new Collection(this); } diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index 8882ba94..e5b4389e 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -14,13 +14,14 @@ import { } from '@dwengo-1/common/interfaces/learning-content'; import { Language } from '@dwengo-1/common/util/language'; import {Group} from "../../entities/assignments/group.entity"; +import {Collection} from "@mikro-orm/core"; /** * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its * corresponding learning object. * @param nodes The nodes to find the learning object for. */ -async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise> { +async function getLearningObjectsForNodes(nodes: Collection): Promise> { // Fetching the corresponding learning object for each of the nodes and creating a map that maps each node to // Its corresponding learning object. const nullableNodesToLearningObjects = new Map( @@ -208,7 +209,7 @@ const databaseLearningPathProvider: LearningPathProvider = { const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); return await Promise.all(searchResults.map(async (result, index) => convertLearningPath(result, index, personalizedFor))); - }, + } }; export default databaseLearningPathProvider; diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index 73315769..18e28c22 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -1,13 +1,79 @@ import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; import databaseLearningPathProvider from './database-learning-path-provider.js'; import { envVars, getEnvVar } from '../../util/envVars.js'; -import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; +import {LearningObjectNode, LearningPath, LearningPathResponse} from '@dwengo-1/common/interfaces/learning-content'; import { Language } from '@dwengo-1/common/util/language'; import {Group} from "../../entities/assignments/group.entity"; +import {LearningPath as LearningPathEntity} from "../../entities/content/learning-path.entity"; +import {getLearningPathRepository} from "../../data/repositories"; +import {LearningPathNode} from "../../entities/content/learning-path-node.entity"; +import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity"; +import {base64ToArrayBuffer} from "../../util/base64-buffer-conversion"; +import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher"; +import {mapToTeacher} from "../../interfaces/teacher"; +import {Collection} from "@mikro-orm/core"; const userContentPrefix = getEnvVar(envVars.UserContentPrefix); const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; +export function mapToLearningPath( + dto: LearningPath, adminsDto: TeacherDTO[] +): LearningPathEntity { + const admins = adminsDto.map(admin => mapToTeacher(admin)); + const repo = getLearningPathRepository(); + const path = repo.create({ + hruid: dto.hruid, + language: dto.language as Language, + description: dto.description, + title: dto.title, + admins, + image: dto.image ? Buffer.from(base64ToArrayBuffer(dto.image)) : null + }); + const nodes = dto.nodes.map((nodeDto: LearningObjectNode, i: number) => + repo.createNode({ + learningPath: path, + learningObjectHruid: nodeDto.learningobject_hruid, + language: nodeDto.language, + version: nodeDto.version, + startNode: nodeDto.start_node ?? false, + nodeNumber: i, + createdAt: new Date(), + updatedAt: new Date() + }) + ); + dto.nodes.forEach(nodeDto => { + const fromNode = nodes.find(it => + it.learningObjectHruid === nodeDto.learningobject_hruid + && it.language === nodeDto.language + && it.version === nodeDto.version + )!; + const transitions = nodeDto.transitions.map((transDto, i) => { + const toNode = nodes.find(it => + it.learningObjectHruid === transDto.next.hruid + && it.language === transDto.next.language + && it.version === transDto.next.version + ); + + if (toNode) { + return repo.createTransition({ + transitionNumber: i, + node: fromNode, + next: toNode, + condition: transDto.condition ?? "true" + }); + } else { + return undefined; + } + }).filter(it => it).map(it => it!); + + fromNode.transitions = new Collection(transitions); + }); + + path.nodes = new Collection(nodes); + + return path; +} + /** * Service providing access to data about learning paths from the appropriate data source (database or Dwengo-api) */ @@ -54,6 +120,17 @@ const learningPathService = { ); return providerResponses.flat(); }, + + /** + * Add a new learning path to the database. + * @param dto Learning path DTO from which the learning path will be created. + * @param admins Teachers who should become an admin of the learning path. + */ + async createNewLearningPath(dto: LearningPath, admins: TeacherDTO[]): Promise { + const repo = getLearningPathRepository(); + const path = mapToLearningPath(dto, admins); + await repo.save(path, {preventOverwrite: true}) + } }; export default learningPathService; diff --git a/backend/src/util/base64-buffer-conversion.ts b/backend/src/util/base64-buffer-conversion.ts new file mode 100644 index 00000000..0453b604 --- /dev/null +++ b/backend/src/util/base64-buffer-conversion.ts @@ -0,0 +1,12 @@ +/** + * Convert a Base64-encoded string into a buffer with the same data. + * @param base64 The Base64 encoded string. + */ +export function base64ToArrayBuffer(base64: string) { + var binaryString = atob(base64); + var bytes = new Uint8Array(binaryString.length); + for (var i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes.buffer; +} diff --git a/backend/tests/data/content/learning-object-repository.test.ts b/backend/tests/data/content/learning-object-repository.test.ts index 12e14452..d99beb4b 100644 --- a/backend/tests/data/content/learning-object-repository.test.ts +++ b/backend/tests/data/content/learning-object-repository.test.ts @@ -2,48 +2,36 @@ import { beforeAll, describe, it, expect } from 'vitest'; import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; import { setupTestApp } from '../../setup-tests.js'; import { getLearningObjectRepository } from '../../../src/data/repositories.js'; -import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.js'; import { LearningObject } from '../../../src/entities/content/learning-object.entity.js'; import { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; +import { + testLearningObject01, + testLearningObject02, + testLearningObject03 +} from "../../test_assets/content/learning-objects.testdata"; describe('LearningObjectRepository', () => { let learningObjectRepository: LearningObjectRepository; - let exampleLearningObject: LearningObject; - beforeAll(async () => { await setupTestApp(); learningObjectRepository = getLearningObjectRepository(); }); - it('should be able to add a learning object to it without an error', async () => { - exampleLearningObject = example.createLearningObject(); - await learningObjectRepository.insert(exampleLearningObject); - }); - - it('should return the learning object when queried by id', async () => { + it('should return a learning object when queried by id', async () => { const result = await learningObjectRepository.findByIdentifier({ - hruid: exampleLearningObject.hruid, - language: exampleLearningObject.language, - version: exampleLearningObject.version, + hruid: testLearningObject01.hruid, + language: testLearningObject02.language, + version: testLearningObject03.version, }); expect(result).toBeInstanceOf(LearningObject); - expectToBeCorrectEntity( - { - name: 'actual', - entity: result!, - }, - { - name: 'expected', - entity: exampleLearningObject, - } - ); + expectToBeCorrectEntity(result!, testLearningObject01); }); it('should return null when non-existing version is queried', async () => { const result = await learningObjectRepository.findByIdentifier({ - hruid: exampleLearningObject.hruid, - language: exampleLearningObject.language, + hruid: testLearningObject01.hruid, + language: testLearningObject01.language, version: 100, }); expect(result).toBe(null); @@ -52,21 +40,27 @@ describe('LearningObjectRepository', () => { let newerExample: LearningObject; it('should allow a learning object with the same id except a different version to be added', async () => { - newerExample = example.createLearningObject(); - newerExample.version = 10; - newerExample.title += ' (nieuw)'; + let testLearningObject01Newer = structuredClone(testLearningObject01); + testLearningObject01Newer.version = 10; + testLearningObject01Newer.title += " (nieuw)"; + testLearningObject01Newer.content = Buffer.from("This is the new content."); + newerExample = learningObjectRepository.create(testLearningObject01Newer); await learningObjectRepository.save(newerExample); }); it('should return the newest version of the learning object when queried by only hruid and language', async () => { - const result = await learningObjectRepository.findLatestByHruidAndLanguage(newerExample.hruid, newerExample.language); + const result = await learningObjectRepository.findLatestByHruidAndLanguage( + newerExample.hruid, newerExample.language + ); expect(result).toBeInstanceOf(LearningObject); expect(result?.version).toBe(10); expect(result?.title).toContain('(nieuw)'); }); it('should return null when queried by non-existing hruid or language', async () => { - const result = await learningObjectRepository.findLatestByHruidAndLanguage('something_that_does_not_exist', exampleLearningObject.language); + const result = await learningObjectRepository.findLatestByHruidAndLanguage( + 'something_that_does_not_exist', testLearningObject01.language + ); expect(result).toBe(null); }); }); diff --git a/backend/tests/test-assets/learning-objects/create-example-learning-object-with-attachments.ts b/backend/tests/test-assets/learning-objects/create-example-learning-object-with-attachments.ts deleted file mode 100644 index 9bd0b4c3..00000000 --- a/backend/tests/test-assets/learning-objects/create-example-learning-object-with-attachments.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { LearningObjectExample } from './learning-object-example'; -import { LearningObject } from '../../../src/entities/content/learning-object.entity'; - -export function createExampleLearningObjectWithAttachments(example: LearningObjectExample): LearningObject { - const learningObject = example.createLearningObject(); - for (const creationFn of Object.values(example.createAttachment)) { - learningObject.attachments.push(creationFn(learningObject)); - } - return learningObject; -} diff --git a/backend/tests/test-assets/learning-objects/learning-object-example.d.ts b/backend/tests/test-assets/learning-objects/learning-object-example.d.ts deleted file mode 100644 index 3054644f..00000000 --- a/backend/tests/test-assets/learning-objects/learning-object-example.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { LearningObject } from '../../../src/entities/content/learning-object.entity'; -import { Attachment } from '../../../src/entities/content/attachment.entity'; - -interface LearningObjectExample { - createLearningObject: () => LearningObject; - createAttachment: Record Attachment>; - getHTMLRendering: () => string; -} diff --git a/backend/tests/test-assets/learning-objects/test-essay/content.txt b/backend/tests/test-assets/learning-objects/test-essay/content.txt deleted file mode 100644 index dd6b5c77..00000000 --- a/backend/tests/test-assets/learning-objects/test-essay/content.txt +++ /dev/null @@ -1,2 +0,0 @@ -::MC basic:: -How are you? {} diff --git a/backend/tests/test-assets/learning-paths/learning-path-example.d.ts b/backend/tests/test-assets/learning-paths/learning-path-example.d.ts deleted file mode 100644 index d8e94dc8..00000000 --- a/backend/tests/test-assets/learning-paths/learning-path-example.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -interface LearningPathExample { - createLearningPath: () => LearningPath; -} diff --git a/backend/tests/test-assets/learning-paths/learning-path-utils.ts b/backend/tests/test-assets/learning-paths/learning-path-utils.ts deleted file mode 100644 index 177d905f..00000000 --- a/backend/tests/test-assets/learning-paths/learning-path-utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Language } from '@dwengo-1/common/util/language'; -import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity'; -import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; -import { LearningPath } from '../../../src/entities/content/learning-path.entity'; - -export function createLearningPathTransition( - node: LearningPathNode, - transitionNumber: number, - condition: string | null, - to: LearningPathNode -): LearningPathTransition { - const trans = new LearningPathTransition(); - trans.node = node; - trans.transitionNumber = transitionNumber; - trans.condition = condition || 'true'; - trans.next = to; - return trans; -} - -export function createLearningPathNode( - learningPath: LearningPath, - nodeNumber: number, - learningObjectHruid: string, - version: number, - language: Language, - startNode: boolean -): LearningPathNode { - const node = new LearningPathNode(); - node.learningPath = learningPath; - node.nodeNumber = nodeNumber; - node.learningObjectHruid = learningObjectHruid; - node.version = version; - node.language = language; - node.startNode = startNode; - return node; -} diff --git a/backend/tests/test-assets/learning-paths/pn-werking-example.ts b/backend/tests/test-assets/learning-paths/pn-werking-example.ts deleted file mode 100644 index 1ac1c40d..00000000 --- a/backend/tests/test-assets/learning-paths/pn-werking-example.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { LearningPath } from '../../../src/entities/content/learning-path.entity'; -import { Language } from '@dwengo-1/common/util/language'; -import { envVars, getEnvVar } from '../../../src/util/envVars'; -import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; -import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; - -function createNodes(learningPath: LearningPath): LearningPathNode[] { - const nodes = [ - createLearningPathNode(learningPath, 0, 'u_pn_werkingnotebooks', 3, Language.Dutch, true), - createLearningPathNode(learningPath, 1, 'pn_werkingnotebooks2', 3, Language.Dutch, false), - createLearningPathNode(learningPath, 2, 'pn_werkingnotebooks3', 3, Language.Dutch, false), - ]; - nodes[0].transitions.push(createLearningPathTransition(nodes[0], 0, 'true', nodes[1])); - nodes[1].transitions.push(createLearningPathTransition(nodes[1], 0, 'true', nodes[2])); - return nodes; -} - -const example: LearningPathExample = { - createLearningPath: () => { - const path = new LearningPath(); - path.language = Language.Dutch; - path.hruid = `${getEnvVar(envVars.UserContentPrefix)}pn_werking`; - 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.nodes = createNodes(path); - return path; - }, -}; - -export default example; diff --git a/backend/tests/test-assets/learning-paths/test-conditions-example.ts b/backend/tests/test-assets/learning-paths/test-conditions-example.ts deleted file mode 100644 index 0fb7ead5..00000000 --- a/backend/tests/test-assets/learning-paths/test-conditions-example.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { LearningPath } from '../../../src/entities/content/learning-path.entity'; -import { Language } from '@dwengo-1/common/util/language'; -import testMultipleChoiceExample from '../learning-objects/test-multiple-choice/test-multiple-choice-example'; -import { dummyLearningObject } from '../learning-objects/dummy/dummy-learning-object-example'; -import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; -import { LearningObject } from '../../../src/entities/content/learning-object.entity'; -import { envVars, getEnvVar } from '../../../src/util/envVars'; - -export interface ConditionTestLearningPathAndLearningObjects { - branchingObject: LearningObject; - extraExerciseObject: LearningObject; - finalObject: LearningObject; - learningPath: LearningPath; -} - -export function createConditionTestLearningPathAndLearningObjects(): ConditionTestLearningPathAndLearningObjects { - const learningPath = new LearningPath(); - learningPath.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_conditions`; - learningPath.language = Language.English; - learningPath.title = 'Example learning path with conditional transitions'; - learningPath.description = 'This learning path was made for the purpose of testing conditional transitions'; - - const branchingLearningObject = testMultipleChoiceExample.createLearningObject(); - const extraExerciseLearningObject = dummyLearningObject( - 'test_extra_exercise', - Language.English, - 'Extra exercise (for students with difficulties)' - ).createLearningObject(); - const finalLearningObject = dummyLearningObject( - 'test_final_learning_object', - Language.English, - 'Final exercise (for everyone)' - ).createLearningObject(); - - const branchingNode = createLearningPathNode( - learningPath, - 0, - branchingLearningObject.hruid, - branchingLearningObject.version, - branchingLearningObject.language, - true - ); - const extraExerciseNode = createLearningPathNode( - learningPath, - 1, - extraExerciseLearningObject.hruid, - extraExerciseLearningObject.version, - extraExerciseLearningObject.language, - false - ); - const finalNode = createLearningPathNode( - learningPath, - 2, - finalLearningObject.hruid, - finalLearningObject.version, - finalLearningObject.language, - false - ); - - const transitionToExtraExercise = createLearningPathTransition( - branchingNode, - 0, - '$[?(@[0] == 0)]', // The answer to the first question was the first one, which says that it is difficult for the student to follow along. - extraExerciseNode - ); - const directTransitionToFinal = createLearningPathTransition(branchingNode, 1, '$[?(@[0] == 1)]', finalNode); - const transitionExtraExerciseToFinal = createLearningPathTransition(extraExerciseNode, 0, 'true', finalNode); - - branchingNode.transitions = [transitionToExtraExercise, directTransitionToFinal]; - extraExerciseNode.transitions = [transitionExtraExerciseToFinal]; - - learningPath.nodes = [branchingNode, extraExerciseNode, finalNode]; - - return { - branchingObject: branchingLearningObject, - finalObject: finalLearningObject, - extraExerciseObject: extraExerciseLearningObject, - learningPath: learningPath, - }; -} diff --git a/backend/tests/test-utils/expectations.ts b/backend/tests/test-utils/expectations.ts index b6462702..0ee0eff8 100644 --- a/backend/tests/test-utils/expectations.ts +++ b/backend/tests/test-utils/expectations.ts @@ -11,52 +11,45 @@ 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 + * @param propertyPrefix Prefix to append to property in error messages. */ -export function expectToBeCorrectEntity(actual: { entity: T; name?: string }, expected: { entity: T; name?: string }): void { - if (!actual.name) { - actual.name = 'actual'; - } - if (!expected.name) { - expected.name = 'expected'; - } - for (const property in expected.entity) { +export function expectToBeCorrectEntity( + actual: T, + expected: T, + propertyPrefix: string = "" +): void { + for (const property in expected) { + const prefixedProperty = propertyPrefix + property; 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 + expected[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[property] !== 'function' // Functions obviously are not persisted via the database ) { - if (!Object.prototype.hasOwnProperty.call(actual.entity, property)) { + if (!Object.prototype.hasOwnProperty.call(actual, property)) { throw new AssertionError({ - message: `${expected.name} has defined property ${property}, but ${actual.name} is missing it.`, + message: `Expected property ${prefixedProperty}, but it is missing.`, }); } - if (typeof expected.entity[property] === 'boolean') { + if (typeof expected[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])) { + if (Boolean(expected[property]) !== Boolean(actual[property])) { throw new AssertionError({ - message: `${property} was ${expected.entity[property]} in ${expected.name}, - but ${actual.entity[property]} (${Boolean(expected.entity[property])}) in ${actual.name}`, + message: `Expected ${prefixedProperty} to be ${expected[property]}, + but was ${actual[property]} (${Boolean(expected[property])}).`, }); } - } else if (typeof expected.entity[property] !== typeof actual.entity[property]) { + } else if (typeof expected[property] !== typeof actual[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: `${prefixedProperty} was expected to have type ${typeof expected[property]},` + + `but had type ${typeof actual[property]}.`, }); - } 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, - } - ); + } else if (typeof expected[property] === 'object') { + expectToBeCorrectEntity(actual[property] as object, expected[property] as object, property); } else { - if (expected.entity[property] !== actual.entity[property]) { + if (expected[property] !== actual[property]) { throw new AssertionError({ - message: `${property} was ${expected.entity[property]} in ${expected.name}, but ${actual.entity[property]} in ${actual.name}`, + message: `${prefixedProperty} was expected to be ${expected[property]}, ` + + `but was ${actual[property]}.`, }); } } diff --git a/backend/tests/test-utils/load-test-asset.ts b/backend/tests/test-utils/load-test-asset.ts index 35f6cdbf..2a4a7786 100644 --- a/backend/tests/test-utils/load-test-asset.ts +++ b/backend/tests/test-utils/load-test-asset.ts @@ -6,5 +6,5 @@ import path from 'node:path'; * @param relPath Path of the asset relative to the test-assets folder. */ export function loadTestAsset(relPath: string): Buffer { - return fs.readFileSync(path.resolve(__dirname, `../test-assets/${relPath}`)); + return fs.readFileSync(path.resolve(__dirname, `../test_assets/${relPath}`)); } diff --git a/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts b/backend/tests/test_assets/content/learning-object-resources/dummy/dummy-learning-object-example.ts similarity index 76% rename from backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts rename to backend/tests/test_assets/content/learning-object-resources/dummy/dummy-learning-object-example.ts index 6889c93b..e7645622 100644 --- a/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts +++ b/backend/tests/test_assets/content/learning-object-resources/dummy/dummy-learning-object-example.ts @@ -1,9 +1,9 @@ import { LearningObjectExample } from '../learning-object-example'; -import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; -import { Language } from '@dwengo-1/common/util/language'; -import { loadTestAsset } from '../../../test-utils/load-test-asset'; -import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; -import { envVars, getEnvVar } from '../../../../src/util/envVars'; +import { LearningObject } from '../../../../../src/entities/content/learning-object.entity'; +import { Language } from '@dwengo-1/common/dist/util/language'; +import { loadTestAsset } from '../../../../test-utils/load-test-asset'; +import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type'; +import { envVars, getEnvVar } from '../../../../../src/util/envVars'; /** * Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use diff --git a/backend/tests/test-assets/learning-objects/dummy/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/dummy/rendering.txt similarity index 100% rename from backend/tests/test-assets/learning-objects/dummy/rendering.txt rename to backend/tests/test_assets/content/learning-object-resources/dummy/rendering.txt diff --git a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/Knop.png b/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/Knop.png similarity index 100% rename from backend/tests/test-assets/learning-objects/pn-werkingnotebooks/Knop.png rename to backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/Knop.png diff --git a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/content.md b/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/content.md similarity index 100% rename from backend/tests/test-assets/learning-objects/pn-werkingnotebooks/content.md rename to backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/content.md diff --git a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/dwengo.png b/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/dwengo.png similarity index 100% rename from backend/tests/test-assets/learning-objects/pn-werkingnotebooks/dwengo.png rename to backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/dwengo.png diff --git a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts b/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/pn-werkingnotebooks-example.ts similarity index 80% rename from backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts rename to backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/pn-werkingnotebooks-example.ts index ab0c8640..3737d551 100644 --- a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts +++ b/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/pn-werkingnotebooks-example.ts @@ -1,12 +1,12 @@ import { LearningObjectExample } from '../learning-object-example'; -import { Language } from '@dwengo-1/common/util/language'; -import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; -import { loadTestAsset } from '../../../test-utils/load-test-asset'; -import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; -import { Attachment } from '../../../../src/entities/content/attachment.entity'; -import { envVars, getEnvVar } from '../../../../src/util/envVars'; -import { EducationalGoal } from '../../../../src/entities/content/educational-goal.entity'; -import { ReturnValue } from '../../../../src/entities/content/return-value.entity'; +import { Language } from '@dwengo-1/common/dist/util/language'; +import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type'; +import { loadTestAsset } from '../../../../test-utils/load-test-asset'; +import { LearningObject } from '../../../../../src/entities/content/learning-object.entity'; +import { Attachment } from '../../../../../src/entities/content/attachment.entity'; +import { envVars, getEnvVar } from '../../../../../src/util/envVars'; +import { EducationalGoal } from '../../../../../src/entities/content/educational-goal.entity'; +import { ReturnValue } from '../../../../../src/entities/content/return-value.entity'; const ASSETS_PREFIX = 'learning-objects/pn-werkingnotebooks/'; diff --git a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/rendering.txt similarity index 100% rename from backend/tests/test-assets/learning-objects/pn-werkingnotebooks/rendering.txt rename to backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/rendering.txt diff --git a/backend/tests/test_assets/content/learning-object-resources/test-essay/content.txt b/backend/tests/test_assets/content/learning-object-resources/test-essay/content.txt new file mode 100644 index 00000000..aa634cbd --- /dev/null +++ b/backend/tests/test_assets/content/learning-object-resources/test-essay/content.txt @@ -0,0 +1,2 @@ +::MC basic:: +Reflect on this learning path. What have you learned today? {} diff --git a/backend/tests/test-assets/learning-objects/test-essay/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/test-essay/rendering.txt similarity index 68% rename from backend/tests/test-assets/learning-objects/test-essay/rendering.txt rename to backend/tests/test_assets/content/learning-object-resources/test-essay/rendering.txt index adb072a0..94a5ab79 100644 --- a/backend/tests/test-assets/learning-objects/test-essay/rendering.txt +++ b/backend/tests/test_assets/content/learning-object-resources/test-essay/rendering.txt @@ -1,7 +1,7 @@

MC basic

-

How are you?

+

Reflect on this learning path. What have you learned today?

diff --git a/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts b/backend/tests/test_assets/content/learning-object-resources/test-essay/test-essay-example.ts similarity index 72% rename from backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts rename to backend/tests/test_assets/content/learning-object-resources/test-essay/test-essay-example.ts index 5a444fc0..1f95fc2c 100644 --- a/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts +++ b/backend/tests/test_assets/content/learning-object-resources/test-essay/test-essay-example.ts @@ -1,9 +1,9 @@ import { LearningObjectExample } from '../learning-object-example'; -import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; -import { loadTestAsset } from '../../../test-utils/load-test-asset'; -import { envVars, getEnvVar } from '../../../../src/util/envVars'; -import { Language } from '@dwengo-1/common/util/language'; -import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; +import { LearningObject } from '../../../../../src/entities/content/learning-object.entity'; +import { loadTestAsset } from '../../../../test-utils/load-test-asset'; +import { envVars, getEnvVar } from '../../../../../src/util/envVars'; +import { Language } from '@dwengo-1/common/dist/util/language'; +import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type'; const example: LearningObjectExample = { createLearningObject: () => { diff --git a/backend/tests/test-assets/learning-objects/test-multiple-choice/content.txt b/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/content.txt similarity index 62% rename from backend/tests/test-assets/learning-objects/test-multiple-choice/content.txt rename to backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/content.txt index 7dd5527d..98b7c9c3 100644 --- a/backend/tests/test-assets/learning-objects/test-multiple-choice/content.txt +++ b/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/content.txt @@ -1,5 +1,5 @@ ::MC basic:: -Are you following along well with the class? { +Are you following along well? { ~No, it's very difficult to follow along. =Yes, no problem! } diff --git a/backend/tests/test-assets/learning-objects/test-multiple-choice/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/rendering.txt similarity index 95% rename from backend/tests/test-assets/learning-objects/test-multiple-choice/rendering.txt rename to backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/rendering.txt index c1829f24..9c56f40b 100644 --- a/backend/tests/test-assets/learning-objects/test-multiple-choice/rendering.txt +++ b/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/rendering.txt @@ -1,7 +1,7 @@

MC basic

-

Are you following along well with the class?

+

Are you following along well?

diff --git a/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts b/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/test-multiple-choice-example.ts similarity index 73% rename from backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts rename to backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/test-multiple-choice-example.ts index 129665ae..277ab486 100644 --- a/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts +++ b/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/test-multiple-choice-example.ts @@ -1,9 +1,9 @@ import { LearningObjectExample } from '../learning-object-example'; -import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; -import { loadTestAsset } from '../../../test-utils/load-test-asset'; -import { envVars, getEnvVar } from '../../../../src/util/envVars'; -import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; -import { Language } from '@dwengo-1/common/util/language'; +import { LearningObject } from '../../../../../src/entities/content/learning-object.entity'; +import { loadTestAsset } from '../../../../test-utils/load-test-asset'; +import { envVars, getEnvVar } from '../../../../../src/util/envVars'; +import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type'; +import { Language } from '@dwengo-1/common/dist/util/language'; const example: LearningObjectExample = { createLearningObject: () => { diff --git a/backend/tests/test_assets/content/learning-objects.testdata.ts b/backend/tests/test_assets/content/learning-objects.testdata.ts index 6e28dc16..4b33c85a 100644 --- a/backend/tests/test_assets/content/learning-objects.testdata.ts +++ b/backend/tests/test_assets/content/learning-objects.testdata.ts @@ -1,135 +1,252 @@ -import { EntityManager } from '@mikro-orm/core'; -import { LearningObject } from '../../../src/entities/content/learning-object.entity'; -import { Language } from '@dwengo-1/common/util/language'; -import { DwengoContentType } from '../../../src/services/learning-objects/processing/content-type'; -import { ReturnValue } from '../../../src/entities/content/return-value.entity'; +import {EntityManager, RequiredEntityData} from '@mikro-orm/core'; +import {LearningObject} from '../../../src/entities/content/learning-object.entity'; +import {Language} from '@dwengo-1/common/util/language'; +import {DwengoContentType} from '../../../src/services/learning-objects/processing/content-type'; +import {ReturnValue} from '../../../src/entities/content/return-value.entity'; +import {envVars, getEnvVar} from "../../../src/util/envVars"; +import {loadTestAsset} from "../../test-utils/load-test-asset"; export function makeTestLearningObjects(em: EntityManager): LearningObject[] { const returnValue: ReturnValue = new ReturnValue(); returnValue.callbackSchema = ''; returnValue.callbackUrl = ''; - const learningObject01 = em.create(LearningObject, { - hruid: 'id01', - language: Language.English, - version: 1, - admins: [], - title: 'Undertow', - description: 'debute', - contentType: DwengoContentType.TEXT_MARKDOWN, - keywords: [], - teacherExclusive: false, - skosConcepts: [], - educationalGoals: [], - copyright: '', - license: '', - estimatedTime: 45, - returnValue: returnValue, - available: true, - contentLocation: '', - attachments: [], - content: Buffer.from("there's a shadow just behind me, shrouding every step i take, making every promise empty pointing every finger at me"), - }); + const learningObject01 = em.create(LearningObject, testLearningObject01); + const learningObject02 = em.create(LearningObject, testLearningObject02); + const learningObject03 = em.create(LearningObject, testLearningObject03); + const learningObject04 = em.create(LearningObject, testLearningObject04); + const learningObject05 = em.create(LearningObject, testLearningObject05); - const learningObject02 = em.create(LearningObject, { - hruid: 'id02', - language: Language.English, - version: 1, - admins: [], - title: 'Aenema', - description: 'second album', - contentType: DwengoContentType.TEXT_MARKDOWN, - keywords: [], - teacherExclusive: false, - skosConcepts: [], - educationalGoals: [], - copyright: '', - license: '', - estimatedTime: 80, - returnValue: returnValue, - available: true, - contentLocation: '', - attachments: [], - content: Buffer.from( - "I've been crawling on my belly clearing out what could've been I've been wallowing in my own confused and insecure delusions" - ), - }); + const learningObjectMultipleChoice = em.create(LearningObject, testLearningObjectMultipleChoice); + const learningObjectEssayQuestion= em.create(LearningObject, testLearningObjectEssayQuestion); - const learningObject03 = em.create(LearningObject, { - hruid: 'id03', - language: Language.English, - version: 1, - admins: [], - title: 'love over gold', - description: 'third album', - contentType: DwengoContentType.TEXT_MARKDOWN, - keywords: [], - teacherExclusive: false, - skosConcepts: [], - educationalGoals: [], - copyright: '', - license: '', - estimatedTime: 55, - returnValue: returnValue, - available: true, - contentLocation: '', - attachments: [], - content: Buffer.from( - 'he wrote me a prescription, he said you are depressed, \ - but I am glad you came to see me to get this off your chest, \ - come back and see me later next patient please \ - send in another victim of industrial disease' - ), - }); + const learningObjectPnNotebooks = em.create(LearningObject, testLearningObjectPnNotebooks); - const learningObject04 = em.create(LearningObject, { - hruid: 'id04', - language: Language.English, - version: 1, - admins: [], - title: 'making movies', - description: 'fifth album', - contentType: DwengoContentType.TEXT_MARKDOWN, - keywords: [], - teacherExclusive: false, - skosConcepts: [], - educationalGoals: [], - copyright: '', - license: '', - estimatedTime: 55, - returnValue: returnValue, - available: true, - contentLocation: '', - attachments: [], - content: Buffer.from( - 'I put my hand upon the lever \ - Said let it rock and let it roll \ - I had the one-arm bandit fever \ - There was an arrow through my heart and my soul' - ), - }); - - const learningObject05 = em.create(LearningObject, { - hruid: 'id05', - language: Language.English, - version: 1, - admins: [], - title: 'on every street', - description: 'sixth album', - contentType: DwengoContentType.TEXT_MARKDOWN, - keywords: [], - teacherExclusive: false, - skosConcepts: [], - educationalGoals: [], - copyright: '', - license: '', - estimatedTime: 55, - returnValue: returnValue, - available: true, - contentLocation: '', - attachments: [], - content: Buffer.from('calling Elvis, is anybody home, calling elvis, I am here all alone'), - }); - - return [learningObject01, learningObject02, learningObject03, learningObject04, learningObject05]; + return [ + learningObject01, learningObject02, learningObject03, learningObject04, learningObject05, + learningObjectMultipleChoice, learningObjectEssayQuestion, learningObjectPnNotebooks + ]; +} + +export function createReturnValue(): ReturnValue { + const returnValue: ReturnValue = new ReturnValue(); + returnValue.callbackSchema = ''; + returnValue.callbackUrl = ''; + return returnValue; +} + +export const testLearningObject01: RequiredEntityData = { + hruid: 'id01', + language: Language.English, + version: 1, + admins: [], + title: 'Undertow', + description: 'debute', + contentType: DwengoContentType.TEXT_MARKDOWN, + keywords: [], + teacherExclusive: false, + skosConcepts: [], + educationalGoals: [], + copyright: '', + license: '', + estimatedTime: 45, + returnValue: createReturnValue(), + available: true, + contentLocation: '', + attachments: [], + content: Buffer.from("there's a shadow just behind me, shrouding every step i take, making every promise empty pointing every finger at me"), +}; + +export const testLearningObject02: RequiredEntityData = { + hruid: 'id02', + language: Language.English, + version: 1, + admins: [], + title: 'Aenema', + description: 'second album', + contentType: DwengoContentType.TEXT_MARKDOWN, + keywords: [], + teacherExclusive: false, + skosConcepts: [], + educationalGoals: [], + copyright: '', + license: '', + estimatedTime: 80, + returnValue: createReturnValue(), + available: true, + contentLocation: '', + attachments: [], + content: Buffer.from( + "I've been crawling on my belly clearing out what could've been I've been wallowing in my own confused and insecure delusions" + ), +}; + +export const testLearningObject03: RequiredEntityData = { + hruid: 'id03', + language: Language.English, + version: 1, + admins: [], + title: 'love over gold', + description: 'third album', + contentType: DwengoContentType.TEXT_MARKDOWN, + keywords: [], + teacherExclusive: false, + skosConcepts: [], + educationalGoals: [], + copyright: '', + license: '', + estimatedTime: 55, + returnValue: createReturnValue(), + available: true, + contentLocation: '', + attachments: [], + content: Buffer.from( + 'he wrote me a prescription, he said you are depressed, \ + but I am glad you came to see me to get this off your chest, \ + come back and see me later next patient please \ + send in another victim of industrial disease' + ), +}; + +export const testLearningObject04: RequiredEntityData = { + hruid: 'id04', + language: Language.English, + version: 1, + admins: [], + title: 'making movies', + description: 'fifth album', + contentType: DwengoContentType.TEXT_MARKDOWN, + keywords: [], + teacherExclusive: false, + skosConcepts: [], + educationalGoals: [], + copyright: '', + license: '', + estimatedTime: 55, + returnValue: createReturnValue(), + available: true, + contentLocation: '', + attachments: [], + content: Buffer.from( + 'I put my hand upon the lever \ + Said let it rock and let it roll \ + I had the one-arm bandit fever \ + There was an arrow through my heart and my soul' + ), +}; + +export const testLearningObject05: RequiredEntityData = { + hruid: 'id05', + language: Language.English, + version: 1, + admins: [], + title: 'on every street', + description: 'sixth album', + contentType: DwengoContentType.TEXT_MARKDOWN, + keywords: [], + teacherExclusive: false, + skosConcepts: [], + educationalGoals: [], + copyright: '', + license: '', + estimatedTime: 55, + returnValue: createReturnValue(), + available: true, + contentLocation: '', + attachments: [], + content: Buffer.from('calling Elvis, is anybody home, calling elvis, I am here all alone'), +}; + +export const testLearningObjectMultipleChoice: RequiredEntityData = { + hruid: `${getEnvVar(envVars.UserContentPrefix)}test_multiple_choice`, + language: Language.English, + version: 1, + title: "Self-evaluation", + description: "Time to evaluate how well you understand what you've learned so far.", + keywords: ["test"], + teacherExclusive: false, + skosConcepts: [], + educationalGoals: [], + copyright: "Groep 1 SEL-2 2025", + license: "CC0", + difficulty: 1, + estimatedTime: 1, + attachments: [], + available: true, + targetAges: [10, 11, 12, 13, 14, 15, 16, 17, 18], + admins: [], + contentType: DwengoContentType.GIFT, + content: loadTestAsset('content/learning-object-resources/test-multiple-choice/content.txt'), + returnValue: { + callbackUrl: `%SUBMISSION%`, + callbackSchema: '["antwoord vraag 1"]', + } +}; + +export const testLearningObjectEssayQuestion: RequiredEntityData = { + hruid: `${getEnvVar(envVars.UserContentPrefix)}test_essay_question`, + language: Language.English, + version: 1, + title: "Reflection", + description: "Reflect on your learning progress.", + keywords: ["test"], + teacherExclusive: false, + skosConcepts: [], + educationalGoals: [], + copyright: "Groep 1 SEL-2 2025", + license: "CC0", + difficulty: 1, + estimatedTime: 1, + attachments: [], + available: true, + targetAges: [10, 11, 12, 13, 14, 15, 16, 17, 18], + admins: [], + contentType: DwengoContentType.GIFT, + content: loadTestAsset('content/learning-object-resources/test-essay/content.txt'), + returnValue: { + callbackUrl: `%SUBMISSION%`, + callbackSchema: '["antwoord vraag 1"]', + } +}; + +export const testLearningObjectPnNotebooks: RequiredEntityData = { + hruid: `${getEnvVar(envVars.UserContentPrefix)}pn_werkingnotebooks`, + version: 3, + language: Language.Dutch, + title: "Werken met notebooks", + description: "Leren werken met notebooks", + keywords: ["Python", "KIKS", "Wiskunde", "STEM", "AI"], + targetAges: [14, 15, 16, 17, 18], + admins: [], + copyright: "dwengo", + educationalGoals: [], + license: "dwengo", + contentType: DwengoContentType.TEXT_MARKDOWN, + difficulty: 3, + estimatedTime: 10, + uuid: "2adf9929-b424-4650-bf60-186f730d38ab", + teacherExclusive: false, + skosConcepts: [ + "http://ilearn.ilabt.imec.be/vocab/curr1/s-vaktaal", + "http://ilearn.ilabt.imec.be/vocab/curr1/s-digitale-media-en-toepassingen", + "http://ilearn.ilabt.imec.be/vocab/curr1/s-computers-en-systemen", + ], + attachments: [ + { + name: "dwengo.png", + mimeType: "image/png", + content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/dwengo.png") + }, + { + name: "Knop.png", + mimeType: "image/png", + content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/Knop.png") + } + ], + available: false, + content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/content.md"), + returnValue: { + callbackUrl: "%SUBMISSION%", + callbackSchema: "[]" + } } diff --git a/backend/tests/test_assets/content/learning-paths.testdata.ts b/backend/tests/test_assets/content/learning-paths.testdata.ts index 72581f42..117f0341 100644 --- a/backend/tests/test_assets/content/learning-paths.testdata.ts +++ b/backend/tests/test_assets/content/learning-paths.testdata.ts @@ -1,100 +1,237 @@ -import { EntityManager } from '@mikro-orm/core'; -import { LearningPath } from '../../../src/entities/content/learning-path.entity'; -import { Language } from '@dwengo-1/common/util/language'; -import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity'; -import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; +import {EntityManager} from '@mikro-orm/core'; +import {LearningPath} from '../../../src/entities/content/learning-path.entity'; +import {Language} from '@dwengo-1/common/util/language'; +import {mapToLearningPath} from "../../../src/services/learning-paths/learning-path-service"; +import {envVars, getEnvVar} from "../../../src/util/envVars"; +import {LearningPath as LearningPathDTO} from "@dwengo-1/common/interfaces/learning-content"; +import { + testLearningObject01, testLearningObject02, testLearningObject03, testLearningObject04, testLearningObject05, + testLearningObjectEssayQuestion, + testLearningObjectMultipleChoice, testLearningObjectPnNotebooks +} from "./learning-objects.testdata"; -export function makeTestLearningPaths(em: EntityManager): LearningPath[] { - const learningPathNode01: LearningPathNode = new LearningPathNode(); - const learningPathNode02: LearningPathNode = new LearningPathNode(); - const learningPathNode03: LearningPathNode = new LearningPathNode(); - const learningPathNode04: LearningPathNode = new LearningPathNode(); - const learningPathNode05: LearningPathNode = new LearningPathNode(); +export function makeTestLearningPaths(_em: EntityManager): LearningPath[] { + const learningPath01 = mapToLearningPath(testLearningPath01, []); + const learningPath02 = mapToLearningPath(testLearningPath02, []); - const transitions01: LearningPathTransition = new LearningPathTransition(); - const transitions02: LearningPathTransition = new LearningPathTransition(); - const transitions03: LearningPathTransition = new LearningPathTransition(); - const transitions04: LearningPathTransition = new LearningPathTransition(); - const transitions05: LearningPathTransition = new LearningPathTransition(); + const partiallyDatabasePartiallyDwengoApiLearningPath + = mapToLearningPath(testPartiallyDatabaseAndPartiallyDwengoApiLearningPath, []) + const learningPathWithConditions = mapToLearningPath(testLearningPathWithConditions, []) - transitions01.condition = 'true'; - transitions01.next = learningPathNode02; - - transitions02.condition = 'true'; - transitions02.next = learningPathNode02; - - transitions03.condition = 'true'; - transitions03.next = learningPathNode04; - - transitions04.condition = 'true'; - transitions04.next = learningPathNode05; - - transitions05.condition = 'true'; - transitions05.next = learningPathNode05; - - learningPathNode01.instruction = ''; - learningPathNode01.language = Language.English; - learningPathNode01.learningObjectHruid = 'id01'; - learningPathNode01.startNode = true; - learningPathNode01.transitions = [transitions01]; - learningPathNode01.version = 1; - - learningPathNode02.instruction = ''; - learningPathNode02.language = Language.English; - learningPathNode02.learningObjectHruid = 'id02'; - learningPathNode02.startNode = false; - learningPathNode02.transitions = [transitions02]; - learningPathNode02.version = 1; - - learningPathNode03.instruction = ''; - learningPathNode03.language = Language.English; - learningPathNode03.learningObjectHruid = 'id03'; - learningPathNode03.startNode = true; - learningPathNode03.transitions = [transitions03]; - learningPathNode03.version = 1; - - learningPathNode04.instruction = ''; - learningPathNode04.language = Language.English; - learningPathNode04.learningObjectHruid = 'id04'; - learningPathNode04.startNode = false; - learningPathNode04.transitions = [transitions04]; - learningPathNode04.version = 1; - - learningPathNode05.instruction = ''; - learningPathNode05.language = Language.English; - learningPathNode05.learningObjectHruid = 'id05'; - learningPathNode05.startNode = false; - learningPathNode05.transitions = [transitions05]; - learningPathNode05.version = 1; - - const nodes01: LearningPathNode[] = [ - // LearningPathNode01, - // LearningPathNode02, + return [ + learningPath01, + learningPath02, + partiallyDatabasePartiallyDwengoApiLearningPath, + learningPathWithConditions ]; - const learningPath01 = em.create(LearningPath, { - hruid: 'id01', - language: Language.English, - admins: [], - title: 'repertoire Tool', - description: 'all about Tool', - image: null, - nodes: nodes01, - }); - - const nodes02: LearningPathNode[] = [ - // LearningPathNode03, - // LearningPathNode04, - // LearningPathNode05, - ]; - const learningPath02 = em.create(LearningPath, { - hruid: 'id02', - language: Language.English, - admins: [], - title: 'repertoire Dire Straits', - description: 'all about Dire Straits', - image: null, - nodes: nodes02, - }); - - return [learningPath01, learningPath02]; +} + +const nowString = new Date().toString(); + +export const testLearningPath01: LearningPathDTO = { + keywords: "test", + target_ages: [16, 17, 18], + hruid: "id01", + language: Language.English, + title: "repertoire Tool", + description: "all about Tool", + nodes: [ + { + learningobject_hruid: testLearningObject01.hruid, + language: testLearningObject01.language, + version: testLearningObject01.version, + start_node: true, + created_at: nowString, + updatedAt: nowString, + transitions: [ + { + next: { + hruid: testLearningObject02.hruid, + language: testLearningObject02.language, + version: testLearningObject02.version + } + } + ] + }, + { + learningobject_hruid: testLearningObject02.hruid, + language: testLearningObject02.language, + version: testLearningObject02.version, + created_at: nowString, + updatedAt: nowString, + transitions: [] + } + ] +}; + +export const testLearningPath02: LearningPathDTO = { + keywords: "test", + target_ages: [16, 17, 18], + hruid: "id02", + language: Language.English, + title: "repertoire Dire Straits", + description: "all about Dire Straits", + nodes: [ + { + learningobject_hruid: testLearningObject03.hruid, + language: testLearningObject03.language, + version: testLearningObject03.version, + start_node: true, + created_at: nowString, + updatedAt: nowString, + transitions: [ + { + next: { + hruid: testLearningObject04.hruid, + language: testLearningObject04.language, + version: testLearningObject04.version + } + } + ] + }, + { + learningobject_hruid: testLearningObject04.hruid, + language: testLearningObject04.language, + version: testLearningObject04.version, + created_at: nowString, + updatedAt: nowString, + transitions: [ + { + next: { + hruid: testLearningObject05.hruid, + language: testLearningObject05.language, + version: testLearningObject05.version + } + } + ] + }, + { + learningobject_hruid: testLearningObject05.hruid, + language: testLearningObject05.language, + version: testLearningObject05.version, + created_at: nowString, + updatedAt: nowString, + transitions: [] + } + ] +}; + +export const testPartiallyDatabaseAndPartiallyDwengoApiLearningPath: LearningPathDTO = { + hruid: `${getEnvVar(envVars.UserContentPrefix)}pn_werking`, + title: "Werken met notebooks", + language: Language.Dutch, + description: 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiƫnt met de notebooks aan de slag?', + keywords: "Python KIKS Wiskunde STEM AI", + target_ages: [14, 15, 16, 17, 18], + nodes: [ + { + learningobject_hruid: testLearningObjectPnNotebooks.hruid, + language: testLearningObjectPnNotebooks.language, + version: testLearningObjectPnNotebooks.version, + start_node: true, + created_at: nowString, + updatedAt: nowString, + transitions: [ + { + default: true, + next: { + hruid: "pn_werkingnotebooks2", + language: Language.Dutch, + version: 3 + } + } + ] + }, + { + learningobject_hruid: "pn_werkingnotebooks2", + language: Language.Dutch, + version: 3, + created_at: nowString, + updatedAt: nowString, + transitions: [ + { + default: true, + next: { + hruid: "pn_werkingnotebooks3", + language: Language.Dutch, + version: 3 + } + } + ] + }, + { + learningobject_hruid: "pn_werkingnotebooks3", + language: Language.Dutch, + version: 3, + created_at: nowString, + updatedAt: nowString, + transitions: [] + } + ] +} + +export const testLearningPathWithConditions: LearningPathDTO = { + hruid: `${getEnvVar(envVars.UserContentPrefix)}test_conditions`, + language: Language.English, + title: 'Example learning path with conditional transitions', + description: 'This learning path was made for the purpose of testing conditional transitions', + keywords: "test", + target_ages: [18, 19, 20, 21], + nodes: [ + { + learningobject_hruid: testLearningObjectMultipleChoice.hruid, + language: testLearningObjectMultipleChoice.language, + version: testLearningObjectMultipleChoice.version, + start_node: true, + created_at: nowString, + updatedAt: nowString, + transitions: [ + { + // If the answer to the first question was the first one (It's difficult to follow along): + condition: '$[?(@[0] == 0)]', + next: { + //... we let the student do an extra exercise. + hruid: testLearningObject01.hruid, + language: testLearningObject01.language, + version: testLearningObject01.version + } + }, + { + // If the answer to the first question was the second one (I can follow along): + condition: '$[?(@[0] == 1)]', + next: { + //... we let the student right through to the final question. + hruid: testLearningObjectEssayQuestion.hruid, + language: testLearningObjectEssayQuestion.language, + version: testLearningObjectEssayQuestion.version + } + } + ] + }, + { + learningobject_hruid: testLearningObject01.hruid, + language: testLearningObject01.language, + version: testLearningObject01.version, + created_at: nowString, + updatedAt: nowString, + transitions: [ + { + default: true, + next: { + hruid: testLearningObjectEssayQuestion.hruid, + language: testLearningObjectEssayQuestion.language, + version: testLearningObjectEssayQuestion.version + } + } + ] + }, + { + learningobject_hruid: testLearningObjectEssayQuestion.hruid, + language: testLearningObjectEssayQuestion.language, + version: testLearningObjectEssayQuestion.version, + created_at: nowString, + updatedAt: nowString, + transitions: [] + } + ] } diff --git a/common/src/interfaces/learning-content.ts b/common/src/interfaces/learning-content.ts index 435e7001..f580b00c 100644 --- a/common/src/interfaces/learning-content.ts +++ b/common/src/interfaces/learning-content.ts @@ -9,6 +9,7 @@ export interface Transition { version: number; language: string; }; + condition?: string; } export interface LearningObjectIdentifierDTO { @@ -18,7 +19,7 @@ export interface LearningObjectIdentifierDTO { } export interface LearningObjectNode { - _id: string; + _id?: string; learningobject_hruid: string; version: number; language: Language; @@ -30,20 +31,20 @@ export interface LearningObjectNode { } export interface LearningPath { - _id: string; + _id?: string; language: string; hruid: string; title: string; description: string; image?: string; // Image might be missing, so it's optional - num_nodes: number; - num_nodes_left: number; + num_nodes?: number; + num_nodes_left?: number; nodes: LearningObjectNode[]; keywords: string; target_ages: number[]; - min_age: number; - max_age: number; - __order: number; + min_age?: number; + max_age?: number; + __order?: number; } export interface LearningPathIdentifier { @@ -62,8 +63,8 @@ export interface ReturnValue { } export interface LearningObjectMetadata { - _id: string; - uuid: string; + _id?: string; + uuid?: string; hruid: string; version: number; language: Language; @@ -84,7 +85,7 @@ export interface LearningObjectMetadata { export interface FilteredLearningObject { key: string; - _id: string; + _id?: string; uuid: string; version: number; title: string; From a3be3699f20f9bab9fc5011f8d419953ad23a599 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 00:47:42 +0200 Subject: [PATCH 03/31] fix(backend): Testen AssignmentRepository gerepareerd na refactoring. --- .../content/attachment-repository.test.ts | 72 +++++++++---------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/backend/tests/data/content/attachment-repository.test.ts b/backend/tests/data/content/attachment-repository.test.ts index b76c9016..af128177 100644 --- a/backend/tests/data/content/attachment-repository.test.ts +++ b/backend/tests/data/content/attachment-repository.test.ts @@ -2,66 +2,60 @@ import { beforeAll, describe, expect, it } from 'vitest'; import { setupTestApp } from '../../setup-tests.js'; import { getAttachmentRepository, getLearningObjectRepository } from '../../../src/data/repositories.js'; import { AttachmentRepository } from '../../../src/data/content/attachment-repository.js'; -import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; -import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.js'; import { LearningObject } from '../../../src/entities/content/learning-object.entity.js'; import { Attachment } from '../../../src/entities/content/attachment.entity.js'; import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier.js'; - -const NEWER_TEST_SUFFIX = 'nEweR'; - -async function createTestLearningObjects(learningObjectRepo: LearningObjectRepository): Promise<{ - older: LearningObject; - newer: LearningObject; -}> { - const olderExample = example.createLearningObject(); - await learningObjectRepo.save(olderExample); - - const newerExample = example.createLearningObject(); - newerExample.title = 'Newer example'; - newerExample.version = 100; - - return { - older: olderExample, - newer: newerExample, - }; -} +import {testLearningObjectPnNotebooks} from "../../test_assets/content/learning-objects.testdata"; +import {v4 as uuidV4} from "uuid"; describe('AttachmentRepository', () => { let attachmentRepo: AttachmentRepository; - let exampleLearningObjects: { older: LearningObject; newer: LearningObject }; + let newLearningObject: LearningObject; let attachmentsOlderLearningObject: Attachment[]; beforeAll(async () => { await setupTestApp(); + + attachmentsOlderLearningObject = testLearningObjectPnNotebooks.attachments as Attachment[]; + attachmentRepo = getAttachmentRepository(); - exampleLearningObjects = await createTestLearningObjects(getLearningObjectRepository()); - }); + const learningObjectRepo = getLearningObjectRepository(); - it('can add attachments to learning objects without throwing an error', async () => { - attachmentsOlderLearningObject = Object.values(example.createAttachment).map((fn) => fn(exampleLearningObjects.older)); + const newLearningObjectData = structuredClone(testLearningObjectPnNotebooks); + newLearningObjectData.title = 'Newer example'; + newLearningObjectData.version = 101; + newLearningObjectData.attachments = []; + newLearningObjectData.uuid = uuidV4(); + newLearningObjectData.content = Buffer.from("Content of the newer example"); - await Promise.all(attachmentsOlderLearningObject.map(async (attachment) => attachmentRepo.save(attachment))); + newLearningObject = learningObjectRepo.create(newLearningObjectData); + await learningObjectRepo.save(newLearningObject); }); let attachmentOnlyNewer: Attachment; it('allows us to add attachments with the same name to a different learning object without throwing an error', async () => { - attachmentOnlyNewer = Object.values(example.createAttachment)[0](exampleLearningObjects.newer); - attachmentOnlyNewer.content.write(NEWER_TEST_SUFFIX); + attachmentOnlyNewer = structuredClone(attachmentsOlderLearningObject[0]); + attachmentOnlyNewer.learningObject = newLearningObject; + attachmentOnlyNewer.content = Buffer.from("New attachment content"); - await attachmentRepo.save(attachmentOnlyNewer); + await attachmentRepo.save(attachmentRepo.create(attachmentOnlyNewer)); }); let olderLearningObjectId: LearningObjectIdentifier; it('returns the correct attachment when queried by learningObjectId and attachment name', async () => { olderLearningObjectId = { - hruid: exampleLearningObjects.older.hruid, - language: exampleLearningObjects.older.language, - version: exampleLearningObjects.older.version, + hruid: testLearningObjectPnNotebooks.hruid, + language: testLearningObjectPnNotebooks.language, + version: testLearningObjectPnNotebooks.version, }; - const result = await attachmentRepo.findByLearningObjectIdAndName(olderLearningObjectId, attachmentsOlderLearningObject[0].name); - expect(result).toBe(attachmentsOlderLearningObject[0]); + const result = await attachmentRepo.findByLearningObjectIdAndName( + olderLearningObjectId, + attachmentsOlderLearningObject[0].name + ); + expect(result).not.toBeNull(); + expect(result!.name).toEqual(attachmentsOlderLearningObject[0].name); + expect(result!.content).toEqual(attachmentsOlderLearningObject[0].content); }); it('returns null when queried by learningObjectId and non-existing attachment name', async () => { @@ -71,10 +65,12 @@ describe('AttachmentRepository', () => { it('returns the newer version of the attachment when only queried by hruid, language and attachment name (but not version)', async () => { const result = await attachmentRepo.findByMostRecentVersionOfLearningObjectAndName( - exampleLearningObjects.older.hruid, - exampleLearningObjects.older.language, + testLearningObjectPnNotebooks.hruid, + testLearningObjectPnNotebooks.language, attachmentOnlyNewer.name ); - expect(result).toBe(attachmentOnlyNewer); + expect(result).not.toBeNull(); + expect(result!.name).toEqual(attachmentOnlyNewer.name); + expect(result!.content).toEqual(attachmentOnlyNewer.content); }); }); From ee9afab6cabdba406ecda3c3af0facac1e479a0d Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 00:55:43 +0200 Subject: [PATCH 04/31] fix(backend): Testen LearningPathRepository gerepareerd na refactoring. --- .../content/learning-path-repository.test.ts | 41 ++++++++----------- backend/tests/test-utils/expectations.ts | 17 ++++++++ 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/backend/tests/data/content/learning-path-repository.test.ts b/backend/tests/data/content/learning-path-repository.test.ts index bdf5377e..370eec2a 100644 --- a/backend/tests/data/content/learning-path-repository.test.ts +++ b/backend/tests/data/content/learning-path-repository.test.ts @@ -2,41 +2,34 @@ import { beforeAll, describe, expect, it } from 'vitest'; import { setupTestApp } from '../../setup-tests.js'; import { getLearningPathRepository } from '../../../src/data/repositories.js'; import { LearningPathRepository } from '../../../src/data/content/learning-path-repository.js'; -import example from '../../test-assets/learning-paths/pn-werking-example.js'; import { LearningPath } from '../../../src/entities/content/learning-path.entity.js'; -import { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; +import { + expectToBeCorrectEntity, + expectToHaveFoundNothing, + expectToHaveFoundPrecisely +} from '../../test-utils/expectations.js'; import { Language } from '@dwengo-1/common/util/language'; - -function expectToHaveFoundPrecisely(expected: LearningPath, result: LearningPath[]): void { - expect(result).toHaveProperty('length'); - expect(result.length).toBe(1); - expectToBeCorrectEntity({ entity: result[0] }, { entity: expected }); -} - -function expectToHaveFoundNothing(result: LearningPath[]): void { - expect(result).toHaveProperty('length'); - expect(result.length).toBe(0); -} +import {testLearningPath01} from "../../test_assets/content/learning-paths.testdata"; +import {mapToLearningPath} from "../../../src/services/learning-paths/learning-path-service"; describe('LearningPathRepository', () => { let learningPathRepo: LearningPathRepository; + let examplePath: LearningPath; beforeAll(async () => { await setupTestApp(); learningPathRepo = getLearningPathRepository(); + + examplePath = mapToLearningPath(testLearningPath01, []); }); - let examplePath: LearningPath; - - it('should be able to add a learning path without throwing an error', async () => { - examplePath = example.createLearningPath(); - await learningPathRepo.insert(examplePath); - }); - - it('should return the added path when it is queried by hruid and language', async () => { - const result = await learningPathRepo.findByHruidAndLanguage(examplePath.hruid, examplePath.language); + it('should return a learning path when it is queried by hruid and language', async () => { + const result = await learningPathRepo.findByHruidAndLanguage( + testLearningPath01.hruid, + testLearningPath01.language as Language + ); expect(result).toBeInstanceOf(LearningPath); - expectToBeCorrectEntity({ entity: result! }, { entity: examplePath }); + expectToBeCorrectEntity(result!, examplePath); }); it('should return null to a query on a non-existing hruid or language', async () => { @@ -45,7 +38,7 @@ describe('LearningPathRepository', () => { }); it('should return the learning path when we search for a search term occurring in its title', async () => { - const result = await learningPathRepo.findByQueryStringAndLanguage(examplePath.title.slice(4, 9), examplePath.language); + const result = await learningPathRepo.findByQueryStringAndLanguage(examplePath.title.slice(9, 13), examplePath.language); expectToHaveFoundPrecisely(examplePath, result); }); diff --git a/backend/tests/test-utils/expectations.ts b/backend/tests/test-utils/expectations.ts index 0ee0eff8..3a2e2283 100644 --- a/backend/tests/test-utils/expectations.ts +++ b/backend/tests/test-utils/expectations.ts @@ -141,3 +141,20 @@ export function expectToBeCorrectLearningPath( expect(new Set(node.transitions.map((it) => it.next.version))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.version))); } } + +/** + * Expect that the given result is a singleton list with exactly the given element. + */ +export function expectToHaveFoundPrecisely(expected: T, result: T[]): void { + expect(result).toHaveProperty('length'); + expect(result.length).toBe(1); + expectToBeCorrectEntity(result[0], expected); +} + +/** + * Expect that the given result is an empty list. + */ +export function expectToHaveFoundNothing(result: T[]): void { + expect(result).toHaveProperty('length'); + expect(result.length).toBe(0); +} From 51268af79c8593104617d5ffe0dfb4a88f96bbae Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 07:58:55 +0200 Subject: [PATCH 05/31] fix(backend): Testen DatabaseLearningObjectProvider gerepareerd na refactoring. --- .../content/learning-object.entity.ts | 4 +- .../content/learning-path-node.entity.ts | 2 +- backend/src/services/learning-objects.ts | 3 +- .../database-learning-object-provider.ts | 3 +- .../dwengo-api-learning-object-provider.ts | 3 +- .../database-learning-path-provider.ts | 3 +- .../learning-paths/learning-path-service.ts | 7 +- .../database-learning-object-provider.test.ts | 59 +++++++------- backend/tests/test-utils/expectations.ts | 5 +- .../tests/test-utils/get-html-rendering.ts | 12 +++ .../dummy/dummy-learning-object-example.ts | 32 -------- .../pn-werkingnotebooks-example.ts | 74 ------------------ .../Knop.png | Bin .../content.md | 0 .../dwengo.png | Bin .../rendering.txt | 0 .../test-essay/test-essay-example.ts | 28 ------- .../test-multiple-choice-example.ts | 28 ------- .../content/learning-objects.testdata.ts | 13 +-- common/src/interfaces/learning-content.ts | 6 +- 20 files changed, 72 insertions(+), 210 deletions(-) create mode 100644 backend/tests/test-utils/get-html-rendering.ts delete mode 100644 backend/tests/test_assets/content/learning-object-resources/dummy/dummy-learning-object-example.ts delete mode 100644 backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/pn-werkingnotebooks-example.ts rename backend/tests/test_assets/content/learning-object-resources/{pn-werkingnotebooks => pn_werkingnotebooks}/Knop.png (100%) rename backend/tests/test_assets/content/learning-object-resources/{pn-werkingnotebooks => pn_werkingnotebooks}/content.md (100%) rename backend/tests/test_assets/content/learning-object-resources/{pn-werkingnotebooks => pn_werkingnotebooks}/dwengo.png (100%) rename backend/tests/test_assets/content/learning-object-resources/{pn-werkingnotebooks => pn_werkingnotebooks}/rendering.txt (100%) delete mode 100644 backend/tests/test_assets/content/learning-object-resources/test-essay/test-essay-example.ts delete mode 100644 backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/test-multiple-choice-example.ts diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index ff858fe6..ac111906 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -1,4 +1,4 @@ -import { Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; +import {ArrayType, Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property} from '@mikro-orm/core'; import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js'; @@ -42,7 +42,7 @@ export class LearningObject { @Property({ type: 'array' }) keywords: string[] = []; - @Property({ type: 'array', nullable: true }) + @Property({ type: new ArrayType(i => +i), nullable: true }) targetAges?: number[] = []; @Property({ type: 'bool' }) diff --git a/backend/src/entities/content/learning-path-node.entity.ts b/backend/src/entities/content/learning-path-node.entity.ts index 8ad2e1f8..818799b7 100644 --- a/backend/src/entities/content/learning-path-node.entity.ts +++ b/backend/src/entities/content/learning-path-node.entity.ts @@ -9,7 +9,7 @@ export class LearningPathNode { learningPath!: Rel; @PrimaryKey({ type: 'integer', autoincrement: true }) - nodeNumber!: number; + nodeNumber?: number; @Property({ type: 'string' }) learningObjectHruid!: string; diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index 436c4a08..146a7664 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -8,12 +8,13 @@ import { LearningPathResponse, } from '@dwengo-1/common/interfaces/learning-content'; import { getLogger } from '../logging/initalize.js'; +import {v4} from "uuid"; function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject { return { key: data.hruid, // Hruid learningObject (not path) _id: data._id, - uuid: data.uuid, + uuid: data.uuid || v4(), version: data.version, title: data.title, htmlUrl, // Url to fetch html content diff --git a/backend/src/services/learning-objects/database-learning-object-provider.ts b/backend/src/services/learning-objects/database-learning-object-provider.ts index fa278bba..712802b3 100644 --- a/backend/src/services/learning-objects/database-learning-object-provider.ts +++ b/backend/src/services/learning-objects/database-learning-object-provider.ts @@ -32,7 +32,8 @@ function convertLearningObject(learningObject: LearningObject | null): FilteredL educationalGoals: learningObject.educationalGoals, returnValue: { callback_url: learningObject.returnValue.callbackUrl, - callback_schema: JSON.parse(learningObject.returnValue.callbackSchema), + callback_schema: learningObject.returnValue.callbackSchema === "" ? "" + : JSON.parse(learningObject.returnValue.callbackSchema), }, skosConcepts: learningObject.skosConcepts, targetAges: learningObject.targetAges || [], diff --git a/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts b/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts index e9898f62..a3863505 100644 --- a/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts +++ b/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts @@ -11,6 +11,7 @@ import { LearningPathIdentifier, LearningPathResponse, } from '@dwengo-1/common/interfaces/learning-content'; +import {v4} from "uuid"; const logger: Logger = getLogger(); @@ -23,7 +24,7 @@ function filterData(data: LearningObjectMetadata): FilteredLearningObject { return { key: data.hruid, // Hruid learningObject (not path) _id: data._id, - uuid: data.uuid, + uuid: data.uuid ?? v4(), version: data.version, title: data.title, htmlUrl: `/learningObject/${data.hruid}/html?language=${data.language}&version=${data.version}`, // Url to fetch html content diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index e5b4389e..b1e04129 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -15,6 +15,7 @@ import { import { Language } from '@dwengo-1/common/util/language'; import {Group} from "../../entities/assignments/group.entity"; import {Collection} from "@mikro-orm/core"; +import {v4} from "uuid"; /** * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its @@ -163,7 +164,7 @@ function convertTransition( _id: String(index), // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path. default: false, // We don't work with default transitions but retain this for backwards compatibility. next: { - _id: nextNode._id + index, // Construct a unique ID for the transition for backwards compatibility. + _id: nextNode._id ? (nextNode._id + index) : v4(), // Construct a unique ID for the transition for backwards compatibility. hruid: transition.next.learningObjectHruid, language: nextNode.language, version: nextNode.version, diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index 18e28c22..1f99fa84 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -29,14 +29,13 @@ export function mapToLearningPath( admins, image: dto.image ? Buffer.from(base64ToArrayBuffer(dto.image)) : null }); - const nodes = dto.nodes.map((nodeDto: LearningObjectNode, i: number) => + const nodes = dto.nodes.map((nodeDto: LearningObjectNode) => repo.createNode({ learningPath: path, learningObjectHruid: nodeDto.learningobject_hruid, language: nodeDto.language, version: nodeDto.version, startNode: nodeDto.start_node ?? false, - nodeNumber: i, createdAt: new Date(), updatedAt: new Date() }) @@ -66,10 +65,10 @@ export function mapToLearningPath( } }).filter(it => it).map(it => it!); - fromNode.transitions = new Collection(transitions); + fromNode.transitions = new Collection(fromNode, transitions); }); - path.nodes = new Collection(nodes); + path.nodes = new Collection(path, nodes); return path; } diff --git a/backend/tests/services/learning-objects/database-learning-object-provider.test.ts b/backend/tests/services/learning-objects/database-learning-object-provider.test.ts index 31899ded..b9e2a737 100644 --- a/backend/tests/services/learning-objects/database-learning-object-provider.test.ts +++ b/backend/tests/services/learning-objects/database-learning-object-provider.test.ts @@ -1,43 +1,44 @@ import { beforeAll, describe, expect, it } from 'vitest'; import { setupTestApp } from '../../setup-tests'; -import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; -import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; import { LearningObject } from '../../../src/entities/content/learning-object.entity'; import databaseLearningObjectProvider from '../../../src/services/learning-objects/database-learning-object-provider'; import { expectToBeCorrectFilteredLearningObject } from '../../test-utils/expectations'; import { Language } from '@dwengo-1/common/util/language'; -import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; -import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; -import { LearningPath } from '../../../src/entities/content/learning-path.entity'; -import { FilteredLearningObject } from '@dwengo-1/common/interfaces/learning-content'; - -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 }; -} +import { + FilteredLearningObject, + LearningObjectNode, + LearningPathIdentifier +} from '@dwengo-1/common/interfaces/learning-content'; +import { + testPartiallyDatabaseAndPartiallyDwengoApiLearningPath +} from "../../test_assets/content/learning-paths.testdata"; +import {testLearningObjectPnNotebooks} from "../../test_assets/content/learning-objects.testdata"; +import { LearningPath } from '@dwengo-1/common/dist/interfaces/learning-content'; +import {RequiredEntityData} from "@mikro-orm/core"; +import {getHtmlRenderingForTestLearningObject} from "../../test-utils/get-html-rendering"; const EXPECTED_TITLE_FROM_DWENGO_LEARNING_OBJECT = 'Notebook opslaan'; describe('DatabaseLearningObjectProvider', () => { - let exampleLearningObject: LearningObject; + let exampleLearningObject: RequiredEntityData; let exampleLearningPath: LearningPath; + let exampleLearningPathId: LearningPathIdentifier; beforeAll(async () => { await setupTestApp(); - const exampleData = await initExampleData(); - exampleLearningObject = exampleData.learningObject; - exampleLearningPath = exampleData.learningPath; + exampleLearningObject = testLearningObjectPnNotebooks; + exampleLearningPath = testPartiallyDatabaseAndPartiallyDwengoApiLearningPath; + + exampleLearningPathId = { + hruid: exampleLearningPath.hruid, + language: exampleLearningPath.language as Language + }; }); describe('getLearningObjectById', () => { it('should return the learning object when it is queried by its id', async () => { const result: FilteredLearningObject | null = await databaseLearningObjectProvider.getLearningObjectById(exampleLearningObject); expect(result).toBeTruthy(); - expectToBeCorrectFilteredLearningObject(result, exampleLearningObject); + expectToBeCorrectFilteredLearningObject(result!, exampleLearningObject); }); it('should return the learning object when it is queried by only hruid and language (but not version)', async () => { @@ -46,7 +47,7 @@ describe('DatabaseLearningObjectProvider', () => { language: exampleLearningObject.language, }); expect(result).toBeTruthy(); - expectToBeCorrectFilteredLearningObject(result, exampleLearningObject); + expectToBeCorrectFilteredLearningObject(result!, exampleLearningObject); }); it('should return null when queried with an id that does not exist', async () => { @@ -61,7 +62,7 @@ describe('DatabaseLearningObjectProvider', () => { it('should return the correct rendering of the learning object', async () => { const result = await databaseLearningObjectProvider.getLearningObjectHTML(exampleLearningObject); // Set newlines so your tests are platform-independent. - expect(result).toEqual(example.getHTMLRendering().replace(/\r\n/g, '\n')); + expect(result).toEqual(getHtmlRenderingForTestLearningObject(exampleLearningObject).replace(/\r\n/g, '\n')); }); it('should return null for a non-existing learning object', async () => { const result = await databaseLearningObjectProvider.getLearningObjectHTML({ @@ -73,8 +74,10 @@ describe('DatabaseLearningObjectProvider', () => { }); describe('getLearningObjectIdsFromPath', () => { it('should return all learning object IDs from a path', async () => { - const result = await databaseLearningObjectProvider.getLearningObjectIdsFromPath(exampleLearningPath); - expect(new Set(result)).toEqual(new Set(exampleLearningPath.nodes.map((it) => it.learningObjectHruid))); + const result = await databaseLearningObjectProvider.getLearningObjectIdsFromPath(exampleLearningPathId); + expect(new Set(result)).toEqual( + new Set(exampleLearningPath.nodes.map((it: LearningObjectNode) => it.learningobject_hruid)) + ); }); it('should throw an error if queried with a path identifier for which there is no learning path', async () => { await expect( @@ -89,9 +92,11 @@ describe('DatabaseLearningObjectProvider', () => { }); describe('getLearningObjectsFromPath', () => { it('should correctly return all learning objects which are on the path, even those who are not in the database', async () => { - const result = await databaseLearningObjectProvider.getLearningObjectsFromPath(exampleLearningPath); + const result = await databaseLearningObjectProvider.getLearningObjectsFromPath(exampleLearningPathId); expect(result.length).toBe(exampleLearningPath.nodes.length); - expect(new Set(result.map((it) => it.key))).toEqual(new Set(exampleLearningPath.nodes.map((it) => it.learningObjectHruid))); + expect(new Set(result.map((it) => it.key))).toEqual( + new Set(exampleLearningPath.nodes.map((it: LearningObjectNode) => it.learningobject_hruid)) + ); expect(result.map((it) => it.title)).toContainEqual(EXPECTED_TITLE_FROM_DWENGO_LEARNING_OBJECT); }); diff --git a/backend/tests/test-utils/expectations.ts b/backend/tests/test-utils/expectations.ts index 3a2e2283..b6d01069 100644 --- a/backend/tests/test-utils/expectations.ts +++ b/backend/tests/test-utils/expectations.ts @@ -3,6 +3,7 @@ import { LearningObject } from '../../src/entities/content/learning-object.entit import { LearningPath as LearningPathEntity } from '../../src/entities/content/learning-path.entity'; import { expect } from 'vitest'; import { FilteredLearningObject, LearningPath } from '@dwengo-1/common/interfaces/learning-content'; +import {RequiredEntityData} from "@mikro-orm/core"; // Ignored properties because they belang for example to the class, not to the entity itself. const IGNORE_PROPERTIES = ['parent']; @@ -60,9 +61,9 @@ export function expectToBeCorrectEntity( /** * Checks that filtered is the correct representation of original as FilteredLearningObject. * @param filtered the representation as FilteredLearningObject - * @param original the original entity added to the database + * @param original the data of the entity in the database that was filtered. */ -export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearningObject, original: LearningObject): void { +export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearningObject, original: RequiredEntityData): void { expect(filtered.uuid).toEqual(original.uuid); expect(filtered.version).toEqual(original.version); expect(filtered.language).toEqual(original.language); diff --git a/backend/tests/test-utils/get-html-rendering.ts b/backend/tests/test-utils/get-html-rendering.ts new file mode 100644 index 00000000..28d24ed2 --- /dev/null +++ b/backend/tests/test-utils/get-html-rendering.ts @@ -0,0 +1,12 @@ +import {RequiredEntityData} from "@mikro-orm/core"; +import {loadTestAsset} from "./load-test-asset"; +import {LearningObject} from "../../src/entities/content/learning-object.entity"; +import {envVars, getEnvVar} from "../../src/util/envVars"; + +export function getHtmlRenderingForTestLearningObject(learningObject: RequiredEntityData): string { + const userPrefix = getEnvVar(envVars.UserContentPrefix); + const cleanedHruid = learningObject.hruid.startsWith(userPrefix) + ? learningObject.hruid.substring(userPrefix.length) + : learningObject.hruid; + return loadTestAsset(`/content/learning-object-resources/${cleanedHruid}/rendering.txt`).toString(); +} diff --git a/backend/tests/test_assets/content/learning-object-resources/dummy/dummy-learning-object-example.ts b/backend/tests/test_assets/content/learning-object-resources/dummy/dummy-learning-object-example.ts deleted file mode 100644 index e7645622..00000000 --- a/backend/tests/test_assets/content/learning-object-resources/dummy/dummy-learning-object-example.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { LearningObjectExample } from '../learning-object-example'; -import { LearningObject } from '../../../../../src/entities/content/learning-object.entity'; -import { Language } from '@dwengo-1/common/dist/util/language'; -import { loadTestAsset } from '../../../../test-utils/load-test-asset'; -import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type'; -import { envVars, getEnvVar } from '../../../../../src/util/envVars'; - -/** - * Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use - * on a path), but where the precise contents of the learning object are not important. - */ -export function dummyLearningObject(hruid: string, language: Language, title: string): LearningObjectExample { - return { - createLearningObject: (): LearningObject => { - const learningObject = new LearningObject(); - learningObject.hruid = getEnvVar(envVars.UserContentPrefix) + hruid; - learningObject.language = language; - learningObject.version = 1; - learningObject.title = title; - learningObject.description = 'Just a dummy learning object for testing purposes'; - learningObject.contentType = DwengoContentType.TEXT_PLAIN; - learningObject.content = Buffer.from('Dummy content'); - learningObject.returnValue = { - callbackUrl: `/learningObject/${hruid}/submissions`, - callbackSchema: '[]', - }; - return learningObject; - }, - createAttachment: {}, - getHTMLRendering: () => loadTestAsset('learning-objects/dummy/rendering.txt').toString(), - }; -} diff --git a/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/pn-werkingnotebooks-example.ts b/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/pn-werkingnotebooks-example.ts deleted file mode 100644 index 3737d551..00000000 --- a/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/pn-werkingnotebooks-example.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { LearningObjectExample } from '../learning-object-example'; -import { Language } from '@dwengo-1/common/dist/util/language'; -import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type'; -import { loadTestAsset } from '../../../../test-utils/load-test-asset'; -import { LearningObject } from '../../../../../src/entities/content/learning-object.entity'; -import { Attachment } from '../../../../../src/entities/content/attachment.entity'; -import { envVars, getEnvVar } from '../../../../../src/util/envVars'; -import { EducationalGoal } from '../../../../../src/entities/content/educational-goal.entity'; -import { ReturnValue } from '../../../../../src/entities/content/return-value.entity'; - -const ASSETS_PREFIX = 'learning-objects/pn-werkingnotebooks/'; - -const example: LearningObjectExample = { - createLearningObject: () => { - const learningObject = new LearningObject(); - learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}pn_werkingnotebooks`; - learningObject.version = 3; - learningObject.language = Language.Dutch; - learningObject.title = 'Werken met notebooks'; - learningObject.description = 'Leren werken met notebooks'; - learningObject.keywords = ['Python', 'KIKS', 'Wiskunde', 'STEM', 'AI']; - - const educationalGoal1 = new EducationalGoal(); - educationalGoal1.source = 'Source'; - educationalGoal1.id = 'id'; - - const educationalGoal2 = new EducationalGoal(); - educationalGoal2.source = 'Source2'; - educationalGoal2.id = 'id2'; - - learningObject.educationalGoals = [educationalGoal1, educationalGoal2]; - learningObject.admins = []; - learningObject.contentType = DwengoContentType.TEXT_MARKDOWN; - learningObject.teacherExclusive = false; - learningObject.skosConcepts = [ - 'http://ilearn.ilabt.imec.be/vocab/curr1/s-vaktaal', - 'http://ilearn.ilabt.imec.be/vocab/curr1/s-digitale-media-en-toepassingen', - 'http://ilearn.ilabt.imec.be/vocab/curr1/s-computers-en-systemen', - ]; - learningObject.copyright = 'dwengo'; - learningObject.license = 'dwengo'; - learningObject.estimatedTime = 10; - - const returnValue = new ReturnValue(); - returnValue.callbackUrl = 'callback_url_example'; - returnValue.callbackSchema = '{"att": "test", "att2": "test2"}'; - - learningObject.returnValue = returnValue; - learningObject.available = true; - learningObject.content = loadTestAsset(`${ASSETS_PREFIX}/content.md`); - - return learningObject; - }, - createAttachment: { - dwengoLogo: (learningObject) => { - const att = new Attachment(); - att.learningObject = learningObject; - att.name = 'dwengo.png'; - att.mimeType = 'image/png'; - att.content = loadTestAsset(`${ASSETS_PREFIX}/dwengo.png`); - return att; - }, - knop: (learningObject) => { - const att = new Attachment(); - att.learningObject = learningObject; - att.name = 'Knop.png'; - att.mimeType = 'image/png'; - att.content = loadTestAsset(`${ASSETS_PREFIX}/Knop.png`); - return att; - }, - }, - getHTMLRendering: () => loadTestAsset(`${ASSETS_PREFIX}/rendering.txt`).toString(), -}; -export default example; diff --git a/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/Knop.png b/backend/tests/test_assets/content/learning-object-resources/pn_werkingnotebooks/Knop.png similarity index 100% rename from backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/Knop.png rename to backend/tests/test_assets/content/learning-object-resources/pn_werkingnotebooks/Knop.png diff --git a/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/content.md b/backend/tests/test_assets/content/learning-object-resources/pn_werkingnotebooks/content.md similarity index 100% rename from backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/content.md rename to backend/tests/test_assets/content/learning-object-resources/pn_werkingnotebooks/content.md diff --git a/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/dwengo.png b/backend/tests/test_assets/content/learning-object-resources/pn_werkingnotebooks/dwengo.png similarity index 100% rename from backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/dwengo.png rename to backend/tests/test_assets/content/learning-object-resources/pn_werkingnotebooks/dwengo.png diff --git a/backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/pn_werkingnotebooks/rendering.txt similarity index 100% rename from backend/tests/test_assets/content/learning-object-resources/pn-werkingnotebooks/rendering.txt rename to backend/tests/test_assets/content/learning-object-resources/pn_werkingnotebooks/rendering.txt diff --git a/backend/tests/test_assets/content/learning-object-resources/test-essay/test-essay-example.ts b/backend/tests/test_assets/content/learning-object-resources/test-essay/test-essay-example.ts deleted file mode 100644 index 1f95fc2c..00000000 --- a/backend/tests/test_assets/content/learning-object-resources/test-essay/test-essay-example.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { LearningObjectExample } from '../learning-object-example'; -import { LearningObject } from '../../../../../src/entities/content/learning-object.entity'; -import { loadTestAsset } from '../../../../test-utils/load-test-asset'; -import { envVars, getEnvVar } from '../../../../../src/util/envVars'; -import { Language } from '@dwengo-1/common/dist/util/language'; -import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type'; - -const example: LearningObjectExample = { - createLearningObject: () => { - const learningObject = new LearningObject(); - learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_essay`; - learningObject.language = Language.English; - learningObject.version = 1; - learningObject.title = 'Essay question for testing'; - learningObject.description = 'This essay question was only created for testing purposes.'; - learningObject.contentType = DwengoContentType.GIFT; - learningObject.returnValue = { - callbackUrl: `/learningObject/${learningObject.hruid}/submissions`, - callbackSchema: '["antwoord vraag 1"]', - }; - learningObject.content = loadTestAsset('learning-objects/test-essay/content.txt'); - return learningObject; - }, - createAttachment: {}, - getHTMLRendering: () => loadTestAsset('learning-objects/test-essay/rendering.txt').toString(), -}; - -export default example; diff --git a/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/test-multiple-choice-example.ts b/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/test-multiple-choice-example.ts deleted file mode 100644 index 277ab486..00000000 --- a/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/test-multiple-choice-example.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { LearningObjectExample } from '../learning-object-example'; -import { LearningObject } from '../../../../../src/entities/content/learning-object.entity'; -import { loadTestAsset } from '../../../../test-utils/load-test-asset'; -import { envVars, getEnvVar } from '../../../../../src/util/envVars'; -import { DwengoContentType } from '../../../../../src/services/learning-objects/processing/content-type'; -import { Language } from '@dwengo-1/common/dist/util/language'; - -const example: LearningObjectExample = { - createLearningObject: () => { - const learningObject = new LearningObject(); - learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_multiple_choice`; - learningObject.language = Language.English; - learningObject.version = 1; - learningObject.title = 'Multiple choice question for testing'; - learningObject.description = 'This multiple choice question was only created for testing purposes.'; - learningObject.contentType = DwengoContentType.GIFT; - learningObject.returnValue = { - callbackUrl: `/learningObject/${learningObject.hruid}/submissions`, - callbackSchema: '["antwoord vraag 1"]', - }; - learningObject.content = loadTestAsset('learning-objects/test-multiple-choice/content.txt'); - return learningObject; - }, - createAttachment: {}, - getHTMLRendering: () => loadTestAsset('learning-objects/test-multiple-choice/rendering.txt').toString(), -}; - -export default example; diff --git a/backend/tests/test_assets/content/learning-objects.testdata.ts b/backend/tests/test_assets/content/learning-objects.testdata.ts index 4b33c85a..adb11572 100644 --- a/backend/tests/test_assets/content/learning-objects.testdata.ts +++ b/backend/tests/test_assets/content/learning-objects.testdata.ts @@ -5,6 +5,7 @@ import {DwengoContentType} from '../../../src/services/learning-objects/processi import {ReturnValue} from '../../../src/entities/content/return-value.entity'; import {envVars, getEnvVar} from "../../../src/util/envVars"; import {loadTestAsset} from "../../test-utils/load-test-asset"; +import {v4} from "uuid"; export function makeTestLearningObjects(em: EntityManager): LearningObject[] { const returnValue: ReturnValue = new ReturnValue(); @@ -30,8 +31,8 @@ export function makeTestLearningObjects(em: EntityManager): LearningObject[] { export function createReturnValue(): ReturnValue { const returnValue: ReturnValue = new ReturnValue(); - returnValue.callbackSchema = ''; - returnValue.callbackUrl = ''; + returnValue.callbackSchema = '[]'; + returnValue.callbackUrl = '%SUBMISSION%'; return returnValue; } @@ -44,6 +45,8 @@ export const testLearningObject01: RequiredEntityData = { description: 'debute', contentType: DwengoContentType.TEXT_MARKDOWN, keywords: [], + uuid: v4(), + targetAges: [16, 17, 18], teacherExclusive: false, skosConcepts: [], educationalGoals: [], @@ -235,16 +238,16 @@ export const testLearningObjectPnNotebooks: RequiredEntityData = { name: "dwengo.png", mimeType: "image/png", - content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/dwengo.png") + content: loadTestAsset("/content/learning-object-resources/pn_werkingnotebooks/dwengo.png") }, { name: "Knop.png", mimeType: "image/png", - content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/Knop.png") + content: loadTestAsset("/content/learning-object-resources/pn_werkingnotebooks/Knop.png") } ], available: false, - content: loadTestAsset("/content/learning-object-resources/pn-werkingnotebooks/content.md"), + content: loadTestAsset("/content/learning-object-resources/pn_werkingnotebooks/content.md"), returnValue: { callbackUrl: "%SUBMISSION%", callbackSchema: "[]" diff --git a/common/src/interfaces/learning-content.ts b/common/src/interfaces/learning-content.ts index f580b00c..582e0086 100644 --- a/common/src/interfaces/learning-content.ts +++ b/common/src/interfaces/learning-content.ts @@ -1,10 +1,10 @@ import { Language } from '../util/language'; export interface Transition { - default: boolean; - _id: string; + default?: boolean; + _id?: string; next: { - _id: string; + _id?: string; hruid: string; version: number; language: string; From 1815371a7b90ab1e725172476237f857dd189bd8 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 08:10:48 +0200 Subject: [PATCH 06/31] fix(backend): Testen LearningObjectService gerepareerd na refactoring. --- .../learning-object-service.test.ts | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/backend/tests/services/learning-objects/learning-object-service.test.ts b/backend/tests/services/learning-objects/learning-object-service.test.ts index 3ea4143d..2f793156 100644 --- a/backend/tests/services/learning-objects/learning-object-service.test.ts +++ b/backend/tests/services/learning-objects/learning-object-service.test.ts @@ -1,14 +1,20 @@ import { beforeAll, describe, expect, it } from 'vitest'; import { setupTestApp } from '../../setup-tests'; import { LearningObject } from '../../../src/entities/content/learning-object.entity'; -import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; -import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; import learningObjectService from '../../../src/services/learning-objects/learning-object-service'; import { envVars, getEnvVar } from '../../../src/util/envVars'; -import { LearningPath } from '../../../src/entities/content/learning-path.entity'; -import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; -import { LearningObjectIdentifierDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; +import { + LearningObjectIdentifierDTO, + LearningPath as LearningPathDTO, + LearningPathIdentifier +} from '@dwengo-1/common/interfaces/learning-content'; import { Language } from '@dwengo-1/common/util/language'; +import {testLearningObjectPnNotebooks} from "../../test_assets/content/learning-objects.testdata"; +import { + testPartiallyDatabaseAndPartiallyDwengoApiLearningPath +} from "../../test_assets/content/learning-paths.testdata"; +import {RequiredEntityData} from "@mikro-orm/core"; +import {getHtmlRenderingForTestLearningObject} from "../../test-utils/get-html-rendering"; const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = 'Werken met notebooks'; const DWENGO_TEST_LEARNING_OBJECT_ID: LearningObjectIdentifierDTO = { @@ -23,25 +29,20 @@ const DWENGO_TEST_LEARNING_PATH_ID: LearningPathIdentifier = { }; const DWENGO_TEST_LEARNING_PATH_HRUIDS = new Set(['pn_werkingnotebooks', 'pn_werkingnotebooks2', 'pn_werkingnotebooks3']); -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 }; -} - describe('LearningObjectService', () => { - let exampleLearningObject: LearningObject; - let exampleLearningPath: LearningPath; + let exampleLearningObject: RequiredEntityData; + let exampleLearningPath: LearningPathDTO; + let exampleLearningPathId: LearningPathIdentifier; beforeAll(async () => { await setupTestApp(); - const exampleData = await initExampleData(); - exampleLearningObject = exampleData.learningObject; - exampleLearningPath = exampleData.learningPath; + exampleLearningObject = testLearningObjectPnNotebooks; + exampleLearningPath = testPartiallyDatabaseAndPartiallyDwengoApiLearningPath; + + exampleLearningPathId = { + hruid: exampleLearningPath.hruid, + language: exampleLearningPath.language as Language + } }); describe('getLearningObjectById', () => { @@ -69,7 +70,7 @@ describe('LearningObjectService', () => { const result = await learningObjectService.getLearningObjectHTML(exampleLearningObject); expect(result).not.toBeNull(); // Set newlines so your tests are platform-independent. - expect(result).toEqual(learningObjectExample.getHTMLRendering().replace(/\r\n/g, '\n')); + expect(result).toEqual(getHtmlRenderingForTestLearningObject(exampleLearningObject).replace(/\r\n/g, '\n')); }); it( 'returns the same HTML as the Dwengo API when queried with the identifier of a learning object that does ' + @@ -97,8 +98,10 @@ describe('LearningObjectService', () => { describe('getLearningObjectsFromPath', () => { it('returns all learning objects when a learning path in the database is queried', async () => { - const result = await learningObjectService.getLearningObjectsFromPath(exampleLearningPath); - expect(result.map((it) => it.key)).toEqual(exampleLearningPath.nodes.map((it) => it.learningObjectHruid)); + const result = await learningObjectService.getLearningObjectsFromPath(exampleLearningPathId); + expect(result.map(it=> it.key)).toEqual( + exampleLearningPath.nodes.map(it => it.learningobject_hruid) + ); }); it('also returns all learning objects when a learning path from the Dwengo API is queried', async () => { const result = await learningObjectService.getLearningObjectsFromPath(DWENGO_TEST_LEARNING_PATH_ID); @@ -115,8 +118,8 @@ describe('LearningObjectService', () => { describe('getLearningObjectIdsFromPath', () => { it('returns all learning objects when a learning path in the database is queried', async () => { - const result = await learningObjectService.getLearningObjectIdsFromPath(exampleLearningPath); - expect(result).toEqual(exampleLearningPath.nodes.map((it) => it.learningObjectHruid)); + const result = await learningObjectService.getLearningObjectIdsFromPath(exampleLearningPathId); + expect(result).toEqual(exampleLearningPath.nodes.map(it => it.learningobject_hruid)); }); it('also returns all learning object hruids when a learning path from the Dwengo API is queried', async () => { const result = await learningObjectService.getLearningObjectIdsFromPath(DWENGO_TEST_LEARNING_PATH_ID); From c624e366809ccc006a0b1fc11fc4fbd193e401e1 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 09:08:23 +0200 Subject: [PATCH 07/31] fix(backend): Testen DatabaseLearningPathProvider en LearningPathService gerepareerd na refactoring. --- .../database-learning-path-provider.test.ts | 265 +++++++----------- .../learning-path-service.test.ts | 48 ++-- backend/tests/setup-tests.ts | 3 + backend/tests/test-utils/expectations.ts | 2 +- .../assignments/groups.testdata.ts | 39 ++- .../content/learning-objects.testdata.ts | 10 +- 6 files changed, 163 insertions(+), 204 deletions(-) diff --git a/backend/tests/services/learning-path/database-learning-path-provider.test.ts b/backend/tests/services/learning-path/database-learning-path-provider.test.ts index 0a0370a3..b48f8c3e 100644 --- a/backend/tests/services/learning-path/database-learning-path-provider.test.ts +++ b/backend/tests/services/learning-path/database-learning-path-provider.test.ts @@ -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; + let extraExerciseLearningObject: RequiredEntityData; + let finalLearningObject: RequiredEntityData; + 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); }); diff --git a/backend/tests/services/learning-path/learning-path-service.test.ts b/backend/tests/services/learning-path/learning-path-service.test.ts index 972a7fa1..cb263d73 100644 --- a/backend/tests/services/learning-path/learning-path-service.test.ts +++ b/backend/tests/services/learning-path/learning-path-service.test.ts @@ -1,22 +1,11 @@ import { beforeAll, describe, expect, it } from 'vitest'; import { setupTestApp } from '../../setup-tests'; -import { LearningObject } from '../../../src/entities/content/learning-object.entity'; -import { LearningPath } from '../../../src/entities/content/learning-path.entity'; -import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; -import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; -import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; import learningPathService from '../../../src/services/learning-paths/learning-path-service'; import { Language } from '@dwengo-1/common/util/language'; - -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 }; -} +import { + testPartiallyDatabaseAndPartiallyDwengoApiLearningPath +} from "../../test_assets/content/learning-paths.testdata"; +import {LearningPath as LearningPathDTO} from "@dwengo-1/common/interfaces/learning-content"; const TEST_DWENGO_LEARNING_PATH_HRUID = 'pn_werking'; const TEST_DWENGO_LEARNING_PATH_TITLE = 'Werken met notebooks'; @@ -24,42 +13,49 @@ const TEST_DWENGO_EXCLUSIVE_LEARNING_PATH_SEARCH_QUERY = 'Microscopie'; const TEST_SEARCH_QUERY_EXPECTING_NO_MATCHES = 'su$m8f9usf89ud { - let example: { learningObject: LearningObject; learningPath: LearningPath }; + let testLearningPath: LearningPathDTO; beforeAll(async () => { await setupTestApp(); - example = await initExampleData(); + testLearningPath = testPartiallyDatabaseAndPartiallyDwengoApiLearningPath }); describe('fetchLearningPaths', () => { it('should return learning paths both from the database and from the Dwengo API', async () => { const result = await learningPathService.fetchLearningPaths( - [example.learningPath.hruid, TEST_DWENGO_LEARNING_PATH_HRUID], - example.learningPath.language, + [testLearningPath.hruid, TEST_DWENGO_LEARNING_PATH_HRUID], + testLearningPath.language as Language, 'the source' ); expect(result.success).toBeTruthy(); expect(result.data?.filter((it) => it.hruid === TEST_DWENGO_LEARNING_PATH_HRUID).length).not.toBe(0); - expect(result.data?.filter((it) => it.hruid === example.learningPath.hruid).length).not.toBe(0); + expect(result.data?.filter((it) => it.hruid === testLearningPath.hruid).length).not.toBe(0); expect(result.data?.find((it) => it.hruid === TEST_DWENGO_LEARNING_PATH_HRUID)?.title).toEqual(TEST_DWENGO_LEARNING_PATH_TITLE); - expect(result.data?.find((it) => it.hruid === example.learningPath.hruid)?.title).toEqual(example.learningPath.title); + expect(result.data?.find((it) => it.hruid === testLearningPath.hruid)?.title).toEqual(testLearningPath.title); }); it('should include both the learning objects from the Dwengo API and learning objects from the database in its response', async () => { - const result = await learningPathService.fetchLearningPaths([example.learningPath.hruid], example.learningPath.language, 'the source'); + const result = await learningPathService.fetchLearningPaths( + [testLearningPath.hruid], + testLearningPath.language as Language, + 'the source' + ); expect(result.success).toBeTruthy(); expect(result.data?.length).toBe(1); // Should include all the nodes, even those pointing to foreign learning objects. expect([...result.data![0].nodes.map((it) => it.learningobject_hruid)].sort((a, b) => a.localeCompare(b))).toEqual( - example.learningPath.nodes.map((it) => it.learningObjectHruid).sort((a, b) => a.localeCompare(b)) + testLearningPath.nodes.map((it) => it.learningobject_hruid).sort((a, b) => a.localeCompare(b)) ); }); }); describe('searchLearningPath', () => { it('should include both the learning paths from the Dwengo API and those from the database in its response', async () => { // This matches the learning object in the database, but definitely also some learning objects in the Dwengo API. - const result = await learningPathService.searchLearningPaths(example.learningPath.title.substring(2, 3), example.learningPath.language); + const result = await learningPathService.searchLearningPaths( + testLearningPath.title.substring(2, 3), + testLearningPath.language as Language + ); // Should find the one from the database - expect(result.filter((it) => it.hruid === example.learningPath.hruid && it.title === example.learningPath.title).length).toBe(1); + expect(result.filter((it) => it.hruid === testLearningPath.hruid && it.title === testLearningPath.title).length).toBe(1); // But should not only find that one. expect(result.length).not.toBeLessThan(2); @@ -71,7 +67,7 @@ describe('LearningPathService', () => { expect(result.length).not.toBe(0); // But not the example learning path. - expect(result.filter((it) => it.hruid === example.learningPath.hruid && it.title === example.learningPath.title).length).toBe(0); + expect(result.filter((it) => it.hruid === testLearningPath.hruid && it.title === testLearningPath.title).length).toBe(0); }); it('should return an empty list if neither the Dwengo API nor the database contains matches', async () => { const result = await learningPathService.searchLearningPaths(TEST_SEARCH_QUERY_EXPECTING_NO_MATCHES, Language.Dutch); diff --git a/backend/tests/setup-tests.ts b/backend/tests/setup-tests.ts index 0d71b434..ceefaf2e 100644 --- a/backend/tests/setup-tests.ts +++ b/backend/tests/setup-tests.ts @@ -15,6 +15,7 @@ import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; import {Collection} from "@mikro-orm/core"; import {Group} from "../src/entities/assignments/group.entity"; +import {LearningObject} from "../src/entities/content/learning-object.entity"; export async function setupTestApp(): Promise { dotenv.config({ path: '.env.test' }); @@ -58,4 +59,6 @@ export async function setupTestApp(): Promise { ...answers, ...submissions, ]); + + console.log(await em.findAll(LearningObject)); } diff --git a/backend/tests/test-utils/expectations.ts b/backend/tests/test-utils/expectations.ts index b6d01069..2ca10d20 100644 --- a/backend/tests/test-utils/expectations.ts +++ b/backend/tests/test-utils/expectations.ts @@ -133,7 +133,7 @@ export function expectToBeCorrectLearningPath( const expectedNode = [...expectedLearningPathNodes.entries()].find( ([key, _]) => key.learningObjectHruid === nodeKey.learningObjectHruid && key.language === node.language && key.version === node.version )![1]; - expect(node.start_node).toEqual(expectedNode.startNode); + expect(Boolean(node.start_node)).toEqual(Boolean(expectedNode.startNode)); expect(new Set(node.transitions.map((it) => it.next.hruid))).toEqual( new Set(expectedNode.transitions.map((it) => it.next.learningObjectHruid)) diff --git a/backend/tests/test_assets/assignments/groups.testdata.ts b/backend/tests/test_assets/assignments/groups.testdata.ts index c82887bb..c203bea3 100644 --- a/backend/tests/test_assets/assignments/groups.testdata.ts +++ b/backend/tests/test_assets/assignments/groups.testdata.ts @@ -1,4 +1,4 @@ -import { EntityManager } from '@mikro-orm/core'; +import {EntityManager} from '@mikro-orm/core'; import { Group } from '../../../src/entities/assignments/group.entity'; import { Assignment } from '../../../src/entities/assignments/assignment.entity'; import { Student } from '../../../src/entities/users/student.entity'; @@ -8,7 +8,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen * Group #1 for Assignment #1 in class 'id01' * => Assigned to do learning path 'id02' */ - const group01 = em.create(Group, { + group01 = em.create(Group, { assignment: assignments[0], groupNumber: 1, members: students.slice(0, 2), @@ -18,7 +18,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen * Group #2 for Assignment #1 in class 'id01' * => Assigned to do learning path 'id02' */ - const group02 = em.create(Group, { + group02 = em.create(Group, { assignment: assignments[0], groupNumber: 2, members: students.slice(2, 4), @@ -28,7 +28,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen * Group #3 for Assignment #1 in class 'id01' * => Assigned to do learning path 'id02' */ - const group03 = em.create(Group, { + group03 = em.create(Group, { assignment: assignments[0], groupNumber: 3, members: students.slice(4, 6), @@ -38,7 +38,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen * Group #4 for Assignment #2 in class 'id02' * => Assigned to do learning path 'id01' */ - const group04 = em.create(Group, { + group04 = em.create(Group, { assignment: assignments[1], groupNumber: 4, members: students.slice(3, 4), @@ -48,7 +48,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen * Group #5 for Assignment #4 in class 'id01' * => Assigned to do learning path 'id01' */ - const group05 = em.create(Group, { + group05 = em.create(Group, { assignment: assignments[3], groupNumber: 1, members: students.slice(0, 2), @@ -56,3 +56,30 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen return [group01, group02, group03, group04, group05]; } + +let group01: Group; +let group02: Group; +let group03: Group; +let group04: Group; +let group05: Group; + +export function getTestGroup01() { + return group01; +} + +export function getTestGroup02() { + return group02; +} + +export function getTestGroup03() { + return group03; +} + +export function getTestGroup04() { + return group04; +} + +export function getTestGroup05() { + return group05; +} + diff --git a/backend/tests/test_assets/content/learning-objects.testdata.ts b/backend/tests/test_assets/content/learning-objects.testdata.ts index adb11572..287a59ca 100644 --- a/backend/tests/test_assets/content/learning-objects.testdata.ts +++ b/backend/tests/test_assets/content/learning-objects.testdata.ts @@ -37,7 +37,7 @@ export function createReturnValue(): ReturnValue { } export const testLearningObject01: RequiredEntityData = { - hruid: 'id01', + hruid: `${getEnvVar(envVars.UserContentPrefix)}id01`, language: Language.English, version: 1, admins: [], @@ -61,7 +61,7 @@ export const testLearningObject01: RequiredEntityData = { }; export const testLearningObject02: RequiredEntityData = { - hruid: 'id02', + hruid: `${getEnvVar(envVars.UserContentPrefix)}id02`, language: Language.English, version: 1, admins: [], @@ -85,7 +85,7 @@ export const testLearningObject02: RequiredEntityData = { }; export const testLearningObject03: RequiredEntityData = { - hruid: 'id03', + hruid: `${getEnvVar(envVars.UserContentPrefix)}id03`, language: Language.English, version: 1, admins: [], @@ -112,7 +112,7 @@ export const testLearningObject03: RequiredEntityData = { }; export const testLearningObject04: RequiredEntityData = { - hruid: 'id04', + hruid: `${getEnvVar(envVars.UserContentPrefix)}id04`, language: Language.English, version: 1, admins: [], @@ -139,7 +139,7 @@ export const testLearningObject04: RequiredEntityData = { }; export const testLearningObject05: RequiredEntityData = { - hruid: 'id05', + hruid: `${getEnvVar(envVars.UserContentPrefix)}id05`, language: Language.English, version: 1, admins: [], From 0609152cb396eb1a585dece4617c98631acc27f1 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 09:34:11 +0200 Subject: [PATCH 08/31] fix(backend): Overige falende testen gerepareerd. --- .../tests/data/content/attachments.test.ts | 15 +++----- .../learning-object-repository.test.ts | 2 ++ .../data/content/learning-objects.test.ts | 5 +-- .../processing/processing-service.test.ts | 36 +++++++++++++------ backend/tests/setup-tests.ts | 3 -- .../content.txt | 0 .../rendering.txt | 0 .../content.txt | 0 .../rendering.txt | 0 .../content/learning-objects.testdata.ts | 4 +-- 10 files changed, 36 insertions(+), 29 deletions(-) rename backend/tests/test_assets/content/learning-object-resources/{test-essay => test_essay_question}/content.txt (100%) rename backend/tests/test_assets/content/learning-object-resources/{test-essay => test_essay_question}/rendering.txt (100%) rename backend/tests/test_assets/content/learning-object-resources/{test-multiple-choice => test_multiple_choice}/content.txt (100%) rename backend/tests/test_assets/content/learning-object-resources/{test-multiple-choice => test_multiple_choice}/rendering.txt (100%) diff --git a/backend/tests/data/content/attachments.test.ts b/backend/tests/data/content/attachments.test.ts index 4e65954e..7d0bfd6b 100644 --- a/backend/tests/data/content/attachments.test.ts +++ b/backend/tests/data/content/attachments.test.ts @@ -1,28 +1,21 @@ import { beforeAll, describe, expect, it } from 'vitest'; import { setupTestApp } from '../../setup-tests.js'; -import { getAttachmentRepository, getLearningObjectRepository } from '../../../src/data/repositories.js'; +import { getAttachmentRepository } from '../../../src/data/repositories.js'; import { AttachmentRepository } from '../../../src/data/content/attachment-repository.js'; -import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; -import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier.js'; -import { Language } from '@dwengo-1/common/util/language'; +import { testLearningObject02 } from "../../test_assets/content/learning-objects.testdata"; describe('AttachmentRepository', () => { let attachmentRepository: AttachmentRepository; - let learningObjectRepository: LearningObjectRepository; beforeAll(async () => { await setupTestApp(); attachmentRepository = getAttachmentRepository(); - learningObjectRepository = getLearningObjectRepository(); }); it('should return the requested attachment', async () => { - const id = new LearningObjectIdentifier('id02', Language.English, 1); - const learningObject = await learningObjectRepository.findByIdentifier(id); - const attachment = await attachmentRepository.findByMostRecentVersionOfLearningObjectAndName( - learningObject!.hruid, - Language.English, + testLearningObject02.hruid, + testLearningObject02.language, 'attachment01' ); diff --git a/backend/tests/data/content/learning-object-repository.test.ts b/backend/tests/data/content/learning-object-repository.test.ts index d99beb4b..75d46796 100644 --- a/backend/tests/data/content/learning-object-repository.test.ts +++ b/backend/tests/data/content/learning-object-repository.test.ts @@ -9,6 +9,7 @@ import { testLearningObject02, testLearningObject03 } from "../../test_assets/content/learning-objects.testdata"; +import {v4} from "uuid"; describe('LearningObjectRepository', () => { let learningObjectRepository: LearningObjectRepository; @@ -43,6 +44,7 @@ describe('LearningObjectRepository', () => { let testLearningObject01Newer = structuredClone(testLearningObject01); testLearningObject01Newer.version = 10; testLearningObject01Newer.title += " (nieuw)"; + testLearningObject01Newer.uuid = v4(); testLearningObject01Newer.content = Buffer.from("This is the new content."); newerExample = learningObjectRepository.create(testLearningObject01Newer); await learningObjectRepository.save(newerExample); diff --git a/backend/tests/data/content/learning-objects.test.ts b/backend/tests/data/content/learning-objects.test.ts index 3c9d5dde..ef7fca79 100644 --- a/backend/tests/data/content/learning-objects.test.ts +++ b/backend/tests/data/content/learning-objects.test.ts @@ -4,6 +4,7 @@ import { getLearningObjectRepository } from '../../../src/data/repositories'; import { setupTestApp } from '../../setup-tests'; import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; import { Language } from '@dwengo-1/common/util/language'; +import {testLearningObject01} from "../../test_assets/content/learning-objects.testdata"; describe('LearningObjectRepository', () => { let learningObjectRepository: LearningObjectRepository; @@ -13,8 +14,8 @@ describe('LearningObjectRepository', () => { learningObjectRepository = getLearningObjectRepository(); }); - const id01 = new LearningObjectIdentifier('id01', Language.English, 1); - const id02 = new LearningObjectIdentifier('test_id', Language.English, 1); + const id01 = new LearningObjectIdentifier(testLearningObject01.hruid, testLearningObject01.language, testLearningObject01.version); + const id02 = new LearningObjectIdentifier('non_existing_id', Language.English, 1); it('should return the learning object that matches identifier 1', async () => { const learningObject = await learningObjectRepository.findByIdentifier(id01); diff --git a/backend/tests/services/learning-objects/processing/processing-service.test.ts b/backend/tests/services/learning-objects/processing/processing-service.test.ts index 570a014d..d41b8afa 100644 --- a/backend/tests/services/learning-objects/processing/processing-service.test.ts +++ b/backend/tests/services/learning-objects/processing/processing-service.test.ts @@ -1,26 +1,40 @@ -import { describe, expect, it } from 'vitest'; -import mdExample from '../../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; -import multipleChoiceExample from '../../../test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example'; -import essayExample from '../../../test-assets/learning-objects/test-essay/test-essay-example'; +import {beforeAll, describe, expect, it} from 'vitest'; import processingService from '../../../../src/services/learning-objects/processing/processing-service'; +import { + testLearningObjectEssayQuestion, + testLearningObjectMultipleChoice, testLearningObjectPnNotebooks +} from "../../../test_assets/content/learning-objects.testdata"; +import {getHtmlRenderingForTestLearningObject} from "../../../test-utils/get-html-rendering"; +import {getLearningObjectRepository} from "../../../../src/data/repositories"; +import {setupTestApp} from "../../../setup-tests"; describe('ProcessingService', () => { + beforeAll(async () => { + await setupTestApp(); + }); + it('renders a markdown learning object correctly', async () => { - const markdownLearningObject = mdExample.createLearningObject(); + const markdownLearningObject = getLearningObjectRepository().create(testLearningObjectPnNotebooks); const result = await processingService.render(markdownLearningObject); // Set newlines so your tests are platform-independent. - expect(result).toEqual(mdExample.getHTMLRendering().replace(/\r\n/g, '\n')); + expect(result).toEqual( + getHtmlRenderingForTestLearningObject(markdownLearningObject).replace(/\r\n/g, '\n') + ); }); it('renders a multiple choice question correctly', async () => { - const multipleChoiceLearningObject = multipleChoiceExample.createLearningObject(); - const result = await processingService.render(multipleChoiceLearningObject); - expect(result).toEqual(multipleChoiceExample.getHTMLRendering().replace(/\r\n/g, '\n')); + const testLearningObject = getLearningObjectRepository().create(testLearningObjectMultipleChoice); + const result = await processingService.render(testLearningObject); + expect(result).toEqual( + getHtmlRenderingForTestLearningObject(testLearningObjectMultipleChoice).replace(/\r\n/g, '\n') + ); }); it('renders an essay question correctly', async () => { - const essayLearningObject = essayExample.createLearningObject(); + const essayLearningObject = getLearningObjectRepository().create(testLearningObjectEssayQuestion); const result = await processingService.render(essayLearningObject); - expect(result).toEqual(essayExample.getHTMLRendering().replace(/\r\n/g, '\n')); + expect(result).toEqual( + getHtmlRenderingForTestLearningObject(essayLearningObject).replace(/\r\n/g, '\n') + ); }); }); diff --git a/backend/tests/setup-tests.ts b/backend/tests/setup-tests.ts index ceefaf2e..0d71b434 100644 --- a/backend/tests/setup-tests.ts +++ b/backend/tests/setup-tests.ts @@ -15,7 +15,6 @@ import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; import {Collection} from "@mikro-orm/core"; import {Group} from "../src/entities/assignments/group.entity"; -import {LearningObject} from "../src/entities/content/learning-object.entity"; export async function setupTestApp(): Promise { dotenv.config({ path: '.env.test' }); @@ -59,6 +58,4 @@ export async function setupTestApp(): Promise { ...answers, ...submissions, ]); - - console.log(await em.findAll(LearningObject)); } diff --git a/backend/tests/test_assets/content/learning-object-resources/test-essay/content.txt b/backend/tests/test_assets/content/learning-object-resources/test_essay_question/content.txt similarity index 100% rename from backend/tests/test_assets/content/learning-object-resources/test-essay/content.txt rename to backend/tests/test_assets/content/learning-object-resources/test_essay_question/content.txt diff --git a/backend/tests/test_assets/content/learning-object-resources/test-essay/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/test_essay_question/rendering.txt similarity index 100% rename from backend/tests/test_assets/content/learning-object-resources/test-essay/rendering.txt rename to backend/tests/test_assets/content/learning-object-resources/test_essay_question/rendering.txt diff --git a/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/content.txt b/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/content.txt similarity index 100% rename from backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/content.txt rename to backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/content.txt diff --git a/backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/rendering.txt similarity index 100% rename from backend/tests/test_assets/content/learning-object-resources/test-multiple-choice/rendering.txt rename to backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/rendering.txt diff --git a/backend/tests/test_assets/content/learning-objects.testdata.ts b/backend/tests/test_assets/content/learning-objects.testdata.ts index 287a59ca..35ff46bf 100644 --- a/backend/tests/test_assets/content/learning-objects.testdata.ts +++ b/backend/tests/test_assets/content/learning-objects.testdata.ts @@ -179,7 +179,7 @@ export const testLearningObjectMultipleChoice: RequiredEntityData targetAges: [10, 11, 12, 13, 14, 15, 16, 17, 18], admins: [], contentType: DwengoContentType.GIFT, - content: loadTestAsset('content/learning-object-resources/test-essay/content.txt'), + content: loadTestAsset('content/learning-object-resources/test_essay_question/content.txt'), returnValue: { callbackUrl: `%SUBMISSION%`, callbackSchema: '["antwoord vraag 1"]', From ffa09156472e7cdaa653b8c457ea61041f45e473 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 09:39:10 +0200 Subject: [PATCH 09/31] fix(backend): package.json aangepast volgens instructies van @tdpeuter --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 3a89eb87..40d49982 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,7 @@ "format": "prettier --write src/", "format-check": "prettier --check src/", "lint": "eslint . --fix", - "pretest:unit": "npm run build", + "pretest:unit": "tsx ../docs/api/generate.ts && npm run build", "test:unit": "vitest --run" }, "dependencies": { From a803b45046904a2e4220801b21267668a2045f50 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 10:53:30 +0200 Subject: [PATCH 10/31] fix(backend): nodeNumber handmatig invullen Om MikroORM bug die optreedt bij het persisteren van een kind van een entity met automatisch gegenereerde PK te vermijden. --- .../content/learning-path-node.entity.ts | 8 ++++---- .../content/learning-path-transition.entity.ts | 6 +++--- .../learning-paths/learning-path-service.ts | 3 ++- backend/tests/test-utils/load-test-asset.ts | 4 ++++ backend/tool/seed.ts | 18 +++++++++++++++--- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/backend/src/entities/content/learning-path-node.entity.ts b/backend/src/entities/content/learning-path-node.entity.ts index 818799b7..f21adc78 100644 --- a/backend/src/entities/content/learning-path-node.entity.ts +++ b/backend/src/entities/content/learning-path-node.entity.ts @@ -5,12 +5,12 @@ import { Language } from '@dwengo-1/common/util/language'; @Entity() export class LearningPathNode { - @ManyToOne({ entity: () => LearningPath, primary: true }) - learningPath!: Rel; - @PrimaryKey({ type: 'integer', autoincrement: true }) nodeNumber?: number; + @ManyToOne({ entity: () => LearningPath, primary: true }) + learningPath!: Rel; + @Property({ type: 'string' }) learningObjectHruid!: string; @@ -27,7 +27,7 @@ export class LearningPathNode { startNode!: boolean; @OneToMany({ entity: () => LearningPathTransition, mappedBy: 'node' }) - transitions: Collection = new Collection(this); + transitions!: Collection; @Property({ length: 3 }) createdAt: Date = new Date(); diff --git a/backend/src/entities/content/learning-path-transition.entity.ts b/backend/src/entities/content/learning-path-transition.entity.ts index 7d6601a3..0f466fdd 100644 --- a/backend/src/entities/content/learning-path-transition.entity.ts +++ b/backend/src/entities/content/learning-path-transition.entity.ts @@ -3,12 +3,12 @@ import { LearningPathNode } from './learning-path-node.entity.js'; @Entity() export class LearningPathTransition { - @ManyToOne({ entity: () => LearningPathNode, primary: true }) - node!: Rel; - @PrimaryKey({ type: 'numeric' }) transitionNumber!: number; + @ManyToOne({ entity: () => LearningPathNode, primary: true }) + node!: Rel; + @Property({ type: 'string' }) condition!: string; diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index 1f99fa84..3b8a257c 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -29,10 +29,11 @@ export function mapToLearningPath( admins, image: dto.image ? Buffer.from(base64ToArrayBuffer(dto.image)) : null }); - const nodes = dto.nodes.map((nodeDto: LearningObjectNode) => + const nodes = dto.nodes.map((nodeDto: LearningObjectNode, i: number) => repo.createNode({ learningPath: path, learningObjectHruid: nodeDto.learningobject_hruid, + nodeNumber: i, language: nodeDto.language, version: nodeDto.version, startNode: nodeDto.start_node ?? false, diff --git a/backend/tests/test-utils/load-test-asset.ts b/backend/tests/test-utils/load-test-asset.ts index 2a4a7786..c3879d12 100644 --- a/backend/tests/test-utils/load-test-asset.ts +++ b/backend/tests/test-utils/load-test-asset.ts @@ -1,5 +1,9 @@ import fs from 'fs'; import path from 'node:path'; +import {fileURLToPath} from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); /** * Load the asset at the given path. diff --git a/backend/tool/seed.ts b/backend/tool/seed.ts index f1742b69..393f9721 100644 --- a/backend/tool/seed.ts +++ b/backend/tool/seed.ts @@ -49,12 +49,24 @@ export async function seedDatabase(): Promise { const answers = makeTestAnswers(em, teachers, questions); const submissions = makeTestSubmissions(em, students, groups); + // Persist all entities - await em.persistAndFlush([ + /*await em.persistAndFlush([ ...students, ...teachers, ...learningObjects, - ...learningPaths, + ]);*/ + + try { + await em.persistAndFlush(learningPaths[0]); + } catch (e) { + "hey"; + } + /*await em.persistAndFlush(learningPaths[1]); + await em.persistAndFlush(learningPaths[2]); + await em.persistAndFlush(learningPaths[3]); + + await em.persistAndFlush([ ...classes, ...assignments, ...groups, @@ -64,7 +76,7 @@ export async function seedDatabase(): Promise { ...questions, ...answers, ...submissions, - ]); + ])*/ logger.info('Development database seeded successfully!'); From 6d452c7f725d35a35c6d037120b49eca359642a0 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 16 Apr 2025 11:42:13 +0200 Subject: [PATCH 11/31] fix: diverse kleine bugs Bij het zoeken van leerpaden en het renderen van leerobjecten. --- .../multiple-choice-question-renderer.ts | 2 +- .../test_essay_question/content.txt | 2 +- .../test_essay_question/rendering.txt | 2 +- .../test_multiple_choice/content.txt | 2 +- .../test_multiple_choice/rendering.txt | 6 +++--- .../content/learning-paths.testdata.ts | 4 ++-- backend/tool/seed.ts | 17 +++-------------- frontend/src/controllers/learning-paths.ts | 4 ++-- frontend/src/queries/learning-paths.ts | 6 ++++-- .../learning-paths/LearningPathSearchPage.vue | 4 ++-- 10 files changed, 20 insertions(+), 29 deletions(-) diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/multiple-choice-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/multiple-choice-question-renderer.ts index 9b09004f..40afdbd5 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/multiple-choice-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/multiple-choice-question-renderer.ts @@ -14,7 +14,7 @@ export class MultipleChoiceQuestionRenderer extends GIFTQuestionRenderer\n`; renderedHtml += ` \n`; - renderedHtml += ` \n`; + renderedHtml += ` \n`; renderedHtml += `
\n`; i++; } diff --git a/backend/tests/test_assets/content/learning-object-resources/test_essay_question/content.txt b/backend/tests/test_assets/content/learning-object-resources/test_essay_question/content.txt index aa634cbd..0e9fa7de 100644 --- a/backend/tests/test_assets/content/learning-object-resources/test_essay_question/content.txt +++ b/backend/tests/test_assets/content/learning-object-resources/test_essay_question/content.txt @@ -1,2 +1,2 @@ -::MC basic:: +::Reflection:: Reflect on this learning path. What have you learned today? {} diff --git a/backend/tests/test_assets/content/learning-object-resources/test_essay_question/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/test_essay_question/rendering.txt index 94a5ab79..46f605bd 100644 --- a/backend/tests/test_assets/content/learning-object-resources/test_essay_question/rendering.txt +++ b/backend/tests/test_assets/content/learning-object-resources/test_essay_question/rendering.txt @@ -1,6 +1,6 @@
-

MC basic

+

Reflection

Reflect on this learning path. What have you learned today?

diff --git a/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/content.txt b/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/content.txt index 98b7c9c3..4c2ba46e 100644 --- a/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/content.txt +++ b/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/content.txt @@ -1,4 +1,4 @@ -::MC basic:: +::Self-evaluation:: Are you following along well? { ~No, it's very difficult to follow along. =Yes, no problem! diff --git a/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/rendering.txt b/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/rendering.txt index 9c56f40b..8c94b4f2 100644 --- a/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/rendering.txt +++ b/backend/tests/test_assets/content/learning-object-resources/test_multiple_choice/rendering.txt @@ -1,14 +1,14 @@
-

MC basic

+

Self-evaluation

Are you following along well?

- +
- +
diff --git a/backend/tests/test_assets/content/learning-paths.testdata.ts b/backend/tests/test_assets/content/learning-paths.testdata.ts index 117f0341..17dc2c30 100644 --- a/backend/tests/test_assets/content/learning-paths.testdata.ts +++ b/backend/tests/test_assets/content/learning-paths.testdata.ts @@ -31,7 +31,7 @@ const nowString = new Date().toString(); export const testLearningPath01: LearningPathDTO = { keywords: "test", target_ages: [16, 17, 18], - hruid: "id01", + hruid: `${getEnvVar(envVars.UserContentPrefix)}id01`, language: Language.English, title: "repertoire Tool", description: "all about Tool", @@ -67,7 +67,7 @@ export const testLearningPath01: LearningPathDTO = { export const testLearningPath02: LearningPathDTO = { keywords: "test", target_ages: [16, 17, 18], - hruid: "id02", + hruid: `${getEnvVar(envVars.UserContentPrefix)}id02`, language: Language.English, title: "repertoire Dire Straits", description: "all about Dire Straits", diff --git a/backend/tool/seed.ts b/backend/tool/seed.ts index 393f9721..259bccb7 100644 --- a/backend/tool/seed.ts +++ b/backend/tool/seed.ts @@ -51,22 +51,11 @@ export async function seedDatabase(): Promise { // Persist all entities - /*await em.persistAndFlush([ + await em.persistAndFlush([ ...students, ...teachers, ...learningObjects, - ]);*/ - - try { - await em.persistAndFlush(learningPaths[0]); - } catch (e) { - "hey"; - } - /*await em.persistAndFlush(learningPaths[1]); - await em.persistAndFlush(learningPaths[2]); - await em.persistAndFlush(learningPaths[3]); - - await em.persistAndFlush([ + ...learningPaths, ...classes, ...assignments, ...groups, @@ -76,7 +65,7 @@ export async function seedDatabase(): Promise { ...questions, ...answers, ...submissions, - ])*/ + ]); logger.info('Development database seeded successfully!'); diff --git a/frontend/src/controllers/learning-paths.ts b/frontend/src/controllers/learning-paths.ts index 15967d28..e1aeda3e 100644 --- a/frontend/src/controllers/learning-paths.ts +++ b/frontend/src/controllers/learning-paths.ts @@ -8,8 +8,8 @@ export class LearningPathController extends BaseController { constructor() { super("learningPath"); } - async search(query: string): Promise { - const dtos = await this.get("/", { search: query }); + async search(query: string, language: string): Promise { + const dtos = await this.get("/", { search: query, language }); return dtos.map((dto) => LearningPath.fromDTO(dto)); } async getBy( diff --git a/frontend/src/queries/learning-paths.ts b/frontend/src/queries/learning-paths.ts index 3d8e6fcf..e7fefc34 100644 --- a/frontend/src/queries/learning-paths.ts +++ b/frontend/src/queries/learning-paths.ts @@ -34,12 +34,14 @@ export function useGetAllLearningPathsByThemeQuery( export function useSearchLearningPathQuery( query: MaybeRefOrGetter, + language: MaybeRefOrGetter ): UseQueryReturnType { return useQuery({ - queryKey: [LEARNING_PATH_KEY, "search", query], + queryKey: [LEARNING_PATH_KEY, "search", query, language], queryFn: async () => { const queryVal = toValue(query)!; - return learningPathController.search(queryVal); + const languageVal = toValue(language)!; + return learningPathController.search(queryVal, languageVal); }, enabled: () => Boolean(toValue(query)), }); diff --git a/frontend/src/views/learning-paths/LearningPathSearchPage.vue b/frontend/src/views/learning-paths/LearningPathSearchPage.vue index 44bc0306..4e2c6475 100644 --- a/frontend/src/views/learning-paths/LearningPathSearchPage.vue +++ b/frontend/src/views/learning-paths/LearningPathSearchPage.vue @@ -9,11 +9,11 @@ import LearningPathsGrid from "@/components/LearningPathsGrid.vue"; const route = useRoute(); - const { t } = useI18n(); + const { t, locale } = useI18n(); const query = computed(() => route.query.query as string | undefined); - const searchQueryResults = useSearchLearningPathQuery(query); + const searchQueryResults = useSearchLearningPathQuery(query, locale);