@opentermsarchive/engine 0.28.0 → 0.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ota-serve.js +9 -0
- package/bin/ota.js +1 -0
- package/config/default.json +4 -0
- package/package.json +8 -2
- package/scripts/declarations/validate/index.mocha.js +1 -1
- package/scripts/rewrite/rewrite-versions.js +4 -4
- package/src/api/logger.js +40 -0
- package/src/api/middlewares/errors.js +6 -0
- package/src/api/middlewares/logger.js +7 -0
- package/src/api/routes/index.js +11 -0
- package/src/api/routes/services.js +142 -0
- package/src/api/routes/services.test.js +151 -0
- package/src/api/routes/specs.js +28 -0
- package/src/api/routes/specs.test.js +59 -0
- package/src/api/server.js +24 -0
- package/src/archivist/index.js +3 -1
- package/src/archivist/index.test.js +8 -8
- package/src/archivist/services/index.test.js +12 -12
- package/src/archivist/services/service.js +16 -11
- package/src/archivist/services/service.test.js +67 -50
- package/src/tracker/index.js +1 -1
package/bin/ota-serve.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
import './env.js';
|
|
3
|
+
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
await import(pathToFileURL(path.resolve(__dirname, '../src/api/server.js'))); // load asynchronously to ensure env.js is loaded before
|
package/bin/ota.js
CHANGED
|
@@ -14,4 +14,5 @@ program
|
|
|
14
14
|
.command('validate', 'Run a series of tests to check the validity of terms declarations')
|
|
15
15
|
.command('lint', 'Check format and stylistic errors in declarations and auto fix them')
|
|
16
16
|
.command('dataset', 'Export the versions dataset into a ZIP file and optionally publish it to GitHub releases')
|
|
17
|
+
.command('serve', 'Start the collection metadata API server')
|
|
17
18
|
.parse(process.argv);
|
package/config/default.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentermsarchive/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"description": "Tracks and makes visible changes to the terms of online services",
|
|
5
5
|
"homepage": "https://github.com/OpenTermsArchive/engine#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"lint": "eslint src test scripts bin",
|
|
40
40
|
"lint:fix": "npm run lint -- --fix",
|
|
41
41
|
"start": "node --max-http-header-size=32768 bin/ota.js track",
|
|
42
|
+
"start:api": "node bin/ota.js serve",
|
|
42
43
|
"start:scheduler": "npm start -- --schedule",
|
|
43
44
|
"test": "cross-env NODE_ENV=test mocha --recursive \"./src/**/*.test.js\" \"./scripts/**/*.test.js\" --exit",
|
|
44
45
|
"posttest": "npm run lint",
|
|
@@ -68,7 +69,9 @@
|
|
|
68
69
|
"eslint-plugin-chai-friendly": "^0.7.2",
|
|
69
70
|
"eslint-plugin-import": "^2.25.3",
|
|
70
71
|
"eslint-plugin-json-format": "^2.0.1",
|
|
72
|
+
"express": "^4.18.2",
|
|
71
73
|
"fs-extra": "^10.0.0",
|
|
74
|
+
"helmet": "^6.0.1",
|
|
72
75
|
"http-proxy-agent": "^5.0.0",
|
|
73
76
|
"https": "^1.0.0",
|
|
74
77
|
"https-proxy-agent": "^5.0.0",
|
|
@@ -80,6 +83,7 @@
|
|
|
80
83
|
"mime": "^2.5.2",
|
|
81
84
|
"mocha": "^9.1.3",
|
|
82
85
|
"mongodb": "^4.9.0",
|
|
86
|
+
"morgan": "^1.10.0",
|
|
83
87
|
"node-fetch": "^3.1.0",
|
|
84
88
|
"octokit": "^1.7.0",
|
|
85
89
|
"pdfjs-dist": "^2.9.359",
|
|
@@ -88,6 +92,7 @@
|
|
|
88
92
|
"puppeteer-extra-plugin-stealth": "^2.9.0",
|
|
89
93
|
"sib-api-v3-sdk": "^8.2.1",
|
|
90
94
|
"simple-git": "^3.8.0",
|
|
95
|
+
"swagger-jsdoc": "^6.2.8",
|
|
91
96
|
"winston": "^3.3.3",
|
|
92
97
|
"winston-mail": "^2.0.0"
|
|
93
98
|
},
|
|
@@ -97,7 +102,8 @@
|
|
|
97
102
|
"node-stream-zip": "^1.15.0",
|
|
98
103
|
"prettier": "^2.2.1",
|
|
99
104
|
"sinon": "^12.0.1",
|
|
100
|
-
"sinon-chai": "^3.7.0"
|
|
105
|
+
"sinon-chai": "^3.7.0",
|
|
106
|
+
"supertest": "^6.3.3"
|
|
101
107
|
},
|
|
102
108
|
"peerDependencies": {
|
|
103
109
|
"@opentermsarchive/terms-types": "~0.1.1"
|
|
@@ -95,10 +95,10 @@ let recorder;
|
|
|
95
95
|
continue;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
const terms = servicesDeclarations[serviceId].getTerms(
|
|
99
|
-
termsType,
|
|
100
|
-
commit.date,
|
|
101
|
-
);
|
|
98
|
+
const terms = servicesDeclarations[serviceId].getTerms({
|
|
99
|
+
type: termsType,
|
|
100
|
+
date: commit.date,
|
|
101
|
+
});
|
|
102
102
|
|
|
103
103
|
if (!terms) {
|
|
104
104
|
console.log(`⌙ Skip unknown terms type "${termsType}" for service "${serviceId}"`);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
|
|
3
|
+
import config from 'config';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
import winston from 'winston';
|
|
6
|
+
import 'winston-mail';
|
|
7
|
+
|
|
8
|
+
dotenv.config();
|
|
9
|
+
|
|
10
|
+
const { combine, timestamp, printf, colorize } = winston.format;
|
|
11
|
+
|
|
12
|
+
const transports = [new winston.transports.Console()];
|
|
13
|
+
|
|
14
|
+
if (config.get('logger.sendMailOnError')) {
|
|
15
|
+
transports.push(new winston.transports.Mail({
|
|
16
|
+
to: config.get('logger.sendMailOnError.to'),
|
|
17
|
+
from: config.get('logger.sendMailOnError.from'),
|
|
18
|
+
host: config.get('logger.smtp.host'),
|
|
19
|
+
username: config.get('logger.smtp.username'),
|
|
20
|
+
password: process.env.SMTP_PASSWORD,
|
|
21
|
+
ssl: true,
|
|
22
|
+
timeout: 30 * 1000,
|
|
23
|
+
formatter: args => args[Object.getOwnPropertySymbols(args)[1]], // Returns the full error message, the same visible in the console. It is referenced in the argument object with a Symbol of which we do not have the reference but we know it is the second one.
|
|
24
|
+
exitOnError: true,
|
|
25
|
+
level: 'error',
|
|
26
|
+
subject: `[OTA API] Error Report — ${os.hostname()}`,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const logger = winston.createLogger({
|
|
31
|
+
format: combine(
|
|
32
|
+
colorize(),
|
|
33
|
+
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
34
|
+
printf(({ level, message, timestamp }) => `${timestamp} ${level.padEnd(15)} ${message}`),
|
|
35
|
+
),
|
|
36
|
+
transports,
|
|
37
|
+
rejectionHandlers: transports,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export default logger;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
|
|
3
|
+
import servicesRouter from './services.js';
|
|
4
|
+
import specsRouter from './specs.js';
|
|
5
|
+
|
|
6
|
+
const apiRouter = express.Router();
|
|
7
|
+
|
|
8
|
+
apiRouter.use('/specs', specsRouter);
|
|
9
|
+
apiRouter.use('/services', servicesRouter);
|
|
10
|
+
|
|
11
|
+
export default apiRouter;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
|
|
3
|
+
import * as Services from '../../archivist/services/index.js';
|
|
4
|
+
|
|
5
|
+
const services = await Services.load();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @swagger
|
|
9
|
+
* tags:
|
|
10
|
+
* name: Services
|
|
11
|
+
* description: Services API
|
|
12
|
+
* components:
|
|
13
|
+
* schemas:
|
|
14
|
+
* Service:
|
|
15
|
+
* type: object
|
|
16
|
+
* description: Definition of a service and the agreements its provider sets forth. While the information is the same, the format differs from the JSON declaration files that are designed for readability by contributors.
|
|
17
|
+
* properties:
|
|
18
|
+
* id:
|
|
19
|
+
* type: string
|
|
20
|
+
* description: The ID of the service.
|
|
21
|
+
* name:
|
|
22
|
+
* type: string
|
|
23
|
+
* description: The name of the service.
|
|
24
|
+
* terms:
|
|
25
|
+
* type: array
|
|
26
|
+
* description: Information that enables tracking the content of agreements defined by the service provider.
|
|
27
|
+
* items:
|
|
28
|
+
* type: object
|
|
29
|
+
* properties:
|
|
30
|
+
* type:
|
|
31
|
+
* type: string
|
|
32
|
+
* description: The type of terms.
|
|
33
|
+
* sourceDocuments:
|
|
34
|
+
* type: array
|
|
35
|
+
* items:
|
|
36
|
+
* type: object
|
|
37
|
+
* properties:
|
|
38
|
+
* location:
|
|
39
|
+
* type: string
|
|
40
|
+
* format: uri
|
|
41
|
+
* description: The URL of the source document.
|
|
42
|
+
* executeClientScripts:
|
|
43
|
+
* type: boolean
|
|
44
|
+
* description: Whether client-side scripts should be executed.
|
|
45
|
+
* contentSelectors:
|
|
46
|
+
* type: string
|
|
47
|
+
* description: The CSS selectors for selecting significant content.
|
|
48
|
+
* insignificantContentSelectors:
|
|
49
|
+
* type: string
|
|
50
|
+
* description: The CSS selectors for selecting insignificant content.
|
|
51
|
+
* filters:
|
|
52
|
+
* type: array
|
|
53
|
+
* description: The names of filters to apply to the content.
|
|
54
|
+
* items:
|
|
55
|
+
* type: string
|
|
56
|
+
*/
|
|
57
|
+
const router = express.Router();
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @swagger
|
|
61
|
+
* /services:
|
|
62
|
+
* get:
|
|
63
|
+
* summary: Enumerate all services.
|
|
64
|
+
* tags: [Services]
|
|
65
|
+
* produces:
|
|
66
|
+
* - application/json
|
|
67
|
+
* responses:
|
|
68
|
+
* 200:
|
|
69
|
+
* description: A JSON array of all services.
|
|
70
|
+
* content:
|
|
71
|
+
* application/json:
|
|
72
|
+
* schema:
|
|
73
|
+
* type: array
|
|
74
|
+
* items:
|
|
75
|
+
* $ref: '#/components/schemas/Service'
|
|
76
|
+
*/
|
|
77
|
+
router.get('/', (req, res) => {
|
|
78
|
+
res.status(200).json(Object.values(services).map(service => ({
|
|
79
|
+
id: service.id,
|
|
80
|
+
name: service.name,
|
|
81
|
+
})));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @swagger
|
|
86
|
+
* /service/{serviceId}:
|
|
87
|
+
* get:
|
|
88
|
+
* summary: Retrieve the declaration of a specific service through its ID.
|
|
89
|
+
* tags: [Services]
|
|
90
|
+
* produces:
|
|
91
|
+
* - application/json
|
|
92
|
+
* parameters:
|
|
93
|
+
* - in: path
|
|
94
|
+
* name: serviceId
|
|
95
|
+
* description: The ID of the service.
|
|
96
|
+
* schema:
|
|
97
|
+
* type: string
|
|
98
|
+
* required: true
|
|
99
|
+
* examples:
|
|
100
|
+
* service-1:
|
|
101
|
+
* value: service-1
|
|
102
|
+
* summary: Simple service ID
|
|
103
|
+
* service-2:
|
|
104
|
+
* value: Service 2!
|
|
105
|
+
* summary: Service ID with spaces and special characters
|
|
106
|
+
* responses:
|
|
107
|
+
* 200:
|
|
108
|
+
* description: The full JSON declaration of the service with the given ID.
|
|
109
|
+
* content:
|
|
110
|
+
* application/json:
|
|
111
|
+
* schema:
|
|
112
|
+
* $ref: '#/components/schemas/Service'
|
|
113
|
+
* 404:
|
|
114
|
+
* description: No service matching the provided ID is found.
|
|
115
|
+
*/
|
|
116
|
+
router.get('/:serviceId', (req, res) => {
|
|
117
|
+
const matchedServiceID = Object.keys(services).find(key => key.toLowerCase() === req.params.serviceId?.toLowerCase());
|
|
118
|
+
const service = services[matchedServiceID];
|
|
119
|
+
|
|
120
|
+
if (!service) {
|
|
121
|
+
res.status(404).send('Service not found');
|
|
122
|
+
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
res.status(200).json({
|
|
127
|
+
id: service.id,
|
|
128
|
+
name: service.name,
|
|
129
|
+
terms: service.getTerms().map(terms => ({
|
|
130
|
+
type: terms.type,
|
|
131
|
+
sourceDocuments: terms.sourceDocuments.map(({ location, contentSelectors, insignificantContentSelectors, filters, executeClientScripts }) => ({
|
|
132
|
+
location,
|
|
133
|
+
contentSelectors,
|
|
134
|
+
insignificantContentSelectors,
|
|
135
|
+
executeClientScripts,
|
|
136
|
+
filters: filters?.map(filter => filter.name),
|
|
137
|
+
})),
|
|
138
|
+
})),
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export default router;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import config from 'config';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
|
|
5
|
+
import app from '../server.js';
|
|
6
|
+
|
|
7
|
+
const basePath = config.get('api.basePath');
|
|
8
|
+
|
|
9
|
+
describe('Services API', () => {
|
|
10
|
+
describe('GET /services', () => {
|
|
11
|
+
let response;
|
|
12
|
+
|
|
13
|
+
before(async () => {
|
|
14
|
+
response = await request(app).get(`${basePath}/v1/services`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('responds with 200 status code', () => {
|
|
18
|
+
expect(response.status).to.equal(200);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('responds with Content-Type application/json', () => {
|
|
22
|
+
expect(response.type).to.equal('application/json');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns an array of services', () => {
|
|
26
|
+
expect(response.body).to.be.an('array');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('each service should have an id', () => {
|
|
30
|
+
response.body.forEach(service => {
|
|
31
|
+
expect(service).to.have.property('id');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('each service should have a name', () => {
|
|
36
|
+
response.body.forEach(service => {
|
|
37
|
+
expect(service).to.have.property('name');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('GET /services/:serviceId', () => {
|
|
43
|
+
let response;
|
|
44
|
+
const SERVICE_ID = 'Service B!';
|
|
45
|
+
const CASE_INSENSITIVE_SERVICE_ID = 'service b!';
|
|
46
|
+
|
|
47
|
+
before(async () => {
|
|
48
|
+
response = await request(app).get(`${basePath}/v1/services/${encodeURI(SERVICE_ID)}`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('responds with 200 status code', () => {
|
|
52
|
+
expect(response.status).to.equal(200);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('responds with Content-Type application/json', () => {
|
|
56
|
+
expect(response.type).to.equal('application/json');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns a service object with id', () => {
|
|
60
|
+
expect(response.body).to.have.property('id');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns the proper service object', () => {
|
|
64
|
+
expect(response.body.id).to.equal(SERVICE_ID);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('returns a service object with name', () => {
|
|
68
|
+
expect(response.body).to.have.property('name');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('returns a service object with an array of terms', () => {
|
|
72
|
+
expect(response.body).to.have.property('terms').that.is.an('array');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('each terms should have a type property', () => {
|
|
76
|
+
response.body.terms.forEach(terms => {
|
|
77
|
+
expect(terms).to.have.property('type');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('each terms should have an array of source documents', () => {
|
|
82
|
+
response.body.terms.forEach(terms => {
|
|
83
|
+
expect(terms).to.have.property('sourceDocuments').that.is.an('array');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('each source document should have a location', () => {
|
|
88
|
+
response.body.terms.forEach(terms => {
|
|
89
|
+
terms.sourceDocuments.forEach(sourceDocument => {
|
|
90
|
+
expect(sourceDocument).to.have.property('location');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
context('With a case-insensitive service ID parameter', () => {
|
|
96
|
+
before(async () => {
|
|
97
|
+
response = await request(app).get(`${basePath}/v1/services/${encodeURI(CASE_INSENSITIVE_SERVICE_ID)}`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('responds with 200 status code', () => {
|
|
101
|
+
expect(response.status).to.equal(200);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns a service object with id', () => {
|
|
105
|
+
expect(response.body).to.have.property('id');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('returns the proper service object', () => {
|
|
109
|
+
expect(response.body.id).to.equal(SERVICE_ID);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('returns a service object with name', () => {
|
|
113
|
+
expect(response.body).to.have.property('name');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('returns a service object with an array of terms', () => {
|
|
117
|
+
expect(response.body).to.have.property('terms').that.is.an('array');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('each terms should have a type property', () => {
|
|
121
|
+
response.body.terms.forEach(terms => {
|
|
122
|
+
expect(terms).to.have.property('type');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('each terms should have an array of source documents', () => {
|
|
127
|
+
response.body.terms.forEach(terms => {
|
|
128
|
+
expect(terms).to.have.property('sourceDocuments').that.is.an('array');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('each source document should have a location', () => {
|
|
133
|
+
response.body.terms.forEach(terms => {
|
|
134
|
+
terms.sourceDocuments.forEach(sourceDocument => {
|
|
135
|
+
expect(sourceDocument).to.have.property('location');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
context('When no matching service is found', () => {
|
|
142
|
+
before(async () => {
|
|
143
|
+
response = await request(app).get(`${basePath}/v1/nonExistentService`);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('responds with 404 status code', () => {
|
|
147
|
+
expect(response.status).to.equal(404);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import swaggerJsdoc from 'swagger-jsdoc';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
router.get('/', (req, res) => {
|
|
11
|
+
res.json(swaggerJsdoc({
|
|
12
|
+
definition: {
|
|
13
|
+
swagger: '2.0',
|
|
14
|
+
openapi: '3.1.0',
|
|
15
|
+
info: {
|
|
16
|
+
title: 'Open Terms Archive API',
|
|
17
|
+
version: '1.0.0',
|
|
18
|
+
license: {
|
|
19
|
+
name: 'EUPL-1.2',
|
|
20
|
+
url: 'https://eupl.eu/1.2/',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
apis: [`${__dirname}/*.js`],
|
|
25
|
+
}));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default router;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import config from 'config';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
|
|
5
|
+
import app from '../server.js';
|
|
6
|
+
|
|
7
|
+
const basePath = config.get('api.basePath');
|
|
8
|
+
|
|
9
|
+
describe('Specs API', () => {
|
|
10
|
+
describe('GET /specs', () => {
|
|
11
|
+
let response;
|
|
12
|
+
|
|
13
|
+
before(async () => {
|
|
14
|
+
response = await request(app).get(`${basePath}/v1/specs`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('responds with 200 status code', () => {
|
|
18
|
+
expect(response.status).to.equal(200);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('responds with Content-Type application/json', () => {
|
|
22
|
+
expect(response.type).to.equal('application/json');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('body response defines', () => {
|
|
26
|
+
let subject;
|
|
27
|
+
|
|
28
|
+
before(async () => {
|
|
29
|
+
subject = response.body;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('openapi version', () => {
|
|
33
|
+
expect(subject).to.have.property('openapi');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('swagger version', () => {
|
|
37
|
+
expect(subject).to.have.property('swagger');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('paths', () => {
|
|
41
|
+
expect(subject).to.have.property('paths');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('with endpoints', () => {
|
|
45
|
+
before(async () => {
|
|
46
|
+
subject = response.body.paths;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('/services', () => {
|
|
50
|
+
expect(subject).to.have.property('/services');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('/service/{serviceId}', () => {
|
|
54
|
+
expect(subject).to.have.property('/service/{serviceId}');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import config from 'config';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import helmet from 'helmet';
|
|
4
|
+
|
|
5
|
+
import logger from './logger.js';
|
|
6
|
+
import errorsMiddleware from './middlewares/errors.js';
|
|
7
|
+
import loggerMiddleware from './middlewares/logger.js';
|
|
8
|
+
import apiRouter from './routes/index.js';
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
|
|
12
|
+
app.use(helmet());
|
|
13
|
+
|
|
14
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
15
|
+
app.use(loggerMiddleware);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
app.use(`${config.get('api.basePath')}/v1`, apiRouter);
|
|
19
|
+
app.use(errorsMiddleware);
|
|
20
|
+
|
|
21
|
+
app.listen(config.get('api.port'));
|
|
22
|
+
logger.info('Start Open Terms Archive API\n');
|
|
23
|
+
|
|
24
|
+
export default app;
|
package/src/archivist/index.js
CHANGED
|
@@ -65,6 +65,8 @@ export default class Archivist extends events.EventEmitter {
|
|
|
65
65
|
await this.recorder.finalize().then(() => console.log('Recorder finalized'));
|
|
66
66
|
process.exit(1);
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
return this;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
initQueue() {
|
|
@@ -106,7 +108,7 @@ export default class Archivist extends events.EventEmitter {
|
|
|
106
108
|
return;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
|
-
this.trackingQueue.push({ terms: this.services[serviceId].getTerms(termsType), extractOnly });
|
|
111
|
+
this.trackingQueue.push({ terms: this.services[serviceId].getTerms({ type: termsType }), extractOnly });
|
|
110
112
|
});
|
|
111
113
|
});
|
|
112
114
|
|
|
@@ -42,14 +42,14 @@ describe('Archivist', function () {
|
|
|
42
42
|
let serviceASnapshotExpectedContent;
|
|
43
43
|
let serviceAVersionExpectedContent;
|
|
44
44
|
|
|
45
|
-
const SERVICE_B_ID = '
|
|
45
|
+
const SERVICE_B_ID = 'Service B!';
|
|
46
46
|
const SERVICE_B_TYPE = 'Privacy Policy';
|
|
47
47
|
const SERVICE_B_EXPECTED_SNAPSHOT_FILE_PATH = `${SNAPSHOTS_PATH}/${SERVICE_B_ID}/${SERVICE_B_TYPE}.pdf`;
|
|
48
48
|
const SERVICE_B_EXPECTED_VERSION_FILE_PATH = `${VERSIONS_PATH}/${SERVICE_B_ID}/${SERVICE_B_TYPE}.md`;
|
|
49
49
|
let serviceBSnapshotExpectedContent;
|
|
50
50
|
let serviceBVersionExpectedContent;
|
|
51
51
|
|
|
52
|
-
const services = [ 'service_A', '
|
|
52
|
+
const services = [ 'service_A', 'Service B!' ];
|
|
53
53
|
|
|
54
54
|
before(async () => {
|
|
55
55
|
gitVersion = new Git({
|
|
@@ -164,9 +164,9 @@ describe('Archivist', function () {
|
|
|
164
164
|
|
|
165
165
|
serviceBCommits = await gitVersion.log({ file: SERVICE_B_EXPECTED_VERSION_FILE_PATH });
|
|
166
166
|
|
|
167
|
-
app.services[SERVICE_A_ID].getTerms(SERVICE_A_TYPE).sourceDocuments[0].contentSelectors = 'h1';
|
|
167
|
+
app.services[SERVICE_A_ID].getTerms({ type: SERVICE_A_TYPE }).sourceDocuments[0].contentSelectors = 'h1';
|
|
168
168
|
|
|
169
|
-
await app.track({ services: [ 'service_A', '
|
|
169
|
+
await app.track({ services: [ 'service_A', 'Service B!' ], extractOnly: true });
|
|
170
170
|
|
|
171
171
|
const [reExtractedVersionCommit] = await gitVersion.log({ file: SERVICE_A_EXPECTED_VERSION_FILE_PATH });
|
|
172
172
|
|
|
@@ -218,12 +218,12 @@ describe('Archivist', function () {
|
|
|
218
218
|
|
|
219
219
|
await app.initialize();
|
|
220
220
|
await app.track({ services });
|
|
221
|
-
app.services[SERVICE_A_ID].getTerms(SERVICE_A_TYPE).sourceDocuments[0].contentSelectors = 'inexistant-selector';
|
|
221
|
+
app.services[SERVICE_A_ID].getTerms({ type: SERVICE_A_TYPE }).sourceDocuments[0].contentSelectors = 'inexistant-selector';
|
|
222
222
|
inaccessibleContentSpy = sinon.spy();
|
|
223
223
|
versionNotChangedSpy = sinon.spy();
|
|
224
224
|
app.on('inaccessibleContent', inaccessibleContentSpy);
|
|
225
225
|
app.on('versionNotChanged', record => {
|
|
226
|
-
if (record.serviceId == '
|
|
226
|
+
if (record.serviceId == 'Service B!') {
|
|
227
227
|
versionB = record;
|
|
228
228
|
}
|
|
229
229
|
versionNotChangedSpy(record);
|
|
@@ -282,7 +282,7 @@ describe('Archivist', function () {
|
|
|
282
282
|
let snapshot;
|
|
283
283
|
|
|
284
284
|
before(async () => {
|
|
285
|
-
terms = app.services.service_A.getTerms(SERVICE_A_TYPE);
|
|
285
|
+
terms = app.services.service_A.getTerms({ type: SERVICE_A_TYPE });
|
|
286
286
|
terms.fetchDate = FETCH_DATE;
|
|
287
287
|
terms.sourceDocuments.forEach(async sourceDocument => {
|
|
288
288
|
sourceDocument.content = serviceASnapshotExpectedContent;
|
|
@@ -364,7 +364,7 @@ describe('Archivist', function () {
|
|
|
364
364
|
let version;
|
|
365
365
|
|
|
366
366
|
before(async () => {
|
|
367
|
-
terms = app.services.service_A.getTerms(SERVICE_A_TYPE);
|
|
367
|
+
terms = app.services.service_A.getTerms({ type: SERVICE_A_TYPE });
|
|
368
368
|
terms.fetchDate = FETCH_DATE;
|
|
369
369
|
terms.sourceDocuments.forEach(async sourceDocument => {
|
|
370
370
|
sourceDocument.content = serviceASnapshotExpectedContent;
|
|
@@ -22,7 +22,7 @@ describe('Services', () => {
|
|
|
22
22
|
let actualInsignificantContentSelectors;
|
|
23
23
|
let actualExecuteClientScripts;
|
|
24
24
|
|
|
25
|
-
const expectedTerms = expected.getTerms(termsType);
|
|
25
|
+
const expectedTerms = expected.getTerms({ type: termsType });
|
|
26
26
|
|
|
27
27
|
const { sourceDocuments } = expectedTerms;
|
|
28
28
|
|
|
@@ -36,7 +36,7 @@ describe('Services', () => {
|
|
|
36
36
|
|
|
37
37
|
context(`source document: ${sourceDocument.id}`, () => {
|
|
38
38
|
before(() => {
|
|
39
|
-
actualTerms = result[serviceId].getTerms(termsType);
|
|
39
|
+
actualTerms = result[serviceId].getTerms({ type: termsType });
|
|
40
40
|
const { sourceDocuments: actualDocuments } = actualTerms;
|
|
41
41
|
|
|
42
42
|
({
|
|
@@ -102,7 +102,7 @@ describe('Services', () => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
describe('Service B', async () => {
|
|
105
|
-
await validateServiceWithoutHistory('
|
|
105
|
+
await validateServiceWithoutHistory('Service B!', expectedServices['Service B!']);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
describe('Service without history', async () => {
|
|
@@ -127,11 +127,11 @@ describe('Services', () => {
|
|
|
127
127
|
|
|
128
128
|
context('when specifying services to load', async () => {
|
|
129
129
|
before(async () => {
|
|
130
|
-
result = await services.load([ 'service_A', '
|
|
130
|
+
result = await services.load([ 'service_A', 'Service B!' ]);
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
it('loads only the given services', async () => {
|
|
134
|
-
expect(result).to.have.all.keys('service_A', '
|
|
134
|
+
expect(result).to.have.all.keys('service_A', 'Service B!');
|
|
135
135
|
});
|
|
136
136
|
});
|
|
137
137
|
});
|
|
@@ -148,12 +148,12 @@ describe('Services', () => {
|
|
|
148
148
|
|
|
149
149
|
let actualTerms;
|
|
150
150
|
let actualFilters;
|
|
151
|
-
const expectedTerms = expected.getTerms(termsType);
|
|
151
|
+
const expectedTerms = expected.getTerms({ type: termsType });
|
|
152
152
|
|
|
153
153
|
const { sourceDocuments } = expectedTerms;
|
|
154
154
|
|
|
155
155
|
before(() => {
|
|
156
|
-
actualTerms = result[serviceId].getTerms(termsType);
|
|
156
|
+
actualTerms = result[serviceId].getTerms({ type: termsType });
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
it('has the proper service name', () => {
|
|
@@ -182,7 +182,7 @@ describe('Services', () => {
|
|
|
182
182
|
let insignificantContentSelectorsForThisDate;
|
|
183
183
|
let actualExecuteClientScriptsForThisDate;
|
|
184
184
|
|
|
185
|
-
const { sourceDocuments: documentsForThisDate } = expected.getTerms(termsType, date);
|
|
185
|
+
const { sourceDocuments: documentsForThisDate } = expected.getTerms({ type: termsType, date });
|
|
186
186
|
const {
|
|
187
187
|
filters: expectedFiltersForThisDate,
|
|
188
188
|
contentSelectors: expectedContentSelectors,
|
|
@@ -191,7 +191,7 @@ describe('Services', () => {
|
|
|
191
191
|
} = documentsForThisDate[index];
|
|
192
192
|
|
|
193
193
|
before(() => {
|
|
194
|
-
const { sourceDocuments: actualDocumentsForThisDate } = result[serviceId].getTerms(termsType, date);
|
|
194
|
+
const { sourceDocuments: actualDocumentsForThisDate } = result[serviceId].getTerms({ type: termsType, date });
|
|
195
195
|
|
|
196
196
|
({
|
|
197
197
|
filters: actualFiltersForThisDate,
|
|
@@ -271,7 +271,7 @@ describe('Services', () => {
|
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
describe('Service B', async () => {
|
|
274
|
-
await validateServiceWithHistory('
|
|
274
|
+
await validateServiceWithHistory('Service B!', expectedServices['Service B!']);
|
|
275
275
|
});
|
|
276
276
|
|
|
277
277
|
describe('Service without history', async () => {
|
|
@@ -296,11 +296,11 @@ describe('Services', () => {
|
|
|
296
296
|
|
|
297
297
|
context('when specifying services to load', async () => {
|
|
298
298
|
before(async () => {
|
|
299
|
-
result = await services.loadWithHistory([ 'service_A', '
|
|
299
|
+
result = await services.loadWithHistory([ 'service_A', 'Service B!' ]);
|
|
300
300
|
});
|
|
301
301
|
|
|
302
302
|
it('loads only the given services', async () => {
|
|
303
|
-
expect(result).to.have.all.keys('service_A', '
|
|
303
|
+
expect(result).to.have.all.keys('service_A', 'Service B!');
|
|
304
304
|
});
|
|
305
305
|
});
|
|
306
306
|
});
|
|
@@ -6,21 +6,26 @@ export default class Service {
|
|
|
6
6
|
this.name = name;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
getTerms(
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
getTerms({ type, date } = {}) {
|
|
10
|
+
if (type) {
|
|
11
|
+
if (date) {
|
|
12
|
+
return this.#getTermsAtDate(type, date);
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
return this.#getTermsAtDate(type, new Date());
|
|
16
|
+
}
|
|
15
17
|
|
|
16
|
-
if (
|
|
17
|
-
return
|
|
18
|
+
if (date) {
|
|
19
|
+
return this.getTermsTypes().map(termsType => this.#getTermsAtDate(termsType, date));
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
return (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
return this.getTermsTypes().map(termsType => this.#getTermsAtDate(termsType, new Date()));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#getTermsAtDate(termsType, date) {
|
|
26
|
+
const { latest: currentlyValidTerms, history } = this.terms[termsType];
|
|
27
|
+
|
|
28
|
+
return history?.find(entry => new Date(date) <= new Date(entry.validUntil)) || currentlyValidTerms;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
getTermsTypes() {
|
|
@@ -19,18 +19,18 @@ describe('Service', () => {
|
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
context('when terms
|
|
22
|
+
context('when terms have no validity date', () => {
|
|
23
23
|
before(async () => {
|
|
24
24
|
subject = new Service({ id: 'serviceID', name: 'serviceName' });
|
|
25
25
|
subject.addTerms(terms);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
it('adds the terms as the last valid terms
|
|
29
|
-
expect(subject.getTerms(TERMS_TYPE)).to.deep.eql(terms);
|
|
28
|
+
it('adds the terms as the last valid terms', async () => {
|
|
29
|
+
expect(subject.getTerms({ type: TERMS_TYPE })).to.deep.eql(terms);
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
context('when terms
|
|
33
|
+
context('when terms have a validity date', () => {
|
|
34
34
|
let expiredTerms;
|
|
35
35
|
const VALIDITY_DATE = new Date('2020-07-22T11:30:21.000Z');
|
|
36
36
|
|
|
@@ -46,7 +46,7 @@ describe('Service', () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('adds the terms with the proper validity date', async () => {
|
|
49
|
-
expect(subject.getTerms(TERMS_TYPE, VALIDITY_DATE)).to.deep.eql(expiredTerms);
|
|
49
|
+
expect(subject.getTerms({ type: TERMS_TYPE, date: VALIDITY_DATE })).to.deep.eql(expiredTerms);
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
});
|
|
@@ -54,59 +54,76 @@ describe('Service', () => {
|
|
|
54
54
|
describe('#getTerms', () => {
|
|
55
55
|
let subject;
|
|
56
56
|
|
|
57
|
-
const
|
|
57
|
+
const EARLIEST_DATE = '2020-06-21T11:30:21.000Z';
|
|
58
|
+
const DATE = '2020-07-22T11:30:21.000Z';
|
|
59
|
+
const LATEST_DATE = '2020-08-21T11:30:21.000Z';
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
subject = new Service({ id: 'serviceID', name: 'serviceName' });
|
|
62
|
-
subject.addTerms(lastDeclaration);
|
|
63
|
-
});
|
|
61
|
+
const firstTermsOfService = new Terms({ type: TERMS_TYPE, validUntil: DATE });
|
|
62
|
+
const firstPrivacyPolicy = new Terms({ type: 'Privacy Policy', validUntil: DATE });
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
expect(subject.getTerms(TERMS_TYPE)).to.eql(lastDeclaration);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
64
|
+
const latestTermsOfService = new Terms({ type: TERMS_TYPE });
|
|
65
|
+
const latestPrivacyPolicy = new Terms({ type: 'Privacy Policy' });
|
|
70
66
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
const latestDeveloperTerms = new Terms({ type: 'Developer Terms' });
|
|
68
|
+
|
|
69
|
+
before(async () => {
|
|
70
|
+
subject = new Service({ id: 'serviceID', name: 'serviceName' });
|
|
71
|
+
subject.addTerms(firstTermsOfService);
|
|
72
|
+
subject.addTerms(firstPrivacyPolicy);
|
|
73
|
+
subject.addTerms(latestTermsOfService);
|
|
74
|
+
subject.addTerms(latestPrivacyPolicy);
|
|
75
|
+
subject.addTerms(latestDeveloperTerms);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
context('when
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
validUntil: '2020-07-22T11:30:21.000Z',
|
|
78
|
+
context('when no params are given', () => {
|
|
79
|
+
it('returns all latest terms', async () => {
|
|
80
|
+
expect(subject.getTerms()).to.deep.eql([ latestTermsOfService, latestPrivacyPolicy, latestDeveloperTerms ]);
|
|
82
81
|
});
|
|
82
|
+
});
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
context('when a terms type is given', () => {
|
|
85
|
+
context('when a date is given', () => {
|
|
86
|
+
context('when the terms has no history', () => {
|
|
87
|
+
it('returns the latest terms according to the given type', async () => {
|
|
88
|
+
expect(subject.getTerms({ type: 'Developer Terms', date: LATEST_DATE })).to.eql(latestDeveloperTerms);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
context('when the terms have a history', () => {
|
|
93
|
+
it('returns the terms according to the given type and date', async () => {
|
|
94
|
+
expect(subject.getTerms({ type: TERMS_TYPE, date: EARLIEST_DATE })).to.eql(firstTermsOfService);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
context('when the given date is strictly equal to a terms validity date', () => {
|
|
98
|
+
it('returns the terms according to the given type with the validity date equal to the given date', async () => {
|
|
99
|
+
expect(subject.getTerms({ type: TERMS_TYPE, date: DATE })).to.eql(firstTermsOfService);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
87
103
|
});
|
|
88
104
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
subject.addTerms(secondDeclaration);
|
|
105
|
+
context('without a given date', () => {
|
|
106
|
+
it('returns the latest terms of given type', async () => {
|
|
107
|
+
expect(subject.getTerms({ type: TERMS_TYPE })).to.eql(latestTermsOfService);
|
|
108
|
+
});
|
|
94
109
|
});
|
|
110
|
+
});
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
context('when only a date is given', () => {
|
|
113
|
+
context('when there is no history', () => {
|
|
114
|
+
it('returns all latest terms', async () => {
|
|
115
|
+
expect(subject.getTerms({ date: LATEST_DATE })).to.deep.eql([ latestTermsOfService, latestPrivacyPolicy, latestDeveloperTerms ]);
|
|
99
116
|
});
|
|
100
117
|
});
|
|
101
118
|
|
|
102
|
-
context('
|
|
103
|
-
it('returns the terms
|
|
104
|
-
expect(subject.getTerms(
|
|
119
|
+
context('when the terms have a history', () => {
|
|
120
|
+
it('returns all the terms according to the given date', async () => {
|
|
121
|
+
expect(subject.getTerms({ date: EARLIEST_DATE })).to.deep.eql([ firstTermsOfService, firstPrivacyPolicy, latestDeveloperTerms ]);
|
|
105
122
|
});
|
|
106
123
|
|
|
107
|
-
context('strictly equal to a terms
|
|
108
|
-
it('returns the terms
|
|
109
|
-
expect(subject.getTerms(
|
|
124
|
+
context('when the given date is strictly equal to a terms validity date', () => {
|
|
125
|
+
it('returns all the terms with the validity date equal to the given date', async () => {
|
|
126
|
+
expect(subject.getTerms({ date: DATE })).to.deep.eql([ firstTermsOfService, firstPrivacyPolicy, latestDeveloperTerms ]);
|
|
110
127
|
});
|
|
111
128
|
});
|
|
112
129
|
});
|
|
@@ -115,27 +132,27 @@ describe('Service', () => {
|
|
|
115
132
|
|
|
116
133
|
describe('#getTermsTypes', () => {
|
|
117
134
|
let subject;
|
|
118
|
-
let
|
|
119
|
-
let
|
|
135
|
+
let termsOfService;
|
|
136
|
+
let privacyPolicy;
|
|
120
137
|
|
|
121
138
|
before(async () => {
|
|
122
139
|
subject = new Service({ id: 'serviceID', name: 'serviceName' });
|
|
123
140
|
|
|
124
|
-
|
|
141
|
+
termsOfService = new Terms({ type: TERMS_TYPE });
|
|
125
142
|
|
|
126
|
-
|
|
143
|
+
privacyPolicy = new Terms({
|
|
127
144
|
type: 'Privacy Policy',
|
|
128
145
|
validUntil: '2020-07-22T11:30:21.000Z',
|
|
129
146
|
});
|
|
130
147
|
|
|
131
|
-
subject.addTerms(
|
|
132
|
-
subject.addTerms(
|
|
148
|
+
subject.addTerms(termsOfService);
|
|
149
|
+
subject.addTerms(privacyPolicy);
|
|
133
150
|
});
|
|
134
151
|
|
|
135
152
|
it('returns the service terms types', async () => {
|
|
136
153
|
expect(subject.getTermsTypes()).to.have.members([
|
|
137
|
-
|
|
138
|
-
|
|
154
|
+
termsOfService.type,
|
|
155
|
+
privacyPolicy.type,
|
|
139
156
|
]);
|
|
140
157
|
});
|
|
141
158
|
});
|
package/src/tracker/index.js
CHANGED
|
@@ -69,7 +69,7 @@ export default class Tracker {
|
|
|
69
69
|
return this.onVersionRecorded(serviceId, type);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
async onInaccessibleContent(error,
|
|
72
|
+
async onInaccessibleContent(error, terms) {
|
|
73
73
|
const { title, body } = Tracker.formatIssueTitleAndBody({ message: error.toString(), repository: this.repository, terms });
|
|
74
74
|
|
|
75
75
|
await this.createIssueIfNotExists({
|