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
56 changes: 56 additions & 0 deletions apps/api/src/tests/job.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createJobSchema, updateJobSchema } from "../validators/job.js";

const validJob = {
title: "Build a website",
description: "We need a responsive website built",
budgetMin: 100,
budgetMax: 500,
categoryId: "cat_1",
skills: ["javascript", "react"],
};

test("createJobSchema accepts valid budget range", () => {
const result = createJobSchema.safeParse(validJob);
assert.equal(result.success, true);
});

test("createJobSchema accepts equal budgetMin and budgetMax", () => {
const result = createJobSchema.safeParse({ ...validJob, budgetMin: 300, budgetMax: 300 });
assert.equal(result.success, true);
});

test("createJobSchema rejects inverted budget range", () => {
const result = createJobSchema.safeParse({ ...validJob, budgetMin: 500, budgetMax: 100 });
assert.equal(result.success, false);
assert.ok(result.error.issues.some((i) => i.message.includes("budgetMax")));
});

test("createJobSchema rejects zero budgetMax with positive budgetMin", () => {
const result = createJobSchema.safeParse({ ...validJob, budgetMin: 100, budgetMax: 0 });
assert.equal(result.success, false);
});

test("updateJobSchema accepts partial update with valid range", () => {
const result = updateJobSchema.safeParse({ budgetMin: 100, budgetMax: 500 });
assert.equal(result.success, true);
});

test("updateJobSchema rejects inverted range when both fields present", () => {
const result = updateJobSchema.safeParse({ budgetMin: 500, budgetMax: 100 });
assert.equal(result.success, false);
});

test("updateJobSchema accepts single budget field without the other", () => {
const result = updateJobSchema.safeParse({ budgetMin: 100 });
assert.equal(result.success, true);

const result2 = updateJobSchema.safeParse({ budgetMax: 500 });
assert.equal(result2.success, true);
});

test("createJobSchema rejects missing required fields", () => {
const result = createJobSchema.safeParse({ title: "Hi" });
assert.equal(result.success, false);
});
25 changes: 22 additions & 3 deletions apps/api/src/validators/job.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { z } from "zod";

export const createJobSchema = z.object({
const jobBaseSchema = z.object({
title: z.string().min(4),
description: z.string().min(10),
budgetMin: z.number().nonnegative(),
budgetMax: z.number().nonnegative(),
categoryId: z.string().min(1),
skills: z.array(z.string().min(1)).default([])
skills: z.array(z.string().min(1)).default([]),
});

export const updateJobSchema = createJobSchema.partial();
const budgetRangeCheck = {
refine: (data) => {
if (data.budgetMin !== undefined && data.budgetMax !== undefined) {
return data.budgetMax >= data.budgetMin;
}
return true;
},
message: "budgetMax must be greater than or equal to budgetMin",
path: ["budgetMax"],
};

export const createJobSchema = jobBaseSchema.refine(budgetRangeCheck.refine, {
message: budgetRangeCheck.message,
path: budgetRangeCheck.path,
});

export const updateJobSchema = jobBaseSchema.partial().refine(budgetRangeCheck.refine, {
message: budgetRangeCheck.message,
path: budgetRangeCheck.path,
});
Loading