test(api): cover assistant country tools

This commit is contained in:
2026-04-01 00:26:21 +02:00
parent 734e1eff42
commit c88f2342d5
5 changed files with 394 additions and 0 deletions
@@ -0,0 +1,141 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { PermissionKey } from "@capakraken/shared";
import { executeTool } from "../router/assistant-tools.js";
import { createToolContext } from "./assistant-tools-country-test-helpers.js";
describe("assistant country get tools", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("gets a country by identifier and falls back from id to code and name lookup", async () => {
const db = {
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 },
})
.mockResolvedValueOnce(null)
.mockResolvedValueOnce({
id: "country_de",
code: "DE",
name: "Germany",
dailyWorkingHours: 8,
scheduleRules: null,
isActive: true,
metroCities: [{ id: "city_muc", name: "Munich" }],
_count: { resources: 12 },
}),
},
};
const ctx = createToolContext(db, [PermissionKey.VIEW_ALL_RESOURCES]);
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" }],
});
const fallbackResult = await executeTool(
"get_country",
JSON.stringify({ identifier: "Germany" }),
ctx,
);
expect(db.country.findUnique).toHaveBeenNthCalledWith(1, {
where: { id: "ES" },
include: {
metroCities: { orderBy: { name: "asc" } },
_count: { select: { resources: true } },
},
});
expect(db.country.findUnique).toHaveBeenNthCalledWith(2, {
where: { id: "Germany" },
include: {
metroCities: { orderBy: { name: "asc" } },
_count: { select: { resources: true } },
},
});
expect(db.country.findFirst).toHaveBeenNthCalledWith(1, {
where: { code: { equals: "ES", mode: "insensitive" } },
include: {
metroCities: { orderBy: { name: "asc" } },
_count: { select: { resources: true } },
},
});
expect(db.country.findFirst).toHaveBeenNthCalledWith(2, {
where: { code: { equals: "GERMANY", mode: "insensitive" } },
include: {
metroCities: { orderBy: { name: "asc" } },
_count: { select: { resources: true } },
},
});
expect(db.country.findFirst).toHaveBeenNthCalledWith(3, {
where: { name: { equals: "Germany", mode: "insensitive" } },
include: {
metroCities: { orderBy: { name: "asc" } },
_count: { select: { resources: true } },
},
});
expect(JSON.parse(fallbackResult.content)).toEqual({
id: "country_de",
code: "DE",
name: "Germany",
dailyWorkingHours: 8,
scheduleRules: null,
isActive: true,
resourceCount: 12,
metroCities: [{ id: "city_muc", name: "Munich" }],
cities: ["Munich"],
});
});
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),
},
}, [PermissionKey.VIEW_ALL_RESOURCES]);
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.",
});
});
});
@@ -0,0 +1,67 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { executeTool } from "../router/assistant-tools.js";
import { createToolContext } from "./assistant-tools-country-test-helpers.js";
describe("assistant country list tools", () => {
beforeEach(() => {
vi.clearAllMocks();
});
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" }],
});
});
});
@@ -0,0 +1,65 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { SystemRole } from "@capakraken/shared";
import { executeTool } from "../router/assistant-tools.js";
import { createToolContext } from "./assistant-tools-country-test-helpers.js";
describe("assistant country mutation tools - errors", () => {
beforeEach(() => {
vi.clearAllMocks();
});
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: "You do not have permission to perform this action.",
});
});
});
@@ -0,0 +1,95 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { executeTool } from "../router/assistant-tools.js";
import { createToolContext } from "./assistant-tools-country-test-helpers.js";
describe("assistant country mutation tools - success", () => {
beforeEach(() => {
vi.clearAllMocks();
});
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("updates a country for admin users and keeps invalidation semantics stable", async () => {
const update = vi.fn().mockResolvedValue({
id: "country_es",
code: "ES",
name: "Spain Updated",
dailyWorkingHours: 7.5,
scheduleRules: { shortFriday: true },
isActive: false,
metroCities: [],
_count: { resources: 0 },
});
const ctx = createToolContext({
country: {
findUnique: vi.fn().mockResolvedValue({ id: "country_es", code: "ES", name: "Spain" }),
update,
},
});
const result = await executeTool(
"update_country",
JSON.stringify({
id: "country_es",
data: {
name: "Spain Updated",
dailyWorkingHours: 7.5,
scheduleRules: null,
isActive: false,
},
}),
ctx,
);
expect(update).toHaveBeenCalledWith({
where: { id: "country_es" },
data: {
name: "Spain Updated",
dailyWorkingHours: 7.5,
scheduleRules: expect.anything(),
isActive: false,
},
include: { metroCities: true },
});
expect(result.action).toEqual({
type: "invalidate",
scope: ["country", "resource", "holidayCalendar", "vacation"],
});
expect(result.data).toMatchObject({
success: true,
country: { id: "country_es", isActive: false, name: "Spain Updated" },
});
});
});
@@ -0,0 +1,26 @@
import { SystemRole } from "@capakraken/shared";
import type { ToolContext } from "../router/assistant-tools.js";
export function createToolContext(
db: Record<string, unknown>,
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,
};
}