From cd9b3263532903067181105e2d51352d9875ba84 Mon Sep 17 00:00:00 2001 From: Matti Luukkainen Date: Mon, 23 Oct 2017 13:37:20 +0300 Subject: [PATCH] api tests and caching department stats --- .eslintrc.json | 28 + .gitignore | 3 +- package-lock.json | 2566 ++++++++++++++++++++++++++-- package.json | 9 +- src/app.js | 85 +- src/models/index.js | 53 +- src/services/courses.js | 16 +- src/services/departments.js | 25 +- src/services/populations.js | 36 +- src/services/students.js | 42 +- src/services/tags.js | 6 +- src/services/teachers.js | 13 +- src/services/users.js | 5 +- src/util/index.js | 2 +- test/api-test.js | 73 - test/api_test.js | 394 +++++ test/course_statistics_test.js | 36 + test/population_statistics_test.js | 7 + test/teacher_statistics_test.js | 27 + 19 files changed, 3122 insertions(+), 304 deletions(-) create mode 100644 .eslintrc.json delete mode 100644 test/api-test.js create mode 100644 test/api_test.js create mode 100644 test/course_statistics_test.js create mode 100644 test/population_statistics_test.js create mode 100644 test/teacher_statistics_test.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..29f522e26 --- /dev/null +++ b/.eslintrc.json @@ -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 }] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4a87ad1a1..a6277fbb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .vscode -src/tester.js \ No newline at end of file +.nyc_output +src/tester.js diff --git a/package-lock.json b/package-lock.json index 77df48e98..51b167ad4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -135,6 +135,29 @@ "negotiator": "0.6.1" } }, + "acorn": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", + "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, "ajv": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", @@ -146,6 +169,12 @@ "json-stable-stringify": "1.0.1" } }, + "ajv-keywords": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.0.tgz", + "integrity": "sha1-opbhf3v658HOT34N5T0pyzIWLfA=", + "dev": true + }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -300,12 +329,6 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, - "assertion-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", - "dev": true - }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", @@ -527,6 +550,154 @@ "source-map": "0.5.7" } }, + "babel-eslint": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.0.1.tgz", + "integrity": "sha512-h3moF6PCTQE06UjMMG+ydZSBvZ4Q7rqPE/5WAUOvUyHYUTqxm8JVhjZRiG1avI/tGVOK4BnZLDQapyLzh8DeKg==", + "dev": true, + "requires": { + "babel-code-frame": "7.0.0-beta.0", + "babel-traverse": "7.0.0-beta.0", + "babel-types": "7.0.0-beta.0", + "babylon": "7.0.0-beta.22" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "babel-code-frame": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-7.0.0-beta.0.tgz", + "integrity": "sha512-/xr1ADm5bnTjjN+xwoXb7lF4v2rnxMzNZzFU7h8SxB+qB6+IqSTOOqVcpaPTUC2Non/MbQxS3OIZnJpQ2X21aQ==", + "dev": true, + "requires": { + "chalk": "2.2.0", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-helper-function-name": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-7.0.0-beta.0.tgz", + "integrity": "sha512-DaQccFBBWBEzMdqbKmNXamY0m1yLHJGOdbbEsNoGdJrrU7wAF3wwowtDDPzF0ZT3SqJXPgZW/P2kgBX9moMuAA==", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "7.0.0-beta.0", + "babel-template": "7.0.0-beta.0", + "babel-traverse": "7.0.0-beta.0", + "babel-types": "7.0.0-beta.0" + } + }, + "babel-helper-get-function-arity": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-7.0.0-beta.0.tgz", + "integrity": "sha512-csqAic15/2Vm1951nJxkkL9K8E6ojyNF/eAOjk7pqJlO8kvgrccGNFCV9eDwcGHDPe5AjvJGwVSAcQ5fit9wuA==", + "dev": true, + "requires": { + "babel-types": "7.0.0-beta.0" + } + }, + "babel-messages": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-7.0.0-beta.0.tgz", + "integrity": "sha512-eXdShsm9ZTh9AQhlIaAn6HR3xWpxCnK9ZwIDA9QyjnwTgMctGxHHflw4b4RJ3/ZjTL0Vrmvm0tQXPkp49mTAUw==", + "dev": true + }, + "babel-template": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-7.0.0-beta.0.tgz", + "integrity": "sha512-tmdH+MmmU0F6Ur8humpevSmFzYKbrN3Oru0g5Qyg4R6+sxjnzZmnvzUbsP0aKMr7tB0Ua6xhEb9arKTOsEMkyA==", + "dev": true, + "requires": { + "babel-traverse": "7.0.0-beta.0", + "babel-types": "7.0.0-beta.0", + "babylon": "7.0.0-beta.22", + "lodash": "4.17.4" + } + }, + "babel-traverse": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-7.0.0-beta.0.tgz", + "integrity": "sha512-IKzuTqUcQtMRZ0Vv5RjIrGGj33eBKmNTNeRexWSyjPPuAciyNkva1rt7WXPfHfkb+dX7coRAIUhzeTUEzhnwdA==", + "dev": true, + "requires": { + "babel-code-frame": "7.0.0-beta.0", + "babel-helper-function-name": "7.0.0-beta.0", + "babel-messages": "7.0.0-beta.0", + "babel-types": "7.0.0-beta.0", + "babylon": "7.0.0-beta.22", + "debug": "3.1.0", + "globals": "10.1.0", + "invariant": "2.2.2", + "lodash": "4.17.4" + } + }, + "babel-types": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-7.0.0-beta.0.tgz", + "integrity": "sha512-rJc2kV9iPJGLlqIY71AM3nPcdkoeLRCDuR07GFgfd3lFl4TsBQq76TxYQQIZ2MONg1HpsqmuoCXr9aZ1Oa4wYw==", + "dev": true, + "requires": { + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "2.0.0" + } + }, + "babylon": { + "version": "7.0.0-beta.22", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.22.tgz", + "integrity": "sha512-Yl7iT8QGrS8OfR7p6R12AJexQm+brKwrryai4VWZ7NHUbPoZ5al3+klhvl/14shXZiLa7uK//OIFuZ1/RKHgoA==", + "dev": true + }, + "chalk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.2.0.tgz", + "integrity": "sha512-0BMM/2hG3ZaoPfR6F+h/oWpZtsh3b/s62TjSM6MGCJWEbJDN1acqCXvyhhZsDSVFklpebUoQ5O1kKC7lOzrn9g==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-10.1.0.tgz", + "integrity": "sha1-RCWhiBvg0za0qCOoKnvnJdXdmHw=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, "babel-generator": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", @@ -1028,12 +1199,6 @@ "repeat-element": "1.1.2" } }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, "buf-compare": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", @@ -1107,6 +1272,21 @@ "integrity": "sha1-qEq8glpV70yysCi9dOIFpluaSZY=", "dev": true }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -1142,20 +1322,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", - "dev": true, - "requires": { - "assertion-error": "1.0.2", - "check-error": "1.0.2", - "deep-eql": "3.0.1", - "get-func-name": "2.0.0", - "pathval": "1.1.0", - "type-detect": "4.0.3" - } - }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -1168,12 +1334,6 @@ "supports-color": "2.0.0" } }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -1197,6 +1357,12 @@ "integrity": "sha512-vHDDF/bP9RYpTWtUhpJRhCFdvvp3iDWvEbuDbWgvjUrNGV1MXJrE0MPcwGtEled04m61iwdBLUIHZtDgzWS4ZQ==", "dev": true }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, "clean-stack": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz", @@ -1240,6 +1406,12 @@ "string-width": "2.1.1" } }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, "cls-bluebird": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.0.1.tgz", @@ -1328,6 +1500,43 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, "concordance": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/concordance/-/concordance-3.0.0.tgz", @@ -1525,15 +1734,6 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "4.0.3" - } - }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", @@ -1545,6 +1745,58 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + }, + "dependencies": { + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + } + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1582,11 +1834,15 @@ "repeating": "2.0.1" } }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", - "dev": true + "doctrine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", + "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } }, "dot-prop": { "version": "4.2.0", @@ -1602,6 +1858,11 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz", "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA=" }, + "double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -1680,6 +1941,122 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, + "eslint": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.9.0.tgz", + "integrity": "sha1-doedJ0BoJhsZH+Dy9Wx0wvQgjos=", + "dev": true, + "requires": { + "ajv": "5.2.3", + "babel-code-frame": "6.26.0", + "chalk": "2.2.0", + "concat-stream": "1.6.0", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.0.0", + "eslint-scope": "3.7.1", + "espree": "3.5.1", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.6", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.0.0", + "js-yaml": "3.10.0", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.4.1", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.2.0.tgz", + "integrity": "sha512-0BMM/2hG3ZaoPfR6F+h/oWpZtsh3b/s62TjSM6MGCJWEbJDN1acqCXvyhhZsDSVFklpebUoQ5O1kKC7lOzrn9g==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, "espower-location-detector": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz", @@ -1692,6 +2069,16 @@ "xtend": "4.0.1" } }, + "espree": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz", + "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=", + "dev": true, + "requires": { + "acorn": "5.1.2", + "acorn-jsx": "3.0.1" + } + }, "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", @@ -1707,6 +2094,25 @@ "core-js": "2.5.1" } }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, "estraverse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", @@ -1840,6 +2246,17 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, + "external-editor": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz", + "integrity": "sha512-Msjo64WT5W+NhOpQXh0nOHm+n0RfU1QUwDnKYvJ8dEJ8zlwLrqXNTv5mSUTJpepf41PDJGyhueTw2vNZW+Fr/w==", + "dev": true, + "requires": { + "iconv-lite": "0.4.19", + "jschardet": "1.5.1", + "tmp": "0.0.33" + } + }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", @@ -1864,6 +2281,12 @@ "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", "dev": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -1873,6 +2296,16 @@ "escape-string-regexp": "1.0.5" } }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -1940,6 +2373,18 @@ "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.2.tgz", "integrity": "sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU=" }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, "fn-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz", @@ -2940,6 +3385,12 @@ "integrity": "sha1-VdO/qm6v1QWlD5vIH99XVkoLsHE=", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -2980,12 +3431,6 @@ "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.5.4.tgz", "integrity": "sha1-OMYYhRPhQDCUjsblz2VSPZd5KZs=" }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, "get-port": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", @@ -3131,12 +3576,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", - "dev": true - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3193,12 +3632,6 @@ "sntp": "2.0.2" } }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, "hoek": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", @@ -3283,6 +3716,12 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, + "ignore": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.6.tgz", + "integrity": "sha512-HrxmNxKTGZ9a3uAl/FNG66Sdt0G9L4TtMbbUQjP1WhGmSj0FOyHvSgx7623aGJvXfPOur8MwmarlHT+37jmzlw==", + "dev": true + }, "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -3341,6 +3780,80 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.0.0", + "chalk": "2.2.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.0.5", + "figures": "2.0.0", + "lodash": "4.17.4", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.2.0.tgz", + "integrity": "sha512-0BMM/2hG3ZaoPfR6F+h/oWpZtsh3b/s62TjSM6MGCJWEbJDN1acqCXvyhhZsDSVFklpebUoQ5O1kKC7lOzrn9g==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, "install": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/install/-/install-0.10.1.tgz", @@ -3511,6 +4024,21 @@ "symbol-observable": "0.2.4" } }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } + }, "is-path-inside": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", @@ -3548,6 +4076,15 @@ "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, + "is-resolvable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "1.0.3" + } + }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -3632,6 +4169,12 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "optional": true }, + "jschardet": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", + "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", + "dev": true + }, "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", @@ -3734,6 +4277,16 @@ "package-json": "4.0.1" } }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, "liftoff": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.2.5.tgz", @@ -4191,44 +4744,6 @@ } } }, - "mocha": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.1.tgz", - "integrity": "sha512-evDmhkoA+cBNiQQQdSKZa2b9+W2mpLoj50367lhy+Klnx9OV8XlCIhigUnn1gaTFLQCa0kdNhEGDr0hCXOQFDw==", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", - "mkdirp": "0.5.1", - "supports-color": "4.4.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, "moment": { "version": "2.19.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.1.tgz", @@ -4259,6 +4774,12 @@ "minimatch": "3.0.4" } }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, "nan": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", @@ -4266,6 +4787,12 @@ "dev": true, "optional": true }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -7324,6 +7851,1647 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "nyc": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.2.1.tgz", + "integrity": "sha1-rYUK/p261/SXByi0suR/7Rw4chw=", + "dev": true, + "requires": { + "archy": "1.0.0", + "arrify": "1.0.1", + "caching-transform": "1.0.1", + "convert-source-map": "1.5.0", + "debug-log": "1.0.1", + "default-require-extensions": "1.0.0", + "find-cache-dir": "0.1.1", + "find-up": "2.1.0", + "foreground-child": "1.5.6", + "glob": "7.1.2", + "istanbul-lib-coverage": "1.1.1", + "istanbul-lib-hook": "1.0.7", + "istanbul-lib-instrument": "1.8.0", + "istanbul-lib-report": "1.1.1", + "istanbul-lib-source-maps": "1.2.1", + "istanbul-reports": "1.1.2", + "md5-hex": "1.3.0", + "merge-source-map": "1.0.4", + "micromatch": "2.3.11", + "mkdirp": "0.5.1", + "resolve-from": "2.0.0", + "rimraf": "2.6.1", + "signal-exit": "3.0.2", + "spawn-wrap": "1.3.8", + "test-exclude": "4.1.1", + "yargs": "8.0.2", + "yargs-parser": "5.0.0" + }, + "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-generator": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.4", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "2.5.1", + "regenerator-runtime": "0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.8", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "bundled": true, + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "md5-hex": "1.3.0", + "mkdirp": "0.5.1", + "write-file-atomic": "1.3.4" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.5.0", + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.5.1", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "which": "1.3.0" + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "2.0.0" + } + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "bundled": true, + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, + "extglob": { + "version": "0.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "bundled": true, + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "for-own": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "4.0.2", + "signal-exit": "3.0.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "globals": { + "version": "9.18.0", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.10", + "bundled": true, + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.5.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.2", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.5", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-dotfile": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-glob": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.0.7", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.8.0", + "bundled": true, + "dev": true, + "requires": { + "babel-generator": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.1.1", + "semver": "5.4.1" + } + }, + "istanbul-lib-report": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "1.1.1", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "debug": "2.6.8", + "istanbul-lib-coverage": "1.1.1", + "mkdirp": "0.5.1", + "rimraf": "2.6.1", + "source-map": "0.5.7" + } + }, + "istanbul-reports": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "4.0.10" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "lodash": { + "version": "4.17.4", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "md5-hex": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "md5-o-matic": "0.1.1" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "merge-source-map": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "micromatch": { + "version": "2.3.11", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "mimic-fn": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.4.1", + "validate-npm-package-license": "3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "1.1.0" + } + }, + "parse-glob": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "preserve": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "bundled": true, + "dev": true + }, + "regex-cache": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "semver": { + "version": "5.4.1", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.3.8", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "1.5.6", + "mkdirp": "0.5.1", + "os-homedir": "1.0.2", + "rimraf": "2.6.1", + "signal-exit": "3.0.2", + "which": "1.3.0" + } + }, + "spdx-correct": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "2.3.11", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "8.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "read-pkg-up": "2.0.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "3.2.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "load-json-file": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "yargs-parser": { + "version": "7.0.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + } + } + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -7392,6 +9560,20 @@ "integrity": "sha1-k41zvU4Xg/lI00AjZEraI2aeMPI=", "dev": true }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -7547,12 +9729,6 @@ "pify": "2.3.0" } }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true - }, "pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -7668,6 +9844,12 @@ "irregular-plurals": "1.4.0" } }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, "postgres-array": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", @@ -7691,6 +9873,12 @@ "xtend": "4.0.1" } }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", @@ -7732,6 +9920,12 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, "proxy-addr": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", @@ -7943,6 +10137,26 @@ } } }, + "redis": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "requires": { + "double-ended-queue": "2.1.0-0", + "redis-commands": "1.3.1", + "redis-parser": "2.6.0" + } + }, + "redis-commands": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz", + "integrity": "sha1-gdgm9F+pyLIBH0zXoP5ZfSQdRCs=" + }, + "redis-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" + }, "regenerate": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", @@ -8075,6 +10289,24 @@ "integrity": "sha1-WhtS63Dr7UPrmC6XTIWrWVceVvo=", "dev": true }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + }, + "dependencies": { + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + } + } + }, "resolve": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", @@ -8134,6 +10366,30 @@ "glob": "7.1.2" } }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -8554,6 +10810,51 @@ "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=", "dev": true }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.2.3", + "ajv-keywords": "2.1.0", + "chalk": "2.2.0", + "lodash": "4.17.4", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.2.0.tgz", + "integrity": "sha512-0BMM/2hG3ZaoPfR6F+h/oWpZtsh3b/s62TjSM6MGCJWEbJDN1acqCXvyhhZsDSVFklpebUoQ5O1kKC7lOzrn9g==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, "tar": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", @@ -8753,6 +11054,15 @@ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", "dev": true }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -8799,6 +11109,12 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -8813,11 +11129,14 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, - "type-detect": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", - "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", - "dev": true + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } }, "type-is": { "version": "1.6.15", @@ -8828,6 +11147,12 @@ "mime-types": "2.1.17" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "uid-number": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", @@ -9077,11 +11402,26 @@ "@types/node": "8.0.34" } }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, "write-file-atomic": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", diff --git a/package.json b/package.json index c78947005..9b0442bde 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/app.js b/src/app.js index 8f0be6e7f..02f5f59d0 100644 --- a/src/app.js +++ b/src/app.js @@ -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) }) diff --git a/src/models/index.js b/src/models/index.js index b65a4db75..5c1f5a605 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -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, diff --git a/src/services/courses.js b/src/services/courses.js index 076e98210..909407e51 100644 --- a/src/services/courses.js +++ b/src/services/courses.js @@ -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) => { diff --git a/src/services/departments.js b/src/services/departments.js index b639d9ef6..1b48e9c51 100644 --- a/src/services/departments.js +++ b/src/services/departments.js @@ -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) diff --git a/src/services/populations.js b/src/services/populations.js index 08dd21bbc..8cf59f0e4 100644 --- a/src/services/populations.js +++ b/src/services/populations.js @@ -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) } diff --git a/src/services/students.js b/src/services/students.js index c0959b866..1d58cc741 100644 --- a/src/services/students.js +++ b/src/services/students.js @@ -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 { diff --git a/src/services/tags.js b/src/services/tags.js index 3352190f9..40eaf1e98 100644 --- a/src/services/tags.js +++ b/src/services/tags.js @@ -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 } diff --git a/src/services/teachers.js b/src/services/teachers.js index 494d63984..89ee73732 100644 --- a/src/services/teachers.js +++ b/src/services/teachers.js @@ -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) { diff --git a/src/services/users.js b/src/services/users.js index b1a4918ab..41d6c0668 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -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({ diff --git a/src/util/index.js b/src/util/index.js index b0cc09ac8..7584f94d2 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -1,5 +1,5 @@ function arrayUnique(value, index, self) { - return self.indexOf(value) === index; + return self.indexOf(value) === index } module.exports = { diff --git a/test/api-test.js b/test/api-test.js deleted file mode 100644 index 1727076cd..000000000 --- a/test/api-test.js +++ /dev/null @@ -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') -}) \ No newline at end of file diff --git a/test/api_test.js b/test/api_test.js new file mode 100644 index 000000000..5a99204fc --- /dev/null +++ b/test/api_test.js @@ -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) +}) \ No newline at end of file diff --git a/test/course_statistics_test.js b/test/course_statistics_test.js new file mode 100644 index 000000000..3115e08f9 --- /dev/null +++ b/test/course_statistics_test.js @@ -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) + +}) \ No newline at end of file diff --git a/test/population_statistics_test.js b/test/population_statistics_test.js new file mode 100644 index 000000000..e88bb704d --- /dev/null +++ b/test/population_statistics_test.js @@ -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() +}) \ No newline at end of file diff --git a/test/teacher_statistics_test.js b/test/teacher_statistics_test.js new file mode 100644 index 000000000..b08dbe39a --- /dev/null +++ b/test/teacher_statistics_test.js @@ -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) +}) \ No newline at end of file