22 * Unit tests for the action's main functionality, src/main.ts
33 */
44
5+ import * as cache from '@actions/cache' ;
56import * as core from '@actions/core' ;
67import * as exec from '@actions/exec' ;
78import * as tc from '@actions/tool-cache' ;
89import * 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
1415let _debugMock : jest . SpiedFunction < typeof core . debug > ;
1516let errorMock : jest . SpiedFunction < typeof core . error > ;
1617let 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 > ;
2021let _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
2426let 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 > ;
2830let 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+
3036describe ( '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} ) ;
0 commit comments