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
68 changes: 65 additions & 3 deletions backend/src/controllers/input.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,64 @@ import { extractSkills } from "../utils/resumeparser.js";
import axios from "axios";
import pdfParse from "pdf-parse";

/**
* Normalizes the multipart `data` field into the object shape expected by the
* profile upload flow.
*
* @param {unknown} data Raw `req.body.data` payload from the upload request.
* @returns {Record<string, unknown> | null} A plain object for valid payloads,
* `{}` for omitted payloads, or `null` when the input is invalid JSON or not an object.
*/
function parseInputDataPayload(data) {
if (data === undefined || data === null) {
return {};
}

if (
typeof data === "object" &&
!Array.isArray(data)
) {
return data;
}

try {
if (typeof data !== "string" || data.trim() === "") {
return null;
}

const parsedData = JSON.parse(data);

if (
parsedData === null ||
Array.isArray(parsedData) ||
typeof parsedData !== "object"
) {
return null;
}

return parsedData;
} catch {
return null;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Creates an input profile entry from the uploaded resume and structured form data.
*
* @param {import("express").Request} req Express request containing multipart form data.
* @param {import("express").Response} res Express response used to return upload status.
* @returns {Promise<import("express").Response>} The JSON response for the upload attempt.
*/
async function inputupload(req, res) {
try {
const parsedData = parseInputDataPayload(req.body.data);

if (parsedData === null) {
return res.status(400).json({
message: "Invalid JSON format in request body",
});
}

const user = await userModel.findById(req.user.id);

if (!user) {
Expand All @@ -14,8 +70,6 @@ async function inputupload(req, res) {
});
}

const parsedData = req.body.data ? JSON.parse(req.body.data) : {};

let experienceLevel = "Entry";

if (parsedData.experience?.includes("Junior")) {
Expand Down Expand Up @@ -86,6 +140,13 @@ async function inputupload(req, res) {
}
}

/**
* Returns the most recently created input profile for the authenticated user.
*
* @param {import("express").Request} req Express request for the current user.
* @param {import("express").Response} res Express response used to return the profile data.
* @returns {Promise<void>} Resolves after the response has been sent.
*/
async function getinput(req, res) {
try {
const data = await inputModel
Expand All @@ -104,6 +165,7 @@ async function getinput(req, res) {
}

export {
parseInputDataPayload,
inputupload,
getinput,
};
};
81 changes: 81 additions & 0 deletions backend/src/controllers/input.controller.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import assert from "node:assert/strict";
import { describe, test, mock } from "node:test";

import { inputupload, parseInputDataPayload } from "./input.controller.js";
import userModel from "../models/User.model.js";

describe("parseInputDataPayload", () => {
test("returns parsed object for valid JSON strings", () => {
assert.deepEqual(parseInputDataPayload('{"jobRole":"Engineer"}'), {
jobRole: "Engineer",
});
});

test("returns empty object for missing payloads", () => {
assert.deepEqual(parseInputDataPayload(undefined), {});
assert.deepEqual(parseInputDataPayload(null), {});
});

test("returns null for malformed JSON strings", () => {
assert.equal(parseInputDataPayload("{invalid json"), null);
});

test("returns null for empty strings", () => {
assert.equal(parseInputDataPayload(""), null);
assert.equal(parseInputDataPayload(" "), null);
});

test("passes through object payloads", () => {
const payload = { experience: "Mid Level" };
assert.equal(parseInputDataPayload(payload), payload);
});

test("returns null for arrays and primitive JSON values", () => {
assert.equal(parseInputDataPayload([]), null);
assert.equal(parseInputDataPayload('["React"]'), null);
assert.equal(parseInputDataPayload('"Engineer"'), null);
assert.equal(parseInputDataPayload("42"), null);
assert.equal(parseInputDataPayload("true"), null);
});
});

describe("inputupload", () => {
test("returns 400 for invalid JSON payloads before touching persistence", async () => {
const findByIdMock = mock.method(userModel, "findById", async () => {
throw new Error("findById should not be reached");
});

const req = {
body: {
data: "{invalid json",
},
user: {
id: "user-id",
},
file: null,
};

let statusCode;
let body;
const res = {
status(code) {
statusCode = code;
return this;
},
json(payload) {
body = payload;
return this;
},
};

await inputupload(req, res);

assert.equal(statusCode, 400);
assert.deepEqual(body, {
message: "Invalid JSON format in request body",
});
assert.equal(findByIdMock.mock.callCount(), 0);

findByIdMock.mock.restore();
});
});