api tests and caching department stats

This commit is contained in:
Matti Luukkainen
2017-10-23 13:37:20 +03:00
parent 6220b7993b
commit cd9b326353
19 changed files with 3122 additions and 304 deletions
+28
View File
@@ -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
View File
@@ -1,3 +1,4 @@
node_modules
.vscode
src/tester.js
.nyc_output
src/tester.js
+2453 -113
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+8 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+2 -4
View File
@@ -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
}
+5 -8
View File
@@ -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) {
+2 -3
View File
@@ -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
View File
@@ -1,5 +1,5 @@
function arrayUnique(value, index, self) {
return self.indexOf(value) === index;
return self.indexOf(value) === index
}
module.exports = {
-73
View File
@@ -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')
})
+394
View File
@@ -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)
})
+36
View File
@@ -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)
})
+7
View File
@@ -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()
})
+27
View File
@@ -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)
})