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;