Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions app/api/student/resume/upload/route.mock-integrations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { POST } from './route';
import { RateLimiter } from '@/lib/rate-limit';
import { parseResume, hasValidFileSignature } from '@/lib/resume-parser';

// 1. Mock the External Modules and Service Layers
vi.mock('@/utils/getClientIp', () => ({
getClientIp: vi.fn(() => '127.0.0.1'),
}));

vi.mock('@/lib/rate-limit', () => {
const RateLimiterMock = vi.fn();
RateLimiterMock.prototype.check = vi.fn().mockResolvedValue(true);
return { RateLimiter: RateLimiterMock };
});

vi.mock('@/lib/resume-parser', () => ({
parseResume: vi.fn(),
hasValidFileSignature: vi.fn().mockReturnValue(true),
ALLOWED_MIME_TYPES: [
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB
}));

describe('API Route: Student Resume Upload (Mock Integrations)', () => {
beforeEach(() => {
vi.clearAllMocks();
});

// THE FIX: Explicitly mock formData() to bypass Vitest parsing issues
const createMockRequest = (
fileName = 'resume.pdf',
fileType = 'application/pdf',
fieldName = 'resume'
) => {
const formData = new FormData();
const file = new File(['dummy pdf buffer content'], fileName, { type: fileType });
if (fieldName) formData.append(fieldName, file);

const req = new Request('http://localhost/api/student/resume/upload', {
method: 'POST',
// We don't attach the body here to avoid parsing crashes
});

// We force the request to resolve our formData object when asked
req.formData = vi.fn().mockResolvedValue(formData) as unknown as () => Promise<FormData>;

return req;
};

it('1. should test service loading paths to ensure successful parsing returns 200 (mock success)', async () => {
// Arrange: Mock the async service to succeed
const mockParsedData = { name: 'Priyanuj', skills: ['React', 'Next.js'] };
vi.mocked(parseResume).mockResolvedValueOnce(mockParsedData);

const req = createMockRequest();

// Act
const res = await POST(req);
const json = await res.json();

// Assert
expect(res.status).toBe(200);
expect(json.success).toBe(true);
expect(json.data).toEqual(mockParsedData);
expect(parseResume).toHaveBeenCalledTimes(1);
});

it('2. should assert local cache layers (RateLimiter) block requests before triggering async services', async () => {
// Arrange: Mock the RateLimiter cache to block the user
vi.mocked(RateLimiter.prototype.check).mockResolvedValueOnce(false);

const req = createMockRequest();

// Act
const res = await POST(req);
const json = await res.json();

// Assert: Blocked with 429, and parseResume is NEVER called
expect(res.status).toBe(429);
expect(json.error).toMatch(/Too many requests/i);
expect(parseResume).not.toHaveBeenCalled();
});

it('3. should verify correct fallback procedures during fake endpoint/parsing errors', async () => {
// Arrange: Simulate the PDF parser crashing or timing out
vi.mocked(parseResume).mockRejectedValueOnce(new Error('Parser timeout'));

const req = createMockRequest();

// Act
const res = await POST(req);
const json = await res.json();

// Assert: Route catches the error safely and returns 422
expect(res.status).toBe(422);
expect(json.success).toBe(false);
expect(json.error).toMatch(/Failed to parse resume/i);
});

it('4. should block invalid file signatures without hitting the parser service', async () => {
// Arrange: Simulate a user renaming a .exe file to .pdf
vi.mocked(hasValidFileSignature).mockReturnValueOnce(false);

const req = createMockRequest();

// Act
const res = await POST(req);
const json = await res.json();

// Assert: Fails security check (400) before reaching the async parser
expect(res.status).toBe(400);
expect(json.error).toMatch(/File content does not match its type/i);
expect(parseResume).not.toHaveBeenCalled();
});

it('5. should reject requests with missing formData fields without processing', async () => {
// Arrange: Send a request where the file is named 'wrong_field' instead of 'resume'
const req = createMockRequest('test.pdf', 'application/pdf', 'wrong_field_name');

// Act
const res = await POST(req);
const json = await res.json();

// Assert: Immediately fails validation (400)
expect(res.status).toBe(400);
expect(json.error).toMatch(/No resume file provided/i);
expect(RateLimiter.prototype.check).toHaveBeenCalled();
expect(parseResume).not.toHaveBeenCalled();
});
});
Loading
Loading