Files
local-cal/tests/ai-event-schema.test.ts
Dmytro Stanchiev 513aafcebc feat: support multiple image uploads for AI event generation 🖼️
- Updated OpenRouter integration to accept an array of image URLs
- Updated ImagePicker to use the `multiple` attribute natively
- Added `appendImagesDeduped` for handling client-side image deduplication
- Enhanced clipboard pasting to extract multiple images at once
- Rendered multiple images in a horizontal thumbnail strip in the AIToolbar
- Added tests to cover multi-image logic and AI request mapping
2026-04-08 20:46:43 -04:00

102 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, test } from "bun:test";
import { AiEventRequestSchema } from "@/lib/types";
// ---------------------------------------------------------------------------
// AiEventRequestSchema behavioral validation tests
//
// The schema is the contract between the client and the API route.
// Tests verify what the schema ALLOWS and what it REJECTS.
// ---------------------------------------------------------------------------
const VALID_PNG =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
const VALID_JPEG =
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AJQAB/9k=";
describe("AiEventRequestSchema images array", () => {
test("accepts a prompt with no images", () => {
const result = AiEventRequestSchema.safeParse({ prompt: "Team standup every Monday at 9am" });
expect(result.success).toBe(true);
});
test("accepts images array with one valid base64 image", () => {
const result = AiEventRequestSchema.safeParse({
prompt: "What events are on this flyer?",
images: [VALID_PNG],
});
expect(result.success).toBe(true);
});
test("accepts images array with multiple valid base64 images", () => {
const result = AiEventRequestSchema.safeParse({
images: [VALID_PNG, VALID_JPEG],
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.images).toHaveLength(2);
}
});
test("accepts request with images only (no prompt)", () => {
const result = AiEventRequestSchema.safeParse({ images: [VALID_PNG] });
expect(result.success).toBe(true);
});
test("rejects request with neither prompt nor images", () => {
const result = AiEventRequestSchema.safeParse({});
expect(result.success).toBe(false);
});
test("rejects images array containing an invalid data URL", () => {
const result = AiEventRequestSchema.safeParse({
images: ["not-a-data-url"],
});
expect(result.success).toBe(false);
});
test("rejects images array containing a non-image data URL (e.g. PDF)", () => {
const result = AiEventRequestSchema.safeParse({
images: ["data:application/pdf;base64,abc123"],
});
expect(result.success).toBe(false);
});
test("rejects empty images array (must have at least one image OR a prompt)", () => {
// An empty images array with no prompt should fail the 'prompt or images' refinement
const result = AiEventRequestSchema.safeParse({ images: [] });
expect(result.success).toBe(false);
});
test("returns data.images as string[] when images are valid", () => {
const result = AiEventRequestSchema.safeParse({
images: [VALID_PNG, VALID_JPEG],
});
expect(result.success).toBe(true);
if (result.success) {
expect(Array.isArray(result.data.images)).toBe(true);
for (const img of result.data.images ?? []) {
expect(typeof img).toBe("string");
}
}
});
});
describe("AiEventRequestSchema prompt validation", () => {
test("accepts a plain text prompt with no images", () => {
const result = AiEventRequestSchema.safeParse({ prompt: "Birthday party Saturday" });
expect(result.success).toBe(true);
});
test("rejects a prompt that exceeds 2000 characters", () => {
const longPrompt = "a".repeat(2001);
const result = AiEventRequestSchema.safeParse({ prompt: longPrompt });
expect(result.success).toBe(false);
});
test("accepts a prompt of exactly 2000 characters", () => {
const maxPrompt = "a".repeat(2000);
const result = AiEventRequestSchema.safeParse({ prompt: maxPrompt });
expect(result.success).toBe(true);
});
});