@@ -9,9 +9,12 @@ jest.mock('fs', () => ({
99 existsSync : jest . fn ( ) ,
1010 readFileSync : jest . fn ( ) ,
1111 writeFileSync : jest . fn ( ) ,
12+ readdirSync : jest . fn ( ) ,
13+ statSync : jest . fn ( ) ,
1214} ) ) ;
1315jest . mock ( 'path' , ( ) => ( {
1416 resolve : jest . fn ( ( _ , p ) => p ) ,
17+ join : jest . fn ( ( ...args ) => args . join ( '/' ) ) ,
1518} ) ) ;
1619jest . mock ( '@actions/core' , ( ) => ( {
1720 info : jest . fn ( ) ,
@@ -69,12 +72,13 @@ describe('coverage-evaluator', () => {
6972 mockGetProjectThresholds . mockReturnValue ( { lines : 80 , statements : 75 } ) ;
7073
7174 const mockExistsSync = fs . existsSync as jest . Mock ;
75+ // All potential coverage file paths return false
7276 mockExistsSync . mockReturnValue ( false ) ;
7377
7478 const result = evaluateCoverage ( [ 'project-a' ] , { global : { } , projects : { } } ) ;
7579
7680 expect ( result ) . toBe ( 1 ) ;
77- expect ( core . warning ) . toHaveBeenCalledWith ( 'No coverage report found for project-a at coverage/project-a/coverage-summary.json ' ) ;
81+ expect ( core . warning ) . toHaveBeenCalledWith ( 'No coverage report found for project-a' ) ;
7882
7983 // Verify that the comment shows individual thresholds with "No Data"
8084 const writeFileSyncMock = fs . writeFileSync as jest . Mock ;
@@ -94,7 +98,8 @@ describe('coverage-evaluator', () => {
9498 } ) ;
9599
96100 const mockExistsSync = fs . existsSync as jest . Mock ;
97- mockExistsSync . mockReturnValue ( true ) ;
101+ // First path (specific project path) returns true
102+ mockExistsSync . mockImplementation ( ( path ) => path . includes ( 'coverage/project-a/coverage-summary.json' ) ) ;
98103
99104 const mockReadFileSync = fs . readFileSync as jest . Mock ;
100105 mockReadFileSync . mockReturnValue ( JSON . stringify ( {
@@ -132,7 +137,7 @@ describe('coverage-evaluator', () => {
132137 } ) ;
133138
134139 const mockExistsSync = fs . existsSync as jest . Mock ;
135- mockExistsSync . mockReturnValue ( true ) ;
140+ mockExistsSync . mockImplementation ( ( path ) => path . includes ( 'coverage/project-a/coverage-summary.json' ) ) ;
136141
137142 const mockReadFileSync = fs . readFileSync as jest . Mock ;
138143 mockReadFileSync . mockReturnValue ( JSON . stringify ( {
@@ -169,7 +174,7 @@ describe('coverage-evaluator', () => {
169174 } ) ;
170175
171176 const mockExistsSync = fs . existsSync as jest . Mock ;
172- mockExistsSync . mockReturnValue ( true ) ;
177+ mockExistsSync . mockImplementation ( ( path ) => path . includes ( 'coverage/project-a/coverage-summary.json' ) ) ;
173178
174179 const mockReadFileSync = fs . readFileSync as jest . Mock ;
175180 mockReadFileSync . mockImplementation ( ( ) => {
@@ -179,7 +184,7 @@ describe('coverage-evaluator', () => {
179184 const result = evaluateCoverage ( [ 'project-a' ] , { global : { } , projects : { } } ) ;
180185
181186 expect ( result ) . toBe ( 1 ) ;
182- expect ( core . error ) . toHaveBeenCalledWith ( 'Error processing coverage for project-a: Test error' ) ;
187+ expect ( core . error ) . toHaveBeenCalledWith ( 'Error reading coverage file coverage/ project-a/coverage-summary.json : Test error' ) ;
183188
184189 // Verify that the comment shows individual thresholds with "No Data"
185190 const writeFileSyncMock = fs . writeFileSync as jest . Mock ;
@@ -200,7 +205,7 @@ describe('coverage-evaluator', () => {
200205 } ) ;
201206
202207 const mockExistsSync = fs . existsSync as jest . Mock ;
203- mockExistsSync . mockReturnValue ( true ) ;
208+ mockExistsSync . mockImplementation ( ( path ) => path . includes ( 'coverage/project-a/coverage-summary.json' ) ) ;
204209
205210 const mockReadFileSync = fs . readFileSync as jest . Mock ;
206211 mockReadFileSync . mockReturnValue ( JSON . stringify ( {
@@ -231,7 +236,7 @@ describe('coverage-evaluator', () => {
231236 mockGetProjectThresholds . mockReturnValue ( { } ) ;
232237
233238 const mockExistsSync = fs . existsSync as jest . Mock ;
234- mockExistsSync . mockReturnValue ( true ) ;
239+ mockExistsSync . mockImplementation ( ( path ) => path . includes ( 'coverage/project-a/coverage-summary.json' ) ) ;
235240
236241 const mockReadFileSync = fs . readFileSync as jest . Mock ;
237242 mockReadFileSync . mockReturnValue ( JSON . stringify ( {
@@ -250,6 +255,77 @@ describe('coverage-evaluator', () => {
250255 expect ( core . info ) . toHaveBeenCalledWith ( 'Project project-a passed all coverage thresholds' ) ;
251256 } ) ;
252257
258+ it ( 'should find coverage files in alternative locations' , ( ) => {
259+ const mockGetProjectThresholds = getProjectThresholds as jest . Mock ;
260+ mockGetProjectThresholds . mockReturnValue ( {
261+ lines : 80 ,
262+ statements : 80 ,
263+ functions : 75 ,
264+ branches : 70
265+ } ) ;
266+
267+ const mockExistsSync = fs . existsSync as jest . Mock ;
268+ // First path fails, second path (apps/) succeeds
269+ mockExistsSync . mockImplementation ( ( path ) => {
270+ return path . includes ( 'coverage/apps/project-a/coverage-summary.json' ) ;
271+ } ) ;
272+
273+ const mockReadFileSync = fs . readFileSync as jest . Mock ;
274+ mockReadFileSync . mockReturnValue ( JSON . stringify ( {
275+ total : {
276+ lines : { pct : 85 } ,
277+ statements : { pct : 85 } ,
278+ functions : { pct : 80 } ,
279+ branches : { pct : 75 }
280+ }
281+ } ) ) ;
282+
283+ const result = evaluateCoverage ( [ 'project-a' ] , { global : { } , projects : { } } ) ;
284+
285+ expect ( result ) . toBe ( 0 ) ;
286+ expect ( core . info ) . toHaveBeenCalledWith ( 'Found coverage file for project-a at: coverage/apps/project-a/coverage-summary.json' ) ;
287+ expect ( core . info ) . toHaveBeenCalledWith ( 'Project project-a passed all coverage thresholds' ) ;
288+ } ) ;
289+
290+ it ( 'should handle global coverage file with project-specific data' , ( ) => {
291+ const mockGetProjectThresholds = getProjectThresholds as jest . Mock ;
292+ mockGetProjectThresholds . mockReturnValue ( {
293+ lines : 80 ,
294+ statements : 80 ,
295+ functions : 75 ,
296+ branches : 70
297+ } ) ;
298+
299+ const mockExistsSync = fs . existsSync as jest . Mock ;
300+ // Only the global coverage file exists
301+ mockExistsSync . mockImplementation ( ( path ) => {
302+ return path . includes ( 'coverage/coverage-summary.json' ) ;
303+ } ) ;
304+
305+ const mockReadFileSync = fs . readFileSync as jest . Mock ;
306+ // Global file contains data for multiple projects
307+ mockReadFileSync . mockReturnValue ( JSON . stringify ( {
308+ 'project-a' : {
309+ lines : { pct : 85 } ,
310+ statements : { pct : 85 } ,
311+ functions : { pct : 80 } ,
312+ branches : { pct : 75 }
313+ } ,
314+ 'project-b' : {
315+ lines : { pct : 90 } ,
316+ statements : { pct : 90 } ,
317+ functions : { pct : 85 } ,
318+ branches : { pct : 80 }
319+ }
320+ } ) ) ;
321+
322+ const result = evaluateCoverage ( [ 'project-a' ] , { global : { } , projects : { } } ) ;
323+
324+ expect ( result ) . toBe ( 0 ) ;
325+ expect ( core . info ) . toHaveBeenCalledWith ( 'Using project-specific coverage for project-a from global file' ) ;
326+ expect ( core . info ) . toHaveBeenCalledWith ( 'Project project-a passed all coverage thresholds' ) ;
327+ } ) ;
328+
253329 it ( 'should count multiple failures correctly' , ( ) => {
254330 // First project passes
255331 // Second project is skipped
@@ -283,7 +359,7 @@ describe('coverage-evaluator', () => {
283359 if ( path . includes ( 'project-d' ) ) {
284360 return false ; // No coverage file for project-d
285361 }
286- return true ;
362+ return path . includes ( 'project-a' ) || path . includes ( 'project-c' ) ;
287363 } ) ;
288364
289365 const mockReadFileSync = fs . readFileSync as jest . Mock ;
@@ -342,6 +418,32 @@ describe('coverage-evaluator', () => {
342418 // Artifact URL is included
343419 expect ( comment ) . toContain ( '📊 [View Detailed HTML Coverage Reports](https://example.com/artifact)' ) ;
344420 } ) ;
421+
422+ it ( 'should list available coverage files when debugging' , ( ) => {
423+ const mockGetProjectThresholds = getProjectThresholds as jest . Mock ;
424+ mockGetProjectThresholds . mockReturnValue ( { lines : 80 } ) ;
425+
426+ const mockExistsSync = fs . existsSync as jest . Mock ;
427+ mockExistsSync . mockImplementation ( ( path ) => {
428+ // Coverage directory exists, but no project-specific files
429+ return path === 'coverage' ;
430+ } ) ;
431+
432+ const mockReaddirSync = fs . readdirSync as jest . Mock ;
433+ const mockStatSync = fs . statSync as jest . Mock ;
434+
435+ // Mock directory structure
436+ mockReaddirSync . mockReturnValue ( [ 'other-project' , 'coverage-summary.json' ] ) ;
437+ mockStatSync . mockImplementation ( ( path ) => ( {
438+ isDirectory : ( ) => path . includes ( 'other-project' )
439+ } ) ) ;
440+
441+ const result = evaluateCoverage ( [ 'project-a' ] , { global : { } , projects : { } } ) ;
442+
443+ expect ( result ) . toBe ( 1 ) ;
444+ expect ( core . warning ) . toHaveBeenCalledWith ( 'No coverage report found for project-a' ) ;
445+ expect ( core . warning ) . toHaveBeenCalledWith ( 'No coverage file found for project-a. Available coverage files:' ) ;
446+ } ) ;
345447 } ) ;
346448
347449 describe ( 'generateEmptyCoverageReport' , ( ) => {
0 commit comments