Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ package-lock.json
dev
bun.*
bun.lock
fix.md
83 changes: 83 additions & 0 deletions app/api/routesF/__tests__/business-days.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { POST } from "../business-days/route";

type RequestBody = {
date: string;
days: number;
country?: string;
custom_holidays?: string[];
};

function makeReq(body: RequestBody) {
return new NextRequest("http://localhost/api/routesF/business-days", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
}

describe("/api/routesF/business-days", () => {
it("adds one business day and skips a weekend", async () => {
const res = await POST(makeReq({ date: "2026-03-13", days: 1 }));
const data = await res.json();

expect(res.status).toBe(200);
expect(data.result).toBe("2026-03-15T00:00:00.000Z");
expect(data.skipped_days).toBe(2);
});

it("subtracts one business day and skips a weekend", async () => {
const res = await POST(makeReq({ date: "2026-03-15", days: -1 }));
const data = await res.json();

expect(res.status).toBe(200);
expect(data.result).toBe("2026-03-13T00:00:00.000Z");
expect(data.skipped_days).toBe(2);
});

it("skips a holiday from bundled country holidays", async () => {
const res = await POST(
makeReq({ date: "2026-12-24", days: 1, country: "US" })
);
const data = await res.json();

expect(res.status).toBe(200);
expect(data.result).toBe("2026-12-27T00:00:00.000Z");
expect(data.skipped_days).toBe(3);
});

it("uses custom_holidays to skip additional dates", async () => {
const res = await POST(
makeReq({
date: "2026-03-13",
days: 1,
custom_holidays: ["2026-03-15"],
})
);
const data = await res.json();

expect(res.status).toBe(200);
expect(data.result).toBe("2026-03-16T00:00:00.000Z");
expect(data.skipped_days).toBe(2);
});

it("rejects invalid date values", async () => {
const res = await POST(
makeReq({ date: "invalid", days: 1 } as unknown as RequestBody)
);
expect(res.status).toBe(400);
});

it("rejects non-integer days", async () => {
const req = new NextRequest("http://localhost/api/routesF/business-days", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ date: "2026-03-13", days: 1.5 }),
});
const res = await POST(req);
expect(res.status).toBe(400);
});
});
55 changes: 55 additions & 0 deletions app/api/routesF/__tests__/sentence-case-capitalizer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { POST } from "../sentence-case-capitalizer/route";

function makeReq(body: unknown) {
return new NextRequest(
"http://localhost/api/routesF/sentence-case-capitalizer",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
}
);
}

describe("/api/routesF/sentence-case-capitalizer", () => {
it("capitalizes the first letter of each sentence", async () => {
const res = await POST(
makeReq({ text: "hello world. this is a test! is it working? yes." })
);
const data = await res.json();

expect(res.status).toBe(200);
expect(data.result).toBe(
"Hello world. This is a test! Is it working? Yes."
);
});

it("does not split sentences on common abbreviations", async () => {
const res = await POST(
makeReq({ text: "dr. smith arrived at 10 a.m. he said hello." })
);
const data = await res.json();

expect(res.status).toBe(200);
expect(data.result).toBe("Dr. Smith arrived at 10 a.m. He said hello.");
});

it("handles a paragraph with mixed punctuation", async () => {
const res = await POST(
makeReq({ text: "wow! this is great? yes it is. fantastic." })
);
const data = await res.json();

expect(res.status).toBe(200);
expect(data.result).toBe("Wow! This is great? Yes it is. Fantastic.");
});

it("rejects invalid request bodies", async () => {
const res = await POST(makeReq({ text: 123 }));
expect(res.status).toBe(400);
});
});
52 changes: 52 additions & 0 deletions app/api/routesF/__tests__/trailing-zeros-factorial.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { GET } from "../trailing-zeros-factorial/route";

function makeReq(query: string) {
return new NextRequest(
`http://localhost/api/routesF/trailing-zeros-factorial?${query}`
);
}

describe("/api/routesF/trailing-zeros-factorial", () => {
it("returns trailing zeros for n=100", async () => {
const res = await GET(makeReq("n=100"));
const data = await res.json();

expect(res.status).toBe(200);
expect(data.n).toBe(100);
expect(data.trailing_zeros).toBe(24);
});

it("returns zero trailing zeros for n=0", async () => {
const res = await GET(makeReq("n=0"));
const data = await res.json();

expect(res.status).toBe(200);
expect(data.n).toBe(0);
expect(data.trailing_zeros).toBe(0);
});

it("returns the correct count for a large n", async () => {
const res = await GET(makeReq("n=1000000"));
const data = await res.json();

expect(res.status).toBe(200);
expect(data.n).toBe(1000000);
expect(data.trailing_zeros).toBe(249998);
});

it("rejects missing n parameter", async () => {
const res = await GET(
new NextRequest("http://localhost/api/routesF/trailing-zeros-factorial")
);
expect(res.status).toBe(400);
});

it("rejects invalid n values", async () => {
const res = await GET(makeReq("n=-1"));
expect(res.status).toBe(400);
});
});
57 changes: 57 additions & 0 deletions app/api/routesF/__tests__/word-count-reading-time.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { POST } from "../word-count-reading-time/route";

function makeReq(body: unknown) {
return new NextRequest(
"http://localhost/api/routesF/word-count-reading-time",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
}
);
}

describe("/api/routesF/word-count-reading-time", () => {
it("counts words, characters, sentences, and reading time with default WPM", async () => {
const text = "Hello world. This is a test.";
const res = await POST(makeReq({ text }));
const data = await res.json();

expect(res.status).toBe(200);
expect(data.words).toBe(6);
expect(data.characters).toBe(28);
expect(data.characters_no_spaces).toBe(23);
expect(data.sentences).toBe(2);
expect(data.reading_time_seconds).toBe(2);
});

it("uses custom WPM when provided", async () => {
const text = "One two three four five six seven eight nine ten.";
const res = await POST(makeReq({ text, wpm: 250 }));
const data = await res.json();

expect(res.status).toBe(200);
expect(data.words).toBe(10);
expect(data.reading_time_seconds).toBe(3);
});

it("rejects text larger than 1MB", async () => {
const largeText = "a".repeat(1024 * 1024 + 1);
const res = await POST(makeReq({ text: largeText }));
expect(res.status).toBe(400);
});

it("rejects non-string text values", async () => {
const res = await POST(makeReq({ text: 123 }));
expect(res.status).toBe(400);
});

it("rejects invalid wpm values", async () => {
const res = await POST(makeReq({ text: "Hello world.", wpm: 0 }));
expect(res.status).toBe(400);
});
});
32 changes: 32 additions & 0 deletions app/api/routesF/business-days/holidays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export type Holiday = {
date: string;
name: string;
};

export type HolidayCountry = "US" | "GB" | "JP";

export const HOLIDAYS: Record<HolidayCountry, Holiday[]> = {
US: [
{ date: "2026-01-01", name: "New Year's Day" },
{ date: "2026-07-04", name: "Independence Day" },
{ date: "2026-11-26", name: "Thanksgiving Day" },
{ date: "2026-12-24", name: "Christmas Day (observed)" },
{ date: "2026-12-25", name: "Christmas Day" },
],
GB: [
{ date: "2026-01-01", name: "New Year's Day" },
{ date: "2026-04-10", name: "Good Friday" },
{ date: "2026-12-25", name: "Christmas Day" },
{ date: "2026-12-28", name: "Boxing Day (substitute day)" },
],
JP: [
{ date: "2026-01-01", name: "New Year's Day" },
{ date: "2026-02-11", name: "National Foundation Day" },
{ date: "2026-05-05", name: "Children's Day" },
{ date: "2026-11-03", name: "Culture Day" },
],
};

export const COUNTRY_ALIASES: Record<string, HolidayCountry> = {
UK: "GB",
};
Loading