Skip to content
This repository has been archived by the owner on Apr 13, 2024. It is now read-only.

Commit

Permalink
feat: Add unit tests for reviewing resume
Browse files Browse the repository at this point in the history
  • Loading branch information
samhwang committed Apr 8, 2024
1 parent 1da40ed commit 59aa25c
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 8 deletions.
6 changes: 5 additions & 1 deletion mocks/handlers/pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'node:path';
import { http, HttpResponse } from 'msw';

export const handlers = [
http.get('https://example.com/a.pdf', () => {
http.get('https://example.com/dummy.pdf', () => {
const buffer = fs.readFileSync(path.resolve(__dirname, 'dummy.pdf'));
return HttpResponse.arrayBuffer(buffer, {
headers: {
Expand All @@ -12,6 +12,10 @@ export const handlers = [
});
}),

http.get('https://example.com/invalid.pdf', () => {
return new HttpResponse(null, { status: 404, statusText: 'Not Found' });
}),

http.get('https://docs.google.com/uc', ({ request }) => {
const url = new URL(request.url);
const id = url.searchParams.get('id');
Expand Down
2 changes: 1 addition & 1 deletion src/review-resume/downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import QueryStringAddon from 'wretch/addons/queryString';
import { z } from 'zod';
import { logger } from '../utils/logger';

export const PDFURL = z.string().url();
export const PDFURL = z.union([z.string().url().endsWith('.pdf'), z.string().url().includes('drive.google.com')]);
export type PDFURL = z.infer<typeof PDFURL>;

export async function download(url: string, filename: string): Promise<void> {
Expand Down
150 changes: 144 additions & 6 deletions src/review-resume/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { captor, mockDeep, mockReset } from 'vitest-mock-extended';
import { getClient } from '../llm/client';
import { execute } from './index';
import * as reader from './reader';

const mockChatInputInteraction = mockDeep<ChatInputCommandInteraction>();

Expand All @@ -20,6 +21,8 @@ mockGetClient.mockReturnValue({
},
} as unknown as OpenAI);

const readerSpy = vi.spyOn(reader, 'readPDF');

describe('review resume command', () => {
describe('execute', () => {
beforeEach(() => {
Expand All @@ -32,8 +35,11 @@ describe('review resume command', () => {
case 'model':
return 'invalidmodel';

case 'url':
return 'https://example.com/dummy.pdf';

default:
return 'asdf1234';
throw new Error('Invalid');
}
});
const respondInput = captor<Parameters<ChatInputCommandInteraction['reply']>['0']>();
Expand All @@ -44,14 +50,102 @@ describe('review resume command', () => {
expect(respondInput.value).toContain('Invalid model');
});

it('Should respond with error if URL is invalid', async () => {
mockChatInputInteraction.options.getString.mockImplementation((param) => {
switch (param) {
case 'model':
return 'tinydolphin';

case 'url':
return 'https://example.com';

default:
throw new Error('Invalid');
}
});
const respondInput = captor<Parameters<ChatInputCommandInteraction['reply']>['0']>();

await execute(mockChatInputInteraction);

expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput);
expect(respondInput.value).toContain('Invalid URL');
});

it('Should respond with error with invalid Google Drive URL', async () => {
mockChatInputInteraction.options.getString.mockImplementation((param) => {
switch (param) {
case 'model':
return 'tinydolphin';

case 'url':
return 'https://drive.google.com';

default:
throw new Error('Invalid');
}
});
const respondInput = captor<Parameters<ChatInputCommandInteraction['reply']>['0']>();

await execute(mockChatInputInteraction);

expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput);
expect(respondInput.value).toContain('Error downloading resume');
});

it('Should respond with error if there is an error downloading the file', async () => {
mockChatInputInteraction.options.getString.mockImplementation((param) => {
switch (param) {
case 'model':
return 'tinydolphin';

case 'url':
return 'https://example.com/invalid.pdf';

default:
throw new Error('Invalid');
}
});
const respondInput = captor<Parameters<ChatInputCommandInteraction['reply']>['0']>();

await execute(mockChatInputInteraction);

expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput);
expect(respondInput.value).toContain('Error downloading resume');
});

it('Should respond with error if there is an error reading the file', async () => {
mockChatInputInteraction.options.getString.mockImplementation((param) => {
switch (param) {
case 'model':
return 'tinydolphin';

case 'url':
return 'https://example.com/dummy.pdf';

default:
throw new Error('Invalid');
}
});
readerSpy.mockRejectedValueOnce(new Error('Synthetic Error.'));
const respondInput = captor<Parameters<ChatInputCommandInteraction['reply']>['0']>();

await execute(mockChatInputInteraction);

expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput);
expect(respondInput.value).toContain('Error reading resume');
});

it('Should respond with error if there is an error asking the LLM', async () => {
mockChatInputInteraction.options.getString.mockImplementation((param) => {
switch (param) {
case 'model':
return 'tinydolphin';

case 'url':
return 'https://example.com/dummy.pdf';

default:
return 'asdf1234';
throw new Error('Invalid');
}
});
mockChatCompletions.mockRejectedValueOnce(new Error('Synthetic Error.'));
Expand All @@ -69,8 +163,11 @@ describe('review resume command', () => {
case 'model':
return 'tinydolphin';

case 'url':
return 'https://example.com/dummy.pdf';

default:
return 'asdf1234';
throw new Error('Invalid');
}
});
mockChatCompletions.mockResolvedValueOnce({
Expand All @@ -97,14 +194,55 @@ describe('review resume command', () => {
expect(respondInput.value).toContain('No response');
});

it('Should respond with the LLM response', async () => {
it('Should respond with the LLM response if it can download from Google Drive', async () => {
mockChatInputInteraction.options.getString.mockImplementation((param) => {
switch (param) {
case 'model':
return 'tinydolphin';

case 'url':
return `https://drive.google.com/file/d/${faker.string.alphanumeric()}/view?usp=sharing`;

default:
throw new Error('Invalid');
}
});
const mockAnswer = faker.lorem.sentence();
mockChatCompletions.mockResolvedValueOnce({
id: faker.string.uuid(),
created: faker.number.int(),
model: 'tinydolphin',
object: 'chat.completion',
choices: [
{
finish_reason: 'stop',
index: 0,
logprobs: null,
message: {
content: mockAnswer,
},
},
] as ChatCompletion.Choice[],
});
const respondInput = captor<Parameters<ChatInputCommandInteraction['reply']>['0']>();

await execute(mockChatInputInteraction);

expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput);
expect(respondInput.value).toContain(`${mockAnswer}`);
});

it('Should respond with the LLM response if it can download from regular URL', async () => {
mockChatInputInteraction.options.getString.mockImplementation((param) => {
switch (param) {
case 'model':
return 'tinydolphin';

case 'url':
return 'https://example.com/dummy.pdf';

default:
return 'asdf1234';
throw new Error('Invalid');
}
});
const mockAnswer = faker.lorem.sentence();
Expand All @@ -129,7 +267,7 @@ describe('review resume command', () => {
await execute(mockChatInputInteraction);

expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput);
expect(respondInput.value).toContain(`Q: asdf1234\nA: ${mockAnswer}`);
expect(respondInput.value).toContain(`${mockAnswer}`);
});
});
});

0 comments on commit 59aa25c

Please sign in to comment.