diff --git a/.idea/dictionaries/VLE2FE.xml b/.idea/dictionaries/VLE2FE.xml
index c274b8b..1dd7309 100644
--- a/.idea/dictionaries/VLE2FE.xml
+++ b/.idea/dictionaries/VLE2FE.xml
@@ -4,6 +4,8 @@
bcrypt
cfenv
dfopdb
+ janedoe
+ testcomment
\ No newline at end of file
diff --git a/.idea/libraries/dist.xml b/.idea/libraries/dist.xml
deleted file mode 100644
index 3d92275..0000000
--- a/.idea/libraries/dist.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api/api.yaml b/api/api.yaml
index f890477..9090378 100644
--- a/api/api.yaml
+++ b/api/api.yaml
@@ -54,7 +54,6 @@ tags:
- name: /
- name: /sample
- name: /material
- - name: /condition
- name: /measurement
- name: /template
- name: /model
@@ -66,7 +65,6 @@ paths:
- $ref: 'others.yaml'
- $ref: 'sample.yaml'
- $ref: 'material.yaml'
- - $ref: 'condition.yaml'
- $ref: 'measurement.yaml'
- $ref: 'template.yaml'
- $ref: 'model.yaml'
diff --git a/api/condition.yaml b/api/condition.yaml
deleted file mode 100644
index ec8b245..0000000
--- a/api/condition.yaml
+++ /dev/null
@@ -1,111 +0,0 @@
-/condition/{id}:
- parameters:
- - $ref: 'api.yaml#/components/parameters/Id'
- get:
- summary: condition by id
- description: 'Auth: all, levels: read, write, maintain, dev, admin'
- x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision
- tags:
- - /condition
- responses:
- 200:
- description: condition details
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Condition'
- 400:
- $ref: 'api.yaml#/components/responses/400'
- 401:
- $ref: 'api.yaml#/components/responses/401'
- 404:
- $ref: 'api.yaml#/components/responses/404'
- 500:
- $ref: 'api.yaml#/components/responses/500'
- put:
- summary: change condition
- description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to reference samples created by another user'
- x-doc: status is reset to 0 on any changes
- tags:
- - /condition
- security:
- - BasicAuth: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- allOf:
- - $ref: 'api.yaml#/components/schemas/_Id'
- properties:
- parameters:
- type: object
- responses:
- 200:
- description: condition details
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Condition'
- 400:
- $ref: 'api.yaml#/components/responses/400'
- 401:
- $ref: 'api.yaml#/components/responses/401'
- 403:
- $ref: 'api.yaml#/components/responses/403'
- 404:
- $ref: 'api.yaml#/components/responses/404'
- 500:
- $ref: 'api.yaml#/components/responses/500'
- delete:
- summary: delete condition
- description: 'Auth: basic, levels: write, maintain, dev, admin'
- x-doc: sets status to -1
- tags:
- - /condition
- security:
- - BasicAuth: []
- responses:
- 200:
- $ref: 'api.yaml#/components/responses/Ok'
- 400:
- $ref: 'api.yaml#/components/responses/400'
- 401:
- $ref: 'api.yaml#/components/responses/401'
- 403:
- $ref: 'api.yaml#/components/responses/403'
- 404:
- $ref: 'api.yaml#/components/responses/404'
- 500:
- $ref: 'api.yaml#/components/responses/500'
-
-/condition/new:
- post:
- summary: add condition
- description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to reference samples created by another user'
- x-doc: 'Adds status: 0 automatically'
- tags:
- - /condition
- security:
- - BasicAuth: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Condition'
- responses:
- 200:
- description: condition details
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Condition'
- 400:
- $ref: 'api.yaml#/components/responses/400'
- 401:
- $ref: 'api.yaml#/components/responses/401'
- 403:
- $ref: 'api.yaml#/components/responses/403'
- 500:
- $ref: 'api.yaml#/components/responses/500'
\ No newline at end of file
diff --git a/api/sample.yaml b/api/sample.yaml
index c699809..9e830ff 100644
--- a/api/sample.yaml
+++ b/api/sample.yaml
@@ -19,7 +19,7 @@
500:
$ref: 'api.yaml#/components/responses/500'
-/samples{group}:
+/samples/{group}:
parameters:
- $ref: 'api.yaml#/components/parameters/Group'
get:
@@ -48,7 +48,7 @@
get:
summary: TODO sample details
description: 'Auth: all, levels: read, write, maintain, dev, admin'
- x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision
+ x-doc: deleted samples are available only for maintain/admin
tags:
- /sample
responses:
diff --git a/api/schemas.yaml b/api/schemas.yaml
index c872443..e76cfb0 100644
--- a/api/schemas.yaml
+++ b/api/schemas.yaml
@@ -24,6 +24,15 @@ SampleProperties:
batch:
type: string
example: 1560237365
+ condition:
+ type: object
+ properties:
+ condition_template:
+ $ref: 'api.yaml#/components/schemas/Id'
+ example:
+ condition_template: 5ea0450ed851c30a90e70894
+ material: hot air
+ weeks: 5
SampleRefs:
allOf:
@@ -55,7 +64,7 @@ Sample:
type: array
items:
properties:
- id:
+ sample_id:
$ref: 'api.yaml#/components/schemas/Id'
relation:
type: string
@@ -67,7 +76,8 @@ SampleDetail:
- $ref: 'api.yaml#/components/schemas/SampleProperties'
properties:
material:
- $ref: 'api.yaml#/components/schemas/Material'
+ allOf:
+ - $ref: 'api.yaml#/components/schemas/Material'
notes:
type: object
properties:
@@ -77,10 +87,14 @@ SampleDetail:
type: array
items:
$ref: 'api.yaml#/components/schemas/Id'
- conditions:
+ measurements:
type: array
items:
- $ref: 'api.yaml#/components/schemas/Condition'
+ allOf:
+ - $ref: 'api.yaml#/components/schemas/Measurement'
+ user:
+ type: string
+ example: admin
Material:
allOf:
@@ -115,26 +129,11 @@ Material:
type: string
example: 5514263423
-Condition:
- allOf:
- - $ref: 'api.yaml#/components/schemas/_Id'
- properties:
- sample_id:
- $ref: 'api.yaml#/components/schemas/Id'
- number:
- type: string
- readOnly: true
- example: B1
- parameters:
- type: object
- treatment_template:
- $ref: 'api.yaml#/components/schemas/Id'
-
Measurement:
allOf:
- $ref: 'api.yaml#/components/schemas/_Id'
properties:
- condition_id:
+ sample_id:
$ref: 'api.yaml#/components/schemas/Id'
values:
type: object
@@ -166,7 +165,7 @@ Template:
min: 0
max: 2
-TreatmentTemplate:
+ConditionTemplate:
allOf:
- $ref: 'api.yaml#/components/schemas/Template'
properties:
diff --git a/api/template.yaml b/api/template.yaml
index 37f374a..71a282f 100644
--- a/api/template.yaml
+++ b/api/template.yaml
@@ -1,6 +1,6 @@
-/template/treatments:
+/template/conditions:
get:
- summary: all available treatment methods
+ summary: all available condition methods
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
tags:
- /template
@@ -8,23 +8,23 @@
- BasicAuth: []
responses:
200:
- description: list of treatments
+ description: list of conditions
content:
application/json:
schema:
type: array
items:
- $ref: 'api.yaml#/components/schemas/TreatmentTemplate'
+ $ref: 'api.yaml#/components/schemas/ConditionTemplate'
401:
$ref: 'api.yaml#/components/responses/401'
500:
$ref: 'api.yaml#/components/responses/500'
-/template/treatment/{id}:
+/template/condition/{id}:
parameters:
- $ref: 'api.yaml#/components/parameters/Id'
get:
- summary: treatment method details
+ summary: condition method details
description: 'Auth: basic, levels: read, write, maintain, admin'
tags:
- /template
@@ -32,11 +32,11 @@
- BasicAuth: []
responses:
200:
- description: treatment details
+ description: condition details
content:
application/json:
schema:
- $ref: 'api.yaml#/components/schemas/TreatmentTemplate'
+ $ref: 'api.yaml#/components/schemas/ConditionTemplate'
401:
$ref: 'api.yaml#/components/responses/401'
404:
@@ -44,7 +44,7 @@
500:
$ref: 'api.yaml#/components/responses/500'
put:
- summary: change treatment method
+ summary: change condition method
description: 'Auth: basic, levels: maintain, admin'
x-doc: With a change a new version is set, resulting in a new template with a new id
tags:
@@ -56,14 +56,14 @@
content:
application/json:
schema:
- $ref: 'api.yaml#/components/schemas/TreatmentTemplate'
+ $ref: 'api.yaml#/components/schemas/ConditionTemplate'
responses:
200:
- description: treatment details
+ description: condition details
content:
application/json:
schema:
- $ref: 'api.yaml#/components/schemas/TreatmentTemplate'
+ $ref: 'api.yaml#/components/schemas/ConditionTemplate'
400:
$ref: 'api.yaml#/components/responses/400'
401:
@@ -75,9 +75,9 @@
500:
$ref: 'api.yaml#/components/responses/500'
-/template/treatment/new:
+/template/condition/new:
post:
- summary: add treatment method
+ summary: add condition method
description: 'Auth: basic, levels: maintain, admin'
tags:
- /template
@@ -88,14 +88,14 @@
content:
application/json:
schema:
- $ref: 'api.yaml#/components/schemas/TreatmentTemplate'
+ $ref: 'api.yaml#/components/schemas/ConditionTemplate'
responses:
200:
- description: treatment details
+ description: condition details
content:
application/json:
schema:
- $ref: 'api.yaml#/components/schemas/TreatmentTemplate'
+ $ref: 'api.yaml#/components/schemas/ConditionTemplate'
400:
$ref: 'api.yaml#/components/responses/400'
401:
diff --git a/package-lock.json b/package-lock.json
index 4c3c77d..6d935ee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -46,6 +46,169 @@
"@babel/highlight": "^7.8.3"
}
},
+ "@babel/core": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz",
+ "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.8.3",
+ "@babel/generator": "^7.9.6",
+ "@babel/helper-module-transforms": "^7.9.0",
+ "@babel/helpers": "^7.9.6",
+ "@babel/parser": "^7.9.6",
+ "@babel/template": "^7.8.6",
+ "@babel/traverse": "^7.9.6",
+ "@babel/types": "^7.9.6",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.1",
+ "json5": "^2.1.2",
+ "lodash": "^4.17.13",
+ "resolve": "^1.3.2",
+ "semver": "^5.4.1",
+ "source-map": "^0.5.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/generator": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz",
+ "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.9.6",
+ "jsesc": "^2.5.1",
+ "lodash": "^4.17.13",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.9.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz",
+ "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.8.3",
+ "@babel/template": "^7.8.3",
+ "@babel/types": "^7.9.5"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz",
+ "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz",
+ "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz",
+ "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz",
+ "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.8.3",
+ "@babel/helper-replace-supers": "^7.8.6",
+ "@babel/helper-simple-access": "^7.8.3",
+ "@babel/helper-split-export-declaration": "^7.8.3",
+ "@babel/template": "^7.8.6",
+ "@babel/types": "^7.9.0",
+ "lodash": "^4.17.13"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz",
+ "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz",
+ "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-member-expression-to-functions": "^7.8.3",
+ "@babel/helper-optimise-call-expression": "^7.8.3",
+ "@babel/traverse": "^7.9.6",
+ "@babel/types": "^7.9.6"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz",
+ "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.8.3",
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz",
+ "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.9.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
+ "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==",
+ "dev": true
+ },
+ "@babel/helpers": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz",
+ "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.8.3",
+ "@babel/traverse": "^7.9.6",
+ "@babel/types": "^7.9.6"
+ }
+ },
"@babel/highlight": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz",
@@ -56,6 +219,68 @@
"js-tokens": "^4.0.0"
}
},
+ "@babel/parser": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz",
+ "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
+ "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.8.3",
+ "@babel/parser": "^7.8.6",
+ "@babel/types": "^7.8.6"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz",
+ "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.8.3",
+ "@babel/generator": "^7.9.6",
+ "@babel/helper-function-name": "^7.9.5",
+ "@babel/helper-split-export-declaration": "^7.8.3",
+ "@babel/parser": "^7.9.6",
+ "@babel/types": "^7.9.6",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.13"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/types": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz",
+ "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.9.5",
+ "lodash": "^4.17.13",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
"@hapi/address": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz",
@@ -99,6 +324,67 @@
"@hapi/hoek": "^9.0.0"
}
},
+ "@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true
+ }
+ }
+ },
+ "@istanbuljs/schema": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
+ "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==",
+ "dev": true
+ },
"@jsdevtools/ono": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz",
@@ -233,6 +519,16 @@
"negotiator": "0.6.2"
}
},
+ "aggregate-error": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz",
+ "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==",
+ "dev": true,
+ "requires": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ }
+ },
"ansi-align": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",
@@ -295,6 +591,21 @@
"picomatch": "^2.0.4"
}
},
+ "append-transform": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz",
+ "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==",
+ "dev": true,
+ "requires": {
+ "default-require-extensions": "^3.0.0"
+ }
+ },
+ "archy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
+ "dev": true
+ },
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -528,6 +839,18 @@
}
}
},
+ "caching-transform": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz",
+ "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==",
+ "dev": true,
+ "requires": {
+ "hasha": "^5.0.0",
+ "make-dir": "^3.0.0",
+ "package-hash": "^4.0.0",
+ "write-file-atomic": "^3.0.0"
+ }
+ },
"call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
@@ -578,6 +901,12 @@
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="
},
+ "clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true
+ },
"cli-boxes": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz",
@@ -657,6 +986,12 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@@ -699,6 +1034,15 @@
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
+ "convert-source-map": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
@@ -721,6 +1065,28 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "dependencies": {
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
"crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -753,6 +1119,15 @@
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
},
+ "default-require-extensions": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz",
+ "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==",
+ "dev": true,
+ "requires": {
+ "strip-bom": "^4.0.0"
+ }
+ },
"defer-to-connect": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
@@ -854,6 +1229,12 @@
"is-symbol": "^1.0.2"
}
},
+ "es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true
+ },
"escape-goat": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz",
@@ -949,6 +1330,17 @@
"unpipe": "~1.0.0"
}
},
+ "find-cache-dir": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
+ "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ }
+ },
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
@@ -985,6 +1377,16 @@
}
}
},
+ "foreground-child": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
+ "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
"form-data": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
@@ -1012,6 +1414,12 @@
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
+ "fromentries": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz",
+ "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==",
+ "dev": true
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -1029,12 +1437,24 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
+ "gensync": {
+ "version": "1.0.0-beta.1",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
+ "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
+ "dev": true
+ },
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
+ "get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true
+ },
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@@ -1072,6 +1492,12 @@
"ini": "^1.3.5"
}
},
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ },
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
@@ -1126,12 +1552,28 @@
"resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
"integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw=="
},
+ "hasha": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz",
+ "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==",
+ "dev": true,
+ "requires": {
+ "is-stream": "^2.0.0",
+ "type-fest": "^0.8.0"
+ }
+ },
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
+ "html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
@@ -1172,6 +1614,12 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
},
+ "indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true
+ },
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -1286,6 +1734,12 @@
"has": "^1.0.3"
}
},
+ "is-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
+ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
+ "dev": true
+ },
"is-symbol": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
@@ -1300,6 +1754,12 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true
+ },
"is-yarn-global": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
@@ -1317,6 +1777,128 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
+ "istanbul-lib-coverage": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz",
+ "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==",
+ "dev": true
+ },
+ "istanbul-lib-hook": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz",
+ "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==",
+ "dev": true,
+ "requires": {
+ "append-transform": "^2.0.0"
+ }
+ },
+ "istanbul-lib-instrument": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz",
+ "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.7.5",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.0.0",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "istanbul-lib-processinfo": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz",
+ "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==",
+ "dev": true,
+ "requires": {
+ "archy": "^1.0.0",
+ "cross-spawn": "^7.0.0",
+ "istanbul-lib-coverage": "^3.0.0-alpha.1",
+ "make-dir": "^3.0.0",
+ "p-map": "^3.0.0",
+ "rimraf": "^3.0.0",
+ "uuid": "^3.3.3"
+ }
+ },
+ "istanbul-lib-report": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+ "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "dev": true,
+ "requires": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^3.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "istanbul-lib-source-maps": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz",
+ "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "istanbul-reports": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz",
+ "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==",
+ "dev": true,
+ "requires": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ }
+ },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1331,6 +1913,12 @@
"esprima": "^4.0.0"
}
},
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ },
"json-buffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
@@ -1341,6 +1929,15 @@
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.5.tgz",
"integrity": "sha512-gWJOWYFrhQ8j7pVm0EM8Slr+EPVq1Phf6lvzvD/WCeqkrx/f2xBI0xOsRRS9xCn3I4vKtP519dvs3TP09r24wQ=="
},
+ "json5": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+ "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
"kareem": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
@@ -1377,6 +1974,12 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
+ "lodash.flattendeep": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
+ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
+ "dev": true
+ },
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
@@ -1677,6 +2280,15 @@
"semver": "^5.7.0"
}
},
+ "node-preload": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
+ "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==",
+ "dev": true,
+ "requires": {
+ "process-on-spawn": "^1.0.0"
+ }
+ },
"nodemon": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.3.tgz",
@@ -1727,6 +2339,196 @@
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
"integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ=="
},
+ "nyc": {
+ "version": "15.0.1",
+ "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.0.1.tgz",
+ "integrity": "sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg==",
+ "dev": true,
+ "requires": {
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "caching-transform": "^4.0.0",
+ "convert-source-map": "^1.7.0",
+ "decamelize": "^1.2.0",
+ "find-cache-dir": "^3.2.0",
+ "find-up": "^4.1.0",
+ "foreground-child": "^2.0.0",
+ "glob": "^7.1.6",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-hook": "^3.0.0",
+ "istanbul-lib-instrument": "^4.0.0",
+ "istanbul-lib-processinfo": "^2.0.2",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.0.2",
+ "make-dir": "^3.0.0",
+ "node-preload": "^0.2.1",
+ "p-map": "^3.0.0",
+ "process-on-spawn": "^1.0.0",
+ "resolve-from": "^5.0.0",
+ "rimraf": "^3.0.0",
+ "signal-exit": "^3.0.2",
+ "spawn-wrap": "^2.0.0",
+ "test-exclude": "^6.0.0",
+ "yargs": "^15.0.2"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "dev": true,
+ "requires": {
+ "@types/color-name": "^1.1.1",
+ "color-convert": "^2.0.1"
+ }
+ },
+ "cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "yargs": {
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
+ "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
+ "dev": true,
+ "requires": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
"object-inspect": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
@@ -1805,12 +2607,33 @@
"p-limit": "^2.0.0"
}
},
+ "p-map": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
+ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
+ "dev": true,
+ "requires": {
+ "aggregate-error": "^3.0.0"
+ }
+ },
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
+ "package-hash": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz",
+ "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.15",
+ "hasha": "^5.0.0",
+ "lodash.flattendeep": "^4.4.0",
+ "release-zalgo": "^1.0.0"
+ }
+ },
"package-json": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz",
@@ -1845,6 +2668,12 @@
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
@@ -1860,6 +2689,51 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz",
"integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA=="
},
+ "pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "requires": {
+ "find-up": "^4.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ }
+ }
+ },
"ports": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ports/-/ports-1.1.0.tgz",
@@ -1876,6 +2750,15 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
+ "process-on-spawn": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz",
+ "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==",
+ "dev": true,
+ "requires": {
+ "fromentries": "^1.2.0"
+ }
+ },
"proxy-addr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
@@ -1983,6 +2866,15 @@
"rc": "^1.2.8"
}
},
+ "release-zalgo": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
+ "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=",
+ "dev": true,
+ "requires": {
+ "es6-error": "^4.0.1"
+ }
+ },
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -2025,6 +2917,15 @@
"lowercase-keys": "^1.0.0"
}
},
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -2113,6 +3014,21 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
"should": {
"version": "13.2.3",
"resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz",
@@ -2182,6 +3098,12 @@
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
},
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
"sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@@ -2191,6 +3113,31 @@
"memory-pager": "^1.0.2"
}
},
+ "spawn-wrap": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
+ "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==",
+ "dev": true,
+ "requires": {
+ "foreground-child": "^2.0.0",
+ "is-windows": "^1.0.2",
+ "make-dir": "^3.0.0",
+ "rimraf": "^3.0.0",
+ "signal-exit": "^3.0.2",
+ "which": "^2.0.1"
+ },
+ "dependencies": {
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -2271,6 +3218,12 @@
"ansi-regex": "^3.0.0"
}
},
+ "strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true
+ },
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
@@ -2347,6 +3300,23 @@
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz",
"integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw=="
},
+ "test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "requires": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "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
+ },
"to-readable-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
@@ -2544,6 +3514,12 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
+ "uuid": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+ "dev": true
+ },
"validator": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz",
diff --git a/package.json b/package.json
index 4ec763a..6e7f289 100644
--- a/package.json
+++ b/package.json
@@ -5,10 +5,12 @@
"main": "index.js",
"scripts": {
"tsc": "tsc",
+ "tsc-full": "del /q dist\\* & (for /d %x in (dist\\*) do @rd /s /q \"%x\") & tsc",
"test": "mocha dist/**/**.spec.js",
"start": "tsc && node dist/index.js || exit 1",
"dev": "nodemon -e ts,yaml --exec \"npm run start\"",
- "loadDev": "node dist/test/loadDev.js"
+ "loadDev": "node dist/test/loadDev.js",
+ "coverage": "tsc && nyc --reporter=html --reporter=text mocha dist/**/**.spec.js --timeout 5000"
},
"keywords": [],
"author": "",
@@ -44,6 +46,7 @@
"devDependencies": {
"@types/lodash": "^4.14.150",
"mocha": "^7.1.2",
+ "nyc": "^15.0.1",
"should": "^13.2.3",
"supertest": "^4.0.2"
}
diff --git a/src/db.ts b/src/db.ts
index c1d1fbb..fb5d424 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -42,15 +42,18 @@ export default class db {
});
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
mongoose.connection.on('disconnected', () => { // reset state on disconnect
- console.info('Database disconnected');
- this.state.db = 0;
- done();
+ if (process.env.NODE_ENV !== 'test') { // Do not interfere with testing
+ console.info('Database disconnected');
+ this.state.db = 0;
+ }
});
process.on('SIGINT', () => { // close connection when app is terminated
- mongoose.connection.close(() => {
- console.info('Mongoose default connection disconnected through app termination');
- process.exit(0);
- });
+ if (!this.state.db) { // database still connected
+ mongoose.connection.close(() => {
+ console.info('Mongoose default connection disconnected through app termination');
+ process.exit(0);
+ });
+ }
});
mongoose.connection.once('open', () => {
mongoose.set('useFindAndModify', false);
@@ -60,6 +63,14 @@ export default class db {
});
}
+ static disconnect (done) {
+ mongoose.connection.close(() => {
+ console.info(process.env.NODE_ENV === 'test' ? '' : `Disconnected from database`);
+ this.state.db = 0;
+ done();
+ });
+ }
+
static getState () {
return this.state;
}
diff --git a/src/globals.ts b/src/globals.ts
index 0d4ccdb..81f80b8 100644
--- a/src/globals.ts
+++ b/src/globals.ts
@@ -5,7 +5,13 @@ const globals = {
'maintain',
'dev',
'admin'
- ]
+ ],
+
+ status: { // document statuses
+ deleted: -1,
+ new: 0,
+ validated: 10,
+ }
};
export default globals;
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 4ce0581..0de6ff4 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,6 +11,9 @@ import db from './db';
// TODO: validation: VZ, Humidity: min/max value, DPT: filename
// TODO: condition values not needed on initial add
// TODO: add multiple samples at once
+// TODO: coverage
+// TODO: think about the display of deleted/new samples and validation in data and UI
+// TODO: improve error coverage
// tell if server is running in debug or production environment
console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
@@ -48,14 +51,22 @@ app.use((req, res, next) => { // no database connection error
});
app.use(require('./helpers/authorize')); // handle authentication
+// redirect /api routes for Angular proxy in development
+if (process.env.NODE_ENV !== 'production') {
+ app.use('/api/:url', (req, res) => {
+ req.url = '/' + req.params.url;
+ app.handle(req, res);
+ });
+}
+
+
// require routes
-app.use('/api', require('./routes/root'));
-app.use('/api', require('./routes/sample'));
-app.use('/api', require('./routes/material'));
-app.use('/api', require('./routes/template'));
-app.use('/api', require('./routes/user'));
-app.use('/api', require('./routes/condition'));
-app.use('/api', require('./routes/measurement'));
+app.use('/', require('./routes/root'));
+app.use('/', require('./routes/sample'));
+app.use('/', require('./routes/material'));
+app.use('/', require('./routes/template'));
+app.use('/', require('./routes/user'));
+app.use('/', require('./routes/measurement'));
// static files
app.use('/static', express.static('static'));
diff --git a/src/models/condition.ts b/src/models/condition.ts
deleted file mode 100644
index e0f79da..0000000
--- a/src/models/condition.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import mongoose from 'mongoose';
-import SampleModel from './sample';
-import TreatmentTemplateModel from './treatment_template';
-
-const ConditionSchema = new mongoose.Schema({
- sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel},
- number: String,
- parameters: mongoose.Schema.Types.Mixed,
- treatment_template: {type: mongoose.Schema.Types.ObjectId, ref: TreatmentTemplateModel},
- status: Number
-});
-
-export default mongoose.model('condition', ConditionSchema);
\ No newline at end of file
diff --git a/src/models/treatment_template.ts b/src/models/condition_template.ts
similarity index 59%
rename from src/models/treatment_template.ts
rename to src/models/condition_template.ts
index 154ae79..20c7234 100644
--- a/src/models/treatment_template.ts
+++ b/src/models/condition_template.ts
@@ -1,13 +1,12 @@
import mongoose from 'mongoose';
-const TreatmentTemplateSchema = new mongoose.Schema({
+const ConditionTemplateSchema = new mongoose.Schema({
name: String,
version: Number,
- number_prefix: String,
parameters: [{
name: String,
range: mongoose.Schema.Types.Mixed
}]
}, {minimize: false}); // to allow empty objects
-export default mongoose.model('treatment_template', TreatmentTemplateSchema);
\ No newline at end of file
+export default mongoose.model('condition_template', ConditionTemplateSchema);
\ No newline at end of file
diff --git a/src/models/measurement.ts b/src/models/measurement.ts
index ac0ef20..d003ea5 100644
--- a/src/models/measurement.ts
+++ b/src/models/measurement.ts
@@ -1,12 +1,14 @@
import mongoose from 'mongoose';
-import ConditionModel from './condition';
+import SampleModel from './sample';
import MeasurementTemplateModel from './measurement_template';
+
+
const MeasurementSchema = new mongoose.Schema({
- condition_id: {type: mongoose.Schema.Types.ObjectId, ref: ConditionModel},
+ sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel},
values: mongoose.Schema.Types.Mixed,
measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel},
status: Number
-});
+}, {minimize: false});
export default mongoose.model('measurement', MeasurementSchema);
\ No newline at end of file
diff --git a/src/models/note.ts b/src/models/note.ts
index a13fd6a..cd0847b 100644
--- a/src/models/note.ts
+++ b/src/models/note.ts
@@ -3,7 +3,7 @@ import mongoose from 'mongoose';
const NoteSchema = new mongoose.Schema({
comment: String,
sample_references: [{
- id: mongoose.Schema.Types.ObjectId,
+ sample_id: mongoose.Schema.Types.ObjectId,
relation: String
}],
custom_fields: mongoose.Schema.Types.Mixed
diff --git a/src/models/sample.ts b/src/models/sample.ts
index 9e5353b..1338728 100644
--- a/src/models/sample.ts
+++ b/src/models/sample.ts
@@ -9,10 +9,11 @@ const SampleSchema = new mongoose.Schema({
type: String,
color: String,
batch: String,
+ condition: mongoose.Schema.Types.Mixed,
material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel},
note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel},
user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel},
status: Number
-});
+}, {minimize: false});
export default mongoose.model('sample', SampleSchema);
\ No newline at end of file
diff --git a/src/routes/condition.spec.ts b/src/routes/condition.spec.ts
deleted file mode 100644
index 90c7c43..0000000
--- a/src/routes/condition.spec.ts
+++ /dev/null
@@ -1,583 +0,0 @@
-import should from 'should/as-function';
-import ConditionModel from '../models/condition';
-import TestHelper from "../test/helper";
-
-// TODO: adding conditions allowed only for m/a
-// TODO: deleted data only visible for m/a
-// TODO: restore deleted
-// TODO: remove number_prefix
-
-describe('/condition', () => {
- let server;
- before(done => TestHelper.before(done));
- beforeEach(done => server = TestHelper.beforeEach(server, done));
- afterEach(done => TestHelper.afterEach(server, done));
-
- describe('GET /condition/{id}', () => {
- it('returns the right condition', done => {
- TestHelper.request(server, done, {
- method: 'get',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', parameters: {material: 'copper', weeks: 3}, treatment_template: '200000000000000000000001'}
- });
- });
- it('returns the right condition for an API key', done => {
- TestHelper.request(server, done, {
- method: 'get',
- url: '/condition/700000000000000000000001',
- auth: {key: 'janedoe'},
- httpStatus: 200,
- res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', parameters: {material: 'copper', weeks: 3}, treatment_template: '200000000000000000000001'}
- });
- });
- it('rejects an invalid id', done => {
- TestHelper.request(server, done, {
- method: 'get',
- url: '/condition/70000000000t000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 404
- });
- });
- it('rejects an unknown id', done => {
- TestHelper.request(server, done, {
- method: 'get',
- url: '/condition/000000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 404
- });
- });
- it('rejects unauthorized requests', done => {
- TestHelper.request(server, done, {
- method: 'get',
- url: '/condition/700000000000000000000001',
- httpStatus: 401
- });
- });
- });
-
- describe('PUT /condition{id}', () => {
- it('returns the right condition', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {},
- res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}}
- });
- });
- it('keeps unchanged properties', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {parameters: {material: 'copper', weeks: 3}}
- }).end((err, res) => {
- if (err) return done(err);
- should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}});
- ConditionModel.findById('700000000000000000000001').lean().exec((err, data) => {
- if (err) return done(err);
- should(data).have.property('status', 10);
- done();
- });
- });
- });
- it('keeps only one unchanged parameter', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {parameters: {material: 'copper'}}
- }).end((err, res) => {
- if (err) return done(err);
- should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}});
- ConditionModel.findById('700000000000000000000001').lean().exec((err, data) => {
- if (err) return done(err);
- should(data).have.property('status', 10);
- done();
- });
- });
- });
- it('changes the given properties', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {parameters: {material: 'hot air', weeks: 10}}
- }).end((err, res) => {
- if (err) return done(err);
- should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}});
- ConditionModel.findById('700000000000000000000001').lean().exec((err, data: any) => {
- if (err) return done(err);
- should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v');
- should(data.sample_id.toString()).be.eql('400000000000000000000001');
- should(data).have.property('number', 'A1');
- should(data.treatment_template.toString()).be.eql('200000000000000000000001');
- should(data).have.property('status', 0);
- should(data).have.property('parameters');
- should(data.parameters).have.property('material', 'hot air');
- should(data.parameters).have.property('weeks', 10);
- done();
- });
- });
- });
- it('allows changing only one parameter', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {parameters: {weeks: 8}},
- res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 8}}
- });
- });
- it('rejects changing the condition number', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {number: 'C2'},
- res: {status: 'Invalid body format', details: '"number" is not allowed'}
- });
- });
- it('rejects not specified parameters', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {parameters: {xx: 13}},
- res: {status: 'Invalid body format', details: '"xx" is not allowed'}
- });
- });
- it('rejects a parameter not in the value range', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {parameters: {material: 'xxx'}},
- res: {status: 'Invalid body format', details: '"material" must be one of [copper, hot air]'}
- });
- });
- it('rejects a parameter below minimum range', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {parameters: {weeks: -10}},
- res: {status: 'Invalid body format', details: '"weeks" must be larger than or equal to 1'}
- });
- });
- it('rejects a parameter above maximum range', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {parameters: {weeks: 11}},
- res: {status: 'Invalid body format', details: '"weeks" must be less than or equal to 10'}
- });
- });
- it('rejects a new treatment_template', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {treatment_template: '200000000000000000000002'},
- res: {status: 'Invalid body format', details: '"treatment_template" is not allowed'}
- });
- });
- it('rejects editing a condition for a write user who did not create this condition', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000003',
- auth: {basic: 'janedoe'},
- httpStatus: 403,
- req: {parameters: {weeks: 8}}
- });
- });
- it('accepts editing a condition of another user for a maintain/admin user', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'admin'},
- httpStatus: 200,
- req: {parameters: {material: 'hot air', weeks: 10}},
- res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}}
- });
- });
- it('rejects an API key', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {key: 'janedoe'},
- httpStatus: 401,
- req: {parameters: {material: 'hot air', weeks: 10}}
- });
- });
- it('rejects requests from a read user', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- auth: {basic: 'user'},
- httpStatus: 403,
- req: {parameters: {material: 'hot air', weeks: 10}}
- });
- });
- it('rejects unauthorized requests', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/condition/700000000000000000000001',
- httpStatus: 401,
- req: {parameters: {material: 'hot air', weeks: 10}}
- });
- });
- });
-
- describe('DELETE /condition/{id}', () => {
- it('sets the status to deleted', done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/700000000000000000000004',
- auth: {basic: 'janedoe'},
- httpStatus: 200
- }).end((err, res) => {
- if (err) return done(err);
- should(res.body).be.eql({status: 'OK'});
- ConditionModel.findById('700000000000000000000004').lean().exec((err, data: any) => {
- if (err) return done(err);
- should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v');
- should(data.sample_id.toString()).be.eql('400000000000000000000001');
- should(data).have.property('number', 'A2');
- should(data.treatment_template.toString()).be.eql('200000000000000000000001');
- should(data).have.property('status', -1);
- should(data).have.property('parameters');
- should(data.parameters).have.property('material', 'hot air');
- should(data.parameters).have.property('weeks', 5);
- done();
- });
- });
- });
- it('rejects deleting a condition referenced by measurements'/*, done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/700000000000000000000002',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- res: {status: 'Condition still in use'}
- });
- }*/); // TODO after decision
- it('rejects an invalid id', done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/70000000000w000000000002',
- auth: {basic: 'janedoe'},
- httpStatus: 404
- });
- });
- it('rejects an API key', done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/700000000000000000000004',
- auth: {key: 'janedoe'},
- httpStatus: 401
- });
- });
- it('rejects requests from a read user', done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/700000000000000000000004',
- auth: {basic: 'user'},
- httpStatus: 403
- });
- });
- it('rejects a write user deleting a condition belonging to a sample of another user', done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/700000000000000000000003',
- auth: {basic: 'janedoe'},
- httpStatus: 403
- });
- });
- it('accepts an maintain/admin user deleting a condition belonging to a sample of another user', done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/700000000000000000000004',
- auth: {basic: 'admin'},
- httpStatus: 200
- }).end((err, res) => {
- if (err) return done(err);
- should(res.body).be.eql({status: 'OK'});
- done();
- });
- });
- it('returns 404 for an unknown id', done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/000000000000000000000002',
- auth: {basic: 'janedoe'},
- httpStatus: 404
- });
- });
- it('rejects unauthorized requests', done => {
- TestHelper.request(server, done, {
- method: 'delete',
- url: '/condition/700000000000000000000004',
- httpStatus: 401
- });
- });
- });
-
- describe('POST /condition/new', () => {
- it('returns the right condition', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
- }).end((err, res) => {
- if (err) return done(err);
- should(res.body).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template');
- should(res.body).have.property('_id').be.type('string');
- should(res.body).have.property('sample_id', '400000000000000000000002');
- should(res.body).have.property('number', 'A2');
- should(res.body).have.property('treatment_template', '200000000000000000000001');
- should(res.body).have.property('parameters');
- should(res.body.parameters).have.property('material', 'hot air');
- should(res.body.parameters).have.property('weeks', 10);
- done();
- });
- });
- it('stores the condition', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
- }).end((err, res) => {
- if (err) return done(err);
- ConditionModel.findById(res.body._id).lean().exec((err, data: any) => {
- if (err) return done(err);
- should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v');
- should(data.sample_id.toString()).be.eql('400000000000000000000002');
- should(data).have.property('number', 'A2');
- should(data.treatment_template.toString()).be.eql('200000000000000000000001');
- should(data).have.property('status', 0);
- should(data).have.property('parameters');
- should(data.parameters).have.property('material', 'hot air');
- should(data.parameters).have.property('weeks', 10);
- done();
- });
- });
- });
- it('stores the first condition as 1', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'admin'},
- httpStatus: 200,
- req: {sample_id: '400000000000000000000003', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
- }).end((err, res) => {
- if (err) return done(err);
- ConditionModel.findById(res.body._id).lean().exec((err, data: any) => {
- if (err) return done(err);
- should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v');
- should(data.sample_id.toString()).be.eql('400000000000000000000003');
- should(data).have.property('number', 'A1');
- should(data.treatment_template.toString()).be.eql('200000000000000000000001');
- should(data).have.property('status', 0);
- should(data).have.property('parameters');
- should(data.parameters).have.property('material', 'hot air');
- should(data.parameters).have.property('weeks', 10);
- done();
- });
- });
- });
- it('rejects an invalid sample id', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '4000000000h0000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
- res: {status: 'Invalid body format', details: '"sample_id" with value "4000000000h0000000000002" fails to match the required pattern: /[0-9a-f]{24}/'}
- });
- });
- it('rejects a sample id not available', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '000000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
- res: {status: 'Sample id not available'}
- });
- });
- it('rejects an invalid treatment_template id', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000h00000000001'},
- res: {status: 'Invalid body format', details: '"treatment_template" with value "200000000000h00000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
- });
- });
- it('rejects a treatment_template which does not exist', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '000000000000000000000001'},
- res: {status: 'Treatment template not available'}
- });
- });
- it('rejects setting a condition number', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000001', number: 'A7', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
- res: {status: 'Invalid body format', details: '"number" is not allowed'}
- });
- });
- it('rejects not specified parameters', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10, xx: 12}, treatment_template: '200000000000000000000001'},
- res: {status: 'Invalid body format', details: '"xx" is not allowed'}
- });
- });
- it('rejects missing parameters', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air'}, treatment_template: '200000000000000000000001'},
- res: {status: 'Invalid body format', details: '"weeks" is required'}
- });
- });
- it('rejects a parameter not in the value range', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'xxx', weeks: 10}, treatment_template: '200000000000000000000001'},
- res: {status: 'Invalid body format', details: '"material" must be one of [copper, hot air]'}
- });
- });
- it('rejects a parameter below minimum range', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: -10}, treatment_template: '200000000000000000000001'},
- res: {status: 'Invalid body format', details: '"weeks" must be larger than or equal to 1'}
- });
- });
- it('rejects a parameter above maximum range', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 11}, treatment_template: '200000000000000000000001'},
- res: {status: 'Invalid body format', details: '"weeks" must be less than or equal to 10'}
- });
- });
- it('rejects a missing sample id', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
- res: {status: 'Invalid body format', details: '"sample_id" is required'}
- });
- });
- it('rejects a missing treatment_template', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}},
- res: {status: 'Invalid body format', details: '"treatment_template" is required'}
- });
- });
- it('rejects adding a condition to the sample of an other user for a write user', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'janedoe'},
- httpStatus: 403,
- req: {sample_id: '400000000000000000000003', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
- });
- });
- it('accepts adding a condition to the sample of an other user for a maintain/admin user', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'admin'},
- httpStatus: 200,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
- }).end((err, res) => {
- if (err) return done(err);
- should(res.body).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template');
- should(res.body).have.property('_id').be.type('string');
- should(res.body).have.property('sample_id', '400000000000000000000002');
- should(res.body).have.property('number', 'A2');
- should(res.body).have.property('treatment_template', '200000000000000000000001');
- should(res.body).have.property('parameters');
- should(res.body.parameters).have.property('material', 'hot air');
- should(res.body.parameters).have.property('weeks', 10);
- done();
- });
- });
- it('rejects an API key', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {key: 'janedoe'},
- httpStatus: 401,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
- });
- });
- it('rejects requests from a read user', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- auth: {basic: 'user'},
- httpStatus: 403,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
- });
- });
- it('rejects unauthorized requests', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/condition/new',
- httpStatus: 401,
- req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
- });
- });
- });
-});
\ No newline at end of file
diff --git a/src/routes/condition.ts b/src/routes/condition.ts
deleted file mode 100644
index f66d10a..0000000
--- a/src/routes/condition.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import express from 'express';
-import _ from 'lodash';
-
-import ConditionValidate from './validate/condition';
-import ParametersValidate from './validate/parameters';
-import res400 from './validate/res400';
-import SampleModel from '../models/sample';
-import ConditionModel from '../models/condition';
-import TreatmentTemplateModel from '../models/treatment_template';
-import IdValidate from './validate/id';
-
-
-const router = express.Router();
-
-router.get('/condition/' + IdValidate.parameter(), (req, res, next) => {
- if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
-
- ConditionModel.findById(req.params.id).lean().exec((err, data) => {
- if (err) return next(err);
- if (data) {
- res.json(ConditionValidate.output(data));
- }
- else {
- res.status(404).json({status: 'Not found'});
- }
- });
-});
-
-router.put('/condition/' + IdValidate.parameter(), async (req, res, next) => {
- if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
-
- const {error, value: condition} = ConditionValidate.input(req.body, 'change');
- if (error) return res400(error, res);
-
- const data = await ConditionModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
- if (data instanceof Error) return;
- if (!data) {
- res.status(404).json({status: 'Not found'});
- }
-
- // add properties needed for sampleIdCheck
- condition.treatment_template = data.treatment_template;
- condition.sample_id = data.sample_id;
- if (!await sampleIdCheck(condition, req, res, next)) return;
- if (condition.parameters) {
- condition.parameters = _.assign({}, data.parameters, condition.parameters);
- if (!_.isEqual(condition.parameters, data.parameters)) { // parameters did not change
- condition.status = 0;
- }
- }
- if (!await treatmentCheck(condition, 'change', res, next)) return;
-
- await ConditionModel.findByIdAndUpdate(req.params.id, condition, {new: true}).lean().exec((err, data) => {
- if (err) return next(err);
- res.json(ConditionValidate.output(data));
- });
-});
-
-router.delete('/condition/' + IdValidate.parameter(), (req, res, next) => {
- if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
-
- ConditionModel.findById(req.params.id).lean().exec(async (err, data: any) => {
- if (err) return next(err);
- if (!data) {
- res.status(404).json({status: 'Not found'});
- }
- if (!await sampleIdCheck(data, req, res, next)) return;
- await ConditionModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => {
- if (err) return next(err);
- res.json({status: 'OK'});
- });
- });
-});
-
-router.post('/condition/new', async (req, res, next) => {
- if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
-
- const {error, value: condition} = ConditionValidate.input(req.body, 'new');
- if (error) return res400(error, res);
-
- if (!await sampleIdCheck(condition, req, res, next)) return;
- const treatmentData = await treatmentCheck(condition, 'new', res, next)
- if (!treatmentData) return;
-
- condition.number = await numberGenerate(condition, treatmentData, next);
- if (!condition.number) return;
- condition.status = 0; // set status to new
- await new ConditionModel(condition).save((err, data) => {
- if (err) return next(err);
- res.json(ConditionValidate.output(data.toObject()));
- });
-})
-
-
-module.exports = router;
-
-
-async function sampleIdCheck (condition, req, res, next) { // validate sample_id, returns false if invalid
- const sampleData = await SampleModel.findById(condition.sample_id).lean().exec().catch(err => {next(err); return false;}) as any;
- if (!sampleData) { // sample_id not found
- res.status(400).json({status: 'Sample id not available'});
- return false
- }
-
- if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return false; // sample does not belong to user
- return true;
-}
-
-async function numberGenerate (condition, treatmentData, next) { // generate number, returns false on error
- const conditionData = await ConditionModel // find condition with highest number belonging to the same sample
- .find({sample_id: condition.sample_id, number: new RegExp('^' + treatmentData.number_prefix + '[0-9]+$', 'm')})
- .sort({number: -1})
- .limit(1)
- .lean()
- .exec()
- .catch(err => next(err)) as any;
- if (conditionData instanceof Error) return false;
- return treatmentData.number_prefix + (conditionData.length > 0 ? Number(conditionData[0].number.replace(/[^0-9]+/g, '')) + 1 : 1); // return new number
-}
-
-async function treatmentCheck (condition, param, res, next) { // validate treatment template, returns false if invalid, otherwise template data
- const treatmentData = await TreatmentTemplateModel.findById(condition.treatment_template).lean().exec().catch(err => next(err)) as any;
- if (treatmentData instanceof Error) return false;
- if (!treatmentData) { // template not found
- res.status(400).json({status: 'Treatment template not available'});
- return false;
- }
-
- // validate parameters
- const {error, value: ignore} = ParametersValidate.input(condition.parameters, treatmentData.parameters, param);
- if (error) {res400(error, res); return false;}
- return treatmentData;
-}
\ No newline at end of file
diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts
index 21a278b..ae8d305 100644
--- a/src/routes/material.spec.ts
+++ b/src/routes/material.spec.ts
@@ -2,6 +2,7 @@ import should from 'should/as-function';
import _ from 'lodash';
import MaterialModel from '../models/material';
import TestHelper from "../test/helper";
+import globals from '../globals';
// TODO: color name must be unique to get color number
// TODO: separate supplier/ material name into own collections
@@ -11,6 +12,7 @@ describe('/material', () => {
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
+ after(done => TestHelper.after(done));
describe('GET /materials', () => {
it('returns all materials', done => {
@@ -22,7 +24,7 @@ describe('/material', () => {
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
- should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).length);
+ should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === globals.status.validated).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string');
@@ -50,7 +52,7 @@ describe('/material', () => {
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
- should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).length);
+ should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === globals.status.validated).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string');
@@ -89,7 +91,7 @@ describe('/material', () => {
if (err) return done(err);
const json = require('../test/db.json');
let asyncCounter = res.body.length;
- should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 0).length);
+ should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status ===globals.status.new).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string');
@@ -105,7 +107,7 @@ describe('/material', () => {
should(number).have.property('number').be.type('string');
});
MaterialModel.findById(material._id).lean().exec((err, data) => {
- should(data).have.property('status', 0);
+ should(data).have.property('status',globals.status.new);
if (--asyncCounter === 0) {
done();
}
@@ -123,7 +125,7 @@ describe('/material', () => {
if (err) return done(err);
const json = require('../test/db.json');
let asyncCounter = res.body.length;
- should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === -1).length);
+ should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status ===globals.status.deleted).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string');
@@ -139,13 +141,12 @@ describe('/material', () => {
should(number).have.property('number').be.type('string');
});
MaterialModel.findById(material._id).lean().exec((err, data) => {
- should(data).have.property('status', -1);
+ should(data).have.property('status',globals.status.deleted);
if (--asyncCounter === 0) {
done();
}
});
});
- done();
});
});
it('rejects requests from a write user', done => {
@@ -249,7 +250,7 @@ describe('/material', () => {
should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]});
MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
- should(data).have.property('status', 10);
+ should(data).have.property('status',globals.status.validated);
done();
});
});
@@ -266,7 +267,7 @@ describe('/material', () => {
should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]});
MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
- should(data).have.property('status', 10);
+ should(data).have.property('status',globals.status.validated);
done();
});
});
@@ -513,7 +514,7 @@ describe('/material', () => {
should(data[0]).have.property('mineral', '0');
should(data[0]).have.property('glass_fiber', '30');
should(data[0]).have.property('carbon_fiber', '0');
- should(data[0]).have.property('status', 0);
+ should(data[0]).have.property('status',globals.status.new);
should(data[0].numbers).have.lengthOf(0);
done();
});
@@ -552,7 +553,7 @@ describe('/material', () => {
should(data[0]).have.property('mineral', '0');
should(data[0]).have.property('glass_fiber', '30');
should(data[0]).have.property('carbon_fiber', '0');
- should(data[0]).have.property('status', 0);
+ should(data[0]).have.property('status',globals.status.new);
should(_.omit(data[0].numbers[0], '_id')).be.eql({color: 'black', number: ''});
done();
});
diff --git a/src/routes/material.ts b/src/routes/material.ts
index dd89985..4a1adb8 100644
--- a/src/routes/material.ts
+++ b/src/routes/material.ts
@@ -7,6 +7,7 @@ import SampleModel from '../models/sample';
import IdValidate from './validate/id';
import res400 from './validate/res400';
import mongoose from 'mongoose';
+import globals from '../globals';
@@ -15,7 +16,7 @@ const router = express.Router();
router.get('/materials', (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
- MaterialModel.find({status: 10}).lean().exec((err, data) => {
+ MaterialModel.find({status:globals.status.validated}).lean().exec((err, data) => {
if (err) return next(err);
res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors
});
@@ -24,14 +25,7 @@ router.get('/materials', (req, res, next) => {
router.get('/materials/:group(new|deleted)', (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
- let status;
- switch (req.params.group) {
- case 'new': status = 0;
- break;
- case 'deleted': status = -1;
- break;
- }
- MaterialModel.find({status: status}).lean().exec((err, data) => {
+ MaterialModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => {
if (err) return next(err);
res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors
});
@@ -67,7 +61,7 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
// check for changes
if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), material)) {
- material.status = 0; // set status to new
+ material.status = globals.status.new; // set status to new
}
await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => {
@@ -86,7 +80,7 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
if (data.length) {
return res.status(400).json({status: 'Material still in use'});
}
- MaterialModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec((err, data) => {
+ MaterialModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec((err, data) => {
if (err) return next(err);
if (data) {
res.json({status: 'OK'});
@@ -106,7 +100,7 @@ router.post('/material/new', async (req, res, next) => {
if (!await nameCheck(material, res, next)) return;
- material.status = 0; // set status to new
+ material.status = globals.status.new; // set status to new
await new MaterialModel(material).save((err, data) => {
if (err) return next(err);
res.json(MaterialValidate.output(data.toObject()));
diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts
index 7fe4b7f..5af91a3 100644
--- a/src/routes/measurement.spec.ts
+++ b/src/routes/measurement.spec.ts
@@ -1,8 +1,9 @@
import should from 'should/as-function';
import MeasurementModel from '../models/measurement';
import TestHelper from "../test/helper";
+import globals from '../globals';
-// TODO: allow empty values
+// TODO: restore measurements for m/a
describe('/measurement', () => {
@@ -10,15 +11,16 @@ describe('/measurement', () => {
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
+ after(done => TestHelper.after(done));
- describe('GET /mesurement/{id}', () => {
+ describe('GET /measurement/{id}', () => {
it('returns the right measurement', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/measurement/800000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
- res: {_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
+ res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
});
});
it('returns the measurement for an API key', done => {
@@ -27,7 +29,24 @@ describe('/measurement', () => {
url: '/measurement/800000000000000000000001',
auth: {key: 'janedoe'},
httpStatus: 200,
- res: {_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
+ res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
+ });
+ });
+ it('returns deleted measurements for a maintain/admin user', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/measurement/800000000000000000000004',
+ auth: {basic: 'admin'},
+ httpStatus: 200,
+ res: {_id: '800000000000000000000004', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003'}
+ });
+ });
+ it('rejects requests for deleted measurements from a write user', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/measurement/800000000000000000000004',
+ auth: {basic: 'janedoe'},
+ httpStatus: 403
});
});
it('rejects an invalid id', done => {
@@ -63,7 +82,7 @@ describe('/measurement', () => {
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {},
- res: {_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
+ res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
});
});
it('keeps unchanged values', done => {
@@ -75,10 +94,10 @@ describe('/measurement', () => {
req: {values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'});
+ should(res.body).be.eql({_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'});
MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => {
if (err) return done(err);
- should(data).have.property('status', 10);
+ should(data).have.property('status',globals.status.validated);
done();
});
});
@@ -92,10 +111,10 @@ describe('/measurement', () => {
req: {values: {'weight %': 0.5}}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '800000000000000000000002', condition_id: '700000000000000000000002', values: {'weight %': 0.5, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'});
+ should(res.body).be.eql({_id: '800000000000000000000002', sample_id: '400000000000000000000002', values: {'weight %': 0.5, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'});
MeasurementModel.findById('800000000000000000000002').lean().exec((err, data: any) => {
if (err) return done(err);
- should(data).have.property('status', 10);
+ should(data).have.property('status',globals.status.validated);
done();
});
});
@@ -109,12 +128,12 @@ describe('/measurement', () => {
req: {values: {dpt: [[1,2],[3,4],[5,6]]}}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[1,2],[3,4],[5,6]]}, measurement_template: '300000000000000000000001'});
+ should(res.body).be.eql({_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[1,2],[3,4],[5,6]]}, measurement_template: '300000000000000000000001'});
MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => {
- should(data).have.only.keys('_id', 'condition_id', 'values', 'measurement_template', 'status', '__v');
- should(data.condition_id.toString()).be.eql('700000000000000000000001');
+ should(data).have.only.keys('_id', 'sample_id', 'values', 'measurement_template', 'status', '__v');
+ should(data.sample_id.toString()).be.eql('400000000000000000000001');
should(data.measurement_template.toString()).be.eql('300000000000000000000001');
- should(data).have.property('status', 0);
+ should(data).have.property('status',globals.status.new);
should(data).have.property('values');
should(data.values).have.property('dpt', [[1,2],[3,4],[5,6]]);
done();
@@ -128,7 +147,17 @@ describe('/measurement', () => {
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {values: {'weight %': 0.9}},
- res: {_id: '800000000000000000000002', condition_id: '700000000000000000000002', values: {'weight %': 0.9, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'}
+ res: {_id: '800000000000000000000002', sample_id: '400000000000000000000002', values: {'weight %': 0.9, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'}
+ });
+ });
+ it('allows keeping empty values empty', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/measurement/800000000000000000000005',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200,
+ req: {values: {'weight %': 0.9}},
+ res: {_id: '800000000000000000000005', sample_id: '400000000000000000000002', values: {'weight %': 0.9, 'standard deviation': null}, measurement_template: '300000000000000000000002'}
});
});
it('rejects not specified values', done => {
@@ -148,7 +177,7 @@ describe('/measurement', () => {
auth: {basic: 'admin'},
httpStatus: 400,
req: {values: {val1: 4}},
- res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3]'}
+ res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3, null]'}
});
});
it('rejects a value below minimum range', done => {
@@ -181,6 +210,16 @@ describe('/measurement', () => {
res: {status: 'Invalid body format', details: '"measurement_template" is not allowed'}
});
});
+ it('rejects a new sample id', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/measurement/800000000000000000000002',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {values: {'weight %': 0.9, 'standard deviation': 0.3}, sample_id: '400000000000000000000002'},
+ res: {status: 'Invalid body format', details: '"sample_id" is not allowed'}
+ });
+ });
it('rejects editing a measurement for a write user who did not create this measurement', done => {
TestHelper.request(server, done, {
method: 'put',
@@ -197,7 +236,7 @@ describe('/measurement', () => {
auth: {basic: 'admin'},
httpStatus: 200,
req: {values: {'weight %': 0.9, 'standard deviation': 0.3}},
- res: {_id: '800000000000000000000002', condition_id: '700000000000000000000002', values: {'weight %': 0.9, 'standard deviation': 0.3}, measurement_template: '300000000000000000000002'}
+ res: {_id: '800000000000000000000002', sample_id: '400000000000000000000002', values: {'weight %': 0.9, 'standard deviation': 0.3}, measurement_template: '300000000000000000000002'}
});
});
it('rejects an invalid id', done => {
@@ -256,7 +295,7 @@ describe('/measurement', () => {
should(res.body).be.eql({status: 'OK'});
MeasurementModel.findById('800000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
- should(data).have.property('status', -1);
+ should(data).have.property('status',globals.status.deleted);
done();
});
});
@@ -326,12 +365,12 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).have.only.keys('_id', 'condition_id', 'values', 'measurement_template');
+ should(res.body).have.only.keys('_id', 'sample_id', 'values', 'measurement_template');
should(res.body).have.property('_id').be.type('string');
- should(res.body).have.property('condition_id', '700000000000000000000001');
+ should(res.body).have.property('sample_id', '400000000000000000000001');
should(res.body).have.property('measurement_template', '300000000000000000000002');
should(res.body).have.property('values');
should(res.body.values).have.property('weight %', 0.8);
@@ -345,13 +384,13 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
}).end((err, res) => {
if (err) return done(err);
MeasurementModel.findById(res.body._id).lean().exec((err, data: any) => {
if (err) return done(err);
- should(data).have.only.keys('_id', 'condition_id', 'values', 'measurement_template', 'status', '__v');
- should(data.condition_id.toString()).be.eql('700000000000000000000001');
+ should(data).have.only.keys('_id', 'sample_id', 'values', 'measurement_template', 'status', '__v');
+ should(data.sample_id.toString()).be.eql('400000000000000000000001');
should(data.measurement_template.toString()).be.eql('300000000000000000000002');
should(data).have.property('status', 0);
should(data).have.property('values');
@@ -361,24 +400,24 @@ describe('/measurement', () => {
});
});
});
- it('rejects an invalid condition id', done => {
+ it('rejects an invalid sample id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '700000000000h00000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
- res: {status: 'Invalid body format', details: '"condition_id" with value "700000000000h00000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
+ req: {sample_id: '400000000000h00000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
+ res: {status: 'Invalid body format', details: '"sample_id" with value "400000000000h00000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
- it('rejects a condition id not available', done => {
+ it('rejects a sample id not available', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '000000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
- res: {status: 'Condition id not available'}
+ req: {sample_id: '000000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
+ res: {status: 'Sample id not available'}
});
});
it('rejects an invalid measurement_template id', done => {
@@ -387,7 +426,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '30000000000h000000000002'},
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '30000000000h000000000002'},
res: {status: 'Invalid body format', details: '"measurement_template" with value "30000000000h000000000002" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
@@ -397,7 +436,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '000000000000000000000002'},
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '000000000000000000000002'},
res: {status: 'Measurement template not available'}
});
});
@@ -407,18 +446,27 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1, xx: 44}, measurement_template: '300000000000000000000002'},
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1, xx: 44}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"xx" is not allowed'}
});
});
- it('rejects missing values', done => {
+ it('accepts missing values', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/measurement/new',
auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8}, measurement_template: '300000000000000000000002'},
- res: {status: 'Invalid body format', details: '"standard deviation" is required'}
+ httpStatus: 200,
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8}, measurement_template: '300000000000000000000002'}
+ }).end((err, res) => {
+ if (err) return done(err);
+ should(res.body).have.only.keys('_id', 'sample_id', 'values', 'measurement_template');
+ should(res.body).have.property('_id').be.type('string');
+ should(res.body).have.property('sample_id', '400000000000000000000001');
+ should(res.body).have.property('measurement_template', '300000000000000000000002');
+ should(res.body).have.property('values');
+ should(res.body.values).have.property('weight %', 0.8);
+ should(res.body.values).have.property('standard deviation', null);
+ done();
});
});
it('rejects a value not in the value range', done => {
@@ -427,8 +475,8 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '700000000000000000000001', values: {val1: 4}, measurement_template: '300000000000000000000003'},
- res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3]'}
+ req: {sample_id: '400000000000000000000001', values: {val1: 4}, measurement_template: '300000000000000000000003'},
+ res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3, null]'}
});
});
it('rejects a value below minimum range', done => {
@@ -437,7 +485,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '700000000000000000000001', values: {'weight %': -1, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
+ req: {sample_id: '400000000000000000000001', values: {'weight %': -1, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"weight %" must be larger than or equal to 0'}
});
});
@@ -447,18 +495,18 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 2}, measurement_template: '300000000000000000000002'},
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 2}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"standard deviation" must be less than or equal to 0.5'}
});
});
- it('rejects a missing condition id', done => {
+ it('rejects a missing sample id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
- res: {status: 'Invalid body format', details: '"condition_id" is required'}
+ res: {status: 'Invalid body format', details: '"sample_id" is required'}
});
});
it('rejects a missing measurement_template', done => {
@@ -467,7 +515,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}},
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}},
res: {status: 'Invalid body format', details: '"measurement_template" is required'}
});
});
@@ -477,7 +525,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 403,
- req: {condition_id: '700000000000000000000003', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
+ req: {sample_id: '400000000000000000000003', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
});
});
it('accepts adding a measurement to the sample of another user for a maintain/admin user', done => {
@@ -486,12 +534,12 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'admin'},
httpStatus: 200,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).have.only.keys('_id', 'condition_id', 'values', 'measurement_template');
+ should(res.body).have.only.keys('_id', 'sample_id', 'values', 'measurement_template');
should(res.body).have.property('_id').be.type('string');
- should(res.body).have.property('condition_id', '700000000000000000000001');
+ should(res.body).have.property('sample_id', '400000000000000000000001');
should(res.body).have.property('measurement_template', '300000000000000000000002');
should(res.body).have.property('values');
should(res.body.values).have.property('weight %', 0.8);
@@ -505,7 +553,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {key: 'janedoe'},
httpStatus: 401,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
});
});
it('rejects requests from a read user', done => {
@@ -514,7 +562,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'user'},
httpStatus: 403,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
});
});
it('rejects unauthorized requests', done => {
@@ -522,7 +570,7 @@ describe('/measurement', () => {
method: 'post',
url: '/measurement/new',
httpStatus: 401,
- req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
+ req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
});
});
});
diff --git a/src/routes/measurement.ts b/src/routes/measurement.ts
index eda839e..0d0f0f6 100644
--- a/src/routes/measurement.ts
+++ b/src/routes/measurement.ts
@@ -2,12 +2,13 @@ import express from 'express';
import _ from 'lodash';
import MeasurementModel from '../models/measurement';
-import ConditionModel from '../models/condition';
import MeasurementTemplateModel from '../models/measurement_template';
+import SampleModel from '../models/sample';
import MeasurementValidate from './validate/measurement';
import IdValidate from './validate/id';
import res400 from './validate/res400';
import ParametersValidate from './validate/parameters';
+import globals from '../globals';
const router = express.Router();
@@ -15,11 +16,12 @@ const router = express.Router();
router.get('/measurement/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
- MeasurementModel.findById(req.params.id).lean().exec((err, data) => {
+ MeasurementModel.findById(req.params.id).lean().exec((err, data: any) => {
if (err) return next(err);
if (!data) {
return res.status(404).json({status: 'Not found'});
}
+ if (data.status ===globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted measurements only available for maintain/admin
res.json(MeasurementValidate.output(data));
});
@@ -34,19 +36,19 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => {
const data = await MeasurementModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
if (data instanceof Error) return;
if (!data) {
- res.status(404).json({status: 'Not found'});
+ return res.status(404).json({status: 'Not found'});
}
- // add properties needed for conditionIdCheck
+ // add properties needed for sampleIdCheck
measurement.measurement_template = data.measurement_template;
- measurement.condition_id = data.condition_id;
- if (!await conditionIdCheck(measurement, req, res, next)) return;
+ measurement.sample_id = data.sample_id;
+ if (!await sampleIdCheck(measurement, req, res, next)) return;
// check for changes
- if (measurement.values) {
+ if (measurement.values) { // fill not changed values from database
measurement.values = _.assign({}, data.values, measurement.values);
if (!_.isEqual(measurement.values, data.values)) {
- measurement.status = 0; // set status to new
+ measurement.status = globals.status.new; // set status to new
}
}
@@ -63,12 +65,12 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => {
MeasurementModel.findById(req.params.id).lean().exec(async (err, data) => {
if (err) return next(err);
if (!data) {
- res.status(404).json({status: 'Not found'});
+ return res.status(404).json({status: 'Not found'});
}
- if (!await conditionIdCheck(data, req, res, next)) return;
- await MeasurementModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => {
+ if (!await sampleIdCheck(data, req, res, next)) return;
+ await MeasurementModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec(err => {
if (err) return next(err);
- res.json({status: 'OK'});
+ return res.json({status: 'OK'});
});
});
});
@@ -79,8 +81,9 @@ router.post('/measurement/new', async (req, res, next) => {
const {error, value: measurement} = MeasurementValidate.input(req.body, 'new');
if (error) return res400(error, res);
- if (!await conditionIdCheck(measurement, req, res, next)) return;
- if (!await templateCheck(measurement, 'new', res, next)) return;
+ if (!await sampleIdCheck(measurement, req, res, next)) return;
+ measurement.values = await templateCheck(measurement, 'new', res, next);
+ if (!measurement.values) return;
measurement.status = 0;
await new MeasurementModel(measurement).save((err, data) => {
@@ -93,25 +96,38 @@ router.post('/measurement/new', async (req, res, next) => {
module.exports = router;
-async function conditionIdCheck (measurement, req, res, next) { // validate condition_id, returns false if invalid
- const sampleData = await ConditionModel.findById(measurement.condition_id).populate('sample_id').lean().exec().catch(err => {next(err); return false;}) as any;
+async function sampleIdCheck (measurement, req, res, next) { // validate sample_id, returns false if invalid or user has no access for this sample
+ const sampleData = await SampleModel.findById(measurement.sample_id).lean().exec().catch(err => {next(err); return false;}) as any;
if (!sampleData) { // sample_id not found
- res.status(400).json({status: 'Condition id not available'});
+ res.status(400).json({status: 'Sample id not available'});
return false
}
- if (sampleData.sample_id.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return false; // sample does not belong to user
+ if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return false; // sample does not belong to user
return true;
}
-async function templateCheck (measurement, param, res, next) { // validate measurement_template and values, param for new/change
+async function templateCheck (measurement, param, res, next) { // validate measurement_template and values, returns values, true if values are {} or false if invalid, param for 'new'/'change'
const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template).lean().exec().catch(err => {next(err); return false;}) as any;
if (!templateData) { // template not found
res.status(400).json({status: 'Measurement template not available'});
return false
}
+ // fill not given values for new measurements
+ if (param === 'new') {
+ if (Object.keys(measurement.values).length === 0) {
+ res.status(400).json({status: 'At least one value is required'});
+ return false
+ }
+ const fillValues = {}; // initialize not given values with null
+ templateData.parameters.forEach(parameter => {
+ fillValues[parameter.name] = null;
+ });
+ measurement.values = _.assign({}, fillValues, measurement.values);
+ }
+
// validate values
- const {error, value: ignore} = ParametersValidate.input(measurement.values, templateData.parameters, param);
+ const {error, value} = ParametersValidate.input(measurement.values, templateData.parameters, 'null');
if (error) {res400(error, res); return false;}
- return true;
+ return value || true;
}
\ No newline at end of file
diff --git a/src/routes/root.spec.ts b/src/routes/root.spec.ts
index f8a803f..569af8b 100644
--- a/src/routes/root.spec.ts
+++ b/src/routes/root.spec.ts
@@ -1,4 +1,5 @@
import TestHelper from "../test/helper";
+import db from '../db';
describe('/', () => {
@@ -6,6 +7,7 @@ describe('/', () => {
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
+ after(done => TestHelper.after(done));
describe('GET /', () => {
it('returns the root message', done => {
@@ -40,7 +42,15 @@ describe('/', () => {
TestHelper.request(server, done, {
method: 'get',
url: '/authorized',
- auth: {name: 'admin', pass: 'Abc123!!'},
+ auth: {basic: {name: 'admin', pass: 'Abc123!!'}},
+ httpStatus: 401
+ });
+ });
+ it('does not work with incorrect username', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/authorized',
+ auth: {basic: {name: 'adminxx', pass: 'Abc123!!'}},
httpStatus: 401
});
});
@@ -66,4 +76,65 @@ describe('/', () => {
});
});
});
+
+ describe('An invalid JSON body', () => {
+ it('is rejected', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/',
+ httpStatus: 400,
+ reqType: 'json',
+ req: '{"xxx"}',
+ res: {status: 'Invalid JSON body'}
+ });
+
+ });
+ });
+
+ describe('A not connected database', () => { // RUN AS LAST OR RECONNECT DATABASE!!
+ it('resolves to an 500 error', done => {
+ db.disconnect(() => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/',
+ httpStatus: 500
+ });
+ });
+ });
+ });
+});
+
+describe('The /api/{url} redirect', () => {
+ let server;
+ let counter = 0; // count number of current test method
+ before(done => {
+ process.env.port = '2999';
+ db.connect('test', done);
+ });
+ beforeEach(done => {
+ process.env.NODE_ENV = counter === 1 ? 'production' : 'test';
+ counter ++;
+ server = TestHelper.beforeEach(server, done);
+ });
+ afterEach(done => TestHelper.afterEach(server, done));
+ after(done => TestHelper.after(done));
+
+
+ it('returns the right method', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/api/authorized',
+ auth: {basic: 'admin'},
+ httpStatus: 200,
+ res: {status: 'Authorization successful', method: 'basic'}
+ });
+ });
+ it('is disabled in production', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/api/authorized',
+ auth: {basic: 'admin'},
+ httpStatus: 404
+ });
+ });
});
\ No newline at end of file
diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts
index df1ad05..cfeeb7c 100644
--- a/src/routes/sample.spec.ts
+++ b/src/routes/sample.spec.ts
@@ -3,16 +3,23 @@ import SampleModel from '../models/sample';
import NoteModel from '../models/note';
import NoteFieldModel from '../models/note_field';
import TestHelper from "../test/helper";
+import globals from '../globals';
// TODO: generate output for ML in format DPT -> data, implement filtering, field selection
+// TODO: filter by not completely filled/no measurements
// TODO: write script for data import
// TODO: delete everything (measurements, condition) with sample
+// TODO: allow adding sample numbers for existing samples
+
+// TODO: Do not allow validation or measurement entry without condition
+
describe('/sample', () => {
let server;
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
+ after(done => TestHelper.after(done));
describe('GET /samples', () => {
it('returns all samples', done => {
@@ -24,14 +31,16 @@ describe('/sample', () => {
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
- should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length);
+ should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status ===globals.status.validated).length);
should(res.body).matchEach(sample => {
- should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
+ should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(sample).have.property('_id').be.type('string');
should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').be.type('string');
+ should(sample).have.property('condition').be.type('object');
+ should(sample.condition).have.property('condition_template').be.type('string');
should(sample).have.property('material_id').be.type('string');
should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string');
@@ -48,17 +57,19 @@ describe('/sample', () => {
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
- should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length);
- should(res.body).matchEach(material => {
- should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
- should(material).have.property('_id').be.type('string');
- should(material).have.property('number').be.type('string');
- should(material).have.property('type').be.type('string');
- should(material).have.property('color').be.type('string');
- should(material).have.property('batch').be.type('string');
- should(material).have.property('material_id').be.type('string');
- should(material).have.property('note_id');
- should(material).have.property('user_id').be.type('string');
+ should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status ===globals.status.validated).length);
+ should(res.body).matchEach(sample => {
+ should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
+ should(sample).have.property('_id').be.type('string');
+ should(sample).have.property('number').be.type('string');
+ should(sample).have.property('type').be.type('string');
+ should(sample).have.property('color').be.type('string');
+ should(sample).have.property('batch').be.type('string');
+ should(sample).have.property('condition').be.type('object');
+ should(sample.condition).have.property('condition_template').be.type('string');
+ should(sample).have.property('material_id').be.type('string');
+ should(sample).have.property('note_id');
+ should(sample).have.property('user_id').be.type('string');
});
done();
});
@@ -83,25 +94,28 @@ describe('/sample', () => {
if (err) return done(err);
const json = require('../test/db.json');
let asyncCounter = res.body.length;
- should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 0).length);
+ should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status ===globals.status.new).length);
should(res.body).matchEach(sample => {
- should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
+ should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(sample).have.property('_id').be.type('string');
should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').be.type('string');
+ should(sample).have.property('condition').be.type('object');
+ if (Object.keys(sample.condition).length > 0) {
+ should(sample.condition).have.property('condition_template').be.type('string');
+ }
should(sample).have.property('material_id').be.type('string');
should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string');
SampleModel.findById(sample._id).lean().exec((err, data) => {
- should(data).have.property('status', 0);
+ should(data).have.property('status',globals.status.new);
if (--asyncCounter === 0) {
done();
}
});
});
- done();
});
});
it('returns all deleted samples', done => {
@@ -116,23 +130,26 @@ describe('/sample', () => {
let asyncCounter = res.body.length;
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === -1).length);
should(res.body).matchEach(sample => {
- should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
+ should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(sample).have.property('_id').be.type('string');
should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').be.type('string');
+ should(sample).have.property('condition').be.type('object');
+ should(sample.condition).have.property('condition_template').be.type('string');
+ should(sample.condition).have.property('condition_template').be.type('string');
+ should(sample.condition).have.property('condition_template').be.type('string');
should(sample).have.property('material_id').be.type('string');
should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string');
SampleModel.findById(sample._id).lean().exec((err, data) => {
- should(data).have.property('status', -1);
+ should(data).have.property('status',globals.status.deleted);
if (--asyncCounter === 0) {
done();
}
});
});
- done();
});
});
it('rejects requests from a write user', done => {
@@ -160,6 +177,73 @@ describe('/sample', () => {
});
});
+ describe('GET /sample/{id}', () => {
+ it('returns the right sample', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/400000000000000000000003',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200,
+ res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, user: 'admin'}
+ });
+ });
+
+ it('works with an API key', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/400000000000000000000003',
+ auth: {key: 'janedoe'},
+ httpStatus: 200,
+ res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, user: 'admin'}
+ });
+ });
+
+ it('returns a deleted sample for a maintain/admin user', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/400000000000000000000005',
+ auth: {basic: 'admin'},
+ httpStatus: 200,
+ res: {_id: '400000000000000000000005', number: 'Rng33', type: 'granulate', color: 'black', batch: '1653000308', condition: {condition_template: '200000000000000000000003'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {}, user: 'admin'}
+ });
+ });
+
+ it('returns 403 for a write user when requesting a deleted sample', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/400000000000000000000005',
+ auth: {basic: 'janedoe'},
+ httpStatus: 403
+ });
+ });
+
+ it('returns 404 for an unknown sample', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/000000000000000000000005',
+ auth: {basic: 'janedoe'},
+ httpStatus: 404
+ });
+ });
+
+ it('rejects an invalid id', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/400000000h00000000000005',
+ auth: {basic: 'janedoe'},
+ httpStatus: 404
+ });
+ });
+
+ it('rejects unauthorized requests', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/400000000000000000000005',
+ httpStatus: 401
+ });
+ });
+ });
+
describe('PUT /sample/{id}', () => {
it('returns the right sample', done => {
TestHelper.request(server, done, {
@@ -168,7 +252,7 @@ describe('/sample', () => {
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {},
- res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
+ res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
});
});
it('keeps unchanged properties', done => {
@@ -177,21 +261,22 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}}
+ req: {type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', notes: {}}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
+ should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err);
- should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
+ should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.property('_id');
should(data).have.property('number', '1');
should(data).have.property('color', 'black');
should(data).have.property('type', 'granulate');
should(data).have.property('batch', '');
+ should(data).have.property('condition', {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'});
should(data.material_id.toString()).be.eql('100000000000000000000004');
should(data.user_id.toString()).be.eql('000000000000000000000002');
- should(data).have.property('status', 10);
+ should(data).have.property('status',globals.status.validated);
should(data).have.property('note_id', null);
done();
});
@@ -206,10 +291,27 @@ describe('/sample', () => {
req: {type: 'granulate'}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
+ should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err);
- should(data).have.property('status', 10);
+ should(data).have.property('status',globals.status.validated);
+ done();
+ });
+ });
+ });
+ it('keeps an unchanged condition', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200,
+ req: {condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}}
+ }).end((err, res) => {
+ if (err) return done(err);
+ should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
+ SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
+ if (err) return done (err);
+ should(data).have.property('status',globals.status.validated);
done();
});
});
@@ -223,18 +325,21 @@ describe('/sample', () => {
req: {notes: {comment: 'Stoff gesperrt', sample_references: []}}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '400000000000000000000002', number: '21', type: 'granulate', color: 'natural', batch: '1560237365', material_id: '100000000000000000000001', note_id: '500000000000000000000001', user_id: '000000000000000000000002'});
+ should(res.body).be.eql({_id: '400000000000000000000002', number: '21', type: 'granulate', color: 'natural', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000001', note_id: '500000000000000000000001', user_id: '000000000000000000000002'});
SampleModel.findById('400000000000000000000002').lean().exec((err, data: any) => {
if (err) return done (err);
- should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
+ should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.property('_id');
should(data).have.property('number', '21');
should(data).have.property('color', 'natural');
should(data).have.property('type', 'granulate');
should(data).have.property('batch', '1560237365');
+ should(data.condition).have.property('material', 'copper');
+ should(data.condition).have.property('weeks', 3);
+ should(data.condition.condition_template.toString()).be.eql('200000000000000000000001');
should(data.material_id.toString()).be.eql('100000000000000000000001');
should(data.user_id.toString()).be.eql('000000000000000000000002');
- should(data).have.property('status', 10);
+ should(data).have.property('status',globals.status.validated);
should(data.note_id.toString()).be.eql('500000000000000000000001');
done();
});
@@ -246,20 +351,21 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ req: {type: 'part', color: 'signalviolet', batch: '114531', condition: {condition_template: '200000000000000000000003'}, material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end(err => {
if (err) return done (err);
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err);
- should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
+ should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.property('_id');
should(data).have.property('number', '1');
should(data).have.property('color', 'signalviolet');
should(data).have.property('type', 'part');
should(data).have.property('batch', '114531');
+ should(data).have.property('condition', {condition_template: '200000000000000000000003'});
should(data.material_id.toString()).be.eql('100000000000000000000002');
should(data.user_id.toString()).be.eql('000000000000000000000002');
- should(data).have.property('status', 0);
+ should(data).have.property('status',globals.status.new);
should(data).have.property('note_id');
NoteModel.findById(data.note_id).lean().exec((err, data: any) => {
if (err) return done (err);
@@ -267,7 +373,7 @@ describe('/sample', () => {
should(data).have.property('comment', 'Testcomment');
should(data).have.property('sample_references');
should(data.sample_references).have.lengthOf(1);
- should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
+ should(data.sample_references[0].sample_id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0]).have.property('relation', 'part to this sample');
done();
});
@@ -350,7 +456,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Color not available for material'}
});
});
@@ -360,7 +466,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '000000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '000000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Material not available'}
});
});
@@ -370,7 +476,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {number: 25, type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {number: 25, type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"number" is not allowed'}
});
});
@@ -380,7 +486,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '000000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Sample reference not available'}
});
});
@@ -390,7 +496,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
@@ -400,7 +506,87 @@ describe('/sample', () => {
url: '/sample/10000000000h000000000001',
auth: {basic: 'janedoe'},
httpStatus: 404,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ });
+ });
+ it('rejects not specified condition parameters', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {condition: {material: 'copper', weeks: 3, xxx: 44, condition_template: '200000000000000000000001'}},
+ res: {status: 'Invalid body format', details: '"xxx" is not allowed'}
+ });
+ });
+ it('rejects a condition parameter not in the value range', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {condition: {material: 'xx', weeks: 3, condition_template: '200000000000000000000001'}},
+ res: {status: 'Invalid body format', details: '"material" must be one of [copper, hot air]'}
+ });
+ });
+ it('rejects a condition parameter below minimum range', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {condition: {material: 'copper', weeks: 0, condition_template: '200000000000000000000001'}},
+ res: {status: 'Invalid body format', details: '"weeks" must be larger than or equal to 1'}
+ });
+ });
+ it('rejects a condition parameter above maximum range', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {condition: {material: 'copper', weeks: 10.5, condition_template: '200000000000000000000001'}},
+ res: {status: 'Invalid body format', details: '"weeks" must be less than or equal to 10'}
+ });
+ });
+ it('rejects an invalid condition template', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {condition: {material: 'copper', weeks: 3, condition_template: '200000000000h00000000001'}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('rejects an unknown condition template', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {condition: {material: 'copper', weeks: 3, condition_template: '000000000000000000000001'}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('allows keeping an empty condition empty', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000006',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200,
+ req: {condition: {}},
+ res: {_id: '400000000000000000000006', number: 'Rng36', type: 'granulate', color: 'black', batch: '', condition: {}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
+ });
+ });
+ it('rejects an changing back to an empty condition', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/sample/400000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {condition: {}},
+ res: {status: 'Condition template not available'}
});
});
it('rejects an API key', done => {
@@ -409,7 +595,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {key: 'janedoe'},
httpStatus: 401,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
});
});
it('rejects changes for samples from another user for a write user', done => {
@@ -428,7 +614,7 @@ describe('/sample', () => {
auth: {basic: 'admin'},
httpStatus: 200,
req: {},
- res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
+ res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {condition_template: '200000000000000000000001', material: 'copper', weeks: 3}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
});
});
it('rejects requests from a read user', done => {
@@ -437,7 +623,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'user'},
httpStatus: 403,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
});
});
it('returns 404 for an unknown sample', done => {
@@ -446,7 +632,7 @@ describe('/sample', () => {
url: '/sample/000000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 404,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
});
})
it('rejects unauthorized requests', done => {
@@ -454,7 +640,7 @@ describe('/sample', () => {
method: 'put',
url: '/sample/400000000000000000000001',
httpStatus: 401,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
});
});
});
@@ -471,15 +657,18 @@ describe('/sample', () => {
should(res.body).be.eql({status: 'OK'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done(err);
- should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
+ should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.property('_id');
should(data).have.property('number', '1');
should(data).have.property('color', 'black');
should(data).have.property('type', 'granulate');
should(data).have.property('batch', '');
+ should(data.condition).have.property('material', 'copper');
+ should(data.condition).have.property('weeks', 3);
+ should(data.condition.condition_template.toString()).be.eql('200000000000000000000001');
should(data.material_id.toString()).be.eql('100000000000000000000004');
should(data.user_id.toString()).be.eql('000000000000000000000002');
- should(data).have.property('status', -1);
+ should(data).have.property('status',globals.status.deleted);
should(data).have.property('note_id', null);
done();
});
@@ -536,7 +725,7 @@ describe('/sample', () => {
NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.property('sample_references').with.lengthOf(1);
- should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
+ should(data.sample_references[0].sample_id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0]).have.property('relation', 'part to sample');
done();
});
@@ -555,7 +744,7 @@ describe('/sample', () => {
should(res.body).be.eql({status: 'OK'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
- should(data).have.property('status', -1);
+ should(data).have.property('status',globals.status.deleted);
done();
});
});
@@ -617,15 +806,16 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end((err, res) => {
if (err) return done (err);
- should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id');
+ should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(res.body).have.property('_id').be.type('string');
- should(res.body).have.property('number', 'Rng34');
+ should(res.body).have.property('number', 'Rng37');
should(res.body).have.property('color', 'black');
should(res.body).have.property('type', 'granulate');
should(res.body).have.property('batch', '1560237365');
+ should(res.body).have.property('condition', {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'});
should(res.body).have.property('material_id', '100000000000000000000001');
should(res.body).have.property('note_id').be.type('string');
should(res.body).have.property('user_id', '000000000000000000000002');
@@ -638,21 +828,22 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end(err => {
if (err) return done (err);
- SampleModel.find({number: 'Rng34'}).lean().exec((err, data: any) => {
+ SampleModel.find({number: 'Rng37'}).lean().exec((err, data: any) => {
if (err) return done (err);
should(data).have.lengthOf(1);
- should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
+ should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data[0]).have.property('_id');
- should(data[0]).have.property('number', 'Rng34');
+ should(data[0]).have.property('number', 'Rng37');
should(data[0]).have.property('color', 'black');
should(data[0]).have.property('type', 'granulate');
should(data[0]).have.property('batch', '1560237365');
+ should(data[0]).have.property('condition', {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'});
should(data[0].material_id.toString()).be.eql('100000000000000000000001');
should(data[0].user_id.toString()).be.eql('000000000000000000000002');
- should(data[0]).have.property('status', 0);
+ should(data[0]).have.property('status',globals.status.new);
should(data[0]).have.property('note_id');
NoteModel.findById(data[0].note_id).lean().exec((err, data: any) => {
if (err) return done (err);
@@ -660,7 +851,7 @@ describe('/sample', () => {
should(data).have.property('comment', 'Testcomment');
should(data).have.property('sample_references');
should(data.sample_references).have.lengthOf(1);
- should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
+ should(data.sample_references[0].sample_id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0]).have.property('relation', 'part to this sample');
done();
});
@@ -710,10 +901,10 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'johnnydoe'},
httpStatus: 200,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end((err, res) => {
if (err) return done (err);
- should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id');
+ should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('number', 'Fe1');
should(res.body).have.property('color', 'black');
@@ -725,13 +916,35 @@ describe('/sample', () => {
done();
});
});
+ it('accepts a sample without condition', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ }).end((err, res) => {
+ if (err) return done (err);
+ should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
+ should(res.body).have.property('_id').be.type('string');
+ should(res.body).have.property('number', 'Rng37');
+ should(res.body).have.property('color', 'black');
+ should(res.body).have.property('type', 'granulate');
+ should(res.body).have.property('batch', '1560237365');
+ should(res.body).have.property('condition', {});
+ should(res.body).have.property('material_id', '100000000000000000000001');
+ should(res.body).have.property('note_id').be.type('string');
+ should(res.body).have.property('user_id', '000000000000000000000002');
+ done();
+ });
+ });
it('rejects a color not defined for the material', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {color: 'green', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {color: 'green', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Color not available for material'}
});
});
@@ -741,7 +954,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '000000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '000000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Material not available'}
});
});
@@ -751,7 +964,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {number: 'Rng34', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {number: 'Rng34', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"number" is not allowed'}
});
});
@@ -761,17 +974,97 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
+ req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '000000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Sample reference not available'}
});
});
+ it('rejects an invalid condition_template id', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '20000h000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('rejects a not existing condition_template id', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '000000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('rejects not specified condition parameters', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, xxx: 23, condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('rejects missing condition parameters', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('rejects condition parameters not in the value range', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'xxx', weeks: 3, condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('rejects a condition parameter below minimum range', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 0, condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('rejects a condition parameter above maximum range', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 11, condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ res: {status: 'Condition template not available'}
+ });
+ });
+ it('rejects a condition without condition template', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/sample/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ res: {status: 'Condition template not available'}
+ });
+ });
it('rejects a missing color', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"color" is required'}
});
});
@@ -781,7 +1074,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {color: 'black', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {color: 'black', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"type" is required'}
});
});
@@ -791,7 +1084,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {color: 'black', type: 'granulate', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {color: 'black', type: 'granulate', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"batch" is required'}
});
});
@@ -801,7 +1094,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {color: 'black', type: 'granulate', batch: '1560237365', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {color: 'black', type: 'granulate', batch: '1560237365', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"material_id" is required'}
});
});
@@ -811,7 +1104,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
+ req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
@@ -821,7 +1114,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {key: 'janedoe'},
httpStatus: 401,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
});
});
it('rejects requests from a read user', done => {
@@ -830,7 +1123,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'user'},
httpStatus: 403,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
});
});
it('rejects unauthorized requests', done => {
@@ -838,7 +1131,7 @@ describe('/sample', () => {
method: 'post',
url: '/sample/new',
httpStatus: 401,
- req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
+ req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
});
});
});
diff --git a/src/routes/sample.ts b/src/routes/sample.ts
index 43acd6e..ed1afb3 100644
--- a/src/routes/sample.ts
+++ b/src/routes/sample.ts
@@ -5,10 +5,15 @@ import SampleValidate from './validate/sample';
import NoteFieldValidate from './validate/note_field';
import res400 from './validate/res400';
import SampleModel from '../models/sample'
+import MeasurementModel from '../models/measurement';
import MaterialModel from '../models/material';
import NoteModel from '../models/note';
import NoteFieldModel from '../models/note_field';
import IdValidate from './validate/id';
+import mongoose from "mongoose";
+import ConditionTemplateModel from '../models/condition_template';
+import ParametersValidate from './validate/parameters';
+import globals from '../globals';
const router = express.Router();
@@ -16,7 +21,7 @@ const router = express.Router();
router.get('/samples', (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
- SampleModel.find({status: 10}).lean().exec((err, data) => {
+ SampleModel.find({status: globals.status.validated}).lean().exec((err, data) => {
if (err) return next(err);
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors
})
@@ -25,17 +30,32 @@ router.get('/samples', (req, res, next) => {
router.get('/samples/:group(new|deleted)', (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
- let status;
- switch (req.params.group) {
- case 'new': status = 0;
- break;
- case 'deleted': status = -1;
- break;
- }
- SampleModel.find({status: status}).lean().exec((err, data) => {
+ SampleModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => {
if (err) return next(err);
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors
- })
+ });
+});
+
+router.get('/sample/' + IdValidate.parameter(), (req, res, next) => {
+ if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
+
+ SampleModel.findById(req.params.id).populate('material_id').populate('user_id', 'name').populate('note_id').lean().exec((err, sampleData: any) => {
+ if (err) return next(err);
+
+ if (sampleData) {
+ if (sampleData.status ===globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted samples only available for maintain/admin
+ sampleData.material = sampleData.material_id; // map data to right keys
+ sampleData.user = sampleData.user_id.name;
+ sampleData.notes = sampleData.note_id ? sampleData.note_id : {};
+ MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data) => {
+ sampleData.measurements = data;
+ res.json(SampleValidate.output(sampleData, 'details'));
+ });
+ }
+ else {
+ res.status(404).json({status: 'Not found'});
+ }
+ });
});
router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
@@ -60,6 +80,10 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
if (!await materialCheck(sample, res, next, sampleData.material_id)) return;
}
+ if (sample.hasOwnProperty('condition') && !(_.isEmpty(sample.condition) && _.isEmpty(sampleData.condition))) { // do not execute check if condition is and was empty
+ if (!await conditionCheck(sample.condition, 'change', res, next)) return;
+ }
+
if (sample.hasOwnProperty('notes')) {
let newNotes = true;
if (sampleData.note_id !== null) { // old notes data exists
@@ -89,10 +113,10 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
// check for changes
if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) {
- sample.status = 0;
+ sample.status = globals.status.new;
}
- await SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data) => {
+ await SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data: any) => {
if (err) return next(err);
res.json(SampleValidate.output(data));
});
@@ -112,7 +136,7 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
// only maintain and admin are allowed to edit other user's data
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
- await SampleModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { // set sample status
+ await SampleModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec(err => { // set sample status
if (err) return next(err);
if (sampleData.note_id !== null) { // handle notes
NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // find notes to update note_fields
@@ -133,6 +157,10 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
router.post('/sample/new', async (req, res, next) => {
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
+ if (!req.body.hasOwnProperty('condition')) { // add empty condition if not specified
+ req.body.condition = {};
+ }
+
const {error, value: sample} = SampleValidate.input(req.body, 'new');
if (error) return res400(error, res);
@@ -143,7 +171,11 @@ router.post('/sample/new', async (req, res, next) => {
customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
}
- sample.status = 0; // set status to new
+ if (!_.isEmpty(sample.condition)) { // do not execute check if condition is empty
+ if (!await conditionCheck(sample.condition, 'change', res, next)) return;
+ }
+
+ sample.status = globals.status.new; // set status to new
sample.number = await numberGenerate(sample, req, res, next);
if (!sample.number) return;
@@ -152,6 +184,7 @@ router.post('/sample/new', async (req, res, next) => {
delete sample.notes;
sample.note_id = data._id;
sample.user_id = req.authDetails.id;
+
new SampleModel(sample).save((err, data) => {
if (err) return next(err);
res.json(SampleValidate.output(data.toObject()));
@@ -172,14 +205,15 @@ router.get('/sample/notes/fields', (req, res, next) => {
module.exports = router;
-async function numberGenerate (sample, req, res, next) { // generate number, returns false on error
+async function numberGenerate (sample, req, res, next) { // generate number in format Location32, returns false on error
const sampleData = await SampleModel
- .find({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')})
+ .findOne({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')})
+ .sort({number: -1})
.lean()
.exec()
.catch(err => next(err));
if (sampleData instanceof Error) return false;
- return req.authDetails.location + (sampleData.length > 0 ? Number(sampleData[0].number.replace(/[^0-9]+/g, '')) + 1 : 1);
+ return req.authDetails.location + (sampleData ? Number(sampleData.number.replace(/[^0-9]+/g, '')) + 1 : 1);
}
async function materialCheck (sample, res, next, id = sample.material_id) { // validate material_id and color, returns false if invalid
@@ -196,13 +230,31 @@ async function materialCheck (sample, res, next, id = sample.material_id) { //
return true;
}
+async function conditionCheck (condition, param, res, next) { // validate treatment template, returns false if invalid, otherwise template data
+ if (!condition.condition_template || !IdValidate.valid(condition.condition_template)) { // template id not found
+ res.status(400).json({status: 'Condition template not available'});
+ return false;
+ }
+ const conditionData = await ConditionTemplateModel.findById(condition.condition_template).lean().exec().catch(err => next(err)) as any;
+ if (conditionData instanceof Error) return false;
+ if (!conditionData) { // template not found
+ res.status(400).json({status: 'Condition template not available'});
+ return false;
+ }
+
+ // validate parameters
+ const {error, value: ignore} = ParametersValidate.input(_.omit(condition, 'condition_template'), conditionData.parameters, param);
+ if (error) {res400(error, res); return false;}
+ return conditionData;
+}
+
function sampleRefCheck (sample, res, next) { // validate sample_references, resolves false for invalid reference
return new Promise(resolve => {
if (sample.notes.sample_references.length > 0) { // there are sample_references
let referencesCount = sample.notes.sample_references.length; // count to keep track of running async operations
sample.notes.sample_references.forEach(reference => {
- SampleModel.findById(reference.id).lean().exec((err, data) => {
+ SampleModel.findById(reference.sample_id).lean().exec((err, data) => {
if (err) {next(err); resolve(false)}
if (!data) {
res.status(400).json({status: 'Sample reference not available'});
@@ -230,7 +282,7 @@ function customFieldsChange (fields, amount) { // update custom_fields and resp
if (err) return console.error(err);
})
}
- else if (data.qty <= 0) {
+ else if (data.qty <= 0) { // delete document if field is not used anymore
NoteFieldModel.findOneAndDelete({name: field}).lean().exec(err => {
if (err) return console.error(err);
});
diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts
index 878b778..54adfcb 100644
--- a/src/routes/template.spec.ts
+++ b/src/routes/template.spec.ts
@@ -1,36 +1,38 @@
import should from 'should/as-function';
import _ from 'lodash';
-import TemplateTreatmentModel from '../models/treatment_template';
+import TemplateConditionModel from '../models/condition_template';
import TemplateMeasurementModel from '../models/measurement_template';
import TestHelper from "../test/helper";
// TODO: do not allow usage of old templates for new samples
+// TODO: remove number_prefix
+// TODO: template parameters are not allowed to be condition_template
describe('/template', () => {
let server;
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
+ after(done => TestHelper.after(done));
- describe('/template/treatment', () => {
- describe('GET /template/treatments', () => {
- it('returns all treatment templates', done => {
+ describe('/template/condition', () => {
+ describe('GET /template/conditions', () => {
+ it('returns all condition templates', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/template/treatments',
+ url: '/template/conditions',
auth: {basic: 'janedoe'},
httpStatus: 200
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
- should(res.body).have.lengthOf(json.collections.treatment_templates.length);
- should(res.body).matchEach(treatment => {
- should(treatment).have.only.keys('_id', 'name', 'version', 'parameters', 'number_prefix');
- should(treatment).have.property('_id').be.type('string');
- should(treatment).have.property('name').be.type('string');
- should(treatment).have.property('version').be.type('number');
- should(treatment).have.property('number_prefix').be.type('string');
- should(treatment.parameters).matchEach(number => {
+ should(res.body).have.lengthOf(json.collections.condition_templates.length);
+ should(res.body).matchEach(condition => {
+ should(condition).have.only.keys('_id', 'name', 'version', 'parameters');
+ should(condition).have.property('_id').be.type('string');
+ should(condition).have.property('name').be.type('string');
+ should(condition).have.property('version').be.type('number');
+ should(condition.parameters).matchEach(number => {
should(number).have.only.keys('name', 'range');
should(number).have.property('name').be.type('string');
should(number).have.property('range').be.type('object');
@@ -42,7 +44,7 @@ describe('/template', () => {
it('rejects an API key', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/template/treatments',
+ url: '/template/conditions',
auth: {key: 'janedoe'},
httpStatus: 401
});
@@ -50,26 +52,26 @@ describe('/template', () => {
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/template/treatments',
+ url: '/template/conditions',
httpStatus: 401
});
});
});
- describe('GET /template/treatment/{id}', () => {
- it('returns the right treatment template', done => {
+ describe('GET /template/condition/{id}', () => {
+ it('returns the right condition template', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
- res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
+ res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
});
});
it('rejects an API key', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {key: 'janedoe'},
httpStatus: 401
});
@@ -77,7 +79,7 @@ describe('/template', () => {
it('rejects an unknown id', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/template/treatment/000000000000000000000001',
+ url: '/template/condition/000000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 404
});
@@ -85,58 +87,57 @@ describe('/template', () => {
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
httpStatus: 401
});
});
});
- describe('PUT /template/treatment/{name}', () => {
- it('returns the right treatment template', done => {
+ describe('PUT /template/condition/{name}', () => {
+ it('returns the right condition template', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {},
- res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
+ res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
});
});
it('keeps unchanged properties', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]},
- res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
+ res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
});
});
it('keeps only one unchanged property', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {name: 'heat treatment'},
- res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
+ res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
});
});
it('changes the given properties', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
}).end((err, res) => {
if (err) return done(err);
- TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => {
+ TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
if (err) return done(err);
- should(data).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v');
+ should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
should(data).have.property('name', 'heat aging');
should(data).have.property('version', 2);
- should(data).have.property('number_prefix', 'A');
should(data).have.property('parameters').have.lengthOf(1);
should(data.parameters[0]).have.property('name', 'time');
should(data.parameters[0]).have.property('range');
@@ -148,18 +149,17 @@ describe('/template', () => {
it('allows changing only one property', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {name: 'heat aging'}
}).end((err, res) => {
if (err) return done(err);
- TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => {
+ TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
if (err) return done(err);
- should(data).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v');
+ should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
should(data).have.property('name', 'heat aging');
should(data).have.property('version', 2);
- should(data).have.property('number_prefix', 'A');
should(data).have.property('parameters').have.lengthOf(2);
should(data.parameters[0]).have.property('name', 'material');
should(data.parameters[1]).have.property('name', 'weeks');
@@ -170,59 +170,59 @@ describe('/template', () => {
it('supports values ranges', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {parameters: [{name: 'time', range: {values: [1, 2, 5]}}]}
}).end((err, res) => {
if (err) return done(err);
- should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {values: [1, 2, 5]}}]});
+ should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, parameters: [{name: 'time', range: {values: [1, 2, 5]}}]});
done();
});
});
it('supports min max ranges', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {parameters: [{name: 'time', range: {min: 1, max: 11}}]}
}).end((err, res) => {
if (err) return done(err);
- should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1, max: 11}}]});
+ should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, parameters: [{name: 'time', range: {min: 1, max: 11}}]});
done();
});
});
it('supports array type ranges', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {parameters: [{name: 'time', range: {type: 'array'}}]}
}).end((err, res) => {
if (err) return done(err);
- should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {type: 'array'}}]});
+ should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, parameters: [{name: 'time', range: {type: 'array'}}]});
done();
});
});
it('supports empty ranges', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {parameters: [{name: 'time', range: {}}]}
}).end((err, res) => {
if (err) return done(err);
- should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {}}]});
+ should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, parameters: [{name: 'time', range: {}}]});
done();
});
});
it('rejects not specified parameters', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 400,
req: {name: 'heat treatment', parameters: [{name: 'material', range: {xx: 5}}]},
@@ -232,7 +232,7 @@ describe('/template', () => {
it('rejects an invalid id', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/2000000000h0000000000001',
+ url: '/template/condition/2000000000h0000000000001',
auth: {basic: 'admin'},
httpStatus: 404,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
@@ -241,26 +241,16 @@ describe('/template', () => {
it('rejects an unknown id', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/000000000000000000000001',
+ url: '/template/condition/000000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 404,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
});
});
- it('rejects already existing number prefixes', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/template/treatment/200000000000000000000001',
- auth: {basic: 'admin'},
- httpStatus: 400,
- req: {number_prefix: 'B', parameters: [{name: 'time', range: {min: 1}}]},
- res: {status: 'Number prefix already taken'}
- });
- });
it('rejects an API key', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {key: 'admin'},
httpStatus: 401,
req: {}
@@ -269,7 +259,7 @@ describe('/template', () => {
it('rejects requests from a write user', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 403,
req: {}
@@ -278,27 +268,26 @@ describe('/template', () => {
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'put',
- url: '/template/treatment/200000000000000000000001',
+ url: '/template/condition/200000000000000000000001',
httpStatus: 401,
req: {}
});
});
});
- describe('POST /template/treatment/new', () => {
- it('returns the right treatment template', done => {
+ describe('POST /template/condition/new', () => {
+ it('returns the right condition template', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 200,
- req: {name: 'heat treatment3', number_prefix: 'C', parameters: [{name: 'material', range: {values: ['copper']}}]}
+ req: {name: 'heat treatment3', parameters: [{name: 'material', range: {values: ['copper']}}]}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters');
+ should(res.body).have.only.keys('_id', 'name', 'version', 'parameters');
should(res.body).have.property('name', 'heat treatment3');
should(res.body).have.property('version', 1);
- should(res.body).have.property('number_prefix', 'C');
should(res.body).have.property('parameters').have.lengthOf(1);
should(res.body.parameters[0]).have.property('name', 'material');
should(res.body.parameters[0]).have.property('range');
@@ -310,18 +299,17 @@ describe('/template', () => {
it('stores the template', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 200,
- req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}
+ req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
}).end((err, res) => {
if (err) return done(err);
- TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => {
+ TemplateConditionModel.findById(res.body._id).lean().exec((err, data:any) => {
if (err) return done(err);
- should(data).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v');
+ should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v');
should(data).have.property('name', 'heat aging');
should(data).have.property('version', 1);
- should(data).have.property('number_prefix', 'C');
should(data).have.property('parameters').have.lengthOf(1);
should(data.parameters[0]).have.property('name', 'time');
should(data.parameters[0]).have.property('range');
@@ -333,117 +321,97 @@ describe('/template', () => {
it('rejects a missing name', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
- req: {number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]},
+ req: {parameters: [{name: 'time', range: {min: 1}}]},
res: {status: 'Invalid body format', details: '"name" is required'}
});
});
- it('rejects a missing number prefix', done => {
+ it('rejects a number prefix', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
- req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]},
- res: {status: 'Invalid body format', details: '"number_prefix" is required'}
+ req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]},
+ res: {status: 'Invalid body format', details: '"number_prefix" is not allowed'}
});
});
it('rejects missing parameters', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
- req: {name: 'heat aging', number_prefix: 'C'},
+ req: {name: 'heat aging'},
res: {status: 'Invalid body format', details: '"parameters" is required'}
});
});
it('rejects a missing parameter name', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
- req: {name: 'heat aging', number_prefix: 'C', parameters: [{range: {min: 1}}]},
+ req: {name: 'heat aging', parameters: [{range: {min: 1}}]},
res: {status: 'Invalid body format', details: '"parameters[0].name" is required'}
});
});
- it('rejects a number prefix containing numbers', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/template/treatment/new',
- auth: {basic: 'admin'},
- httpStatus: 400,
- req: {name: 'heat aging', number_prefix: 'AB5', parameters: [{name: 'time', range: {min: 1}}]},
- res: {status: 'Invalid body format', details: '"number_prefix" with value "AB5" fails to match the required pattern: /^[a-zA-Z]+$/'}
- });
- });
it('rejects a missing parameter range', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
- req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time'}]},
+ req: {name: 'heat aging', parameters: [{name: 'time'}]},
res: {status: 'Invalid body format', details: '"parameters[0].range" is required'}
});
});
it('rejects an invalid parameter range property', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
- req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {xx: 1}}]},
+ req: {name: 'heat aging', parameters: [{name: 'time', range: {xx: 1}}]},
res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'}
});
});
it('rejects wrong properties', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
- req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {}}], xx: 33},
+ req: {name: 'heat aging', parameters: [{name: 'time', range: {}}], xx: 33},
res: {status: 'Invalid body format', details: '"xx" is not allowed'}
});
});
- it('rejects already existing number prefixes', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/template/treatment/new',
- auth: {basic: 'admin'},
- httpStatus: 400,
- req: {name: 'heat aging', number_prefix: 'B', parameters: [{name: 'time', range: {min: 1}}]},
- res: {status: 'Number prefix already taken'}
- });
- });
it('rejects an API key', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {key: 'admin'},
httpStatus: 401,
- req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}
+ req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
});
});
it('rejects requests from a write user', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 403,
- req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}
+ req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
});
});
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'post',
- url: '/template/treatment/new',
+ url: '/template/condition/new',
httpStatus: 401,
- req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}
+ req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
});
});
});
diff --git a/src/routes/template.ts b/src/routes/template.ts
index a8f7413..849cf59 100644
--- a/src/routes/template.ts
+++ b/src/routes/template.ts
@@ -2,8 +2,8 @@ import express from 'express';
import _ from 'lodash';
import TemplateValidate from './validate/template';
-import TemplateTreatmentModel from '../models/treatment_template';
-import TemplateMeasurementModel from '../models/measurement_template';
+import ConditionTemplateModel from '../models/condition_template';
+import MeasurementTemplateModel from '../models/measurement_template';
import res400 from './validate/res400';
import IdValidate from './validate/id';
@@ -11,23 +11,23 @@ import IdValidate from './validate/id';
const router = express.Router();
-router.get('/template/:collection(measurements|treatments)', (req, res, next) => {
+router.get('/template/:collection(measurements|conditions)', (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
req.params.collection = req.params.collection.replace(/s$/g, ''); // remove trailing s
model(req).find({}).lean().exec((err, data) => {
if (err) next (err);
- res.json(_.compact(data.map(e => TemplateValidate.output(e, req.params.collection)))); // validate all and filter null values from validation errors
+ res.json(_.compact(data.map(e => TemplateValidate.output(e)))); // validate all and filter null values from validation errors
});
});
-router.get('/template/:collection(measurement|treatment)/' + IdValidate.parameter(), (req, res, next) => {
+router.get('/template/:collection(measurement|condition)/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
model(req).findById(req.params.id).lean().exec((err, data) => {
if (err) next (err);
if (data) {
- res.json(TemplateValidate.output(data, req.params.collection));
+ res.json(TemplateValidate.output(data));
}
else {
res.status(404).json({status: 'Not found'});
@@ -35,64 +35,46 @@ router.get('/template/:collection(measurement|treatment)/' + IdValidate.paramete
});
});
-router.put('/template/:collection(measurement|treatment)/' + IdValidate.parameter(), async (req, res, next) => {
+router.put('/template/:collection(measurement|condition)/' + IdValidate.parameter(), async (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
- const {error, value: template} = TemplateValidate.input(req.body, 'change', req.params.collection);
+ const {error, value: template} = TemplateValidate.input(req.body, 'change');
if (error) return res400(error, res);
const templateData = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
if (templateData instanceof Error) return;
if (!templateData) {
- res.status(404).json({status: 'Not found'});
- }
-
- if (_.has(template, 'number_prefix') && template.number_prefix !== templateData.number_prefix) { // got new number_prefix
- if (!await numberPrefixCheck(template, req, res, next)) return;
+ return res.status(404).json({status: 'Not found'});
}
if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed
template.version = templateData.version + 1; // increase version
await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => { // save new template, fill with old properties
if (err) next (err);
- res.json(TemplateValidate.output(data.toObject(), req.params.collection));
+ res.json(TemplateValidate.output(data.toObject()));
});
}
else {
- res.json(TemplateValidate.output(templateData, req.params.collection));
+ res.json(TemplateValidate.output(templateData));
}
});
-router.post('/template/:collection(measurement|treatment)/new', async (req, res, next) => {
+router.post('/template/:collection(measurement|condition)/new', async (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
- const {error, value: template} = TemplateValidate.input(req.body, 'new', req.params.collection);
+ const {error, value: template} = TemplateValidate.input(req.body, 'new');
if (error) return res400(error, res);
- if (_.has(template, 'number_prefix')) { // got number_prefix
- if (!await numberPrefixCheck(template, req, res, next)) return;
- }
-
template.version = 1; // set template version
await new (model(req))(template).save((err, data) => {
if (err) next (err);
- res.json(TemplateValidate.output(data.toObject(), req.params.collection));
+ res.json(TemplateValidate.output(data.toObject()));
});
});
module.exports = router;
-
-async function numberPrefixCheck (template, req, res, next) { // check if number_prefix is available
- const data = await model(req).findOne({number_prefix: template.number_prefix}).lean().exec().catch(err => {next(err); return false;}) as any;
- if (data) {
- res.status(400).json({status: 'Number prefix already taken'});
- return false
- }
- return true;
-}
-
function model (req) { // return right template model
- return req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel;
+ return req.params.collection === 'condition' ? ConditionTemplateModel : MeasurementTemplateModel;
}
\ No newline at end of file
diff --git a/src/routes/user.spec.ts b/src/routes/user.spec.ts
index 6a7d69e..a0d67a5 100644
--- a/src/routes/user.spec.ts
+++ b/src/routes/user.spec.ts
@@ -9,6 +9,7 @@ describe('/user', () => {
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
+ after(done => TestHelper.after(done));
describe('GET /users', () => {
it('returns all users', done => {
@@ -288,7 +289,7 @@ describe('/user', () => {
auth: {basic: 'admin'},
httpStatus: 400,
req: {pass: 'password'},
- res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$/'}
+ res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$)[a-zA-Z0-9!"#%&\'()*+,\\-.\\/:;<=>?@[\\]^_`{|}~]{8,}$/'}
});
});
it('rejects requests from non-admins for another user', done => {
@@ -546,7 +547,7 @@ describe('/user', () => {
auth: {basic: 'admin'},
httpStatus: 400,
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'password', level: 'read', location: 'Rng', device_name: 'Alpha II'},
- res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$/'}
+ res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$)[a-zA-Z0-9!"#%&\'()*+,\\-.\\/:;<=>?@[\\]^_`{|}~]{8,}$/'}
});
});
it('rejects requests from non-admins', done => {
diff --git a/src/routes/user.ts b/src/routes/user.ts
index 4fb2c0f..6ebed4b 100644
--- a/src/routes/user.ts
+++ b/src/routes/user.ts
@@ -40,7 +40,6 @@ router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => {
const username = getUsername(req, res);
if (!username) return;
- console.log(username);
const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
if (error) return res400(error, res);
@@ -154,8 +153,6 @@ function getUsername (req, res) { // returns username or false if action is not
async function usernameCheck (name, res, next) { // check if username is already taken
const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
if (userData instanceof Error) return false;
- console.log(userData);
- console.log(UserValidate.isSpecialName(name));
if (userData || UserValidate.isSpecialName(name)) {
res.status(400).json({status: 'Username already taken'});
return false;
diff --git a/src/routes/validate/condition.ts b/src/routes/validate/condition.ts
deleted file mode 100644
index d752ff3..0000000
--- a/src/routes/validate/condition.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import Joi from '@hapi/joi';
-
-import IdValidate from './id';
-
-export default class ConditionValidate {
- private static condition = {
- number: Joi.string()
- .max(128),
-
- parameters: Joi.object()
- .pattern(/.*/, Joi.alternatives()
- .try(
- Joi.string().max(128),
- Joi.number(),
- Joi.boolean(),
- Joi.array()
- )
- )
- }
-
- static input (data, param) { // validate input, set param to 'new' to make all attributes required
- if (param === 'new') {
- return Joi.object({
- sample_id: IdValidate.get().required(),
- parameters: this.condition.parameters.required(),
- treatment_template: IdValidate.get().required()
- }).validate(data);
- }
- else if (param === 'change') {
- return Joi.object({
- parameters: this.condition.parameters
- }).validate(data);
- }
- else {
- return{error: 'No parameter specified!', value: {}};
- }
- }
-
- static output (data) { // validate output and strip unwanted properties, returns null if not valid
- data = IdValidate.stringify(data);
- const {value, error} = Joi.object({
- _id: IdValidate.get(),
- sample_id: IdValidate.get(),
- number: this.condition.number,
- parameters: this.condition.parameters,
- treatment_template: IdValidate.get()
- }).validate(data, {stripUnknown: true});
- return error !== undefined? null : value;
- }
-}
\ No newline at end of file
diff --git a/src/routes/validate/material.ts b/src/routes/validate/material.ts
index c92f440..805ccd2 100644
--- a/src/routes/validate/material.ts
+++ b/src/routes/validate/material.ts
@@ -1,39 +1,39 @@
-import joi from '@hapi/joi';
+import Joi from '@hapi/joi';
import IdValidate from './id';
export default class MaterialValidate { // validate input for material
private static material = {
- name: joi.string()
+ name: Joi.string()
.max(128),
- supplier: joi.string()
+ supplier: Joi.string()
.max(128),
- group: joi.string()
+ group: Joi.string()
.max(128),
- mineral: joi.number()
+ mineral: Joi.number()
.integer()
.min(0)
.max(100),
- glass_fiber: joi.number()
+ glass_fiber: Joi.number()
.integer()
.min(0)
.max(100),
- carbon_fiber: joi.number()
+ carbon_fiber: Joi.number()
.integer()
.min(0)
.max(100),
- numbers: joi.array()
- .items(joi.object({
- color: joi.string()
+ numbers: Joi.array()
+ .items(Joi.object({
+ color: Joi.string()
.max(128)
.required(),
- number: joi.string()
+ number: Joi.string()
.max(128)
.allow('')
.required()
@@ -42,7 +42,7 @@ export default class MaterialValidate { // validate input for material
static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') {
- return joi.object({
+ return Joi.object({
name: this.material.name.required(),
supplier: this.material.supplier.required(),
group: this.material.group.required(),
@@ -53,7 +53,7 @@ export default class MaterialValidate { // validate input for material
}).validate(data);
}
else if (param === 'change') {
- return joi.object({
+ return Joi.object({
name: this.material.name,
supplier: this.material.supplier,
group: this.material.group,
@@ -70,7 +70,7 @@ export default class MaterialValidate { // validate input for material
static output (data) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data);
- const {value, error} = joi.object({
+ const {value, error} = Joi.object({
_id: IdValidate.get(),
name: this.material.name,
supplier: this.material.supplier,
@@ -82,4 +82,17 @@ export default class MaterialValidate { // validate input for material
}).validate(data, {stripUnknown: true});
return error !== undefined? null : value;
}
+
+ static outputV() { // return output validator
+ return Joi.object({
+ _id: IdValidate.get(),
+ name: this.material.name,
+ supplier: this.material.supplier,
+ group: this.material.group,
+ mineral: this.material.mineral,
+ glass_fiber: this.material.glass_fiber,
+ carbon_fiber: this.material.carbon_fiber,
+ numbers: this.material.numbers
+ });
+ }
}
\ No newline at end of file
diff --git a/src/routes/validate/measurement.ts b/src/routes/validate/measurement.ts
index 21b38a2..74c2409 100644
--- a/src/routes/validate/measurement.ts
+++ b/src/routes/validate/measurement.ts
@@ -12,13 +12,14 @@ export default class MeasurementValidate {
Joi.boolean(),
Joi.array()
)
+ .allow(null)
)
};
static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') {
return Joi.object({
- condition_id: IdValidate.get().required(),
+ sample_id: IdValidate.get().required(),
values: this.measurement.values.required(),
measurement_template: IdValidate.get().required()
}).validate(data);
@@ -37,7 +38,7 @@ export default class MeasurementValidate {
data = IdValidate.stringify(data);
const {value, error} = Joi.object({
_id: IdValidate.get(),
- condition_id: IdValidate.get(),
+ sample_id: IdValidate.get(),
values: this.measurement.values,
measurement_template: IdValidate.get()
}).validate(data, {stripUnknown: true});
diff --git a/src/routes/validate/parameters.ts b/src/routes/validate/parameters.ts
index 79e62ef..e6070b0 100644
--- a/src/routes/validate/parameters.ts
+++ b/src/routes/validate/parameters.ts
@@ -1,7 +1,7 @@
import Joi from '@hapi/joi';
export default class ParametersValidate {
- static input (data, parameters, param) { // data to validate, parameters from template, param: 'new', 'change'
+ static input (data, parameters, param) { // data to validate, parameters from template, param: 'new', 'change', 'null'(null values are allowed)
let joiObject = {};
parameters.forEach(parameter => {
if (parameter.range.hasOwnProperty('values')) { // append right validation method according to parameter
@@ -39,6 +39,9 @@ export default class ParametersValidate {
if (param === 'new') {
joiObject[parameter.name] = joiObject[parameter.name].required()
}
+ else if (param === 'null') {
+ joiObject[parameter.name] = joiObject[parameter.name].allow(null)
+ }
});
return Joi.object(joiObject).validate(data);
}
diff --git a/src/routes/validate/sample.ts b/src/routes/validate/sample.ts
index 1b23cb1..93b86b1 100644
--- a/src/routes/validate/sample.ts
+++ b/src/routes/validate/sample.ts
@@ -1,6 +1,8 @@
import Joi from '@hapi/joi';
import IdValidate from './id';
+import UserValidate from './user';
+import MaterialValidate from './material';
export default class SampleValidate {
private static sample = {
@@ -17,13 +19,16 @@ export default class SampleValidate {
.max(128)
.allow(''),
+ condition: Joi.object(),
+
notes: Joi.object({
comment: Joi.string()
- .max(512),
+ .max(512)
+ .allow(''),
sample_references: Joi.array()
.items(Joi.object({
- id: IdValidate.get(),
+ sample_id: IdValidate.get(),
relation: Joi.string()
.max(128)
@@ -47,6 +52,7 @@ export default class SampleValidate {
color: this.sample.color.required(),
type: this.sample.type.required(),
batch: this.sample.batch.required(),
+ condition: this.sample.condition.required(),
material_id: IdValidate.get().required(),
notes: this.sample.notes.required()
}).validate(data);
@@ -56,6 +62,7 @@ export default class SampleValidate {
color: this.sample.color,
type: this.sample.type,
batch: this.sample.batch,
+ condition: this.sample.condition,
material_id: IdValidate.get(),
notes: this.sample.notes,
}).validate(data);
@@ -65,18 +72,39 @@ export default class SampleValidate {
}
}
- static output (data) { // validate output and strip unwanted properties, returns null if not valid
+ static output (data, param = 'refs') { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data);
- const {value, error} = Joi.object({
- _id: IdValidate.get(),
- number: this.sample.number,
- color: this.sample.color,
- type: this.sample.type,
- batch: this.sample.batch,
- material_id: IdValidate.get(),
- note_id: IdValidate.get().allow(null),
- user_id: IdValidate.get()
- }).validate(data, {stripUnknown: true});
+ let joiObject;
+ if (param === 'refs') {
+ joiObject = {
+ _id: IdValidate.get(),
+ number: this.sample.number,
+ color: this.sample.color,
+ type: this.sample.type,
+ batch: this.sample.batch,
+ condition: this.sample.condition,
+ material_id: IdValidate.get(),
+ note_id: IdValidate.get().allow(null),
+ user_id: IdValidate.get()
+ };
+ }
+ else if(param === 'details') {
+ joiObject = {
+ _id: IdValidate.get(),
+ number: this.sample.number,
+ color: this.sample.color,
+ type: this.sample.type,
+ batch: this.sample.batch,
+ condition: this.sample.condition,
+ material: MaterialValidate.outputV(),
+ notes: this.sample.notes,
+ user: UserValidate.username()
+ }
+ }
+ else {
+ return null;
+ }
+ const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
return error !== undefined? null : value;
}
}
\ No newline at end of file
diff --git a/src/routes/validate/template.ts b/src/routes/validate/template.ts
index 571f48c..111951e 100644
--- a/src/routes/validate/template.ts
+++ b/src/routes/validate/template.ts
@@ -9,13 +9,7 @@ export default class TemplateValidate {
version: Joi.number()
.min(1),
- number_prefix: Joi.string()
- .pattern(/^[a-zA-Z]+$/)
- .min(1)
- .max(16),
-
parameters: Joi.array()
- .min(1)
.items(
Joi.object({
name: Joi.string()
@@ -43,63 +37,32 @@ export default class TemplateValidate {
)
};
- static input (data, param, template) { // validate input, set param to 'new' to make all attributes required
+ static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') {
- if (template === 'treatment') {
- return Joi.object({
- name: this.template.name.required(),
- number_prefix: this.template.number_prefix.required(),
- parameters: this.template.parameters.required()
- }).validate(data);
- }
- else {
- return Joi.object({
- name: this.template.name.required(),
- parameters: this.template.parameters.required()
- }).validate(data);
- }
+ return Joi.object({
+ name: this.template.name.required(),
+ parameters: this.template.parameters.required()
+ }).validate(data);
}
else if (param === 'change') {
- if (template === 'treatment') {
- return Joi.object({
- name: this.template.name,
- number_prefix: this.template.number_prefix,
- parameters: this.template.parameters
- }).validate(data);
- }
- else {
- return Joi.object({
- name: this.template.name,
- parameters: this.template.parameters
- }).validate(data);
- }
+ return Joi.object({
+ name: this.template.name,
+ parameters: this.template.parameters
+ }).validate(data);
}
else {
return{error: 'No parameter specified!', value: {}};
}
}
- static output (data, template) { // validate output and strip unwanted properties, returns null if not valid
+ static output (data) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data);
- let joiObject;
- if (template === 'treatment') { // differentiate between measurement and treatment (has number_prefix) template
- joiObject = {
- _id: IdValidate.get(),
- name: this.template.name,
- version: this.template.version,
- number_prefix: this.template.number_prefix,
- parameters: this.template.parameters
- };
- }
- else {
- joiObject = {
- _id: IdValidate.get(),
- name: this.template.name,
- version: this.template.version,
- parameters: this.template.parameters
- };
- }
- const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
+ const {value, error} = Joi.object({
+ _id: IdValidate.get(),
+ name: this.template.name,
+ version: this.template.version,
+ parameters: this.template.parameters
+ }).validate(data, {stripUnknown: true});
return error !== undefined? null : value;
}
}
\ No newline at end of file
diff --git a/src/routes/validate/user.ts b/src/routes/validate/user.ts
index bd4dfbd..9c0c7d1 100644
--- a/src/routes/validate/user.ts
+++ b/src/routes/validate/user.ts
@@ -84,4 +84,8 @@ export default class UserValidate { // validate input for user
static isSpecialName (name) { // true if name belongs to special names
return this.specialUsernames.indexOf(name) > -1;
}
+
+ static username() {
+ return this.user.name;
+ }
}
diff --git a/src/test/db.json b/src/test/db.json
index b78f8e7..372b09a 100644
--- a/src/test/db.json
+++ b/src/test/db.json
@@ -7,6 +7,11 @@
"type": "granulate",
"color": "black",
"batch": "",
+ "condition": {
+ "material": "copper",
+ "weeks": 3,
+ "condition_template": {"$oid":"200000000000000000000001"}
+ },
"material_id": {"$oid":"100000000000000000000004"},
"note_id": null,
"user_id": {"$oid":"000000000000000000000002"},
@@ -19,6 +24,11 @@
"type": "granulate",
"color": "natural",
"batch": "1560237365",
+ "condition": {
+ "material": "copper",
+ "weeks": 3,
+ "condition_template": {"$oid":"200000000000000000000001"}
+ },
"material_id": {"$oid":"100000000000000000000001"},
"note_id": {"$oid":"500000000000000000000001"},
"user_id": {"$oid":"000000000000000000000002"},
@@ -31,6 +41,11 @@
"type": "part",
"color": "black",
"batch": "1704-005",
+ "condition": {
+ "material": "copper",
+ "weeks": 3,
+ "condition_template": {"$oid":"200000000000000000000001"}
+ },
"material_id": {"$oid":"100000000000000000000005"},
"note_id": {"$oid":"500000000000000000000002"},
"user_id": {"$oid":"000000000000000000000003"},
@@ -43,6 +58,11 @@
"type": "granulate",
"color": "black",
"batch": "1653000308",
+ "condition": {
+ "material": "hot air",
+ "weeks": 5,
+ "condition_template": {"$oid":"200000000000000000000001"}
+ },
"material_id": {"$oid":"100000000000000000000005"},
"note_id": {"$oid":"500000000000000000000003"},
"user_id": {"$oid":"000000000000000000000003"},
@@ -55,11 +75,27 @@
"type": "granulate",
"color": "black",
"batch": "1653000308",
+ "condition": {
+ "condition_template": {"$oid":"200000000000000000000003"}
+ },
"material_id": {"$oid":"100000000000000000000005"},
- "note_id": {"$oid":"500000000000000000000003"},
+ "note_id": null,
"user_id": {"$oid":"000000000000000000000003"},
"status": -1,
"__v": 0
+ },
+ {
+ "_id": {"$oid":"400000000000000000000006"},
+ "number": "Rng36",
+ "type": "granulate",
+ "color": "black",
+ "batch": "",
+ "condition": {},
+ "material_id": {"$oid":"100000000000000000000004"},
+ "note_id": null,
+ "user_id": {"$oid":"000000000000000000000002"},
+ "status": 0,
+ "__v": 0
}
],
"notes": [
@@ -73,7 +109,7 @@
"_id": {"$oid":"500000000000000000000002"},
"comment": "",
"sample_references": [{
- "id": {"$oid":"400000000000000000000004"},
+ "sample_id": {"$oid":"400000000000000000000004"},
"relation": "granulate to sample"
}],
"custom_fields": {
@@ -85,7 +121,7 @@
"_id": {"$oid":"500000000000000000000003"},
"comment": "",
"sample_references": [{
- "id": {"$oid":"400000000000000000000003"},
+ "sample_id": {"$oid":"400000000000000000000003"},
"relation": "part to sample"
}],
"custom_fields": {
@@ -234,60 +270,10 @@
"__v": 0
}
],
- "conditions": [
- {
- "_id": {"$oid":"700000000000000000000001"},
- "sample_id": {"$oid":"400000000000000000000001"},
- "number": "A1",
- "parameters": {
- "material": "copper",
- "weeks": 3
- },
- "treatment_template": {"$oid":"200000000000000000000001"},
- "status": 10,
- "__v": 0
- },
- {
- "_id": {"$oid":"700000000000000000000002"},
- "sample_id": {"$oid":"400000000000000000000002"},
- "number": "A1",
- "parameters": {
- "material": "copper",
- "weeks": 3
- },
- "treatment_template": {"$oid":"200000000000000000000001"},
- "status": 10,
- "__v": 0
- },
- {
- "_id": {"$oid":"700000000000000000000003"},
- "sample_id": {"$oid":"400000000000000000000004"},
- "number": "A1",
- "parameters": {
- "material": "copper",
- "weeks": 3
- },
- "treatment_template": {"$oid":"200000000000000000000001"},
- "status": 10,
- "__v": 0
- },
- {
- "_id": {"$oid":"700000000000000000000004"},
- "sample_id": {"$oid":"400000000000000000000001"},
- "number": "A2",
- "parameters": {
- "material": "hot air",
- "weeks": 5
- },
- "treatment_template": {"$oid":"200000000000000000000001"},
- "status": 10,
- "__v": 0
- }
- ],
"measurements": [
{
"_id": {"$oid":"800000000000000000000001"},
- "condition_id": {"$oid":"700000000000000000000001"},
+ "sample_id": {"$oid":"400000000000000000000001"},
"values": {
"dpt": [
[3997.12558,98.00555],
@@ -301,7 +287,7 @@
},
{
"_id": {"$oid":"800000000000000000000002"},
- "condition_id": {"$oid":"700000000000000000000002"},
+ "sample_id": {"$oid":"400000000000000000000002"},
"values": {
"weight %": 0.5,
"standard deviation": 0.2
@@ -312,21 +298,41 @@
},
{
"_id": {"$oid":"800000000000000000000003"},
- "condition_id": {"$oid":"700000000000000000000003"},
+ "sample_id": {"$oid":"400000000000000000000003"},
"values": {
"val1": 1
},
"status": 0,
"measurement_template": {"$oid":"300000000000000000000003"},
"__v": 0
+ },
+ {
+ "_id": {"$oid":"800000000000000000000004"},
+ "sample_id": {"$oid":"400000000000000000000003"},
+ "values": {
+ "val1": 1
+ },
+ "status": -1,
+ "measurement_template": {"$oid":"300000000000000000000003"},
+ "__v": 0
+ },
+ {
+ "_id": {"$oid":"800000000000000000000005"},
+ "sample_id": {"$oid":"400000000000000000000002"},
+ "values": {
+ "weight %": 0.5,
+ "standard deviation":null
+ },
+ "status": 10,
+ "measurement_template": {"$oid":"300000000000000000000002"},
+ "__v": 0
}
],
- "treatment_templates": [
+ "condition_templates": [
{
"_id": {"$oid":"200000000000000000000001"},
"name": "heat treatment",
"version": 1,
- "number_prefix": "A",
"parameters": [
{
"name": "material",
@@ -351,7 +357,6 @@
"_id": {"$oid":"200000000000000000000002"},
"name": "heat treatment 2",
"version": 2,
- "number_prefix": "B",
"parameters": [
{
"name": "material",
@@ -359,6 +364,14 @@
}
],
"__v": 0
+ },
+ {
+ "_id": {"$oid":"200000000000000000000003"},
+ "name": "raw material",
+ "version": 1,
+ "parameters": [
+ ],
+ "__v": 0
}
],
"measurement_templates": [
diff --git a/src/test/helper.ts b/src/test/helper.ts
index 3983959..fbb45ff 100644
--- a/src/test/helper.ts
+++ b/src/test/helper.ts
@@ -10,6 +10,7 @@ export default class TestHelper {
user: {pass: 'Xyz890*)', key: '000000000000000000001001'},
johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004'}
}
+
public static res = { // default responses
400: {status: 'Bad request'},
401: {status: 'Unauthorized'},
@@ -38,7 +39,11 @@ export default class TestHelper {
server.close(done);
}
- static request (server, done, options) { // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res}
+ static after(done) {
+ db.disconnect(done);
+ }
+
+ static request (server, done, options) { // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res, default (set to false if you want to dismiss default .end handling)}
let st = supertest(server);
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key
options.url += '?key=' + (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
@@ -57,6 +62,9 @@ export default class TestHelper {
st = st.delete(options.url)
break;
}
+ if (options.hasOwnProperty('reqType')) { // request body
+ st = st.type(options.reqType);
+ }
if (options.hasOwnProperty('req')) { // request body
st = st.send(options.req);
}