Files
accounted/lib/agent/ask/__tests__/ledger-tools.test.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

90 lines
3.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import type { SupabaseClient } from '@supabase/supabase-js'
import type { AgentTool } from '@/lib/agent/tools/types'
const getMany = vi.fn()
vi.mock('@/lib/agent/tools/registry', () => ({
agentToolRegistry: { getMany: (names: string[]) => getMany(names) },
}))
import { buildLedgerTools, LEDGER_READ_TOOLS } from '../ledger-tools'
function tool(name: string, ann?: AgentTool['annotations']): AgentTool {
return {
name,
description: `desc ${name}`,
inputSchema: { type: 'object', properties: {} },
...(ann ? { annotations: ann } : {}),
execute: vi.fn().mockResolvedValue({ tool: name }),
}
}
const supabase = {} as SupabaseClient
beforeEach(() => vi.clearAllMocks())
describe('buildLedgerTools', () => {
it('requests exactly the read whitelist from the registry', () => {
getMany.mockReturnValue([])
buildLedgerTools(supabase, 'c1', 'u1')
expect(getMany).toHaveBeenCalledWith([...LEDGER_READ_TOOLS])
})
it('keeps read-only + unannotated tools, drops writable/destructive ones', () => {
getMany.mockReturnValue([
tool('gnubok_get_income_statement', { readOnlyHint: true }),
tool('gnubok_list_accounts'), // unannotated: on the curated read whitelist, kept
tool('gnubok_categorize_transaction', { readOnlyHint: false }),
tool('gnubok_delete_voucher', { destructiveHint: true }),
])
const defs = buildLedgerTools(supabase, 'c1', 'u1')
expect(defs.map((d) => d.name)).toEqual(['gnubok_get_income_statement', 'gnubok_list_accounts'])
})
it('adapts each tool: name/description/jsonSchema and an execute bound to the actor', async () => {
const src = tool('gnubok_get_vat_report', { readOnlyHint: true })
getMany.mockReturnValue([src])
const defs = buildLedgerTools(supabase, 'company-9', 'user-7', 'conv-3')
expect(defs).toHaveLength(1)
const def = defs[0]
expect(def.name).toBe('gnubok_get_vat_report')
expect(def.description).toBe('desc gnubok_get_vat_report')
expect(def.jsonSchema).toEqual({ type: 'object', properties: {} })
const out = await def.execute({ period: '2026-07' })
expect(out).toEqual({ tool: 'gnubok_get_vat_report' })
expect(src.execute).toHaveBeenCalledWith(
{ period: '2026-07' },
'company-9',
'user-7',
supabase,
{ type: 'agent_chat', id: 'conv-3' },
)
})
it('omits the actor id when there is no conversation', async () => {
const src = tool('gnubok_get_trial_balance', { readOnlyHint: true })
getMany.mockReturnValue([src])
const [def] = buildLedgerTools(supabase, 'c1', 'u1')
await def.execute({})
expect(src.execute).toHaveBeenCalledWith({}, 'c1', 'u1', supabase, { type: 'agent_chat' })
})
it('returns nothing when the registry is empty (core-only build)', () => {
getMany.mockReturnValue([])
expect(buildLedgerTools(supabase, 'c1', 'u1')).toEqual([])
})
it('never lists a write or memory-write tool on the whitelist', () => {
for (const bad of [
'gnubok_categorize_transaction',
'gnubok_create_invoice',
'gnubok_approve_supplier_invoice',
'gnubok_remember_fact',
'gnubok_forget_fact',
]) {
expect(LEDGER_READ_TOOLS).not.toContain(bad)
}
})
})