Skip to content

Commit

Permalink
sprint 2
Browse files Browse the repository at this point in the history
sprint 2
  • Loading branch information
AlaraBread authored Dec 16, 2024
2 parents 9a47e37 + 6618faf commit bc591ab
Show file tree
Hide file tree
Showing 53 changed files with 6,700 additions and 2,249 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deno.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ jobs:
run: cd backend; deno lint

- name: Run tests
run: cd backend; deno test --allow-read --allow-env --trace-leaks
run: cd backend; deno task test

2 changes: 2 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,7 @@ jobs:
run: cd frontend; npm run lint
- name: nextjs build
run: cd frontend; npm run build
- name: install playwright
run: npx playwright install --with-deps
- name: run tests
run: cd frontend; npm run test:ci
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
*.tmp

# Environment variables
.env
.env
local.env
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ deno run dev

(in `/backend`)
```sh
deno test --allow-read --allow-env --trace-leaks
deno task test
```
1 change: 0 additions & 1 deletion backend/.env

This file was deleted.

163 changes: 163 additions & 0 deletions backend/api/analyze/analyze.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { assertEquals } from "@std/assert";
import { createMockContext } from "@oak/oak/testing";
import { Router } from "@oak/oak";
import { analyzeHandler } from "./analyze.ts";
import { restore } from "@std/testing/mock";
import { SessionData } from "../../in_memory/in_memory.ts";
import {
AnalyseResponse,
analyzeText,
generateResumeFeedback,
} from "../openai/openai.ts";

const mockAnalyzeText: typeof analyzeText = <T>(
_inputText: string,
type: T,
) => {
if (type == "resume") {
return Promise.resolve(["1", "2"] as AnalyseResponse<
T
>);
} else {
return Promise.resolve({
mustHave: ["a", "b", "c"],
niceToHave: ["x", "y"],
} as AnalyseResponse<
T
>);
}
};

const mockGenerateFeedback: typeof generateResumeFeedback = (
_resumeText: string,
_jobDescription: string,
) => Promise.resolve([{ feedback: "a", category: "b" }]);

Deno.test("POST /api/analyze - Valid Input", async () => {
restore();

const ctx = createMockContext({
method: "POST",
path: "/api/analyze",
headers: [["Content-Type", "application/json"]],
state: {
sessionData: {
resumeText: "my resume",
jobDescription: "my job description",
} as SessionData,
},
});

const router = new Router();
router.post(
"/api/analyze",
analyzeHandler.bind(undefined, mockAnalyzeText, mockGenerateFeedback),
);

const next = async () => {};

await router.routes()(ctx, next);

// Assertions
const responseBody = ctx.response.body as {
isError: boolean;
message: string;
data: {
resumeAnalysis: string[];
jobDescriptionAnalysis: {
mustHave: string[];
niceToHave: string[];
};
feedback: {
feedback: string;
category: string;
}[];
};
};
assertEquals(ctx.response.status, 200);
assertEquals(responseBody.isError, false);
assertEquals(responseBody.message, "Analysis successful.");
assertEquals(responseBody.data.resumeAnalysis, ["1", "2"]);
assertEquals(responseBody.data.jobDescriptionAnalysis, {
mustHave: ["a", "b", "c"],
niceToHave: ["x", "y"],
});
assertEquals(responseBody.data.feedback, [{ feedback: "a", category: "b" }]);
});

Deno.test("POST /api/analyze - Missing Input", async () => {
restore();

const ctx = createMockContext({
method: "POST",
path: "/api/analyze",
headers: [["Content-Type", "application/json"]],
state: {
sessionData: {
jobDescription: "Job content", // Missing resumeText
} as SessionData,
},
});

const router = new Router();
router.post(
"/api/analyze",
analyzeHandler.bind(undefined, mockAnalyzeText, mockGenerateFeedback),
);

const next = async () => {};

await router.routes()(ctx, next);

// Assertions
const responseBody = ctx.response.body as {
isError: boolean;
message: string;
};
assertEquals(ctx.response.status, 400);
assertEquals(responseBody.isError, true);
assertEquals(
responseBody.message,
"You must upload a resume and job description.",
);
});

Deno.test("POST /api/analyze - Input Too Long", async () => {
restore();

const longText = "a".repeat(10001);

const ctx = createMockContext({
method: "POST",
path: "/api/analyze",
headers: [["Content-Type", "application/json"]],
state: {
sessionData: {
jobDescription: "Job content",
resumeText: longText,
} as SessionData,
},
});

const router = new Router();
router.post(
"/api/analyze",
analyzeHandler.bind(undefined, mockAnalyzeText, mockGenerateFeedback),
);

const next = async () => {};

await router.routes()(ctx, next);

// Assertions
const responseBody = ctx.response.body as {
isError: boolean;
message: string;
};
assertEquals(ctx.response.status, 400);
assertEquals(responseBody.isError, true);
assertEquals(
responseBody.message,
"Resume or Job description too long.",
);
});
89 changes: 89 additions & 0 deletions backend/api/analyze/analyze.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Context, Middleware, Router } from "@oak/oak";
import { analyzeText, generateResumeFeedback } from "../openai/openai.ts";
import { SessionData } from "../../in_memory/in_memory.ts";

export default function (router: Router, sessionMiddleware: Middleware) {
router.post(
"/api/analyze",
sessionMiddleware,
analyzeHandler.bind(undefined, analyzeText, generateResumeFeedback),
);
}

export const MAX_TEXT_LENGTH = 10000;

export async function analyzeHandler(
analyze: typeof analyzeText,
getFeedback: typeof generateResumeFeedback,
ctx: Context,
) {
// Retrieve session data
const sessionData = ctx.state.sessionData as SessionData | null;

// Validate session data
if (
!sessionData ||
sessionData.resumeText == undefined ||
sessionData.jobDescription == undefined
) {
console.log(sessionData);
ctx.response.status = 400;
ctx.response.body = {
isError: true,
message: "You must upload a resume and job description.",
};
return;
}

if (
sessionData.resumeText.length > MAX_TEXT_LENGTH ||
sessionData.jobDescription.length > MAX_TEXT_LENGTH
) {
ctx.response.status = 400;
ctx.response.body = {
isError: true,
message: "Resume or Job description too long.",
};
return;
}

try {
// Analyze the resume
const resumeAnalysis = await analyze(sessionData.resumeText, "resume");

// Analyze the job description
const jobDescriptionAnalysis = await analyze(
sessionData.jobDescription,
"jobDescription",
);

const feedback = await getFeedback(
sessionData.resumeText,
sessionData.jobDescription,
);

// Combine results
const analysisResult = {
resumeAnalysis,
jobDescriptionAnalysis,
feedback,
};

// Return success response
ctx.response.status = 200;
ctx.response.body = {
isError: false,
message: "Analysis successful.",
data: analysisResult,
};
} catch (error) {
console.error("Error during analysis:", error);

// Return error response
ctx.response.status = 500;
ctx.response.body = {
isError: true,
message: "Failed to analyze the text. Please try again later.",
};
}
}
Loading

0 comments on commit bc591ab

Please sign in to comment.