Skip to content

Commit b84d522

Browse files
committed
test: add more coverage
1 parent 74226bd commit b84d522

2 files changed

Lines changed: 322 additions & 0 deletions

File tree

test/fileUtils.spec.js

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
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(/^(EBUSY|EACCES|EPERM)?$/);
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

Comments
 (0)