@@ -8,11 +8,13 @@ import { join } from 'path';
88import { tmpdir } from 'os' ;
99
1010jest . mock ( 'child_process' , ( ) => ( {
11+ execFileSync : jest . fn ( ) ,
1112 execSync : jest . fn ( )
1213} ) ) ;
1314
14- import { execSync } from 'child_process' ;
15+ import { execFileSync , execSync } from 'child_process' ;
1516const mockExecSync = execSync as jest . MockedFunction < typeof execSync > ;
17+ const mockExecFileSync = execFileSync as jest . MockedFunction < typeof execFileSync > ;
1618
1719describe ( 'aibolit' , ( ) => {
1820 beforeEach ( ( ) => {
@@ -35,7 +37,7 @@ describe('aibolit', () => {
3537 `
3638 ) ;
3739 mockExecSync . mockReturnValueOnce ( Buffer . from ( 'aibolit 1.3.0\n' ) ) ;
38- mockExecSync . mockReturnValueOnce ( Buffer . from ( '' ) ) ;
40+ mockExecFileSync . mockReturnValueOnce ( Buffer . from ( '' ) ) ;
3941 try {
4042 const issue = await aibolit ( path ) ;
4143 expect ( issue ) . toContain ( 'Your code is perfect' ) ;
@@ -60,7 +62,7 @@ describe('aibolit', () => {
6062 `
6163 ) ;
6264 mockExecSync . mockReturnValueOnce ( Buffer . from ( 'aibolit 1.3.0\n' ) ) ;
63- mockExecSync . mockReturnValueOnce (
65+ mockExecFileSync . mockReturnValueOnce (
6466 Buffer . from ( 'Test.java[44]: Missing final keyword (P13: 5.0)\n' ) ) ;
6567 try {
6668 const issue = await aibolit ( path ) ;
@@ -116,7 +118,7 @@ describe('aibolit', () => {
116118
117119 test ( 'executes normally when version is exactly required' , async ( ) => {
118120 mockExecSync . mockReturnValueOnce ( Buffer . from ( 'aibolit 1.3.0\n' ) ) ;
119- mockExecSync . mockReturnValueOnce ( Buffer . from ( '' ) ) ;
121+ mockExecFileSync . mockReturnValueOnce ( Buffer . from ( '' ) ) ;
120122 const path = '/tmp/test.java' ;
121123 writeFileSync ( path , 'public class Test {}' ) ;
122124 const result = await aibolit ( path ) ;
@@ -125,7 +127,7 @@ describe('aibolit', () => {
125127
126128 test ( 'executes normally when version is newer than required' , async ( ) => {
127129 mockExecSync . mockReturnValueOnce ( Buffer . from ( 'aibolit 1.4.0\n' ) ) ;
128- mockExecSync . mockReturnValueOnce ( Buffer . from ( '' ) ) ;
130+ mockExecFileSync . mockReturnValueOnce ( Buffer . from ( '' ) ) ;
129131 const path = '/tmp/test.java' ;
130132 writeFileSync ( path , 'public class Test {}' ) ;
131133 const result = await aibolit ( path ) ;
@@ -173,11 +175,53 @@ describe('aibolit', () => {
173175
174176 test ( 'handles aibolit output with invalid warning format' , async ( ) => {
175177 mockExecSync . mockReturnValueOnce ( Buffer . from ( 'aibolit 1.3.0\n' ) ) ;
176- mockExecSync . mockReturnValueOnce (
178+ mockExecFileSync . mockReturnValueOnce (
177179 Buffer . from ( 'Invalid warning format\nAnother invalid line\n' ) ) ;
178180 const path = '/tmp/test.java' ;
179181 writeFileSync ( path , 'public class Test {}' ) ;
180182 const result = await aibolit ( path ) ;
181183 expect ( result ) . toBe ( 'Your code is perfect' ) ;
182184 } ) ;
185+
186+ test ( 'parses warnings captured on stdout when aibolit exits non-zero' , async ( ) => {
187+ mockExecSync . mockReturnValueOnce ( Buffer . from ( 'aibolit 1.3.0\n' ) ) ;
188+ mockExecFileSync . mockImplementationOnce ( ( ) => {
189+ const err = new Error ( 'Command failed' ) as Error & { stdout : Buffer } ;
190+ err . stdout = Buffer . from ( 'Test.java[44]: Missing final keyword (P13: 5.0)\n' ) ;
191+ throw err ;
192+ } ) ;
193+ const path = '/tmp/test.java' ;
194+ writeFileSync ( path , 'public class Test {}' ) ;
195+ const result = await aibolit ( path ) ;
196+ expect ( result ) . not . toContain ( 'Your code is perfect' ) ;
197+ expect ( result ) . toContain ( 'Missing final keyword' ) ;
198+ } ) ;
199+
200+ test ( 'returns perfect code when aibolit throws without stdout' , async ( ) => {
201+ mockExecSync . mockReturnValueOnce ( Buffer . from ( 'aibolit 1.3.0\n' ) ) ;
202+ mockExecFileSync . mockImplementationOnce ( ( ) => {
203+ throw new Error ( 'Command failed' ) ;
204+ } ) ;
205+ const path = '/tmp/test.java' ;
206+ writeFileSync ( path , 'public class Test {}' ) ;
207+ const result = await aibolit ( path ) ;
208+ expect ( result ) . toBe ( 'Your code is perfect' ) ;
209+ } ) ;
210+
211+ test ( 'passes path as separate argv element so shell metacharacters do not execute' , async ( ) => {
212+ const tmp = mkdtempSync ( join ( tmpdir ( ) , 'aibolit-test-' ) ) ;
213+ const path = join ( tmp , 'Test.java; touch pwned.java' ) ;
214+ writeFileSync ( path , 'public class Test {}' ) ;
215+ mockExecSync . mockReturnValueOnce ( Buffer . from ( 'aibolit 1.3.0\n' ) ) ;
216+ mockExecFileSync . mockReturnValueOnce ( Buffer . from ( '' ) ) ;
217+ try {
218+ await aibolit ( path ) ;
219+ expect ( mockExecFileSync ) . toHaveBeenCalledWith (
220+ 'python3' ,
221+ [ '-m' , 'aibolit' , 'check' , '--full' , '--filenames' , path ]
222+ ) ;
223+ } finally {
224+ rmSync ( tmp , { recursive : true , force : true } ) ;
225+ }
226+ } ) ;
183227} ) ;
0 commit comments