mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
api tests and caching department stats
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
2
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"no-console": 0,
|
||||
"indent": ["error", 2, { "MemberExpression": 1 }]
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
.vscode
|
||||
src/tester.js
|
||||
.nyc_output
|
||||
src/tester.js
|
||||
|
||||
Generated
+2453
-113
File diff suppressed because it is too large
Load Diff
+8
-1
@@ -5,13 +5,16 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "nodemon index.js",
|
||||
"test": "NODE_ENV=test ava"
|
||||
"test": "NODE_ENV=test ava",
|
||||
"lint": "eslint --parser babel-eslint src",
|
||||
"coverage": "NODE_ENV=test nyc ava"
|
||||
},
|
||||
"author": "Matti Luukkainen",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^0.16.2",
|
||||
"bcrypt": "^1.0.3",
|
||||
"bluebird": "^3.5.1",
|
||||
"body-parser": "^1.18.2",
|
||||
"cors": "^2.8.4",
|
||||
"express": "^4.16.2",
|
||||
@@ -22,11 +25,15 @@
|
||||
"npm": "^5.4.2",
|
||||
"pg": "^7.3.0",
|
||||
"pg-hstore": "^2.3.2",
|
||||
"redis": "^2.8.0",
|
||||
"sequelize": "^4.13.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^0.22.0",
|
||||
"babel-eslint": "^8.0.1",
|
||||
"eslint": "^4.9.0",
|
||||
"nodemon": "^1.12.1",
|
||||
"nyc": "^11.2.1",
|
||||
"supertest": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+62
-23
@@ -24,11 +24,12 @@ async function authorizer (username, password, cb) {
|
||||
return cb(null, bcrypt.compareSync(password, hash))
|
||||
}
|
||||
|
||||
app.use(basicAuth( {
|
||||
app.use(
|
||||
basicAuth({
|
||||
authorizer,
|
||||
challenge: true,
|
||||
authorizeAsync: true,
|
||||
unauthorizedResponse: (req) => { error: 'unauthorized' }
|
||||
unauthorizedResponse: () => ({ error: 'unauthorized' })
|
||||
})
|
||||
)
|
||||
|
||||
@@ -42,9 +43,29 @@ const Tag = require('./services/tags')
|
||||
app.get('/api/departmentsuccess', async function (req, res) {
|
||||
const startDate = req.query.date? req.query.date.split('.').join('-'): '2005-08-01'
|
||||
const months = 13
|
||||
const results = await Department.averagesInMonths(startDate, months)
|
||||
|
||||
res.json(results)
|
||||
const redis = require('redis')
|
||||
require('bluebird').promisifyAll(redis.RedisClient.prototype)
|
||||
const client = redis.createClient()
|
||||
const env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development'
|
||||
|
||||
const key = `department-statistics-${startDate}-${months}-${env}`
|
||||
const timeToLive = env==='test' ? 60*60 : 60*60*24*7 // one hour or one week
|
||||
|
||||
try{
|
||||
let results = await client.getAsync(key)
|
||||
if ( results === null ) {
|
||||
results = await Department.averagesInMonths(startDate, months)
|
||||
await client.setAsync(key, JSON.stringify(results), 'EX', timeToLive)
|
||||
} else {
|
||||
results = JSON.parse(results)
|
||||
}
|
||||
|
||||
res.json(results)
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
app.get('/api/students', async function (req, res) {
|
||||
@@ -61,6 +82,21 @@ app.get('/api/students/:id', async function (req, res) {
|
||||
res.json(results)
|
||||
})
|
||||
|
||||
app.post('/api/students/:id/tags', async function (req, res) {
|
||||
const tagname = req.body.text
|
||||
const result = await Student.addTag(req.params.id, tagname)
|
||||
const status = result.error === undefined ? 201 : 400
|
||||
|
||||
res.status(status).json(result)
|
||||
})
|
||||
|
||||
app.delete('/api/students/:id/tags', async function (req, res) {
|
||||
const tagname = req.body.text
|
||||
const result = await Student.deleteTag(req.params.id, tagname)
|
||||
const status = result.error === undefined ? 200 : 400
|
||||
res.status(status).json(result)
|
||||
})
|
||||
|
||||
app.get('/api/courses', async function (req, res) {
|
||||
let results = []
|
||||
if (req.query.name) {
|
||||
@@ -77,7 +113,7 @@ app.post('/api/courselist', async function(req, res) {
|
||||
})
|
||||
|
||||
app.post('/api/coursestatistics', async function(req, res) {
|
||||
const code = req.body.code;
|
||||
const code = req.body.code
|
||||
const date = req.body.date.split('.').join('-')
|
||||
const months = req.body.subsequentMonthsToInvestigate
|
||||
|
||||
@@ -92,7 +128,7 @@ app.post('/api/teacherstatistics', async function(req, res) {
|
||||
const minCourses = req.body.minCourses || 1
|
||||
const minStudents = req.body.minStudents || 1
|
||||
const studyRights = req.body.studyRights || 1
|
||||
|
||||
|
||||
const results = await Teacher.statisticsOf(courses, fromDate, toDate, minCourses, minStudents, studyRights)
|
||||
res.json(results)
|
||||
})
|
||||
@@ -112,41 +148,44 @@ app.get('/api/enrollmentdates', async function(req, res) {
|
||||
})
|
||||
|
||||
app.post('/api/populationstatistics', async function(req, res) {
|
||||
const confFromBody = req.body
|
||||
|
||||
if (confFromBody.maxBirthDate) {
|
||||
confFromBody.maxBirthDate = confFromBody.maxBirthDate.split('.').join('-')
|
||||
}
|
||||
|
||||
if (confFromBody.minBirthDate) {
|
||||
confFromBody.minBirthDate =confFromBody. minBirthDate.split('.').join('-')
|
||||
}
|
||||
|
||||
confFromBody.courses = confFromBody.courses.map(c=>c.code)
|
||||
|
||||
try {
|
||||
const confFromBody = req.body
|
||||
|
||||
if (confFromBody.maxBirthDate) {
|
||||
confFromBody.maxBirthDate = confFromBody.maxBirthDate.split('.').join('-')
|
||||
}
|
||||
|
||||
if (confFromBody.minBirthDate) {
|
||||
confFromBody.minBirthDate =confFromBody. minBirthDate.split('.').join('-')
|
||||
}
|
||||
|
||||
confFromBody.courses = confFromBody.courses.map(c=>c.code)
|
||||
|
||||
const result = await Population.statisticsOf(confFromBody)
|
||||
res.json(result)
|
||||
} catch(e) {
|
||||
res.json({})
|
||||
console.log(e)
|
||||
res.status(400).json({ error: e })
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
app.get('/api/tags', async function(req, res) {
|
||||
results = await Tag.bySeachTerm(req.query.query || '')
|
||||
const results = await Tag.bySeachTerm(req.query.query || '')
|
||||
res.json(results)
|
||||
})
|
||||
|
||||
app.post('/api/tags/:tagname', async function(req, res) {
|
||||
const tagname = req.params.tagname
|
||||
const students = req.body
|
||||
results = await Tag.addToStudents(tagname, students)
|
||||
res.json(results)
|
||||
const results = await Tag.addToStudents(tagname, students)
|
||||
const status = results.error === undefined ? 201 : 400
|
||||
|
||||
res.status(status).json(results)
|
||||
})
|
||||
|
||||
app.get('*', async function (req, res) {
|
||||
const results = { error: "unknown endpoint" }
|
||||
const results = { error: 'unknown endpoint' }
|
||||
res.status(404).json(results)
|
||||
})
|
||||
|
||||
|
||||
+26
-27
@@ -4,8 +4,9 @@ const moment = require('moment')
|
||||
const sequelize = new Sequelize('tkt_oodi', 'tkt_oodi', process.ENV.PW, {
|
||||
host: 'localhost',
|
||||
dialect:'postgres',
|
||||
logging: process.env.NODE_ENV==='test' ? false : true
|
||||
});
|
||||
logging: process.env.NODE_ENV==='test' ? false : true,
|
||||
operatorsAliases: false
|
||||
})
|
||||
|
||||
const Student = sequelize.define('student',
|
||||
{
|
||||
@@ -34,7 +35,7 @@ const Student = sequelize.define('student',
|
||||
tableName: 'student',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
Student.hasNoPreviousStudies = (startDate) => (student) => {
|
||||
const by = (a, b) => moment(a).isSameOrBefore(b) ? -1 : 1
|
||||
@@ -57,7 +58,7 @@ const TagStudent = sequelize.define('tag_student',
|
||||
tableName: 'tag_student',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
TagStudent.removeAttribute('id')
|
||||
|
||||
@@ -72,7 +73,7 @@ const Tag = sequelize.define('tag',
|
||||
tableName: 'tag',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const Credit = sequelize.define('credit',
|
||||
{
|
||||
@@ -92,7 +93,7 @@ const Credit = sequelize.define('credit',
|
||||
tableName: 'credit',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
Credit.inTimeRange = (date, months) => (credit) => {
|
||||
const creditDate = credit.courseinstance.coursedate
|
||||
@@ -108,8 +109,8 @@ Credit.notUnnecessary = (credit) => {
|
||||
Credit.failed = (credit) =>
|
||||
['Luop', 'Hyl.', 'Eisa', '0'].includes(credit.grade)
|
||||
|
||||
Credit.passed = (credit) =>
|
||||
!['Luop', 'Hyl.', 'Eisa', '0'].includes(credit.grade)
|
||||
Credit.passed = (credit) =>
|
||||
!['Luop', 'Hyl.', 'Eisa', '0'].includes(credit.grade)
|
||||
|
||||
const Studyright = sequelize.define('studyright',
|
||||
{
|
||||
@@ -134,7 +135,7 @@ const Studyright = sequelize.define('studyright',
|
||||
tableName: 'studyright',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const CourseInstance = sequelize.define('courseinstance',
|
||||
{
|
||||
@@ -149,7 +150,7 @@ const CourseInstance = sequelize.define('courseinstance',
|
||||
tableName: 'courseinstance',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const Course = sequelize.define('course',
|
||||
{
|
||||
@@ -163,7 +164,7 @@ const Course = sequelize.define('course',
|
||||
tableName: 'course',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const Teacher = sequelize.define('teacher',
|
||||
{
|
||||
@@ -178,7 +179,7 @@ const Teacher = sequelize.define('teacher',
|
||||
tableName: 'teacher',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const CourseTeacher = sequelize.define('courseteacher',
|
||||
{
|
||||
@@ -194,7 +195,7 @@ const CourseTeacher = sequelize.define('courseteacher',
|
||||
tableName: 'courseteacher',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const User = sequelize.define('users',
|
||||
{
|
||||
@@ -209,26 +210,24 @@ const User = sequelize.define('users',
|
||||
tableName: 'users',
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
CourseInstance.belongsTo(Course, {foreignKey: 'course_code', targetKey: 'code'});
|
||||
Course.hasMany(CourseInstance, {foreignKey: 'course_code', targetKey: 'code'});
|
||||
CourseInstance.belongsTo(Course, {foreignKey: 'course_code', targetKey: 'code'})
|
||||
Course.hasMany(CourseInstance, {foreignKey: 'course_code', targetKey: 'code'})
|
||||
|
||||
CourseInstance.hasMany(Credit, {foreignKey: 'courseinstance_id', targetKey: 'id'});
|
||||
Credit.belongsTo(CourseInstance, {foreignKey: 'courseinstance_id', targetKey: 'id'});
|
||||
CourseInstance.hasMany(Credit, {foreignKey: 'courseinstance_id', targetKey: 'id'})
|
||||
Credit.belongsTo(CourseInstance, {foreignKey: 'courseinstance_id', targetKey: 'id'})
|
||||
|
||||
CourseInstance.hasMany(CourseTeacher, {foreignKey: 'courseinstance_id', targetKey: 'id'});
|
||||
CourseInstance.hasMany(CourseTeacher, {foreignKey: 'courseinstance_id', targetKey: 'id'})
|
||||
|
||||
Credit.belongsTo(Student, {foreignKey: 'student_studentnumber', targetKey: 'studentnumber'});
|
||||
Student.hasMany(Credit, {foreignKey: 'student_studentnumber', sourceKey: 'studentnumber'});
|
||||
Credit.belongsTo(Student, {foreignKey: 'student_studentnumber', targetKey: 'studentnumber'})
|
||||
Student.hasMany(Credit, {foreignKey: 'student_studentnumber', sourceKey: 'studentnumber'})
|
||||
|
||||
Student.hasMany(TagStudent, {foreignKey: 'taggedstudents_studentnumber', sourceKey: 'studentnumber'});
|
||||
Tag.hasMany(TagStudent, {foreignKey: 'tags_tagname', sourceKey: 'tagname'});
|
||||
Student.hasMany(TagStudent, {foreignKey: 'taggedstudents_studentnumber', sourceKey: 'studentnumber'})
|
||||
Tag.hasMany(TagStudent, {foreignKey: 'tags_tagname', sourceKey: 'tagname'})
|
||||
|
||||
// TagStudent belongsTo
|
||||
|
||||
Studyright.belongsTo(Student, {foreignKey: 'student_studentnumber', targetKey: 'studentnumber'});
|
||||
Student.hasMany(Studyright, {foreignKey: 'student_studentnumber', sourceKey: 'studentnumber'});
|
||||
Studyright.belongsTo(Student, {foreignKey: 'student_studentnumber', targetKey: 'studentnumber'})
|
||||
Student.hasMany(Studyright, {foreignKey: 'student_studentnumber', sourceKey: 'studentnumber'})
|
||||
|
||||
module.exports = {
|
||||
Student,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const moment = require('moment')
|
||||
const { Student, Credit, CourseInstance, Course, TagStudent, Tag, CourseTeacher, sequelize } = require('../models')
|
||||
const { Student, Credit, CourseInstance, Course, CourseTeacher } = require('../models')
|
||||
const { arrayUnique } = require('../util')
|
||||
const Op = Sequelize.Op;
|
||||
const Op = Sequelize.Op
|
||||
|
||||
const byNameOrCode = (searchTerm) => {
|
||||
return Course.findAll({
|
||||
@@ -64,8 +64,8 @@ const byIds = (ids) => {
|
||||
return Student.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Credit,
|
||||
include: [
|
||||
model: Credit,
|
||||
include: [
|
||||
{
|
||||
model: CourseInstance,
|
||||
}
|
||||
@@ -111,10 +111,10 @@ async function statisticsOf(code, date, months) {
|
||||
|
||||
const creditsAfter = (student) => {
|
||||
return student.credits
|
||||
.filter(Credit.inTimeRange(date, months))
|
||||
.filter(Credit.passed)
|
||||
.filter(Credit.notUnnecessary)
|
||||
.reduce((set, c) => set+c.credits, 0.0)
|
||||
.filter(Credit.inTimeRange(date, months))
|
||||
.filter(Credit.passed)
|
||||
.filter(Credit.notUnnecessary)
|
||||
.reduce((set, c) => set+c.credits, 0.0)
|
||||
}
|
||||
|
||||
const toStudent = (set, student) => {
|
||||
|
||||
+12
-13
@@ -1,7 +1,6 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const moment = require('moment')
|
||||
const { Student, Credit, Studyright, CourseInstance, sequelize } = require('../models')
|
||||
const Op = Sequelize.Op;
|
||||
const { Student, Credit, Studyright, CourseInstance } = require('../models')
|
||||
const Op = Sequelize.Op
|
||||
|
||||
const studyrightsStartedAt = (date) => {
|
||||
return Studyright.findAll({
|
||||
@@ -31,10 +30,10 @@ const studentsStartedAtDepartment = (studyrights, department, startDate) => {
|
||||
}
|
||||
|
||||
return studyrights
|
||||
.filter( departmentMatches )
|
||||
.map( s => s.student )
|
||||
.filter( Student.hasStarted )
|
||||
.filter( Student.hasNoPreviousStudies(startDate) )
|
||||
.filter( departmentMatches )
|
||||
.map( s => s.student )
|
||||
.filter( Student.hasStarted )
|
||||
.filter( Student.hasNoPreviousStudies(startDate) )
|
||||
}
|
||||
|
||||
const averageGradesInMonths = (students, start, months) => {
|
||||
@@ -51,11 +50,11 @@ const averageGradesInMonths = (students, start, months) => {
|
||||
}
|
||||
|
||||
return sum + student.credits
|
||||
.filter(Credit.passed)
|
||||
.filter(Credit.notUnnecessary)
|
||||
.filter(Credit.inTimeRange(start, months))
|
||||
.reduce(notRepeatEntry, [])
|
||||
.reduce((sum, c) => sum+c.credits, 0)
|
||||
.filter(Credit.passed)
|
||||
.filter(Credit.notUnnecessary)
|
||||
.filter(Credit.inTimeRange(start, months))
|
||||
.reduce(notRepeatEntry, [])
|
||||
.reduce((sum, c) => sum+c.credits, 0)
|
||||
}
|
||||
|
||||
return students.reduce(studentsAverageCreditsInMonths, 0.0) / students.length
|
||||
@@ -84,7 +83,7 @@ async function averagesInMonths(startDate, months) {
|
||||
try {
|
||||
const studyrights = await studyrightsStartedAt(startDate)
|
||||
|
||||
result = {}
|
||||
const result = {}
|
||||
departments.forEach( p => {
|
||||
const students = studentsStartedAtDepartment(studyrights, p.name, startDate)
|
||||
|
||||
|
||||
+20
-16
@@ -1,8 +1,7 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const moment = require('moment')
|
||||
const { Studyright, Student, Credit, CourseInstance, Course, TagStudent, Tag, CourseTeacher, Teacher, sequelize } = require('../models')
|
||||
const { Studyright, Student, Credit, CourseInstance, Course, TagStudent, sequelize } = require('../models')
|
||||
const { formatStudent } = require('../services/students')
|
||||
const Op = Sequelize.Op;
|
||||
const Op = Sequelize.Op
|
||||
|
||||
const enrolmentDates = () => {
|
||||
const query = 'SELECT DISTINCT s.dateOfUniversityEnrollment as date FROM Student s'
|
||||
@@ -24,12 +23,11 @@ const byCriteria = (conf) => {
|
||||
if (conf.enrollmentDates && conf.enrollmentDates.length>0) {
|
||||
const enrollmentDateCriterias =
|
||||
conf.enrollmentDates.map( enrollmentDate => (
|
||||
{ // for some reason Op.eq does not work...
|
||||
dateofuniversityenrollment: {
|
||||
[Op.between]: [enrollmentDate, enrollmentDate]
|
||||
}
|
||||
}
|
||||
)
|
||||
{ // for some reason Op.eq does not work...
|
||||
dateofuniversityenrollment: {
|
||||
[Op.between]: [enrollmentDate, enrollmentDate]
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
terms.push({
|
||||
@@ -37,7 +35,7 @@ const byCriteria = (conf) => {
|
||||
})
|
||||
}
|
||||
|
||||
if ( conf.minBirthDate || conf.maxBirthDate ) {
|
||||
if ( conf.minBirthDate || conf.maxBirthDate ) {
|
||||
const minBirthDate = conf.minBirthDate || '1900-01-01'
|
||||
const maxBirthDate = conf.maxBirthDate || `${new Date().getFullYear()}-01-01`
|
||||
terms.push({
|
||||
@@ -170,7 +168,13 @@ const restrictToMonths = (months) => (student) => {
|
||||
const withinTimerange = Credit.inTimeRange(student.dateofuniversityenrollment, months)
|
||||
const creditsWithinTimelimit = student.credits.filter(withinTimerange)
|
||||
|
||||
return {...student, credits: creditsWithinTimelimit }
|
||||
return {
|
||||
studentnumber: student.studentnumber,
|
||||
tag_students: student.tag_students,
|
||||
dateofuniversityenrollment: student.dateofuniversityenrollment,
|
||||
creditcount: student.creditcount,
|
||||
credits: creditsWithinTimelimit
|
||||
}
|
||||
}
|
||||
|
||||
async function studyrightsByKeyword(searchTerm) {
|
||||
@@ -180,17 +184,17 @@ async function studyrightsByKeyword(searchTerm) {
|
||||
}
|
||||
|
||||
async function universityEnrolmentDates() {
|
||||
const [result, metadata] = await enrolmentDates()
|
||||
const [result] = await enrolmentDates()
|
||||
|
||||
return result.map( r => r.date ).filter( d => d ).sort()
|
||||
}
|
||||
|
||||
async function statisticsOf(conf) {
|
||||
const students = (await byCriteria(conf))
|
||||
.filter(bySelectedCourses(conf.courses))
|
||||
.filter(notAmongExcludes(conf))
|
||||
.map(restrictToMonths(conf.monthsToStudy))
|
||||
|
||||
.filter(bySelectedCourses(conf.courses))
|
||||
.filter(notAmongExcludes(conf))
|
||||
.map(restrictToMonths(conf.monthsToStudy))
|
||||
|
||||
return students.map(formatStudent)
|
||||
}
|
||||
|
||||
|
||||
+29
-13
@@ -1,7 +1,7 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const moment = require('moment')
|
||||
const { Student, Credit, CourseInstance, Course, TagStudent, Tag, sequelize } = require('../models')
|
||||
const Op = Sequelize.Op;
|
||||
const Op = Sequelize.Op
|
||||
|
||||
const byId = (id) => {
|
||||
return Student.findOne({
|
||||
@@ -25,6 +25,16 @@ const byId = (id) => {
|
||||
})
|
||||
}
|
||||
|
||||
const tagsOfStudent = (id) => {
|
||||
return TagStudent.findAll({
|
||||
where: {
|
||||
taggedstudents_studentnumber:{
|
||||
[Op.eq]: id
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const byAbreviatedNameOrStudentNumber = (searchTerm) => {
|
||||
return Student.findAll({
|
||||
limit: 10,
|
||||
@@ -46,12 +56,14 @@ const byAbreviatedNameOrStudentNumber = (searchTerm) => {
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
TODO unused?
|
||||
const withCreditsAfter = (ids, date) => {
|
||||
return Student.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Credit,
|
||||
include: [
|
||||
include: [
|
||||
{
|
||||
model: CourseInstance,
|
||||
where: {
|
||||
@@ -70,27 +82,28 @@ const withCreditsAfter = (ids, date) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
const formatStudent = ({studentnumber, dateofuniversityenrollment, creditcount, credits, tag_students}) => {
|
||||
|
||||
const toCourse = ({grade, credits, status, statuscode, courseinstance}) => {
|
||||
|
||||
const toCourse = ({grade, credits, courseinstance}) => {
|
||||
return {
|
||||
course: {
|
||||
code: courseinstance.course_code,
|
||||
name: courseinstance.course.name
|
||||
},
|
||||
date: courseinstance.coursedate,
|
||||
passed: Credit.passed(grade),
|
||||
passed: Credit.passed({grade}),
|
||||
grade,
|
||||
credits
|
||||
}
|
||||
}
|
||||
|
||||
const tagnames = (tag_students) => {
|
||||
if ( tag_students === undefined || tag_students[0].tags_tagname === null ) {
|
||||
if ( tag_students===undefined || tag_students.length===0 || tag_students[0].tags_tagname===null ) {
|
||||
return []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return tag_students.map(t=>t.tags_tagname)
|
||||
}
|
||||
|
||||
@@ -170,8 +183,14 @@ async function bySeachTerm(term) {
|
||||
async function withId(id) {
|
||||
try {
|
||||
const result = await byId(id)
|
||||
// TODO for some reason including tags in query do not work
|
||||
// perhaps it is due to lacking primary key field
|
||||
const tags = await tagsOfStudent(id)
|
||||
result.tag_students = tags.map(t=>t.dataValues)
|
||||
|
||||
return formatStudent(result)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return {
|
||||
error: e
|
||||
}
|
||||
@@ -181,17 +200,15 @@ async function withId(id) {
|
||||
async function addTag(id, tagname) {
|
||||
try {
|
||||
const student = await byId(id)
|
||||
const tag = await findTag(tagname);
|
||||
const tag = await findTag(tagname)
|
||||
|
||||
if (tag === null) {
|
||||
sequelize.close()
|
||||
return {
|
||||
error: `tag '${tagname}' does not exist`
|
||||
}
|
||||
}
|
||||
|
||||
if (student.tag_students.map(t=>t.tags_tagname).includes(tagname)) {
|
||||
sequelize.close()
|
||||
return {
|
||||
error: `tag '${tagname}' already assosiated with student '${id}'`
|
||||
}
|
||||
@@ -209,13 +226,12 @@ async function deleteTag(id, tagname) {
|
||||
try {
|
||||
const tag = await findTagOf(id, tagname)
|
||||
if (tag === null) {
|
||||
sequelize.close()
|
||||
return {
|
||||
error: `tag '${tagname}' is not assosiated with student '${id}'`
|
||||
}
|
||||
}
|
||||
|
||||
const result = await deleteTagStudent(id, tagname)
|
||||
await deleteTagStudent(id, tagname)
|
||||
return {}
|
||||
} catch (e) {
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const moment = require('moment')
|
||||
const { Student, Credit, CourseInstance, Course, TagStudent, Tag, sequelize } = require('../models')
|
||||
const Op = Sequelize.Op;
|
||||
const { TagStudent, Tag } = require('../models')
|
||||
const Op = Sequelize.Op
|
||||
|
||||
const tagsByTerm = (searchTerm) => {
|
||||
return Tag.findAll({
|
||||
@@ -72,7 +71,6 @@ async function addToStudents(tagname, students) {
|
||||
|
||||
return results.map(t=>({student: t.taggedstudents_studentnumber, tag: t.tags_tagname }))
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return {
|
||||
error: e
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const moment = require('moment')
|
||||
const { Student, Studyright, Credit, CourseInstance, Course, TagStudent, Tag, CourseTeacher, Teacher, sequelize } = require('../models')
|
||||
const { Student, Studyright, Credit, CourseInstance, CourseTeacher, Teacher } = require('../models')
|
||||
const { arrayUnique } = require('../util')
|
||||
const Op = Sequelize.Op;
|
||||
const Op = Sequelize.Op
|
||||
|
||||
const courseInstancesInBetween = (courses, fromDate, toDate) => {
|
||||
return CourseInstance.findAll({
|
||||
@@ -110,11 +109,11 @@ async function statisticsOf(courses, fromDate, toDate, minCourses, minStudents,
|
||||
|
||||
|
||||
const totalCredits = creditsWithAskedStudyrights
|
||||
.filter(Credit.passed)
|
||||
.filter(noRepeatedEntry)
|
||||
.filter(Credit.passed)
|
||||
.filter(noRepeatedEntry)
|
||||
|
||||
const students = creditsWithAskedStudyrights
|
||||
.filter(noRepeatedAttendance).length
|
||||
.filter(noRepeatedAttendance).length
|
||||
|
||||
teachers[teacher_id].students += students
|
||||
teachers[teacher_id].courses += 1
|
||||
@@ -152,8 +151,6 @@ async function statisticsOf(courses, fromDate, toDate, minCourses, minStudents,
|
||||
const courseStats = await courseInstancesInBetween(courses, fromDate, toDate)
|
||||
const teacherStats = extractStatsPerTeacherId(courseStats)
|
||||
|
||||
// take into account studyRights
|
||||
|
||||
const teachers = await teachersByIds(Object.keys(teacherStats))
|
||||
return format(teacherStats, teachers)
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const moment = require('moment')
|
||||
const { User, sequelize } = require('../models')
|
||||
const { User } = require('../models')
|
||||
|
||||
const Op = Sequelize.Op;
|
||||
const Op = Sequelize.Op
|
||||
|
||||
const byUsername = (username) => {
|
||||
return User.findOne({
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
function arrayUnique(value, index, self) {
|
||||
return self.indexOf(value) === index;
|
||||
return self.indexOf(value) === index
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
const test = require('ava')
|
||||
const supertest = require('supertest')
|
||||
const app = require('../src/app')
|
||||
const { sequelize } = require('../src/models')
|
||||
|
||||
const api = supertest(app)
|
||||
|
||||
const auth = {
|
||||
username: 'tktl',
|
||||
password: 'CsZerc8TUtR1S95CYOtTaC69'
|
||||
}
|
||||
|
||||
test('should pong when pinged', async t => {
|
||||
const res = await api
|
||||
.get('/ping')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.deepEqual(res.body, {data:"pong"} )
|
||||
})
|
||||
|
||||
|
||||
test('request without basic auth credentials fails', async t => {
|
||||
await api
|
||||
.get('/api/enrollmentdates')
|
||||
.expect(401)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('request with basic nonexisting user fails', async t => {
|
||||
await api
|
||||
.get('/api/enrollmentdates')
|
||||
.auth('nonexistent', 'wrong')
|
||||
.expect(401)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('request with wrong password fails', async t => {
|
||||
await api
|
||||
.get('/api/enrollmentdates')
|
||||
.auth('tktl', 'wrong')
|
||||
.expect(401)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('request with correct credentials succeed', async t => {
|
||||
await api
|
||||
.get('/api/enrollmentdates')
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('Studemts can be searched by searchterm', async t => {
|
||||
const res = await api
|
||||
.get('/api/students')
|
||||
.query({ searchTerm: 'Luukkainen Matti' })
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const students = res.body
|
||||
t.is(students.length, 1)
|
||||
t.is(students[0].studentNumber, '011120775')
|
||||
})
|
||||
@@ -0,0 +1,394 @@
|
||||
const test = require('ava')
|
||||
const supertest = require('supertest')
|
||||
const app = require('../src/app')
|
||||
const api = supertest(app)
|
||||
const { sequelize } = require('../src/models')
|
||||
|
||||
const auth = {
|
||||
username: 'tktl',
|
||||
password: 'CsZerc8TUtR1S95CYOtTaC69'
|
||||
}
|
||||
|
||||
test.before( async () => {
|
||||
sequelize.query(
|
||||
`DELETE
|
||||
FROM tag_student
|
||||
WHERE tags_tagname = 'test3'`
|
||||
)
|
||||
})
|
||||
|
||||
test('should pong when pinged', async t => {
|
||||
const res = await api
|
||||
.get('/ping')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.deepEqual(res.body, {data:'pong'} )
|
||||
})
|
||||
|
||||
|
||||
test('request without basic auth credentials fails', async t => {
|
||||
await api
|
||||
.get('/api/enrollmentdates')
|
||||
.expect(401)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('request with basic nonexisting user fails', async t => {
|
||||
await api
|
||||
.get('/api/enrollmentdates')
|
||||
.auth('nonexistent', 'wrong')
|
||||
.expect(401)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('request with wrong password fails', async t => {
|
||||
await api
|
||||
.get('/api/enrollmentdates')
|
||||
.auth('tktl', 'wrong')
|
||||
.expect(401)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('request with correct credentials succeed', async t => {
|
||||
await api
|
||||
.get('/api/enrollmentdates')
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('students can be searched by searchterm', async t => {
|
||||
const res = await api
|
||||
.get('/api/students')
|
||||
.query({ searchTerm: 'Luukkainen Matti' })
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const students = res.body
|
||||
t.is(students.length, 1)
|
||||
t.is(students[0].studentNumber, '011120775')
|
||||
})
|
||||
|
||||
test('a student credit info is returned with student number', async t => {
|
||||
const res = await api
|
||||
.get('/api/students/011120775')
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const student = res.body
|
||||
|
||||
t.is(student.studentNumber, '011120775')
|
||||
t.is(student.credits, 514)
|
||||
t.is(student.courses.length, 86)
|
||||
t.is(student.tags.length, 2)
|
||||
})
|
||||
|
||||
test('a tagless student credit info is returned with student number', async t => {
|
||||
const res = await api
|
||||
.get('/api/students/011821126')
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const student = res.body
|
||||
|
||||
t.is(student.studentNumber, '011821126')
|
||||
t.is(student.credits, 330)
|
||||
t.is(student.tags.length, 0)
|
||||
})
|
||||
|
||||
|
||||
test('if student already has a tag, it can not be added', async t => {
|
||||
const tagToAdd = { text: 'test' }
|
||||
|
||||
const res = await api
|
||||
.post('/api/students/011120775/tags')
|
||||
.send(tagToAdd)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(400)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.is(res.body.error, 'tag \'test\' already assosiated with student \'011120775\'')
|
||||
})
|
||||
|
||||
test('tag can be added to and deleted from a student', async t => {
|
||||
const tagToAdd = { text: 'test2' }
|
||||
|
||||
let res = await api
|
||||
.post('/api/students/011120775/tags')
|
||||
.send(tagToAdd)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(201)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const expectedResult = {
|
||||
taggedstudents_studentnumber: '011120775',
|
||||
tags_tagname: 'test2'
|
||||
}
|
||||
|
||||
t.deepEqual(res.body, expectedResult)
|
||||
|
||||
res = await api
|
||||
.get('/api/students/011120775')
|
||||
.auth(auth.username, auth.password)
|
||||
|
||||
t.truthy(res.body.tags.includes(tagToAdd.text))
|
||||
|
||||
res = await api
|
||||
.delete('/api/students/011120775/tags')
|
||||
.send(tagToAdd)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
|
||||
res = await api
|
||||
.get('/api/students/011120775')
|
||||
.auth(auth.username, auth.password)
|
||||
|
||||
t.falsy(res.body.tags.includes(tagToAdd.text))
|
||||
})
|
||||
|
||||
test('courses can be searched by a searchterm', async t => {
|
||||
const res = await api
|
||||
.get('/api/courses')
|
||||
.query({ name: 'Ohjelmoinnin' })
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const courses = res.body
|
||||
|
||||
t.is(courses.length, 10)
|
||||
const courseNames = courses.map(c=>c.name.toUpperCase())
|
||||
|
||||
t.truthy( courseNames.every(n=>n.includes('Ohjelmoinnin'.toUpperCase())) )
|
||||
})
|
||||
|
||||
test('instances of a course can be fetched', async t => {
|
||||
const course = {
|
||||
code: '582206'
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post('/api/courselist')
|
||||
.send(course)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const instances = res.body
|
||||
|
||||
t.is(instances.length, 83)
|
||||
t.deepEqual(Object.keys(instances[0]), ['id', 'date', 'fail', 'pass', 'students', 'teachers'])
|
||||
})
|
||||
|
||||
test('statistics of an instance can be fetched', async t => {
|
||||
const course = {
|
||||
code: '581259',
|
||||
date: '2016-06-14',
|
||||
months: 13
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post('/api/coursestatistics')
|
||||
.send(course)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const stats = res.body
|
||||
|
||||
t.deepEqual(Object.keys(stats), ['all', 'pass', 'fail', 'startYear'])
|
||||
t.truthy(Object.keys(stats.all).includes('014013627'))
|
||||
})
|
||||
|
||||
test('teacher statistics can be fetched', async t => {
|
||||
const query = {
|
||||
courses: [{ code:'581259'}],
|
||||
fromDate: '2016.05.01',
|
||||
toDate: '2016.10.01',
|
||||
studyRights: ['Bachelor of Science, Computer Science']
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post('/api/teacherstatistics')
|
||||
.send(query)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const stats = res.body
|
||||
|
||||
t.truthy(stats.length>0)
|
||||
t.deepEqual(Object.keys(stats[0]), ['teacherId', 'credits', 'studentsPassed', 'studentsTeached', 'instancesTeached'])
|
||||
})
|
||||
|
||||
test('populations can be searched by a searchterm', async t => {
|
||||
const res = await api
|
||||
.get('/api/studyrightkeywords')
|
||||
.query({ search: 'computer Science' })
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.is(res.body.length, 40)
|
||||
t.truthy( res.body.every(r=>r.toUpperCase().includes('COMPUTER SCIENCE')) )
|
||||
})
|
||||
|
||||
test('enrollment dates can be fetched', async t => {
|
||||
const res = await api
|
||||
.get('/api/enrollmentdates')
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.is(res.body.length, 722)
|
||||
t.truthy( res.body.includes('2014-09-02') )
|
||||
})
|
||||
|
||||
test('tags can be searched by a searchterm', async t => {
|
||||
const res = await api
|
||||
.get('/api/tags')
|
||||
.query({ query: 'mooc' })
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.is(res.body.length, 2)
|
||||
t.deepEqual( res.body, [ 'mooc-2012', 'mooc-2013'] )
|
||||
})
|
||||
|
||||
test('tags can be added to a set of studets', async t => {
|
||||
const tagToAdd = 'test3'
|
||||
const students = ['011120775', '011311063']
|
||||
|
||||
students.forEach(async (s) => {
|
||||
let res = await api
|
||||
.get(`/api/students/${s}`)
|
||||
.auth(auth.username, auth.password)
|
||||
|
||||
t.falsy(res.body.tags.includes(tagToAdd))
|
||||
})
|
||||
|
||||
await api
|
||||
.post('/api/tags/test3')
|
||||
.send(students)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(201)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
students.forEach(async (s) => {
|
||||
let res = await api
|
||||
.get(`/api/students/${s}`)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.truthy(res.body.tags.includes(tagToAdd))
|
||||
})
|
||||
|
||||
students.forEach(async (s) => {
|
||||
await api
|
||||
.delete(`/api/students/${s}/tags`)
|
||||
.send({text: tagToAdd})
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
})
|
||||
|
||||
students.forEach(async (s) => {
|
||||
let res = await api
|
||||
.get(`/api/students/${s}`)
|
||||
.auth(auth.username, auth.password)
|
||||
|
||||
t.falsy(res.body.tags.includes(tagToAdd))
|
||||
})
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('population statistics can be fetched', async t => {
|
||||
const query = {
|
||||
courses: [],
|
||||
enrollmentDates: [ '2017-01-01', '2016-01-01' ] ,
|
||||
matriculationExamination: '',
|
||||
monthsToStudy: 15,
|
||||
sex: '',
|
||||
studentNumbers: [],
|
||||
studyRights: ['Bachelor of Science, Computer Science'],
|
||||
tags: [],
|
||||
maxBirthDate: '2000-01-01',
|
||||
minBirthDate: '1981-09-01',
|
||||
excludeStudentsThatHaveNotStartedStudies: true,
|
||||
excludeStudentsWithPreviousStudies: false,
|
||||
excludeStudentsWithZeroCredits: true,
|
||||
excludedStudentNumbers: ['014618200'],
|
||||
excludedTags: ['mooc-2012'],
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post('/api/populationstatistics')
|
||||
.send(query)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const stats = res.body
|
||||
|
||||
t.is(stats.length, 5)
|
||||
t.deepEqual(Object.keys(stats[0]).sort(), ['courses', 'tags', 'studentNumber','credits', 'started'].sort())
|
||||
})
|
||||
|
||||
test('population statistics can be fetched with another configuration', async t => {
|
||||
const query = {
|
||||
courses: [ {code: '582507'} ],
|
||||
enrollmentDates: [ '2017-01-01', '2016-01-01' ] ,
|
||||
matriculationExamination: '',
|
||||
monthsToStudy: 15,
|
||||
sex: '',
|
||||
studentNumbers: [ '014195503', '014234985', '014552890', '014619584', '014055834', '014013627' ],
|
||||
studyRights: ['Bachelor of Science, Computer Science'],
|
||||
tags: [],
|
||||
excludeStudentsThatHaveNotStartedStudies: true,
|
||||
excludeStudentsWithPreviousStudies: false,
|
||||
excludeStudentsWithZeroCredits: true,
|
||||
excludedStudentNumbers: ['014055834'],
|
||||
excludedTags: ['mooc-2012'],
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post('/api/populationstatistics')
|
||||
.send(query)
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
const stats = res.body
|
||||
|
||||
t.is(stats.length, 1)
|
||||
t.deepEqual(Object.keys(stats[0]).sort(), ['courses', 'tags', 'studentNumber','credits', 'started'].sort())
|
||||
})
|
||||
|
||||
test('populations can be searched by a searchterm', async t => {
|
||||
const res = await api
|
||||
.get('/api/departmentsuccess')
|
||||
.query({ date: '2005.08.01' })
|
||||
.auth(auth.username, auth.password)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/)
|
||||
|
||||
t.deepEqual(Object.keys(res.body).sort(), ['Chemistry', 'Computer Science', 'Mathematics', 'Physics'].sort())
|
||||
t.truthy(res.body['Computer Science']>20)
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
const test = require('ava')
|
||||
|
||||
const Course = require('../src/services/courses')
|
||||
|
||||
test.only('statistics of an instance are caclulated right', async t => {
|
||||
const expected = {
|
||||
all: {
|
||||
'014248036': 16,
|
||||
'014289817': 5,
|
||||
'014420809': 13,
|
||||
'014464603': 19,
|
||||
'014471764': 5,
|
||||
'014013627': 44,
|
||||
'014195503': 52
|
||||
},
|
||||
pass: {
|
||||
'014248036': 16,
|
||||
'014289817': 5,
|
||||
'014420809': 13,
|
||||
'014471764': 5,
|
||||
'014013627': 44,
|
||||
'014195503': 52
|
||||
},
|
||||
fail: {
|
||||
'014464603': 19
|
||||
},
|
||||
startYear: {
|
||||
'2011': 1, '2013': 2, '2014': 3, '2016': 1
|
||||
}
|
||||
}
|
||||
|
||||
const result = await Course.statisticsOf("581259", "2016-06-14", 13)
|
||||
|
||||
t.deepEqual(result, expected)
|
||||
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
const test = require('ava')
|
||||
|
||||
//const Population = require('../src/services/populations')
|
||||
|
||||
test.only('population statistics are calculated right', async t => {
|
||||
t.pass()
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
const test = require('ava')
|
||||
|
||||
const Teacher = require('../src/services/teachers')
|
||||
|
||||
test('teacher statistics are calculated right', async t => {
|
||||
const expected = [
|
||||
{
|
||||
teacherId: 'Lemström Kjell Michael Bernhard',
|
||||
credits: 6,
|
||||
studentsPassed: 1,
|
||||
studentsTeached: 1,
|
||||
instancesTeached: 1
|
||||
},
|
||||
{
|
||||
teacherId: 'Luukkainen Matti Juhani',
|
||||
credits: 593,
|
||||
studentsPassed: 108,
|
||||
studentsTeached: 118,
|
||||
instancesTeached: 3
|
||||
}
|
||||
]
|
||||
|
||||
const sr = ['Bachelor of Science, Computer Science']
|
||||
const result = await Teacher.statisticsOf(['581259'],'2016-05-01', '2016-10-1', 1, 1, sr)
|
||||
|
||||
t.deepEqual(result, expected)
|
||||
})
|
||||
Reference in New Issue
Block a user