fix: Test- en linting-errors opgelost

This commit is contained in:
Gerald Schmittinger 2025-04-18 23:47:56 +02:00
parent ce5c0ea629
commit ba912c3ef0
20 changed files with 103 additions and 116 deletions

View file

@ -19,8 +19,7 @@ export async function getSubmissionsHandler(req: Request, res: Response): Promis
const forGroup = req.query.forGroup as string | undefined; const forGroup = req.query.forGroup as string | undefined;
let submissions: SubmissionDTO[] const submissions: SubmissionDTO[] = await getSubmissionsForLearningObjectAndAssignment(
submissions = await getSubmissionsForLearningObjectAndAssignment(
loHruid, loHruid,
lang, lang,
version, version,

View file

@ -61,28 +61,36 @@ export class SubmissionRepository extends DwengoEntityRepository<Submission> {
/** /**
* Looks up all submissions for the given learning object which were submitted as part of the given assignment. * Looks up all submissions for the given learning object which were submitted as part of the given assignment.
* When forGroup is set, only the submissions of the given group are shown.
*/ */
public async findAllSubmissionsForLearningObjectAndAssignment( public async findAllSubmissionsForLearningObjectAndAssignment(
loId: LearningObjectIdentifier, loId: LearningObjectIdentifier,
assignment: Assignment, assignment: Assignment,
forGroup?: number
): Promise<Submission[]> { ): Promise<Submission[]> {
const onBehalfOf = forGroup
? {
assignment,
groupNumber: forGroup
}
: {
assignment,
};
return this.findAll({ return this.findAll({
where: { where: {
learningObjectHruid: loId.hruid, learningObjectHruid: loId.hruid,
learningObjectLanguage: loId.language, learningObjectLanguage: loId.language,
learningObjectVersion: loId.version, learningObjectVersion: loId.version,
onBehalfOf, onBehalfOf: {
assignment
}
},
});
}
/**
* Looks up all submissions for the given learning object which were submitted by the given group
*/
public async findAllSubmissionsForLearningObjectAndGroup(
loId: LearningObjectIdentifier,
group: Group,
): Promise<Submission[]> {
return this.findAll({
where: {
learningObjectHruid: loId.hruid,
learningObjectLanguage: loId.language,
learningObjectVersion: loId.version,
onBehalfOf: group
}, },
}); });
} }

View file

@ -29,13 +29,13 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath>
} }
public createNode( public createNode(
nodeData: RequiredEntityData<LearningPathNode, never, false> nodeData: RequiredEntityData<LearningPathNode>
): LearningPathNode { ): LearningPathNode {
return this.em.create(LearningPathNode, nodeData); return this.em.create(LearningPathNode, nodeData);
} }
public createTransition( public createTransition(
transitionData: RequiredEntityData<LearningPathTransition, never, false> transitionData: RequiredEntityData<LearningPathTransition>
): LearningPathTransition { ): LearningPathTransition {
return this.em.create(LearningPathTransition, transitionData) return this.em.create(LearningPathTransition, transitionData)
} }
@ -53,7 +53,7 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath>
} }
const em = this.getEntityManager(); const em = this.getEntityManager();
await em.persistAndFlush(path); await em.persistAndFlush(path);
await Promise.all(nodes.map(it => em.persistAndFlush(it))); await Promise.all(nodes.map(async it => em.persistAndFlush(it)));
await Promise.all(transitions.map(it => em.persistAndFlush(it))); await Promise.all(transitions.map(async it => em.persistAndFlush(it)));
} }
} }

View file

@ -42,7 +42,7 @@ export class LearningObject {
@Property({ type: 'array' }) @Property({ type: 'array' })
keywords: string[] = []; keywords: string[] = [];
@Property({ type: new ArrayType(i => +i), nullable: true }) @Property({ type: new ArrayType(i => Number(i)), nullable: true })
targetAges?: number[] = []; targetAges?: number[] = [];
@Property({ type: 'bool' }) @Property({ type: 'bool' })

View file

@ -61,9 +61,9 @@ export function mapToLearningPath(
next: toNode, next: toNode,
condition: transDto.condition ?? "true" condition: transDto.condition ?? "true"
}); });
} else {
return undefined;
} }
return undefined;
}).filter(it => it).map(it => it!); }).filter(it => it).map(it => it!);
fromNode.transitions = new Collection<LearningPathTransition>(fromNode, transitions); fromNode.transitions = new Collection<LearningPathTransition>(fromNode, transitions);

View file

@ -1,4 +1,4 @@
import {getAssignmentRepository, getSubmissionRepository} from '../data/repositories.js'; import {getAssignmentRepository, getGroupRepository, getSubmissionRepository} from '../data/repositories.js';
import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js';
import { NotFoundException } from '../exceptions/not-found-exception.js'; import { NotFoundException } from '../exceptions/not-found-exception.js';
import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js';
@ -37,11 +37,8 @@ export async function createSubmission(submissionDTO: SubmissionDTO): Promise<Su
const submissionRepository = getSubmissionRepository(); const submissionRepository = getSubmissionRepository();
const submission = mapToSubmission(submissionDTO, submitter, group); const submission = mapToSubmission(submissionDTO, submitter, group);
try {
await submissionRepository.save(submission); await submissionRepository.save(submission);
} catch (e) {
"test"
}
return mapToSubmissionDTO(submission); return mapToSubmissionDTO(submission);
} }
@ -69,7 +66,13 @@ export async function getSubmissionsForLearningObjectAndAssignment(
const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); const loId = new LearningObjectIdentifier(learningObjectHruid, language, version);
const assignment = await getAssignmentRepository().findByClassIdAndAssignmentId(classId, assignmentId); const assignment = await getAssignmentRepository().findByClassIdAndAssignmentId(classId, assignmentId);
const submissions = await getSubmissionRepository().findAllSubmissionsForLearningObjectAndAssignment(loId, assignment!, groupId); let submissions: Submission[];
if (groupId !== undefined) {
const group = await getGroupRepository().findByAssignmentAndGroupNumber(assignment!, groupId);
submissions = await getSubmissionRepository().findAllSubmissionsForLearningObjectAndGroup(loId, group!);
} else {
submissions = await getSubmissionRepository().findAllSubmissionsForLearningObjectAndAssignment(loId, assignment!);
}
return submissions.map((s) => mapToSubmissionDTO(s)); return submissions.map((s) => mapToSubmissionDTO(s));
} }

View file

@ -27,7 +27,10 @@ export class SqliteAutoincrementSubscriber implements EventSubscriber {
for (const prop of Object.values(args.meta.properties)) { for (const prop of Object.values(args.meta.properties)) {
const property = prop as EntityProperty<T>; const property = prop as EntityProperty<T>;
if (property.primary && property.autoincrement && !(args.entity as Record<string, unknown>)[property.name]) { if (
property.primary && property.autoincrement
&& (args.entity as Record<string, unknown>)[property.name] === undefined
) {
// Obtain and increment sequence number of this entity. // Obtain and increment sequence number of this entity.
const propertyKey = args.meta.class.name + '.' + property.name; const propertyKey = args.meta.class.name + '.' + property.name;
const nextSeqNumber = this.sequenceNumbersForEntityType.get(propertyKey) || 0; const nextSeqNumber = this.sequenceNumbersForEntityType.get(propertyKey) || 0;

View file

@ -2,10 +2,10 @@
* Convert a Base64-encoded string into a buffer with the same data. * Convert a Base64-encoded string into a buffer with the same data.
* @param base64 The Base64 encoded string. * @param base64 The Base64 encoded string.
*/ */
export function base64ToArrayBuffer(base64: string) { export function base64ToArrayBuffer(base64: string): ArrayBuffer {
var binaryString = atob(base64); const binaryString = atob(base64);
var bytes = new Uint8Array(binaryString.length); const bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; i++) { for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i); bytes[i] = binaryString.charCodeAt(i);
} }
return bytes.buffer; return bytes.buffer;

View file

@ -33,9 +33,10 @@ describe('AssignmentRepository', () => {
it('should find all by username of the responsible teacher', async () => { it('should find all by username of the responsible teacher', async () => {
const result = await assignmentRepository.findAllByResponsibleTeacher('testleerkracht1'); const result = await assignmentRepository.findAllByResponsibleTeacher('testleerkracht1');
const resultIds = result.map((it) => it.id).sort((a, b) => (a ?? 0) - (b ?? 0)); const resultIds = result.map((it) => it.id)
.sort((a, b) => (a ?? 0) - (b ?? 0));
expect(resultIds).toEqual([1, 3, 4]); expect(resultIds).toEqual([1, 1, 3, 4]);
}); });
it('should not find removed assignment', async () => { it('should not find removed assignment', async () => {

View file

@ -91,9 +91,11 @@ describe('SubmissionRepository', () => {
expect(result[2].submissionNumber).toBe(3); expect(result[2].submissionNumber).toBe(3);
}); });
it("should find only the submissions for a certain learning object and assignment made for the user's group", async () => { it("should find only the submissions for a certain learning object and assignment made for the given group", async () => {
const result = await submissionRepository.findAllSubmissionsForLearningObjectAndAssignment(loId, assignment!, 'Tool'); const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 2);
// (student Tool is in group #2) const result = await submissionRepository.findAllSubmissionsForLearningObjectAndGroup(
loId, group!
);
expect(result).toHaveLength(1); expect(result).toHaveLength(1);

View file

@ -41,7 +41,7 @@ describe('LearningObjectRepository', () => {
let newerExample: LearningObject; let newerExample: LearningObject;
it('should allow a learning object with the same id except a different version to be added', async () => { it('should allow a learning object with the same id except a different version to be added', async () => {
let testLearningObject01Newer = structuredClone(testLearningObject01); const testLearningObject01Newer = structuredClone(testLearningObject01);
testLearningObject01Newer.version = 10; testLearningObject01Newer.version = 10;
testLearningObject01Newer.title += " (nieuw)"; testLearningObject01Newer.title += " (nieuw)";
testLearningObject01Newer.uuid = v4(); testLearningObject01Newer.uuid = v4();

View file

@ -3,6 +3,7 @@ import { getLearningPathRepository } from '../../../src/data/repositories';
import { LearningPathRepository } from '../../../src/data/content/learning-path-repository'; import { LearningPathRepository } from '../../../src/data/content/learning-path-repository';
import { setupTestApp } from '../../setup-tests'; import { setupTestApp } from '../../setup-tests';
import { Language } from '@dwengo-1/common/util/language'; import { Language } from '@dwengo-1/common/util/language';
import {testLearningPath01} from "../../test_assets/content/learning-paths.testdata";
describe('LearningPathRepository', () => { describe('LearningPathRepository', () => {
let learningPathRepository: LearningPathRepository; let learningPathRepository: LearningPathRepository;
@ -19,10 +20,12 @@ describe('LearningPathRepository', () => {
}); });
it('should return requested learning path', async () => { it('should return requested learning path', async () => {
const learningPath = await learningPathRepository.findByHruidAndLanguage('id01', Language.English); const learningPath = await learningPathRepository.findByHruidAndLanguage(
testLearningPath01.hruid, testLearningPath01.language as Language
);
expect(learningPath).toBeTruthy(); expect(learningPath).toBeTruthy();
expect(learningPath?.title).toBe('repertoire Tool'); expect(learningPath?.title).toBe(testLearningPath01.title);
expect(learningPath?.description).toBe('all about Tool'); expect(learningPath?.description).toBe(testLearningPath01.description);
}); });
}); });

View file

@ -6,7 +6,6 @@ import { getSubmissionRepository } from '../../../src/data/repositories.js';
import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider.js'; import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider.js';
import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.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 { Language } from '@dwengo-1/common/util/language';
import { import {
@ -79,29 +78,13 @@ describe('DatabaseLearningPathProvider', () => {
it('returns the learning path correctly', async () => { it('returns the learning path correctly', async () => {
const result = await databaseLearningPathProvider.fetchLearningPaths( const result = await databaseLearningPathProvider.fetchLearningPaths(
[testLearningPath.hruid], [testLearningPath.hruid],
testLearningPath.language as Language, testLearningPath.language,
'the source' 'the source'
); );
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.data?.length).toBe(1); expect(result.data?.length).toBe(1);
const learningObjectsOnPath = ( expectToBeCorrectLearningPath(result.data![0], testLearningPathWithConditions);
await Promise.all(
testLearningPath.nodes.map(async (node) =>
learningObjectService.getLearningObjectById({
hruid: node.learningObjectHruid,
version: node.version,
language: node.language,
})
)
)
).filter((it) => it !== null);
expectToBeCorrectLearningPath(
result.data![0],
mapToLearningPath(testLearningPathWithConditions, []),
learningObjectsOnPath
);
}); });
it('returns the correct personalized learning path', async () => { it('returns the correct personalized learning path', async () => {
// For student A: // For student A:

View file

@ -1,6 +1,5 @@
import { AssertionError } from 'node:assert'; import { AssertionError } from 'node:assert';
import { LearningObject } from '../../src/entities/content/learning-object.entity'; import { LearningObject } from '../../src/entities/content/learning-object.entity';
import { LearningPath as LearningPathEntity } from '../../src/entities/content/learning-path.entity';
import { expect } from 'vitest'; import { expect } from 'vitest';
import { FilteredLearningObject, LearningPath } from '@dwengo-1/common/interfaces/learning-content'; import { FilteredLearningObject, LearningPath } from '@dwengo-1/common/interfaces/learning-content';
import {RequiredEntityData} from "@mikro-orm/core"; import {RequiredEntityData} from "@mikro-orm/core";
@ -17,7 +16,7 @@ const IGNORE_PROPERTIES = ['parent'];
export function expectToBeCorrectEntity<T extends object>( export function expectToBeCorrectEntity<T extends object>(
actual: T, actual: T,
expected: T, expected: T,
propertyPrefix: string = "" propertyPrefix = ""
): void { ): void {
for (const property in expected) { for (const property in expected) {
const prefixedProperty = propertyPrefix + property; const prefixedProperty = propertyPrefix + property;
@ -91,55 +90,41 @@ export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearni
* is a correct representation of the given learning path entity. * is a correct representation of the given learning path entity.
* *
* @param learningPath The learning path returned by the retriever, service or endpoint * @param learningPath The learning path returned by the retriever, service or endpoint
* @param expectedEntity The expected entity * @param expected The learning path that should have been returned.
* @param learningObjectsOnPath The learning objects on LearningPath. Necessary since some information in
* the learning path returned from the API endpoint
*/ */
export function expectToBeCorrectLearningPath( export function expectToBeCorrectLearningPath(
learningPath: LearningPath, learningPath: LearningPath,
expectedEntity: LearningPathEntity, expected: LearningPath
learningObjectsOnPath: FilteredLearningObject[]
): void { ): void {
expect(learningPath.hruid).toEqual(expectedEntity.hruid); expect(learningPath.hruid).toEqual(expected.hruid);
expect(learningPath.language).toEqual(expectedEntity.language); expect(learningPath.language).toEqual(expected.language);
expect(learningPath.description).toEqual(expectedEntity.description); expect(learningPath.description).toEqual(expected.description);
expect(learningPath.title).toEqual(expectedEntity.title); expect(learningPath.title).toEqual(expected.title);
const keywords = new Set(learningObjectsOnPath.flatMap((it) => it.keywords || [])); expect(new Set(learningPath.keywords.split(' '))).toEqual(new Set(learningPath.keywords.split(' ')));
expect(new Set(learningPath.keywords.split(' '))).toEqual(keywords);
const targetAges = new Set(learningObjectsOnPath.flatMap((it) => it.targetAges || [])); expect(new Set(learningPath.target_ages)).toEqual(new Set(expected.target_ages));
expect(new Set(learningPath.target_ages)).toEqual(targetAges); expect(learningPath.min_age).toEqual(Math.min(...expected.target_ages));
expect(learningPath.min_age).toEqual(Math.min(...targetAges)); expect(learningPath.max_age).toEqual(Math.max(...expected.target_ages));
expect(learningPath.max_age).toEqual(Math.max(...targetAges));
expect(learningPath.num_nodes).toEqual(expectedEntity.nodes.length); expect(learningPath.num_nodes).toEqual(expected.nodes.length);
expect(learningPath.image || null).toEqual(expectedEntity.image); expect(learningPath.image ?? null).toEqual(expected.image ?? null);
const expectedLearningPathNodes = new Map( for (const node of expected.nodes) {
expectedEntity.nodes.map((node) => [ const correspondingNode = learningPath.nodes.find(it =>
{ learningObjectHruid: node.learningObjectHruid, language: node.language, version: node.version }, node.learningobject_hruid === it.learningobject_hruid && node.language === it.language
{ startNode: node.startNode, transitions: node.transitions }, && node.version === it.version
])
); );
expect(correspondingNode).toBeTruthy();
expect(Boolean(correspondingNode!.start_node)).toEqual(Boolean(node.start_node));
for (const node of learningPath.nodes) { for (const transition of node.transitions) {
const nodeKey = { const correspondingTransition = correspondingNode!.transitions.find(it =>
learningObjectHruid: node.learningobject_hruid, it.next.hruid === transition.next.hruid && it.next.language === transition.next.language
language: node.language, && it.next.version === transition.next.version
version: node.version,
};
expect(expectedLearningPathNodes.keys()).toContainEqual(nodeKey);
const expectedNode = [...expectedLearningPathNodes.entries()].find(
([key, _]) => key.learningObjectHruid === nodeKey.learningObjectHruid && key.language === node.language && key.version === node.version
)![1];
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))
); );
expect(new Set(node.transitions.map((it) => it.next.language))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.language))); expect(correspondingTransition).toBeTruthy();
expect(new Set(node.transitions.map((it) => it.next.version))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.version))); }
} }
} }

View file

@ -2,13 +2,13 @@ import fs from 'fs';
import path from 'node:path'; import path from 'node:path';
import {fileURLToPath} from "node:url"; import {fileURLToPath} from "node:url";
const __filename = fileURLToPath(import.meta.url); const fileName = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const dirName = path.dirname(fileName);
/** /**
* Load the asset at the given path. * Load the asset at the given path.
* @param relPath Path of the asset relative to the test-assets folder. * @param relPath Path of the asset relative to the test-assets folder.
*/ */
export function loadTestAsset(relPath: string): Buffer { export function loadTestAsset(relPath: string): Buffer {
return fs.readFileSync(path.resolve(__dirname, `../test_assets/${relPath}`)); return fs.readFileSync(path.resolve(dirName, `../test_assets/${relPath}`));
} }

View file

@ -75,26 +75,26 @@ let group04: Group;
let group05: Group; let group05: Group;
let group1ConditionalLearningPath: Group; let group1ConditionalLearningPath: Group;
export function getTestGroup01() { export function getTestGroup01(): Group {
return group01; return group01;
} }
export function getTestGroup02() { export function getTestGroup02(): Group {
return group02; return group02;
} }
export function getTestGroup03() { export function getTestGroup03(): Group {
return group03; return group03;
} }
export function getTestGroup04() { export function getTestGroup04(): Group {
return group04; return group04;
} }
export function getTestGroup05() { export function getTestGroup05(): Group {
return group05; return group05;
} }
export function getGroup1ConditionalLearningPath() { export function getGroup1ConditionalLearningPath(): Group {
return group1ConditionalLearningPath; return group1ConditionalLearningPath;
} }

View file

@ -1,5 +1,5 @@
<div class="learning-object-gift"> <div class="learning-object-gift">
<div id="gift-q1" class="gift-question"> <div id="gift-q1" class="gift-question gift-question-type-Essay">
<h2 id="gift-q1-title" class="gift-title">Reflection</h2> <h2 id="gift-q1-title" class="gift-title">Reflection</h2>
<p id="gift-q1-stem" class="gift-stem">Reflect on this learning path. What have you learned today?</p> <p id="gift-q1-stem" class="gift-stem">Reflect on this learning path. What have you learned today?</p>
<textarea id="gift-q1-answer" class="gift-essay-answer"></textarea> <textarea id="gift-q1-answer" class="gift-essay-answer"></textarea>

View file

@ -1,5 +1,5 @@
<div class="learning-object-gift"> <div class="learning-object-gift">
<div id="gift-q1" class="gift-question"> <div id="gift-q1" class="gift-question gift-question-type-MC">
<h2 id="gift-q1-title" class="gift-title">Self-evaluation</h2> <h2 id="gift-q1-title" class="gift-title">Self-evaluation</h2>
<p id="gift-q1-stem" class="gift-stem">Are you following along well?</p> <p id="gift-q1-stem" class="gift-stem">Are you following along well?</p>
<div class="gift-choice-div"> <div class="gift-choice-div">

View file

@ -176,7 +176,7 @@ export const testLearningPathWithConditions: LearningPathDTO = {
title: 'Example learning path with conditional transitions', title: 'Example learning path with conditional transitions',
description: 'This learning path was made for the purpose of testing conditional transitions', description: 'This learning path was made for the purpose of testing conditional transitions',
keywords: "test", keywords: "test",
target_ages: [18, 19, 20, 21], target_ages: [10, 11, 12, 13, 14, 15, 16, 17, 18],
nodes: [ nodes: [
{ {
learningobject_hruid: testLearningObjectMultipleChoice.hruid, learningobject_hruid: testLearningObjectMultipleChoice.hruid,

View file

@ -24,5 +24,5 @@ export function makeTestStudents(em: EntityManager): Student[] {
} }
export function getTestleerling1(): Student { export function getTestleerling1(): Student {
return testStudents.find(it => it.username == "testleerling1"); return testStudents.find(it => it.username === "testleerling1");
} }