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
35 changes: 35 additions & 0 deletions src/test/data/utils/fetch-json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as fs from "fs/promises";
import { describe, expect, it, vi } from "vitest";
import { fetchJsonFromUrl } from "../../../data/utils";

vi.mock("fs/promises", () => ({
readFile: vi.fn(),
}));

describe("fetchJsonFromUrl", () => {
it("reads and parses JSON from a file path", async () => {
vi.mocked(fs.readFile).mockResolvedValueOnce('{"key":"value"}');
const result = await fetchJsonFromUrl("./data/seed.json");
expect(result).toEqual({ key: "value" });
expect(fs.readFile).toHaveBeenCalledWith("./data/seed.json", "utf-8");
});

it("fetches JSON from a URL", async () => {
const mockFetch = vi.fn().mockResolvedValueOnce({
json: () => Promise.resolve({ remote: true }),
});
vi.stubGlobal("fetch", mockFetch);

const result = await fetchJsonFromUrl("https://example.com/data.json");
expect(result).toEqual({ remote: true });
expect(mockFetch).toHaveBeenCalledWith("https://example.com/data.json");

vi.unstubAllGlobals();
});

it("treats absolute paths as file system paths", async () => {
vi.mocked(fs.readFile).mockResolvedValueOnce('{"absolute":true}');
const result = await fetchJsonFromUrl("/etc/config.json");
expect(result).toEqual({ absolute: true });
});
});
32 changes: 32 additions & 0 deletions src/test/data/utils/get-rrule.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
import { getRRULE } from "../../../data/utils";

describe("getRRULE", () => {
it("returns RRULE string for each valid weekday (case-insensitive)", () => {
expect(getRRULE("monday")).toBe("FREQ=WEEKLY;BYDAY=MO;");
expect(getRRULE("Tuesday")).toBe("FREQ=WEEKLY;BYDAY=TU;");
expect(getRRULE("WEDNESDAY")).toBe("FREQ=WEEKLY;BYDAY=WE;");
expect(getRRULE("thursday")).toBe("FREQ=WEEKLY;BYDAY=TH;");
expect(getRRULE("Friday")).toBe("FREQ=WEEKLY;BYDAY=FR;");
expect(getRRULE("saturday")).toBe("FREQ=WEEKLY;BYDAY=SA;");
expect(getRRULE("sunday")).toBe("FREQ=WEEKLY;BYDAY=SU;");
});

it("returns null for invalid day names", () => {
expect(getRRULE("weekday")).toBeNull();
expect(getRRULE("mon")).toBeNull();
expect(getRRULE("")).toBeNull();
expect(getRRULE("holiday")).toBeNull();
});

it("returns null for non-string input", () => {
expect(getRRULE(null as any)).toBeNull();
expect(getRRULE(undefined as any)).toBeNull();
expect(getRRULE(1 as any)).toBeNull();
});

it("returns null for strings with leading or trailing whitespace", () => {
expect(getRRULE(" monday")).toBeNull();
expect(getRRULE("monday ")).toBeNull();
});
});
65 changes: 65 additions & 0 deletions src/test/data/utils/get-start-end-dates.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, it } from "vitest";
import { getStartEnd, getStartEndDates } from "../../../data/utils";

describe("getStartEndDates", () => {
it("returns start and end Date objects with correct hours", () => {
const { start, end } = getStartEndDates(8, 11);
expect(start).toBeInstanceOf(Date);
expect(end).toBeInstanceOf(Date);
expect(start.getHours()).toBe(8);
expect(end.getHours()).toBe(11);
});

it("sets minutes, seconds, and ms to 0 on start", () => {
const { start } = getStartEndDates(10, 14);
expect(start.getMinutes()).toBe(0);
expect(start.getSeconds()).toBe(0);
expect(start.getMilliseconds()).toBe(0);
});

it("uses provided date for start, defaults to 2024-01-01 for end", () => {
const custom = new Date("2025-06-15");
const { start, end } = getStartEndDates(9, 12, custom);
expect(start.getFullYear()).toBe(2025);
expect(start.getMonth()).toBe(5); // June = 5
expect(end.getFullYear()).toBe(2024);
});
});

describe("getStartEnd", () => {
it("maps known morning/noon/afternoon/evening labels to hour ranges", () => {
const morning = getStartEnd("morning");
expect(morning!.start.getHours()).toBe(8);
expect(morning!.end.getHours()).toBe(11);

const noon = getStartEnd("noon");
expect(noon!.start.getHours()).toBe(11);
expect(noon!.end.getHours()).toBe(14);

const afternoon = getStartEnd("afternoon");
expect(afternoon!.start.getHours()).toBe(14);
expect(afternoon!.end.getHours()).toBe(17);

const evening = getStartEnd("evening");
expect(evening!.start.getHours()).toBe(17);
expect(evening!.end.getHours()).toBe(20);
});

it("maps time-range string formats", () => {
const r1 = getStartEnd("08-11");
expect(r1!.start.getHours()).toBe(8);
expect(r1!.end.getHours()).toBe(11);

// "8:00 - 10:00" is mapped to startHour:8, endHour:11 in the source map
const r2 = getStartEnd("8:00 - 10:00");
expect(r2!.start.getHours()).toBe(8);
expect(r2!.end.getHours()).toBe(11);
});

it("returns null for unrecognized or unavailability strings", () => {
expect(getStartEnd("not")).toBeNull();
expect(getStartEnd("available")).toBeNull();
expect(getStartEnd("verfügbar")).toBeNull();
expect(getStartEnd("unknown")).toBeNull();
});
});
44 changes: 44 additions & 0 deletions src/test/data/utils/passwd.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as bcrypt from "bcrypt";
import { describe, expect, it, vi } from "vitest";
import { hashPassword, verifyPassword } from "../../../data/utils";

vi.mock("bcrypt");

describe("hashPassword", () => {
it("uses 10 salt rounds and returns the resulting hash", async () => {
vi.mocked(bcrypt.genSalt).mockResolvedValue("fakesalt" as any);
vi.mocked(bcrypt.hash).mockResolvedValue("$2b$10$fakehash" as any);

const result = await hashPassword("mypassword");

expect(bcrypt.genSalt).toHaveBeenCalledWith(10);
expect(bcrypt.hash).toHaveBeenCalledWith("mypassword", "fakesalt");
expect(result).toBe("$2b$10$fakehash");
});

it("wraps bcrypt errors in a generic message", async () => {
vi.mocked(bcrypt.genSalt).mockRejectedValue(new Error("bcrypt internal failure"));
await expect(hashPassword("password")).rejects.toThrow("Could not hash password.");
});
});

describe("verifyPassword", () => {
it("returns true when bcrypt.compare resolves to true", async () => {
vi.mocked(bcrypt.compare).mockResolvedValue(true as any);

const result = await verifyPassword("plain", "$2b$10$hash");

expect(bcrypt.compare).toHaveBeenCalledWith("plain", "$2b$10$hash");
expect(result).toBe(true);
});

it("returns false when bcrypt.compare resolves to false", async () => {
vi.mocked(bcrypt.compare).mockResolvedValue(false as any);
expect(await verifyPassword("wrong", "$2b$10$hash")).toBe(false);
});

it("wraps bcrypt errors in a generic message", async () => {
vi.mocked(bcrypt.compare).mockRejectedValue(new Error("bcrypt internal failure"));
await expect(verifyPassword("plain", "hash")).rejects.toThrow("Could not verify password.");
});
});