fix(mcp): allow null optional fields on transaction list tools (#484)
* fix(mcp): allow null optional fields on transaction list tools Signed-off-by: antonisoaho <anton@isoaho.se> * test(mcp): assert null optional fields on list_transactions_without_documents * Update extensions/general/mcp-server/__tests__/list-uncategorized-transactions.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Signed-off-by: antonisoaho <anton@isoaho.se> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
greptile-apps[bot]
parent
3f422b9dc8
commit
5f79a74a2e
@@ -55,6 +55,45 @@ describe('gnubok_list_transactions_without_documents', () => {
|
||||
expect(result.transactions[0].journal_entry_id).toBe('je-1')
|
||||
})
|
||||
|
||||
it('returns rows when DB has null merchant_name, reference, is_business, category (MCP structured output)', async () => {
|
||||
const rows = [
|
||||
{
|
||||
id: 't-no-doc-1',
|
||||
date: '2026-03-17',
|
||||
description: 'Nolla skuld',
|
||||
amount: -2745,
|
||||
currency: 'SEK',
|
||||
merchant_name: null,
|
||||
reference: null,
|
||||
is_business: null,
|
||||
category: null,
|
||||
journal_entry_id: 'je-nolla-1',
|
||||
},
|
||||
]
|
||||
const { supabase, enqueue } = createQueuedMockSupabase()
|
||||
enqueue({ data: null, error: null, count: 1 })
|
||||
enqueue({ data: rows, error: null })
|
||||
|
||||
const result = (await tool.execute(
|
||||
{ limit: 20 },
|
||||
'company-1',
|
||||
'user-1',
|
||||
supabase as never
|
||||
)) as {
|
||||
transactions: typeof rows
|
||||
count: number
|
||||
total_count: number
|
||||
has_more: boolean
|
||||
}
|
||||
|
||||
expect(result.count).toBe(1)
|
||||
expect(result.transactions[0].journal_entry_id).toBe('je-nolla-1')
|
||||
expect(result.transactions[0].merchant_name).toBeNull()
|
||||
expect(result.transactions[0].reference).toBeNull()
|
||||
expect(result.transactions[0].is_business).toBeNull()
|
||||
expect(result.transactions[0].category).toBeNull()
|
||||
})
|
||||
|
||||
it('returns empty result when nothing matches', async () => {
|
||||
const { supabase, enqueue } = createQueuedMockSupabase()
|
||||
enqueue({ data: null, error: null, count: 0 })
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { createQueuedMockSupabase } from '@/tests/helpers'
|
||||
import { tools } from '../server'
|
||||
|
||||
const tool = tools.find((t) => t.name === 'gnubok_list_uncategorized_transactions')!
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('gnubok_list_uncategorized_transactions', () => {
|
||||
it('is registered as a read-only paginated tool', () => {
|
||||
expect(tool).toBeDefined()
|
||||
expect(tool.annotations?.readOnlyHint).toBe(true)
|
||||
const schema = tool.outputSchema as Record<string, unknown>
|
||||
expect((schema.properties as Record<string, unknown>).transactions).toBeDefined()
|
||||
expect((schema.properties as Record<string, unknown>).total_count).toBeDefined()
|
||||
})
|
||||
|
||||
it('returns rows when DB has null merchant_name, reference, is_business (MCP structured output)', async () => {
|
||||
const rows = [
|
||||
{
|
||||
id: 't-uncat-1',
|
||||
date: '2026-03-09',
|
||||
description: 'Transfer',
|
||||
amount: -6000,
|
||||
currency: 'SEK',
|
||||
merchant_name: null,
|
||||
reference: null,
|
||||
is_business: null,
|
||||
category: null,
|
||||
},
|
||||
]
|
||||
const { supabase, enqueue } = createQueuedMockSupabase()
|
||||
enqueue({ data: null, error: null, count: 1 })
|
||||
enqueue({ data: rows, error: null })
|
||||
|
||||
const result = (await tool.execute(
|
||||
{ limit: 20 },
|
||||
'company-1',
|
||||
'user-1',
|
||||
supabase as never
|
||||
)) as {
|
||||
transactions: typeof rows
|
||||
count: number
|
||||
total_count: number
|
||||
has_more: boolean
|
||||
}
|
||||
|
||||
expect(result.count).toBe(1)
|
||||
expect(result.transactions[0].merchant_name).toBeNull()
|
||||
expect(result.transactions[0].reference).toBeNull()
|
||||
expect(result.transactions[0].is_business).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -2231,10 +2231,10 @@ export const tools: McpTool[] = [
|
||||
description: { type: 'string' },
|
||||
amount: { type: 'number' },
|
||||
currency: { type: 'string' },
|
||||
merchant_name: { type: 'string' },
|
||||
reference: { type: 'string' },
|
||||
is_business: { type: 'boolean' },
|
||||
category: { type: 'string' },
|
||||
merchant_name: { type: ['string', 'null'] },
|
||||
reference: { type: ['string', 'null'] },
|
||||
is_business: { type: ['boolean', 'null'] },
|
||||
category: { type: ['string', 'null'] },
|
||||
},
|
||||
}),
|
||||
annotations: {
|
||||
@@ -2303,10 +2303,10 @@ export const tools: McpTool[] = [
|
||||
description: { type: 'string' },
|
||||
amount: { type: 'number' },
|
||||
currency: { type: 'string' },
|
||||
merchant_name: { type: 'string' },
|
||||
reference: { type: 'string' },
|
||||
is_business: { type: 'boolean' },
|
||||
category: { type: 'string' },
|
||||
merchant_name: { type: ['string', 'null'] },
|
||||
reference: { type: ['string', 'null'] },
|
||||
is_business: { type: ['boolean', 'null'] },
|
||||
category: { type: ['string', 'null'] },
|
||||
journal_entry_id: { type: 'string' },
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user