@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentermsarchive/engine",
3
- "version": "5.6.1",
3
+ "version": "6.0.0",
4
4
  "description": "Tracks and makes visible changes to the terms of online services",
5
5
  "homepage": "https://opentermsarchive.org",
6
6
  "bugs": {
@@ -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 = require('./labels.json');
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, label }) {
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: [label] });
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 [managedLabel] = issue.labels.filter(label => managedLabelsNames.includes(label.name)); // It is assumed that only one specific reason for failure is possible at a time, making managed labels mutually exclusive
206
+ const managedLabels = issue.labels.filter(label => managedLabelsNames.includes(label.name));
190
207
 
191
- if (issue.state !== GitHub.ISSUE_STATE_CLOSED && managedLabel?.name === label) {
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: [ label, ...labelsNotManagedToKeep ] });
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 GitHub from './index.js';
4
+ import { LABELS } from '../labels.js';
7
5
 
8
- const require = createRequire(import.meta.url);
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: 'location' }] };
16
- const EXISTING_CLOSED_ISSUE = { number: 2, title: 'Closed issue', description: 'Issue description', state: GitHub.ISSUE_STATE_CLOSED, labels: [{ name: '403' }] };
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 = require('./labels.json');
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 = require('./labels.json');
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
- label: 'bug',
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: [ISSUE_TO_CREATE.label],
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: ['location'] })
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, label: 'location' });
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: ['404'] })
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, label: '404' });
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
- context('when the reason did not change', () => {
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}`, { state: GitHub.ISSUE_STATE_OPEN, labels: EXISTING_OPEN_ISSUE.labels })
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`, { body: EXISTING_OPEN_ISSUE.description })
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, label: EXISTING_OPEN_ISSUE.labels[0].name });
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 = require('./labels.json');
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
- const existingLabels = await this.getRepositoryLabels();
53
- const existingLabelsNames = existingLabels.map(label => label.name);
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
- if (missingLabels.length) {
57
- logger.info(`The following required labels are not present on the repository: ${missingLabels.map(label => `"${label.name}"`).join(', ')}. Creating them…`);
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
- for (const label of missingLabels) {
60
- await this.createLabel({ /* eslint-disable-line no-await-in-loop */
61
- name: label.name,
62
- color: label.color,
63
- description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
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, label }) {
322
- const issue = await this.getIssue({ title, state: GitLab.ISSUE_STATE_ALL });
353
+ async createOrUpdateIssue({ title, description, labels }) {
354
+ try {
355
+ const issue = await this.getIssue({ title, state: GitLab.ISSUE_STATE_ALL });
323
356
 
324
- if (!issue) {
325
- return this.createIssue({ title, description, labels: [label] });
326
- }
357
+ if (!issue) {
358
+ const createdIssue = await this.createIssue({ title, description, labels });
327
359
 
328
- if (issue.state == GitLab.ISSUE_STATE_CLOSED) {
329
- await this.openIssue(issue);
330
- }
360
+ return logger.info(`Created GitLab issue #${createdIssue.iid} "${title}": ${createdIssue.web_url}`);
361
+ }
331
362
 
332
- const managedLabelsNames = this.MANAGED_LABELS.map(label => label.name);
333
- const [managedLabel] = issue.labels.filter(label =>
334
- managedLabelsNames.includes(label.name)); // it is assumed that only one specific reason for failure is possible at a time, making managed labels mutually exclusive
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
- if (managedLabel?.name == label) {
337
- // if the label is already assigned to the issue, the error is redundant with the one already reported and no further action is necessary
338
- return;
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
- const labelsNotManagedToKeep = issue.labels
342
- .map(label => label.name)
343
- .filter(label => !managedLabelsNames.includes(label));
371
+ if (issue.state == GitLab.ISSUE_STATE_CLOSED) {
372
+ await this.openIssue(issue);
373
+ }
344
374
 
345
- await this.setIssueLabels({
346
- issue,
347
- labels: [ label, ...labelsNotManagedToKeep ],
348
- });
349
- await this.addCommentToIssue({ issue, comment: description });
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(serviceName, termsType)}.md`;
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 GitLab from './index.js';
4
+ import { LABELS } from '../labels.js';
7
5
 
8
- const require = createRequire(import.meta.url);
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 = require('./labels.json');
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
- label: 'bug',
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: [ISSUE_TO_CREATE.label],
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
- label: 'location',
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: 'selectors' }],
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: ['location'],
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: ['location'] })
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
- const GITLAB_RESPONSE_FOR_EXISTING_ISSUE = {
503
- number: 123,
504
- title: ISSUE.title,
505
- description: ISSUE.description,
506
- labels: [{ name: 'selectors' }],
507
- state: GitLab.ISSUE_STATE_OPEN,
508
- };
509
-
510
- const EXPECTED_REQUEST_BODY = { state_event: 'reopen' };
511
- const responseIssuereopened = { iid: 123 };
512
- const responseSetLabels = {
513
- iid: 123,
514
- labels: ['location'],
515
- };
516
- const responseAddcomment = { iid: 123, id: 23, body: ISSUE.description };
517
- const { iid } = GITLAB_RESPONSE_FOR_EXISTING_ISSUE;
518
-
519
- before(async () => {
520
- nock(gitlab.apiBaseURL)
521
- .get(`/projects/${PROJECT_ID}/issues?search=${encodeURIComponent(ISSUE.title)}&per_page=100`)
522
- .reply(200, [GITLAB_RESPONSE_FOR_EXISTING_ISSUE]);
523
-
524
- openIssueScope = nock(gitlab.apiBaseURL)
525
- .put(`/projects/${PROJECT_ID}/issues/${iid}`, EXPECTED_REQUEST_BODY)
526
- .reply(200, responseIssuereopened);
527
-
528
- setIssueLabelsScope = nock(gitlab.apiBaseURL)
529
- .put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: ['location'] })
530
- .reply(200, responseSetLabels);
531
-
532
- addCommentScope = nock(gitlab.apiBaseURL)
533
- .post(`/projects/${PROJECT_ID}/issues/${iid}/notes`, { body: ISSUE.description })
534
- .reply(200, responseAddcomment);
535
-
536
- await gitlab.createOrUpdateIssue(ISSUE);
537
- });
538
-
539
- it('does not change the issue state', () => {
540
- expect(openIssueScope.isDone()).to.be.false;
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
- it("updates the issue's label", () => {
544
- expect(setIssueLabelsScope.isDone()).to.be.true;
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
- it('adds comment to the issue', () => {
548
- expect(addCommentScope.isDone()).to.be.true;
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
  });
@@ -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 ERROR_MESSAGE_TO_ISSUE_LABEL_MAP = {
12
- 'has no match': 'selectors',
13
- 'HTTP code 404': 'location',
14
- 'HTTP code 403': '403',
15
- 'HTTP code 429': '429',
16
- 'HTTP code 500': '500',
17
- 'HTTP code 502': '502',
18
- 'HTTP code 503': '503',
19
- 'Timed out after': 'timeout',
20
- EAI_AGAIN: 'EAI_AGAIN',
21
- ENOTFOUND: 'ENOTFOUND',
22
- 'Response is empty': 'empty response',
23
- 'unable to verify the first certificate': 'first certificate',
24
- 'certificate has expired': 'certificate expired',
25
- 'maximum redirect reached': 'redirects',
26
- 'not a valid selector': 'invalid selector',
27
- 'empty content': '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 getLabelNameFromError(error) {
31
- return ERROR_MESSAGE_TO_ISSUE_LABEL_MAP[Object.keys(ERROR_MESSAGE_TO_ISSUE_LABEL_MAP).find(substring => error.toString().includes(substring))] || 'to clarify';
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
- label: getLabelNameFromError(error),
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
- });