import { describe, expect, it } from "vitest"; import { z } from "zod/v4"; import { defineApp, defineFileType, defineTool } from "../define/define"; import { ui } from "../tree/tree"; import { createTestWorkspace } from "./test-workspace"; const createContact = defineTool({ name: "crm.create_contact", description: "Create a contact.", input: z.object({ name: z.string(), email: z.string().optional() }), effect: "write", execute: async (input, context) => { const file = await context.coline.files.create({ typeKey: "crm.contact", name: input.name, document: { name: input.name, email: input.email ?? null }, }); await context.coline.search.index.upsert({ documents: [ { documentKey: `contact:${file.fileId}`, documentType: "contact", fileId: file.fileId, title: input.name, body: input.email ?? "", }, ], }); return { output: { fileId: file.fileId }, card: ui.stack([ui.reference({ kind: "file", id: file.fileId })]), }; }, }); const app = defineApp({ key: "crm", name: "CRM", permissions: ["files.read", "files.write", "storage.app", "ai.tools", "search.index"], surfaces: { home: { tier: "tree" } }, files: [ defineFileType({ metadata: { typeKey: "crm.contact", name: "Contact", indexable: false, surfaces: { preview: { tier: "tree" }, inline: { tier: "tree" } }, }, runtime: { renderPreview: (context) => ui.heading(String(context.document.name ?? "Contact")), renderInline: (context) => ui.text(String(context.document.name ?? "Contact")), }, }), ], tools: [createContact], triggers: { schedules: [{ id: "digest", cron: "0 9 * * 1-5" }] }, handlers: { renderHome: async (context) => { const { files } = await context.coline.files.list({ typeKey: "crm.contact" }); return ui.stack([ui.heading("CRM"), ui.text(`${files.length} contacts`)]); }, onSchedule: async (context) => { await context.coline.storage.kv.set("last-digest", context.firedAt); }, }, }); describe("createTestWorkspace", () => { it("invokes tools through the schema and captures state", async () => { const workspace = createTestWorkspace(app); const result = await workspace.invokeTool("crm.create_contact", { name: "Sarah" }); expect(result.output).toMatchObject({ fileId: expect.stringContaining("file_test") }); expect(result.card?.type).toBe("Stack"); expect(workspace.files.byType("crm.contact")).toHaveLength(1); expect(workspace.state.indexDocuments.size).toBe(1); }); it("rejects invalid tool input with schema detail", async () => { const workspace = createTestWorkspace(app); await expect(workspace.invokeTool("crm.create_contact", { name: 42 })).rejects.toThrow( /invalid/i, ); }); it("renders home against live fake state", async () => { const workspace = createTestWorkspace(app); await workspace.invokeTool("crm.create_contact", { name: "A" }); await workspace.invokeTool("crm.create_contact", { name: "B" }); const tree = await workspace.renderHome(); expect(JSON.stringify(tree)).toContain("2 contacts"); }); it("renders file surfaces and enforces the inline subset", async () => { const workspace = createTestWorkspace(app); const preview = await workspace.renderFileSurface( "preview", { fileId: "f1", name: "Sarah", typeKey: "crm.contact" }, { name: "Sarah Chen" }, ); expect(JSON.stringify(preview)).toContain("Sarah Chen"); }); it("fires declared schedules with the schedule context", async () => { const workspace = createTestWorkspace(app); const at = new Date("2026-08-10T09:00:00Z"); await workspace.fireSchedule("digest", at); expect(workspace.state.kv.get("last-digest")).toBe(at.toISOString()); }); it("rejects undeclared schedules", async () => { const workspace = createTestWorkspace(app); await expect(workspace.fireSchedule("nope")).rejects.toThrow(/not declared/); }); }); describe("runtime parity", () => { it("allows interactive home writes but rejects preview writes", async () => { const ws = createTestWorkspace(app); await ws.client().files.create({ typeKey: "crm.contact", name: "Allowed" }); await expect( ws.client("preview").files.create({ typeKey: "crm.contact", name: "Denied" }), ).rejects.toMatchObject({ code: "effect_ceiling_exceeded" }); }); it("enforces revoked grants in tools and clients", async () => { const ws = createTestWorkspace(app, { grantedPermissions: ["files.read"] }); await expect(ws.invokeTool("crm.create_contact", { name: "Denied" })).rejects.toMatchObject({ code: "permission_not_granted", }); expect(ws.files.all()).toHaveLength(0); }); it("filters, sorts and pages records without exposing mutable stored objects", async () => { const ws = createTestWorkspace(app); const records = ws.client().storage.collection<{ name: string; score: number }>("contacts"); await records.insert({ name: "Alice", score: 2 }); await records.insert({ name: "Alex", score: 3 }); await records.insert({ name: "Bob", score: 4 }); const first = await records.query({ where: { name: { contains: "al" }, score: { gte: 2 } }, orderBy: "name", order: "asc", limit: 1, }); expect(first.records[0]?.data.name).toBe("Alex"); const second = await records.query({ where: { name: { contains: "al" } }, orderBy: "name", order: "asc", limit: 1, cursor: first.nextCursor!, }); expect(second.records[0]?.data.name).toBe("Alice"); first.records[0]!.data.name = "Changed outside capability"; expect((await records.get(first.records[0]!.id))?.data.name).toBe("Alex"); }); it("enforces read-only rendering", async () => { const bad = defineApp({ key: "bad", name: "Bad", permissions: ["storage.app"], surfaces: { home: { tier: "tree" } }, handlers: { renderHome: async ({ coline }) => { await coline.storage.kv.set("bad", true); return ui.text("bad"); }, }, }); await expect(createTestWorkspace(bad).renderHome()).rejects.toMatchObject({ code: "effect_ceiling_exceeded", }); }); }); describe("interactive tools", () => { it("invokes declared tools from a client and preserves preview ceilings", async () => { const ws = createTestWorkspace(app); await ws.client().tools.invoke("crm.create_contact", { name: "Client" }); expect(ws.files.all()).toHaveLength(1); await expect( ws.client("preview").tools.invoke("crm.create_contact", { name: "Denied" }), ).rejects.toMatchObject({ code: "effect_ceiling_exceeded" }); expect(ws.files.all()).toHaveLength(1); }); it("blocks interactive invocation when the tool grant is revoked", async () => { const ws = createTestWorkspace(app, { grantedPermissions: ["files.read", "files.write", "storage.app", "search.index"], }); await expect( ws.client().tools.invoke("crm.create_contact", { name: "Denied" }), ).rejects.toMatchObject({ code: "permission_not_granted" }); expect(ws.files.all()).toHaveLength(0); }); });