Skip to content

Commit 78bdbc1

Browse files
committed
test(api): verify async service layer mocking for resume upload route
1 parent 25ec7f3 commit 78bdbc1

1 file changed

Lines changed: 133 additions & 0 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { POST } from './route';
3+
import { RateLimiter } from '@/lib/rate-limit';
4+
import { parseResume, hasValidFileSignature } from '@/lib/resume-parser';
5+
6+
// 1. Mock the External Modules and Service Layers
7+
vi.mock('@/utils/getClientIp', () => ({
8+
getClientIp: vi.fn(() => '127.0.0.1'),
9+
}));
10+
11+
// ESLINT FIX: Use vi.fn() to safely build the class prototype without 'any'
12+
vi.mock('@/lib/rate-limit', () => {
13+
const RateLimiterMock = vi.fn();
14+
RateLimiterMock.prototype.checkWithResult = vi.fn().mockResolvedValue({
15+
success: true,
16+
limit: 10,
17+
remaining: 9,
18+
reset: 0,
19+
});
20+
21+
return {
22+
RateLimiter: RateLimiterMock,
23+
getRateLimitHeaders: vi.fn().mockReturnValue({}),
24+
};
25+
});
26+
27+
vi.mock('@/lib/resume-parser', () => ({
28+
parseResume: vi.fn(),
29+
hasValidFileSignature: vi.fn().mockReturnValue(true),
30+
ALLOWED_MIME_TYPES: [
31+
'application/pdf',
32+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
33+
],
34+
MAX_FILE_SIZE: 5 * 1024 * 1024,
35+
}));
36+
37+
describe('API Route: Student Resume Upload (Mock Integrations)', () => {
38+
beforeEach(() => {
39+
vi.clearAllMocks();
40+
});
41+
42+
const createMockRequest = (
43+
fileName = 'resume.pdf',
44+
fileType = 'application/pdf',
45+
fieldName = 'resume'
46+
) => {
47+
const formData = new FormData();
48+
const file = new File(['dummy pdf buffer content'], fileName, { type: fileType });
49+
if (fieldName) formData.append(fieldName, file);
50+
51+
const req = new Request('http://localhost/api/student/resume/upload', {
52+
method: 'POST',
53+
});
54+
55+
req.formData = vi.fn().mockResolvedValue(formData) as unknown as () => Promise<FormData>;
56+
57+
return req;
58+
};
59+
60+
it('1. should test service loading paths to ensure successful parsing returns 200 (mock success)', async () => {
61+
const mockParsedData = {
62+
name: 'Priyanuj',
63+
email: 'test@example.com',
64+
phone: '1234567890',
65+
skills: ['React', 'Next.js'],
66+
education: [],
67+
experience: [],
68+
};
69+
vi.mocked(parseResume).mockResolvedValueOnce(mockParsedData);
70+
71+
const req = createMockRequest();
72+
const res = await POST(req);
73+
const json = await res.json();
74+
75+
expect(res.status).toBe(200);
76+
expect(json.success).toBe(true);
77+
expect(json.data).toEqual(mockParsedData);
78+
expect(parseResume).toHaveBeenCalledTimes(1);
79+
});
80+
81+
it('2. should assert local cache layers (RateLimiter) block requests before triggering async services', async () => {
82+
// ESLINT FIX: We provide all required properties without 'any'
83+
vi.mocked(RateLimiter.prototype.checkWithResult).mockResolvedValueOnce({
84+
success: false,
85+
limit: 10,
86+
remaining: 0,
87+
reset: 0,
88+
});
89+
90+
const req = createMockRequest();
91+
const res = await POST(req);
92+
const json = await res.json();
93+
94+
expect(res.status).toBe(429);
95+
expect(json.error).toMatch(/Too many requests/i);
96+
expect(parseResume).not.toHaveBeenCalled();
97+
});
98+
99+
it('3. should verify correct fallback procedures during fake endpoint/parsing errors', async () => {
100+
vi.mocked(parseResume).mockRejectedValueOnce(new Error('Parser timeout'));
101+
102+
const req = createMockRequest();
103+
const res = await POST(req);
104+
const json = await res.json();
105+
106+
expect(res.status).toBe(422);
107+
expect(json.success).toBe(false);
108+
expect(json.error).toMatch(/Failed to parse resume/i);
109+
});
110+
111+
it('4. should block invalid file signatures without hitting the parser service', async () => {
112+
vi.mocked(hasValidFileSignature).mockReturnValueOnce(false);
113+
114+
const req = createMockRequest();
115+
const res = await POST(req);
116+
const json = await res.json();
117+
118+
expect(res.status).toBe(400);
119+
expect(json.error).toMatch(/File content does not match its type/i);
120+
expect(parseResume).not.toHaveBeenCalled();
121+
});
122+
123+
it('5. should reject requests with missing formData fields without processing', async () => {
124+
const req = createMockRequest('test.pdf', 'application/pdf', 'wrong_field_name');
125+
const res = await POST(req);
126+
const json = await res.json();
127+
128+
expect(res.status).toBe(400);
129+
expect(json.error).toMatch(/No resume file provided/i);
130+
expect(RateLimiter.prototype.checkWithResult).toHaveBeenCalled();
131+
expect(parseResume).not.toHaveBeenCalled();
132+
});
133+
});

0 commit comments

Comments
 (0)