release-it 19.0.0-next.1 → 19.0.0-next.3

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/test/github.js CHANGED
@@ -1,5 +1,5 @@
1
- import test from 'ava';
2
- import sinon from 'sinon';
1
+ import test, { describe, before, after, afterEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
3
  import { RequestError } from '@octokit/request-error';
4
4
  import GitHub from '../lib/plugin/github/GitHub.js';
5
5
  import { getSearchQueries } from '../lib/plugin/github/util.js';
@@ -12,536 +12,619 @@ import {
12
12
  interceptUpdate,
13
13
  interceptAsset
14
14
  } from './stub/github.js';
15
+ import { mockFetch } from './util/mock.js';
16
+
17
+ describe('github', () => {
18
+ const tokenRef = 'GITHUB_TOKEN';
19
+ const pushRepo = 'git://github.com/user/repo';
20
+ const host = 'github.com';
21
+ const git = { changelog: '' };
22
+ const requestErrorOptions = { request: { url: '', headers: {} }, response: { headers: {} } };
23
+
24
+ const [mocker, api, assets, example, custom] = mockFetch([
25
+ 'https://api.github.com',
26
+ 'https://uploads.github.com',
27
+ 'https://github.example.org/api/v3',
28
+ 'https://custom.example.org/api/v3'
29
+ ]);
30
+
31
+ before(() => {
32
+ mocker.mockGlobal();
33
+ });
15
34
 
16
- const tokenRef = 'GITHUB_TOKEN';
17
- const pushRepo = 'git://github.com/user/repo';
18
- const host = 'github.com';
19
- const git = { changelog: '' };
20
- const requestErrorOptions = { request: { url: '', headers: {} }, response: { headers: {} } };
35
+ afterEach(() => {
36
+ mocker.clearAll();
37
+ });
21
38
 
22
- test.serial('should check token and perform checks', async t => {
23
- const tokenRef = 'MY_GITHUB_TOKEN';
24
- const options = { github: { release: true, tokenRef, pushRepo } };
25
- const github = factory(GitHub, { options });
39
+ after(() => {
40
+ mocker.unmockGlobal();
41
+ });
26
42
 
27
- process.env[tokenRef] = '123';
43
+ test('should check token and perform checks', async () => {
44
+ const tokenRef = 'MY_GITHUB_TOKEN';
45
+ const options = { github: { release: true, tokenRef, pushRepo } };
46
+ const github = factory(GitHub, { options });
28
47
 
29
- interceptAuthentication();
30
- interceptCollaborator();
31
- await t.notThrowsAsync(github.init());
32
- });
48
+ interceptAuthentication(api);
49
+ interceptCollaborator(api);
50
+ await assert.doesNotReject(github.init());
51
+ });
33
52
 
34
- test.serial('should check token and warn', async t => {
35
- const tokenRef = 'MY_GITHUB_TOKEN';
36
- const options = { github: { release: true, tokenRef, pushRepo } };
37
- const github = factory(GitHub, { options });
38
- delete process.env[tokenRef];
53
+ test('should check token and warn', async () => {
54
+ const tokenRef = 'MY_GITHUB_TOKEN';
55
+ const options = { github: { release: true, tokenRef, pushRepo } };
56
+ const github = factory(GitHub, { options });
57
+ delete process.env[tokenRef];
39
58
 
40
- await t.notThrowsAsync(github.init());
59
+ await assert.doesNotReject(github.init());
41
60
 
42
- t.is(github.log.warn.args[0][0], 'Environment variable "MY_GITHUB_TOKEN" is required for automated GitHub Releases.');
43
- t.is(github.log.warn.args[1][0], 'Falling back to web-based GitHub Release.');
44
- });
61
+ assert.equal(
62
+ github.log.warn.mock.calls[0].arguments[0],
63
+ 'Environment variable "MY_GITHUB_TOKEN" is required for automated GitHub Releases.'
64
+ );
65
+ assert.equal(github.log.warn.mock.calls[1].arguments[0], 'Falling back to web-based GitHub Release.');
66
+ });
45
67
 
46
- test('should release and upload assets', async t => {
47
- const options = {
48
- git,
49
- github: {
50
- pushRepo,
51
- tokenRef,
52
- release: true,
53
- releaseName: 'Release ${tagName}',
54
- releaseNotes: 'echo Custom notes',
55
- assets: 'test/resources/file-v${version}.txt'
56
- }
57
- };
58
- const github = factory(GitHub, { options });
59
- const exec = sinon.stub(github.shell, 'exec').callThrough();
60
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
61
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
62
-
63
- interceptAuthentication();
64
- interceptCollaborator();
65
- interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', body: 'Custom notes' } });
66
- interceptAsset({ body: '*' });
67
-
68
- await runTasks(github);
69
-
70
- const { isReleased, releaseUrl } = github.getContext();
71
- t.true(isReleased);
72
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
73
- exec.restore();
74
- });
68
+ test('should release and upload assets', async t => {
69
+ const options = {
70
+ git,
71
+ github: {
72
+ pushRepo,
73
+ tokenRef,
74
+ release: true,
75
+ releaseName: 'Release ${tagName}',
76
+ releaseNotes: 'echo Custom notes',
77
+ assets: 'test/resources/file-v${version}.txt'
78
+ }
79
+ };
80
+ const github = factory(GitHub, { options });
81
+
82
+ const original = github.shell.exec.bind(github.shell);
83
+ t.mock.method(github.shell, 'exec', (...args) => {
84
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
85
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
86
+ return original(...args);
87
+ });
88
+
89
+ interceptAuthentication(api);
90
+ interceptCollaborator(api);
91
+ interceptCreate(api, { body: { tag_name: '2.0.2', name: 'Release 2.0.2', body: 'Custom notes' } });
92
+ interceptAsset(assets, { body: '*' });
93
+
94
+ await runTasks(github);
95
+
96
+ const { isReleased, releaseUrl } = github.getContext();
97
+ assert(isReleased);
98
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
99
+ });
75
100
 
76
- test('should create a pre-release and draft release notes', async t => {
77
- const options = {
78
- git,
79
- github: {
80
- pushRepo,
81
- tokenRef,
82
- release: true,
83
- releaseName: 'Release ${tagName}',
84
- preRelease: true,
85
- draft: true
86
- }
87
- };
88
- const github = factory(GitHub, { options });
89
- const exec = sinon.stub(github.shell, 'exec').callThrough();
90
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
91
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
92
-
93
- interceptAuthentication();
94
- interceptCollaborator();
95
- interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', prerelease: true, draft: true } });
96
-
97
- await runTasks(github);
98
-
99
- const { isReleased, releaseUrl } = github.getContext();
100
- t.true(isReleased);
101
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
102
- exec.restore();
103
- });
101
+ test('should create a pre-release and draft release notes', async t => {
102
+ const options = {
103
+ git,
104
+ github: {
105
+ pushRepo,
106
+ tokenRef,
107
+ release: true,
108
+ releaseName: 'Release ${tagName}',
109
+ preRelease: true,
110
+ draft: true
111
+ }
112
+ };
113
+ const github = factory(GitHub, { options });
104
114
 
105
- test('should create auto-generated release notes', async t => {
106
- const options = {
107
- git,
108
- github: {
109
- pushRepo,
110
- tokenRef,
111
- release: true,
112
- releaseName: 'Release ${tagName}',
113
- autoGenerate: true
114
- }
115
- };
116
- const github = factory(GitHub, { options });
117
- const exec = sinon.stub(github.shell, 'exec').callThrough();
118
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
115
+ const original = github.shell.exec.bind(github.shell);
116
+ t.mock.method(github.shell, 'exec', (...args) => {
117
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
118
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
119
+ return original(...args);
120
+ });
119
121
 
120
- interceptAuthentication();
121
- interceptCollaborator();
122
- interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', generate_release_notes: true, body: '' } });
122
+ interceptAuthentication(api);
123
+ interceptCollaborator(api);
124
+ interceptCreate(api, { body: { tag_name: '2.0.2', name: 'Release 2.0.2', prerelease: true, draft: true } });
123
125
 
124
- await runTasks(github);
126
+ await runTasks(github);
125
127
 
126
- const { isReleased, releaseUrl } = github.getContext();
127
- t.true(isReleased);
128
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
129
- exec.restore();
130
- });
128
+ const { isReleased, releaseUrl } = github.getContext();
129
+ assert(isReleased);
130
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
131
+ });
131
132
 
132
- test('should update release and upload assets', async t => {
133
- const asset = 'file1';
134
- const options = {
135
- increment: false,
136
- git,
137
- github: {
138
- update: true,
139
- pushRepo,
140
- tokenRef,
141
- release: true,
142
- releaseName: 'Release ${tagName}',
143
- releaseNotes: 'echo Custom notes',
144
- assets: `test/resources/${asset}`
145
- }
146
- };
147
- const github = factory(GitHub, { options });
148
- const exec = sinon.stub(github.shell, 'exec').callThrough();
149
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
150
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
151
- exec.withArgs('git rev-list 2.0.1 --tags --max-count=1').resolves('a123456');
152
- exec.withArgs('git describe --tags --abbrev=0 "a123456^"').resolves('2.0.1');
153
-
154
- interceptAuthentication();
155
- interceptCollaborator();
156
- interceptListReleases({ tag_name: '2.0.1' });
157
- interceptUpdate({ body: { tag_name: '2.0.1', name: 'Release 2.0.1', body: 'Custom notes' } });
158
- interceptAsset({ body: asset });
159
-
160
- await runTasks(github);
161
-
162
- const { isReleased, releaseUrl } = github.getContext();
163
- t.true(isReleased);
164
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.1');
165
- exec.restore();
166
- });
133
+ test('should create auto-generated release notes', async t => {
134
+ const options = {
135
+ git,
136
+ github: {
137
+ pushRepo,
138
+ tokenRef,
139
+ release: true,
140
+ releaseName: 'Release ${tagName}',
141
+ autoGenerate: true
142
+ }
143
+ };
144
+ const github = factory(GitHub, { options });
145
+
146
+ const original = github.shell.exec.bind(github.shell);
147
+ t.mock.method(github.shell, 'exec', (...args) => {
148
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
149
+ return original(...args);
150
+ });
151
+
152
+ interceptAuthentication(api);
153
+ interceptCollaborator(api);
154
+ interceptCreate(api, {
155
+ body: { tag_name: '2.0.2', name: 'Release 2.0.2', generate_release_notes: true, body: '' }
156
+ });
157
+
158
+ await runTasks(github);
159
+
160
+ const { isReleased, releaseUrl } = github.getContext();
161
+ assert(isReleased);
162
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
163
+ });
167
164
 
168
- test('should create custom release notes using releaseNotes function', async t => {
169
- const options = {
170
- git,
171
- github: {
172
- pushRepo,
173
- tokenRef,
174
- release: true,
175
- releaseName: 'Release ${tagName}',
176
- releaseNotes(context) {
177
- return `Custom notes for tag ${context.tagName}`;
165
+ test('should update release and upload assets', async t => {
166
+ const asset = 'file1';
167
+ const options = {
168
+ increment: false,
169
+ git,
170
+ github: {
171
+ update: true,
172
+ pushRepo,
173
+ tokenRef,
174
+ release: true,
175
+ releaseName: 'Release ${tagName}',
176
+ releaseNotes: 'echo Custom notes',
177
+ assets: `test/resources/${asset}`
178
178
  }
179
- }
180
- };
181
- const github = factory(GitHub, { options });
182
- const exec = sinon.stub(github.shell, 'exec').callThrough();
183
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
179
+ };
180
+ const github = factory(GitHub, { options });
181
+
182
+ const original = github.shell.exec.bind(github.shell);
183
+ t.mock.method(github.shell, 'exec', (...args) => {
184
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
185
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
186
+ if (args[0] === 'git rev-list 2.0.1 --tags --max-count=1') return Promise.resolve('a123456');
187
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
188
+ return original(...args);
189
+ });
190
+
191
+ interceptAuthentication(api);
192
+ interceptCollaborator(api);
193
+ interceptListReleases(api, { tag_name: '2.0.1' });
194
+ interceptUpdate(api, { body: { tag_name: '2.0.1', name: 'Release 2.0.1', body: 'Custom notes' } });
195
+ interceptAsset(assets, { body: asset });
196
+
197
+ await runTasks(github);
198
+
199
+ const { isReleased, releaseUrl } = github.getContext();
200
+ assert(isReleased);
201
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.1');
202
+ });
184
203
 
185
- interceptAuthentication();
186
- interceptCollaborator();
187
- interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', body: 'Custom notes for tag 2.0.2' } });
204
+ test('should create custom release notes using releaseNotes function', async t => {
205
+ const options = {
206
+ git,
207
+ github: {
208
+ pushRepo,
209
+ tokenRef,
210
+ release: true,
211
+ releaseName: 'Release ${tagName}',
212
+ releaseNotes(context) {
213
+ return `Custom notes for tag ${context.tagName}`;
214
+ }
215
+ }
216
+ };
217
+ const github = factory(GitHub, { options });
218
+
219
+ const original = github.shell.exec.bind(github.shell);
220
+ t.mock.method(github.shell, 'exec', (...args) => {
221
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
222
+ return original(...args);
223
+ });
224
+
225
+ interceptAuthentication(api);
226
+ interceptCollaborator(api);
227
+ interceptCreate(api, {
228
+ body: { tag_name: '2.0.2', name: 'Release 2.0.2', body: 'Custom notes for tag 2.0.2' }
229
+ });
230
+
231
+ await runTasks(github);
232
+
233
+ const { isReleased, releaseUrl } = github.getContext();
234
+ assert(isReleased);
235
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
236
+ });
188
237
 
189
- await runTasks(github);
238
+ test('should create new release for unreleased tag', async t => {
239
+ const options = {
240
+ increment: false,
241
+ git,
242
+ github: {
243
+ update: true,
244
+ pushRepo,
245
+ tokenRef,
246
+ release: true,
247
+ releaseName: 'Release ${tagName}',
248
+ releaseNotes: 'echo Custom notes'
249
+ }
250
+ };
251
+ const github = factory(GitHub, { options });
252
+
253
+ const original = github.shell.exec.bind(github.shell);
254
+ t.mock.method(github.shell, 'exec', (...args) => {
255
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
256
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
257
+ if (args[0] === 'git rev-list 2.0.1 --tags --max-count=1') return Promise.resolve('b123456');
258
+ if (args[0] === 'git describe --tags --abbrev=0 "b123456^"') return Promise.resolve('2.0.1');
259
+ return original(...args);
260
+ });
261
+
262
+ interceptAuthentication(api);
263
+ interceptCollaborator(api);
264
+ interceptListReleases(api, { tag_name: '2.0.0' });
265
+ interceptCreate(api, { body: { tag_name: '2.0.1', name: 'Release 2.0.1', body: 'Custom notes' } });
266
+
267
+ await runTasks(github);
268
+
269
+ const { isReleased, releaseUrl } = github.getContext();
270
+ assert(isReleased);
271
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.1');
272
+ });
190
273
 
191
- const { isReleased, releaseUrl } = github.getContext();
192
- t.true(isReleased);
193
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
194
- exec.restore();
195
- });
274
+ test('should release to enterprise host', async t => {
275
+ const options = { git, github: { tokenRef, pushRepo: 'git://github.example.org/user/repo' } };
276
+ const github = factory(GitHub, { options });
277
+
278
+ const original = github.shell.exec.bind(github.shell);
279
+ t.mock.method(github.shell, 'exec', (...args) => {
280
+ if (args[0] === 'git remote get-url origin') return Promise.resolve(`https://github.example.org/user/repo`);
281
+ if (args[0] === 'git config --get remote.origin.url')
282
+ return Promise.resolve('https://github.example.org/user/repo');
283
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('1.0.0');
284
+ return original(...args);
285
+ });
286
+
287
+ interceptAuthentication(example);
288
+ interceptCollaborator(example);
289
+ interceptCreate(example, {
290
+ api: 'https://github.example.org/api/v3',
291
+ host: 'github.example.org',
292
+ body: { tag_name: '1.0.1' }
293
+ });
294
+
295
+ await runTasks(github);
296
+
297
+ const { isReleased, releaseUrl } = github.getContext();
298
+ assert(isReleased);
299
+ assert.equal(releaseUrl, 'https://github.example.org/user/repo/releases/tag/1.0.1');
300
+ });
196
301
 
197
- test('should create new release for unreleased tag', async t => {
198
- const options = {
199
- increment: false,
200
- git,
201
- github: {
202
- update: true,
203
- pushRepo,
204
- tokenRef,
205
- release: true,
206
- releaseName: 'Release ${tagName}',
207
- releaseNotes: 'echo Custom notes'
208
- }
209
- };
210
- const github = factory(GitHub, { options });
211
- const exec = sinon.stub(github.shell, 'exec').callThrough();
212
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
213
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
214
- exec.withArgs('git rev-list 2.0.1 --tags --max-count=1').resolves('b123456');
215
- exec.withArgs('git describe --tags --abbrev=0 "b123456^"').resolves('2.0.1');
216
-
217
- interceptAuthentication();
218
- interceptCollaborator();
219
- interceptListReleases({ tag_name: '2.0.0' });
220
- interceptCreate({ body: { tag_name: '2.0.1', name: 'Release 2.0.1', body: 'Custom notes' } });
221
-
222
- await runTasks(github);
223
-
224
- const { isReleased, releaseUrl } = github.getContext();
225
- t.true(isReleased);
226
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.1');
227
- exec.restore();
228
- });
302
+ test('should release to alternative host and proxy', async t => {
303
+ const options = {
304
+ git,
305
+ github: {
306
+ tokenRef,
307
+ pushRepo: `git://custom.example.org/user/repo`,
308
+ host: 'custom.example.org',
309
+ proxy: 'http://proxy:8080'
310
+ }
311
+ };
312
+ const github = factory(GitHub, { options });
313
+
314
+ const original = github.shell.exec.bind(github.shell);
315
+ t.mock.method(github.shell, 'exec', (...args) => {
316
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
317
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('1.0.0');
318
+ return original(...args);
319
+ });
320
+
321
+ interceptAuthentication(custom);
322
+ interceptCollaborator(custom);
323
+ interceptCreate(custom, {
324
+ api: 'https://custom.example.org/api/v3',
325
+ host: 'custom.example.org',
326
+ body: { tag_name: '1.0.1' }
327
+ });
229
328
 
230
- test('should release to enterprise host', async t => {
231
- const options = { git, github: { tokenRef, pushRepo: 'git://github.example.org/user/repo' } };
232
- const github = factory(GitHub, { options });
233
- const exec = sinon.stub(github.shell, 'exec').callThrough();
234
- exec.withArgs('git remote get-url origin').resolves(`https://github.example.org/user/repo`);
235
- exec.withArgs('git config --get remote.origin.url').resolves(`https://github.example.org/user/repo`);
236
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves(`1.0.0`);
237
-
238
- const remote = { api: 'https://github.example.org/api/v3', host: 'github.example.org' };
239
- interceptAuthentication(remote);
240
- interceptCollaborator(remote);
241
- interceptCreate(Object.assign({ body: { tag_name: '1.0.1' } }, remote));
242
-
243
- await runTasks(github);
244
-
245
- const { isReleased, releaseUrl } = github.getContext();
246
- t.true(isReleased);
247
- t.is(releaseUrl, `https://github.example.org/user/repo/releases/tag/1.0.1`);
248
- exec.restore();
249
- });
329
+ await runTasks(github);
330
+
331
+ const { isReleased, releaseUrl } = github.getContext();
332
+ assert(isReleased);
333
+ assert.equal(releaseUrl, 'https://custom.example.org/user/repo/releases/tag/1.0.1');
334
+ assert.equal(github.options.proxy, 'http://proxy:8080');
335
+ });
336
+
337
+ test('should release to git.pushRepo', async t => {
338
+ const options = { git: { pushRepo: 'upstream', changelog: '' }, github: { tokenRef, skipChecks: true } };
339
+ const github = factory(GitHub, { options });
250
340
 
251
- test('should release to alternative host and proxy', async t => {
252
- const remote = { api: 'https://custom.example.org/api/v3', host: 'custom.example.org' };
253
- interceptAuthentication(remote);
254
- interceptCollaborator(remote);
255
- interceptCreate(Object.assign({ body: { tag_name: '1.0.1' } }, remote));
256
- const options = {
257
- git,
258
- github: {
259
- tokenRef,
260
- pushRepo: `git://custom.example.org/user/repo`,
341
+ const original = github.shell.exec.bind(github.shell);
342
+ t.mock.method(github.shell, 'exec', (...args) => {
343
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
344
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('1.0.0');
345
+ if (args[0] === 'git remote get-url upstream') return Promise.resolve('https://custom.example.org/user/repo');
346
+ return original(...args);
347
+ });
348
+
349
+ interceptCreate(custom, {
350
+ api: 'https://custom.example.org/api/v3',
261
351
  host: 'custom.example.org',
262
- proxy: 'http://proxy:8080'
263
- }
264
- };
265
- const github = factory(GitHub, { options });
266
- const exec = sinon.stub(github.shell, 'exec').callThrough();
267
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
268
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('1.0.0');
269
-
270
- await runTasks(github);
271
-
272
- const { isReleased, releaseUrl } = github.getContext();
273
- t.true(isReleased);
274
- t.is(releaseUrl, `https://custom.example.org/user/repo/releases/tag/1.0.1`);
275
- t.is(github.options.proxy, 'http://proxy:8080');
276
- exec.restore();
277
- });
352
+ body: { tag_name: '1.0.1' }
353
+ });
278
354
 
279
- test('should release to git.pushRepo', async t => {
280
- const remote = { api: 'https://custom.example.org/api/v3', host: 'custom.example.org' };
281
- interceptCreate(Object.assign({ body: { tag_name: '1.0.1' } }, remote));
282
- const options = { git: { pushRepo: 'upstream', changelog: '' }, github: { tokenRef, skipChecks: true } };
283
- const github = factory(GitHub, { options });
284
- const exec = sinon.stub(github.shell, 'exec').callThrough();
285
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
286
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('1.0.0');
287
- exec.withArgs('git remote get-url upstream').resolves('https://custom.example.org/user/repo');
288
-
289
- await runTasks(github);
290
-
291
- const { isReleased, releaseUrl } = github.getContext();
292
- t.true(isReleased);
293
- t.is(releaseUrl, 'https://custom.example.org/user/repo/releases/tag/1.0.1');
294
- exec.restore();
295
- });
355
+ await runTasks(github);
296
356
 
297
- const testSkipOnActions = process.env.GITHUB_ACTIONS ? test.skip : test;
357
+ const { isReleased, releaseUrl } = github.getContext();
358
+ assert(isReleased);
359
+ assert.equal(releaseUrl, 'https://custom.example.org/user/repo/releases/tag/1.0.1');
360
+ });
298
361
 
299
- testSkipOnActions('should throw for unauthenticated user', async t => {
300
- const options = { github: { tokenRef, pushRepo, host } };
301
- const github = factory(GitHub, { options });
302
- const stub = sinon.stub(github.client.users, 'getAuthenticated');
303
- stub.throws(new RequestError('Bad credentials', 401, requestErrorOptions));
362
+ const testSkipOnActions = process.env.GITHUB_ACTIONS ? test.skip : test;
304
363
 
305
- await t.throwsAsync(runTasks(github), {
306
- message: /^Could not authenticate with GitHub using environment variable "GITHUB_TOKEN"/
364
+ testSkipOnActions('should throw for unauthenticated user', async t => {
365
+ const options = { github: { tokenRef, pushRepo, host } };
366
+ const github = factory(GitHub, { options });
367
+
368
+ const getAuthenticated = t.mock.method(github.client.users, 'getAuthenticated', () => {
369
+ throw new RequestError('Bad credentials', 401, requestErrorOptions);
370
+ });
371
+
372
+ await assert.rejects(runTasks(github), {
373
+ message: /Could not authenticate with GitHub using environment variable "GITHUB_TOKEN"/
374
+ });
375
+
376
+ assert.equal(getAuthenticated.mock.callCount(), 1);
307
377
  });
308
378
 
309
- t.is(stub.callCount, 1);
310
- stub.restore();
311
- });
379
+ testSkipOnActions('should throw for non-collaborator', async t => {
380
+ const options = { github: { tokenRef, pushRepo, host } };
381
+ const github = factory(GitHub, { options });
312
382
 
313
- testSkipOnActions('should throw for non-collaborator', async t => {
314
- interceptAuthentication({ username: 'john' });
315
- const options = { github: { tokenRef, pushRepo, host } };
316
- const github = factory(GitHub, { options });
317
- const stub = sinon.stub(github.client.repos, 'checkCollaborator');
318
- stub.throws(new RequestError('HttpError', 401, requestErrorOptions));
383
+ t.mock.method(github.client.repos, 'checkCollaborator', () => {
384
+ throw new RequestError('HttpError', 401, requestErrorOptions);
385
+ });
319
386
 
320
- await t.throwsAsync(runTasks(github), { message: /^User john is not a collaborator for user\/repo/ });
387
+ interceptAuthentication(api, { username: 'john' });
321
388
 
322
- stub.restore();
323
- });
389
+ await assert.rejects(runTasks(github), /User john is not a collaborator for user\/repo/);
390
+ });
324
391
 
325
- test.serial('should skip authentication and collaborator checks when running on GitHub Actions', async t => {
326
- const { GITHUB_ACTIONS, GITHUB_ACTOR } = process.env;
327
- if (!GITHUB_ACTIONS) {
328
- process.env.GITHUB_ACTIONS = 1;
329
- process.env.GITHUB_ACTOR = 'webpro';
330
- }
392
+ test('should skip authentication and collaborator checks when running on GitHub Actions', async t => {
393
+ const { GITHUB_ACTIONS, GITHUB_ACTOR } = process.env;
394
+ if (!GITHUB_ACTIONS) {
395
+ process.env.GITHUB_ACTIONS = 1;
396
+ process.env.GITHUB_ACTOR = 'webpro';
397
+ }
331
398
 
332
- const options = { github: { tokenRef } };
333
- const github = factory(GitHub, { options });
334
- const authStub = sinon.stub(github, 'isAuthenticated');
335
- const collaboratorStub = sinon.stub(github, 'isCollaborator');
399
+ const options = { github: { tokenRef } };
400
+ const github = factory(GitHub, { options });
401
+ const authStub = t.mock.method(github, 'isAuthenticated');
402
+ const collaboratorStub = t.mock.method(github, 'isCollaborator');
336
403
 
337
- await t.notThrowsAsync(github.init());
404
+ await assert.doesNotReject(github.init());
338
405
 
339
- t.is(authStub.callCount, 0);
340
- t.is(collaboratorStub.callCount, 0);
341
- t.is(github.getContext('username'), process.env.GITHUB_ACTOR);
406
+ assert.equal(authStub.mock.callCount(), 0);
407
+ assert.equal(collaboratorStub.mock.callCount(), 0);
408
+ assert.equal(github.getContext('username'), process.env.GITHUB_ACTOR);
342
409
 
343
- authStub.restore();
344
- collaboratorStub.restore();
410
+ if (!GITHUB_ACTIONS) {
411
+ process.env.GITHUB_ACTIONS = GITHUB_ACTIONS || '';
412
+ process.env.GITHUB_ACTOR = GITHUB_ACTOR || '';
413
+ }
414
+ });
345
415
 
346
- if (!GITHUB_ACTIONS) {
347
- process.env.GITHUB_ACTIONS = GITHUB_ACTIONS || '';
348
- process.env.GITHUB_ACTOR = GITHUB_ACTOR || '';
349
- }
350
- });
416
+ test('should handle octokit client error (without retries)', async t => {
417
+ const github = factory(GitHub, { options: { github: { tokenRef, pushRepo, host } } });
418
+ const createRelease = t.mock.method(github.client.repos, 'createRelease', () => {
419
+ throw new RequestError('Not found', 404, requestErrorOptions);
420
+ });
351
421
 
352
- test('should handle octokit client error (without retries)', async t => {
353
- const github = factory(GitHub, { options: { github: { tokenRef, pushRepo, host } } });
354
- const stub = sinon.stub(github.client.repos, 'createRelease');
355
- stub.throws(new RequestError('Not found', 404, requestErrorOptions));
356
- interceptAuthentication();
357
- interceptCollaborator();
422
+ interceptAuthentication(api);
423
+ interceptCollaborator(api);
358
424
 
359
- await t.throwsAsync(runTasks(github), { message: /^404 \(Not found\)/ });
425
+ await assert.rejects(runTasks(github), /404 \(Not found\)/);
360
426
 
361
- t.is(stub.callCount, 1);
362
- stub.restore();
363
- });
427
+ assert.equal(createRelease.mock.callCount(), 1);
428
+ });
364
429
 
365
- test('should handle octokit client error (with retries)', async t => {
366
- const options = { github: { tokenRef, pushRepo, host, retryMinTimeout: 0 } };
367
- const github = factory(GitHub, { options });
368
- const stub = sinon.stub(github.client.repos, 'createRelease');
369
- stub.throws(new RequestError('Request failed', 500, requestErrorOptions));
370
- interceptAuthentication();
371
- interceptCollaborator();
430
+ test('should handle octokit client error (with retries)', async t => {
431
+ const options = { github: { tokenRef, pushRepo, host, retryMinTimeout: 0 } };
432
+ const github = factory(GitHub, { options });
372
433
 
373
- await t.throwsAsync(runTasks(github), { message: /^500 \(Request failed\)/ });
434
+ const createRelease = t.mock.method(github.client.repos, 'createRelease', () => {
435
+ throw new RequestError('Request failed', 500, requestErrorOptions);
436
+ });
374
437
 
375
- t.is(stub.callCount, 3);
376
- stub.restore();
377
- });
438
+ interceptAuthentication(api);
439
+ interceptCollaborator(api);
378
440
 
379
- test('should not call octokit client in dry run', async t => {
380
- const options = { 'dry-run': true, git, github: { tokenRef, pushRepo, releaseName: 'R ${version}', assets: ['*'] } };
381
- const github = factory(GitHub, { options });
382
- const spy = sinon.spy(github, 'client', ['get']);
383
- const exec = sinon.stub(github.shell, 'exec').callThrough();
384
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
385
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('v1.0.0');
386
-
387
- await runTasks(github);
388
-
389
- t.is(spy.get.callCount, 0);
390
- t.is(github.log.exec.args[1][0], 'octokit repos.createRelease "R 1.0.1" (v1.0.1)');
391
- t.is(github.log.exec.lastCall.args[0], 'octokit repos.uploadReleaseAssets');
392
- const { isReleased, releaseUrl } = github.getContext();
393
- t.true(isReleased);
394
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/v1.0.1');
395
- spy.get.restore();
396
- exec.restore();
397
- });
441
+ await assert.rejects(runTasks(github), /500 \(Request failed\)/);
398
442
 
399
- test('should skip checks', async t => {
400
- const options = { github: { tokenRef, skipChecks: true } };
401
- const github = factory(GitHub, { options });
402
- await t.notThrowsAsync(github.init());
403
- });
443
+ assert.equal(createRelease.mock.callCount(), 3);
444
+ });
404
445
 
405
- test('should generate GitHub web release url', async t => {
406
- const options = {
407
- github: {
408
- pushRepo,
409
- release: true,
410
- web: true,
411
- releaseName: 'Release ${tagName}',
412
- releaseNotes: 'echo Custom notes'
413
- }
414
- };
415
- const github = factory(GitHub, { options });
416
- const exec = sinon.stub(github.shell, 'exec').callThrough();
417
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
418
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
419
-
420
- await runTasks(github);
421
-
422
- const { isReleased, releaseUrl } = github.getContext();
423
- t.true(isReleased);
424
- t.is(
425
- releaseUrl,
426
- 'https://github.com/user/repo/releases/new?tag=2.0.2&title=Release+2.0.2&body=Custom+notes&prerelease=false'
427
- );
428
- exec.restore();
429
- });
446
+ test('should not call octokit client in dry run', async t => {
447
+ const options = {
448
+ 'dry-run': true,
449
+ git,
450
+ github: { tokenRef, pushRepo, releaseName: 'R ${version}', assets: ['*'] }
451
+ };
452
+ const github = factory(GitHub, { options });
453
+
454
+ const get = t.mock.getter(github, 'client');
455
+ const original = github.shell.exec.bind(github.shell);
456
+ t.mock.method(github.shell, 'exec', (...args) => {
457
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
458
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('v1.0.0');
459
+ return original(...args);
460
+ });
461
+
462
+ await runTasks(github);
463
+
464
+ assert.equal(get.mock.callCount(), 0);
465
+ assert.equal(github.log.exec.mock.calls[1].arguments[0], 'octokit repos.createRelease "R 1.0.1" (v1.0.1)');
466
+ assert.equal(github.log.exec.mock.calls.at(-1).arguments[0], 'octokit repos.uploadReleaseAssets');
467
+ const { isReleased, releaseUrl } = github.getContext();
468
+ assert(isReleased);
469
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/v1.0.1');
470
+ });
430
471
 
431
- test('should generate GitHub web release url for enterprise host', async t => {
432
- const options = {
433
- git,
434
- github: {
435
- pushRepo: 'git@custom.example.org:user/repo',
436
- release: true,
437
- web: true,
438
- host: 'custom.example.org',
439
- releaseName: 'The Launch',
440
- releaseNotes: 'echo It happened'
441
- }
442
- };
443
- const github = factory(GitHub, { options });
444
- const exec = sinon.stub(github.shell, 'exec').callThrough();
445
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
446
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
447
-
448
- await runTasks(github);
449
-
450
- const { isReleased, releaseUrl } = github.getContext();
451
- t.true(isReleased);
452
- t.is(
453
- releaseUrl,
454
- 'https://custom.example.org/user/repo/releases/new?tag=2.0.2&title=The+Launch&body=It+happened&prerelease=false'
455
- );
456
- exec.restore();
457
- });
472
+ test('should generate GitHub web release url', async t => {
473
+ const options = {
474
+ github: {
475
+ pushRepo,
476
+ release: true,
477
+ web: true,
478
+ releaseName: 'Release ${tagName}',
479
+ releaseNotes: 'echo Custom notes'
480
+ }
481
+ };
482
+ const github = factory(GitHub, { options });
483
+
484
+ const original = github.shell.exec.bind(github.shell);
485
+ t.mock.method(github.shell, 'exec', (...args) => {
486
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
487
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
488
+ return original(...args);
489
+ });
490
+
491
+ await runTasks(github);
492
+
493
+ const { isReleased, releaseUrl } = github.getContext();
494
+ assert(isReleased);
495
+ assert.equal(
496
+ releaseUrl,
497
+ 'https://github.com/user/repo/releases/new?tag=2.0.2&title=Release+2.0.2&body=Custom+notes&prerelease=false'
498
+ );
499
+ });
458
500
 
459
- // eslint-disable-next-line ava/no-skip-test
460
- test.skip('should truncate long body', async t => {
461
- const releaseNotes = 'a'.repeat(125001);
462
- const body = 'a'.repeat(124000) + '...';
463
- const options = {
464
- git,
465
- github: {
466
- pushRepo,
467
- tokenRef,
468
- release: true,
469
- releaseName: 'Release ${tagName}',
470
- releaseNotes: 'echo ' + releaseNotes
471
- }
472
- };
473
- const github = factory(GitHub, { options });
474
- const exec = sinon.stub(github.shell, 'exec').callThrough();
475
- exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
476
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
477
-
478
- interceptAuthentication();
479
- interceptCollaborator();
480
- interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', body } });
481
-
482
- await runTasks(github);
483
-
484
- const { isReleased, releaseUrl } = github.getContext();
485
- t.true(isReleased);
486
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
487
- exec.restore();
488
- });
501
+ test('should generate GitHub web release url for enterprise host', async t => {
502
+ const options = {
503
+ git,
504
+ github: {
505
+ pushRepo: 'git@custom.example.org:user/repo',
506
+ release: true,
507
+ web: true,
508
+ host: 'custom.example.org',
509
+ releaseName: 'The Launch',
510
+ releaseNotes: 'echo It happened'
511
+ }
512
+ };
513
+ const github = factory(GitHub, { options });
514
+
515
+ const original = github.shell.exec.bind(github.shell);
516
+ t.mock.method(github.shell, 'exec', (...args) => {
517
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
518
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
519
+ return original(...args);
520
+ });
521
+
522
+ await runTasks(github);
523
+
524
+ const { isReleased, releaseUrl } = github.getContext();
525
+ assert(isReleased);
526
+ assert.equal(
527
+ releaseUrl,
528
+ 'https://custom.example.org/user/repo/releases/new?tag=2.0.2&title=The+Launch&body=It+happened&prerelease=false'
529
+ );
530
+ });
489
531
 
490
- test('should generate search queries correctly', t => {
491
- const generateCommit = () => Math.random().toString(36).substring(2, 9);
492
- const base = 'repo:owner/repo+type:pr+is:merged';
493
- const commits = Array.from({ length: 5 }, generateCommit);
494
- const separator = '+';
495
-
496
- const result = getSearchQueries(base, commits, separator);
497
-
498
- // Test case 1: Check if all commits are included in the search queries
499
- const allCommitsIncluded = commits.every(commit => result.some(query => query.includes(commit)));
500
- t.true(allCommitsIncluded, 'All commits should be included in the search queries');
501
-
502
- // Test case 2: Check if the function respects the 256 character limit
503
- const manyCommits = Array.from({ length: 100 }, generateCommit);
504
- const longResult = getSearchQueries(base, manyCommits, separator);
505
- t.true(longResult.length > 1, 'Many commits should be split into multiple queries');
506
- t.true(
507
- longResult.every(query => encodeURIComponent(query).length <= 256),
508
- 'Each query should not exceed 256 characters after encoding'
509
- );
510
- });
532
+ test.skip('should truncate long body', async t => {
533
+ const releaseNotes = 'a'.repeat(125001);
534
+ const body = 'a'.repeat(124000) + '...';
535
+ const options = {
536
+ git,
537
+ github: {
538
+ pushRepo,
539
+ tokenRef,
540
+ release: true,
541
+ releaseName: 'Release ${tagName}',
542
+ releaseNotes: 'echo ' + releaseNotes
543
+ }
544
+ };
545
+ const github = factory(GitHub, { options });
511
546
 
512
- test('should create auto-generated discussion', async t => {
513
- const options = {
514
- git,
515
- github: {
516
- pushRepo,
517
- tokenRef,
518
- release: true,
519
- releaseName: 'Release ${tagName}',
520
- autoGenerate: false,
521
- discussionCategoryName: 'Announcement'
522
- }
523
- };
524
- const github = factory(GitHub, { options });
525
- const exec = sinon.stub(github.shell, 'exec').callThrough();
526
- exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
527
-
528
- interceptAuthentication();
529
- interceptCollaborator();
530
- interceptCreate({
531
- body: {
532
- tag_name: '2.0.2',
533
- name: 'Release 2.0.2',
534
- generate_release_notes: false,
535
- body: null,
536
- discussion_category_name: 'Announcement'
537
- }
547
+ const original = github.shell.exec.bind(github.shell);
548
+ t.mock.method(github.shell, 'exec', (...args) => {
549
+ if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
550
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
551
+ return original(...args);
552
+ });
553
+
554
+ interceptAuthentication(api);
555
+ interceptCollaborator(api);
556
+ interceptCreate(api, { body: { tag_name: '2.0.2', name: 'Release 2.0.2', body } });
557
+
558
+ await runTasks(github);
559
+
560
+ const { isReleased, releaseUrl } = github.getContext();
561
+ assert(isReleased);
562
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
563
+ });
564
+
565
+ test('should generate search queries correctly', () => {
566
+ const generateCommit = () => Math.random().toString(36).substring(2, 9);
567
+ const base = 'repo:owner/repo+type:pr+is:merged';
568
+ const commits = Array.from({ length: 5 }, generateCommit);
569
+ const separator = '+';
570
+
571
+ const result = getSearchQueries(base, commits, separator);
572
+
573
+ // Test case 1: Check if all commits are included in the search queries
574
+ const allCommitsIncluded = commits.every(commit => result.some(query => query.includes(commit)));
575
+ assert(allCommitsIncluded, 'All commits should be included in the search queries');
576
+
577
+ assert.equal(
578
+ commits.every(commit => result.some(query => query.includes(commit))),
579
+ true
580
+ );
581
+
582
+ // Test case 2: Check if the function respects the 256 character limit
583
+ const manyCommits = Array.from({ length: 100 }, generateCommit);
584
+ const longResult = getSearchQueries(base, manyCommits, separator);
585
+ assert(longResult.length > 1, 'Many commits should be split into multiple queries');
586
+ assert(
587
+ longResult.every(query => encodeURIComponent(query).length <= 256),
588
+ 'Each query should not exceed 256 characters after encoding'
589
+ );
538
590
  });
539
591
 
540
- await runTasks(github);
592
+ test('should create auto-generated discussion', async t => {
593
+ const options = {
594
+ git,
595
+ github: {
596
+ pushRepo,
597
+ tokenRef,
598
+ release: true,
599
+ releaseName: 'Release ${tagName}',
600
+ autoGenerate: false,
601
+ discussionCategoryName: 'Announcement'
602
+ }
603
+ };
604
+ const github = factory(GitHub, { options });
605
+ const original = github.shell.exec.bind(github.shell);
606
+ t.mock.method(github.shell, 'exec', (...args) => {
607
+ if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
608
+ return original(...args);
609
+ });
610
+
611
+ interceptAuthentication(api);
612
+ interceptCollaborator(api);
613
+ interceptCreate(api, {
614
+ body: {
615
+ tag_name: '2.0.2',
616
+ name: 'Release 2.0.2',
617
+ generate_release_notes: false,
618
+ body: null,
619
+ discussion_category_name: 'Announcement'
620
+ }
621
+ });
541
622
 
542
- const { isReleased, releaseUrl, discussionUrl } = github.getContext();
543
- t.true(isReleased);
544
- t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
545
- t.is(discussionUrl, 'https://github.com/user/repo/discussions/1');
546
- exec.restore();
623
+ await runTasks(github);
624
+
625
+ const { isReleased, releaseUrl, discussionUrl } = github.getContext();
626
+ assert(isReleased);
627
+ assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
628
+ assert.equal(discussionUrl, 'https://github.com/user/repo/discussions/1');
629
+ });
547
630
  });