[Tests] Add api and unit tests for Custom population related features

This commit is contained in:
Aaro Koivukangas
2026-08-18 10:26:06 +03:00
parent 2449dc521f
commit c16ad83477
4 changed files with 251 additions and 23 deletions
@@ -0,0 +1,50 @@
import { Express } from 'express'
import request from 'supertest'
import { describe, it, beforeAll, assert } from 'vitest'
import { CanError } from '@oodikone/shared/routes'
import { CustomPopulationByProgrammesResBody } from '@oodikone/shared/routes/populations'
import { initTests, ResponseWithBody } from '../../utils'
void describe('Population statistics by programme codes', () => {
let app: Express
beforeAll(async () => {
app = await initTests()
})
it('should not return anything with missing years', async () => {
const res = (await request(app)
.post('/populationstatisticsbyprogrammecodes')
.send({ programmes: ['KH50_001'] })
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CanError<CustomPopulationByProgrammesResBody>>
assert.deepEqual(res.status, 400)
assert.deepEqual(res.body, { error: 'Programme based populations require years to be defined' })
})
it('should not return any data to unauthorized user', async () => {
const res = (await request(app)
.post('/populationstatisticsbyprogrammecodes')
.send({ programmes: ['MH50_001'], years: ['2021'] })
.set('shib-session-id', 'test')
.set('uid', 'onlyiamrights')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CanError<CustomPopulationByProgrammesResBody>>
assert.deepEqual(res.status, 403)
assert.deepEqual(res.body, { error: 'Trying to request unauthorized students data' })
})
it('should return students in the programmes for authorized user', async () => {
const res = (await request(app)
.post('/populationstatisticsbyprogrammecodes')
.send({ programmes: ['KH50_001'], years: ['2021'] })
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CustomPopulationByProgrammesResBody>
assert.strictEqual(res.status, 200)
assert.notStrictEqual(res.body.students.length, 0)
})
})
@@ -0,0 +1,49 @@
import { Express } from 'express'
import request from 'supertest'
import { describe, it, beforeAll, assert } from 'vitest'
import { CustomPopulationByStudentNumbersResBody } from '@oodikone/shared/routes/populations'
import { initTests, ResponseWithBody } from '../../utils'
void describe('Population statistics by student number', () => {
let app: Express
beforeAll(async () => {
app = await initTests()
})
it('should not return anything with missing parameters', async () => {
const res = await request(app)
.post('/populationstatisticsbystudentnumbers')
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')
assert.deepEqual(res.status, 422)
assert.deepEqual(res.body.error, 'Body should include student numbers')
})
it('should not return anything to unauthorized user', async () => {
const res = await request(app)
.post('/populationstatisticsbystudentnumbers')
.send({ studentNumbers: ['433237', '457144'] })
.set('shib-session-id', 'test')
.set('uid', 'norights')
.set('hygroupcn', 'grp-oodikone-basic-users')
assert.deepEqual(res.status, 200)
assert.deepEqual(res.body.students, [])
})
it('should return students for authorized user', async () => {
const res = (await request(app)
.post('/populationstatisticsbystudentnumbers')
.send({ studentNumbers: ['433237', '457144'] })
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CustomPopulationByStudentNumbersResBody>
assert.strictEqual(res.status, 200)
assert.strictEqual(res.body.students.length, 2)
assert.deepEqual(res.body.discardedStudentNumbers, [])
})
})
@@ -1,23 +0,0 @@
import { Express } from 'express'
import request from 'supertest'
import { describe, it, beforeAll, assert } from 'vitest'
import { initTests } from '../../utils'
void describe('Population statistics by student number', () => {
let app: Express
beforeAll(async () => {
app = await initTests()
})
it('should not return anything with missing parameters', async () => {
const res = await request(app)
.post('/populationstatisticsbystudentnumbers')
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')
assert.deepEqual(res.status, 422)
assert.deepEqual(res.body.error, 'Body should include student numbers')
})
})
@@ -0,0 +1,152 @@
import { assert, describe, it } from 'vitest'
import { calculateExcelData, getHopsCourses } from '@/components/CustomPopulation/courseMatrix'
import { CreditTypeCode } from '@oodikone/shared/types'
import { FormattedStudent, StudentCourse, StudentStudyPlan } from '@oodikone/shared/types/studentData'
const createCourse = (
course_code: string,
credits: number,
passed: boolean,
date: Date,
credittypecode: CreditTypeCode = passed ? CreditTypeCode.PASSED : CreditTypeCode.FAILED
): StudentCourse => ({
course_code,
date,
passed,
grade: passed ? '5' : 'Hyl.',
credits,
isStudyModuleCredit: false,
credittypecode,
language: 'fi',
studyright_id: 'sr-1',
})
const createHops = (includedCourses: string[]): StudentStudyPlan => ({
included_courses: includedCourses,
programme_code: 'TEST',
includedModules: [],
completed_credits: 0,
curriculum_period_id: 'cp-1',
sis_study_right_id: 'sr-1',
})
const createStudent = (overrides: Partial<FormattedStudent>): FormattedStudent => ({
firstnames: 'Testi',
lastname: 'Opiskelija',
started: new Date('2024-08-01'),
studentNumber: '123456789',
credits: 0,
hopsCredits: 0,
name: 'Testi Opiskelija',
gender_code: '1',
email: 'testi@example.com',
secondaryEmail: '',
phoneNumber: '',
updatedAt: new Date(),
tags: [],
studyrightStart: '2024-08-01',
option: null,
birthdate: new Date('2000-01-01'),
sis_person_id: '1',
citizenships: [],
criteriaProgress: {},
curriculumVersion: null,
hasPersonalIdentityCode: false,
transferredStudyright: false,
transferSource: undefined,
studyRights: [],
studyplans: [],
courses: [],
enrollments: [],
...overrides,
})
void describe('getHopsCourses', () => {
void it('returns only passed courses included in the student HOPS', () => {
const student = createStudent({
courses: [
createCourse('A', 5, true, new Date('2024-09-01')),
createCourse('B', 5, true, new Date('2024-10-01')),
createCourse('C', 5, false, new Date('2024-11-01')),
],
studyplans: [createHops(['A', 'C'])],
})
const result = getHopsCourses(student)
assert.equal(result.length, 1)
assert.equal(result[0].course_code, 'A')
})
void it('returns an empty list when the student has no HOPS', () => {
const student = createStudent({
courses: [createCourse('A', 5, true, new Date('2024-09-01'))],
studyplans: [],
})
assert.deepEqual(getHopsCourses(student), [])
})
void it('dedupes multiple completions keeping the latest', () => {
const student = createStudent({
courses: [createCourse('A', 5, true, new Date('2024-09-01')), createCourse('A', 6, true, new Date('2025-01-15'))],
studyplans: [createHops(['A'])],
})
const result = getHopsCourses(student)
assert.equal(result.length, 1)
assert.equal(result[0].credits, 6)
})
})
void describe('calculateExcelData', () => {
void it('builds completed-course rows and aggregates credits per course', () => {
const students = [
createStudent({
studentNumber: '1',
courses: [createCourse('A', 5, true, new Date('2024-09-01'))],
studyplans: [createHops(['A'])],
}),
createStudent({
studentNumber: '2',
courses: [
createCourse('A', 5, true, new Date('2024-09-01')),
createCourse('B', 3, true, new Date('2024-10-01')),
],
studyplans: [createHops(['A', 'B'])],
}),
]
const courseNameByCode = new Map([
['A', 'Course A'],
['B', 'Course B'],
])
const data = calculateExcelData(students, courseNameByCode)
assert.deepEqual(data.completedCoursesRows, [
['1', 'Testi Opiskelija', 'Course A (A)'],
['2', 'Testi Opiskelija', 'Course A (A)', 'Course B (B)'],
])
assert.deepEqual(data.courseCounterRows, [
['A', 'Course A', '2', '10'],
['B', 'Course B', '1', '3'],
])
})
void it('uses an empty name for courses missing from the course map', () => {
const students = [
createStudent({
studentNumber: '1',
courses: [createCourse('RANDOMCODE', 5, true, new Date('2024-09-01'))],
studyplans: [createHops(['RANDOMCODE'])],
}),
]
const data = calculateExcelData(students, new Map())
assert.deepEqual(data.completedCoursesRows, [['1', 'Testi Opiskelija', ' (RANDOMCODE)']])
assert.deepEqual(data.courseCounterRows, [['RANDOMCODE', '', '1', '5']])
})
})