@opentermsarchive/engine 1.0.0 → 1.1.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/.eslintrc.yaml +1 -0
- package/package.json +3 -3
- package/src/archivist/index.js +8 -5
- package/src/archivist/index.test.js +74 -0
- package/src/index.js +12 -4
- package/src/logger/index.js +4 -0
- package/src/reporter/github.js +1 -1
- package/src/reporter/github.test.js +473 -0
package/.eslintrc.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentermsarchive/engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Tracks and makes visible changes to the terms of online services",
|
|
5
|
-
"homepage": "https://
|
|
5
|
+
"homepage": "https://opentermsarchive.org",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/OpenTermsArchive/engine/issues"
|
|
8
8
|
},
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"url": "git+https://github.com/OpenTermsArchive/engine.git"
|
|
12
12
|
},
|
|
13
13
|
"license": "EUPL-1.2",
|
|
14
|
-
"author": "
|
|
14
|
+
"author": "Open Terms Archive contributors",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": "./src/exports.js",
|
package/src/archivist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export const EVENTS = [
|
|
|
29
29
|
'trackingCompleted',
|
|
30
30
|
'inaccessibleContent',
|
|
31
31
|
'error',
|
|
32
|
+
'pluginError',
|
|
32
33
|
];
|
|
33
34
|
|
|
34
35
|
export default class Archivist extends events.EventEmitter {
|
|
@@ -72,10 +73,6 @@ export default class Archivist extends events.EventEmitter {
|
|
|
72
73
|
initQueue() {
|
|
73
74
|
this.trackingQueue = async.queue(this.trackTermsChanges.bind(this), MAX_PARALLEL_TRACKING);
|
|
74
75
|
this.trackingQueue.error(async (error, { terms }) => {
|
|
75
|
-
if (error.toString().includes('HttpError: API rate limit exceeded for user ID')) {
|
|
76
|
-
return; // This is an error due to SendInBlue quota, bypass
|
|
77
|
-
}
|
|
78
|
-
|
|
79
76
|
if (error instanceof InaccessibleContentError) {
|
|
80
77
|
this.emit('inaccessibleContent', error, terms);
|
|
81
78
|
|
|
@@ -91,7 +88,13 @@ export default class Archivist extends events.EventEmitter {
|
|
|
91
88
|
const handlerName = `on${event[0].toUpperCase()}${event.substring(1)}`;
|
|
92
89
|
|
|
93
90
|
if (listener[handlerName]) {
|
|
94
|
-
this.on(event,
|
|
91
|
+
this.on(event, async (...params) => {
|
|
92
|
+
try {
|
|
93
|
+
await listener[handlerName](...params); // Prefer try...catch over .catch() for handling errors to account for both synchronous and asynchronous functions, as .catch() cannot be applied to synchronous functions
|
|
94
|
+
} catch (error) {
|
|
95
|
+
this.emit('pluginError', error, listener.constructor.name);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
95
98
|
}
|
|
96
99
|
});
|
|
97
100
|
}
|
|
@@ -245,6 +245,80 @@ describe('Archivist', function () {
|
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
247
|
|
|
248
|
+
describe('Plugin system', () => {
|
|
249
|
+
const plugin = {};
|
|
250
|
+
|
|
251
|
+
describe('#attach', () => {
|
|
252
|
+
before(async () => {
|
|
253
|
+
app = new Archivist({
|
|
254
|
+
recorderConfig: config.get('recorder'),
|
|
255
|
+
fetcherConfig: config.get('fetcher'),
|
|
256
|
+
});
|
|
257
|
+
await app.initialize();
|
|
258
|
+
|
|
259
|
+
EVENTS.forEach(event => {
|
|
260
|
+
const handlerName = `on${event[0].toUpperCase()}${event.substring(1)}`;
|
|
261
|
+
|
|
262
|
+
plugin[handlerName] = sinon.spy();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
app.removeAllListeners('error');
|
|
266
|
+
expect(app.eventNames()).to.be.empty;
|
|
267
|
+
app.attach(plugin);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
EVENTS.forEach(event => {
|
|
271
|
+
const handlerName = `on${event[0].toUpperCase()}${event.substring(1)}`;
|
|
272
|
+
|
|
273
|
+
it(`attaches plugin "${event}" handler`, () => {
|
|
274
|
+
app.emit(event);
|
|
275
|
+
expect(plugin[handlerName].calledOnce).to.be.true;
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
context('when errors occur within a plugin', () => {
|
|
281
|
+
let error;
|
|
282
|
+
let listeners;
|
|
283
|
+
let plugin;
|
|
284
|
+
|
|
285
|
+
before(async () => {
|
|
286
|
+
nock.cleanAll();
|
|
287
|
+
nock('https://www.servicea.example').get('/tos').reply(200, serviceASnapshotExpectedContent, { 'Content-Type': 'text/html' });
|
|
288
|
+
|
|
289
|
+
app = new Archivist({
|
|
290
|
+
recorderConfig: config.get('recorder'),
|
|
291
|
+
fetcherConfig: config.get('fetcher'),
|
|
292
|
+
});
|
|
293
|
+
await app.initialize();
|
|
294
|
+
|
|
295
|
+
plugin = { onFirstVersionRecorded: () => { throw new Error('Plugin error'); } };
|
|
296
|
+
|
|
297
|
+
listeners = process.listeners('unhandledRejection'); // back up listeners
|
|
298
|
+
process.removeAllListeners('unhandledRejection'); // remove all listeners to avoid exit the process
|
|
299
|
+
|
|
300
|
+
process.on('unhandledRejection', reason => { error = reason; });
|
|
301
|
+
|
|
302
|
+
app.attach(plugin);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
after(async () => {
|
|
306
|
+
process.removeAllListeners('unhandledRejection');
|
|
307
|
+
listeners.forEach(listener => process.on('unhandledRejection', listener));
|
|
308
|
+
await resetGitRepositories();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('does not crash the tracking process', done => {
|
|
312
|
+
app.track({ services: [services[0]] }).then(() => {
|
|
313
|
+
if (error) {
|
|
314
|
+
return done(error);
|
|
315
|
+
}
|
|
316
|
+
done();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
248
322
|
describe('events', () => {
|
|
249
323
|
const spies = {};
|
|
250
324
|
|
package/src/index.js
CHANGED
|
@@ -39,17 +39,25 @@ export default async function track({ services, types, extractOnly, schedule })
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
if (process.env.SENDINBLUE_API_KEY) {
|
|
42
|
-
|
|
42
|
+
try {
|
|
43
|
+
archivist.attach(new Notifier(archivist.services));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
logger.error('Cannot instantiate the Notifier module; it will be ignored:', error);
|
|
46
|
+
}
|
|
43
47
|
} else {
|
|
44
48
|
logger.warn('Environment variable "SENDINBLUE_API_KEY" was not found; the Notifier module will be ignored');
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
if (process.env.GITHUB_TOKEN) {
|
|
48
52
|
if (config.has('reporter.githubIssues.repositories.declarations')) {
|
|
49
|
-
|
|
53
|
+
try {
|
|
54
|
+
const reporter = new Reporter(config.get('reporter'));
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
await reporter.initialize();
|
|
57
|
+
archivist.attach(reporter);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error('Cannot instantiate the Reporter module; it will be ignored:', error);
|
|
60
|
+
}
|
|
53
61
|
} else {
|
|
54
62
|
logger.warn('Configuration key "reporter.githubIssues.repositories.declarations" was not found; issues on the declarations repository cannot be created');
|
|
55
63
|
}
|
package/src/logger/index.js
CHANGED
|
@@ -136,4 +136,8 @@ logger.onError = (error, terms) => {
|
|
|
136
136
|
logger.error({ message: error.stack, serviceId: terms.serviceId, termsType: terms.type });
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
+
logger.onPluginError = (error, pluginName) => {
|
|
140
|
+
logger.error({ message: `Error in "${pluginName}" plugin: ${error.stack}` });
|
|
141
|
+
};
|
|
142
|
+
|
|
139
143
|
export default logger;
|
package/src/reporter/github.js
CHANGED
|
@@ -178,7 +178,7 @@ export default class GitHub {
|
|
|
178
178
|
return this.createIssue({ title, description, labels: [label] });
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
if (issue.state ==
|
|
181
|
+
if (issue.state == GitHub.ISSUE_STATE_CLOSED) {
|
|
182
182
|
await this.openIssue(issue);
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import nock from 'nock';
|
|
5
|
+
|
|
6
|
+
import GitHub from './github.js';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
describe('GitHub', function () {
|
|
11
|
+
this.timeout(5000);
|
|
12
|
+
|
|
13
|
+
let MANAGED_LABELS;
|
|
14
|
+
let github;
|
|
15
|
+
|
|
16
|
+
before(() => {
|
|
17
|
+
MANAGED_LABELS = require('./labels.json');
|
|
18
|
+
github = new GitHub('owner/repo');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('#initialize', () => {
|
|
22
|
+
const scopes = [];
|
|
23
|
+
|
|
24
|
+
before(async () => {
|
|
25
|
+
const existingLabels = MANAGED_LABELS.slice(0, -2);
|
|
26
|
+
|
|
27
|
+
nock('https://api.github.com')
|
|
28
|
+
.get('/repos/owner/repo/labels')
|
|
29
|
+
.reply(200, existingLabels);
|
|
30
|
+
|
|
31
|
+
const missingLabels = MANAGED_LABELS.slice(-2);
|
|
32
|
+
|
|
33
|
+
for (const label of missingLabels) {
|
|
34
|
+
scopes.push(nock('https://api.github.com')
|
|
35
|
+
.post('/repos/owner/repo/labels', body => body.name === label.name)
|
|
36
|
+
.reply(200, label));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await github.initialize();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
after(nock.cleanAll);
|
|
43
|
+
|
|
44
|
+
it('should create missing labels', () => {
|
|
45
|
+
scopes.forEach(scope => expect(scope.isDone()).to.be.true);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('#getRepositoryLabels', () => {
|
|
50
|
+
let scope;
|
|
51
|
+
let result;
|
|
52
|
+
const LABELS = [{ name: 'bug' }, { name: 'enhancement' }];
|
|
53
|
+
|
|
54
|
+
before(async () => {
|
|
55
|
+
scope = nock('https://api.github.com')
|
|
56
|
+
.get('/repos/owner/repo/labels')
|
|
57
|
+
.reply(200, LABELS);
|
|
58
|
+
|
|
59
|
+
result = await github.getRepositoryLabels();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
after(nock.cleanAll);
|
|
63
|
+
|
|
64
|
+
it('fetches repository labels', () => {
|
|
65
|
+
expect(scope.isDone()).to.be.true;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns the repository labels', () => {
|
|
69
|
+
expect(result).to.deep.equal(LABELS);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('#createLabel', () => {
|
|
74
|
+
let scope;
|
|
75
|
+
const LABEL = { name: 'new_label', color: 'ffffff' };
|
|
76
|
+
|
|
77
|
+
before(async () => {
|
|
78
|
+
scope = nock('https://api.github.com')
|
|
79
|
+
.post('/repos/owner/repo/labels', body => body.name === LABEL.name)
|
|
80
|
+
.reply(200, LABEL);
|
|
81
|
+
|
|
82
|
+
await github.createLabel(LABEL);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
after(nock.cleanAll);
|
|
86
|
+
|
|
87
|
+
it('creates the new label', () => {
|
|
88
|
+
expect(scope.isDone()).to.be.true;
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('#createIssue', () => {
|
|
93
|
+
let scope;
|
|
94
|
+
let result;
|
|
95
|
+
const ISSUE = {
|
|
96
|
+
title: 'New Issue',
|
|
97
|
+
description: 'Description of the new issue',
|
|
98
|
+
labels: ['bug'],
|
|
99
|
+
};
|
|
100
|
+
const CREATED_ISSUE = { number: 123, ...ISSUE };
|
|
101
|
+
|
|
102
|
+
before(async () => {
|
|
103
|
+
scope = nock('https://api.github.com')
|
|
104
|
+
.post('/repos/owner/repo/issues', request => request.title === ISSUE.title && request.body === ISSUE.description && request.labels[0] === ISSUE.labels[0])
|
|
105
|
+
.reply(200, CREATED_ISSUE);
|
|
106
|
+
|
|
107
|
+
result = await github.createIssue(ISSUE);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
after(nock.cleanAll);
|
|
111
|
+
|
|
112
|
+
it('creates the new issue', () => {
|
|
113
|
+
expect(scope.isDone()).to.be.true;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('returns the created issue', () => {
|
|
117
|
+
expect(result).to.deep.equal(CREATED_ISSUE);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('#setIssueLabels', () => {
|
|
122
|
+
let scope;
|
|
123
|
+
const ISSUE_NUMBER = 123;
|
|
124
|
+
const LABELS = [ 'bug', 'enhancement' ];
|
|
125
|
+
|
|
126
|
+
before(async () => {
|
|
127
|
+
scope = nock('https://api.github.com')
|
|
128
|
+
.put(`/repos/owner/repo/issues/${ISSUE_NUMBER}/labels`, { labels: LABELS })
|
|
129
|
+
.reply(200);
|
|
130
|
+
|
|
131
|
+
await github.setIssueLabels({ issue: { number: ISSUE_NUMBER }, labels: LABELS });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
after(nock.cleanAll);
|
|
135
|
+
|
|
136
|
+
it('sets labels on the issue', () => {
|
|
137
|
+
expect(scope.isDone()).to.be.true;
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('#openIssue', () => {
|
|
142
|
+
let scope;
|
|
143
|
+
const ISSUE = { number: 123 };
|
|
144
|
+
const EXPECTED_REQUEST_BODY = { state: 'open' };
|
|
145
|
+
|
|
146
|
+
before(async () => {
|
|
147
|
+
scope = nock('https://api.github.com')
|
|
148
|
+
.patch(`/repos/owner/repo/issues/${ISSUE.number}`, EXPECTED_REQUEST_BODY)
|
|
149
|
+
.reply(200);
|
|
150
|
+
|
|
151
|
+
await github.openIssue(ISSUE);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
after(nock.cleanAll);
|
|
155
|
+
|
|
156
|
+
it('opens the issue', () => {
|
|
157
|
+
expect(scope.isDone()).to.be.true;
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('#closeIssue', () => {
|
|
162
|
+
let scope;
|
|
163
|
+
const ISSUE = { number: 123 };
|
|
164
|
+
const EXPECTED_REQUEST_BODY = { state: 'closed' };
|
|
165
|
+
|
|
166
|
+
before(async () => {
|
|
167
|
+
scope = nock('https://api.github.com')
|
|
168
|
+
.patch(`/repos/owner/repo/issues/${ISSUE.number}`, EXPECTED_REQUEST_BODY)
|
|
169
|
+
.reply(200);
|
|
170
|
+
|
|
171
|
+
await github.closeIssue(ISSUE);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
after(nock.cleanAll);
|
|
175
|
+
|
|
176
|
+
it('closes the issue', () => {
|
|
177
|
+
expect(scope.isDone()).to.be.true;
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('#getIssue', () => {
|
|
182
|
+
let scope;
|
|
183
|
+
let result;
|
|
184
|
+
|
|
185
|
+
const ISSUE = { number: 123, title: 'Test Issue' };
|
|
186
|
+
const ANOTHER_ISSUE = { number: 124, title: 'Test Issue 2' };
|
|
187
|
+
|
|
188
|
+
before(async () => {
|
|
189
|
+
scope = nock('https://api.github.com')
|
|
190
|
+
.get('/repos/owner/repo/issues')
|
|
191
|
+
.query(true)
|
|
192
|
+
.reply(200, [ ISSUE, ANOTHER_ISSUE ]);
|
|
193
|
+
|
|
194
|
+
result = await github.getIssue({ title: ISSUE.title });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
after(nock.cleanAll);
|
|
198
|
+
|
|
199
|
+
it('searches for the issue', () => {
|
|
200
|
+
expect(scope.isDone()).to.be.true;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('returns the expected issue', () => {
|
|
204
|
+
expect(result).to.deep.equal(ISSUE);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('#addCommentToIssue', () => {
|
|
209
|
+
let scope;
|
|
210
|
+
const ISSUE_NUMBER = 123;
|
|
211
|
+
const COMMENT = 'Test comment';
|
|
212
|
+
|
|
213
|
+
before(async () => {
|
|
214
|
+
scope = nock('https://api.github.com')
|
|
215
|
+
.post(`/repos/owner/repo/issues/${ISSUE_NUMBER}/comments`, { body: COMMENT })
|
|
216
|
+
.reply(200);
|
|
217
|
+
|
|
218
|
+
await github.addCommentToIssue({ issue: { number: ISSUE_NUMBER }, comment: COMMENT });
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
after(nock.cleanAll);
|
|
222
|
+
|
|
223
|
+
it('adds the comment to the issue', () => {
|
|
224
|
+
expect(scope.isDone()).to.be.true;
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('#closeIssueWithCommentIfExists', () => {
|
|
229
|
+
after(nock.cleanAll);
|
|
230
|
+
|
|
231
|
+
context('when the issue exists and is open', () => {
|
|
232
|
+
const ISSUE = {
|
|
233
|
+
number: 123,
|
|
234
|
+
title: 'Open Issue',
|
|
235
|
+
state: GitHub.ISSUE_STATE_OPEN,
|
|
236
|
+
};
|
|
237
|
+
let addCommentScope;
|
|
238
|
+
let closeIssueScope;
|
|
239
|
+
|
|
240
|
+
before(async () => {
|
|
241
|
+
nock('https://api.github.com')
|
|
242
|
+
.get('/repos/owner/repo/issues')
|
|
243
|
+
.query(true)
|
|
244
|
+
.reply(200, [ISSUE]);
|
|
245
|
+
|
|
246
|
+
addCommentScope = nock('https://api.github.com')
|
|
247
|
+
.post(`/repos/owner/repo/issues/${ISSUE.number}/comments`)
|
|
248
|
+
.reply(200);
|
|
249
|
+
|
|
250
|
+
closeIssueScope = nock('https://api.github.com')
|
|
251
|
+
.patch(`/repos/owner/repo/issues/${ISSUE.number}`, { state: GitHub.ISSUE_STATE_CLOSED })
|
|
252
|
+
.reply(200);
|
|
253
|
+
|
|
254
|
+
await github.closeIssueWithCommentIfExists({ title: ISSUE.title, comment: 'Closing comment' });
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('adds comment to the issue', () => {
|
|
258
|
+
expect(addCommentScope.isDone()).to.be.true;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('closes the issue', () => {
|
|
262
|
+
expect(closeIssueScope.isDone()).to.be.true;
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
context('when the issue exists and is closed', () => {
|
|
267
|
+
const ISSUE = {
|
|
268
|
+
number: 123,
|
|
269
|
+
title: 'Closed Issue',
|
|
270
|
+
state: GitHub.ISSUE_STATE_CLOSED,
|
|
271
|
+
};
|
|
272
|
+
let addCommentScope;
|
|
273
|
+
let closeIssueScope;
|
|
274
|
+
|
|
275
|
+
before(async () => {
|
|
276
|
+
nock('https://api.github.com')
|
|
277
|
+
.get('/repos/owner/repo/issues')
|
|
278
|
+
.query(true)
|
|
279
|
+
.reply(200, []);
|
|
280
|
+
|
|
281
|
+
addCommentScope = nock('https://api.github.com')
|
|
282
|
+
.post(`/repos/owner/repo/issues/${ISSUE.number}/comments`)
|
|
283
|
+
.reply(200);
|
|
284
|
+
|
|
285
|
+
closeIssueScope = nock('https://api.github.com')
|
|
286
|
+
.patch(`/repos/owner/repo/issues/${ISSUE.number}`, { state: GitHub.ISSUE_STATE_CLOSED })
|
|
287
|
+
.reply(200);
|
|
288
|
+
|
|
289
|
+
await github.closeIssueWithCommentIfExists({ title: ISSUE.title, comment: 'Closing comment' });
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('does not add comment', () => {
|
|
293
|
+
expect(addCommentScope.isDone()).to.be.false;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('does not attempt to close the issue', () => {
|
|
297
|
+
expect(closeIssueScope.isDone()).to.be.false;
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
context('when the issue does not exist', () => {
|
|
302
|
+
let addCommentScope;
|
|
303
|
+
let closeIssueScope;
|
|
304
|
+
|
|
305
|
+
before(async () => {
|
|
306
|
+
nock('https://api.github.com')
|
|
307
|
+
.get('/repos/owner/repo/issues')
|
|
308
|
+
.query(true)
|
|
309
|
+
.reply(200, []);
|
|
310
|
+
|
|
311
|
+
addCommentScope = nock('https://api.github.com')
|
|
312
|
+
.post(/\/repos\/owner\/repo\/issues\/\d+\/comments/)
|
|
313
|
+
.reply(200);
|
|
314
|
+
|
|
315
|
+
closeIssueScope = nock('https://api.github.com')
|
|
316
|
+
.patch(/\/repos\/owner\/repo\/issues\/\d+/, { state: GitHub.ISSUE_STATE_CLOSED })
|
|
317
|
+
.reply(200);
|
|
318
|
+
|
|
319
|
+
await github.closeIssueWithCommentIfExists({ title: 'Non-existent Issue', comment: 'Closing comment' });
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('does not attempt to add comment', () => {
|
|
323
|
+
expect(addCommentScope.isDone()).to.be.false;
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('does not attempt to close the issue', () => {
|
|
327
|
+
expect(closeIssueScope.isDone()).to.be.false;
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('#createOrUpdateIssue', () => {
|
|
333
|
+
before(async () => {
|
|
334
|
+
nock('https://api.github.com')
|
|
335
|
+
.get('/repos/owner/repo/labels')
|
|
336
|
+
.reply(200, MANAGED_LABELS);
|
|
337
|
+
|
|
338
|
+
await github.initialize();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
context('when the issue does not exist', () => {
|
|
342
|
+
let createIssueScope;
|
|
343
|
+
const ISSUE_TO_CREATE = {
|
|
344
|
+
title: 'New Issue',
|
|
345
|
+
description: 'Description of the new issue',
|
|
346
|
+
label: 'bug',
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
before(async () => {
|
|
350
|
+
nock('https://api.github.com')
|
|
351
|
+
.get('/repos/owner/repo/issues')
|
|
352
|
+
.query(true)
|
|
353
|
+
.reply(200, []); // Simulate that there is no issues on the repository
|
|
354
|
+
|
|
355
|
+
createIssueScope = nock('https://api.github.com')
|
|
356
|
+
.post('/repos/owner/repo/issues', {
|
|
357
|
+
title: ISSUE_TO_CREATE.title,
|
|
358
|
+
body: ISSUE_TO_CREATE.description,
|
|
359
|
+
labels: [ISSUE_TO_CREATE.label],
|
|
360
|
+
})
|
|
361
|
+
.reply(200, { number: 123 });
|
|
362
|
+
|
|
363
|
+
await github.createOrUpdateIssue(ISSUE_TO_CREATE);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('creates the issue', () => {
|
|
367
|
+
expect(createIssueScope.isDone()).to.be.true;
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
context('when the issue already exists', () => {
|
|
372
|
+
const ISSUE = {
|
|
373
|
+
title: 'Existing Issue',
|
|
374
|
+
description: 'New comment',
|
|
375
|
+
label: 'location',
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
context('when issue is closed', () => {
|
|
379
|
+
let setIssueLabelsScope;
|
|
380
|
+
let addCommentScope;
|
|
381
|
+
let openIssueScope;
|
|
382
|
+
|
|
383
|
+
const GITHUB_RESPONSE_FOR_EXISTING_ISSUE = {
|
|
384
|
+
number: 123,
|
|
385
|
+
title: ISSUE.title,
|
|
386
|
+
description: ISSUE.description,
|
|
387
|
+
labels: [{ name: 'selectors' }],
|
|
388
|
+
state: GitHub.ISSUE_STATE_CLOSED,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
before(async () => {
|
|
392
|
+
nock('https://api.github.com')
|
|
393
|
+
.get('/repos/owner/repo/issues')
|
|
394
|
+
.query(true)
|
|
395
|
+
.reply(200, [GITHUB_RESPONSE_FOR_EXISTING_ISSUE]);
|
|
396
|
+
|
|
397
|
+
openIssueScope = nock('https://api.github.com')
|
|
398
|
+
.patch(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN })
|
|
399
|
+
.reply(200);
|
|
400
|
+
|
|
401
|
+
setIssueLabelsScope = nock('https://api.github.com')
|
|
402
|
+
.put(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}/labels`, { labels: ['location'] })
|
|
403
|
+
.reply(200);
|
|
404
|
+
|
|
405
|
+
addCommentScope = nock('https://api.github.com')
|
|
406
|
+
.post(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}/comments`, { body: ISSUE.description })
|
|
407
|
+
.reply(200);
|
|
408
|
+
|
|
409
|
+
await github.createOrUpdateIssue(ISSUE);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('reopens the issue', () => {
|
|
413
|
+
expect(openIssueScope.isDone()).to.be.true;
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("updates the issue's label", () => {
|
|
417
|
+
expect(setIssueLabelsScope.isDone()).to.be.true;
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('adds comment to the issue', () => {
|
|
421
|
+
expect(addCommentScope.isDone()).to.be.true;
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
context('when issue is already opened', () => {
|
|
426
|
+
let setIssueLabelsScope;
|
|
427
|
+
let addCommentScope;
|
|
428
|
+
let openIssueScope;
|
|
429
|
+
|
|
430
|
+
const GITHUB_RESPONSE_FOR_EXISTING_ISSUE = {
|
|
431
|
+
number: 123,
|
|
432
|
+
title: ISSUE.title,
|
|
433
|
+
description: ISSUE.description,
|
|
434
|
+
labels: [{ name: 'selectors' }],
|
|
435
|
+
state: GitHub.ISSUE_STATE_OPEN,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
before(async () => {
|
|
439
|
+
nock('https://api.github.com')
|
|
440
|
+
.get('/repos/owner/repo/issues')
|
|
441
|
+
.query(true)
|
|
442
|
+
.reply(200, [GITHUB_RESPONSE_FOR_EXISTING_ISSUE]);
|
|
443
|
+
|
|
444
|
+
openIssueScope = nock('https://api.github.com')
|
|
445
|
+
.patch(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN })
|
|
446
|
+
.reply(200);
|
|
447
|
+
|
|
448
|
+
setIssueLabelsScope = nock('https://api.github.com')
|
|
449
|
+
.put(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}/labels`, { labels: ['location'] })
|
|
450
|
+
.reply(200);
|
|
451
|
+
|
|
452
|
+
addCommentScope = nock('https://api.github.com')
|
|
453
|
+
.post(`/repos/owner/repo/issues/${GITHUB_RESPONSE_FOR_EXISTING_ISSUE.number}/comments`, { body: ISSUE.description })
|
|
454
|
+
.reply(200);
|
|
455
|
+
|
|
456
|
+
await github.createOrUpdateIssue(ISSUE);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('does not change the issue state', () => {
|
|
460
|
+
expect(openIssueScope.isDone()).to.be.false;
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("updates the issue's label", () => {
|
|
464
|
+
expect(setIssueLabelsScope.isDone()).to.be.true;
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('adds comment to the issue', () => {
|
|
468
|
+
expect(addCommentScope.isDone()).to.be.true;
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
});
|