@@ -26,7 +26,31 @@ jest.unstable_mockModule('formdata-node/file-from-path', () => ({
2626
2727// The module being tested should be imported dynamically. This ensures that the
2828// mocks are used in place of any actual dependencies.
29- const { run, formatPumpRoomResponse } = await import ( '../src/main.ts' )
29+ // Import the PumpRoomApiResponse interface to avoid using 'any'
30+ interface PumpRoomApiResponse {
31+ repo_updated : boolean
32+ pushed_at : string
33+ tasks_current : number
34+ tasks_updated : number
35+ tasks_created : number
36+ tasks_deleted : number
37+ tasks_cached : number
38+ tasks_synchronized_with_cms : number
39+ }
40+
41+ let run : ( ) => Promise < void >
42+ let formatPumpRoomResponse : ( response : PumpRoomApiResponse ) => string
43+ let validateUniqueFolderNames : ( rootDir : string ) => Promise < void >
44+ let validateInzhenerkaYml : ( rootDir : string ) => Promise < void >
45+
46+ // Import the module in beforeAll to ensure mocks are set up first
47+ beforeAll ( async ( ) => {
48+ const mainModule = await import ( '../src/main.ts' )
49+ run = mainModule . run
50+ formatPumpRoomResponse = mainModule . formatPumpRoomResponse
51+ validateUniqueFolderNames = mainModule . validateUniqueFolderNames
52+ validateInzhenerkaYml = mainModule . validateInzhenerkaYml
53+ } )
3054
3155describe ( 'main.ts' , ( ) => {
3256 const mockRootDir = '/mock/root/dir'
@@ -44,8 +68,11 @@ describe('main.ts', () => {
4468
4569 // Set up fs mocks
4670 fs . readdirSync . mockReturnValue ( [ 'file1.txt' , 'file2.txt' , 'dir1' ] )
47- fs . statSync . mockImplementation ( ( path ) => ( {
48- isDirectory : ( ) => path . includes ( 'dir' )
71+ fs . statSync . mockImplementation ( ( filePath ) => ( {
72+ isDirectory : ( ) => {
73+ if ( ! filePath || typeof filePath !== 'string' ) return false
74+ return filePath . includes ( 'dir' )
75+ }
4976 } ) )
5077 fs . unlinkSync . mockImplementation ( ( ) => { } )
5178
@@ -95,13 +122,30 @@ describe('main.ts', () => {
95122 } )
96123
97124 it ( 'Creates a ZIP archive and uploads it successfully' , async ( ) => {
98- await run ( )
125+ // Set up fs.readdirSync to return some files for the createZipArchive function
126+ fs . readdirSync . mockReturnValue ( [ 'file1.txt' , 'dir1' ] )
127+
128+ // Set up fs.statSync to identify directories correctly
129+ fs . statSync . mockImplementation ( ( filePath ) => ( {
130+ isDirectory : ( ) => {
131+ if ( ! filePath || typeof filePath !== 'string' ) return false
132+ return filePath . includes ( 'dir' )
133+ }
134+ } ) )
99135
100- // Verify that the ZIP constructor was called
101- expect ( admZip ) . toHaveBeenCalled ( )
136+ // Run the main function
137+ await run ( )
102138
103- // Verify success message was logged
104- expect ( core . info ) . toHaveBeenCalled ( )
139+ // Verify that core.info was called with validation messages
140+ expect ( core . info ) . toHaveBeenCalledWith (
141+ '🔍 Validating unique folder names...'
142+ )
143+ expect ( core . info ) . toHaveBeenCalledWith (
144+ '✅ No folder duplicates found'
145+ )
146+ expect ( core . info ) . toHaveBeenCalledWith (
147+ '🔍 Validating .inzhenerka.yml...'
148+ )
105149
106150 // Since we're mocking the API response and not actually calling the real API,
107151 // we can't directly test the formatted output in this test.
@@ -177,4 +221,137 @@ describe('main.ts', () => {
177221 // Verify that the action was marked as failed
178222 expect ( core . setFailed ) . toHaveBeenCalledWith ( 'File system error' )
179223 } )
224+
225+ // The validation functions are already imported in the beforeAll hook
226+
227+ describe ( 'validateUniqueFolderNames' , ( ) => {
228+ beforeEach ( ( ) => {
229+ // Reset mocks
230+ jest . resetAllMocks ( )
231+
232+ // Default mock for fs.readdirSync and fs.statSync
233+ fs . readdirSync . mockReturnValue ( [ 'folder1' , 'folder2' , 'file.txt' ] )
234+ fs . statSync . mockImplementation ( ( filePath ) => ( {
235+ isDirectory : ( ) => {
236+ if ( ! filePath || typeof filePath !== 'string' ) return false
237+ return ! filePath . includes ( 'file' )
238+ }
239+ } ) )
240+ } )
241+
242+ it ( 'Successfully validates when no duplicates exist' , async ( ) => {
243+ // Mock directories that will be recognized by isDirectory
244+ fs . readdirSync . mockReturnValue ( [ 'dir1' , 'dir2' ] )
245+ fs . statSync . mockImplementation ( ( ) => ( {
246+ isDirectory : ( ) => true
247+ } ) )
248+
249+ await validateUniqueFolderNames ( mockRootDir )
250+
251+ // Verify that success message was logged
252+ expect ( core . info ) . toHaveBeenCalledWith ( '✅ No folder duplicates found' )
253+ } )
254+
255+ it ( 'Detects case-insensitive duplicates' , async ( ) => {
256+ // Mock folders with case-insensitive duplicates
257+ fs . readdirSync . mockReturnValue ( [ 'Folder1' , 'folder1' , 'folder2' ] )
258+ fs . statSync . mockImplementation ( ( ) => ( {
259+ isDirectory : ( ) => true
260+ } ) )
261+
262+ // Expect the function to throw an error
263+ await expect ( validateUniqueFolderNames ( mockRootDir ) ) . rejects . toThrow (
264+ '❌ Folder duplicates found:'
265+ )
266+ } )
267+
268+ it ( 'Handles empty directory' , async ( ) => {
269+ // Mock empty directory
270+ fs . readdirSync . mockReturnValue ( [ ] )
271+
272+ await validateUniqueFolderNames ( mockRootDir )
273+
274+ // Verify that info message was logged
275+ expect ( core . info ) . toHaveBeenCalledWith ( 'ℹ️ No folders found to validate' )
276+ } )
277+
278+ it ( 'Handles directory with no subdirectories' , async ( ) => {
279+ // Mock directory with only files
280+ fs . readdirSync . mockReturnValue ( [ 'file1.txt' , 'file2.txt' ] )
281+ fs . statSync . mockImplementation ( ( ) => ( {
282+ isDirectory : ( ) => false
283+ } ) )
284+
285+ await validateUniqueFolderNames ( mockRootDir )
286+
287+ // Verify that info message was logged
288+ expect ( core . info ) . toHaveBeenCalledWith ( 'ℹ️ No folders found to validate' )
289+ } )
290+ } )
291+
292+ describe ( 'validateInzhenerkaYml' , ( ) => {
293+ beforeEach ( ( ) => {
294+ // Reset mocks
295+ jest . resetAllMocks ( )
296+
297+ // Default mock for fs.existsSync and fs.readFileSync
298+ fs . existsSync . mockReturnValue ( true )
299+ fs . readFileSync . mockReturnValue ( 'valid: yaml\ncontent: true' )
300+
301+ // Default mock for axios.post
302+ axios . post . mockResolvedValue ( { status : 200 } )
303+ } )
304+
305+ it ( 'Successfully validates when configuration is valid' , async ( ) => {
306+ await validateInzhenerkaYml ( mockRootDir )
307+
308+ // Verify that success message was logged
309+ expect ( core . info ) . toHaveBeenCalledWith ( '✅ Configuration is valid' )
310+ } )
311+
312+ it ( 'Throws error when configuration file is not found' , async ( ) => {
313+ // Mock file not found
314+ fs . existsSync . mockReturnValue ( false )
315+
316+ // Expect the function to throw an error
317+ await expect ( validateInzhenerkaYml ( mockRootDir ) ) . rejects . toThrow (
318+ '❌ .inzhenerka.yml file not found'
319+ )
320+ } )
321+
322+ it ( 'Throws error when API returns non-200 status' , async ( ) => {
323+ // Mock API error
324+ axios . post . mockResolvedValue ( { status : 400 } )
325+
326+ // Expect the function to throw an error
327+ await expect ( validateInzhenerkaYml ( mockRootDir ) ) . rejects . toThrow (
328+ '❌ Configuration is invalid'
329+ )
330+ } )
331+
332+ it ( 'Handles API request error' , async ( ) => {
333+ // Create a custom error object that will be recognized as an Axios error
334+ const axiosError = new Error ( 'API Error' )
335+ Object . defineProperty ( axiosError , 'isAxiosError' , { value : true } )
336+ Object . defineProperty ( axiosError , 'response' , {
337+ value : {
338+ status : 400 ,
339+ data : { error : 'Bad Request' }
340+ }
341+ } )
342+
343+ // Mock axios.isAxiosError to return true for this error
344+ axios . isAxiosError . mockImplementation ( ( error ) => {
345+ return error && error . isAxiosError === true
346+ } )
347+
348+ // Mock axios.post to reject with the error
349+ axios . post . mockRejectedValueOnce ( axiosError )
350+
351+ // Expect the function to throw an error
352+ await expect ( validateInzhenerkaYml ( mockRootDir ) ) . rejects . toThrow (
353+ '❌ Configuration validation failed:'
354+ )
355+ } )
356+ } )
180357} )
0 commit comments