import { describe, expect, it, vi } from "vitest"; import { SystemRole } from "@capakraken/shared"; import { executeTool, type ToolContext } from "../router/assistant-tools.js"; function createToolContext( db: Record, permissions: string[] = [], userRole: SystemRole = SystemRole.ADMIN, ): ToolContext { return { db: db as ToolContext["db"], userId: "user_1", userRole, permissions: new Set(permissions) as ToolContext["permissions"], session: { user: { email: "assistant@example.com", name: "Assistant User", image: null }, expires: "2026-03-29T00:00:00.000Z", }, dbUser: { id: "user_1", systemRole: userRole, permissionOverrides: null, }, roleDefaults: null, }; } describe("assistant country tools", () => { it("lists countries with schedule rules, active state, and metro cities", async () => { const findMany = vi.fn().mockResolvedValue([ { id: "country_de", code: "DE", name: "Deutschland", dailyWorkingHours: 8, scheduleRules: null, isActive: true, metroCities: [{ id: "city_muc", name: "Munich" }], }, { id: "country_es", code: "ES", name: "Spain", dailyWorkingHours: 8, scheduleRules: null, isActive: true, metroCities: [{ id: "city_mad", name: "Madrid" }], }, ]); const ctx = createToolContext({ country: { findMany, }, }); const result = await executeTool( "list_countries", JSON.stringify({ search: "deu" }), ctx, ); const parsed = JSON.parse(result.content) as { count: number; countries: Array<{ code: string; isActive: boolean; metroCities: Array<{ id: string; name: string }>; cities: string[]; }>; }; expect(findMany).toHaveBeenCalledWith({ where: { isActive: true }, include: { metroCities: { orderBy: { name: "asc" } } }, orderBy: { name: "asc" }, }); expect(parsed.count).toBe(1); expect(parsed.countries[0]).toMatchObject({ code: "DE", isActive: true, cities: ["Munich"], metroCities: [{ id: "city_muc", name: "Munich" }], }); }); it("gets a country by code and exposes schedule details and resource count", async () => { const ctx = createToolContext({ country: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi .fn() .mockResolvedValueOnce({ id: "country_es", code: "ES", name: "Spain", dailyWorkingHours: 8, scheduleRules: { type: "spain", fridayHours: 6.5, summerPeriod: { from: "07-01", to: "09-15" }, summerHours: 6.5, regularHours: 9, }, isActive: true, metroCities: [{ id: "city_mad", name: "Madrid" }], _count: { resources: 4 }, }), }, }); const result = await executeTool( "get_country", JSON.stringify({ identifier: "ES" }), ctx, ); const parsed = JSON.parse(result.content) as { code: string; resourceCount: number | null; scheduleRules: { type: string }; metroCities: Array<{ name: string }>; }; expect(parsed).toMatchObject({ code: "ES", resourceCount: 4, scheduleRules: { type: "spain" }, metroCities: [{ name: "Madrid" }], }); }); it("returns a stable error when a country cannot be resolved", async () => { const ctx = createToolContext({ country: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn().mockResolvedValue(null), }, }); const result = await executeTool( "get_country", JSON.stringify({ identifier: "Atlantis" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Country not found with the given criteria.", }); }); it("creates a country for admin users and returns an invalidation action", async () => { const ctx = createToolContext({ country: { findUnique: vi.fn().mockResolvedValue(null), create: vi.fn().mockResolvedValue({ id: "country_es", code: "ES", name: "Spain", dailyWorkingHours: 8, scheduleRules: null, isActive: true, metroCities: [], _count: { resources: 0 }, }), }, }); const result = await executeTool( "create_country", JSON.stringify({ code: "ES", name: "Spain", dailyWorkingHours: 8 }), ctx, ); expect(result.action).toEqual({ type: "invalidate", scope: ["country", "resource", "holidayCalendar", "vacation"], }); expect(result.data).toMatchObject({ success: true, country: { code: "ES", name: "Spain" }, }); }); it("returns a stable error when creating a country with a duplicate code", async () => { const ctx = createToolContext({ country: { findUnique: vi.fn().mockResolvedValue({ id: "country_es_existing", code: "ES", name: "Existing Spain", }), }, }); const result = await executeTool( "create_country", JSON.stringify({ code: "ES", name: "Spain", dailyWorkingHours: 8 }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "A country with this code already exists.", }); }); it("returns a stable error when updating a missing country", async () => { const ctx = createToolContext({ country: { findUnique: vi.fn().mockResolvedValue(null), }, }); const result = await executeTool( "update_country", JSON.stringify({ id: "country_missing", data: { name: "Atlantis" } }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Country not found with the given criteria.", }); }); it("refuses country mutations for non-admin users", async () => { const ctx = createToolContext({ country: {} }, [], SystemRole.MANAGER); const result = await executeTool( "create_country", JSON.stringify({ code: "ES", name: "Spain" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Admin role required to perform this action.", }); }); it("deletes metro cities only when no resources are assigned", async () => { const ctx = createToolContext({ metroCity: { findUnique: vi.fn().mockResolvedValue({ id: "city_ham", name: "Hamburg", _count: { resources: 0 }, }), delete: vi.fn().mockResolvedValue(undefined), }, }); const result = await executeTool( "delete_metro_city", JSON.stringify({ id: "city_ham" }), ctx, ); expect(result.action).toEqual({ type: "invalidate", scope: ["country", "resource", "holidayCalendar", "vacation"], }); expect(result.data).toMatchObject({ success: true, message: "Deleted metro city: Hamburg", }); }); it("returns a stable error when updating a missing metro city", async () => { const ctx = createToolContext({ metroCity: { findUnique: vi.fn().mockResolvedValue(null), }, }); const result = await executeTool( "update_metro_city", JSON.stringify({ id: "city_missing", data: { name: "Hamburg-Mitte" } }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Metro city not found with the given criteria.", }); }); it("returns a stable error when deleting a metro city that is still assigned", async () => { const ctx = createToolContext({ metroCity: { findUnique: vi.fn().mockResolvedValue({ id: "city_ham", name: "Hamburg", _count: { resources: 3 }, }), }, }); const result = await executeTool( "delete_metro_city", JSON.stringify({ id: "city_ham" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Metro city cannot be deleted while it is still assigned to resources.", }); }); });