/**
* Tests for validation schemas and utilities.
*/
import { describe, it, expect } from "vitest";
import { z } from "zod";
import { validate, formatValidationError, isValidationError } from "../validation/index.js";
import {
getTasksSchema,
createTaskSchema,
markTaskSchema,
markTasksSchema,
setTaskPrioritySchema,
setTaskDeadlineSchema,
searchTasksSchema,
} from "../validation/task-schemas.js";
import {
searchLogseqSchema,
getPageSchema,
getPagesSchema,
createBlockSchema,
updateBlockSchema,
createPageSchema,
findMissingPagesSchema,
updatePagePropertiesSchema,
} from "../validation/page-schemas.js";
import {
appendToTodaySchema,
getRecentJournalsSchema,
findRelatedPagesSchema,
} from "../validation/journal-schemas.js";
describe("Validation Utilities", () => {
describe("validate", () => {
it("returns validated data for valid input", () => {
const schema = z.object({ name: z.string() });
const result = validate(schema, { name: "test" });
expect(result).toEqual({ name: "test" });
});
it("handles undefined args as empty object", () => {
const schema = z.object({ name: z.string().optional() });
const result = validate(schema, undefined);
expect(result).toEqual({});
});
it("throws ZodError for invalid input", () => {
const schema = z.object({ name: z.string() });
expect(() => validate(schema, { name: 123 })).toThrow(z.ZodError);
});
it("throws ZodError for missing required fields", () => {
const schema = z.object({ name: z.string() });
expect(() => validate(schema, {})).toThrow(z.ZodError);
});
});
describe("formatValidationError", () => {
it("formats single error", () => {
const schema = z.object({ name: z.string() });
try {
schema.parse({});
} catch (error) {
const message = formatValidationError(error as z.ZodError);
expect(message).toContain("Validation error:");
expect(message).toContain("name");
}
});
it("formats multiple errors", () => {
const schema = z.object({
name: z.string(),
age: z.number(),
});
try {
schema.parse({});
} catch (error) {
const message = formatValidationError(error as z.ZodError);
expect(message).toContain("name");
expect(message).toContain("age");
}
});
it("includes custom error messages", () => {
const schema = z.object({
uuid: z.string().min(1, "UUID is required"),
});
try {
schema.parse({ uuid: "" });
} catch (error) {
const message = formatValidationError(error as z.ZodError);
expect(message).toContain("UUID is required");
}
});
});
describe("isValidationError", () => {
it("returns true for ZodError", () => {
const schema = z.object({ name: z.string() });
try {
schema.parse({});
} catch (error) {
expect(isValidationError(error)).toBe(true);
}
});
it("returns false for regular Error", () => {
expect(isValidationError(new Error("test"))).toBe(false);
});
it("returns false for non-errors", () => {
expect(isValidationError("string")).toBe(false);
expect(isValidationError(null)).toBe(false);
expect(isValidationError(undefined)).toBe(false);
});
});
});
describe("Task Schemas", () => {
describe("getTasksSchema", () => {
it("accepts empty object", () => {
expect(() => getTasksSchema.parse({})).not.toThrow();
});
it("accepts valid markers", () => {
const result = getTasksSchema.parse({ markers: ["TODO", "DOING"] });
expect(result.markers).toEqual(["TODO", "DOING"]);
});
it("rejects invalid markers", () => {
expect(() => getTasksSchema.parse({ markers: ["INVALID"] })).toThrow();
});
});
describe("createTaskSchema", () => {
it("requires pageName and content", () => {
expect(() => createTaskSchema.parse({})).toThrow();
expect(() => createTaskSchema.parse({ pageName: "Test" })).toThrow();
expect(() => createTaskSchema.parse({ content: "Task" })).toThrow();
});
it("accepts valid task", () => {
const result = createTaskSchema.parse({
pageName: "Test",
content: "My task",
priority: "A",
});
expect(result.pageName).toBe("Test");
expect(result.content).toBe("My task");
expect(result.priority).toBe("A");
});
it("validates date format", () => {
expect(() =>
createTaskSchema.parse({
pageName: "Test",
content: "Task",
deadline: "invalid-date",
})
).toThrow();
const result = createTaskSchema.parse({
pageName: "Test",
content: "Task",
deadline: "2024-12-25",
});
expect(result.deadline).toBe("2024-12-25");
});
it("rejects empty strings", () => {
expect(() => createTaskSchema.parse({ pageName: "", content: "Task" })).toThrow();
expect(() => createTaskSchema.parse({ pageName: "Test", content: "" })).toThrow();
});
});
describe("markTaskSchema", () => {
it("requires uuid and marker", () => {
expect(() => markTaskSchema.parse({})).toThrow();
expect(() => markTaskSchema.parse({ uuid: "123" })).toThrow();
});
it("validates marker enum", () => {
expect(() => markTaskSchema.parse({ uuid: "123", marker: "INVALID" })).toThrow();
const result = markTaskSchema.parse({ uuid: "123", marker: "DONE" });
expect(result.marker).toBe("DONE");
});
});
describe("markTasksSchema", () => {
it("requires non-empty uuids array", () => {
expect(() => markTasksSchema.parse({ uuids: [], marker: "DONE" })).toThrow();
});
it("accepts valid batch", () => {
const result = markTasksSchema.parse({
uuids: ["uuid1", "uuid2"],
marker: "DONE",
});
expect(result.uuids).toHaveLength(2);
});
});
describe("setTaskPrioritySchema", () => {
it("accepts null priority (to remove)", () => {
const result = setTaskPrioritySchema.parse({ uuid: "123", priority: null });
expect(result.priority).toBeNull();
});
it("accepts valid priority", () => {
const result = setTaskPrioritySchema.parse({ uuid: "123", priority: "B" });
expect(result.priority).toBe("B");
});
});
describe("setTaskDeadlineSchema", () => {
it("validates date format", () => {
expect(() => setTaskDeadlineSchema.parse({ uuid: "123", date: "12/25/2024" })).toThrow();
const result = setTaskDeadlineSchema.parse({
uuid: "123",
date: "2024-12-25",
});
expect(result.date).toBe("2024-12-25");
});
it("accepts null date (to remove)", () => {
const result = setTaskDeadlineSchema.parse({ uuid: "123", date: null });
expect(result.date).toBeNull();
});
});
describe("searchTasksSchema", () => {
it("requires query", () => {
expect(() => searchTasksSchema.parse({})).toThrow();
expect(() => searchTasksSchema.parse({ query: "" })).toThrow();
});
it("accepts optional filters", () => {
const result = searchTasksSchema.parse({
query: "test",
markers: ["TODO"],
pageName: "MyPage",
});
expect(result.query).toBe("test");
expect(result.markers).toEqual(["TODO"]);
});
});
});
describe("Page Schemas", () => {
describe("searchLogseqSchema", () => {
it("requires non-empty query", () => {
expect(() => searchLogseqSchema.parse({})).toThrow();
expect(() => searchLogseqSchema.parse({ query: "" })).toThrow();
});
});
describe("getPageSchema", () => {
it("requires pageName", () => {
expect(() => getPageSchema.parse({})).toThrow();
});
it("accepts valid pageName", () => {
const result = getPageSchema.parse({ pageName: "My Page" });
expect(result.pageName).toBe("My Page");
});
});
describe("getPagesSchema", () => {
it("requires non-empty pageNames array", () => {
expect(() => getPagesSchema.parse({ pageNames: [] })).toThrow();
});
it("accepts valid array", () => {
const result = getPagesSchema.parse({ pageNames: ["Page1", "Page2"] });
expect(result.pageNames).toHaveLength(2);
});
});
describe("createBlockSchema", () => {
it("requires content", () => {
expect(() => createBlockSchema.parse({})).toThrow();
});
it("requires pageName OR parentBlockUuid", () => {
expect(() => createBlockSchema.parse({ content: "Test" })).toThrow();
});
it("accepts pageName", () => {
const result = createBlockSchema.parse({
content: "Test",
pageName: "MyPage",
});
expect(result.pageName).toBe("MyPage");
});
it("accepts parentBlockUuid", () => {
const result = createBlockSchema.parse({
content: "Test",
parentBlockUuid: "uuid-123",
});
expect(result.parentBlockUuid).toBe("uuid-123");
});
it("accepts both pageName and parentBlockUuid", () => {
const result = createBlockSchema.parse({
content: "Test",
pageName: "MyPage",
parentBlockUuid: "uuid-123",
});
expect(result.pageName).toBe("MyPage");
expect(result.parentBlockUuid).toBe("uuid-123");
});
});
describe("updateBlockSchema", () => {
it("requires uuid and content", () => {
expect(() => updateBlockSchema.parse({})).toThrow();
expect(() => updateBlockSchema.parse({ uuid: "123" })).toThrow();
expect(() => updateBlockSchema.parse({ content: "Test" })).toThrow();
});
});
describe("createPageSchema", () => {
it("requires pageName", () => {
expect(() => createPageSchema.parse({})).toThrow();
});
it("accepts optional content and blocks", () => {
const result = createPageSchema.parse({
pageName: "Test",
content: "Initial content",
blocks: [{ content: "Block 1" }],
});
expect(result.pageName).toBe("Test");
expect(result.content).toBe("Initial content");
expect(result.blocks).toHaveLength(1);
});
});
describe("findMissingPagesSchema", () => {
it("accepts empty object", () => {
expect(() => findMissingPagesSchema.parse({})).not.toThrow();
});
it("validates minReferences is positive", () => {
expect(() => findMissingPagesSchema.parse({ minReferences: 0 })).toThrow();
expect(() => findMissingPagesSchema.parse({ minReferences: -1 })).toThrow();
const result = findMissingPagesSchema.parse({ minReferences: 5 });
expect(result.minReferences).toBe(5);
});
});
describe("updatePagePropertiesSchema", () => {
it("requires pageName and non-empty properties", () => {
expect(() => updatePagePropertiesSchema.parse({})).toThrow();
expect(() =>
updatePagePropertiesSchema.parse({ pageName: "Test", properties: {} })
).toThrow();
});
it("accepts valid properties", () => {
const result = updatePagePropertiesSchema.parse({
pageName: "Test",
properties: { type: "#person", team: "[[Engineering]]" },
});
expect(result.properties.type).toBe("#person");
});
});
});
describe("Journal Schemas", () => {
describe("appendToTodaySchema", () => {
it("requires non-empty content", () => {
expect(() => appendToTodaySchema.parse({})).toThrow();
expect(() => appendToTodaySchema.parse({ content: "" })).toThrow();
});
});
describe("getRecentJournalsSchema", () => {
it("accepts empty object", () => {
expect(() => getRecentJournalsSchema.parse({})).not.toThrow();
});
it("validates days is positive", () => {
expect(() => getRecentJournalsSchema.parse({ days: 0 })).toThrow();
const result = getRecentJournalsSchema.parse({ days: 14 });
expect(result.days).toBe(14);
});
});
describe("findRelatedPagesSchema", () => {
it("requires pageName", () => {
expect(() => findRelatedPagesSchema.parse({})).toThrow();
});
});
});
describe("Error Message Quality", () => {
it("provides clear message for missing required field", () => {
try {
markTaskSchema.parse({});
} catch (error) {
const message = formatValidationError(error as z.ZodError);
expect(message).toMatch(/uuid|marker/i);
}
});
it("provides clear message for invalid enum value", () => {
try {
markTaskSchema.parse({ uuid: "123", marker: "INVALID" });
} catch (error) {
const message = formatValidationError(error as z.ZodError);
expect(message).toContain("marker");
}
});
it("provides clear message for invalid date format", () => {
try {
setTaskDeadlineSchema.parse({ uuid: "123", date: "bad-date" });
} catch (error) {
const message = formatValidationError(error as z.ZodError);
expect(message).toContain("YYYY-MM-DD");
}
});
it("provides clear message for empty required string", () => {
try {
createTaskSchema.parse({ pageName: "", content: "Task" });
} catch (error) {
const message = formatValidationError(error as z.ZodError);
expect(message).toContain("pageName");
}
});
it("provides clear message for refinement failure", () => {
try {
createBlockSchema.parse({ content: "Test" });
} catch (error) {
const message = formatValidationError(error as z.ZodError);
expect(message).toMatch(/pageName|parentBlockUuid/i);
}
});
});