@unito/integration-cli 0.64.3 → 0.64.5

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.
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const strict_1 = tslib_1.__importDefault(require("node:assert/strict"));
5
+ const child_process_1 = tslib_1.__importDefault(require("child_process"));
6
+ const fs_1 = tslib_1.__importDefault(require("fs"));
7
+ const os_1 = tslib_1.__importDefault(require("os"));
8
+ const path_1 = tslib_1.__importDefault(require("path"));
9
+ const sinon_1 = tslib_1.__importDefault(require("sinon"));
10
+ const updateNotifier_1 = require("../../src/hooks/init/updateNotifier");
11
+ function makeFakeSpawn() {
12
+ const fakeChild = { unref: sinon_1.default.stub() };
13
+ return sinon_1.default.stub().returns(fakeChild);
14
+ }
15
+ function makeDeps(cacheFile, overrides) {
16
+ return {
17
+ cacheFile,
18
+ spawn: makeFakeSpawn(),
19
+ log: sinon_1.default.stub(),
20
+ upgrade: sinon_1.default.stub(),
21
+ ...overrides,
22
+ };
23
+ }
24
+ describe('updateNotifier', () => {
25
+ afterEach(() => {
26
+ sinon_1.default.restore();
27
+ });
28
+ describe('isNewerVersion', () => {
29
+ it('returns true when latest major is higher', () => {
30
+ strict_1.default.equal((0, updateNotifier_1.isNewerVersion)('2.0.0', '1.0.0'), true);
31
+ });
32
+ it('returns true when latest minor is higher', () => {
33
+ strict_1.default.equal((0, updateNotifier_1.isNewerVersion)('1.2.0', '1.1.0'), true);
34
+ });
35
+ it('returns true when latest patch is higher', () => {
36
+ strict_1.default.equal((0, updateNotifier_1.isNewerVersion)('1.0.2', '1.0.1'), true);
37
+ });
38
+ it('returns false when versions are equal', () => {
39
+ strict_1.default.equal((0, updateNotifier_1.isNewerVersion)('1.0.0', '1.0.0'), false);
40
+ });
41
+ it('returns false when current is newer', () => {
42
+ strict_1.default.equal((0, updateNotifier_1.isNewerVersion)('1.0.0', '2.0.0'), false);
43
+ });
44
+ it('returns false when current minor is newer', () => {
45
+ strict_1.default.equal((0, updateNotifier_1.isNewerVersion)('1.1.0', '1.2.0'), false);
46
+ });
47
+ });
48
+ describe('readCache', () => {
49
+ let tmpDir;
50
+ beforeEach(() => {
51
+ tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'update-notifier-test-'));
52
+ });
53
+ afterEach(() => {
54
+ fs_1.default.rmSync(tmpDir, { recursive: true });
55
+ });
56
+ it('returns parsed cache data from file', () => {
57
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
58
+ const cacheData = { latest: '1.2.3', checkedAt: Date.now() };
59
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(cacheData));
60
+ const result = (0, updateNotifier_1.readCache)(cacheFile);
61
+ strict_1.default.deepEqual(result, cacheData);
62
+ });
63
+ it('returns null when cache file does not exist', () => {
64
+ const result = (0, updateNotifier_1.readCache)('/nonexistent/path/cache.json');
65
+ strict_1.default.equal(result, null);
66
+ });
67
+ it('returns null when cache file is invalid JSON', () => {
68
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
69
+ fs_1.default.writeFileSync(cacheFile, 'not valid json');
70
+ const result = (0, updateNotifier_1.readCache)(cacheFile);
71
+ strict_1.default.equal(result, null);
72
+ });
73
+ it('returns null when cache JSON has wrong shape', () => {
74
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
75
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify({ foo: 'bar' }));
76
+ const result = (0, updateNotifier_1.readCache)(cacheFile);
77
+ strict_1.default.equal(result, null);
78
+ });
79
+ it('returns null when latest is not a string', () => {
80
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
81
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify({ latest: 123, checkedAt: Date.now() }));
82
+ const result = (0, updateNotifier_1.readCache)(cacheFile);
83
+ strict_1.default.equal(result, null);
84
+ });
85
+ });
86
+ describe('isCacheStale', () => {
87
+ it('returns true when cache is null', () => {
88
+ strict_1.default.equal((0, updateNotifier_1.isCacheStale)(null), true);
89
+ });
90
+ it('returns true when cache is older than interval', () => {
91
+ const cache = { latest: '1.0.0', checkedAt: Date.now() - 2 * 60 * 60 * 1000 };
92
+ strict_1.default.equal((0, updateNotifier_1.isCacheStale)(cache, 60 * 60 * 1000), true);
93
+ });
94
+ it('returns false when cache is within interval', () => {
95
+ const cache = { latest: '1.0.0', checkedAt: Date.now() - 30 * 60 * 1000 };
96
+ strict_1.default.equal((0, updateNotifier_1.isCacheStale)(cache, 60 * 60 * 1000), false);
97
+ });
98
+ });
99
+ describe('formatNotification', () => {
100
+ it('contains version numbers', () => {
101
+ const plain = (0, updateNotifier_1.stripAnsi)((0, updateNotifier_1.formatNotification)('0.64.4', '0.65.0'));
102
+ (0, strict_1.default)(plain.includes('0.64.4'), 'should include current version');
103
+ (0, strict_1.default)(plain.includes('0.65.0'), 'should include latest version');
104
+ });
105
+ it('contains npm package link', () => {
106
+ const plain = (0, updateNotifier_1.stripAnsi)((0, updateNotifier_1.formatNotification)('0.64.4', '0.65.0'));
107
+ (0, strict_1.default)(plain.includes('https://www.npmjs.com/package/@unito/integration-cli'), 'should include npm URL');
108
+ });
109
+ it('contains upgrade command', () => {
110
+ const plain = (0, updateNotifier_1.stripAnsi)((0, updateNotifier_1.formatNotification)('0.64.4', '0.65.0'));
111
+ (0, strict_1.default)(plain.includes('integration-cli upgrade'), 'should include upgrade command');
112
+ });
113
+ it('renders a box border', () => {
114
+ const msg = (0, updateNotifier_1.formatNotification)('0.64.4', '0.65.0');
115
+ (0, strict_1.default)(msg.includes('\u2500'), 'should have horizontal border');
116
+ (0, strict_1.default)(msg.includes('\u250c'), 'should have top-left corner');
117
+ (0, strict_1.default)(msg.includes('\u2510'), 'should have top-right corner');
118
+ (0, strict_1.default)(msg.includes('\u2514'), 'should have bottom-left corner');
119
+ (0, strict_1.default)(msg.includes('\u2518'), 'should have bottom-right corner');
120
+ });
121
+ });
122
+ describe('spawnBackgroundCheck', () => {
123
+ it('spawns a detached child process', () => {
124
+ const spawn = makeFakeSpawn();
125
+ const deps = makeDeps('/tmp/cache.json', { spawn });
126
+ (0, updateNotifier_1.spawnBackgroundCheck)(deps);
127
+ (0, strict_1.default)(spawn.calledOnce, 'should spawn a process');
128
+ strict_1.default.equal(spawn.firstCall.args[0], process.execPath);
129
+ const options = spawn.firstCall.args[2];
130
+ strict_1.default.equal(options.detached, true);
131
+ strict_1.default.equal(options.stdio, 'ignore');
132
+ });
133
+ it('passes correct cache file to the spawned script', () => {
134
+ const spawn = makeFakeSpawn();
135
+ const deps = makeDeps('/custom/path/cache.json', { spawn });
136
+ (0, updateNotifier_1.spawnBackgroundCheck)(deps);
137
+ const script = spawn.firstCall.args[1][1];
138
+ (0, strict_1.default)(script.includes('/custom/path/cache.json'), 'script should use the provided cache file');
139
+ (0, strict_1.default)(script.includes('/custom/path'), 'script should use the provided cache dir');
140
+ });
141
+ it('unrefs the spawned process so it does not block exit', () => {
142
+ const fakeChild = { unref: sinon_1.default.stub() };
143
+ const spawn = sinon_1.default.stub().returns(fakeChild);
144
+ const deps = makeDeps('/tmp/cache.json', { spawn });
145
+ (0, updateNotifier_1.spawnBackgroundCheck)(deps);
146
+ (0, strict_1.default)(fakeChild.unref.calledOnce, 'should unref the child process');
147
+ });
148
+ });
149
+ describe('checkForUpdate', () => {
150
+ let tmpDir;
151
+ let savedCI;
152
+ let savedArgv;
153
+ beforeEach(() => {
154
+ tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'update-notifier-test-'));
155
+ // Unset CI so checkForUpdate doesn't skip (test helper sets CI=1)
156
+ savedCI = process.env.CI;
157
+ delete process.env.CI;
158
+ savedArgv = process.argv;
159
+ });
160
+ afterEach(() => {
161
+ process.argv = savedArgv;
162
+ fs_1.default.rmSync(tmpDir, { recursive: true });
163
+ if (savedCI !== undefined) {
164
+ process.env.CI = savedCI;
165
+ }
166
+ else {
167
+ delete process.env.CI;
168
+ }
169
+ });
170
+ it('calls upgrade when newer version is cached', () => {
171
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
172
+ const freshCache = { latest: '99.0.0', checkedAt: Date.now() };
173
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(freshCache));
174
+ const upgrade = sinon_1.default.stub();
175
+ const log = sinon_1.default.stub();
176
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn: makeFakeSpawn(), log, upgrade });
177
+ (0, strict_1.default)(upgrade.calledOnce, 'should call upgrade');
178
+ });
179
+ it('logs success message after upgrade succeeds', () => {
180
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
181
+ const freshCache = { latest: '99.0.0', checkedAt: Date.now() };
182
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(freshCache));
183
+ const upgrade = sinon_1.default.stub();
184
+ const log = sinon_1.default.stub();
185
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn: makeFakeSpawn(), log, upgrade });
186
+ strict_1.default.equal(log.callCount, 2, 'should log upgrading + upgraded messages');
187
+ (0, strict_1.default)(log.firstCall.args[0].includes('0.64.4'), 'should mention current version');
188
+ (0, strict_1.default)(log.firstCall.args[0].includes('99.0.0'), 'should mention target version');
189
+ (0, strict_1.default)(log.secondCall.args[0].includes('Upgraded'), 'should confirm upgrade');
190
+ });
191
+ it('falls back to notification box when upgrade fails', () => {
192
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
193
+ const freshCache = { latest: '99.0.0', checkedAt: Date.now() };
194
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(freshCache));
195
+ const upgrade = sinon_1.default.stub().throws(new Error('npm install failed'));
196
+ const log = sinon_1.default.stub();
197
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn: makeFakeSpawn(), log, upgrade });
198
+ // Upgrading message, failure reason, then fallback notification
199
+ strict_1.default.equal(log.callCount, 3, 'should log upgrading + failure reason + fallback notification');
200
+ (0, strict_1.default)(log.firstCall.args[0].includes('Upgrading'), 'first message should be upgrading');
201
+ const failMsg = (0, updateNotifier_1.stripAnsi)(log.secondCall.args[0]);
202
+ (0, strict_1.default)(failMsg.includes('Auto-upgrade failed'), 'should explain the failure');
203
+ (0, strict_1.default)(failMsg.includes('npm install failed'), 'should include error reason');
204
+ const plain = (0, updateNotifier_1.stripAnsi)(log.thirdCall.args[0]);
205
+ (0, strict_1.default)(plain.includes('99.0.0'), 'fallback should show the new version');
206
+ (0, strict_1.default)(plain.includes('integration-cli upgrade'), 'fallback should show manual upgrade command');
207
+ });
208
+ it('does not call upgrade when version is current', () => {
209
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
210
+ const freshCache = { latest: '0.64.4', checkedAt: Date.now() };
211
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(freshCache));
212
+ const upgrade = sinon_1.default.stub();
213
+ const log = sinon_1.default.stub();
214
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn: makeFakeSpawn(), log, upgrade });
215
+ strict_1.default.equal(upgrade.called, false, 'should not call upgrade');
216
+ strict_1.default.equal(log.called, false, 'should not log anything');
217
+ });
218
+ it('does not upgrade or notify when current is newer than cached', () => {
219
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
220
+ const freshCache = { latest: '0.64.4', checkedAt: Date.now() };
221
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(freshCache));
222
+ const upgrade = sinon_1.default.stub();
223
+ const log = sinon_1.default.stub();
224
+ (0, updateNotifier_1.checkForUpdate)('1.0.0', { cacheFile, spawn: makeFakeSpawn(), log, upgrade });
225
+ strict_1.default.equal(upgrade.called, false, 'should not call upgrade');
226
+ strict_1.default.equal(log.called, false, 'should not log anything');
227
+ });
228
+ it('upgrades and spawns background check when cache has newer version but is stale', () => {
229
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
230
+ const staleCache = { latest: '99.0.0', checkedAt: Date.now() - 48 * 60 * 60 * 1000 };
231
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(staleCache));
232
+ const upgrade = sinon_1.default.stub();
233
+ const spawn = makeFakeSpawn();
234
+ const log = sinon_1.default.stub();
235
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn, log, upgrade });
236
+ (0, strict_1.default)(upgrade.calledOnce, 'should attempt upgrade');
237
+ (0, strict_1.default)(spawn.calledOnce, 'should also spawn background check since cache is stale');
238
+ });
239
+ it('spawns background check when cache is stale', () => {
240
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
241
+ const staleCache = { latest: '0.64.4', checkedAt: Date.now() - 48 * 60 * 60 * 1000 };
242
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(staleCache));
243
+ const spawn = makeFakeSpawn();
244
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn, log: sinon_1.default.stub() });
245
+ (0, strict_1.default)(spawn.calledOnce, 'should spawn background check');
246
+ });
247
+ it('spawns background check when no cache exists', () => {
248
+ const cacheFile = path_1.default.join(tmpDir, 'nonexistent.json');
249
+ const spawn = makeFakeSpawn();
250
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn, log: sinon_1.default.stub() });
251
+ (0, strict_1.default)(spawn.calledOnce, 'should spawn background check');
252
+ });
253
+ it('does not spawn background check when cache is fresh', () => {
254
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
255
+ const freshCache = { latest: '0.64.4', checkedAt: Date.now() };
256
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(freshCache));
257
+ const spawn = makeFakeSpawn();
258
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn, log: sinon_1.default.stub() });
259
+ strict_1.default.equal(spawn.called, false, 'should not spawn background check');
260
+ });
261
+ it('skips entirely during upgrade command', () => {
262
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
263
+ const freshCache = { latest: '99.0.0', checkedAt: Date.now() - 48 * 60 * 60 * 1000 };
264
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(freshCache));
265
+ process.argv = ['node', 'integration-cli', 'upgrade'];
266
+ const spawn = makeFakeSpawn();
267
+ const log = sinon_1.default.stub();
268
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn, log });
269
+ strict_1.default.equal(log.called, false, 'should not display notification');
270
+ strict_1.default.equal(spawn.called, false, 'should not spawn background check');
271
+ });
272
+ it('does not throw even if spawn fails', () => {
273
+ const cacheFile = path_1.default.join(tmpDir, 'nonexistent.json');
274
+ const spawn = sinon_1.default.stub().throws(new Error('spawn failed'));
275
+ strict_1.default.doesNotThrow(() => (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn, log: sinon_1.default.stub() }));
276
+ });
277
+ it('skips when CI env var is set', () => {
278
+ const cacheFile = path_1.default.join(tmpDir, 'cache.json');
279
+ const freshCache = { latest: '99.0.0', checkedAt: Date.now() - 48 * 60 * 60 * 1000 };
280
+ fs_1.default.writeFileSync(cacheFile, JSON.stringify(freshCache));
281
+ process.env.CI = 'true';
282
+ const spawn = makeFakeSpawn();
283
+ const log = sinon_1.default.stub();
284
+ (0, updateNotifier_1.checkForUpdate)('0.64.4', { cacheFile, spawn, log });
285
+ strict_1.default.equal(log.called, false, 'should not display notification');
286
+ strict_1.default.equal(spawn.called, false, 'should not spawn background check');
287
+ });
288
+ });
289
+ describe('integration: background check writes cache from npm', () => {
290
+ let tmpDir;
291
+ let savedCI;
292
+ beforeEach(() => {
293
+ tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'update-notifier-integration-'));
294
+ savedCI = process.env.CI;
295
+ });
296
+ afterEach(() => {
297
+ fs_1.default.rmSync(tmpDir, { recursive: true });
298
+ if (savedCI !== undefined) {
299
+ process.env.CI = savedCI;
300
+ }
301
+ else {
302
+ delete process.env.CI;
303
+ }
304
+ });
305
+ function waitForFile(filePath, timeoutMs) {
306
+ const start = Date.now();
307
+ return new Promise((resolve, reject) => {
308
+ const check = () => {
309
+ if (fs_1.default.existsSync(filePath))
310
+ return resolve();
311
+ if (Date.now() - start > timeoutMs)
312
+ return reject(new Error(`Timed out waiting for ${filePath}`));
313
+ setTimeout(check, 200);
314
+ };
315
+ check();
316
+ });
317
+ }
318
+ it('spawns a real process that fetches from npm and writes valid cache', async function () {
319
+ this.timeout(15000);
320
+ const cacheFile = path_1.default.join(tmpDir, 'subcache', 'update-check.json');
321
+ (0, updateNotifier_1.spawnBackgroundCheck)({
322
+ cacheFile,
323
+ spawn: child_process_1.default.spawn,
324
+ log: sinon_1.default.stub(),
325
+ upgrade: sinon_1.default.stub(),
326
+ });
327
+ await waitForFile(cacheFile, 12000);
328
+ const cache = JSON.parse(fs_1.default.readFileSync(cacheFile, 'utf8'));
329
+ (0, strict_1.default)(cache.latest, 'cache should have a latest version');
330
+ (0, strict_1.default)(/^\d+\.\d+\.\d+/.test(cache.latest), `latest should be semver, got: ${cache.latest}`);
331
+ (0, strict_1.default)(typeof cache.checkedAt === 'number', 'cache should have a checkedAt timestamp');
332
+ (0, strict_1.default)(cache.checkedAt <= Date.now(), 'checkedAt should not be in the future');
333
+ });
334
+ it('end-to-end: stale cache triggers spawn, fresh cache triggers upgrade', async function () {
335
+ this.timeout(15000);
336
+ const cacheFile = path_1.default.join(tmpDir, 'e2e', 'update-check.json');
337
+ // Unset CI so checkForUpdate runs
338
+ delete process.env.CI;
339
+ // Step 1: no cache exists — checkForUpdate should spawn a real background check
340
+ const upgrade1 = sinon_1.default.stub();
341
+ const log1 = sinon_1.default.stub();
342
+ (0, updateNotifier_1.checkForUpdate)('0.0.1', { cacheFile, spawn: child_process_1.default.spawn, log: log1, upgrade: upgrade1 });
343
+ strict_1.default.equal(upgrade1.called, false, 'no upgrade without cache');
344
+ // Step 2: wait for the spawned process to write the cache
345
+ await waitForFile(cacheFile, 12000);
346
+ const cache = JSON.parse(fs_1.default.readFileSync(cacheFile, 'utf8'));
347
+ (0, strict_1.default)(/^\d+\.\d+\.\d+/.test(cache.latest), 'cache should contain a semver version');
348
+ // Step 3: run checkForUpdate again with a very old version — should trigger upgrade
349
+ const upgrade2 = sinon_1.default.stub();
350
+ const log2 = sinon_1.default.stub();
351
+ (0, updateNotifier_1.checkForUpdate)('0.0.1', { cacheFile, spawn: makeFakeSpawn(), log: log2, upgrade: upgrade2 });
352
+ (0, strict_1.default)(upgrade2.calledOnce, 'should call upgrade on second run');
353
+ (0, strict_1.default)(log2.firstCall.args[0].includes(cache.latest), 'should mention the target version');
354
+ });
355
+ });
356
+ });
@@ -59,19 +59,38 @@ describe('OAuth2Helper', () => {
59
59
  it('opens the authorization URL', async () => {
60
60
  await oauth2Helper.authorize();
61
61
  sinon_1.default.assert.calledOnce(openSpy);
62
- sinon_1.default.assert.calledWith(openSpy, 'https://provider.com/oauth/authorize?client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback-cli');
62
+ sinon_1.default.assert.calledWith(openSpy, 'https://provider.com/oauth/authorize?client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback');
63
63
  });
64
64
  it('maintains the pre-existing query parameters in authorization URL', async () => {
65
65
  oauth2Helper['providerAuthorizationUrl'] = 'https://provider.com/oauth/authorize?query1=value1&query2=value2';
66
66
  await oauth2Helper.authorize();
67
67
  sinon_1.default.assert.calledOnce(openSpy);
68
- sinon_1.default.assert.calledWith(openSpy, 'https://provider.com/oauth/authorize?query1=value1&query2=value2&client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback-cli');
68
+ sinon_1.default.assert.calledWith(openSpy, 'https://provider.com/oauth/authorize?query1=value1&query2=value2&client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback');
69
69
  });
70
70
  it('supports dynamic params', async () => {
71
71
  oauth2Helper['providerAuthorizationUrl'] = 'https://pro-{+foo}-der.com/oauth/authorize?query1={+bar}';
72
72
  await oauth2Helper.authorize();
73
73
  sinon_1.default.assert.calledOnce(openSpy);
74
- sinon_1.default.assert.calledWith(openSpy, 'https://pro-fooValue-der.com/oauth/authorize?query1=barValue&client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback-cli');
74
+ sinon_1.default.assert.calledWith(openSpy, 'https://pro-fooValue-der.com/oauth/authorize?query1=barValue&client_id=your-client-id&scope=scope1+scope2&state=eyJjbGlDYWxsYmFja1VybCI6Ii9vYXV0aDIvY2FsbGJhY2sifQ%3D%3D&response_type=code&redirect_uri=https%3A%2F%2Fintegrations-platform.unito.io%2Fcredentials%2Fnew%2Foauth2%2Fcallback');
75
+ });
76
+ describe('with legacyRedirectUrl', () => {
77
+ it('uses legacyRedirectUrl as redirect_uri and adds redirectToIPS to state', async () => {
78
+ oauth2Helper['legacyRedirectUrl'] = 'http://localhost:3000/api/auth/notion/callback';
79
+ await oauth2Helper.authorize();
80
+ sinon_1.default.assert.calledOnce(openSpy);
81
+ const url = new URL(openSpy.getCall(0).args[0]);
82
+ strict_1.default.equal(url.searchParams.get('redirect_uri'), 'http://localhost:3000/api/auth/notion/callback');
83
+ const state = JSON.parse(Buffer.from(url.searchParams.get('state'), 'base64').toString('utf-8'));
84
+ strict_1.default.equal(state.redirectToIPS, 'true');
85
+ strict_1.default.equal(state.cliCallbackUrl, '/oauth2/callback');
86
+ });
87
+ it('does not add redirectToIPS to state when legacyRedirectUrl is not set', async () => {
88
+ await oauth2Helper.authorize();
89
+ const url = new URL(openSpy.getCall(0).args[0]);
90
+ const state = JSON.parse(Buffer.from(url.searchParams.get('state'), 'base64').toString('utf-8'));
91
+ strict_1.default.equal(state.redirectToIPS, undefined);
92
+ strict_1.default.equal(url.searchParams.get('redirect_uri'), 'https://integrations-platform.unito.io/credentials/new/oauth2/callback');
93
+ });
75
94
  });
76
95
  });
77
96
  describe('handleCallback', () => {
@@ -299,6 +318,27 @@ describe('OAuth2Helper', () => {
299
318
  sinon_1.default.assert.calledOnce(res.send);
300
319
  sinon_1.default.assert.calledWith(res.send, oauth2Namespace.HTML_ERROR_MSG('Bad request'));
301
320
  });
321
+ describe('with legacyRedirectUrl', () => {
322
+ it('uses legacyRedirectUrl as redirect_uri in token request', async () => {
323
+ oauth2Helper['legacyRedirectUrl'] = 'http://localhost:3000/api/auth/notion/callback';
324
+ const code = 'test-code';
325
+ const req = { query: { code } };
326
+ const res = { send: sinon_1.default.stub(), setHeader: sinon_1.default.stub() };
327
+ await oauth2Helper['handleCallback'](req, res);
328
+ const requestBody = fetchStub.getCall(0).args[1].body;
329
+ const bodyParams = new URLSearchParams(requestBody);
330
+ strict_1.default.equal(bodyParams.get('redirect_uri'), 'http://localhost:3000/api/auth/notion/callback');
331
+ });
332
+ it('uses standard redirect_uri when legacyRedirectUrl is not set', async () => {
333
+ const code = 'test-code';
334
+ const req = { query: { code } };
335
+ const res = { send: sinon_1.default.stub(), setHeader: sinon_1.default.stub() };
336
+ await oauth2Helper['handleCallback'](req, res);
337
+ const requestBody = fetchStub.getCall(0).args[1].body;
338
+ const bodyParams = new URLSearchParams(requestBody);
339
+ strict_1.default.equal(bodyParams.get('redirect_uri'), 'https://integrations-platform.unito.io/credentials/new/oauth2/callback');
340
+ });
341
+ });
302
342
  });
303
343
  describe('updateToken', () => {
304
344
  const setupSuccessfulRefreshResponse = () => {
@@ -1 +1 @@
1
- {"root":["../src/baseCommand.ts","../src/configurationTypes.ts","../src/errors.ts","../src/index.ts","../src/commands/activity.ts","../src/commands/dev.ts","../src/commands/encrypt.ts","../src/commands/init.ts","../src/commands/invite.ts","../src/commands/login.ts","../src/commands/oauth2.ts","../src/commands/publish.ts","../src/commands/test.ts","../src/commands/upgrade.ts","../src/hooks/init/displayLogo.ts","../src/resources/configuration.ts","../src/resources/credentials.ts","../src/resources/decryption.ts","../src/resources/fileSystem.ts","../src/resources/globalConfiguration.ts","../src/resources/integrations.ts","../src/resources/integrationsPlatform.ts","../src/resources/oauth2.ts","../src/resources/template.ts","../src/services/integrationsPlatform.ts","../src/services/integrationsPlatformClient.ts","../src/services/oauth2.ts","../test/errors.test.ts","../test/commands/activity.test.ts","../test/commands/dev.test.ts","../test/commands/encrypt.test.ts","../test/commands/init.test.ts","../test/commands/invite.test.ts","../test/commands/login.test.ts","../test/commands/oauth2.test.ts","../test/commands/publish.test.ts","../test/commands/test.test.ts","../test/commands/upgrade.test.ts","../test/helpers/init.js","../test/helpers/integrations.ts","../test/helpers/styles.ts","../test/resources/configuration.test.ts","../test/resources/decryption.test.ts","../test/resources/globalConfiguration.test.ts","../test/resources/integrations.test.ts","../test/resources/oauth2.test.ts","../test/resources/template.test.ts","../test/services/integrationsPlatform.test.ts","../test/services/oauth2.test.ts","../scripts/generateTypes.ts","../.eslintrc.js"],"version":"5.9.3"}
1
+ {"root":["../src/baseCommand.ts","../src/configurationTypes.ts","../src/errors.ts","../src/index.ts","../src/commands/activity.ts","../src/commands/dev.ts","../src/commands/encrypt.ts","../src/commands/init.ts","../src/commands/invite.ts","../src/commands/login.ts","../src/commands/oauth2.ts","../src/commands/publish.ts","../src/commands/test.ts","../src/commands/upgrade.ts","../src/hooks/init/displayLogo.ts","../src/hooks/init/updateNotifier.ts","../src/resources/configuration.ts","../src/resources/credentials.ts","../src/resources/decryption.ts","../src/resources/fileSystem.ts","../src/resources/globalConfiguration.ts","../src/resources/integrations.ts","../src/resources/integrationsPlatform.ts","../src/resources/oauth2.ts","../src/resources/template.ts","../src/services/integrationsPlatform.ts","../src/services/integrationsPlatformClient.ts","../src/services/oauth2.ts","../test/errors.test.ts","../test/commands/activity.test.ts","../test/commands/dev.test.ts","../test/commands/encrypt.test.ts","../test/commands/init.test.ts","../test/commands/invite.test.ts","../test/commands/login.test.ts","../test/commands/oauth2.test.ts","../test/commands/publish.test.ts","../test/commands/test.test.ts","../test/commands/upgrade.test.ts","../test/helpers/init.js","../test/helpers/integrations.ts","../test/helpers/styles.ts","../test/hooks/updateNotifier.test.ts","../test/resources/configuration.test.ts","../test/resources/decryption.test.ts","../test/resources/globalConfiguration.test.ts","../test/resources/integrations.test.ts","../test/resources/oauth2.test.ts","../test/resources/template.test.ts","../test/services/integrationsPlatform.test.ts","../test/services/oauth2.test.ts","../scripts/generateTypes.ts","../.eslintrc.js"],"version":"5.9.3"}
@@ -748,5 +748,5 @@
748
748
  ]
749
749
  }
750
750
  },
751
- "version": "0.64.3"
751
+ "version": "0.64.5"
752
752
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unito/integration-cli",
3
- "version": "0.64.3",
3
+ "version": "0.64.5",
4
4
  "description": "Integration CLI",
5
5
  "bin": {
6
6
  "integration-cli": "./bin/run"