Skip to content

Commit 8d0a17e

Browse files
committed
feat: restore broken features
1 parent 5bf077a commit 8d0a17e

9 files changed

Lines changed: 79210 additions & 29416 deletions

File tree

__tests__/main.test.ts

Lines changed: 86 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,68 @@
22
* Unit tests for the action's main functionality, src/main.ts
33
*/
44

5+
import * as cache from '@actions/cache';
56
import * as core from '@actions/core';
67
import * as exec from '@actions/exec';
78
import * as tc from '@actions/tool-cache';
89
import * as main from '../src/main';
910

1011
// Mock the action's main function
11-
const runMock = jest.spyOn(main, 'run');
12+
const _runMock = jest.spyOn(main, 'run');
1213

1314
// Mock the GitHub Actions core library
1415
let _debugMock: jest.SpiedFunction<typeof core.debug>;
1516
let errorMock: jest.SpiedFunction<typeof core.error>;
1617
let getInputMock: jest.SpiedFunction<typeof core.getInput>;
17-
let setFailedMock: jest.SpiedFunction<typeof core.setFailed>;
18-
let setSecretMock: jest.SpiedFunction<typeof core.setSecret>;
19-
let addPathMock: jest.SpiedFunction<typeof core.addPath>;
18+
let _setFailedMock: jest.SpiedFunction<typeof core.setFailed>;
19+
let _setSecretMock: jest.SpiedFunction<typeof core.setSecret>;
20+
let _addPathMock: jest.SpiedFunction<typeof core.addPath>;
2021
let _infoMock: jest.SpiedFunction<typeof core.info>;
21-
let warningMock: jest.SpiedFunction<typeof core.warning>;
22+
let _warningMock: jest.SpiedFunction<typeof core.warning>;
23+
let exportVariableMock: jest.SpiedFunction<typeof core.exportVariable>;
2224

2325
// Mock the exec library
2426
let execMock: jest.SpiedFunction<typeof exec.exec>;
2527

2628
// Mock the tool-cache library
27-
let findMock: jest.SpiedFunction<typeof tc.find>;
29+
let _findMock: jest.SpiedFunction<typeof tc.find>;
2830
let cacheDirMock: jest.SpiedFunction<typeof tc.cacheDir>;
2931

32+
// Mock the cache library
33+
let _restoreCacheMock: jest.SpiedFunction<typeof cache.restoreCache>;
34+
let _saveCacheMock: jest.SpiedFunction<typeof cache.saveCache>;
35+
3036
describe('action', () => {
3137
beforeEach(() => {
3238
jest.clearAllMocks();
3339

3440
_debugMock = jest.spyOn(core, 'debug').mockImplementation();
3541
errorMock = jest.spyOn(core, 'error').mockImplementation();
3642
getInputMock = jest.spyOn(core, 'getInput').mockImplementation();
37-
setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation();
38-
setSecretMock = jest.spyOn(core, 'setSecret').mockImplementation();
39-
addPathMock = jest.spyOn(core, 'addPath').mockImplementation();
43+
_setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation();
44+
_setSecretMock = jest.spyOn(core, 'setSecret').mockImplementation();
45+
_addPathMock = jest.spyOn(core, 'addPath').mockImplementation();
4046
_infoMock = jest.spyOn(core, 'info').mockImplementation();
41-
warningMock = jest.spyOn(core, 'warning').mockImplementation();
42-
execMock = jest.spyOn(exec, 'exec').mockImplementation();
43-
findMock = jest.spyOn(tc, 'find').mockImplementation();
47+
_warningMock = jest.spyOn(core, 'warning').mockImplementation();
48+
exportVariableMock = jest.spyOn(core, 'exportVariable').mockImplementation();
49+
execMock = jest.spyOn(exec, 'exec').mockImplementation(() => Promise.resolve(0));
50+
_findMock = jest.spyOn(tc, 'find').mockImplementation();
4451
cacheDirMock = jest.spyOn(tc, 'cacheDir').mockImplementation();
52+
_restoreCacheMock = jest.spyOn(cache, 'restoreCache').mockImplementation();
53+
_saveCacheMock = jest.spyOn(cache, 'saveCache').mockImplementation();
54+
});
55+
56+
afterEach(() => {
57+
jest.restoreAllMocks();
58+
// Clean up environment variables
59+
process.env.SETTLEMINT_ACCESS_TOKEN = undefined;
60+
process.env.SETTLEMINT_PERSONAL_ACCESS_TOKEN = undefined;
61+
process.env.SETTLEMINT_INSTANCE = undefined;
62+
process.env.SETTLEMINT_WORKSPACE = undefined;
4563
});
4664

4765
it('installs CLI and runs command', async () => {
4866
// Mock tool cache to simulate CLI not found
49-
findMock.mockReturnValue('');
5067
cacheDirMock.mockResolvedValue('/cached/path');
5168

5269
getInputMock.mockImplementation((name) => {
@@ -65,36 +82,14 @@ describe('action', () => {
6582
});
6683

6784
await main.run();
68-
expect(runMock).toHaveReturned();
69-
70-
// Check that we tried to find cached version
71-
expect(findMock).toHaveBeenCalledWith('settlemint-cli', 'latest');
72-
73-
// Check that we installed the CLI
74-
expect(execMock).toHaveBeenCalledWith(
75-
'mkdir',
76-
expect.arrayContaining(['-p', expect.stringContaining('settlemint-cli-')])
77-
);
78-
expect(execMock).toHaveBeenCalledWith(
79-
'npm',
80-
expect.arrayContaining(['install', '--prefix', expect.any(String), '@settlemint/sdk-cli'])
81-
);
82-
83-
// Check that we cached the installation
84-
expect(cacheDirMock).toHaveBeenCalled();
85-
86-
// Check that we added to PATH
87-
expect(addPathMock).toHaveBeenCalled();
8885

89-
// Check that we ran the command
90-
expect(execMock).toHaveBeenCalledWith('settlemint', ['status']);
86+
// Check that we ran the command using npx
87+
expect(execMock).toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['status']);
9188

9289
expect(errorMock).not.toHaveBeenCalled();
93-
});
90+
}, 30000);
9491

9592
it('handles auto-connect when using a personal access token', async () => {
96-
findMock.mockReturnValue('/cached/path');
97-
9893
getInputMock.mockImplementation((name) => {
9994
switch (name) {
10095
case 'command':
@@ -111,24 +106,20 @@ describe('action', () => {
111106
});
112107

113108
await main.run();
114-
expect(runMock).toHaveReturned();
115109

116-
// Should use cached version
117-
expect(findMock).toHaveBeenCalledWith('settlemint-cli', 'latest');
118-
expect(addPathMock).toHaveBeenCalled();
110+
// Personal access token should be set as environment variable
111+
expect(process.env.SETTLEMINT_PERSONAL_ACCESS_TOKEN).toBe('sm_pat_1234567890');
119112

120-
// Should mask the access token
121-
expect(setSecretMock).toHaveBeenCalledWith('sm_pat_1234567890');
113+
// Access token should be masked
114+
expect(_setSecretMock).toHaveBeenCalledWith('sm_pat_1234567890');
122115

123-
// Should login and connect
124-
expect(execMock).toHaveBeenCalledWith('settlemint', ['login', '-a']);
125-
expect(execMock).toHaveBeenCalledWith('settlemint', ['connect', '-a']);
126-
expect(execMock).toHaveBeenCalledWith('settlemint', ['status']);
127-
});
128-
129-
it('does not auto-connect when using an application access token', async () => {
130-
findMock.mockReturnValue('/cached/path');
116+
// Should login (because of personal access token) and connect (because auto-connect is true)
117+
expect(execMock).toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['login', '-a']);
118+
expect(execMock).toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['connect', '-a']);
119+
expect(execMock).toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['status']);
120+
}, 30000);
131121

122+
it('does not login but still connects when using an application access token with auto-connect', async () => {
132123
getInputMock.mockImplementation((name) => {
133124
switch (name) {
134125
case 'command':
@@ -145,20 +136,20 @@ describe('action', () => {
145136
});
146137

147138
await main.run();
148-
expect(runMock).toHaveReturned();
149139

150-
// Should mask the access token
151-
expect(setSecretMock).toHaveBeenCalledWith('sm_app_1234567890');
140+
// Application token should be set as environment variable
141+
expect(process.env.SETTLEMINT_ACCESS_TOKEN).toBe('sm_app_1234567890');
152142

153-
// Should NOT login or connect with app token
154-
expect(execMock).not.toHaveBeenCalledWith('settlemint', ['login', '-a']);
155-
expect(execMock).not.toHaveBeenCalledWith('settlemint', ['connect', '-a']);
156-
expect(execMock).toHaveBeenCalledWith('settlemint', ['status']);
157-
});
143+
// Access token should be masked
144+
expect(_setSecretMock).toHaveBeenCalledWith('sm_app_1234567890');
158145

159-
it('sets environment variables when provided', async () => {
160-
findMock.mockReturnValue('/cached/path');
146+
// Should NOT login with app token, but should still connect because auto-connect is true
147+
expect(execMock).not.toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['login', '-a']);
148+
expect(execMock).toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['connect', '-a']);
149+
expect(execMock).toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['status']);
150+
}, 30000);
161151

152+
it('sets environment variables when provided', async () => {
162153
getInputMock.mockImplementation((name) => {
163154
switch (name) {
164155
case 'command':
@@ -177,16 +168,13 @@ describe('action', () => {
177168
});
178169

179170
await main.run();
180-
expect(runMock).toHaveReturned();
181171

182172
expect(process.env.SETTLEMINT_INSTANCE).toBe('test-instance');
183173
expect(process.env.SETTLEMINT_WORKSPACE).toBe('test-workspace');
184174
expect(process.env.SETTLEMINT_ACCESS_TOKEN).toBe('sm_app_1234567890');
185-
});
175+
}, 30000);
186176

187177
it('validates version format', async () => {
188-
findMock.mockReturnValue('');
189-
190178
getInputMock.mockImplementation((name) => {
191179
switch (name) {
192180
case 'version':
@@ -199,16 +187,12 @@ describe('action', () => {
199187
});
200188

201189
await main.run();
202-
expect(runMock).toHaveReturned();
203-
204-
expect(setFailedMock).toHaveBeenCalledWith(
190+
expect(_setFailedMock).toHaveBeenCalledWith(
205191
"Invalid version format: invalid-version. Must be a valid semver version or 'latest'"
206192
);
207-
});
193+
}, 30000);
208194

209195
it('handles command injection attempts', async () => {
210-
findMock.mockReturnValue('/cached/path');
211-
212196
getInputMock.mockImplementation((name) => {
213197
switch (name) {
214198
case 'command':
@@ -223,50 +207,56 @@ describe('action', () => {
223207
});
224208

225209
await main.run();
226-
expect(runMock).toHaveReturned();
227-
228-
expect(setFailedMock).toHaveBeenCalledWith(
210+
expect(_setFailedMock).toHaveBeenCalledWith(
229211
'Failed to execute command: Error: Command contains potentially dangerous characters. Please use simple commands only.'
230212
);
231-
});
232-
233-
it('handles installation failure with retry', async () => {
234-
findMock.mockReturnValue('');
213+
}, 30000);
235214

236-
// First npm install fails, second succeeds
237-
execMock.mockImplementation((cmd, args) => {
238-
if (cmd === 'npm' && args && args.includes('install') && args.includes('--prefix')) {
239-
throw new Error('Network error');
215+
it('supports standalone mode without access token', async () => {
216+
getInputMock.mockImplementation((name) => {
217+
switch (name) {
218+
case 'command':
219+
return 'status';
220+
case 'version':
221+
return 'latest';
222+
case 'instance':
223+
return 'standalone';
224+
case 'access-token':
225+
return ''; // No access token for standalone
226+
default:
227+
return '';
240228
}
241-
return Promise.resolve(0);
242229
});
243230

231+
await main.run();
232+
233+
// Should NOT try to login or connect in standalone mode
234+
expect(execMock).not.toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['login', '-a']);
235+
expect(execMock).not.toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['connect', '-a']);
236+
expect(execMock).toHaveBeenCalledWith('npx -y @settlemint/sdk-cli@latest', ['status']);
237+
}, 30000);
238+
239+
it('requires access token when not in standalone mode', async () => {
244240
getInputMock.mockImplementation((name) => {
245241
switch (name) {
246242
case 'command':
247243
return 'status';
248244
case 'version':
249245
return 'latest';
246+
case 'instance':
247+
return 'production';
250248
case 'access-token':
251-
return 'sm_app_1234567890';
249+
return ''; // No access token
252250
default:
253251
return '';
254252
}
255253
});
256254

257255
await main.run();
258-
expect(runMock).toHaveReturned();
259-
260-
// Should have warned about failure
261-
expect(warningMock).toHaveBeenCalledWith('Initial installation failed, retrying...');
262-
263-
// Should have tried global install as fallback
264-
expect(execMock).toHaveBeenCalledWith('npm', ['install', '-g', '@settlemint/sdk-cli@latest']);
265-
});
256+
expect(_setFailedMock).toHaveBeenCalledWith('access-token is required when not in standalone mode');
257+
}, 30000);
266258

267259
it('processes dotEnvFile content', async () => {
268-
findMock.mockReturnValue('/cached/path');
269-
270260
getInputMock.mockImplementation((name) => {
271261
switch (name) {
272262
case 'command':
@@ -282,15 +272,11 @@ describe('action', () => {
282272
}
283273
});
284274

285-
// Mock exportVariable
286-
const exportVariableMock = jest.spyOn(core, 'exportVariable').mockImplementation();
287-
288275
await main.run();
289-
expect(runMock).toHaveReturned();
290276

291277
// Check that environment variables were set correctly
292278
expect(exportVariableMock).toHaveBeenCalledWith('TEST_VAR', 'test_value');
293279
expect(exportVariableMock).toHaveBeenCalledWith('QUOTED', 'quoted value');
294280
expect(exportVariableMock).toHaveBeenCalledWith('VAR_WITH_EQUALS', 'key=value');
295-
});
281+
}, 30000);
296282
});

badges/coverage.svg

Lines changed: 1 addition & 1 deletion
Loading

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"formatter": {
1919
"lineWidth": 120
2020
},
21-
"globals": ["jest", "describe", "it", "beforeEach", "expect"]
21+
"globals": ["jest", "describe", "it", "beforeEach", "afterEach", "expect"]
2222
},
2323
"css": {
2424
"formatter": {

0 commit comments

Comments
 (0)