Skip to content

Commit e2a5400

Browse files
authored
test(resume-parser): add error-resilience test suite (JhaSourav07#2878) (JhaSourav07#3003)
## Description Adds the comprehensive `resume-parser.error-resilience.test.ts` test suite to cover exception safety, null/undefined checks, corrupt binary data fallbacks, buffer conversion failures, malformed structures, and regexp backtracking boundary limits. Fixes JhaSourav07#2878 ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview N/A (Utility/Unit Tests only) ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally. - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors. - [x] My commits follow the Conventional Commits format. - [x] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have started the repo. - [x] I have made sure that i have only one commit to merge in this PR. - [ ] The SVG output matches the CommitPulse "premium quality" aesthetic standard. - [ ] (Recommended) I joined the CommitPulse Discord community.
2 parents cb3af96 + bc9b207 commit e2a5400

1 file changed

Lines changed: 108 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { parseResume } from './resume-parser';
3+
4+
describe('resume-parser-error-resilience', () => {
5+
it('should handle non-printable characters gracefully by replacing them and parsing the remaining text', async () => {
6+
// \x00 is non-printable. It should be replaced with a space.
7+
const text = 'John Doe\n\x00\x01\x02john.doe@example.com\nSkills\nJavaScript, Python';
8+
const buffer = Buffer.from(text);
9+
const result = await parseResume(buffer, 'application/pdf');
10+
11+
expect(result.name).toBe('John Doe');
12+
expect(result.email).toBe('john.doe@example.com');
13+
expect(result.skills).toContain('JavaScript');
14+
expect(result.skills).toContain('Python');
15+
});
16+
17+
it('should throw an error/TypeError if the buffer is null or undefined (exception safety)', async () => {
18+
// Ensure that passing invalid parameters rejects or throws a TypeError rather than hanging
19+
await expect(parseResume(null as unknown as Buffer, 'application/pdf')).rejects.toThrow();
20+
await expect(parseResume(undefined as unknown as Buffer, 'application/pdf')).rejects.toThrow();
21+
});
22+
23+
it('should not throw and handle corrupt/binary buffers by returning empty fields', async () => {
24+
// Binary/corrupt data that doesn't resemble a text resume
25+
const binaryData = Buffer.from([
26+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00,
27+
]);
28+
const result = await parseResume(binaryData, 'application/pdf');
29+
30+
expect(result).toEqual({
31+
name: '',
32+
email: '',
33+
phone: '',
34+
skills: [],
35+
education: [],
36+
experience: [],
37+
});
38+
});
39+
40+
it('should handle buffer toString failures gracefully (exception safety)', async () => {
41+
const mockBuffer = Buffer.from('Some text');
42+
vi.spyOn(mockBuffer, 'toString').mockImplementation(() => {
43+
throw new Error('Buffer conversion failed');
44+
});
45+
46+
await expect(parseResume(mockBuffer, 'application/pdf')).rejects.toThrow(
47+
'Buffer conversion failed'
48+
);
49+
vi.restoreAllMocks();
50+
});
51+
52+
it('should skip malformed education entries (e.g. invalid date ranges) instead of throwing', async () => {
53+
const text = `John Doe
54+
Education
55+
University of Toronto 201-202
56+
Harvard College 2020 to invalid
57+
MIT 2015 to 2019
58+
`;
59+
const buffer = Buffer.from(text);
60+
const result = await parseResume(buffer, 'application/pdf');
61+
62+
expect(result.education).toEqual([
63+
{
64+
institution: 'MIT 2015 to 2019',
65+
degree: '',
66+
field: '',
67+
startDate: '2015',
68+
endDate: '2019',
69+
},
70+
]);
71+
});
72+
73+
it('should skip malformed experience entries instead of throwing', async () => {
74+
const text = `John Doe
75+
Experience
76+
Software Developer at Google (no date)
77+
Intern at Apple 202 to present
78+
Senior Engineer at Meta 2018 - 2021
79+
`;
80+
const buffer = Buffer.from(text);
81+
const result = await parseResume(buffer, 'application/pdf');
82+
83+
expect(result.experience).toEqual([
84+
{
85+
company: 'Senior Engineer at Meta 2018 - 2021',
86+
role: '',
87+
startDate: '2018',
88+
endDate: '2021',
89+
description: '',
90+
},
91+
]);
92+
});
93+
94+
it('should handle extremely long single-word inputs without causing RegExp backtracking or crashing', async () => {
95+
const longWord = 'A'.repeat(10000);
96+
const text = `John Doe\n${longWord}@example.com\nSkills\n${longWord}`;
97+
const buffer = Buffer.from(text);
98+
99+
const startTime = Date.now();
100+
const result = await parseResume(buffer, 'application/pdf');
101+
const duration = Date.now() - startTime;
102+
103+
// Check that it didn't hang (completed within 500ms)
104+
expect(duration).toBeLessThan(500);
105+
expect(result.name).toBe('John Doe');
106+
expect(result.email).toContain('@example.com');
107+
});
108+
});

0 commit comments

Comments
 (0)