Add/ai native supp (#385)
* feat(branding): implement dynamic branding in service worker and reports * feat(auth): enhance API key scopes and add bookkeeping write scope - Updated transaction write scope description to include additional tools. - Enhanced reports read scope description to reflect new functionality. - Introduced bookkeeping write scope with relevant description. - Updated SCOPE_GROUPS to include bookkeeping domain. - Modified TOOL_SCOPE_MAP to include new bookkeeping operations. - Updated validateApiKey function to return api_key_id and api_key_name for better actor attribution. feat(tests): add unit tests for MCP resource registry - Created tests for data resources to ensure all required fields are present. - Added tests for resource query parsing and retrieval. feat(resources): implement MCP resources for company and accounting data - Added capabilities resource to expose API key capabilities based on granted scopes. - Implemented chart of accounts resource to retrieve active BAS chart. - Created company current resource to fetch active company details. - Developed active fiscal period resource to check posting eligibility. - Implemented recent activity resource to fetch latest journal entries, invoices, and transactions. - Added VAT treatments resource to provide available VAT rates per customer type. feat(pending-operations): introduce risk tiers for operations - Added risk level classification for pending operations to determine auto-commit eligibility. - Implemented functions to classify operation risk levels and identify high-risk operations. feat(migrations): add actor model and risk tier to pending operations - Updated pending_operations table to include actor type and risk level columns. - Enhanced audit_log to mirror actor information for compliance. - Modified validate_and_increment_api_key function to return actor details. - Expanded operation types in pending_operations to include new high-risk operations. * feat: add auto-commit functionality for low-risk pending operations - Implemented shouldAutoCommit function to determine eligibility for auto-commit based on operation type, actor type, and company settings. - Created commitPendingOperation function to handle execution of pending operations with consistent status updates. - Added tests for shouldAutoCommit to cover various scenarios including high-risk operations, user actors, company opt-in status, and monetary thresholds. - Introduced new columns in company_settings for agent_auto_commit_enabled and agent_auto_commit_max_amount to allow companies to opt-in for auto-commit functionality. - Added SQL migration to update the database schema for new auto-commit settings. * feat(idempotency): implement idempotency key handling for safe retries and cleanup * feat: expand API key scopes and pending operations for bookkeeping - Added 'suppliers:write' scope to API key scopes for supplier invoice management. - Updated SCOPE_GROUPS to include the new 'suppliers:write' scope. - Introduced new pending operation types for bookkeeping: close_period, lock_period, run_year_end, set_opening_balances, run_currency_revaluation, explain_voucher_gap, uncategorize_transaction, approve_supplier_invoice, credit_supplier_invoice, and convert_invoice. - Implemented corresponding commit functions for the new operations in the pending operations module. - Enhanced PendingOperation type to include actor model and risk level attributes. - Added tests for new functionality, ensuring proper behavior and constraints in the database. * feat: implement unlockPeriod functionality and related tests * feat: add agent auto-commit settings and related functionality * feat: add attention resource with comprehensive summary of outstanding tasks * feat: enhance pending operations with 'committing' status and immutability checks, improve idempotency handling, and add original voucher reference for credit notes
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { getStructuredError } from '../get-structured-error'
|
||||
|
||||
describe('getStructuredError', () => {
|
||||
it('extracts code from structured bookkeeping error', () => {
|
||||
const result = getStructuredError({
|
||||
error: {
|
||||
code: 'JOURNAL_ENTRY_NOT_BALANCED',
|
||||
message: 'Debits do not match credits',
|
||||
details: { totalDebit: 100, totalCredit: 90 },
|
||||
},
|
||||
})
|
||||
expect(result.code).toBe('JOURNAL_ENTRY_NOT_BALANCED')
|
||||
expect(result.message_sv).toContain('balanserar inte')
|
||||
expect(result.message_en).toContain('Debits')
|
||||
expect(result.remediation?.description).toContain('Recalculate')
|
||||
})
|
||||
|
||||
it('extracts code from typed error class with code property', () => {
|
||||
class FakeBookkeepingError extends Error {
|
||||
readonly code = 'ACCOUNTS_NOT_IN_CHART'
|
||||
readonly accountNumbers = ['1930', '2641']
|
||||
constructor() {
|
||||
super('Accounts not in chart')
|
||||
}
|
||||
}
|
||||
const result = getStructuredError(new FakeBookkeepingError())
|
||||
expect(result.code).toBe('ACCOUNTS_NOT_IN_CHART')
|
||||
expect(result.remediation?.resource).toBe('gnubok://chart-of-accounts')
|
||||
})
|
||||
|
||||
it('infers PERIOD_NOT_LOCKED from message text', () => {
|
||||
const result = getStructuredError(new Error('Period must be locked before closing'))
|
||||
expect(result.code).toBe('PERIOD_NOT_LOCKED')
|
||||
expect(result.remediation?.tool).toBe('gnubok_lock_period')
|
||||
})
|
||||
|
||||
it('infers PERIOD_HAS_UNBOOKED_TRANSACTIONS from Swedish lock-error message', () => {
|
||||
const result = getStructuredError(
|
||||
new Error('Kan inte låsa period: 3 affärstransaktion(er) saknar bokföring.')
|
||||
)
|
||||
expect(result.code).toBe('PERIOD_HAS_UNBOOKED_TRANSACTIONS')
|
||||
expect(result.remediation?.tool).toBe('gnubok_list_uncategorized_transactions')
|
||||
})
|
||||
|
||||
it('produces INSUFFICIENT_SCOPE remediation with attempted scope', () => {
|
||||
const result = getStructuredError(
|
||||
new Error('Insufficient scope: this API key does not have the "bookkeeping:write" scope'),
|
||||
{ attemptedScope: 'bookkeeping:write' }
|
||||
)
|
||||
expect(result.code).toBe('INSUFFICIENT_SCOPE')
|
||||
expect(result.remediation?.description).toContain('"bookkeeping:write"')
|
||||
expect(result.remediation?.resource).toBe('gnubok://capabilities')
|
||||
})
|
||||
|
||||
it('infers TRANSACTION_ALREADY_CATEGORIZED', () => {
|
||||
const result = getStructuredError(new Error('Transaction already has a journal entry'))
|
||||
expect(result.code).toBe('TRANSACTION_ALREADY_CATEGORIZED')
|
||||
expect(result.remediation?.tool).toBe('gnubok_uncategorize_transaction')
|
||||
})
|
||||
|
||||
it('falls back to UNKNOWN_ERROR when no code or pattern matches', () => {
|
||||
const result = getStructuredError(new Error('Something weird happened'))
|
||||
expect(result.code).toBe('UNKNOWN_ERROR')
|
||||
expect(result.remediation).toBeUndefined()
|
||||
})
|
||||
|
||||
it('handles plain string errors', () => {
|
||||
const result = getStructuredError('Period must be locked before closing')
|
||||
expect(result.code).toBe('PERIOD_NOT_LOCKED')
|
||||
expect(result.message_en).toBe('Period must be locked before closing')
|
||||
})
|
||||
|
||||
it('handles null/undefined gracefully', () => {
|
||||
const result = getStructuredError(null)
|
||||
expect(result.code).toBe('UNKNOWN_ERROR')
|
||||
expect(result.message_en).toBe('Unknown error')
|
||||
expect(result.message_sv).toBeTruthy()
|
||||
})
|
||||
|
||||
it('always returns Swedish message even with no match', () => {
|
||||
const result = getStructuredError(new Error('Random gibberish XYZ'))
|
||||
expect(result.message_sv).toBeTruthy()
|
||||
expect(result.message_sv.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user