[Backend][CourseStatistics] Change student count calculation logic to respect selected year

This commit is contained in:
Aaro Koivukangas
2026-07-20 10:50:09 +03:00
parent d7c0cf975a
commit f139f13e3b
5 changed files with 340 additions and 136 deletions
+9 -2
View File
@@ -56,7 +56,7 @@ export type CourseYearlyStatsResBody = Awaited<ReturnType<typeof getCourseYearly
router.get<never, CanError<CourseYearlyStatsResBody>, CourseYearlyStatsReqBody, CourseYearlyStatsQuery>(
'/courseyearlystats',
async (req, res) => {
const { codes, combineSubstitutions, separate } = req.query
const { codes, combineSubstitutions, separate, fromYear, toYear } = req.query
const courseCodes = handleQueryArrays(codes)
@@ -82,7 +82,14 @@ router.get<never, CanError<CourseYearlyStatsResBody>, CourseYearlyStatsReqBody,
const useCombineSubstitutions = combineSubstitutions !== 'false'
const useSeparate = separate === 'true'
const results = await getCourseYearlyStats(courseCodes, useSeparate, anonymizationSalt, useCombineSubstitutions)
const results = await getCourseYearlyStats(
courseCodes,
useSeparate,
anonymizationSalt,
useCombineSubstitutions,
fromYear,
toYear
)
return res.json(results)
}
)
@@ -14,7 +14,7 @@ import {
} from '../../models'
import { getIsOpen } from './helpers'
export const getCreditsForCourses = async (codeGroups: string[][], unification: Unification) => {
export const getCreditsForCourses = async (codeGroups: string[][], unification: Unification, from: Date, to: Date) => {
const allCourseCodes = codeGroups.flatMap(group => group)
// We need the credits grouped by student numbers so that we can check if a student has
@@ -41,6 +41,7 @@ export const getCreditsForCourses = async (codeGroups: string[][], unification:
},
is_open: getIsOpen(unification),
credittypecode: { [Op.not]: CreditTypeCode.IMPROVED }, // We do not care about improved grades
attainment_date: { [Op.between]: [from, to] },
},
order: [['attainment_date', 'ASC']],
include: [
@@ -134,7 +135,12 @@ export const getStudentNumberToSrElementsMap = async (studentNumbers: string[])
}, {})
}
export const getEnrollmentsForCourses = async (codeGroups: string[][], unification: Unification) => {
export const getEnrollmentsForCourses = async (
codeGroups: string[][],
unification: Unification,
from: Date,
to: Date
) => {
const allCourseCodes = codeGroups.flatMap(group => group)
const students = await StudentModel.findAll({
@@ -160,8 +166,13 @@ export const getEnrollmentsForCourses = async (codeGroups: string[][], unificati
course_code: {
[Op.in]: allCourseCodes,
},
// Date when OK changed from Oodi to Sisu data, studyright_id is null before that date
enrollment_date_time: { [Op.gte]: enrollmentTimeDateThreshold },
enrollment_date_time: {
[Op.and]: {
[Op.between]: [from, to],
// Date when OK changed from Oodi to Sisu data, studyright_id is null before that date
[Op.gte]: enrollmentTimeDateThreshold,
},
},
state: EnrollmentState.ENROLLED,
is_open: getIsOpen(unification),
},
+33 -11
View File
@@ -80,7 +80,7 @@ const parseCredit = (
studyRightElements.find(studyRightElement => studyRightElement.studyRightId === studyRightId) ??
studyRightElements
.filter(({ startDate, endDate }) => dateIsBetween(attainmentDate, startDate, endDate))
.sort((a, b) => b.startDate.getTime() - a.startDate.getTime())
.sort((a, b) => (b.endDate?.getTime() ?? 0) - (a.endDate?.getTime() ?? 0))
.at(0) // The newest studyRightElement
const programme = programmeOfCredit
@@ -184,7 +184,9 @@ const getYearlyStatsOfNew = async (
unification: Unification,
anonymizationSalt: string | null,
combineSubstitutions: boolean,
studentNumberToSrElementsMap: Record<string, SISStudyRightElementModel[]>
studentNumberToSrElementsMap: Record<string, SISStudyRightElementModel[]>,
from: Date,
to: Date
) => {
// Includes main course code and substitutions (if enabled)
const creditGroupCodes =
@@ -209,8 +211,8 @@ const getYearlyStatsOfNew = async (
const { semesters, years } = await getSemestersAndYears()
const [creditGroups, enrollmentGroups] = await Promise.all([
getCreditsForCourses(filteredCreditGroupCodes, unification),
getEnrollmentsForCourses(filteredCreditGroupCodes, unification),
getCreditsForCourses(filteredCreditGroupCodes, unification, from, to),
getEnrollmentsForCourses(filteredCreditGroupCodes, unification, from, to),
])
const counter = new CourseYearlyStatsCounter()
@@ -350,12 +352,21 @@ export const getCourseYearlyStats = async (
courseCodes: string[],
separate: boolean,
anonymizationSalt: string | null,
combineSubstitutions: boolean
combineSubstitutions: boolean,
fromYear = '1900',
toYear: string = new Date().getFullYear().toString()
) => {
// Default to 1900 - currentYear+1 so that without parameters the api returns stats for all years
const from = new Date(`${fromYear}-08-01`) // FALL
const to = new Date(`${parseInt(toYear) + 1}-07-31`) // SPRING next year
const [credits, enrollments] = await Promise.all([
CreditModel.findAll({
attributes: ['student_studentnumber'],
where: { course_code: { [Op.in]: courseCodes } },
where: {
course_code: { [Op.in]: courseCodes },
attainment_date: { [Op.between]: [from, to] },
},
}),
EnrollmentModel.findAll({
attributes: ['studentnumber'],
@@ -364,7 +375,12 @@ export const getCourseYearlyStats = async (
[Op.in]: courseCodes,
},
state: EnrollmentState.ENROLLED,
enrollment_date_time: { [Op.gte]: enrollmentTimeDateThreshold },
enrollment_date_time: {
[Op.and]: {
[Op.between]: [from, to],
[Op.gte]: enrollmentTimeDateThreshold,
},
},
},
}),
])
@@ -384,9 +400,9 @@ export const getCourseYearlyStats = async (
const statsRegular = await Promise.all(
courseCodes.map(async courseCode => {
const course: Pick<CourseModel, 'code' | 'name' | 'substitution_groups'> | null = await CourseModel.findOne({
raw: true,
attributes: ['code', 'name', 'substitution_groups'],
where: { code: courseCode },
raw: true,
})
if (!course) {
@@ -402,7 +418,9 @@ export const getCourseYearlyStats = async (
Unification.OPEN,
anonymizationSalt,
combineSubstitutions,
studentNumberToSrElementsMap
studentNumberToSrElementsMap,
from,
to
),
getYearlyStatsOfNew(
course,
@@ -411,7 +429,9 @@ export const getCourseYearlyStats = async (
Unification.REGULAR,
anonymizationSalt,
combineSubstitutions,
studentNumberToSrElementsMap
studentNumberToSrElementsMap,
from,
to
),
getYearlyStatsOfNew(
course,
@@ -420,7 +440,9 @@ export const getCourseYearlyStats = async (
Unification.UNIFY,
anonymizationSalt,
combineSubstitutions,
studentNumberToSrElementsMap
studentNumberToSrElementsMap,
from,
to
),
])
@@ -6,7 +6,7 @@ import { Unarray } from '@oodikone/shared/types'
import { CourseYearlyStatsResBody } from '../../../src/routes/courses'
import { initTests, ResponseWithBody } from '../../utils'
void describe('Course yearly statistics', () => {
void describe('Course yearly statistics (2016-2023)', () => {
let app: Express
beforeAll(async () => {
app = await initTests()
@@ -27,7 +27,7 @@ void describe('Course yearly statistics', () => {
let body: Unarray<CourseYearlyStatsResBody>
beforeAll(async () => {
const res = (await request(app)
.get('/courseyearlystats?codes=TKT10002&combineSubstitutions=false')
.get('/courseyearlystats?codes=TKT10002&combineSubstitutions=false&fromYear=2016&toYear=2023')
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CourseYearlyStatsResBody>
@@ -41,7 +41,7 @@ void describe('Course yearly statistics', () => {
)
})
it('(statistic years)', () => {
it('for statistic years', () => {
assert.strictEqual(body.unifyStats!.statistics.length, 8, 'unifyStats years')
assert.strictEqual(body.regularStats!.statistics.length, 8, 'regularStats years')
assert.strictEqual(body.openStats!.statistics.length, 4, 'openStats years')
@@ -52,121 +52,90 @@ void describe('Course yearly statistics', () => {
assert.strictEqual(Object.keys(body.openStats!.facultyStats).length, 4, 'openStats years')
})
describe('(statistic contents)', () => {
it('- 2016-2017', () => {
const stats2016 = body.unifyStats!.statistics.find(year => year.name === '2016-2017')!
assert.deepStrictEqual(
stats2016.students.studentNumbers,
['484144'],
'Incorrect students marked to 2016 stats (students)'
)
// TODO: Rewrite /courseyearlystats so that total = passed + failed + enrolledNoGrade
it.each([
[
'2016-2017',
{ total: 1, passed: 1, failed: 0, enrolledNoGrade: 0 },
{ 5: 0, 4: 1, 3: 0, 2: 0, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2017-2018',
{ total: 23, passed: 19, failed: 4, enrolledNoGrade: 0 },
{ 5: 11, 4: 5, 3: 2, 2: 1, 1: 0, 0: 0, 'Hyl.': 4, 'Hyv.': 0 },
],
[
'2018-2019',
{ total: 28, passed: 26, failed: 2, enrolledNoGrade: 0 },
{ 5: 18, 4: 3, 3: 2, 2: 1, 1: 2, 0: 2, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2019-2020',
{ total: 28, passed: 28, failed: 0, enrolledNoGrade: 0 },
{ 5: 17, 4: 5, 3: 1, 2: 4, 1: 1, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2020-2021',
{ total: 23, passed: 23, failed: 0, enrolledNoGrade: 0 },
{ 5: 18, 4: 2, 3: 0, 2: 2, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 1 },
],
[
'2021-2022',
{ total: 15, passed: 15, failed: 0, enrolledNoGrade: 20 },
{ 5: 13, 4: 2, 3: 0, 2: 0, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2022-2023',
{ total: 27, passed: 27, failed: 0, enrolledNoGrade: 34 },
{ 5: 21, 4: 6, 3: 0, 2: 0, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2023-2024',
{ total: 1, passed: 1, failed: 0, enrolledNoGrade: 7 },
{ 5: 1, 4: 0, 3: 0, 2: 0, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
])('for statistic content during ($0)', (year, categories, grades) => {
const stats = body.unifyStats!.statistics.find(yearStats => yearStats.name === year)!
assert('enrollments' in stats, 'Missing field enrollment in statsitics')
assert.deepStrictEqual(stats2016.attempts.grades['4'], ['484144'], 'Incorrect grades marked to 2016 stats (4)')
if (categories) {
assert.strictEqual(
stats2016.attempts.categories.passed.length,
1,
'Incorrect students marked as passed to 2016 stats (passed)'
stats.attempts.categories.failed.length,
categories.failed,
`Incorrect amount of failed students for ${year}`
)
assert.strictEqual(
stats2016.attempts.categories.failed.length,
0,
'Incorrect students marked as failed to 2016 stats (failed)'
)
assert('enrollments' in stats2016, 'Missing field enrollment in statsitics')
assert.strictEqual(stats2016.enrollments.length, 0, 'Enrollments found when none should exist')
assert.strictEqual(stats2016.allEnrollments.length, 0, 'Enrollments found when none should exist')
})
it('- 2017-2018', () => {
const stats2017 = body.unifyStats!.statistics.find(year => year.name === '2017-2018')!
assert.strictEqual(stats2017.attempts.grades['5'].length, 11, 'Incorrect grades marked to 2017 stats (5)')
assert.strictEqual(stats2017.attempts.grades['4'].length, 5, 'Incorrect grades marked to 2017 stats, (4)')
assert.strictEqual(stats2017.attempts.grades['3'].length, 2, 'Incorrect grades marked to 2017 stats (3)')
assert.strictEqual(stats2017.attempts.grades['2'].length, 1, 'Incorrect grades marked to 2017 stats (2)')
assert(!('1' in stats2017.attempts.grades), 'Incorrect grades marked to 2017 stats (1)')
assert.strictEqual(stats2017.attempts.grades['Hyl.'].length, 4, 'Incorrect grades marked to 2017 stats (Hyl.)')
assert.strictEqual(stats2017.students.studentNumbers.length, 23, 'Incorrect amount of total students 2017')
assert.strictEqual(
stats2017.attempts.categories.passed.length,
11 + 5 + 2 + 1 + 0,
'Incorrect students marked as passed to 2017 stats'
stats.attempts.categories.passed.length,
categories.passed,
`Incorrect amount of passed students for ${year}`
)
assert.strictEqual(
stats2017.attempts.categories.failed.length,
4,
'Incorrect students marked as failed to 2017 stats'
stats.students.studentNumbers.length,
categories.total,
`Incorrect amount of total students for ${year}`
)
// These should not exist before 21-22
assert('enrollments' in stats2017, 'Missing field enrollment in statsitics')
assert.strictEqual(stats2017.enrollments.length, 0, 'Enrollments found when none should exist')
assert.strictEqual(stats2017.allEnrollments.length, 0, 'Enrollments found when none should exist')
})
it('- 2018-2019', () => {
const stats2018 = body.unifyStats!.statistics.find(year => year.name === '2018-2019')!
// There are more students that have passed this course later even though they have failed the course during 2018-2019
assert.deepStrictEqual(
stats2018.attempts.categories.failed,
['454128', '546124'],
'Incorrect students marked as failed 2018'
assert.strictEqual(
stats.enrollments.length,
categories.enrolledNoGrade,
`Incorrect amount of enrolled students for ${year}`
)
assert.strictEqual(stats2018.attempts.grades['0'].length, 2)
assert.strictEqual((stats2018.attempts.grades['Hyl.'] ?? []).length, 0)
assert.strictEqual(stats2018.attempts.categories.failed.length, 2, 'Incorrect amount of failed students 2018')
})
it('- 2020-2021', () => {
const stats2020 = body.unifyStats!.statistics.find(year => year.name === '2020-2021')!
// These should not exist before 21-22
assert('enrollments' in stats2020, 'Missing field enrollment in statsitics')
assert.strictEqual(stats2020.enrollments.length, 0, 'Enrollments found when none should exist')
assert.strictEqual(stats2020.allEnrollments.length, 0, 'Enrollments found when none should exist')
})
it('- 2022-2023', () => {
const stats2022 = body.unifyStats!.statistics.find(year => year.name === '2022-2023')!
assert('enrollments' in stats2022, 'Missing field enrollment in statsitics')
assert.strictEqual(stats2022.students.studentNumbers.length, 27, 'Incorrect amount of total students 2022')
assert.strictEqual(stats2022.enrollments.length, 34, 'Enrollments found when none should exist')
assert.strictEqual(stats2022.allEnrollments.length, 43, 'Enrollments found when none should exist')
})
it('- 2023-2024', () => {
const stats2023 = body.unifyStats!.statistics.find(year => year.name === '2023-2024')!
assert.strictEqual(stats2023.students.studentNumbers.length, 1, 'Incorrect amount of total students 2023')
assert.deepStrictEqual(
stats2023.attempts.categories.passed,
['521757'],
'Incorrect students marked as passed in 2023 stats'
}
if (grades) {
Object.entries(grades).forEach(([grade, count]) =>
assert.strictEqual(
stats.attempts.grades[grade]?.length ?? 0,
count,
`Incorrect amount of students with grade ${grade}`
)
)
assert.deepStrictEqual(
stats2023.attempts.grades['5'],
['521757'],
"Correct student doesn't exist under correct grade"
)
assert('enrollments' in stats2023, 'Missing field enrollment in statsitics')
assert.strictEqual(stats2023.enrollments.length, 7, 'Enrollments found when none should exist')
assert.strictEqual(stats2023.allEnrollments.length, 7, 'Enrollments found when none should exist')
})
}
})
describe('should work correctly in specific cases (TKT10002)', () => {
let body: Unarray<CourseYearlyStatsResBody>
beforeAll(async () => {
const res = (await request(app)
.get('/courseyearlystats?codes=TKT10002&combineSubstitutions=false')
.get('/courseyearlystats?codes=TKT10002&combineSubstitutions=false&fromYear=2016&toYear=2023')
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CourseYearlyStatsResBody>
@@ -423,24 +392,24 @@ void describe('Course yearly statistics', () => {
})
describe('should work correctly in specific cases (MAT21003)', () => {
let body: Unarray<CourseYearlyStatsResBody>
beforeAll(async () => {
const res = (await request(app)
.get('/courseyearlystats?codes=MAT21003&combineSubstitutions=false')
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CourseYearlyStatsResBody>
describe('should not count duplicate failed grades to different years when choosing a large timespan', () => {
let body: Unarray<CourseYearlyStatsResBody>
beforeAll(async () => {
const res = (await request(app)
.get('/courseyearlystats?codes=MAT21003&combineSubstitutions=false')
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CourseYearlyStatsResBody>
assert.strictEqual(res.status, 200)
assert.strictEqual(res.body.length, 1)
body = res.body.at(0)!
assert(
'unifyStats' in body && 'regularStats' in body && 'openStats' in body,
'All keys of courseyearlystats not defined'
)
})
assert.strictEqual(res.status, 200)
assert.strictEqual(res.body.length, 1)
body = res.body.at(0)!
assert(
'unifyStats' in body && 'regularStats' in body && 'openStats' in body,
'All keys of courseyearlystats not defined'
)
})
describe('should not count duplicate failed grades to different years', () => {
it('- 2017-2018 should not include failed grade', () => {
const year = body.unifyStats?.statistics.find(year => year.name === '2017-2018')
assert.strictEqual(year?.attempts.categories.failed.length, 0, 'Failed stats should not include any students')
@@ -463,3 +432,196 @@ void describe('Course yearly statistics', () => {
it.todo('should return correct amount of students for multiple coursecodes without substitutions')
it.todo('should return correct amount of students for multiple coursecodes with substitutions')
})
describe('Course yearly statistics (smaller timeframes)', () => {
let app: Express
beforeAll(async () => {
app = await initTests()
})
it.each([
['2017-2018', 2017, 2017],
['2020-2023', 2020, 2022],
['2016-2024', 2016, 2023],
])('should include correct years for smaller timeframe (MAT21003 $0)', async (name, from, to) => {
const res = (await request(app)
.get(`/courseyearlystats?codes=MAT21003&combineSubstitutions=false&fromYear=${from}&toYear=${to}`)
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CourseYearlyStatsResBody>
assert.strictEqual(res.status, 200)
assert.strictEqual(res.body.length, 1)
const body = res.body.at(0)!
const years: string[] = []
for (let i = from; i <= to; i++) {
years.push(`${i}-${i + 1}`)
}
assert.deepStrictEqual(
body.unifyStats!.statistics.map(({ name }) => name),
years,
`${name} included incorrect years`
)
})
// TODO: Rewrite /courseyearlystats so that total = passed + failed + enrolledNoGrade
it.each([
[
'2016-2017',
{ total: 1, passed: 1, failed: 0, enrolledNoGrade: 0, extra: {} },
{ 5: 0, 4: 1, 3: 0, 2: 0, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2017-2018',
{
total: 26,
passed: 19,
failed: 7,
enrolledNoGrade: 0,
extra: { failed: ['455478', '457686', '474032', '497183', '509165', '547994', '550840'] },
},
{ 5: 11, 4: 5, 3: 2, 2: 1, 1: 0, 0: 0, 'Hyl.': 7, 'Hyv.': 0 },
],
[
'2018-2019',
{ total: 30, passed: 26, failed: 4, enrolledNoGrade: 0, extra: {} },
{ 5: 18, 4: 3, 3: 2, 2: 1, 1: 2, 0: 3, 'Hyl.': 1, 'Hyv.': 0 },
],
[
'2019-2020',
{ total: 28, passed: 28, failed: 0, enrolledNoGrade: 0, extra: {} },
{ 5: 17, 4: 5, 3: 1, 2: 4, 1: 1, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2020-2021',
{ total: 23, passed: 23, failed: 0, enrolledNoGrade: 0, extra: {} },
{ 5: 18, 4: 2, 3: 0, 2: 2, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 1 },
],
[
'2021-2022',
{ total: 15, passed: 15, failed: 0, enrolledNoGrade: 20, extra: {} },
{ 5: 13, 4: 2, 3: 0, 2: 0, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2022-2023',
{ total: 27, passed: 27, failed: 0, enrolledNoGrade: 34, extra: {} },
{ 5: 21, 4: 6, 3: 0, 2: 0, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
[
'2023-2024',
{ total: 1, passed: 1, failed: 0, enrolledNoGrade: 7, extra: {} },
{ 5: 1, 4: 0, 3: 0, 2: 0, 1: 0, 0: 0, 'Hyl.': 0, 'Hyv.': 0 },
],
])('should include correct stats for one academic year (TKT10002, $0)', async (year, categories, grades) => {
const res = (await request(app)
.get(
`/courseyearlystats?codes=TKT10002&combineSubstitutions=false&fromYear=${year.split('-').at(0)}&toYear=${parseInt(year.split('-').at(1)!) - 1}`
)
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CourseYearlyStatsResBody>
assert.strictEqual(res.status, 200)
assert.strictEqual(res.body.length, 1)
const body = res.body.at(0)!
assert(
'unifyStats' in body && 'regularStats' in body && 'openStats' in body,
'All keys of courseyearlystats not defined'
)
const stats = body.unifyStats!.statistics.find(yearStats => yearStats.name === year)!
assert('enrollments' in stats, 'Missing field enrollment in statsitics')
if (Object.keys(categories.extra).length) {
Object.keys(categories.extra).forEach(category => {
assert.deepStrictEqual(
stats.attempts.categories[category].sort(),
categories.extra[category].sort(),
`"${category}" included incorrect students`
)
})
}
if (categories) {
assert.strictEqual(
stats.attempts.categories.failed.length,
categories.failed,
`Incorrect amount of failed students for ${year}`
)
assert.strictEqual(
stats.attempts.categories.passed.length,
categories.passed,
`Incorrect amount of passed students for ${year}`
)
assert.strictEqual(
stats.students.studentNumbers.length,
categories.total,
`Incorrect amount of total students for ${year}`
)
assert.strictEqual(
stats.enrollments.length,
categories.enrolledNoGrade,
`Incorrect amount of enrolled students for ${year}`
)
}
if (grades) {
Object.entries(grades).forEach(([grade, count]) =>
assert.strictEqual(
stats.attempts.grades[grade]?.length ?? 0,
count,
`Incorrect amount of students with grade ${grade}`
)
)
}
})
describe('should count duplicate grades if an attainment falls out of the timespan (MAT21003)', () => {
it('- 2017-2018 should include a failed grade', async () => {
const res = (await request(app)
.get('/courseyearlystats?codes=MAT21003&combineSubstitutions=false&fromYear=2017&toYear=2017')
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CourseYearlyStatsResBody>
assert.strictEqual(res.status, 200)
assert.strictEqual(res.body.length, 1)
const body = res.body.at(0)!
assert(
'unifyStats' in body && 'regularStats' in body && 'openStats' in body,
'All keys of courseyearlystats not defined'
)
const year = body.unifyStats!.statistics.find(year => year.name === '2017-2018')!
assert.deepStrictEqual(year.attempts.categories.failed, ['539036'], 'Failed stats should have include a students')
assert.strictEqual(
year.attempts.categories.failed.length,
1,
'Failed stats should have included a failed student'
)
})
it('- 2018-2019 should include a failed grade', async () => {
const res = (await request(app)
.get('/courseyearlystats?codes=MAT21003&combineSubstitutions=false&fromYear=2018&toYear=2018')
.set('shib-session-id', 'test')
.set('uid', 'basic')
.set('hygroupcn', 'grp-oodikone-basic-users')) as ResponseWithBody<CourseYearlyStatsResBody>
assert.strictEqual(res.status, 200)
assert.strictEqual(res.body.length, 1)
const body = res.body.at(0)!
assert(
'unifyStats' in body && 'regularStats' in body && 'openStats' in body,
'All keys of courseyearlystats not defined'
)
const year = body.unifyStats?.statistics.find(year => year.name === '2018-2019')
assert.deepStrictEqual(
year?.attempts.categories.failed,
['539036', '540698', '542927', '544688'],
'Failed stats included the incorrect students'
)
assert.strictEqual(year?.attempts.categories.failed.length, 4, 'Failed stats should include only one student')
})
})
})
+2
View File
@@ -14,4 +14,6 @@ export type CourseYearlyStatsQuery = {
codes: string[]
separate: string
combineSubstitutions: string
fromYear: string
toYear: string
}