@opentermsarchive/engine 5.6.1 → 6.0.0
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/package.json +1 -1
- package/src/archivist/fetcher/fullDomFetcher.js +5 -0
- package/src/reporter/github/index.js +26 -9
- package/src/reporter/github/index.test.js +50 -19
- package/src/reporter/gitlab/index.js +78 -42
- package/src/reporter/gitlab/index.test.js +171 -54
- package/src/reporter/index.js +21 -20
- package/src/reporter/labels.js +96 -0
- package/src/reporter/labels.test.js +24 -0
- package/src/reporter/github/labels.json +0 -87
- package/src/reporter/github/labels.test.js +0 -30
- package/src/reporter/gitlab/labels.json +0 -77
- package/src/reporter/gitlab/labels.test.js +0 -30
package/package.json
CHANGED
|
@@ -20,6 +20,11 @@ export default async function fetch(url, cssSelectors, config) {
|
|
|
20
20
|
await page.setDefaultNavigationTimeout(config.navigationTimeout);
|
|
21
21
|
await page.setExtraHTTPHeaders({ 'Accept-Language': config.language });
|
|
22
22
|
|
|
23
|
+
await page.setCacheEnabled(false); // Disable cache to ensure fresh content on each fetch and prevent stale data from previous requests
|
|
24
|
+
const client = await page.target().createCDPSession();
|
|
25
|
+
|
|
26
|
+
await client.send('Network.clearBrowserCookies'); // Clear cookies to ensure clean state between fetches and prevent session persistence across different URLs
|
|
27
|
+
|
|
23
28
|
response = await page.goto(url, { waitUntil: 'load' }); // Using `load` instead of `networkidle0` as it's more reliable and faster. The 'load' event fires when the page and all its resources (stylesheets, scripts, images) have finished loading. `networkidle0` can be problematic as it waits for 500ms of network inactivity, which may never occur on dynamic pages and then triggers a navigation timeout.
|
|
24
29
|
|
|
25
30
|
if (!response) {
|
|
@@ -3,15 +3,15 @@ import { createRequire } from 'module';
|
|
|
3
3
|
import { Octokit } from 'octokit';
|
|
4
4
|
|
|
5
5
|
import logger from '../../logger/index.js';
|
|
6
|
+
import { LABELS, MANAGED_BY_OTA_MARKER, DEPRECATED_MANAGED_BY_OTA_MARKER } from '../labels.js';
|
|
6
7
|
|
|
7
8
|
const require = createRequire(import.meta.url);
|
|
8
9
|
|
|
9
|
-
export const MANAGED_BY_OTA_MARKER = '[managed by OTA]';
|
|
10
|
-
|
|
11
10
|
export default class GitHub {
|
|
12
11
|
static ISSUE_STATE_CLOSED = 'closed';
|
|
13
12
|
static ISSUE_STATE_OPEN = 'open';
|
|
14
13
|
static ISSUE_STATE_ALL = 'all';
|
|
14
|
+
static MAX_LABEL_DESCRIPTION_LENGTH = 100;
|
|
15
15
|
|
|
16
16
|
constructor(repository) {
|
|
17
17
|
const { version } = require('../../../package.json');
|
|
@@ -49,9 +49,19 @@ export default class GitHub {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async initialize() {
|
|
52
|
-
this.MANAGED_LABELS =
|
|
52
|
+
this.MANAGED_LABELS = Object.values(LABELS);
|
|
53
53
|
try {
|
|
54
54
|
const existingLabels = await this.getRepositoryLabels();
|
|
55
|
+
const labelsToRemove = existingLabels.filter(label => label.description && label.description.includes(DEPRECATED_MANAGED_BY_OTA_MARKER));
|
|
56
|
+
|
|
57
|
+
if (labelsToRemove.length) {
|
|
58
|
+
logger.info(`Removing labels with deprecated markers: ${labelsToRemove.map(label => `"${label.name}"`).join(', ')}`);
|
|
59
|
+
|
|
60
|
+
for (const label of labelsToRemove) {
|
|
61
|
+
await this.deleteLabel(label.name); /* eslint-disable-line no-await-in-loop */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
const existingLabelsNames = existingLabels.map(label => label.name);
|
|
56
66
|
const missingLabels = this.MANAGED_LABELS.filter(label => !existingLabelsNames.includes(label.name));
|
|
57
67
|
|
|
@@ -116,6 +126,13 @@ export default class GitHub {
|
|
|
116
126
|
});
|
|
117
127
|
}
|
|
118
128
|
|
|
129
|
+
async deleteLabel(name) {
|
|
130
|
+
await this.octokit.request('DELETE /repos/{owner}/{repo}/labels/{name}', {
|
|
131
|
+
...this.commonParams,
|
|
132
|
+
name,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
119
136
|
async getIssue(title) {
|
|
120
137
|
return (await this.issues).get(title);
|
|
121
138
|
}
|
|
@@ -174,25 +191,25 @@ export default class GitHub {
|
|
|
174
191
|
}
|
|
175
192
|
}
|
|
176
193
|
|
|
177
|
-
async createOrUpdateIssue({ title, description,
|
|
194
|
+
async createOrUpdateIssue({ title, description, labels }) {
|
|
178
195
|
try {
|
|
179
196
|
const issue = await this.getIssue(title);
|
|
180
197
|
|
|
181
198
|
if (!issue) {
|
|
182
|
-
const createdIssue = await this.createIssue({ title, description, labels
|
|
199
|
+
const createdIssue = await this.createIssue({ title, description, labels });
|
|
183
200
|
|
|
184
201
|
return logger.info(`Created issue #${createdIssue.number} "${title}": ${createdIssue.html_url}`);
|
|
185
202
|
}
|
|
186
203
|
|
|
187
204
|
const managedLabelsNames = this.MANAGED_LABELS.map(label => label.name);
|
|
188
205
|
const labelsNotManagedToKeep = issue.labels.map(label => label.name).filter(label => !managedLabelsNames.includes(label));
|
|
189
|
-
const
|
|
206
|
+
const managedLabels = issue.labels.filter(label => managedLabelsNames.includes(label.name));
|
|
190
207
|
|
|
191
|
-
if (issue.state !== GitHub.ISSUE_STATE_CLOSED && managedLabel
|
|
192
|
-
return;
|
|
208
|
+
if (issue.state !== GitHub.ISSUE_STATE_CLOSED && labels.every(label => managedLabels.some(managedLabel => managedLabel.name === label))) {
|
|
209
|
+
return; // if all requested labels are already assigned to the issue, the error is redundant with the one already reported and no further action is necessary
|
|
193
210
|
}
|
|
194
211
|
|
|
195
|
-
const updatedIssue = await this.updateIssue(issue, { state: GitHub.ISSUE_STATE_OPEN, labels: [
|
|
212
|
+
const updatedIssue = await this.updateIssue(issue, { state: GitHub.ISSUE_STATE_OPEN, labels: [ ...labels, ...labelsNotManagedToKeep ] });
|
|
196
213
|
|
|
197
214
|
await this.addCommentToIssue({ issue, comment: description });
|
|
198
215
|
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
|
|
3
1
|
import { expect } from 'chai';
|
|
4
2
|
import nock from 'nock';
|
|
5
3
|
|
|
6
|
-
import
|
|
4
|
+
import { LABELS } from '../labels.js';
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
import GitHub from './index.js';
|
|
9
7
|
|
|
10
8
|
describe('GitHub', function () {
|
|
11
9
|
this.timeout(5000);
|
|
12
10
|
|
|
13
11
|
let MANAGED_LABELS;
|
|
14
12
|
let github;
|
|
15
|
-
const EXISTING_OPEN_ISSUE = { number: 1, title: 'Opened issue', description: 'Issue description', state: GitHub.ISSUE_STATE_OPEN, labels: [{ name: '
|
|
16
|
-
const EXISTING_CLOSED_ISSUE = { number: 2, title: 'Closed issue', description: 'Issue description', state: GitHub.ISSUE_STATE_CLOSED, labels: [{ name: '
|
|
13
|
+
const EXISTING_OPEN_ISSUE = { number: 1, title: 'Opened issue', description: 'Issue description', state: GitHub.ISSUE_STATE_OPEN, labels: [{ name: 'page access restriction' }, { name: 'server error' }] };
|
|
14
|
+
const EXISTING_CLOSED_ISSUE = { number: 2, title: 'Closed issue', description: 'Issue description', state: GitHub.ISSUE_STATE_CLOSED, labels: [{ name: 'empty content' }] };
|
|
17
15
|
|
|
18
16
|
before(async () => {
|
|
19
|
-
MANAGED_LABELS =
|
|
17
|
+
MANAGED_LABELS = Object.values(LABELS);
|
|
20
18
|
github = new GitHub('owner/repo');
|
|
21
19
|
nock('https://api.github.com')
|
|
22
20
|
.get('/repos/owner/repo/issues')
|
|
@@ -274,7 +272,7 @@ describe('GitHub', function () {
|
|
|
274
272
|
|
|
275
273
|
describe('#createOrUpdateIssue', () => {
|
|
276
274
|
before(() => {
|
|
277
|
-
github.MANAGED_LABELS =
|
|
275
|
+
github.MANAGED_LABELS = Object.values(LABELS);
|
|
278
276
|
});
|
|
279
277
|
|
|
280
278
|
context('when the issue does not exist', () => {
|
|
@@ -282,7 +280,7 @@ describe('GitHub', function () {
|
|
|
282
280
|
const ISSUE_TO_CREATE = {
|
|
283
281
|
title: 'New Issue',
|
|
284
282
|
description: 'Description of the new issue',
|
|
285
|
-
|
|
283
|
+
labels: ['empty response'],
|
|
286
284
|
};
|
|
287
285
|
|
|
288
286
|
before(async () => {
|
|
@@ -290,7 +288,7 @@ describe('GitHub', function () {
|
|
|
290
288
|
.post('/repos/owner/repo/issues', {
|
|
291
289
|
title: ISSUE_TO_CREATE.title,
|
|
292
290
|
body: ISSUE_TO_CREATE.description,
|
|
293
|
-
labels:
|
|
291
|
+
labels: ISSUE_TO_CREATE.labels,
|
|
294
292
|
})
|
|
295
293
|
.reply(200, { number: 123 });
|
|
296
294
|
|
|
@@ -313,14 +311,14 @@ describe('GitHub', function () {
|
|
|
313
311
|
|
|
314
312
|
before(async () => {
|
|
315
313
|
updateIssueScope = nock('https://api.github.com')
|
|
316
|
-
.patch(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: ['
|
|
314
|
+
.patch(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: ['page access restriction'] })
|
|
317
315
|
.reply(200);
|
|
318
316
|
|
|
319
317
|
addCommentScope = nock('https://api.github.com')
|
|
320
318
|
.post(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}/comments`, { body: EXISTING_CLOSED_ISSUE.description })
|
|
321
319
|
.reply(200);
|
|
322
320
|
|
|
323
|
-
await github.createOrUpdateIssue({ title: EXISTING_CLOSED_ISSUE.title, description: EXISTING_CLOSED_ISSUE.description,
|
|
321
|
+
await github.createOrUpdateIssue({ title: EXISTING_CLOSED_ISSUE.title, description: EXISTING_CLOSED_ISSUE.description, labels: ['page access restriction'] });
|
|
324
322
|
});
|
|
325
323
|
|
|
326
324
|
after(() => {
|
|
@@ -344,14 +342,14 @@ describe('GitHub', function () {
|
|
|
344
342
|
|
|
345
343
|
before(async () => {
|
|
346
344
|
updateIssueScope = nock('https://api.github.com')
|
|
347
|
-
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: ['
|
|
345
|
+
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: ['empty content'] })
|
|
348
346
|
.reply(200);
|
|
349
347
|
|
|
350
348
|
addCommentScope = nock('https://api.github.com')
|
|
351
349
|
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`, { body: EXISTING_OPEN_ISSUE.description })
|
|
352
350
|
.reply(200);
|
|
353
351
|
|
|
354
|
-
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description,
|
|
352
|
+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: ['empty content'] });
|
|
355
353
|
});
|
|
356
354
|
|
|
357
355
|
after(() => {
|
|
@@ -367,25 +365,27 @@ describe('GitHub', function () {
|
|
|
367
365
|
expect(addCommentScope.isDone()).to.be.true;
|
|
368
366
|
});
|
|
369
367
|
});
|
|
370
|
-
|
|
368
|
+
|
|
369
|
+
context('when all requested labels are already present', () => {
|
|
371
370
|
let addCommentScope;
|
|
372
371
|
let updateIssueScope;
|
|
373
372
|
|
|
374
373
|
before(async () => {
|
|
375
374
|
updateIssueScope = nock('https://api.github.com')
|
|
376
|
-
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}
|
|
375
|
+
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`)
|
|
377
376
|
.reply(200);
|
|
378
377
|
|
|
379
378
|
addCommentScope = nock('https://api.github.com')
|
|
380
|
-
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments
|
|
379
|
+
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`)
|
|
381
380
|
.reply(200);
|
|
382
381
|
|
|
383
|
-
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description,
|
|
382
|
+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [ 'page access restriction', 'server error' ] });
|
|
384
383
|
});
|
|
385
384
|
|
|
386
385
|
after(() => {
|
|
387
|
-
github.issuesCache.delete(EXISTING_OPEN_ISSUE.title);
|
|
388
386
|
github.issuesCache.set(EXISTING_OPEN_ISSUE.title, EXISTING_OPEN_ISSUE);
|
|
387
|
+
|
|
388
|
+
nock.cleanAll();
|
|
389
389
|
});
|
|
390
390
|
|
|
391
391
|
it('does not attempt to updates the issue’s labels', () => {
|
|
@@ -396,6 +396,37 @@ describe('GitHub', function () {
|
|
|
396
396
|
expect(addCommentScope.isDone()).to.be.false;
|
|
397
397
|
});
|
|
398
398
|
});
|
|
399
|
+
|
|
400
|
+
context('when some but not all requested labels are present', () => {
|
|
401
|
+
let addCommentScope;
|
|
402
|
+
let updateIssueScope;
|
|
403
|
+
|
|
404
|
+
before(async () => {
|
|
405
|
+
updateIssueScope = nock('https://api.github.com')
|
|
406
|
+
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [ 'page access restriction', 'empty content' ] })
|
|
407
|
+
.reply(200);
|
|
408
|
+
|
|
409
|
+
addCommentScope = nock('https://api.github.com')
|
|
410
|
+
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`, { body: EXISTING_OPEN_ISSUE.description })
|
|
411
|
+
.reply(200);
|
|
412
|
+
|
|
413
|
+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [ 'page access restriction', 'empty content' ] });
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
after(() => {
|
|
417
|
+
github.issuesCache.set(EXISTING_OPEN_ISSUE.title, EXISTING_OPEN_ISSUE);
|
|
418
|
+
|
|
419
|
+
nock.cleanAll();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("updates the issue's labels", () => {
|
|
423
|
+
expect(updateIssueScope.isDone()).to.be.true;
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('adds a comment to the issue', () => {
|
|
427
|
+
expect(addCommentScope.isDone()).to.be.true;
|
|
428
|
+
});
|
|
429
|
+
});
|
|
399
430
|
});
|
|
400
431
|
});
|
|
401
432
|
});
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
|
|
3
1
|
import HttpProxyAgent from 'http-proxy-agent';
|
|
4
2
|
import HttpsProxyAgent from 'https-proxy-agent';
|
|
5
3
|
import nodeFetch from 'node-fetch';
|
|
6
4
|
|
|
7
5
|
import logger from '../../logger/index.js';
|
|
6
|
+
import { LABELS, MANAGED_BY_OTA_MARKER, DEPRECATED_MANAGED_BY_OTA_MARKER } from '../labels.js';
|
|
8
7
|
|
|
9
|
-
const require = createRequire(import.meta.url);
|
|
10
|
-
|
|
11
|
-
export const MANAGED_BY_OTA_MARKER = '[managed by OTA]';
|
|
12
8
|
const BASE_URL = 'https://gitlab.com';
|
|
13
9
|
const API_BASE_URL = 'https://gitlab.com/api/v4';
|
|
14
10
|
|
|
@@ -16,12 +12,12 @@ export default class GitLab {
|
|
|
16
12
|
static ISSUE_STATE_CLOSED = 'closed';
|
|
17
13
|
static ISSUE_STATE_OPEN = 'opened';
|
|
18
14
|
static ISSUE_STATE_ALL = 'all';
|
|
15
|
+
static MAX_LABEL_DESCRIPTION_LENGTH = 255;
|
|
19
16
|
|
|
20
17
|
constructor(repository, baseURL = BASE_URL, apiBaseURL = API_BASE_URL) {
|
|
21
18
|
this.repositoryPath = repository;
|
|
22
19
|
this.projectId = null;
|
|
23
20
|
this.baseURL = baseURL;
|
|
24
|
-
console.log('this.baseURL', this.baseURL);
|
|
25
21
|
this.apiBaseURL = apiBaseURL;
|
|
26
22
|
}
|
|
27
23
|
|
|
@@ -47,22 +43,35 @@ export default class GitLab {
|
|
|
47
43
|
this.projectId = null;
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
this.MANAGED_LABELS =
|
|
46
|
+
this.MANAGED_LABELS = Object.values(LABELS);
|
|
47
|
+
try {
|
|
48
|
+
const existingLabels = await this.getRepositoryLabels();
|
|
49
|
+
const labelsToRemove = existingLabels.filter(label => label.description && label.description.includes(DEPRECATED_MANAGED_BY_OTA_MARKER));
|
|
50
|
+
|
|
51
|
+
if (labelsToRemove.length) {
|
|
52
|
+
logger.info(`Removing labels with deprecated markers: ${labelsToRemove.map(label => `"${label.name}"`).join(', ')}`);
|
|
53
|
+
|
|
54
|
+
for (const label of labelsToRemove) {
|
|
55
|
+
await this.deleteLabel(label.name); /* eslint-disable-line no-await-in-loop */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const missingLabels = this.MANAGED_LABELS.filter(label => !existingLabelsNames.includes(label.name));
|
|
59
|
+
const existingLabelsNames = existingLabels.map(label => label.name);
|
|
60
|
+
const missingLabels = this.MANAGED_LABELS.filter(label => !existingLabelsNames.includes(label.name));
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
if (missingLabels.length) {
|
|
63
|
+
logger.info(`Following required labels are not present on the repository: ${missingLabels.map(label => `"${label.name}"`).join(', ')}. Creating them…`);
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
for (const label of missingLabels) {
|
|
66
|
+
await this.createLabel({ /* eslint-disable-line no-await-in-loop */
|
|
67
|
+
name: label.name,
|
|
68
|
+
color: `#${label.color}`,
|
|
69
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
65
72
|
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger.error(`Failed to handle repository labels: ${error.message}`);
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
|
|
@@ -122,6 +131,29 @@ export default class GitLab {
|
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
|
|
134
|
+
async deleteLabel(name) {
|
|
135
|
+
try {
|
|
136
|
+
const options = GitLab.baseOptionsHttpReq();
|
|
137
|
+
|
|
138
|
+
options.method = 'DELETE';
|
|
139
|
+
|
|
140
|
+
const response = await nodeFetch(
|
|
141
|
+
`${this.apiBaseURL}/projects/${this.projectId}/labels/${encodeURIComponent(name)}`,
|
|
142
|
+
options,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (response.ok) {
|
|
146
|
+
logger.info(`Label deleted: ${name}`);
|
|
147
|
+
} else {
|
|
148
|
+
const res = await response.json();
|
|
149
|
+
|
|
150
|
+
logger.error(`deleteLabel response: ${JSON.stringify(res)}`);
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
logger.error(`Failed to delete label: ${error}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
125
157
|
async createIssue({ title, description, labels }) {
|
|
126
158
|
try {
|
|
127
159
|
const issue = {
|
|
@@ -318,35 +350,39 @@ export default class GitLab {
|
|
|
318
350
|
return this.closeIssue(issue);
|
|
319
351
|
}
|
|
320
352
|
|
|
321
|
-
async createOrUpdateIssue({ title, description,
|
|
322
|
-
|
|
353
|
+
async createOrUpdateIssue({ title, description, labels }) {
|
|
354
|
+
try {
|
|
355
|
+
const issue = await this.getIssue({ title, state: GitLab.ISSUE_STATE_ALL });
|
|
323
356
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
357
|
+
if (!issue) {
|
|
358
|
+
const createdIssue = await this.createIssue({ title, description, labels });
|
|
327
359
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
360
|
+
return logger.info(`Created GitLab issue #${createdIssue.iid} "${title}": ${createdIssue.web_url}`);
|
|
361
|
+
}
|
|
331
362
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
managedLabelsNames.includes(label.name));
|
|
363
|
+
const managedLabelsNames = this.MANAGED_LABELS.map(label => label.name);
|
|
364
|
+
const labelsNotManagedToKeep = issue.labels.map(label => label.name).filter(label => !managedLabelsNames.includes(label));
|
|
365
|
+
const managedLabels = issue.labels.filter(label => managedLabelsNames.includes(label.name));
|
|
335
366
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
367
|
+
if (issue.state !== GitLab.ISSUE_STATE_CLOSED && labels.every(label => managedLabels.some(managedLabel => managedLabel.name === label))) {
|
|
368
|
+
return; // if all requested labels are already assigned to the issue, the error is redundant with the one already reported and no further action is necessary
|
|
369
|
+
}
|
|
340
370
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
371
|
+
if (issue.state == GitLab.ISSUE_STATE_CLOSED) {
|
|
372
|
+
await this.openIssue(issue);
|
|
373
|
+
}
|
|
344
374
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
375
|
+
await this.setIssueLabels({
|
|
376
|
+
issue,
|
|
377
|
+
labels: [ ...labels, ...labelsNotManagedToKeep ],
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
await this.addCommentToIssue({ issue, comment: description });
|
|
381
|
+
|
|
382
|
+
logger.info(`Updated GitLab issue with comment #${issue.iid}: ${issue.web_url}`);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
logger.error(`Failed to update GitLab issue "${title}": ${error.stack}`);
|
|
385
|
+
}
|
|
350
386
|
}
|
|
351
387
|
|
|
352
388
|
static baseOptionsHttpReq(token = process.env.OTA_ENGINE_GITLAB_TOKEN) {
|
|
@@ -368,7 +404,7 @@ export default class GitLab {
|
|
|
368
404
|
}
|
|
369
405
|
|
|
370
406
|
generateVersionURL(serviceName, termsType) {
|
|
371
|
-
return `${this.baseURL}/${this.repositoryPath}/-/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(
|
|
407
|
+
return `${this.baseURL}/${this.repositoryPath}/-/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}.md`;
|
|
372
408
|
}
|
|
373
409
|
|
|
374
410
|
generateSnapshotsBaseUrl(serviceName, termsType) {
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
|
|
3
1
|
import { expect } from 'chai';
|
|
4
2
|
import nock from 'nock';
|
|
5
3
|
|
|
6
|
-
import
|
|
4
|
+
import { LABELS } from '../labels.js';
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
import GitLab from './index.js';
|
|
9
7
|
|
|
10
8
|
describe('GitLab', function () {
|
|
11
9
|
this.timeout(5000);
|
|
@@ -15,7 +13,7 @@ describe('GitLab', function () {
|
|
|
15
13
|
const PROJECT_ID = '4';
|
|
16
14
|
|
|
17
15
|
before(() => {
|
|
18
|
-
MANAGED_LABELS =
|
|
16
|
+
MANAGED_LABELS = Object.values(LABELS);
|
|
19
17
|
gitlab = new GitLab('owner/repo');
|
|
20
18
|
});
|
|
21
19
|
|
|
@@ -400,12 +398,14 @@ describe('GitLab', function () {
|
|
|
400
398
|
await gitlab.initialize();
|
|
401
399
|
});
|
|
402
400
|
|
|
401
|
+
after(nock.cleanAll);
|
|
402
|
+
|
|
403
403
|
context('when the issue does not exist', () => {
|
|
404
404
|
let createIssueScope;
|
|
405
405
|
const ISSUE_TO_CREATE = {
|
|
406
406
|
title: 'New Issue',
|
|
407
407
|
description: 'Description of the new issue',
|
|
408
|
-
|
|
408
|
+
labels: ['empty response'],
|
|
409
409
|
};
|
|
410
410
|
|
|
411
411
|
before(async () => {
|
|
@@ -419,7 +419,7 @@ describe('GitLab', function () {
|
|
|
419
419
|
{
|
|
420
420
|
title: ISSUE_TO_CREATE.title,
|
|
421
421
|
description: ISSUE_TO_CREATE.description,
|
|
422
|
-
labels:
|
|
422
|
+
labels: ISSUE_TO_CREATE.labels,
|
|
423
423
|
},
|
|
424
424
|
)
|
|
425
425
|
.reply(200, { iid: 123, web_url: 'https://example.com/test/test' });
|
|
@@ -436,7 +436,7 @@ describe('GitLab', function () {
|
|
|
436
436
|
const ISSUE = {
|
|
437
437
|
title: 'Existing Issue',
|
|
438
438
|
description: 'New comment',
|
|
439
|
-
|
|
439
|
+
labels: ['page access restriction'],
|
|
440
440
|
};
|
|
441
441
|
|
|
442
442
|
context('when issue is closed', () => {
|
|
@@ -448,7 +448,7 @@ describe('GitLab', function () {
|
|
|
448
448
|
iid: 123,
|
|
449
449
|
title: ISSUE.title,
|
|
450
450
|
description: ISSUE.description,
|
|
451
|
-
labels: [{ name: '
|
|
451
|
+
labels: [{ name: 'empty content' }],
|
|
452
452
|
state: GitLab.ISSUE_STATE_CLOSED,
|
|
453
453
|
};
|
|
454
454
|
|
|
@@ -456,7 +456,7 @@ describe('GitLab', function () {
|
|
|
456
456
|
const responseIssuereopened = { iid: 123 };
|
|
457
457
|
const responseSetLabels = {
|
|
458
458
|
iid: 123,
|
|
459
|
-
labels: ['
|
|
459
|
+
labels: ['page access restriction'],
|
|
460
460
|
};
|
|
461
461
|
const responseAddcomment = { iid: 123, id: 23, body: ISSUE.description };
|
|
462
462
|
const { iid } = GITLAB_RESPONSE_FOR_EXISTING_ISSUE;
|
|
@@ -471,7 +471,7 @@ describe('GitLab', function () {
|
|
|
471
471
|
.reply(200, responseIssuereopened);
|
|
472
472
|
|
|
473
473
|
setIssueLabelsScope = nock(gitlab.apiBaseURL)
|
|
474
|
-
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: ['
|
|
474
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: ['page access restriction'] })
|
|
475
475
|
.reply(200, responseSetLabels);
|
|
476
476
|
|
|
477
477
|
addCommentScope = nock(gitlab.apiBaseURL)
|
|
@@ -499,53 +499,170 @@ describe('GitLab', function () {
|
|
|
499
499
|
let addCommentScope;
|
|
500
500
|
let openIssueScope;
|
|
501
501
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
.
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
502
|
+
context('when the reason is new', () => {
|
|
503
|
+
const GITLAB_RESPONSE_FOR_EXISTING_ISSUE = {
|
|
504
|
+
iid: 123,
|
|
505
|
+
title: ISSUE.title,
|
|
506
|
+
description: ISSUE.description,
|
|
507
|
+
labels: [{ name: 'empty content' }],
|
|
508
|
+
state: GitLab.ISSUE_STATE_OPEN,
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const EXPECTED_REQUEST_BODY = { state_event: 'reopen' };
|
|
512
|
+
const responseIssuereopened = { iid: 123 };
|
|
513
|
+
const responseSetLabels = {
|
|
514
|
+
iid: 123,
|
|
515
|
+
labels: ['page access restriction'],
|
|
516
|
+
};
|
|
517
|
+
const responseAddcomment = { iid: 123, id: 23, body: ISSUE.description };
|
|
518
|
+
const { iid } = GITLAB_RESPONSE_FOR_EXISTING_ISSUE;
|
|
519
|
+
|
|
520
|
+
before(async () => {
|
|
521
|
+
nock(gitlab.apiBaseURL)
|
|
522
|
+
.get(`/projects/${PROJECT_ID}/issues?search=${encodeURIComponent(ISSUE.title)}&per_page=100`)
|
|
523
|
+
.reply(200, [GITLAB_RESPONSE_FOR_EXISTING_ISSUE]);
|
|
524
|
+
|
|
525
|
+
openIssueScope = nock(gitlab.apiBaseURL)
|
|
526
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`, EXPECTED_REQUEST_BODY)
|
|
527
|
+
.reply(200, responseIssuereopened);
|
|
528
|
+
|
|
529
|
+
setIssueLabelsScope = nock(gitlab.apiBaseURL)
|
|
530
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: ['page access restriction'] })
|
|
531
|
+
.reply(200, responseSetLabels);
|
|
532
|
+
|
|
533
|
+
addCommentScope = nock(gitlab.apiBaseURL)
|
|
534
|
+
.post(`/projects/${PROJECT_ID}/issues/${iid}/notes`, { body: ISSUE.description })
|
|
535
|
+
.reply(200, responseAddcomment);
|
|
536
|
+
|
|
537
|
+
await gitlab.createOrUpdateIssue(ISSUE);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('does not change the issue state', () => {
|
|
541
|
+
expect(openIssueScope.isDone()).to.be.false;
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it("updates the issue's label", () => {
|
|
545
|
+
expect(setIssueLabelsScope.isDone()).to.be.true;
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('adds comment to the issue', () => {
|
|
549
|
+
expect(addCommentScope.isDone()).to.be.true;
|
|
550
|
+
});
|
|
541
551
|
});
|
|
542
552
|
|
|
543
|
-
|
|
544
|
-
|
|
553
|
+
context('when all requested labels are already present', () => {
|
|
554
|
+
let setIssueLabelsScope;
|
|
555
|
+
let addCommentScope;
|
|
556
|
+
let openIssueScope;
|
|
557
|
+
|
|
558
|
+
const GITLAB_RESPONSE_FOR_EXISTING_ISSUE = {
|
|
559
|
+
iid: 123,
|
|
560
|
+
title: ISSUE.title,
|
|
561
|
+
description: ISSUE.description,
|
|
562
|
+
labels: [{ name: 'page access restriction' }, { name: 'server error' }],
|
|
563
|
+
state: GitLab.ISSUE_STATE_OPEN,
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const { iid } = GITLAB_RESPONSE_FOR_EXISTING_ISSUE;
|
|
567
|
+
|
|
568
|
+
before(async () => {
|
|
569
|
+
nock(gitlab.apiBaseURL)
|
|
570
|
+
.get(`/projects/${PROJECT_ID}/issues?search=${encodeURIComponent(ISSUE.title)}&per_page=100`)
|
|
571
|
+
.reply(200, [GITLAB_RESPONSE_FOR_EXISTING_ISSUE]);
|
|
572
|
+
|
|
573
|
+
openIssueScope = nock(gitlab.apiBaseURL)
|
|
574
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`)
|
|
575
|
+
.reply(200);
|
|
576
|
+
|
|
577
|
+
setIssueLabelsScope = nock(gitlab.apiBaseURL)
|
|
578
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`)
|
|
579
|
+
.reply(200);
|
|
580
|
+
|
|
581
|
+
addCommentScope = nock(gitlab.apiBaseURL)
|
|
582
|
+
.post(`/projects/${PROJECT_ID}/issues/${iid}/notes`)
|
|
583
|
+
.reply(200);
|
|
584
|
+
|
|
585
|
+
await gitlab.createOrUpdateIssue({
|
|
586
|
+
title: ISSUE.title,
|
|
587
|
+
description: ISSUE.description,
|
|
588
|
+
labels: [ 'page access restriction', 'server error' ],
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('does not change the issue state', () => {
|
|
593
|
+
expect(openIssueScope.isDone()).to.be.false;
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('does not attempt to update the issue labels', () => {
|
|
597
|
+
expect(setIssueLabelsScope.isDone()).to.be.false;
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('does not attempt to add any comment to the issue', () => {
|
|
601
|
+
expect(addCommentScope.isDone()).to.be.false;
|
|
602
|
+
});
|
|
545
603
|
});
|
|
546
604
|
|
|
547
|
-
|
|
548
|
-
|
|
605
|
+
context('when some but not all requested labels are present', () => {
|
|
606
|
+
let setIssueLabelsScope;
|
|
607
|
+
let addCommentScope;
|
|
608
|
+
let openIssueScope;
|
|
609
|
+
|
|
610
|
+
before(() => {
|
|
611
|
+
nock.cleanAll();
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const GITLAB_RESPONSE_FOR_EXISTING_ISSUE = {
|
|
615
|
+
iid: 123,
|
|
616
|
+
title: ISSUE.title,
|
|
617
|
+
description: ISSUE.description,
|
|
618
|
+
labels: [{ name: 'page access restriction' }],
|
|
619
|
+
state: GitLab.ISSUE_STATE_OPEN,
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
const EXPECTED_REQUEST_BODY = { state_event: 'reopen' };
|
|
623
|
+
const responseIssuereopened = { iid: 123 };
|
|
624
|
+
const responseSetLabels = {
|
|
625
|
+
iid: 123,
|
|
626
|
+
labels: [ 'page access restriction', 'empty content' ],
|
|
627
|
+
};
|
|
628
|
+
const responseAddcomment = { iid: 123, id: 23, body: ISSUE.description };
|
|
629
|
+
const { iid } = GITLAB_RESPONSE_FOR_EXISTING_ISSUE;
|
|
630
|
+
|
|
631
|
+
before(async () => {
|
|
632
|
+
nock(gitlab.apiBaseURL)
|
|
633
|
+
.get(`/projects/${PROJECT_ID}/issues?search=${encodeURIComponent(ISSUE.title)}&per_page=100`)
|
|
634
|
+
.reply(200, [GITLAB_RESPONSE_FOR_EXISTING_ISSUE]);
|
|
635
|
+
|
|
636
|
+
openIssueScope = nock(gitlab.apiBaseURL)
|
|
637
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`, EXPECTED_REQUEST_BODY)
|
|
638
|
+
.reply(200, responseIssuereopened);
|
|
639
|
+
|
|
640
|
+
setIssueLabelsScope = nock(gitlab.apiBaseURL)
|
|
641
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: [ 'page access restriction', 'empty content' ] })
|
|
642
|
+
.reply(200, responseSetLabels);
|
|
643
|
+
|
|
644
|
+
addCommentScope = nock(gitlab.apiBaseURL)
|
|
645
|
+
.post(`/projects/${PROJECT_ID}/issues/${iid}/notes`, { body: ISSUE.description })
|
|
646
|
+
.reply(200, responseAddcomment);
|
|
647
|
+
|
|
648
|
+
await gitlab.createOrUpdateIssue({
|
|
649
|
+
title: ISSUE.title,
|
|
650
|
+
description: ISSUE.description,
|
|
651
|
+
labels: [ 'page access restriction', 'empty content' ],
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('does not change the issue state', () => {
|
|
656
|
+
expect(openIssueScope.isDone()).to.be.false;
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it("updates the issue's labels", () => {
|
|
660
|
+
expect(setIssueLabelsScope.isDone()).to.be.true;
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it('adds comment to the issue', () => {
|
|
664
|
+
expect(addCommentScope.isDone()).to.be.true;
|
|
665
|
+
});
|
|
549
666
|
});
|
|
550
667
|
});
|
|
551
668
|
});
|
package/src/reporter/index.js
CHANGED
|
@@ -4,31 +4,32 @@ import { toISODateWithoutMilliseconds } from '../archivist/utils/date.js';
|
|
|
4
4
|
import logger from '../logger/index.js';
|
|
5
5
|
|
|
6
6
|
import { createReporter } from './factory.js';
|
|
7
|
+
import { LABELS } from './labels.js';
|
|
7
8
|
|
|
8
9
|
const CONTRIBUTION_TOOL_URL = 'https://contribute.opentermsarchive.org/en/service';
|
|
9
10
|
const DOC_URL = 'https://docs.opentermsarchive.org';
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
'has no match':
|
|
13
|
-
'HTTP code 404':
|
|
14
|
-
'HTTP code 403':
|
|
15
|
-
'HTTP code 429':
|
|
16
|
-
'HTTP code 500':
|
|
17
|
-
'HTTP code 502':
|
|
18
|
-
'HTTP code 503':
|
|
19
|
-
'Timed out after':
|
|
20
|
-
EAI_AGAIN:
|
|
21
|
-
ENOTFOUND:
|
|
22
|
-
'Response is empty':
|
|
23
|
-
'unable to verify the first certificate':
|
|
24
|
-
'certificate has expired':
|
|
25
|
-
'maximum redirect reached':
|
|
26
|
-
'not a valid selector':
|
|
27
|
-
'empty content':
|
|
12
|
+
const ERROR_MESSAGE_TO_ISSUE_LABELS_MAP = {
|
|
13
|
+
'has no match': [ LABELS.PAGE_STRUCTURE_CHANGE.name, LABELS.NEEDS_INTERVENTION.name ],
|
|
14
|
+
'HTTP code 404': [ LABELS.PAGE_NOT_FOUND.name, LABELS.NEEDS_INTERVENTION.name ],
|
|
15
|
+
'HTTP code 403': [LABELS.HTTP_403.name],
|
|
16
|
+
'HTTP code 429': [LABELS.HTTP_429.name],
|
|
17
|
+
'HTTP code 500': [LABELS.HTTP_500.name],
|
|
18
|
+
'HTTP code 502': [LABELS.HTTP_502.name],
|
|
19
|
+
'HTTP code 503': [LABELS.HTTP_503.name],
|
|
20
|
+
'Timed out after': [LABELS.PAGE_LOAD_TIMEOUT.name],
|
|
21
|
+
EAI_AGAIN: [LABELS.DNS_LOOKUP_FAILURE.name],
|
|
22
|
+
ENOTFOUND: [LABELS.DNS_RESOLUTION_FAILURE.name],
|
|
23
|
+
'Response is empty': [LABELS.EMPTY_RESPONSE.name],
|
|
24
|
+
'unable to verify the first certificate': [LABELS.SSL_INVALID.name],
|
|
25
|
+
'certificate has expired': [LABELS.SSL_EXPIRED.name],
|
|
26
|
+
'maximum redirect reached': [LABELS.TOO_MANY_REDIRECTS.name],
|
|
27
|
+
'not a valid selector': [LABELS.INVALID_SELECTOR.name],
|
|
28
|
+
'empty content': [LABELS.EMPTY_CONTENT.name],
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
function
|
|
31
|
-
return
|
|
31
|
+
function getLabelNamesFromError(error) {
|
|
32
|
+
return ERROR_MESSAGE_TO_ISSUE_LABELS_MAP[Object.keys(ERROR_MESSAGE_TO_ISSUE_LABELS_MAP).find(substring => error.toString().includes(substring))] || [LABELS.UNKNOWN_FAILURE.name];
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
// In the following class, it is assumed that each issue is managed using its title as a unique identifier
|
|
@@ -118,7 +119,7 @@ No changes were found in the last run, so no new version has been recorded.`,
|
|
|
118
119
|
await this.reporter.createOrUpdateIssue({
|
|
119
120
|
title: Reporter.generateTitleID(terms.service.id, terms.type),
|
|
120
121
|
description: this.generateDescription({ error, terms }),
|
|
121
|
-
|
|
122
|
+
labels: getLabelNamesFromError(error),
|
|
122
123
|
});
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export const DEPRECATED_MANAGED_BY_OTA_MARKER = '[managed by OTA]';
|
|
2
|
+
|
|
3
|
+
export const MANAGED_BY_OTA_MARKER = '- Auto-managed by OTA engine';
|
|
4
|
+
|
|
5
|
+
export const LABELS = {
|
|
6
|
+
HTTP_403: {
|
|
7
|
+
name: 'page access restriction',
|
|
8
|
+
color: 'FFFFFF',
|
|
9
|
+
description: 'Fetching failed with a 403 (forbidden) HTTP code',
|
|
10
|
+
},
|
|
11
|
+
HTTP_429: {
|
|
12
|
+
name: 'request limit exceeded',
|
|
13
|
+
color: 'FFFFFF',
|
|
14
|
+
description: 'Fetching failed with a 429 (too many requests) HTTP code',
|
|
15
|
+
},
|
|
16
|
+
HTTP_500: {
|
|
17
|
+
name: 'server error',
|
|
18
|
+
color: 'FFFFFF',
|
|
19
|
+
description: 'Fetching failed with a 500 (internal server error) HTTP code',
|
|
20
|
+
},
|
|
21
|
+
HTTP_502: {
|
|
22
|
+
name: 'server response failure',
|
|
23
|
+
color: 'FFFFFF',
|
|
24
|
+
description: 'Fetching failed with a 502 (bad gateway) HTTP code',
|
|
25
|
+
},
|
|
26
|
+
HTTP_503: {
|
|
27
|
+
name: 'server unavailability',
|
|
28
|
+
color: 'FFFFFF',
|
|
29
|
+
description: 'Fetching failed with a 503 (service unavailable) HTTP code',
|
|
30
|
+
},
|
|
31
|
+
SSL_EXPIRED: {
|
|
32
|
+
name: 'SSL certificate expiration',
|
|
33
|
+
color: 'FFFFFF',
|
|
34
|
+
description: 'Fetching failed because the domain SSL certificate has expired',
|
|
35
|
+
},
|
|
36
|
+
DNS_LOOKUP_FAILURE: {
|
|
37
|
+
name: 'DNS lookup failure',
|
|
38
|
+
color: 'FFFFFF',
|
|
39
|
+
description: 'Fetching failed because the domain temporarily failed to resolve on DNS',
|
|
40
|
+
},
|
|
41
|
+
DNS_RESOLUTION_FAILURE: {
|
|
42
|
+
name: 'DNS resolution failure',
|
|
43
|
+
color: 'FFFFFF',
|
|
44
|
+
description: 'Fetching failed because the domain fails to resolve on DNS',
|
|
45
|
+
},
|
|
46
|
+
EMPTY_RESPONSE: {
|
|
47
|
+
name: 'empty response',
|
|
48
|
+
color: 'FFFFFF',
|
|
49
|
+
description: 'Fetching failed because server returned an empty response body',
|
|
50
|
+
},
|
|
51
|
+
SSL_INVALID: {
|
|
52
|
+
name: 'SSL certificate invalidity',
|
|
53
|
+
color: 'FFFFFF',
|
|
54
|
+
description: 'Fetching failed because SSL certificate verification failed',
|
|
55
|
+
},
|
|
56
|
+
TOO_MANY_REDIRECTS: {
|
|
57
|
+
name: 'too many redirects',
|
|
58
|
+
color: 'FFFFFF',
|
|
59
|
+
description: 'Fetching failed because too many redirects detected',
|
|
60
|
+
},
|
|
61
|
+
PAGE_LOAD_TIMEOUT: {
|
|
62
|
+
name: 'page load timeout',
|
|
63
|
+
color: 'FFFFFF',
|
|
64
|
+
description: 'Fetching failed with a timeout error',
|
|
65
|
+
},
|
|
66
|
+
EMPTY_CONTENT: {
|
|
67
|
+
name: 'empty content',
|
|
68
|
+
color: 'FFFFFF',
|
|
69
|
+
description: 'Fetching failed because the server returns an empty content',
|
|
70
|
+
},
|
|
71
|
+
UNKNOWN_FAILURE: {
|
|
72
|
+
name: 'unknown failure reason',
|
|
73
|
+
color: 'FFFFFF',
|
|
74
|
+
description: 'Fetching failed for an undetermined reason that needs investigation',
|
|
75
|
+
},
|
|
76
|
+
PAGE_STRUCTURE_CHANGE: {
|
|
77
|
+
name: 'page structure change',
|
|
78
|
+
color: 'FFFFFF',
|
|
79
|
+
description: 'Extraction selectors are outdated',
|
|
80
|
+
},
|
|
81
|
+
PAGE_NOT_FOUND: {
|
|
82
|
+
name: 'page not found',
|
|
83
|
+
color: 'FFFFFF',
|
|
84
|
+
description: 'Fetch location is outdated',
|
|
85
|
+
},
|
|
86
|
+
INVALID_SELECTOR: {
|
|
87
|
+
name: 'invalid selector',
|
|
88
|
+
color: 'FFFFFF',
|
|
89
|
+
description: 'Some selectors cannot be understood by the engine',
|
|
90
|
+
},
|
|
91
|
+
NEEDS_INTERVENTION: {
|
|
92
|
+
name: '⚠ needs intervention',
|
|
93
|
+
color: 'FDD42A',
|
|
94
|
+
description: 'Requires contributors intervention to restore tracking',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import chai from 'chai';
|
|
2
|
+
|
|
3
|
+
import GitHub from './github/index.js';
|
|
4
|
+
import GitLab from './gitlab/index.js';
|
|
5
|
+
import { MANAGED_BY_OTA_MARKER, LABELS } from './labels.js';
|
|
6
|
+
|
|
7
|
+
const { expect } = chai;
|
|
8
|
+
|
|
9
|
+
const MAX_LABEL_DESCRIPTION_LENGTH = Math.min(
|
|
10
|
+
GitHub.MAX_LABEL_DESCRIPTION_LENGTH,
|
|
11
|
+
GitLab.MAX_LABEL_DESCRIPTION_LENGTH,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
describe('Reporter GitHub labels', () => {
|
|
15
|
+
Object.values(LABELS).forEach(label => {
|
|
16
|
+
describe(`"${label.name}"`, () => {
|
|
17
|
+
it('complies with the max description length of supported platforms', () => {
|
|
18
|
+
const descriptionLength = label.description.length + MANAGED_BY_OTA_MARKER.length;
|
|
19
|
+
|
|
20
|
+
expect(descriptionLength).to.be.lessThan(MAX_LABEL_DESCRIPTION_LENGTH);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"name": "403",
|
|
4
|
-
"color": "0b08a0",
|
|
5
|
-
"description": "Fetching fails with a 403 (forbidden) HTTP code"
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"name": "429",
|
|
9
|
-
"color": "0b08a0",
|
|
10
|
-
"description": "Fetching fails with a 429 (too many requests) HTTP code"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"name": "500",
|
|
14
|
-
"color": "0b08a0",
|
|
15
|
-
"description": "Fetching fails with a 500 (internal server error) HTTP code"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"name": "502",
|
|
19
|
-
"color": "0b08a0",
|
|
20
|
-
"description": "Fetching fails with a 502 (bad gateway) HTTP code"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"name": "503",
|
|
24
|
-
"color": "0b08a0",
|
|
25
|
-
"description": "Fetching fails with a 503 (service unavailable) HTTP code"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"name": "certificate expired",
|
|
29
|
-
"color": "0b08a0",
|
|
30
|
-
"description": "Fetching fails because the domain SSL certificate has expired"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"name": "EAI_AGAIN",
|
|
34
|
-
"color": "0b08a0",
|
|
35
|
-
"description": "Fetching fails because the domain fails to resolve on DNS"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"name": "ENOTFOUND",
|
|
39
|
-
"color": "0b08a0",
|
|
40
|
-
"description": "Fetching fails because the domain fails to resolve on DNS"
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"name": "empty response",
|
|
44
|
-
"color": "0b08a0",
|
|
45
|
-
"description": "Fetching fails with a “response is empty” error"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"name": "first certificate",
|
|
49
|
-
"color": "0b08a0",
|
|
50
|
-
"description": "Fetching fails with an “unable to verify the first certificate” error"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"name": "redirects",
|
|
54
|
-
"color": "0b08a0",
|
|
55
|
-
"description": "Fetching fails with a “too many redirects” error"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"name": "timeout",
|
|
59
|
-
"color": "0b08a0",
|
|
60
|
-
"description": "Fetching fails with a timeout error"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"name": "empty content",
|
|
64
|
-
"color": "0b08a0",
|
|
65
|
-
"description": "Fetching fails because the server returns an empty content"
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"name": "to clarify",
|
|
69
|
-
"color": "0496ff",
|
|
70
|
-
"description": "Default failure label"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"name": "selectors",
|
|
74
|
-
"color": "FBCA04",
|
|
75
|
-
"description": "Extraction selectors are outdated"
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"name": "location",
|
|
79
|
-
"color": "FBCA04",
|
|
80
|
-
"description": "Fetch location is outdated"
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"name": "invalid selector",
|
|
84
|
-
"color": "FBCA04",
|
|
85
|
-
"description": "Some selectors cannot be understood by the engine"
|
|
86
|
-
}
|
|
87
|
-
]
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
|
|
3
|
-
import chai from 'chai';
|
|
4
|
-
|
|
5
|
-
import { MANAGED_BY_OTA_MARKER } from './index.js';
|
|
6
|
-
|
|
7
|
-
const require = createRequire(import.meta.url);
|
|
8
|
-
|
|
9
|
-
const { expect } = chai;
|
|
10
|
-
const labels = require('./labels.json');
|
|
11
|
-
|
|
12
|
-
const GITHUB_LABEL_DESCRIPTION_MAX_LENGTH = 100;
|
|
13
|
-
|
|
14
|
-
describe('Reporter GitHub labels', () => {
|
|
15
|
-
labels.forEach(label => {
|
|
16
|
-
describe(`"${label.name}"`, () => {
|
|
17
|
-
it('complies with the GitHub character limit for descriptions', () => {
|
|
18
|
-
const descriptionLength = label.description.length + MANAGED_BY_OTA_MARKER.length;
|
|
19
|
-
|
|
20
|
-
expect(descriptionLength).to.be.lessThan(GITHUB_LABEL_DESCRIPTION_MAX_LENGTH);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('complies with the GitHub constraints for color', () => {
|
|
24
|
-
const validHexColorRegex = /^[0-9a-fA-F]{6}$/; // Regex for a valid 6-digit hexadecimal color code without the `#`
|
|
25
|
-
|
|
26
|
-
expect(validHexColorRegex.test(label.color)).to.be.true;
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"name": "403",
|
|
4
|
-
"color": "#0b08a0",
|
|
5
|
-
"description": "Fetching fails with a 403 (forbidden) HTTP code"
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"name": "429",
|
|
9
|
-
"color": "#0b08a0",
|
|
10
|
-
"description": "Fetching fails with a 429 (too many requests) HTTP code"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"name": "500",
|
|
14
|
-
"color": "#0b08a0",
|
|
15
|
-
"description": "Fetching fails with a 500 (internal server error) HTTP code"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"name": "502",
|
|
19
|
-
"color": "#0b08a0",
|
|
20
|
-
"description": "Fetching fails with a 502 (bad gateway) HTTP code"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"name": "503",
|
|
24
|
-
"color": "#0b08a0",
|
|
25
|
-
"description": "Fetching fails with a 503 (service unavailable) HTTP code"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"name": "certificate expired",
|
|
29
|
-
"color": "#0b08a0",
|
|
30
|
-
"description": "Fetching fails because the domain SSL certificate has expired"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"name": "EAI_AGAIN",
|
|
34
|
-
"color": "#0b08a0",
|
|
35
|
-
"description": "Fetching fails because the domain fails to resolve on DNS"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"name": "ENOTFOUND",
|
|
39
|
-
"color": "#0b08a0",
|
|
40
|
-
"description": "Fetching fails because the domain fails to resolve on DNS"
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"name": "empty response",
|
|
44
|
-
"color": "#0b08a0",
|
|
45
|
-
"description": "Fetching fails with a “response is empty” error"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"name": "first certificate",
|
|
49
|
-
"color": "#0b08a0",
|
|
50
|
-
"description": "Fetching fails with an “unable to verify the first certificate” error"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"name": "redirects",
|
|
54
|
-
"color": "#0b08a0",
|
|
55
|
-
"description": "Fetching fails with a “too many redirects” error"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"name": "timeout",
|
|
59
|
-
"color": "#0b08a0",
|
|
60
|
-
"description": "Fetching fails with a timeout error"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"name": "to clarify",
|
|
64
|
-
"color": "#0496ff",
|
|
65
|
-
"description": "Default failure label"
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"name": "selectors",
|
|
69
|
-
"color": "#FBCA04",
|
|
70
|
-
"description": "Extraction selectors are outdated"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"name": "location",
|
|
74
|
-
"color": "#FBCA04",
|
|
75
|
-
"description": "Fetch location is outdated"
|
|
76
|
-
}
|
|
77
|
-
]
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
|
|
3
|
-
import chai from 'chai';
|
|
4
|
-
|
|
5
|
-
import { MANAGED_BY_OTA_MARKER } from './index.js';
|
|
6
|
-
|
|
7
|
-
const require = createRequire(import.meta.url);
|
|
8
|
-
|
|
9
|
-
const { expect } = chai;
|
|
10
|
-
const labels = require('./labels.json');
|
|
11
|
-
|
|
12
|
-
const GITLAB_LABEL_DESCRIPTION_MAX_LENGTH = 255;
|
|
13
|
-
|
|
14
|
-
describe('Reporter GitLab labels', () => {
|
|
15
|
-
labels.forEach(label => {
|
|
16
|
-
describe(`"${label.name}"`, () => {
|
|
17
|
-
it('complies with the GitLab character limit for descriptions', () => {
|
|
18
|
-
const descriptionLength = label.description.length + MANAGED_BY_OTA_MARKER.length;
|
|
19
|
-
|
|
20
|
-
expect(descriptionLength).to.be.lessThan(GITLAB_LABEL_DESCRIPTION_MAX_LENGTH);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('complies with the GitLab constraints for color', () => {
|
|
24
|
-
const validHexColorRegex = /^#[0-9a-fA-F]{6}$/;
|
|
25
|
-
|
|
26
|
-
expect(validHexColorRegex.test(label.color)).to.be.true;
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
});
|