- add normalizeAiDateString() to coerce bare ISO datetimes, date-only strings, and fractional-second variants into offset-aware format - apply via z.preprocess in AiEventResponseItemSchema so Zod validation no longer rejects AI responses missing a timezone offset - fix system prompt to use toISOString() (unambiguous UTC) and clarify expected datetime format for the AI model - install bun-types and add to tsconfig so bun:test resolves cleanly - add 8 behaviour-driven tests covering all normalizer edge cases
84 lines
2.6 KiB
TypeScript
84 lines
2.6 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { normalizeAiDateString } from "@/lib/date-normalizer";
|
|
import { AiEventResponseSchema } from "@/lib/types";
|
|
|
|
describe("normalizeAiDateString", () => {
|
|
test("converts bare ISO datetime (no offset) to valid offset datetime accepted by Zod", () => {
|
|
const input = "2025-04-10T10:00:00";
|
|
const result = normalizeAiDateString(input);
|
|
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$/);
|
|
expect(new Date(result).getHours()).toBe(10);
|
|
});
|
|
|
|
test("converts date-only string to midnight datetime with offset", () => {
|
|
const input = "2025-04-10";
|
|
const result = normalizeAiDateString(input);
|
|
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T00:00:00(Z|[+-]\d{2}:\d{2})$/);
|
|
});
|
|
|
|
test("passes through already-valid datetime with offset unchanged", () => {
|
|
const input = "2025-04-10T10:00:00-04:00";
|
|
const result = normalizeAiDateString(input);
|
|
expect(result).toBe("2025-04-10T10:00:00-04:00");
|
|
});
|
|
|
|
test("passes through datetime with Z suffix unchanged", () => {
|
|
const input = "2025-04-10T10:00:00Z";
|
|
const result = normalizeAiDateString(input);
|
|
expect(result).toBe("2025-04-10T10:00:00Z");
|
|
});
|
|
|
|
test("handles datetime with fractional seconds", () => {
|
|
const input = "2025-04-10T10:00:00.000Z";
|
|
const result = normalizeAiDateString(input);
|
|
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
|
expect(new Date(result).getHours()).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe("AiEventResponseSchema date normalization", () => {
|
|
test("accepts event with bare ISO datetime (no offset) by normalizing it", () => {
|
|
const raw = [
|
|
{
|
|
title: "Team Meeting",
|
|
start: "2025-04-10T10:00:00",
|
|
end: "2025-04-10T11:00:00",
|
|
},
|
|
];
|
|
const result = AiEventResponseSchema.safeParse(raw);
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data[0].start).toMatch(
|
|
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$/,
|
|
);
|
|
}
|
|
});
|
|
|
|
test("accepts event with date-only start and no end", () => {
|
|
const raw = [
|
|
{
|
|
title: "All-day workshop",
|
|
start: "2025-07-20",
|
|
allDay: true,
|
|
},
|
|
];
|
|
const result = AiEventResponseSchema.safeParse(raw);
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
test("accepts event with already-valid offset datetime", () => {
|
|
const raw = [
|
|
{
|
|
title: "Standup",
|
|
start: "2025-04-10T09:00:00-05:00",
|
|
end: "2025-04-10T09:30:00-05:00",
|
|
},
|
|
];
|
|
const result = AiEventResponseSchema.safeParse(raw);
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data[0].start).toBe("2025-04-10T09:00:00-05:00");
|
|
}
|
|
});
|
|
});
|