rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI
CI / Unit Tests (pull_request) Successful in 5m46s
CI / Lint (pull_request) Failing after 3m49s
CI / E2E Tests (pull_request) Has been skipped
CI / Fresh-Linux Docker Deploy (pull_request) Has been skipped
CI / Assistant Split Regression (pull_request) Failing after 35s
CI / Architecture Guardrails (pull_request) Failing after 2m14s
CI / Typecheck (pull_request) Successful in 4m22s
CI / Build (pull_request) Has been skipped
CI / Release Images (pull_request) Has been skipped
CI / Unit Tests (pull_request) Successful in 5m46s
CI / Lint (pull_request) Failing after 3m49s
CI / E2E Tests (pull_request) Has been skipped
CI / Fresh-Linux Docker Deploy (pull_request) Has been skipped
CI / Assistant Split Regression (pull_request) Failing after 35s
CI / Architecture Guardrails (pull_request) Failing after 2m14s
CI / Typecheck (pull_request) Successful in 4m22s
CI / Build (pull_request) Has been skipped
CI / Release Images (pull_request) Has been skipped
- @capakraken/* → @nexus/* across 12 packages (root + 11 workspaces),
1551 import lines migrated via codemod
- User-visible brand strings renamed (emails, page titles, PWA
manifest, mobile header, MFA backup-codes header, tooltips, signin
page, invite page, weekly digest, install prompt)
- TOTP issuer "CapaKraken" → "Nexus" (existing secrets still valid;
re-enrollment relabels them in users' authenticator apps)
- Function rename: assertCapaKrakenDbTarget → assertNexusDbTarget
- LocalStorage migration shim in apps/web/src/app/layout.tsx copies
capakraken_* → nexus_* on first load (guarded by nexus_migrated_v1
sentinel; runs once per browser, then never again)
- Service-worker cache name capakraken-v2 → nexus-v2 with one-time
caches.delete('capakraken-v2') from the same shim
- Email-domain fixtures @capakraken.{dev,app} → @nexus.{dev,app} in
seed data, e2e specs, SMTP default fallback
- Dockerfile.dev / Dockerfile.prod / all .github/workflows/*.yml
pnpm --filter @capakraken/* → @nexus/*
- README, CLAUDE.md, LEARNINGS.md, all docs/*.md, .env.example,
tooling/deploy/.env.production.example brand sweep
Phase 1 deliberately leaves untouched (handled in Phase 3 cutover):
- PostgreSQL DB name "capakraken" and POSTGRES_USER "capakraken"
- Volume names capakraken_pgdata etc.
- Compose project name "capakraken" / "capakraken-prod"
- db-target-guard default expectedDatabase
- env-var CAPAKRAKEN_EXPECTED_DB_NAME
- Container DNS names in docker-compose.ci.yml
Quality gates green: pnpm typecheck (7/7), pnpm test:unit (7/7),
pnpm lint (0 errors), check:exports/imports/architecture all pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@capakraken/api",
|
||||
"name": "@nexus/api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -22,11 +22,11 @@
|
||||
"test:assistant-split": "node ./scripts/run-assistant-tool-split-regression.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capakraken/application": "workspace:*",
|
||||
"@capakraken/db": "workspace:*",
|
||||
"@capakraken/engine": "workspace:*",
|
||||
"@capakraken/shared": "workspace:*",
|
||||
"@capakraken/staffing": "workspace:*",
|
||||
"@nexus/application": "workspace:*",
|
||||
"@nexus/db": "workspace:*",
|
||||
"@nexus/engine": "workspace:*",
|
||||
"@nexus/shared": "workspace:*",
|
||||
"@nexus/staffing": "workspace:*",
|
||||
"@node-rs/argon2": "^2.0.2",
|
||||
"@trpc/server": "^11.0.0",
|
||||
"@types/nodemailer": "^7.0.11",
|
||||
@@ -38,7 +38,7 @@
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capakraken/tsconfig": "workspace:*",
|
||||
"@nexus/tsconfig": "workspace:*",
|
||||
"@types/node": "^22.10.2",
|
||||
"typescript": "^5.6.3",
|
||||
"vitest": "^2.1.8",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DEFAULT_OPENAI_MODEL } from "@capakraken/shared";
|
||||
import { DEFAULT_OPENAI_MODEL } from "@nexus/shared";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { loggedAiCall, sanitizeDiagnosticError } from "../ai-client.js";
|
||||
import { logger } from "../lib/logger.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AllocationStatus, SystemRole } from "@capakraken/shared";
|
||||
import { AllocationStatus, SystemRole } from "@nexus/shared";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { allocationRouter } from "../router/allocation/index.js";
|
||||
import { createCallerFactory } from "../trpc.js";
|
||||
@@ -91,7 +91,7 @@ describe("allocation.checkConflicts", () => {
|
||||
const result = await caller.checkConflicts({
|
||||
resourceId: "res_1",
|
||||
startDate: new Date("2026-05-04T00:00:00.000Z"), // Monday
|
||||
endDate: new Date("2026-05-08T00:00:00.000Z"), // Friday
|
||||
endDate: new Date("2026-05-08T00:00:00.000Z"), // Friday
|
||||
hoursPerDay: 8,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AllocationStatus, PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { AllocationStatus, PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { allocationRouter } from "../router/allocation/index.js";
|
||||
import {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DEFAULT_OPENAI_MODEL } from "@capakraken/shared";
|
||||
import { DEFAULT_OPENAI_MODEL } from "@nexus/shared";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../ai-client.js", () => ({
|
||||
loggedAiCall: vi.fn(async (_provider, _model, _promptLength, fn: () => Promise<unknown>) => fn()),
|
||||
parseAiError: vi.fn((error: unknown) => error instanceof Error ? error.message : String(error)),
|
||||
parseAiError: vi.fn((error: unknown) => (error instanceof Error ? error.message : String(error))),
|
||||
}));
|
||||
|
||||
vi.mock("../lib/audit.js", () => ({
|
||||
@@ -29,12 +29,14 @@ vi.mock("../router/assistant-approvals.js", () => {
|
||||
return {
|
||||
AssistantApprovalStorageUnavailableError,
|
||||
createPendingAssistantApproval: vi.fn(),
|
||||
toApprovalPayload: vi.fn((approval: { id: string; toolName: string; summary: string }, status: string) => ({
|
||||
id: approval.id,
|
||||
toolName: approval.toolName,
|
||||
summary: approval.summary,
|
||||
status,
|
||||
})),
|
||||
toApprovalPayload: vi.fn(
|
||||
(approval: { id: string; toolName: string; summary: string }, status: string) => ({
|
||||
id: approval.id,
|
||||
toolName: approval.toolName,
|
||||
summary: approval.summary,
|
||||
status,
|
||||
}),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -63,14 +65,13 @@ function createClient(...responses: unknown[]) {
|
||||
return {
|
||||
chat: {
|
||||
completions: {
|
||||
create: vi.fn()
|
||||
.mockImplementation(async () => {
|
||||
const next = responses.shift();
|
||||
if (!next) {
|
||||
throw new Error("No mock AI response configured");
|
||||
}
|
||||
return next;
|
||||
}),
|
||||
create: vi.fn().mockImplementation(async () => {
|
||||
const next = responses.shift();
|
||||
if (!next) {
|
||||
throw new Error("No mock AI response configured");
|
||||
}
|
||||
return next;
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -110,7 +111,10 @@ function createLoopInput(overrides: Partial<Parameters<typeof runAssistantToolLo
|
||||
describe("assistant chat loop", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(checkAiOutput).mockImplementation((content: string) => ({ clean: true, redacted: content }));
|
||||
vi.mocked(checkAiOutput).mockImplementation((content: string) => ({
|
||||
clean: true,
|
||||
redacted: content,
|
||||
}));
|
||||
vi.mocked(buildAssistantInsight).mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
@@ -121,21 +125,27 @@ describe("assistant chat loop", () => {
|
||||
summary: "create project (name=Apollo)",
|
||||
} as never);
|
||||
|
||||
const result = await runAssistantToolLoop(createLoopInput({
|
||||
client: createClient({
|
||||
choices: [{
|
||||
message: {
|
||||
tool_calls: [{
|
||||
id: "call_1",
|
||||
function: {
|
||||
name: "create_project",
|
||||
arguments: "{\"name\":\"Apollo\"}",
|
||||
const result = await runAssistantToolLoop(
|
||||
createLoopInput({
|
||||
client: createClient({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call_1",
|
||||
function: {
|
||||
name: "create_project",
|
||||
arguments: '{"name":"Apollo"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}],
|
||||
},
|
||||
}],
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
role: "assistant",
|
||||
@@ -147,15 +157,17 @@ describe("assistant chat loop", () => {
|
||||
},
|
||||
});
|
||||
expect(executeTool).not.toHaveBeenCalled();
|
||||
expect(createAuditEntry).toHaveBeenCalledWith(expect.objectContaining({
|
||||
entityName: "create_project",
|
||||
summary: "AI tool blocked pending confirmation: create_project",
|
||||
}));
|
||||
expect(createAuditEntry).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
entityName: "create_project",
|
||||
summary: "AI tool blocked pending confirmation: create_project",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("continues after read-only tool calls and returns collected actions and insights", async () => {
|
||||
vi.mocked(executeTool).mockResolvedValue({
|
||||
content: "{\"resources\":1}",
|
||||
content: '{"resources":1}',
|
||||
data: { resources: 1 },
|
||||
action: { type: "navigate", href: "/resources" },
|
||||
} as never);
|
||||
@@ -166,44 +178,54 @@ describe("assistant chat loop", () => {
|
||||
metrics: [{ label: "Resolved holidays", value: "1" }],
|
||||
});
|
||||
|
||||
const result = await runAssistantToolLoop(createLoopInput({
|
||||
client: createClient(
|
||||
{
|
||||
choices: [{
|
||||
message: {
|
||||
tool_calls: [{
|
||||
id: "call_1",
|
||||
function: {
|
||||
name: "search_resources",
|
||||
arguments: "{\"query\":\"Alice\"}",
|
||||
const result = await runAssistantToolLoop(
|
||||
createLoopInput({
|
||||
client: createClient(
|
||||
{
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call_1",
|
||||
function: {
|
||||
name: "search_resources",
|
||||
arguments: '{"query":"Alice"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}],
|
||||
},
|
||||
}],
|
||||
},
|
||||
{
|
||||
choices: [{ message: { content: "Hier ist die passende Resource." } }],
|
||||
},
|
||||
),
|
||||
}));
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
choices: [{ message: { content: "Hier ist die passende Resource." } }],
|
||||
},
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
content: "Hier ist die passende Resource.",
|
||||
actions: [{ type: "navigate", href: "/resources" }],
|
||||
insights: [{
|
||||
kind: "holiday_region",
|
||||
title: "Berlin",
|
||||
}],
|
||||
insights: [
|
||||
{
|
||||
kind: "holiday_region",
|
||||
title: "Berlin",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(executeTool).toHaveBeenCalledWith(
|
||||
"search_resources",
|
||||
"{\"query\":\"Alice\"}",
|
||||
'{"query":"Alice"}',
|
||||
expect.objectContaining({ userId: "user_1" }),
|
||||
);
|
||||
expect(createAuditEntry).toHaveBeenCalledWith(expect.objectContaining({
|
||||
entityName: "search_resources",
|
||||
summary: "AI executed tool: search_resources",
|
||||
}));
|
||||
expect(createAuditEntry).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
entityName: "search_resources",
|
||||
summary: "AI executed tool: search_resources",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("redacts unsafe AI output before returning it", async () => {
|
||||
@@ -212,61 +234,77 @@ describe("assistant chat loop", () => {
|
||||
redacted: "[redacted]",
|
||||
});
|
||||
|
||||
const result = await runAssistantToolLoop(createLoopInput({
|
||||
client: createClient({
|
||||
choices: [{ message: { content: "API key is sk-secret" } }],
|
||||
const result = await runAssistantToolLoop(
|
||||
createLoopInput({
|
||||
client: createClient({
|
||||
choices: [{ message: { content: "API key is sk-secret" } }],
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
);
|
||||
|
||||
expect(result.content).toBe("[redacted]");
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
{ userId: "user_1" },
|
||||
"AI output contained sensitive content — redacted before delivery",
|
||||
);
|
||||
expect(createAuditEntry).toHaveBeenCalledWith(expect.objectContaining({
|
||||
entityName: "AiOutputRedacted",
|
||||
}));
|
||||
expect(createAuditEntry).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
entityName: "AiOutputRedacted",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns a stable fallback after too many tool-call iterations", async () => {
|
||||
vi.mocked(executeTool).mockResolvedValue({
|
||||
content: "{\"ok\":true}",
|
||||
content: '{"ok":true}',
|
||||
data: { ok: true },
|
||||
} as never);
|
||||
|
||||
const result = await runAssistantToolLoop(createLoopInput({
|
||||
client: createClient(
|
||||
{
|
||||
choices: [{
|
||||
message: {
|
||||
tool_calls: [{
|
||||
id: "call_1",
|
||||
function: {
|
||||
name: "search_resources",
|
||||
arguments: "{\"query\":\"A\"}",
|
||||
const result = await runAssistantToolLoop(
|
||||
createLoopInput({
|
||||
client: createClient(
|
||||
{
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call_1",
|
||||
function: {
|
||||
name: "search_resources",
|
||||
arguments: '{"query":"A"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}],
|
||||
},
|
||||
}],
|
||||
},
|
||||
{
|
||||
choices: [{
|
||||
message: {
|
||||
tool_calls: [{
|
||||
id: "call_2",
|
||||
function: {
|
||||
name: "search_resources",
|
||||
arguments: "{\"query\":\"B\"}",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call_2",
|
||||
function: {
|
||||
name: "search_resources",
|
||||
arguments: '{"query":"B"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}],
|
||||
},
|
||||
}],
|
||||
},
|
||||
),
|
||||
maxToolIterations: 2,
|
||||
}));
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
maxToolIterations: 2,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.content).toBe("I had to stop after too many tool calls. Please try a simpler question.");
|
||||
expect(result.content).toBe(
|
||||
"I had to stop after too many tool calls. Please try a simpler question.",
|
||||
);
|
||||
});
|
||||
|
||||
it("degrades mutation confirmations when approval storage is unavailable", async () => {
|
||||
@@ -274,21 +312,27 @@ describe("assistant chat loop", () => {
|
||||
new AssistantApprovalStorageUnavailableError("missing table"),
|
||||
);
|
||||
|
||||
const result = await runAssistantToolLoop(createLoopInput({
|
||||
client: createClient({
|
||||
choices: [{
|
||||
message: {
|
||||
tool_calls: [{
|
||||
id: "call_1",
|
||||
function: {
|
||||
name: "create_project",
|
||||
arguments: "{\"name\":\"Apollo\"}",
|
||||
const result = await runAssistantToolLoop(
|
||||
createLoopInput({
|
||||
client: createClient({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call_1",
|
||||
function: {
|
||||
name: "create_project",
|
||||
arguments: '{"name":"Apollo"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}],
|
||||
},
|
||||
}],
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
);
|
||||
|
||||
expect(result.content).toContain("Schreibende Assistant-Aktionen sind gerade nicht verfuegbar");
|
||||
expect(executeTool).not.toHaveBeenCalled();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AssistantApprovalStatus, type Prisma } from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { AssistantApprovalStatus, type Prisma } from "@nexus/db";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { assistantRouter } from "../router/assistant.js";
|
||||
import { createCallerFactory } from "../trpc.js";
|
||||
@@ -37,12 +37,17 @@ describe("assistant router authorization", () => {
|
||||
it("requires authentication for pending approval lists", async () => {
|
||||
const updateMany = vi.fn();
|
||||
const findMany = vi.fn();
|
||||
const caller = createCaller(createContext({
|
||||
assistantApproval: {
|
||||
updateMany,
|
||||
findMany,
|
||||
},
|
||||
}, { session: false }));
|
||||
const caller = createCaller(
|
||||
createContext(
|
||||
{
|
||||
assistantApproval: {
|
||||
updateMany,
|
||||
findMany,
|
||||
},
|
||||
},
|
||||
{ session: false },
|
||||
),
|
||||
);
|
||||
|
||||
await expect(caller.listPendingApprovals()).rejects.toMatchObject({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -63,19 +68,21 @@ describe("assistant router authorization", () => {
|
||||
userId: "user_1",
|
||||
conversationId: "conv_1",
|
||||
toolName: "create_project",
|
||||
toolArguments: "{\"name\":\"Apollo\"}",
|
||||
toolArguments: '{"name":"Apollo"}',
|
||||
summary: "create project Apollo",
|
||||
status: AssistantApprovalStatus.PENDING,
|
||||
createdAt,
|
||||
expiresAt,
|
||||
},
|
||||
] satisfies Array<Prisma.AssistantApprovalGetPayload<Record<string, never>>>);
|
||||
const caller = createCaller(createContext({
|
||||
assistantApproval: {
|
||||
updateMany,
|
||||
findMany,
|
||||
},
|
||||
}));
|
||||
const caller = createCaller(
|
||||
createContext({
|
||||
assistantApproval: {
|
||||
updateMany,
|
||||
findMany,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await caller.listPendingApprovals();
|
||||
|
||||
@@ -112,15 +119,22 @@ describe("assistant router authorization", () => {
|
||||
|
||||
it("requires authentication before starting assistant chat", async () => {
|
||||
const findUnique = vi.fn();
|
||||
const caller = createCaller(createContext({
|
||||
systemSettings: {
|
||||
findUnique,
|
||||
},
|
||||
}, { session: false }));
|
||||
const caller = createCaller(
|
||||
createContext(
|
||||
{
|
||||
systemSettings: {
|
||||
findUnique,
|
||||
},
|
||||
},
|
||||
{ session: false },
|
||||
),
|
||||
);
|
||||
|
||||
await expect(caller.chat({
|
||||
messages: [{ role: "user", content: "Hallo" }],
|
||||
})).rejects.toMatchObject({
|
||||
await expect(
|
||||
caller.chat({
|
||||
messages: [{ role: "user", content: "Hallo" }],
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "Authentication required",
|
||||
});
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@nexus/shared";
|
||||
import { getAvailableAssistantTools } from "../router/assistant-tool-policy.js";
|
||||
|
||||
function getToolNames(
|
||||
permissions: PermissionKeyValue[],
|
||||
userRole: SystemRole = SystemRole.ADMIN,
|
||||
) {
|
||||
return getAvailableAssistantTools(new Set(permissions), userRole).map((tool) => tool.function.name);
|
||||
function getToolNames(permissions: PermissionKeyValue[], userRole: SystemRole = SystemRole.ADMIN) {
|
||||
return getAvailableAssistantTools(new Set(permissions), userRole).map(
|
||||
(tool) => tool.function.name,
|
||||
);
|
||||
}
|
||||
|
||||
describe("assistant tool policy access", () => {
|
||||
it("hides advanced tools unless the dedicated assistant permission is granted", () => {
|
||||
const withoutAdvanced = getToolNames([
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
PermissionKey.VIEW_COSTS,
|
||||
]);
|
||||
const withoutAdvanced = getToolNames([PermissionKey.VIEW_PLANNING, PermissionKey.VIEW_COSTS]);
|
||||
const withAdvanced = getToolNames([
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
PermissionKey.VIEW_COSTS,
|
||||
@@ -119,14 +115,14 @@ describe("assistant tool policy access", () => {
|
||||
});
|
||||
|
||||
it("keeps controller-grade readmodels hidden from plain users while allowing controller roles", () => {
|
||||
const controllerNames = getToolNames([
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
], SystemRole.CONTROLLER);
|
||||
const userNames = getToolNames([
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
], SystemRole.USER);
|
||||
const controllerNames = getToolNames(
|
||||
[PermissionKey.VIEW_COSTS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
|
||||
SystemRole.CONTROLLER,
|
||||
);
|
||||
const userNames = getToolNames(
|
||||
[PermissionKey.VIEW_COSTS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
|
||||
SystemRole.USER,
|
||||
);
|
||||
|
||||
expect(controllerNames).toContain("query_change_history");
|
||||
expect(controllerNames).toContain("get_entity_timeline");
|
||||
@@ -194,15 +190,18 @@ describe("assistant tool policy access", () => {
|
||||
|
||||
it("keeps cost-aware staffing assistant tools behind cost and advanced gates", () => {
|
||||
const planningOnly = getToolNames([PermissionKey.VIEW_PLANNING], SystemRole.USER);
|
||||
const planningAndCosts = getToolNames([
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
PermissionKey.VIEW_COSTS,
|
||||
], SystemRole.USER);
|
||||
const planningCostsAndAdvanced = getToolNames([
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
], SystemRole.USER);
|
||||
const planningAndCosts = getToolNames(
|
||||
[PermissionKey.VIEW_PLANNING, PermissionKey.VIEW_COSTS],
|
||||
SystemRole.USER,
|
||||
);
|
||||
const planningCostsAndAdvanced = getToolNames(
|
||||
[
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
],
|
||||
SystemRole.USER,
|
||||
);
|
||||
|
||||
expect(planningOnly).not.toContain("get_staffing_suggestions");
|
||||
expect(planningOnly).not.toContain("find_best_project_resource");
|
||||
@@ -213,16 +212,22 @@ describe("assistant tool policy access", () => {
|
||||
});
|
||||
|
||||
it("keeps controller-only project and dashboard reads hidden from plain users", () => {
|
||||
const controllerNames = getToolNames([
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
], SystemRole.CONTROLLER);
|
||||
const userNames = getToolNames([
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
], SystemRole.USER);
|
||||
const controllerNames = getToolNames(
|
||||
[
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
],
|
||||
SystemRole.CONTROLLER,
|
||||
);
|
||||
const userNames = getToolNames(
|
||||
[
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
],
|
||||
SystemRole.USER,
|
||||
);
|
||||
|
||||
expect(controllerNames).toContain("search_projects");
|
||||
expect(controllerNames).toContain("get_project");
|
||||
@@ -245,14 +250,14 @@ describe("assistant tool policy access", () => {
|
||||
});
|
||||
|
||||
it("keeps legacy controller-only analysis and report tools hidden from plain users", () => {
|
||||
const controllerNames = getToolNames([
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
], SystemRole.CONTROLLER);
|
||||
const userNames = getToolNames([
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
], SystemRole.USER);
|
||||
const controllerNames = getToolNames(
|
||||
[PermissionKey.VIEW_COSTS, PermissionKey.VIEW_PLANNING],
|
||||
SystemRole.CONTROLLER,
|
||||
);
|
||||
const userNames = getToolNames(
|
||||
[PermissionKey.VIEW_COSTS, PermissionKey.VIEW_PLANNING],
|
||||
SystemRole.USER,
|
||||
);
|
||||
|
||||
expect(controllerNames).toContain("detect_anomalies");
|
||||
expect(controllerNames).toContain("get_insights_summary");
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@nexus/shared";
|
||||
import { getAvailableAssistantTools } from "../router/assistant-tool-policy.js";
|
||||
import { TOOL_DEFINITIONS } from "../router/assistant-tools.js";
|
||||
|
||||
function getToolNames(
|
||||
permissions: PermissionKeyValue[],
|
||||
userRole: SystemRole = SystemRole.ADMIN,
|
||||
) {
|
||||
return getAvailableAssistantTools(new Set(permissions), userRole).map((tool) => tool.function.name);
|
||||
function getToolNames(permissions: PermissionKeyValue[], userRole: SystemRole = SystemRole.ADMIN) {
|
||||
return getAvailableAssistantTools(new Set(permissions), userRole).map(
|
||||
(tool) => tool.function.name,
|
||||
);
|
||||
}
|
||||
|
||||
describe("assistant tool policy admin parity", () => {
|
||||
@@ -128,8 +127,14 @@ describe("assistant tool policy admin parity", () => {
|
||||
const adminNames = getToolNames([], SystemRole.ADMIN);
|
||||
const managerNames = getToolNames([], SystemRole.MANAGER);
|
||||
const userNames = getToolNames([], SystemRole.USER);
|
||||
const userWithResourceOverview = getToolNames([PermissionKey.VIEW_ALL_RESOURCES], SystemRole.USER);
|
||||
const userWithManagedResources = getToolNames([PermissionKey.MANAGE_RESOURCES], SystemRole.USER);
|
||||
const userWithResourceOverview = getToolNames(
|
||||
[PermissionKey.VIEW_ALL_RESOURCES],
|
||||
SystemRole.USER,
|
||||
);
|
||||
const userWithManagedResources = getToolNames(
|
||||
[PermissionKey.MANAGE_RESOURCES],
|
||||
SystemRole.USER,
|
||||
);
|
||||
|
||||
expect(adminNames).toContain("list_countries");
|
||||
expect(adminNames).toContain("create_country");
|
||||
@@ -155,9 +160,7 @@ describe("assistant tool policy admin parity", () => {
|
||||
});
|
||||
|
||||
it("attaches explicit access metadata to legacy monolithic tools with restricted visibility", () => {
|
||||
const toolAccess = new Map(
|
||||
TOOL_DEFINITIONS.map((tool) => [tool.function.name, tool.access]),
|
||||
);
|
||||
const toolAccess = new Map(TOOL_DEFINITIONS.map((tool) => [tool.function.name, tool.access]));
|
||||
|
||||
expect(toolAccess.get("run_report")).toEqual({
|
||||
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||
@@ -210,10 +213,7 @@ describe("assistant tool policy admin parity", () => {
|
||||
expect(managerWithoutRolePermission).toContain("create_client");
|
||||
expect(managerWithoutRolePermission).toContain("update_client");
|
||||
|
||||
const adminWithRolePermission = getToolNames(
|
||||
[PermissionKey.MANAGE_ROLES],
|
||||
SystemRole.ADMIN,
|
||||
);
|
||||
const adminWithRolePermission = getToolNames([PermissionKey.MANAGE_ROLES], SystemRole.ADMIN);
|
||||
expect(adminWithRolePermission).toContain("create_org_unit");
|
||||
expect(adminWithRolePermission).toContain("update_org_unit");
|
||||
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@nexus/shared";
|
||||
import { getAvailableAssistantTools } from "../router/assistant-tool-policy.js";
|
||||
|
||||
function getToolNames(
|
||||
permissions: PermissionKeyValue[],
|
||||
userRole: SystemRole = SystemRole.ADMIN,
|
||||
) {
|
||||
return getAvailableAssistantTools(new Set(permissions), userRole).map((tool) => tool.function.name);
|
||||
function getToolNames(permissions: PermissionKeyValue[], userRole: SystemRole = SystemRole.ADMIN) {
|
||||
return getAvailableAssistantTools(new Set(permissions), userRole).map(
|
||||
(tool) => tool.function.name,
|
||||
);
|
||||
}
|
||||
|
||||
describe("assistant tool policy planning flows", () => {
|
||||
it("requires both controller role and advanced assistant access for timeline detail tools", () => {
|
||||
const controllerWithAdvanced = getToolNames([
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
], SystemRole.CONTROLLER);
|
||||
const controllerWithAdvanced = getToolNames(
|
||||
[PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
|
||||
SystemRole.CONTROLLER,
|
||||
);
|
||||
const controllerWithoutAdvanced = getToolNames([], SystemRole.CONTROLLER);
|
||||
const userWithAdvanced = getToolNames([
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
], SystemRole.USER);
|
||||
const userWithAdvanced = getToolNames(
|
||||
[PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
|
||||
SystemRole.USER,
|
||||
);
|
||||
|
||||
expect(controllerWithAdvanced).toContain("get_timeline_entries_view");
|
||||
expect(controllerWithAdvanced).toContain("get_timeline_holiday_overlays");
|
||||
@@ -47,17 +48,18 @@ describe("assistant tool policy planning flows", () => {
|
||||
});
|
||||
|
||||
it("keeps timeline write parity tools behind manager/admin role, manageAllocations, and advanced assistant access", () => {
|
||||
const managerNames = getToolNames([
|
||||
PermissionKey.MANAGE_ALLOCATIONS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
], SystemRole.MANAGER);
|
||||
const userNames = getToolNames([
|
||||
PermissionKey.MANAGE_ALLOCATIONS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
], SystemRole.USER);
|
||||
const missingAdvancedNames = getToolNames([
|
||||
PermissionKey.MANAGE_ALLOCATIONS,
|
||||
], SystemRole.MANAGER);
|
||||
const managerNames = getToolNames(
|
||||
[PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
|
||||
SystemRole.MANAGER,
|
||||
);
|
||||
const userNames = getToolNames(
|
||||
[PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
|
||||
SystemRole.USER,
|
||||
);
|
||||
const missingAdvancedNames = getToolNames(
|
||||
[PermissionKey.MANAGE_ALLOCATIONS],
|
||||
SystemRole.MANAGER,
|
||||
);
|
||||
|
||||
expect(managerNames).toContain("update_timeline_allocation_inline");
|
||||
expect(managerNames).toContain("apply_timeline_project_shift");
|
||||
@@ -75,7 +77,10 @@ describe("assistant tool policy planning flows", () => {
|
||||
|
||||
it("keeps estimate lifecycle mutations behind manager/admin role and their router permissions", () => {
|
||||
const managerProjectNames = getToolNames([PermissionKey.MANAGE_PROJECTS], SystemRole.MANAGER);
|
||||
const managerAllocationNames = getToolNames([PermissionKey.MANAGE_ALLOCATIONS], SystemRole.MANAGER);
|
||||
const managerAllocationNames = getToolNames(
|
||||
[PermissionKey.MANAGE_ALLOCATIONS],
|
||||
SystemRole.MANAGER,
|
||||
);
|
||||
const userProjectNames = getToolNames([PermissionKey.MANAGE_PROJECTS], SystemRole.USER);
|
||||
|
||||
expect(managerProjectNames).toContain("create_estimate");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@nexus/shared";
|
||||
import { getAvailableAssistantTools } from "../router/assistant-tool-policy.js";
|
||||
import { selectAssistantToolsForRequest } from "../router/assistant-tool-selection.js";
|
||||
|
||||
@@ -35,7 +35,12 @@ describe("assistant tool selection", () => {
|
||||
const allPermissions = Object.values(PermissionKey);
|
||||
const selectedNames = getSelectedToolNames(
|
||||
allPermissions,
|
||||
[{ role: "user", content: "Kannst du mir alle Feiertage nennen, die Peter Parker in 2026 zustehen?" }],
|
||||
[
|
||||
{
|
||||
role: "user",
|
||||
content: "Kannst du mir alle Feiertage nennen, die Peter Parker in 2026 zustehen?",
|
||||
},
|
||||
],
|
||||
SystemRole.ADMIN,
|
||||
);
|
||||
|
||||
@@ -54,7 +59,13 @@ describe("assistant tool selection", () => {
|
||||
const allPermissions = Object.values(PermissionKey);
|
||||
const selectedNames = getSelectedToolNames(
|
||||
allPermissions,
|
||||
[{ role: "user", content: "Build me a dashboard report for monthly SAH, budget forecast and project health." }],
|
||||
[
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"Build me a dashboard report for monthly SAH, budget forecast and project health.",
|
||||
},
|
||||
],
|
||||
SystemRole.ADMIN,
|
||||
"/dashboard",
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
import type { ToolContext } from "../router/assistant-tools.js";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -35,41 +35,48 @@ describe("assistant advanced project shift preview tool", () => {
|
||||
});
|
||||
|
||||
it("returns project shift preview details from the canonical timeline router", async () => {
|
||||
const projectFindUnique = vi.fn().mockImplementation((args: { where?: { id?: string; shortCode?: string }; select?: Record<string, unknown> }) => {
|
||||
if (args.where?.id === "GDM") {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (args.where?.shortCode === "GDM") {
|
||||
return Promise.resolve({
|
||||
id: "project_shift",
|
||||
name: "Gelddruckmaschine",
|
||||
shortCode: "GDM",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Larissa",
|
||||
startDate: new Date("2026-01-05T00:00:00.000Z"),
|
||||
endDate: new Date("2026-01-16T00:00:00.000Z"),
|
||||
});
|
||||
}
|
||||
if (args.select && "budgetCents" in args.select) {
|
||||
return Promise.resolve({
|
||||
id: "project_shift",
|
||||
budgetCents: 100000,
|
||||
winProbability: 100,
|
||||
startDate: new Date("2026-01-05T00:00:00.000Z"),
|
||||
endDate: new Date("2026-01-16T00:00:00.000Z"),
|
||||
});
|
||||
}
|
||||
const projectFindUnique = vi
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(args: {
|
||||
where?: { id?: string; shortCode?: string };
|
||||
select?: Record<string, unknown>;
|
||||
}) => {
|
||||
if (args.where?.id === "GDM") {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (args.where?.shortCode === "GDM") {
|
||||
return Promise.resolve({
|
||||
id: "project_shift",
|
||||
name: "Gelddruckmaschine",
|
||||
shortCode: "GDM",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Larissa",
|
||||
startDate: new Date("2026-01-05T00:00:00.000Z"),
|
||||
endDate: new Date("2026-01-16T00:00:00.000Z"),
|
||||
});
|
||||
}
|
||||
if (args.select && "budgetCents" in args.select) {
|
||||
return Promise.resolve({
|
||||
id: "project_shift",
|
||||
budgetCents: 100000,
|
||||
winProbability: 100,
|
||||
startDate: new Date("2026-01-05T00:00:00.000Z"),
|
||||
endDate: new Date("2026-01-16T00:00:00.000Z"),
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
id: "project_shift",
|
||||
name: "Gelddruckmaschine",
|
||||
shortCode: "GDM",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Larissa",
|
||||
startDate: new Date("2026-01-05T00:00:00.000Z"),
|
||||
endDate: new Date("2026-01-16T00:00:00.000Z"),
|
||||
});
|
||||
});
|
||||
return Promise.resolve({
|
||||
id: "project_shift",
|
||||
name: "Gelddruckmaschine",
|
||||
shortCode: "GDM",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Larissa",
|
||||
startDate: new Date("2026-01-05T00:00:00.000Z"),
|
||||
endDate: new Date("2026-01-16T00:00:00.000Z"),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
|
||||
+21
-10
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -26,7 +26,7 @@ vi.mock("../lib/cache.js", () => ({
|
||||
invalidateDashboardCache: vi.fn(),
|
||||
}));
|
||||
|
||||
import { listAssignmentBookings } from "@capakraken/application";
|
||||
import { listAssignmentBookings } from "@nexus/application";
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-advanced-timeline-test-helpers.js";
|
||||
|
||||
@@ -60,7 +60,14 @@ describe("assistant advanced project timeline context tool", () => {
|
||||
hoursPerDay: 6,
|
||||
dailyCostCents: 0,
|
||||
status: "CONFIRMED",
|
||||
project: { id: "project_ctx", name: "Gelddruckmaschine", shortCode: "GDM", status: "ACTIVE", orderType: "CHARGEABLE", dynamicFields: null },
|
||||
project: {
|
||||
id: "project_ctx",
|
||||
name: "Gelddruckmaschine",
|
||||
shortCode: "GDM",
|
||||
status: "ACTIVE",
|
||||
orderType: "CHARGEABLE",
|
||||
dynamicFields: null,
|
||||
},
|
||||
resource: { id: "res_1", displayName: "Alice", chapter: "Delivery" },
|
||||
},
|
||||
{
|
||||
@@ -72,7 +79,14 @@ describe("assistant advanced project timeline context tool", () => {
|
||||
hoursPerDay: 4,
|
||||
dailyCostCents: 0,
|
||||
status: "CONFIRMED",
|
||||
project: { id: "project_other", name: "Other Project", shortCode: "OTH", status: "ACTIVE", orderType: "CHARGEABLE", dynamicFields: null },
|
||||
project: {
|
||||
id: "project_other",
|
||||
name: "Other Project",
|
||||
shortCode: "OTH",
|
||||
status: "ACTIVE",
|
||||
orderType: "CHARGEABLE",
|
||||
dynamicFields: null,
|
||||
},
|
||||
resource: { id: "res_1", displayName: "Alice", chapter: "Delivery" },
|
||||
},
|
||||
]);
|
||||
@@ -80,10 +94,7 @@ describe("assistant advanced project timeline context tool", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
project: {
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(project)
|
||||
.mockResolvedValueOnce(project),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(project).mockResolvedValueOnce(project),
|
||||
},
|
||||
demandRequirement: {
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -37,14 +37,11 @@ describe("assistant advanced timeline resource ranking tools", () => {
|
||||
|
||||
it("finds the best project resource with holiday-aware remaining capacity and LCR ranking", async () => {
|
||||
const { db } = createResourceRankingTestDb();
|
||||
const ctx = createToolContext(
|
||||
db,
|
||||
[
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
],
|
||||
);
|
||||
const ctx = createToolContext(db, [
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
PermissionKey.VIEW_COSTS,
|
||||
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
]);
|
||||
|
||||
const result = await executeTool(
|
||||
"find_best_project_resource",
|
||||
@@ -114,10 +111,7 @@ describe("assistant advanced timeline resource ranking tools", () => {
|
||||
});
|
||||
|
||||
it("requires the dedicated advanced assistant permission for the high-level resource tool", async () => {
|
||||
const ctx = createToolContext({}, [
|
||||
PermissionKey.VIEW_PLANNING,
|
||||
PermissionKey.VIEW_COSTS,
|
||||
]);
|
||||
const ctx = createToolContext({}, [PermissionKey.VIEW_PLANNING, PermissionKey.VIEW_COSTS]);
|
||||
|
||||
const result = await executeTool(
|
||||
"find_best_project_resource",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -186,7 +186,12 @@ describe("assistant advanced timeline entries view tool", () => {
|
||||
};
|
||||
demands: Array<{ id: string }>;
|
||||
assignments: Array<{ id: string }>;
|
||||
holidayOverlays: Array<{ resourceId: string; startDate: string; note: string; scope: string }>;
|
||||
holidayOverlays: Array<{
|
||||
resourceId: string;
|
||||
startDate: string;
|
||||
note: string;
|
||||
scope: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
expect(parsed.summary).toEqual(
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
import type { ToolContext } from "../router/assistant-tools.js";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import {
|
||||
@@ -17,9 +17,11 @@ describe("assistant allocation cancel error tools", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
assignment: {
|
||||
findUnique: vi.fn().mockRejectedValue(
|
||||
new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" }),
|
||||
),
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockRejectedValue(
|
||||
new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" }),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
import {
|
||||
createAssignment,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -63,16 +63,13 @@ describe("assistant allocation create tools", () => {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
project: {
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
assignment: {
|
||||
@@ -109,7 +106,8 @@ describe("assistant allocation create tools", () => {
|
||||
);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual({
|
||||
error: "Allocation already exists for this resource/project/dates. No new allocation created.",
|
||||
error:
|
||||
"Allocation already exists for this resource/project/dates. No new allocation created.",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,16 +131,13 @@ describe("assistant allocation create tools", () => {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
project: {
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
import type { ToolContext } from "../router/assistant-tools.js";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import {
|
||||
@@ -17,9 +17,11 @@ describe("assistant allocation status tools", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
assignment: {
|
||||
findUnique: vi.fn().mockRejectedValue(
|
||||
new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" }),
|
||||
),
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockRejectedValue(
|
||||
new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" }),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
import {
|
||||
createAssignment,
|
||||
@@ -46,11 +46,13 @@ describe("assistant allocation status tools", () => {
|
||||
ctx,
|
||||
);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual(expect.objectContaining({
|
||||
success: true,
|
||||
message:
|
||||
"Updated allocation status: Carol Danvers → Project One (PROJ-1), 2026-06-01 to 2026-06-05: PROPOSED → ACTIVE",
|
||||
}));
|
||||
expect(JSON.parse(result.content)).toEqual(
|
||||
expect.objectContaining({
|
||||
success: true,
|
||||
message:
|
||||
"Updated allocation status: Carol Danvers → Project One (PROJ-1), 2026-06-01 to 2026-06-05: PROPOSED → ACTIVE",
|
||||
}),
|
||||
);
|
||||
expect(ctx.db.assignment.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: "assignment_1" },
|
||||
include: expect.objectContaining({
|
||||
@@ -58,12 +60,14 @@ describe("assistant allocation status tools", () => {
|
||||
project: expect.any(Object),
|
||||
}),
|
||||
});
|
||||
expect(assignmentUpdate).toHaveBeenCalledWith(expect.objectContaining({
|
||||
where: { id: "assignment_1" },
|
||||
data: expect.objectContaining({
|
||||
status: "ACTIVE",
|
||||
expect(assignmentUpdate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { id: "assignment_1" },
|
||||
data: expect.objectContaining({
|
||||
status: "ACTIVE",
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
);
|
||||
expect(auditCreate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -24,9 +24,11 @@ describe("assistant audit error and access guards", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
auditLog: {
|
||||
findUniqueOrThrow: vi.fn().mockRejectedValue(
|
||||
new TRPCError({ code: "NOT_FOUND", message: "Audit log entry not found" }),
|
||||
),
|
||||
findUniqueOrThrow: vi
|
||||
.fn()
|
||||
.mockRejectedValue(
|
||||
new TRPCError({ code: "NOT_FOUND", message: "Audit log entry not found" }),
|
||||
),
|
||||
},
|
||||
},
|
||||
{ userRole: SystemRole.ADMIN },
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -70,11 +70,7 @@ describe("assistant audit read tools", () => {
|
||||
{ userRole: SystemRole.CONTROLLER },
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"get_audit_log_timeline",
|
||||
JSON.stringify({ limit: 10 }),
|
||||
ctx,
|
||||
);
|
||||
const result = await executeTool("get_audit_log_timeline", JSON.stringify({ limit: 10 }), ctx);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual({
|
||||
"2026-03-29": [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
import type { ToolContext } from "../router/assistant-tools.js";
|
||||
|
||||
export function createToolContext(
|
||||
@@ -9,14 +9,14 @@ export function createToolContext(
|
||||
): ToolContext {
|
||||
const userRole = options?.userRole ?? SystemRole.ADMIN;
|
||||
let effectiveDb: Record<string, unknown>;
|
||||
effectiveDb = "$transaction" in db
|
||||
? db
|
||||
: {
|
||||
...db,
|
||||
$transaction: async (
|
||||
callback: (tx: ToolContext["db"]) => Promise<unknown>,
|
||||
) => callback(effectiveDb as ToolContext["db"]),
|
||||
};
|
||||
effectiveDb =
|
||||
"$transaction" in db
|
||||
? db
|
||||
: {
|
||||
...db,
|
||||
$transaction: async (callback: (tx: ToolContext["db"]) => Promise<unknown>) =>
|
||||
callback(effectiveDb as ToolContext["db"]),
|
||||
};
|
||||
|
||||
return {
|
||||
db: effectiveDb as ToolContext["db"],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -45,11 +45,7 @@ describe("assistant broadcast list tools", () => {
|
||||
);
|
||||
const ctx = createToolContext(db, SystemRole.MANAGER);
|
||||
|
||||
const result = await executeTool(
|
||||
"list_broadcasts",
|
||||
JSON.stringify({ limit: 10 }),
|
||||
ctx,
|
||||
);
|
||||
const result = await executeTool("list_broadcasts", JSON.stringify({ limit: 10 }), ctx);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual([
|
||||
{
|
||||
@@ -90,11 +86,7 @@ describe("assistant broadcast list tools", () => {
|
||||
SystemRole.USER,
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"list_broadcasts",
|
||||
JSON.stringify({ limit: 5 }),
|
||||
ctx,
|
||||
);
|
||||
const result = await executeTool("list_broadcasts", JSON.stringify({ limit: 5 }), ctx);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual(
|
||||
expect.objectContaining({
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { executeTool } from "./assistant-tools-broadcast-send-test-helpers.js";
|
||||
import { createToolContext } from "./assistant-tools-notification-test-helpers.js";
|
||||
@@ -70,7 +70,8 @@ describe("assistant broadcast send recipient fan-out errors", () => {
|
||||
findMany: vi.fn().mockResolvedValue([{ id: "user_2" }]),
|
||||
},
|
||||
$transaction: vi.fn(async (callback: (db: typeof recipientMissingTx) => Promise<unknown>) =>
|
||||
callback(recipientMissingTx)),
|
||||
callback(recipientMissingTx),
|
||||
),
|
||||
notificationBroadcast: {
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { executeTool } from "./assistant-tools-broadcast-send-test-helpers.js";
|
||||
import { createToolContext } from "./assistant-tools-notification-test-helpers.js";
|
||||
@@ -66,7 +66,8 @@ describe("assistant broadcast send sender fan-out errors", () => {
|
||||
findMany: vi.fn().mockResolvedValue([{ id: "user_2" }]),
|
||||
},
|
||||
$transaction: vi.fn(async (callback: (db: typeof senderMissingTx) => Promise<unknown>) =>
|
||||
callback(senderMissingTx)),
|
||||
callback(senderMissingTx),
|
||||
),
|
||||
notificationBroadcast: {
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
|
||||
+4
-2
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { executeTool } from "./assistant-tools-broadcast-send-test-helpers.js";
|
||||
import { createToolContext } from "./assistant-tools-notification-test-helpers.js";
|
||||
@@ -15,7 +15,9 @@ describe("assistant broadcast send finalization errors", () => {
|
||||
title: "Office update",
|
||||
targetType: "all",
|
||||
});
|
||||
const txCreateNotification = vi.fn().mockResolvedValue({ id: "notification_2", userId: "user_2" });
|
||||
const txCreateNotification = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ id: "notification_2", userId: "user_2" });
|
||||
const txUpdateBroadcast = vi.fn().mockRejectedValue(
|
||||
Object.assign(new Error("Record to update not found"), {
|
||||
code: "P2025",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { executeTool } from "./assistant-tools-broadcast-send-test-helpers.js";
|
||||
import { createToolContext } from "./assistant-tools-notification-test-helpers.js";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { listAssignmentBookings } from "@capakraken/application";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { listAssignmentBookings } from "@nexus/application";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { listAssignmentBookings } from "@capakraken/application";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
import { listAssignmentBookings } from "@nexus/application";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -94,9 +94,9 @@ describe("assistant chargeability report tools", () => {
|
||||
]),
|
||||
},
|
||||
project: {
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
{ id: "project_confirmed", utilizationCategory: { code: "Chg" } },
|
||||
]),
|
||||
findMany: vi
|
||||
.fn()
|
||||
.mockResolvedValue([{ id: "project_confirmed", utilizationCategory: { code: "Chg" } }]),
|
||||
},
|
||||
vacation: {
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }),
|
||||
@@ -12,7 +12,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { countPlanningEntries } from "@capakraken/application";
|
||||
import { countPlanningEntries } from "@nexus/application";
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-master-data-mutation-test-helpers.js";
|
||||
|
||||
@@ -59,17 +59,16 @@ describe("assistant client delete tool - errors", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
client: {
|
||||
findUnique: vi.fn().mockResolvedValueOnce(clientRecord).mockResolvedValueOnce(clientRecord),
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(clientRecord)
|
||||
.mockResolvedValueOnce(clientRecord),
|
||||
},
|
||||
},
|
||||
{ userRole: SystemRole.ADMIN },
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"delete_client",
|
||||
JSON.stringify({ id: "client_1" }),
|
||||
ctx,
|
||||
);
|
||||
const result = await executeTool("delete_client", JSON.stringify({ id: "client_1" }), ctx);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -92,17 +91,16 @@ describe("assistant client delete tool - errors", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
client: {
|
||||
findUnique: vi.fn().mockResolvedValueOnce(clientRecord).mockResolvedValueOnce(clientRecord),
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(clientRecord)
|
||||
.mockResolvedValueOnce(clientRecord),
|
||||
},
|
||||
},
|
||||
{ userRole: SystemRole.ADMIN },
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"delete_client",
|
||||
JSON.stringify({ id: "client_1" }),
|
||||
ctx,
|
||||
);
|
||||
const result = await executeTool("delete_client", JSON.stringify({ id: "client_1" }), ctx);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }),
|
||||
@@ -12,7 +12,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { countPlanningEntries } from "@capakraken/application";
|
||||
import { countPlanningEntries } from "@nexus/application";
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-master-data-mutation-test-helpers.js";
|
||||
|
||||
|
||||
+5
-9
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }),
|
||||
@@ -12,7 +12,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { countPlanningEntries } from "@capakraken/application";
|
||||
import { countPlanningEntries } from "@nexus/application";
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-master-data-mutation-test-helpers.js";
|
||||
|
||||
@@ -26,11 +26,7 @@ describe("assistant client create and update tools - errors", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
client: {
|
||||
findUnique: vi.fn(async ({
|
||||
where,
|
||||
}: {
|
||||
where: { id?: string; code?: string };
|
||||
}) => {
|
||||
findUnique: vi.fn(async ({ where }: { where: { id?: string; code?: string } }) => {
|
||||
if (where.code === "ACM") {
|
||||
return { id: "client_existing", code: "ACM", name: "Existing Client" };
|
||||
}
|
||||
|
||||
+22
-20
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }),
|
||||
@@ -12,7 +12,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { countPlanningEntries } from "@capakraken/application";
|
||||
import { countPlanningEntries } from "@nexus/application";
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-master-data-mutation-test-helpers.js";
|
||||
|
||||
@@ -25,23 +25,25 @@ describe("assistant client create and update tools - success", () => {
|
||||
it("routes create and update client mutations through their backing router", async () => {
|
||||
const db = {
|
||||
client: {
|
||||
findUnique: vi.fn().mockImplementation(async (args?: { where?: { id?: string; code?: string } }) => {
|
||||
if (args?.where?.id === "client_1") {
|
||||
return {
|
||||
id: "client_1",
|
||||
name: "Acme",
|
||||
code: "ACME",
|
||||
parentId: null,
|
||||
isActive: true,
|
||||
sortOrder: 0,
|
||||
tags: [],
|
||||
};
|
||||
}
|
||||
if (args?.where?.code === "ACME-NEW") {
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockImplementation(async (args?: { where?: { id?: string; code?: string } }) => {
|
||||
if (args?.where?.id === "client_1") {
|
||||
return {
|
||||
id: "client_1",
|
||||
name: "Acme",
|
||||
code: "ACME",
|
||||
parentId: null,
|
||||
isActive: true,
|
||||
sortOrder: 0,
|
||||
tags: [],
|
||||
};
|
||||
}
|
||||
if (args?.where?.code === "ACME-NEW") {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
}),
|
||||
create: vi.fn().mockResolvedValue({
|
||||
id: "client_1",
|
||||
name: "Acme",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { createToolContext, executeTool } from "./assistant-tools-comments-test-helpers.js";
|
||||
|
||||
@@ -110,7 +110,12 @@ describe("assistant comment tools create errors", () => {
|
||||
body: "Hello @[Peter Parker](user_missing)",
|
||||
resolved: false,
|
||||
createdAt: new Date("2026-03-29T11:00:00.000Z"),
|
||||
author: { id: "user_1", name: "Assistant User", email: "assistant@example.com", image: null },
|
||||
author: {
|
||||
id: "user_1",
|
||||
name: "Assistant User",
|
||||
email: "assistant@example.com",
|
||||
image: null,
|
||||
},
|
||||
}),
|
||||
},
|
||||
notification: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { createToolContext, executeTool } from "./assistant-tools-comments-test-helpers.js";
|
||||
|
||||
@@ -26,14 +26,24 @@ describe("assistant comment mutation tools", () => {
|
||||
body: "Please review this estimate.",
|
||||
resolved: false,
|
||||
createdAt: new Date("2026-03-29T11:00:00.000Z"),
|
||||
author: { id: "user_1", name: "Assistant User", email: "assistant@example.com", image: null },
|
||||
author: {
|
||||
id: "user_1",
|
||||
name: "Assistant User",
|
||||
email: "assistant@example.com",
|
||||
image: null,
|
||||
},
|
||||
}),
|
||||
update: vi.fn().mockResolvedValue({
|
||||
id: "comment_1",
|
||||
body: "Initial note",
|
||||
resolved: true,
|
||||
createdAt: new Date("2026-03-29T09:00:00.000Z"),
|
||||
author: { id: "user_1", name: "Assistant User", email: "assistant@example.com", image: null },
|
||||
author: {
|
||||
id: "user_1",
|
||||
name: "Assistant User",
|
||||
email: "assistant@example.com",
|
||||
image: null,
|
||||
},
|
||||
}),
|
||||
},
|
||||
notification: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { createToolContext, executeTool } from "./assistant-tools-comments-test-helpers.js";
|
||||
|
||||
@@ -20,14 +20,24 @@ describe("assistant comment list tool", () => {
|
||||
body: "Initial note",
|
||||
resolved: false,
|
||||
createdAt: new Date("2026-03-29T09:00:00.000Z"),
|
||||
author: { id: "user_1", name: "Assistant User", email: "assistant@example.com", image: null },
|
||||
author: {
|
||||
id: "user_1",
|
||||
name: "Assistant User",
|
||||
email: "assistant@example.com",
|
||||
image: null,
|
||||
},
|
||||
replies: [
|
||||
{
|
||||
id: "comment_reply_1",
|
||||
body: "Reply",
|
||||
resolved: false,
|
||||
createdAt: new Date("2026-03-29T10:00:00.000Z"),
|
||||
author: { id: "user_2", name: "Reviewer", email: "reviewer@example.com", image: null },
|
||||
author: {
|
||||
id: "user_2",
|
||||
name: "Reviewer",
|
||||
email: "reviewer@example.com",
|
||||
image: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { createToolContext, executeTool } from "./assistant-tools-comments-test-helpers.js";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -34,7 +34,10 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { executeTool as executeAssistantTool, type ToolContext } from "../router/assistant-tools.js";
|
||||
import {
|
||||
executeTool as executeAssistantTool,
|
||||
type ToolContext,
|
||||
} from "../router/assistant-tools.js";
|
||||
|
||||
export const executeTool = executeAssistantTool;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-country-test-helpers.js";
|
||||
@@ -46,11 +46,7 @@ describe("assistant country get tools", () => {
|
||||
};
|
||||
const ctx = createToolContext(db, [PermissionKey.VIEW_ALL_RESOURCES]);
|
||||
|
||||
const result = await executeTool(
|
||||
"get_country",
|
||||
JSON.stringify({ identifier: "ES" }),
|
||||
ctx,
|
||||
);
|
||||
const result = await executeTool("get_country", JSON.stringify({ identifier: "ES" }), ctx);
|
||||
|
||||
const parsed = JSON.parse(result.content) as {
|
||||
code: string;
|
||||
@@ -121,12 +117,15 @@ describe("assistant country get tools", () => {
|
||||
});
|
||||
|
||||
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 ctx = createToolContext(
|
||||
{
|
||||
country: {
|
||||
findUnique: vi.fn().mockResolvedValue(null),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
},
|
||||
}, [PermissionKey.VIEW_ALL_RESOURCES]);
|
||||
[PermissionKey.VIEW_ALL_RESOURCES],
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"get_country",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-country-test-helpers.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import type { ToolContext } from "../router/assistant-tools.js";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import {
|
||||
createToolContext,
|
||||
@@ -235,7 +235,11 @@ describe("assistant dashboard tools detail aggregation", () => {
|
||||
{ userRole: SystemRole.CONTROLLER },
|
||||
);
|
||||
|
||||
const result = await executeTool("get_dashboard_detail", JSON.stringify({ section: "all" }), ctx);
|
||||
const result = await executeTool(
|
||||
"get_dashboard_detail",
|
||||
JSON.stringify({ section: "all" }),
|
||||
ctx,
|
||||
);
|
||||
|
||||
expect(getDashboardOverview).toHaveBeenCalledTimes(1);
|
||||
expect(getDashboardPeakTimes).toHaveBeenCalledWith(
|
||||
@@ -420,9 +424,7 @@ describe("assistant dashboard tools detail aggregation", () => {
|
||||
|
||||
it("routes the isolated skill gap detail section without unrelated dashboard reads", async () => {
|
||||
vi.mocked(getDashboardSkillGapSummary).mockResolvedValue({
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 },
|
||||
],
|
||||
roleGaps: [{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 }],
|
||||
totalOpenPositions: 3,
|
||||
skillSupplyTop10: [{ skill: "houdini", resourceCount: 5 }],
|
||||
resourcesByRole: [{ role: "Pipeline TD", count: 2 }],
|
||||
@@ -446,9 +448,7 @@ describe("assistant dashboard tools detail aggregation", () => {
|
||||
expect(JSON.parse(result.content)).toEqual({
|
||||
skillGaps: {
|
||||
totalOpenPositions: 3,
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", gap: 3, needed: 4, filled: 1, fillRate: 25 },
|
||||
],
|
||||
roleGaps: [{ role: "Pipeline TD", gap: 3, needed: 4, filled: 1, fillRate: 25 }],
|
||||
topSkillsInSupply: [{ skill: "houdini", resourceCount: 5 }],
|
||||
resourcesByRole: [{ role: "Pipeline TD", count: 2 }],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import {
|
||||
createToolContext,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import {
|
||||
createToolContext,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import {
|
||||
createToolContext,
|
||||
@@ -14,9 +14,7 @@ describe("assistant dashboard tools skill gaps", () => {
|
||||
|
||||
it("routes skill gap reads through the dashboard router path", async () => {
|
||||
vi.mocked(getDashboardSkillGapSummary).mockResolvedValue({
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 },
|
||||
],
|
||||
roleGaps: [{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 }],
|
||||
totalOpenPositions: 3,
|
||||
skillSupplyTop10: [{ skill: "houdini", resourceCount: 5 }],
|
||||
resourcesByRole: [{ role: "Pipeline TD", count: 2 }],
|
||||
@@ -27,9 +25,7 @@ describe("assistant dashboard tools skill gaps", () => {
|
||||
|
||||
expect(getDashboardSkillGapSummary).toHaveBeenCalledTimes(1);
|
||||
expect(JSON.parse(result.content)).toEqual({
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 },
|
||||
],
|
||||
roleGaps: [{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 }],
|
||||
totalOpenPositions: 3,
|
||||
skillSupplyTop10: [{ skill: "houdini", resourceCount: 5 }],
|
||||
resourcesByRole: [{ role: "Pipeline TD", count: 2 }],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardDemand: vi.fn().mockResolvedValue([]),
|
||||
@@ -42,8 +42,11 @@ import {
|
||||
getDashboardPeakTimes as getDashboardPeakTimesMock,
|
||||
getDashboardSkillGapSummary as getDashboardSkillGapSummaryMock,
|
||||
getDashboardTopValueResources as getDashboardTopValueResourcesMock,
|
||||
} from "@capakraken/application";
|
||||
import { executeTool as executeAssistantTool, type ToolContext } from "../router/assistant-tools.js";
|
||||
} from "@nexus/application";
|
||||
import {
|
||||
executeTool as executeAssistantTool,
|
||||
type ToolContext,
|
||||
} from "../router/assistant-tools.js";
|
||||
|
||||
export const executeTool = executeAssistantTool;
|
||||
export const getDashboardChargeabilityOverview = getDashboardChargeabilityOverviewMock;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import {
|
||||
createToolContext,
|
||||
executeTool,
|
||||
} from "./assistant-tools-demand-create-test-helpers.js";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { createToolContext, executeTool } from "./assistant-tools-demand-create-test-helpers.js";
|
||||
|
||||
describe("assistant demand create tool - race errors", () => {
|
||||
beforeEach(() => {
|
||||
@@ -25,24 +22,24 @@ describe("assistant demand create tool - race errors", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
project: {
|
||||
findUnique: vi.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
role: {
|
||||
findUnique: vi.fn().mockResolvedValue({ id: "role_1", name: "Designer" }),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
$transaction: vi.fn().mockImplementation(
|
||||
async (callback: (inner: typeof tx) => Promise<unknown>) => callback(tx),
|
||||
),
|
||||
$transaction: vi
|
||||
.fn()
|
||||
.mockImplementation(async (callback: (inner: typeof tx) => Promise<unknown>) =>
|
||||
callback(tx),
|
||||
),
|
||||
},
|
||||
{
|
||||
userRole: SystemRole.ADMIN,
|
||||
@@ -92,24 +89,24 @@ describe("assistant demand create tool - race errors", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
project: {
|
||||
findUnique: vi.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
role: {
|
||||
findUnique: vi.fn().mockResolvedValue({ id: "role_1", name: "Designer" }),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
$transaction: vi.fn().mockImplementation(
|
||||
async (callback: (inner: typeof tx) => Promise<unknown>) => callback(tx),
|
||||
),
|
||||
$transaction: vi
|
||||
.fn()
|
||||
.mockImplementation(async (callback: (inner: typeof tx) => Promise<unknown>) =>
|
||||
callback(tx),
|
||||
),
|
||||
},
|
||||
{
|
||||
userRole: SystemRole.ADMIN,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import {
|
||||
createToolContext,
|
||||
executeTool,
|
||||
} from "./assistant-tools-demand-create-test-helpers.js";
|
||||
import { createToolContext, executeTool } from "./assistant-tools-demand-create-test-helpers.js";
|
||||
|
||||
describe("assistant demand create tool - role resolution errors", () => {
|
||||
beforeEach(() => {
|
||||
@@ -15,15 +12,13 @@ describe("assistant demand create tool - role resolution errors", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
project: {
|
||||
findUnique: vi.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
role: {
|
||||
@@ -60,15 +55,13 @@ describe("assistant demand create tool - role resolution errors", () => {
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
project: {
|
||||
findUnique: vi.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
shortCode: "PROJ-1",
|
||||
status: "ACTIVE",
|
||||
responsiblePerson: "Peter Parker",
|
||||
}),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
role: {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import {
|
||||
createToolContext,
|
||||
executeTool,
|
||||
} from "./assistant-tools-demand-create-test-helpers.js";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { createToolContext, executeTool } from "./assistant-tools-demand-create-test-helpers.js";
|
||||
|
||||
describe("assistant demand create tool - success", () => {
|
||||
beforeEach(() => {
|
||||
@@ -41,9 +38,9 @@ describe("assistant demand create tool - success", () => {
|
||||
demandRequirement: { create: demandCreate },
|
||||
auditLog: { create: auditCreate },
|
||||
};
|
||||
const transaction = vi.fn().mockImplementation(
|
||||
async (callback: (inner: typeof tx) => Promise<unknown>) => callback(tx),
|
||||
);
|
||||
const transaction = vi
|
||||
.fn()
|
||||
.mockImplementation(async (callback: (inner: typeof tx) => Promise<unknown>) => callback(tx));
|
||||
const projectFindFirst = vi.fn().mockResolvedValue({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -50,15 +50,13 @@ describe("assistant demand fill error mapping", () => {
|
||||
findUnique: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
resource: {
|
||||
findUnique: vi.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({
|
||||
id: "resource_1",
|
||||
displayName: "Carol Danvers",
|
||||
eid: "EMP-001",
|
||||
chapter: "Delivery",
|
||||
isActive: true,
|
||||
}),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
|
||||
id: "resource_1",
|
||||
displayName: "Carol Danvers",
|
||||
eid: "EMP-001",
|
||||
chapter: "Delivery",
|
||||
isActive: true,
|
||||
}),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
},
|
||||
@@ -97,15 +95,13 @@ describe("assistant demand fill error mapping", () => {
|
||||
}),
|
||||
},
|
||||
resource: {
|
||||
findUnique: vi.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({
|
||||
id: "resource_1",
|
||||
displayName: "Carol Danvers",
|
||||
eid: "EMP-001",
|
||||
chapter: "Delivery",
|
||||
isActive: true,
|
||||
}),
|
||||
findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
|
||||
id: "resource_1",
|
||||
displayName: "Carol Danvers",
|
||||
eid: "EMP-001",
|
||||
chapter: "Delivery",
|
||||
isActive: true,
|
||||
}),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -120,7 +120,9 @@ describe("assistant demand fill tool", () => {
|
||||
create: auditCreate,
|
||||
},
|
||||
};
|
||||
const transaction = vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise<unknown>) => callback(tx));
|
||||
const transaction = vi
|
||||
.fn()
|
||||
.mockImplementation(async (callback: (inner: typeof tx) => Promise<unknown>) => callback(tx));
|
||||
const demandRecord = {
|
||||
id: "demand_1",
|
||||
projectId: "project_1",
|
||||
@@ -136,7 +138,8 @@ describe("assistant demand fill tool", () => {
|
||||
roleEntity: { name: "Designer" },
|
||||
project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" },
|
||||
};
|
||||
const demandFindUnique = vi.fn()
|
||||
const demandFindUnique = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(demandRecord)
|
||||
.mockResolvedValueOnce(demandRecord);
|
||||
const resourceFindUnique = vi.fn().mockResolvedValue({
|
||||
@@ -194,16 +197,20 @@ describe("assistant demand fill tool", () => {
|
||||
ctx,
|
||||
);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual(expect.objectContaining({
|
||||
success: true,
|
||||
message: "Assigned Carol Danvers to Designer on Project One (PROJ-1)",
|
||||
assignmentId: "assignment_1",
|
||||
}));
|
||||
expect(JSON.parse(result.content)).toEqual(
|
||||
expect.objectContaining({
|
||||
success: true,
|
||||
message: "Assigned Carol Danvers to Designer on Project One (PROJ-1)",
|
||||
assignmentId: "assignment_1",
|
||||
}),
|
||||
);
|
||||
expect(assignmentCreate).toHaveBeenCalledTimes(1);
|
||||
expect(txDemandUpdate).toHaveBeenCalledWith(expect.objectContaining({
|
||||
where: { id: "demand_1" },
|
||||
data: { status: "COMPLETED" },
|
||||
}));
|
||||
expect(txDemandUpdate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { id: "demand_1" },
|
||||
data: { status: "COMPLETED" },
|
||||
}),
|
||||
);
|
||||
expect(auditCreate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ImportBatchStatus } from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { ImportBatchStatus } from "@nexus/db";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
assessDispoImportReadiness,
|
||||
commitDispoImportBatch,
|
||||
stageDispoImportBatch,
|
||||
} from "@capakraken/application";
|
||||
} from "@nexus/application";
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-dispo-test-helpers.js";
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ImportBatchStatus } from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { ImportBatchStatus } from "@nexus/db";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
import {
|
||||
createToolContext,
|
||||
executeTool,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { StagedRecordStatus } from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { StagedRecordStatus } from "@nexus/db";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { StagedRecordStatus } from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { StagedRecordStatus } from "@nexus/db";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
DispoStagedRecordType,
|
||||
StagedRecordStatus,
|
||||
} from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { DispoStagedRecordType, StagedRecordStatus } from "@nexus/db";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DispoStagedRecordType } from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { DispoStagedRecordType } from "@nexus/db";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SystemRole, type PermissionKey } from "@capakraken/shared";
|
||||
import { SystemRole, type PermissionKey } from "@nexus/shared";
|
||||
import type { ToolContext } from "../router/assistant-tools.js";
|
||||
|
||||
export function createToolContext(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { cloneEstimate } from "@capakraken/application";
|
||||
import { cloneEstimate } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
@@ -36,29 +36,32 @@ describe("assistant estimate clone errors", () => {
|
||||
const cases = [
|
||||
{
|
||||
payload: { sourceEstimateId: "est_missing" },
|
||||
setup: () => vi.mocked(cloneEstimate).mockRejectedValueOnce(new Error("Source estimate not found")),
|
||||
setup: () =>
|
||||
vi.mocked(cloneEstimate).mockRejectedValueOnce(new Error("Source estimate not found")),
|
||||
expected: "Estimate not found with the given criteria.",
|
||||
},
|
||||
{
|
||||
payload: { sourceEstimateId: "est_empty" },
|
||||
setup: () => vi.mocked(cloneEstimate).mockRejectedValueOnce(new Error("Source estimate has no versions")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(cloneEstimate)
|
||||
.mockRejectedValueOnce(new Error("Source estimate has no versions")),
|
||||
expected: "Source estimate has no versions and cannot be cloned.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
testCase.setup();
|
||||
const ctx = createToolContext({}, {
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
});
|
||||
|
||||
const result = await executeTool(
|
||||
"clone_estimate",
|
||||
JSON.stringify(testCase.payload),
|
||||
ctx,
|
||||
const ctx = createToolContext(
|
||||
{},
|
||||
{
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
},
|
||||
);
|
||||
|
||||
const result = await executeTool("clone_estimate", JSON.stringify(testCase.payload), ctx);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual({ error: testCase.expected });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -34,7 +34,8 @@ describe("assistant estimate creation race tools", () => {
|
||||
const missingProjectCtx = createToolContext(
|
||||
{
|
||||
project: {
|
||||
findUnique: vi.fn()
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ id: "project_1" })
|
||||
.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
@@ -215,11 +216,7 @@ describe("assistant estimate creation race tools", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"create_estimate",
|
||||
JSON.stringify(testCase.payload),
|
||||
ctx,
|
||||
);
|
||||
const result = await executeTool("create_estimate", JSON.stringify(testCase.payload), ctx);
|
||||
|
||||
expect(JSON.parse(result.content)).toEqual({
|
||||
error: testCase.expected,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { updateEstimateDraft } from "@capakraken/application";
|
||||
import { updateEstimateDraft } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
@@ -49,7 +49,8 @@ describe("assistant estimate draft update errors", () => {
|
||||
findUnique: vi.fn().mockResolvedValue({ projectId: null }),
|
||||
},
|
||||
},
|
||||
setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce(new Error("Estimate not found")),
|
||||
setup: () =>
|
||||
vi.mocked(updateEstimateDraft).mockRejectedValueOnce(new Error("Estimate not found")),
|
||||
expected: "Estimate not found with the given criteria.",
|
||||
},
|
||||
{
|
||||
@@ -59,7 +60,10 @@ describe("assistant estimate draft update errors", () => {
|
||||
findUnique: vi.fn().mockResolvedValue({ projectId: null }),
|
||||
},
|
||||
},
|
||||
setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce(new Error("Estimate has no working version")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(updateEstimateDraft)
|
||||
.mockRejectedValueOnce(new Error("Estimate has no working version")),
|
||||
expected: "Estimate has no working version.",
|
||||
},
|
||||
{
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { getEstimateById } from "@capakraken/application";
|
||||
import { getEstimateById } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
|
||||
+16
-10
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { getEstimateById } from "@capakraken/application";
|
||||
import { getEstimateById } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
@@ -35,9 +35,12 @@ describe("assistant estimate weekly phasing read errors", () => {
|
||||
it("returns a stable error when get_estimate_weekly_phasing cannot find the estimate", async () => {
|
||||
vi.mocked(getEstimateById).mockResolvedValueOnce(null as never);
|
||||
|
||||
const ctx = createToolContext({}, {
|
||||
userRole: SystemRole.CONTROLLER,
|
||||
});
|
||||
const ctx = createToolContext(
|
||||
{},
|
||||
{
|
||||
userRole: SystemRole.CONTROLLER,
|
||||
},
|
||||
);
|
||||
const result = await executeTool(
|
||||
"get_estimate_weekly_phasing",
|
||||
JSON.stringify({ estimateId: "est_missing" }),
|
||||
@@ -56,9 +59,12 @@ describe("assistant estimate weekly phasing read errors", () => {
|
||||
versions: [],
|
||||
} as Awaited<ReturnType<typeof getEstimateById>>);
|
||||
|
||||
const ctx = createToolContext({}, {
|
||||
userRole: SystemRole.CONTROLLER,
|
||||
});
|
||||
const ctx = createToolContext(
|
||||
{},
|
||||
{
|
||||
userRole: SystemRole.CONTROLLER,
|
||||
},
|
||||
);
|
||||
const result = await executeTool(
|
||||
"get_estimate_weekly_phasing",
|
||||
JSON.stringify({ estimateId: "est_empty" }),
|
||||
|
||||
+42
-25
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { createEstimatePlanningHandoff } from "@capakraken/application";
|
||||
import { createEstimatePlanningHandoff } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
@@ -36,62 +36,79 @@ describe("assistant estimate planning handoff mutation errors", () => {
|
||||
const cases = [
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Linked project not found")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(createEstimatePlanningHandoff)
|
||||
.mockRejectedValueOnce(new Error("Linked project not found")),
|
||||
expected: "Project not found with the given criteria.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Estimate has no approved version")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(createEstimatePlanningHandoff)
|
||||
.mockRejectedValueOnce(new Error("Estimate has no approved version")),
|
||||
expected: "Estimate has no approved version.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () =>
|
||||
vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(
|
||||
new Error("Planning handoff already exists for this approved version"),
|
||||
),
|
||||
vi
|
||||
.mocked(createEstimatePlanningHandoff)
|
||||
.mockRejectedValueOnce(
|
||||
new Error("Planning handoff already exists for this approved version"),
|
||||
),
|
||||
expected: "Planning handoff already exists for this approved version.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () =>
|
||||
vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(
|
||||
new Error("Project window has no working days for demand line dl_1"),
|
||||
),
|
||||
vi
|
||||
.mocked(createEstimatePlanningHandoff)
|
||||
.mockRejectedValueOnce(
|
||||
new Error("Project window has no working days for demand line dl_1"),
|
||||
),
|
||||
expected: "The linked project window has no working days for at least one demand line.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () =>
|
||||
vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(
|
||||
new Error("Only approved versions can be handed off to planning"),
|
||||
),
|
||||
vi
|
||||
.mocked(createEstimatePlanningHandoff)
|
||||
.mockRejectedValueOnce(
|
||||
new Error("Only approved versions can be handed off to planning"),
|
||||
),
|
||||
expected: "Only approved versions can be handed off to planning.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () =>
|
||||
vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(
|
||||
new Error("Estimate must be linked to a project before planning handoff"),
|
||||
),
|
||||
vi
|
||||
.mocked(createEstimatePlanningHandoff)
|
||||
.mockRejectedValueOnce(
|
||||
new Error("Estimate must be linked to a project before planning handoff"),
|
||||
),
|
||||
expected: "Estimate must be linked to a project before planning handoff.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () =>
|
||||
vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(
|
||||
new Error("Linked project has an invalid date range"),
|
||||
),
|
||||
vi
|
||||
.mocked(createEstimatePlanningHandoff)
|
||||
.mockRejectedValueOnce(new Error("Linked project has an invalid date range")),
|
||||
expected: "The linked project has an invalid date range for planning handoff.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
testCase.setup();
|
||||
const ctx = createToolContext({}, {
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_ALLOCATIONS],
|
||||
});
|
||||
const ctx = createToolContext(
|
||||
{},
|
||||
{
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_ALLOCATIONS],
|
||||
},
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"create_estimate_planning_handoff",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { getEstimateById } from "@capakraken/application";
|
||||
import { getEstimateById } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { getEstimateById } from "@capakraken/application";
|
||||
import { getEstimateById } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
|
||||
+4
-6
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -51,9 +51,7 @@ describe("assistant estimate version snapshot tools", () => {
|
||||
assumptions: [
|
||||
{ id: "ass_1", category: "DELIVERY", key: "onsite", label: "Onsite support" },
|
||||
],
|
||||
scopeItems: [
|
||||
{ id: "scope_1", scopeType: "EPIC", sequenceNo: 1, name: "Pipeline" },
|
||||
],
|
||||
scopeItems: [{ id: "scope_1", scopeType: "EPIC", sequenceNo: 1, name: "Pipeline" }],
|
||||
demandLines: [
|
||||
{
|
||||
id: "dl_1",
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
|
||||
+35
-18
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { createEstimateExport, createEstimateRevision } from "@capakraken/application";
|
||||
import { createEstimateExport, createEstimateRevision } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
@@ -36,7 +36,10 @@ describe("assistant estimate revision and export mutation errors", () => {
|
||||
const cases = [
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () => vi.mocked(createEstimateRevision).mockRejectedValueOnce(new Error("Estimate already has a working version")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(createEstimateRevision)
|
||||
.mockRejectedValueOnce(new Error("Estimate already has a working version")),
|
||||
expected: "Estimate already has a working version.",
|
||||
},
|
||||
{
|
||||
@@ -53,24 +56,32 @@ describe("assistant estimate revision and export mutation errors", () => {
|
||||
{
|
||||
payload: { estimateId: "est_1", sourceVersionId: "ver_1" },
|
||||
setup: () =>
|
||||
vi.mocked(createEstimateRevision).mockRejectedValueOnce(
|
||||
new Error("Source version must be locked before creating a revision"),
|
||||
),
|
||||
vi
|
||||
.mocked(createEstimateRevision)
|
||||
.mockRejectedValueOnce(
|
||||
new Error("Source version must be locked before creating a revision"),
|
||||
),
|
||||
expected: "Source version must be locked before creating a revision.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () => vi.mocked(createEstimateRevision).mockRejectedValueOnce(new Error("Estimate has no locked version to revise")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(createEstimateRevision)
|
||||
.mockRejectedValueOnce(new Error("Estimate has no locked version to revise")),
|
||||
expected: "Estimate has no locked version to revise.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
testCase.setup();
|
||||
const ctx = createToolContext({}, {
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
});
|
||||
const ctx = createToolContext(
|
||||
{},
|
||||
{
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
},
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"create_estimate_revision",
|
||||
@@ -86,7 +97,10 @@ describe("assistant estimate revision and export mutation errors", () => {
|
||||
const cases = [
|
||||
{
|
||||
payload: { estimateId: "est_1", versionId: "ver_missing", format: "XLSX" },
|
||||
setup: () => vi.mocked(createEstimateExport).mockRejectedValueOnce(new Error("Estimate version not found")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(createEstimateExport)
|
||||
.mockRejectedValueOnce(new Error("Estimate version not found")),
|
||||
expected: "Estimate version not found with the given criteria.",
|
||||
},
|
||||
{
|
||||
@@ -104,10 +118,13 @@ describe("assistant estimate revision and export mutation errors", () => {
|
||||
|
||||
for (const testCase of cases) {
|
||||
testCase.setup();
|
||||
const ctx = createToolContext({}, {
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
});
|
||||
const ctx = createToolContext(
|
||||
{},
|
||||
{
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
},
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"create_estimate_export",
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
getEstimateById,
|
||||
submitEstimateVersion,
|
||||
updateEstimateDraft,
|
||||
} from "@capakraken/application";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
} from "@nexus/application";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import type { ToolContext } from "../router/assistant-tools.js";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { PermissionKey, SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
approveEstimateVersion: vi.fn(),
|
||||
@@ -19,7 +19,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { approveEstimateVersion, submitEstimateVersion } from "@capakraken/application";
|
||||
import { approveEstimateVersion, submitEstimateVersion } from "@nexus/application";
|
||||
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import {
|
||||
@@ -36,7 +36,10 @@ describe("assistant estimate version status mutation errors", () => {
|
||||
const cases = [
|
||||
{
|
||||
payload: { estimateId: "est_1", versionId: "ver_missing" },
|
||||
setup: () => vi.mocked(submitEstimateVersion).mockRejectedValueOnce(new Error("Estimate version not found")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(submitEstimateVersion)
|
||||
.mockRejectedValueOnce(new Error("Estimate version not found")),
|
||||
expected: "Estimate version not found with the given criteria.",
|
||||
},
|
||||
{
|
||||
@@ -62,22 +65,31 @@ describe("assistant estimate version status mutation errors", () => {
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () => vi.mocked(submitEstimateVersion).mockRejectedValueOnce(new Error("Estimate has no working version")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(submitEstimateVersion)
|
||||
.mockRejectedValueOnce(new Error("Estimate has no working version")),
|
||||
expected: "Estimate has no working version.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () => vi.mocked(submitEstimateVersion).mockRejectedValueOnce(new Error("Only working versions can be submitted")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(submitEstimateVersion)
|
||||
.mockRejectedValueOnce(new Error("Only working versions can be submitted")),
|
||||
expected: "Only working versions can be submitted.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
testCase.setup();
|
||||
const ctx = createToolContext({}, {
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
});
|
||||
const ctx = createToolContext(
|
||||
{},
|
||||
{
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
},
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"submit_estimate_version",
|
||||
@@ -93,7 +105,10 @@ describe("assistant estimate version status mutation errors", () => {
|
||||
const cases = [
|
||||
{
|
||||
payload: { estimateId: "est_1", versionId: "ver_missing" },
|
||||
setup: () => vi.mocked(approveEstimateVersion).mockRejectedValueOnce(new Error("Estimate version not found")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(approveEstimateVersion)
|
||||
.mockRejectedValueOnce(new Error("Estimate version not found")),
|
||||
expected: "Estimate version not found with the given criteria.",
|
||||
},
|
||||
{
|
||||
@@ -109,22 +124,31 @@ describe("assistant estimate version status mutation errors", () => {
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () => vi.mocked(approveEstimateVersion).mockRejectedValueOnce(new Error("Estimate has no submitted version")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(approveEstimateVersion)
|
||||
.mockRejectedValueOnce(new Error("Estimate has no submitted version")),
|
||||
expected: "Estimate has no submitted version.",
|
||||
},
|
||||
{
|
||||
payload: { estimateId: "est_1" },
|
||||
setup: () => vi.mocked(approveEstimateVersion).mockRejectedValueOnce(new Error("Only submitted versions can be approved")),
|
||||
setup: () =>
|
||||
vi
|
||||
.mocked(approveEstimateVersion)
|
||||
.mockRejectedValueOnce(new Error("Only submitted versions can be approved")),
|
||||
expected: "Only submitted versions can be approved.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
testCase.setup();
|
||||
const ctx = createToolContext({}, {
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
});
|
||||
const ctx = createToolContext(
|
||||
{},
|
||||
{
|
||||
userRole: SystemRole.MANAGER,
|
||||
permissions: [PermissionKey.MANAGE_PROJECTS],
|
||||
},
|
||||
);
|
||||
|
||||
const result = await executeTool(
|
||||
"approve_estimate_version",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
import {
|
||||
createToolContext,
|
||||
executeTool,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
import {
|
||||
createToolContext,
|
||||
executeTool,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -13,7 +13,7 @@ vi.mock("../lib/audit.js", () => ({
|
||||
createAuditEntry: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
import { getDashboardBudgetForecast } from "@capakraken/application";
|
||||
import { getDashboardBudgetForecast } from "@nexus/application";
|
||||
import { executeTool } from "../router/assistant-tools.js";
|
||||
import { createToolContext } from "./assistant-tools-holiday-capacity-test-helpers.js";
|
||||
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
+23
-8
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -24,7 +24,9 @@ describe("assistant holiday calendar mutation tools - success", () => {
|
||||
it("creates a holiday calendar through the assistant for admin users", async () => {
|
||||
const ctx = createToolContext({
|
||||
country: {
|
||||
findUnique: vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Deutschland" }),
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ id: "country_de", code: "DE", name: "Deutschland" }),
|
||||
},
|
||||
holidayCalendar: {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
@@ -100,7 +102,8 @@ describe("assistant holiday calendar mutation tools - success", () => {
|
||||
},
|
||||
holidayCalendar: {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
findUnique: vi.fn()
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
id: "cal_de",
|
||||
name: "Germany National",
|
||||
@@ -126,7 +129,10 @@ describe("assistant holiday calendar mutation tools - success", () => {
|
||||
);
|
||||
const updateCalendarResult = await executeTool(
|
||||
"update_holiday_calendar",
|
||||
JSON.stringify({ id: "cal_de", data: { name: "Germany National Updated", isActive: false, priority: 1 } }),
|
||||
JSON.stringify({
|
||||
id: "cal_de",
|
||||
data: { name: "Germany National Updated", isActive: false, priority: 1 },
|
||||
}),
|
||||
ctx,
|
||||
);
|
||||
const deleteCalendarResult = await executeTool(
|
||||
@@ -164,13 +170,22 @@ describe("assistant holiday calendar mutation tools - success", () => {
|
||||
});
|
||||
expect(holidayCalendarDelete).toHaveBeenCalledWith({ where: { id: "cal_de" } });
|
||||
expect(JSON.parse(createCalendarResult.content)).toEqual(
|
||||
expect.objectContaining({ success: true, message: "Created holiday calendar: Germany National" }),
|
||||
expect.objectContaining({
|
||||
success: true,
|
||||
message: "Created holiday calendar: Germany National",
|
||||
}),
|
||||
);
|
||||
expect(JSON.parse(updateCalendarResult.content)).toEqual(
|
||||
expect.objectContaining({ success: true, message: "Updated holiday calendar: Germany National Updated" }),
|
||||
expect.objectContaining({
|
||||
success: true,
|
||||
message: "Updated holiday calendar: Germany National Updated",
|
||||
}),
|
||||
);
|
||||
expect(JSON.parse(deleteCalendarResult.content)).toEqual(
|
||||
expect.objectContaining({ success: true, message: "Deleted holiday calendar: Germany National Updated" }),
|
||||
expect.objectContaining({
|
||||
success: true,
|
||||
message: "Deleted holiday calendar: Germany National Updated",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { SystemRole } from "@nexus/shared";
|
||||
|
||||
import type { ToolContext } from "../router/assistant-tools.js";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PermissionKey } from "@capakraken/shared";
|
||||
import { PermissionKey } from "@nexus/shared";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -30,11 +30,25 @@ describe("assistant holiday-aware chargeability tool", () => {
|
||||
fte: 1,
|
||||
lcrCents: 5000,
|
||||
chargeabilityTarget: 80,
|
||||
availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0 },
|
||||
availability: {
|
||||
monday: 8,
|
||||
tuesday: 8,
|
||||
wednesday: 8,
|
||||
thursday: 8,
|
||||
friday: 8,
|
||||
saturday: 0,
|
||||
sunday: 0,
|
||||
},
|
||||
countryId: "country_de",
|
||||
federalState: "BY",
|
||||
metroCityId: null,
|
||||
country: { id: "country_de", code: "DE", name: "Deutschland", dailyWorkingHours: 8, scheduleRules: null },
|
||||
country: {
|
||||
id: "country_de",
|
||||
code: "DE",
|
||||
name: "Deutschland",
|
||||
dailyWorkingHours: 8,
|
||||
scheduleRules: null,
|
||||
},
|
||||
metroCity: null,
|
||||
managementLevelGroup: null,
|
||||
};
|
||||
@@ -91,7 +105,11 @@ describe("assistant holiday-aware chargeability tool", () => {
|
||||
targetHours: number;
|
||||
unassignedHours: number;
|
||||
holidaySummary: { count: number; workdayCount: number; hoursDeduction: number };
|
||||
capacityBreakdown: { formula: string; holidayHoursDeduction: number; absenceHoursDeduction: number };
|
||||
capacityBreakdown: {
|
||||
formula: string;
|
||||
holidayHoursDeduction: number;
|
||||
absenceHoursDeduction: number;
|
||||
};
|
||||
locationContext: { federalState: string | null };
|
||||
allocations: Array<{ hours: number }>;
|
||||
};
|
||||
@@ -114,7 +132,8 @@ describe("assistant holiday-aware chargeability tool", () => {
|
||||
);
|
||||
expect(parsed.capacityBreakdown).toEqual(
|
||||
expect.objectContaining({
|
||||
formula: "baseAvailableHours - holidayHoursDeduction - absenceHoursDeduction = availableHours",
|
||||
formula:
|
||||
"baseAvailableHours - holidayHoursDeduction - absenceHoursDeduction = availableHours",
|
||||
holidayHoursDeduction: 16,
|
||||
absenceHoursDeduction: 0,
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
||||
vi.mock("@nexus/application", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@nexus/application")>();
|
||||
return {
|
||||
...actual,
|
||||
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
|
||||
@@ -47,10 +47,9 @@ describe("assistant holiday entry mutation tools - success", () => {
|
||||
}),
|
||||
},
|
||||
holidayCalendarEntry: {
|
||||
findFirst: vi.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce(null),
|
||||
findUnique: vi.fn()
|
||||
findFirst: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce(null),
|
||||
findUnique: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
id: "entry_1",
|
||||
name: "New Year",
|
||||
@@ -82,7 +81,12 @@ describe("assistant holiday entry mutation tools - success", () => {
|
||||
"update_holiday_calendar_entry",
|
||||
JSON.stringify({
|
||||
id: "entry_1",
|
||||
data: { date: "2026-01-02", name: "New Year Observed", isRecurringAnnual: false, source: null },
|
||||
data: {
|
||||
date: "2026-01-02",
|
||||
name: "New Year Observed",
|
||||
isRecurringAnnual: false,
|
||||
source: null,
|
||||
},
|
||||
}),
|
||||
ctx,
|
||||
);
|
||||
@@ -115,10 +119,16 @@ describe("assistant holiday entry mutation tools - success", () => {
|
||||
expect.objectContaining({ success: true, message: "Created holiday entry: New Year" }),
|
||||
);
|
||||
expect(JSON.parse(updateEntryResult.content)).toEqual(
|
||||
expect.objectContaining({ success: true, message: "Updated holiday entry: New Year Observed" }),
|
||||
expect.objectContaining({
|
||||
success: true,
|
||||
message: "Updated holiday entry: New Year Observed",
|
||||
}),
|
||||
);
|
||||
expect(JSON.parse(deleteEntryResult.content)).toEqual(
|
||||
expect.objectContaining({ success: true, message: "Deleted holiday entry: New Year Observed" }),
|
||||
expect.objectContaining({
|
||||
success: true,
|
||||
message: "Deleted holiday entry: New Year Observed",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user