1+ 'use strict' ;
2+
3+ const fs = require ( 'fs' ) ;
4+ const path = require ( 'path' ) ;
5+ const fileUtils = require ( '../src/util/fileUtils' ) ;
6+
7+ describe ( 'FileUtils' , ( ) => {
8+ const testDir = path . join ( __dirname , 'temp' ) ;
9+ const testFile = path . join ( testDir , 'test.txt' ) ;
10+ const testContent = 'Hello, World!' ;
11+ const jsonContent = JSON . stringify ( { hello : 'world' } ) ;
12+
13+ beforeAll ( ( ) => {
14+ // Create test directory if it doesn't exist
15+ if ( ! fs . existsSync ( testDir ) ) {
16+ fs . mkdirSync ( testDir ) ;
17+ }
18+ } ) ;
19+
20+ afterAll ( ( ) => {
21+ // Cleanup test directory
22+ if ( fs . existsSync ( testDir ) ) {
23+ fs . rmSync ( testDir , { recursive : true , force : true } ) ;
24+ }
25+ } ) ;
26+
27+ beforeEach ( ( ) => {
28+ // Create a test file before each test
29+ fs . writeFileSync ( testFile , testContent ) ;
30+ } ) ;
31+
32+ afterEach ( ( ) => {
33+ // Clean up test files after each test
34+ if ( fs . existsSync ( testFile ) ) {
35+ fs . unlinkSync ( testFile ) ;
36+ }
37+ } ) ;
38+
39+ describe ( 'Synchronous Operations' , ( ) => {
40+ describe ( 'readFile' , ( ) => {
41+ it ( 'should read file content synchronously' , ( ) => {
42+ const content = fileUtils . readFile ( testFile ) ;
43+ expect ( content ) . toBe ( testContent ) ;
44+ } ) ;
45+
46+ it ( 'should read file with specified encoding' , ( ) => {
47+ const content = fileUtils . readFile ( testFile , 'utf8' ) ;
48+ expect ( content ) . toBe ( testContent ) ;
49+ } ) ;
50+
51+ it ( 'should throw error for non-existent file' , ( ) => {
52+ expect ( ( ) => {
53+ fileUtils . readFile ( 'nonexistent.txt' ) ;
54+ } ) . toThrow ( ) ;
55+ } ) ;
56+ } ) ;
57+
58+ describe ( 'writeFile' , ( ) => {
59+ const outputFile = path . join ( testDir , 'output.json' ) ;
60+
61+ afterEach ( ( ) => {
62+ if ( fs . existsSync ( outputFile ) ) {
63+ fs . unlinkSync ( outputFile ) ;
64+ }
65+ } ) ;
66+
67+ it ( 'should write JSON content to file' , ( done ) => {
68+ fileUtils . writeFile ( jsonContent , outputFile ) ;
69+
70+ // Since writeFile is async but doesn't return a promise,
71+ // we need to wait a bit before checking
72+ setTimeout ( ( ) => {
73+ expect ( fs . existsSync ( outputFile ) ) . toBe ( true ) ;
74+ const content = fs . readFileSync ( outputFile , 'utf8' ) ;
75+ expect ( content ) . toBe ( jsonContent ) ;
76+ done ( ) ;
77+ } , 100 ) ;
78+ } ) ;
79+ } ) ;
80+ } ) ;
81+
82+ describe ( 'Asynchronous Operations' , ( ) => {
83+ describe ( 'readFileAsync' , ( ) => {
84+ it ( 'should read file content asynchronously' , async ( ) => {
85+ const content = await fileUtils . readFileAsync ( testFile ) ;
86+ expect ( content ) . toBe ( testContent ) ;
87+ } ) ;
88+
89+ it ( 'should read file with specified encoding' , async ( ) => {
90+ const content = await fileUtils . readFileAsync ( testFile , 'utf8' ) ;
91+ expect ( content ) . toBe ( testContent ) ;
92+ } ) ;
93+
94+ it ( 'should reject for non-existent file' , async ( ) => {
95+ await expect ( fileUtils . readFileAsync ( 'nonexistent.txt' ) )
96+ . rejects . toThrow ( ) ;
97+ } ) ;
98+
99+ it ( 'should handle large files efficiently' , async ( ) => {
100+ const largeContent = 'x' . repeat ( 1024 * 1024 ) ; // 1MB of data
101+ const largeFile = path . join ( testDir , 'large.txt' ) ;
102+ fs . writeFileSync ( largeFile , largeContent ) ;
103+
104+ try {
105+ const content = await fileUtils . readFileAsync ( largeFile ) ;
106+ expect ( content . length ) . toBe ( largeContent . length ) ;
107+ } finally {
108+ fs . unlinkSync ( largeFile ) ;
109+ }
110+ } ) ;
111+ } ) ;
112+
113+ describe ( 'writeFileAsync' , ( ) => {
114+ const outputFile = path . join ( testDir , 'output-async.json' ) ;
115+
116+ afterEach ( ( ) => {
117+ if ( fs . existsSync ( outputFile ) ) {
118+ fs . unlinkSync ( outputFile ) ;
119+ }
120+ } ) ;
121+
122+ it ( 'should write JSON content to file asynchronously' , async ( ) => {
123+ await fileUtils . writeFileAsync ( jsonContent , outputFile ) ;
124+ expect ( fs . existsSync ( outputFile ) ) . toBe ( true ) ;
125+ const content = fs . readFileSync ( outputFile , 'utf8' ) ;
126+ expect ( content ) . toBe ( jsonContent ) ;
127+ } ) ;
128+
129+ it ( 'should reject on invalid file path' , async ( ) => {
130+ const invalidPath = path . join ( testDir , 'nonexistent' , 'file.json' ) ;
131+ await expect ( fileUtils . writeFileAsync ( jsonContent , invalidPath ) )
132+ . rejects . toThrow ( ) ;
133+ } ) ;
134+
135+ it ( 'should handle large content efficiently' , async ( ) => {
136+ const largeJson = JSON . stringify ( { data : 'x' . repeat ( 1024 * 1024 ) } ) ;
137+ await fileUtils . writeFileAsync ( largeJson , outputFile ) ;
138+ const content = fs . readFileSync ( outputFile , 'utf8' ) ;
139+ expect ( content ) . toBe ( largeJson ) ;
140+ } ) ;
141+ } ) ;
142+
143+ describe ( 'Edge Cases' , ( ) => {
144+ it ( 'should handle empty files' , async ( ) => {
145+ const emptyFile = path . join ( testDir , 'empty.txt' ) ;
146+ fs . writeFileSync ( emptyFile , '' ) ;
147+
148+ try {
149+ const content = await fileUtils . readFileAsync ( emptyFile ) ;
150+ expect ( content ) . toBe ( '' ) ;
151+ } finally {
152+ fs . unlinkSync ( emptyFile ) ;
153+ }
154+ } ) ;
155+
156+ it ( 'should handle special characters in content' , async ( ) => {
157+ const specialChars = '¥€£¢©®™πøΩ∆∑∂ƒ∫√µ≤≥÷' ;
158+ const specialFile = path . join ( testDir , 'special.txt' ) ;
159+
160+ await fileUtils . writeFileAsync ( specialChars , specialFile ) ;
161+ const content = await fileUtils . readFileAsync ( specialFile ) ;
162+ expect ( content ) . toBe ( specialChars ) ;
163+ } ) ;
164+
165+ it ( 'should handle files with only whitespace' , async ( ) => {
166+ const whitespaceFile = path . join ( testDir , 'whitespace.txt' ) ;
167+ const whitespaceContent = ' \n\t \r\n ' ;
168+
169+ await fileUtils . writeFileAsync ( whitespaceContent , whitespaceFile ) ;
170+ const content = await fileUtils . readFileAsync ( whitespaceFile ) ;
171+ expect ( content ) . toBe ( whitespaceContent ) ;
172+ } ) ;
173+
174+ it ( 'should handle null bytes in content' , async ( ) => {
175+ const nullBytesFile = path . join ( testDir , 'nullbytes.txt' ) ;
176+ const contentWithNull = 'Hello\0World\0!' ;
177+
178+ await fileUtils . writeFileAsync ( contentWithNull , nullBytesFile ) ;
179+ const content = await fileUtils . readFileAsync ( nullBytesFile ) ;
180+ expect ( content ) . toBe ( contentWithNull ) ;
181+ } ) ;
182+ } ) ;
183+
184+ describe ( 'Error Handling' , ( ) => {
185+ it ( 'should handle file lock situations' , async ( ) => {
186+ const lockedFile = path . join ( testDir , 'locked.txt' ) ;
187+ let fileHandle ;
188+ let writeHandle ;
189+
190+ try {
191+ // Create file first
192+ await fs . promises . writeFile ( lockedFile , 'initial content' ) ;
193+
194+ // Open file with exclusive lock
195+ fileHandle = await fs . promises . open ( lockedFile , 'r' ) ;
196+ writeHandle = await fs . promises . open ( lockedFile , 'w' ) ;
197+
198+ // Try to write while file is locked (should fail on Windows)
199+ // On Unix-like systems, this might not fail due to different file locking semantics
200+ const writePromise = fileUtils . writeFileAsync ( 'test' , lockedFile ) ;
201+
202+ // Close the file immediately to avoid hanging
203+ await fileHandle . close ( ) ;
204+ await writeHandle . close ( ) ;
205+
206+ await writePromise ; // Should succeed after file is closed
207+ } catch ( error ) {
208+ // Both Windows and Unix behaviors are acceptable
209+ expect ( error . code ) . toMatch ( / ^ ( E B U S Y | E A C C E S | E P E R M ) ? $ / ) ;
210+ } finally {
211+ if ( fileHandle && ! fileHandle . closed ) await fileHandle . close ( ) ;
212+ if ( writeHandle && ! writeHandle . closed ) await writeHandle . close ( ) ;
213+ }
214+ } ) ;
215+
216+ it ( 'should handle permission errors' , async ( ) => {
217+ const restrictedFile = path . join ( testDir , 'restricted.txt' ) ;
218+
219+ // Create file with restricted permissions
220+ fs . writeFileSync ( restrictedFile , 'test' ) ;
221+ fs . chmodSync ( restrictedFile , 0o000 ) ;
222+
223+ await expect ( fileUtils . readFileAsync ( restrictedFile ) )
224+ . rejects . toThrow ( ) ;
225+
226+ // Restore permissions for cleanup
227+ fs . chmodSync ( restrictedFile , 0o666 ) ;
228+ } ) ;
229+
230+ it ( 'should handle directory paths as file paths' , async ( ) => {
231+ await expect ( fileUtils . readFileAsync ( testDir ) )
232+ . rejects . toThrow ( ) ;
233+
234+ await expect ( fileUtils . writeFileAsync ( 'test' , testDir ) )
235+ . rejects . toThrow ( ) ;
236+ } ) ;
237+ } ) ;
238+
239+ describe ( 'Performance Tests' , ( ) => {
240+ it ( 'should handle very large files efficiently' , async ( ) => {
241+ const largeFile = path . join ( testDir , 'verylarge.txt' ) ;
242+ const size = 10 * 1024 * 1024 ; // 10MB
243+ const largeContent = Buffer . alloc ( size , 'x' ) . toString ( ) ;
244+
245+ const startWrite = Date . now ( ) ;
246+ await fileUtils . writeFileAsync ( largeContent , largeFile ) ;
247+ const writeTime = Date . now ( ) - startWrite ;
248+
249+ const startRead = Date . now ( ) ;
250+ const content = await fileUtils . readFileAsync ( largeFile ) ;
251+ const readTime = Date . now ( ) - startRead ;
252+
253+ expect ( content . length ) . toBe ( size ) ;
254+
255+ // Performance assertions (adjust thresholds as needed)
256+ expect ( writeTime ) . toBeLessThan ( 1000 ) ; // Should write 10MB in less than 1s
257+ expect ( readTime ) . toBeLessThan ( 1000 ) ; // Should read 10MB in less than 1s
258+ } ) ;
259+
260+ it ( 'should handle multiple concurrent operations' , async ( ) => {
261+ const operations = [ ] ;
262+ const numOperations = 10 ;
263+
264+ // Create multiple concurrent read/write operations
265+ for ( let i = 0 ; i < numOperations ; i ++ ) {
266+ const file = path . join ( testDir , `concurrent${ i } .txt` ) ;
267+ operations . push ( fileUtils . writeFileAsync ( `content${ i } ` , file ) ) ;
268+ }
269+
270+ await expect ( Promise . all ( operations ) ) . resolves . not . toThrow ( ) ;
271+ } ) ;
272+ } ) ;
273+
274+ describe ( 'Encoding Tests' , ( ) => {
275+ const encodings = [ 'utf8' , 'ascii' , 'base64' , 'hex' ] ;
276+
277+ encodings . forEach ( encoding => {
278+ it ( `should handle ${ encoding } encoding` , async ( ) => {
279+ const encodingFile = path . join ( testDir , `${ encoding } .txt` ) ;
280+ const testContent = 'Hello World 123 !@#' ;
281+
282+ // Write content in UTF-8
283+ await fileUtils . writeFileAsync ( testContent , encodingFile ) ;
284+
285+ // Read with specific encoding
286+ const content = await fileUtils . readFileAsync ( encodingFile , encoding ) ;
287+
288+ // Convert back to UTF-8 for comparison
289+ const buffer = Buffer . from ( content , encoding ) ;
290+ expect ( buffer . toString ( ) ) . toBe ( testContent ) ;
291+ } ) ;
292+ } ) ;
293+
294+ it ( 'should handle UTF-16LE encoding specifically' , async ( ) => {
295+ const encodingFile = path . join ( testDir , 'utf16le.txt' ) ;
296+ const testContent = 'Hello World 123' ;
297+
298+ // Write content as UTF-16LE
299+ const buffer = Buffer . from ( testContent , 'utf16le' ) ;
300+ await fs . promises . writeFile ( encodingFile , buffer ) ;
301+
302+ // Read with UTF-16LE encoding
303+ const content = await fileUtils . readFileAsync ( encodingFile , 'utf16le' ) ;
304+ expect ( content ) . toBe ( testContent ) ;
305+ } ) ;
306+
307+ it ( 'should handle BOM in UTF-8 files' , async ( ) => {
308+ const bomFile = path . join ( testDir , 'bom.txt' ) ;
309+ const testContent = 'Hello World' ;
310+ const contentWithBOM = Buffer . concat ( [
311+ Buffer . from ( [ 0xEF , 0xBB , 0xBF ] ) , // UTF-8 BOM
312+ Buffer . from ( testContent )
313+ ] ) ;
314+
315+ await fs . promises . writeFile ( bomFile , contentWithBOM ) ;
316+ const content = await fileUtils . readFileAsync ( bomFile , 'utf8' ) ;
317+ // Node.js automatically strips the BOM when reading UTF-8
318+ expect ( content . replace ( / ^ \uFEFF / , '' ) ) . toBe ( testContent ) ;
319+ } ) ;
320+ } ) ;
321+ } ) ;
322+ } ) ;
0 commit comments