1+ import { afterEach , beforeEach , describe , expect , jest , test } from '@jest/globals' ;
2+ import { getInput , Main } from '../src/main' ;
3+ import { CommandExecutor } from '../src/exec' ;
4+ import { ReleaseData , Releases } from '../src/releases' ;
5+ import { GitOperations } from '../src/git' ;
6+
7+ const mockGetLatestReleaseData = jest . fn < InstanceType < typeof Releases > [ 'getLatestReleaseData' ] > ( ) ;
8+ const mockGetReleaseDataByName = jest . fn < InstanceType < typeof Releases > [ 'getReleaseDataByName' ] > ( ) ;
9+ jest . mock ( '../src/releases' , ( ) => {
10+ return {
11+ Releases : jest . fn ( ) . mockImplementation ( ( ) => {
12+ return {
13+ getLatestReleaseData : mockGetLatestReleaseData ,
14+ getReleaseDataByName : mockGetReleaseDataByName ,
15+ }
16+ } ) ,
17+ } ;
18+ } ) ;
19+
20+ const mockHasChanges = jest . fn < InstanceType < typeof GitOperations > [ 'hasChanges' ] > ( ) ;
21+ const mockCommitAll = jest . fn < InstanceType < typeof GitOperations > [ 'commitAll' ] > ( ) ;
22+ const mockPush = jest . fn < InstanceType < typeof GitOperations > [ 'push' ] > ( ) ;
23+ jest . mock ( '../src/git' , ( ) => {
24+ return {
25+ GitOperations : jest . fn ( ) . mockImplementation ( ( ) => {
26+ return {
27+ configureGit : ( ) => { } ,
28+ commitAll : mockCommitAll ,
29+ hasChanges : mockHasChanges ,
30+ push : mockPush ,
31+ }
32+ } ) ,
33+ } ;
34+ } ) ;
35+
36+ const executor = jest . fn < CommandExecutor > ( ) ;
37+
38+ beforeEach ( ( ) => {
39+ executor . mockReset ( ) ;
40+ } ) ;
41+
42+ const dummyReleaseData : ReleaseData = {
43+ url : "" ,
44+ html_url : "" ,
45+ assets_url : "" ,
46+ upload_url : "" ,
47+ tarball_url : null ,
48+ zipball_url : null ,
49+ id : 0 ,
50+ node_id : "" ,
51+ tag_name : "" ,
52+ target_commitish : "" ,
53+ name : "dummy-release-data" ,
54+ draft : false ,
55+ prerelease : false ,
56+ created_at : "" ,
57+ published_at : null ,
58+ author : {
59+ name : undefined ,
60+ email : undefined ,
61+ login : "" ,
62+ id : 0 ,
63+ node_id : "" ,
64+ avatar_url : "" ,
65+ gravatar_id : null ,
66+ url : "" ,
67+ html_url : "" ,
68+ followers_url : "" ,
69+ following_url : "" ,
70+ gists_url : "" ,
71+ starred_url : "" ,
72+ subscriptions_url : "" ,
73+ organizations_url : "" ,
74+ repos_url : "" ,
75+ events_url : "" ,
76+ received_events_url : "" ,
77+ type : "" ,
78+ site_admin : false ,
79+ starred_at : undefined
80+ } ,
81+ assets : [ ]
82+ } ;
83+
84+ function defineInput ( inputName : string , inputValue ?: string ) {
85+ process . env [ `INPUT_${ inputName . toUpperCase ( ) } ` ] = inputValue ;
86+ }
87+
88+ describe ( 'test getting inputs' , ( ) => {
89+ const existingInputName = "my-input" ;
90+ const existingInputValue = "my-input-value" ;
91+
92+ beforeEach ( ( ) => {
93+ defineInput ( existingInputName , existingInputValue ) ;
94+ } ) ;
95+
96+ afterEach ( ( ) => {
97+ defineInput ( existingInputName , undefined ) ;
98+ } ) ;
99+
100+ test ( 'test getting existing input value' , ( ) => {
101+ const value = getInput ( [ existingInputName , "non-existing-input" ] ) ;
102+ expect ( value ) . toBe ( existingInputValue ) ;
103+ } ) ;
104+
105+ test ( 'test getting existing input value with fallback input name' , ( ) => {
106+ const value = getInput ( [ "input_name_one" , existingInputName ] ) ;
107+ expect ( value ) . toBe ( existingInputValue ) ;
108+ } ) ;
109+
110+ test ( 'test getting non existing input value' , ( ) => {
111+ const inputName = "my-non-existing-input" ;
112+ const value = getInput ( [ inputName ] ) ;
113+ expect ( value ) . toBeUndefined ( ) ;
114+ } ) ;
115+ } ) ;
116+
117+ describe ( 'test getting Java version' , ( ) => {
118+ const main = new Main ( executor ) ;
119+
120+ test ( 'test invalid return code' , ( ) => {
121+ const error = new Error ( "Command 'java -version' failed with exit code 1" ) ;
122+ executor . mockReturnValueOnce ( Promise . reject ( error ) ) ;
123+ expect ( ( ) => main . getJavaVersion ( ) )
124+ . rejects
125+ . toThrow ( error ) ;
126+ expect ( executor ) . lastCalledWith ( 'java' , [ '-version' ] , { ignoreReturnCode : false , silent : true } ) ;
127+ } ) ;
128+
129+ test ( 'test no output' , ( ) => {
130+ executor . mockReturnValueOnce ( Promise . resolve ( {
131+ exitCode : 0 ,
132+ stdErr : '' ,
133+ stdOut : '' ,
134+ } ) ) ;
135+ expect ( ( ) => main . getJavaVersion ( ) )
136+ . rejects
137+ . toThrow ( new Error ( "Cannot find Java version number" ) ) ;
138+ expect ( executor ) . lastCalledWith ( 'java' , [ '-version' ] , { ignoreReturnCode : false , silent : true } ) ;
139+ } ) ;
140+
141+ test ( 'test version <= JDK 8' , ( ) => {
142+ executor . mockReturnValueOnce ( Promise . resolve ( {
143+ exitCode : 0 ,
144+ stdErr : 'java version "1.8.0_211"' ,
145+ stdOut : '' ,
146+ } ) ) ;
147+ expect ( main . getJavaVersion ( ) )
148+ . resolves
149+ . toBe ( 8 ) ;
150+ expect ( executor ) . lastCalledWith ( 'java' , [ '-version' ] , { ignoreReturnCode : false , silent : true } ) ;
151+ } ) ;
152+
153+ test ( 'test version > JDK 8' , ( ) => {
154+ executor . mockReturnValueOnce ( Promise . resolve ( {
155+ exitCode : 0 ,
156+ stdErr : 'openjdk version "21.0.6" 2025-01-21' ,
157+ stdOut : '' ,
158+ } ) ) ;
159+ expect ( main . getJavaVersion ( ) )
160+ . resolves
161+ . toBe ( 21 ) ;
162+ expect ( executor ) . lastCalledWith ( 'java' , [ '-version' ] , { ignoreReturnCode : false , silent : true } ) ;
163+ } ) ;
164+ } ) ;
165+
166+ describe ( 'test getting release data' , ( ) => {
167+ test ( 'if no release name and java version >= 11, then return latest release' , async ( ) => {
168+ const main = new Main ( executor ) ;
169+ mockGetLatestReleaseData . mockReturnValueOnce ( Promise . resolve ( dummyReleaseData ) ) ;
170+ const releaseData = await main . getReleaseData ( 11 , undefined ) ;
171+ expect ( releaseData ) . toEqual ( dummyReleaseData ) ;
172+ } ) ;
173+
174+ test ( 'if no release name and java version < 11, then return release 1.7' , async ( ) => {
175+ const main = new Main ( executor ) ;
176+ mockGetReleaseDataByName . mockReturnValueOnce ( Promise . resolve ( dummyReleaseData ) ) ;
177+ const releaseData = await main . getReleaseData ( 8 , undefined ) ;
178+ expect ( releaseData ) . toEqual ( dummyReleaseData ) ;
179+ expect ( mockGetReleaseDataByName ) . lastCalledWith ( '1.7' ) ;
180+ } ) ;
181+
182+ test ( 'if release name, then return it' , async ( ) => {
183+ const releaseName = 'my-release' ;
184+ const main = new Main ( executor ) ;
185+ mockGetReleaseDataByName . mockReturnValueOnce ( Promise . resolve ( dummyReleaseData ) ) ;
186+ const releaseData = await main . getReleaseData ( 11 , releaseName ) ;
187+ expect ( releaseData ) . toEqual ( dummyReleaseData ) ;
188+ expect ( mockGetReleaseDataByName ) . lastCalledWith ( releaseName ) ;
189+ } ) ;
190+
191+ test ( 'if release not found, then throw' , async ( ) => {
192+ const releaseName = 'my-release' ;
193+ defineInput ( 'version' , releaseName ) ;
194+ const main = new Main ( executor ) ;
195+ mockGetReleaseDataByName . mockReturnValueOnce ( Promise . resolve ( undefined ) ) ;
196+ expect ( ( ) => main . getReleaseData ( 11 , releaseName ) )
197+ . rejects
198+ . toThrow ( new Error ( `Cannot find release id of Google Java Format ${ releaseName } ` ) ) ;
199+ expect ( mockGetReleaseDataByName ) . lastCalledWith ( releaseName ) ;
200+ } ) ;
201+ } ) ;
202+
203+ describe ( 'test getting args' , ( ) => {
204+ const main = new Main ( executor ) ;
205+
206+ test ( 'if input has args, then output has them too' , async ( ) => {
207+ const inputs = { args : [ '--replace' ] , files : '**/*.java' , filesExcluded : undefined } ;
208+ const output = await main . getGJFArgs ( inputs ) ;
209+ expect ( output ) . toEqual ( [ '--replace' ] ) ;
210+ } ) ;
211+
212+ test ( 'files matching glob are appended to output' , async ( ) => {
213+ const inputs = { args : [ '--replace' ] , files : '*.md' , filesExcluded : undefined } ;
214+ const output = await main . getGJFArgs ( inputs ) ;
215+ expect ( output ) . toHaveLength ( 2 ) ;
216+ expect ( output [ 0 ] ) . toEqual ( '--replace' ) ;
217+ expect ( output [ 1 ] ) . toMatch ( / ^ .* R E A D M E \. m d $ / ) ;
218+ } ) ;
219+
220+ test ( 'if input has exclusion glob, then output excludes matching files' , async ( ) => {
221+ const inputs = { args : [ '--replace' ] , files : '*.md' , filesExcluded : '*.md' } ;
222+ const output = await main . getGJFArgs ( inputs ) ;
223+ expect ( output ) . toEqual ( [ '--replace' ] ) ;
224+ } )
225+ } ) ;
226+
227+ describe ( 'commit changes' , ( ) => {
228+ const githubToken = '***' ;
229+ defineInput ( 'github-token' , githubToken ) ;
230+ const main = new Main ( executor ) ;
231+
232+ test ( 'if there is no change, then skip commit' , async ( ) => {
233+ mockHasChanges . mockReturnValueOnce ( Promise . resolve ( false ) ) ;
234+ await main . commitChanges ( { githubActor : '' , repository : '' , commitMessage : undefined , } ) ;
235+ expect ( mockCommitAll ) . not . toBeCalled ( ) ;
236+ expect ( mockPush ) . not . toBeCalled ( ) ;
237+ } ) ;
238+
239+ test ( 'if there are changes, but no commit message, then commit with default message' , async ( ) => {
240+ mockHasChanges . mockReturnValueOnce ( Promise . resolve ( true ) ) ;
241+ const githubActor = "actor" ;
242+ const repository = "actor/repo" ;
243+ await main . commitChanges ( { githubActor, repository, commitMessage : undefined } ) ;
244+ expect ( mockCommitAll ) . toHaveBeenCalledWith ( 'Google Java Format' ) ;
245+ expect ( mockPush ) . toBeCalledWith ( { githubActor, repository, githubToken } ) ;
246+ } ) ;
247+
248+ test ( 'if there are changes, and a commit message, then commit with message' , async ( ) => {
249+ mockHasChanges . mockReturnValueOnce ( Promise . resolve ( true ) ) ;
250+ const githubActor = "actor" ;
251+ const repository = "actor/repo" ;
252+ const commitMessage = "my message" ;
253+ await main . commitChanges ( { githubActor, repository, commitMessage } ) ;
254+ expect ( mockCommitAll ) . toHaveBeenCalledWith ( commitMessage ) ;
255+ expect ( mockPush ) . toBeCalledWith ( { githubActor, repository, githubToken } ) ;
256+ } ) ;
257+ } ) ;
258+
259+ describe ( 'execute Google Java Format' , ( ) => {
260+ const executablePath = 'google-java-format.jar' ;
261+ const main = new Main ( executor , executablePath ) ;
262+
263+ test ( 'when running GJF, then pass user args to command' , async ( ) => {
264+ const mockedResult = { exitCode : 0 , stdErr : '' , stdOut : '' } ;
265+ executor . mockReturnValueOnce ( Promise . resolve ( mockedResult ) ) ;
266+ const args = [ 'a' , 'b' , 'c' ] ;
267+ const result = await main . executeGJF ( 8 , args ) ;
268+ expect ( result ) . toEqual ( mockedResult ) ;
269+ expect ( executor ) . lastCalledWith ( 'java' , [ '-jar' , executablePath , 'a' , 'b' , 'c' ] , { ignoreReturnCode : false } ) ;
270+ } ) ;
271+
272+ test ( 'when running GJF with java version >= 11, then exports required jdk modules' , async ( ) => {
273+ const mockedResult = { exitCode : 0 , stdErr : '' , stdOut : '' } ;
274+ executor . mockReturnValueOnce ( Promise . resolve ( mockedResult ) ) ;
275+ const args = [ 'a' , 'b' , 'c' ] ;
276+ const result = await main . executeGJF ( 11 , args ) ;
277+ expect ( result ) . toEqual ( mockedResult ) ;
278+ expect ( executor ) . lastCalledWith (
279+ 'java' ,
280+ [
281+ '--add-exports' ,
282+ 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' ,
283+ '--add-exports' ,
284+ 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' ,
285+ '--add-exports' ,
286+ 'jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' ,
287+ '--add-exports' ,
288+ 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' ,
289+ '--add-exports' ,
290+ 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED' ,
291+ '-jar' ,
292+ executablePath ,
293+ 'a' ,
294+ 'b' ,
295+ 'c' ,
296+ ] ,
297+ { ignoreReturnCode : false }
298+ ) ;
299+ } ) ;
300+ } ) ;
0 commit comments