@robosystems/client 0.2.47 → 0.2.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/AgentClient.test.ts +403 -0
- package/extensions/LedgerClient.d.ts +16 -1
- package/extensions/LedgerClient.js +33 -0
- package/extensions/LedgerClient.test.ts +1138 -0
- package/extensions/LedgerClient.ts +47 -0
- package/extensions/OperationClient.test.ts +199 -1
- package/extensions/QueryClient.test.ts +169 -0
- package/extensions/ReportClient.test.ts +828 -0
- package/extensions/hooks.test.ts +373 -0
- package/extensions/index.test.ts +279 -60
- package/index.ts +2 -2
- package/package.json +1 -1
- package/sdk/index.d.ts +2 -2
- package/sdk/index.js +6 -4
- package/sdk/index.ts +2 -2
- package/sdk/sdk.gen.d.ts +20 -1
- package/sdk/sdk.gen.js +30 -1
- package/sdk/sdk.gen.ts +30 -1
- package/sdk/types.gen.d.ts +236 -0
- package/sdk/types.gen.ts +252 -0
- package/sdk-extensions/AgentClient.test.ts +403 -0
- package/sdk-extensions/LedgerClient.d.ts +16 -1
- package/sdk-extensions/LedgerClient.js +33 -0
- package/sdk-extensions/LedgerClient.test.ts +1138 -0
- package/sdk-extensions/LedgerClient.ts +47 -0
- package/sdk-extensions/OperationClient.test.ts +199 -1
- package/sdk-extensions/QueryClient.test.ts +169 -0
- package/sdk-extensions/ReportClient.test.ts +828 -0
- package/sdk-extensions/hooks.test.ts +373 -0
- package/sdk-extensions/index.test.ts +279 -60
- package/sdk.gen.d.ts +20 -1
- package/sdk.gen.js +30 -1
- package/sdk.gen.ts +30 -1
- package/types.gen.d.ts +236 -0
- package/types.gen.ts +252 -0
|
@@ -0,0 +1,1138 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { LedgerClient } from './LedgerClient'
|
|
3
|
+
|
|
4
|
+
// Helper to create proper mock Response objects
|
|
5
|
+
function createMockResponse(data: any, options: { ok?: boolean; status?: number } = {}) {
|
|
6
|
+
return {
|
|
7
|
+
ok: options.ok ?? true,
|
|
8
|
+
status: options.status ?? 200,
|
|
9
|
+
statusText: options.status === 200 ? 'OK' : 'Error',
|
|
10
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
11
|
+
json: async () => data,
|
|
12
|
+
text: async () => JSON.stringify(data),
|
|
13
|
+
blob: async () => new Blob([JSON.stringify(data)]),
|
|
14
|
+
arrayBuffer: async () => new TextEncoder().encode(JSON.stringify(data)).buffer,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('LedgerClient', () => {
|
|
19
|
+
let client: LedgerClient
|
|
20
|
+
let mockFetch: any
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
client = new LedgerClient({
|
|
24
|
+
baseUrl: 'http://localhost:8000',
|
|
25
|
+
token: 'test-token',
|
|
26
|
+
headers: { 'X-API-Key': 'test-key' },
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
mockFetch = vi.fn()
|
|
30
|
+
global.fetch = mockFetch
|
|
31
|
+
globalThis.fetch = mockFetch
|
|
32
|
+
vi.clearAllMocks()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
vi.restoreAllMocks()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// ── Entity ────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe('getEntity', () => {
|
|
42
|
+
it('should return entity data', async () => {
|
|
43
|
+
mockFetch.mockResolvedValueOnce(
|
|
44
|
+
createMockResponse({
|
|
45
|
+
id: 'ent_123',
|
|
46
|
+
name: 'ACME Corp',
|
|
47
|
+
legal_name: 'ACME Corporation Inc.',
|
|
48
|
+
entity_type: 'corporation',
|
|
49
|
+
industry: 'Technology',
|
|
50
|
+
status: 'active',
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const entity = await client.getEntity('graph_1')
|
|
55
|
+
|
|
56
|
+
expect(entity).not.toBeNull()
|
|
57
|
+
expect(entity!.id).toBe('ent_123')
|
|
58
|
+
expect(entity!.name).toBe('ACME Corp')
|
|
59
|
+
expect(entity!.legalName).toBe('ACME Corporation Inc.')
|
|
60
|
+
expect(entity!.entityType).toBe('corporation')
|
|
61
|
+
expect(entity!.industry).toBe('Technology')
|
|
62
|
+
expect(entity!.status).toBe('active')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should return null for 404', async () => {
|
|
66
|
+
mockFetch.mockResolvedValueOnce(
|
|
67
|
+
createMockResponse({ detail: 'Not found' }, { ok: false, status: 404 })
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const entity = await client.getEntity('graph_nonexistent')
|
|
71
|
+
|
|
72
|
+
expect(entity).toBeNull()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should handle missing optional fields', async () => {
|
|
76
|
+
mockFetch.mockResolvedValueOnce(
|
|
77
|
+
createMockResponse({
|
|
78
|
+
id: 'ent_456',
|
|
79
|
+
name: 'Minimal Corp',
|
|
80
|
+
})
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const entity = await client.getEntity('graph_1')
|
|
84
|
+
|
|
85
|
+
expect(entity!.legalName).toBeNull()
|
|
86
|
+
expect(entity!.entityType).toBeNull()
|
|
87
|
+
expect(entity!.industry).toBeNull()
|
|
88
|
+
expect(entity!.status).toBeNull()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should throw on non-404 errors', async () => {
|
|
92
|
+
mockFetch.mockResolvedValueOnce(
|
|
93
|
+
createMockResponse({ detail: 'Server error' }, { ok: false, status: 500 })
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
await expect(client.getEntity('graph_1')).rejects.toThrow('Get entity failed')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// ── Accounts ──────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
describe('listAccounts', () => {
|
|
103
|
+
it('should return account list', async () => {
|
|
104
|
+
const mockAccounts = {
|
|
105
|
+
accounts: [
|
|
106
|
+
{ account_id: 'acc_1', name: 'Cash', account_number: '1000' },
|
|
107
|
+
{ account_id: 'acc_2', name: 'Revenue', account_number: '4000' },
|
|
108
|
+
],
|
|
109
|
+
total_count: 2,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockAccounts))
|
|
113
|
+
|
|
114
|
+
const result = await client.listAccounts('graph_1')
|
|
115
|
+
|
|
116
|
+
expect(result).toEqual(mockAccounts)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should throw on error', async () => {
|
|
120
|
+
mockFetch.mockResolvedValueOnce(
|
|
121
|
+
createMockResponse({ detail: 'Forbidden' }, { ok: false, status: 403 })
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
await expect(client.listAccounts('graph_1')).rejects.toThrow('List accounts failed')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('getAccountTree', () => {
|
|
129
|
+
it('should return hierarchical account tree', async () => {
|
|
130
|
+
const mockTree = {
|
|
131
|
+
root: {
|
|
132
|
+
account_id: 'root',
|
|
133
|
+
name: 'All Accounts',
|
|
134
|
+
children: [
|
|
135
|
+
{ account_id: 'acc_1', name: 'Assets', children: [] },
|
|
136
|
+
{ account_id: 'acc_2', name: 'Liabilities', children: [] },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockTree))
|
|
142
|
+
|
|
143
|
+
const result = await client.getAccountTree('graph_1')
|
|
144
|
+
|
|
145
|
+
expect(result).toEqual(mockTree)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should throw on error', async () => {
|
|
149
|
+
mockFetch.mockResolvedValueOnce(
|
|
150
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
await expect(client.getAccountTree('graph_1')).rejects.toThrow('Get account tree failed')
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// ── Transactions ──────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
describe('listTransactions', () => {
|
|
160
|
+
it('should return transactions', async () => {
|
|
161
|
+
const mockTxns = {
|
|
162
|
+
transactions: [
|
|
163
|
+
{ transaction_id: 'txn_1', date: '2025-01-15', description: 'Payment received' },
|
|
164
|
+
],
|
|
165
|
+
total_count: 1,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockTxns))
|
|
169
|
+
|
|
170
|
+
const result = await client.listTransactions('graph_1')
|
|
171
|
+
|
|
172
|
+
expect(result).toEqual(mockTxns)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should accept date filters and pagination options', async () => {
|
|
176
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ transactions: [], total_count: 0 }))
|
|
177
|
+
|
|
178
|
+
const result = await client.listTransactions('graph_1', {
|
|
179
|
+
startDate: '2025-01-01',
|
|
180
|
+
endDate: '2025-03-31',
|
|
181
|
+
limit: 50,
|
|
182
|
+
offset: 10,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
186
|
+
expect(result).toEqual({ transactions: [], total_count: 0 })
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('should throw on error', async () => {
|
|
190
|
+
mockFetch.mockResolvedValueOnce(
|
|
191
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
await expect(client.listTransactions('graph_1')).rejects.toThrow('List transactions failed')
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
describe('getTransaction', () => {
|
|
199
|
+
it('should return transaction detail', async () => {
|
|
200
|
+
const mockDetail = {
|
|
201
|
+
transaction_id: 'txn_1',
|
|
202
|
+
date: '2025-01-15',
|
|
203
|
+
entries: [{ debit: 1000, credit: 0, account_id: 'acc_1' }],
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockDetail))
|
|
207
|
+
|
|
208
|
+
const result = await client.getTransaction('graph_1', 'txn_1')
|
|
209
|
+
|
|
210
|
+
expect(result).toEqual(mockDetail)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('should throw on error', async () => {
|
|
214
|
+
mockFetch.mockResolvedValueOnce(
|
|
215
|
+
createMockResponse({ detail: 'Not found' }, { ok: false, status: 404 })
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
await expect(client.getTransaction('graph_1', 'txn_bad')).rejects.toThrow(
|
|
219
|
+
'Get transaction failed'
|
|
220
|
+
)
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// ── Trial Balance ─────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
describe('getTrialBalance', () => {
|
|
227
|
+
it('should return trial balance', async () => {
|
|
228
|
+
const mockTB = {
|
|
229
|
+
rows: [
|
|
230
|
+
{ account_id: 'acc_1', debit: 50000, credit: 0 },
|
|
231
|
+
{ account_id: 'acc_2', debit: 0, credit: 50000 },
|
|
232
|
+
],
|
|
233
|
+
total_debit: 50000,
|
|
234
|
+
total_credit: 50000,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockTB))
|
|
238
|
+
|
|
239
|
+
const result = await client.getTrialBalance('graph_1')
|
|
240
|
+
|
|
241
|
+
expect(result).toEqual(mockTB)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('should accept date range options', async () => {
|
|
245
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ rows: [] }))
|
|
246
|
+
|
|
247
|
+
const result = await client.getTrialBalance('graph_1', {
|
|
248
|
+
startDate: '2025-01-01',
|
|
249
|
+
endDate: '2025-03-31',
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
253
|
+
expect(result).toEqual({ rows: [] })
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('should throw on error', async () => {
|
|
257
|
+
mockFetch.mockResolvedValueOnce(
|
|
258
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
await expect(client.getTrialBalance('graph_1')).rejects.toThrow('Get trial balance failed')
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('getMappedTrialBalance', () => {
|
|
266
|
+
it('should return mapped trial balance', async () => {
|
|
267
|
+
const mockMapped = {
|
|
268
|
+
rows: [{ concept: 'Revenue', amount: 100000 }],
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockMapped))
|
|
272
|
+
|
|
273
|
+
const result = await client.getMappedTrialBalance('graph_1', {
|
|
274
|
+
mappingId: 'map_1',
|
|
275
|
+
startDate: '2025-01-01',
|
|
276
|
+
endDate: '2025-03-31',
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
expect(result).toEqual(mockMapped)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('should throw on error', async () => {
|
|
283
|
+
mockFetch.mockResolvedValueOnce(
|
|
284
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
await expect(client.getMappedTrialBalance('graph_1')).rejects.toThrow(
|
|
288
|
+
'Get mapped trial balance failed'
|
|
289
|
+
)
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// ── Summary ───────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
describe('getSummary', () => {
|
|
296
|
+
it('should return ledger summary', async () => {
|
|
297
|
+
const mockSummary = {
|
|
298
|
+
account_count: 150,
|
|
299
|
+
transaction_count: 5000,
|
|
300
|
+
earliest_date: '2024-01-01',
|
|
301
|
+
latest_date: '2025-03-31',
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockSummary))
|
|
305
|
+
|
|
306
|
+
const result = await client.getSummary('graph_1')
|
|
307
|
+
|
|
308
|
+
expect(result).toEqual(mockSummary)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('should throw on error', async () => {
|
|
312
|
+
mockFetch.mockResolvedValueOnce(
|
|
313
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
await expect(client.getSummary('graph_1')).rejects.toThrow('Get summary failed')
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// ── Taxonomy ──────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
describe('getReportingTaxonomy', () => {
|
|
323
|
+
it('should return taxonomy data', async () => {
|
|
324
|
+
const mockTax = {
|
|
325
|
+
taxonomy_id: 'tax_usgaap',
|
|
326
|
+
name: 'US GAAP Reporting',
|
|
327
|
+
elements: [{ id: 'elem_1', name: 'Revenue' }],
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockTax))
|
|
331
|
+
|
|
332
|
+
const result = await client.getReportingTaxonomy('graph_1')
|
|
333
|
+
|
|
334
|
+
expect(result).toEqual(mockTax)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('should throw on error', async () => {
|
|
338
|
+
mockFetch.mockResolvedValueOnce(
|
|
339
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
await expect(client.getReportingTaxonomy('graph_1')).rejects.toThrow(
|
|
343
|
+
'Get reporting taxonomy failed'
|
|
344
|
+
)
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
describe('listStructures', () => {
|
|
349
|
+
it('should return transformed structures', async () => {
|
|
350
|
+
mockFetch.mockResolvedValueOnce(
|
|
351
|
+
createMockResponse({
|
|
352
|
+
structures: [
|
|
353
|
+
{ id: 'str_1', name: 'Income Statement', structure_type: 'income_statement' },
|
|
354
|
+
{ id: 'str_2', name: 'Balance Sheet', structure_type: 'balance_sheet' },
|
|
355
|
+
],
|
|
356
|
+
})
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
const result = await client.listStructures('graph_1')
|
|
360
|
+
|
|
361
|
+
expect(result).toHaveLength(2)
|
|
362
|
+
expect(result[0]).toEqual({
|
|
363
|
+
id: 'str_1',
|
|
364
|
+
name: 'Income Statement',
|
|
365
|
+
structureType: 'income_statement',
|
|
366
|
+
})
|
|
367
|
+
expect(result[1]).toEqual({
|
|
368
|
+
id: 'str_2',
|
|
369
|
+
name: 'Balance Sheet',
|
|
370
|
+
structureType: 'balance_sheet',
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('should return empty array when no structures', async () => {
|
|
375
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ structures: [] }))
|
|
376
|
+
|
|
377
|
+
const result = await client.listStructures('graph_1')
|
|
378
|
+
|
|
379
|
+
expect(result).toEqual([])
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
it('should handle missing structures key', async () => {
|
|
383
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({}))
|
|
384
|
+
|
|
385
|
+
const result = await client.listStructures('graph_1')
|
|
386
|
+
|
|
387
|
+
expect(result).toEqual([])
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('should accept taxonomyId filter', async () => {
|
|
391
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ structures: [] }))
|
|
392
|
+
|
|
393
|
+
const result = await client.listStructures('graph_1', 'tax_usgaap')
|
|
394
|
+
|
|
395
|
+
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
396
|
+
expect(result).toEqual([])
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('should throw on error', async () => {
|
|
400
|
+
mockFetch.mockResolvedValueOnce(
|
|
401
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
await expect(client.listStructures('graph_1')).rejects.toThrow('List structures failed')
|
|
405
|
+
})
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
describe('listElements', () => {
|
|
409
|
+
it('should return elements data', async () => {
|
|
410
|
+
const mockElements = {
|
|
411
|
+
elements: [{ id: 'elem_1', qname: 'us-gaap:Revenue', name: 'Revenue' }],
|
|
412
|
+
total_count: 1,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockElements))
|
|
416
|
+
|
|
417
|
+
const result = await client.listElements('graph_1', {
|
|
418
|
+
taxonomyId: 'tax_usgaap',
|
|
419
|
+
source: 'gaap',
|
|
420
|
+
classification: 'revenue',
|
|
421
|
+
isAbstract: false,
|
|
422
|
+
limit: 100,
|
|
423
|
+
offset: 0,
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
expect(result).toEqual(mockElements)
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('should throw on error', async () => {
|
|
430
|
+
mockFetch.mockResolvedValueOnce(
|
|
431
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
await expect(client.listElements('graph_1')).rejects.toThrow('List elements failed')
|
|
435
|
+
})
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// ── Mappings ──────────────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
describe('createMappingStructure', () => {
|
|
441
|
+
it('should create and return mapping info', async () => {
|
|
442
|
+
mockFetch.mockResolvedValueOnce(
|
|
443
|
+
createMockResponse({
|
|
444
|
+
id: 'map_1',
|
|
445
|
+
name: 'My Mapping',
|
|
446
|
+
description: 'Custom mapping',
|
|
447
|
+
structure_type: 'coa_mapping',
|
|
448
|
+
taxonomy_id: 'tax_usgaap_reporting',
|
|
449
|
+
is_active: true,
|
|
450
|
+
})
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
const result = await client.createMappingStructure('graph_1', {
|
|
454
|
+
name: 'My Mapping',
|
|
455
|
+
description: 'Custom mapping',
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
expect(result).toEqual({
|
|
459
|
+
id: 'map_1',
|
|
460
|
+
name: 'My Mapping',
|
|
461
|
+
description: 'Custom mapping',
|
|
462
|
+
structureType: 'coa_mapping',
|
|
463
|
+
taxonomyId: 'tax_usgaap_reporting',
|
|
464
|
+
isActive: true,
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('should use defaults when no options provided', async () => {
|
|
469
|
+
mockFetch.mockResolvedValueOnce(
|
|
470
|
+
createMockResponse({
|
|
471
|
+
id: 'map_2',
|
|
472
|
+
name: 'CoA to Reporting',
|
|
473
|
+
description: 'Map Chart of Accounts to US GAAP reporting concepts',
|
|
474
|
+
structure_type: 'coa_mapping',
|
|
475
|
+
taxonomy_id: 'tax_usgaap_reporting',
|
|
476
|
+
is_active: true,
|
|
477
|
+
})
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
const result = await client.createMappingStructure('graph_1')
|
|
481
|
+
|
|
482
|
+
expect(result.name).toBe('CoA to Reporting')
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it('should handle null description', async () => {
|
|
486
|
+
mockFetch.mockResolvedValueOnce(
|
|
487
|
+
createMockResponse({
|
|
488
|
+
id: 'map_3',
|
|
489
|
+
name: 'Mapping',
|
|
490
|
+
structure_type: 'coa_mapping',
|
|
491
|
+
taxonomy_id: 'tax_usgaap_reporting',
|
|
492
|
+
})
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
const result = await client.createMappingStructure('graph_1')
|
|
496
|
+
|
|
497
|
+
expect(result.description).toBeNull()
|
|
498
|
+
expect(result.isActive).toBe(true)
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('should throw on error', async () => {
|
|
502
|
+
mockFetch.mockResolvedValueOnce(
|
|
503
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
await expect(client.createMappingStructure('graph_1')).rejects.toThrow(
|
|
507
|
+
'Create mapping structure failed'
|
|
508
|
+
)
|
|
509
|
+
})
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
describe('listMappings', () => {
|
|
513
|
+
it('should return transformed mapping list', async () => {
|
|
514
|
+
mockFetch.mockResolvedValueOnce(
|
|
515
|
+
createMockResponse({
|
|
516
|
+
structures: [
|
|
517
|
+
{
|
|
518
|
+
id: 'map_1',
|
|
519
|
+
name: 'Primary Mapping',
|
|
520
|
+
description: 'Main mapping',
|
|
521
|
+
structure_type: 'coa_mapping',
|
|
522
|
+
taxonomy_id: 'tax_usgaap',
|
|
523
|
+
is_active: true,
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
})
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
const result = await client.listMappings('graph_1')
|
|
530
|
+
|
|
531
|
+
expect(result).toHaveLength(1)
|
|
532
|
+
expect(result[0]).toEqual({
|
|
533
|
+
id: 'map_1',
|
|
534
|
+
name: 'Primary Mapping',
|
|
535
|
+
description: 'Main mapping',
|
|
536
|
+
structureType: 'coa_mapping',
|
|
537
|
+
taxonomyId: 'tax_usgaap',
|
|
538
|
+
isActive: true,
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
it('should return empty array when no mappings', async () => {
|
|
543
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ structures: [] }))
|
|
544
|
+
|
|
545
|
+
const result = await client.listMappings('graph_1')
|
|
546
|
+
|
|
547
|
+
expect(result).toEqual([])
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
it('should throw on error', async () => {
|
|
551
|
+
mockFetch.mockResolvedValueOnce(
|
|
552
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
await expect(client.listMappings('graph_1')).rejects.toThrow('List mappings failed')
|
|
556
|
+
})
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
describe('getMappingDetail', () => {
|
|
560
|
+
it('should return mapping detail', async () => {
|
|
561
|
+
const mockDetail = {
|
|
562
|
+
mapping_id: 'map_1',
|
|
563
|
+
associations: [{ from_element_id: 'coa_1', to_element_id: 'gaap_1', confidence: 0.95 }],
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockDetail))
|
|
567
|
+
|
|
568
|
+
const result = await client.getMappingDetail('graph_1', 'map_1')
|
|
569
|
+
|
|
570
|
+
expect(result).toEqual(mockDetail)
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
it('should throw on error', async () => {
|
|
574
|
+
mockFetch.mockResolvedValueOnce(
|
|
575
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
await expect(client.getMappingDetail('graph_1', 'map_1')).rejects.toThrow(
|
|
579
|
+
'Get mapping detail failed'
|
|
580
|
+
)
|
|
581
|
+
})
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
describe('getMappingCoverage', () => {
|
|
585
|
+
it('should return transformed coverage data', async () => {
|
|
586
|
+
mockFetch.mockResolvedValueOnce(
|
|
587
|
+
createMockResponse({
|
|
588
|
+
total_coa_elements: 150,
|
|
589
|
+
mapped_count: 120,
|
|
590
|
+
unmapped_count: 30,
|
|
591
|
+
coverage_percent: 80.0,
|
|
592
|
+
high_confidence: 100,
|
|
593
|
+
medium_confidence: 15,
|
|
594
|
+
low_confidence: 5,
|
|
595
|
+
})
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
const result = await client.getMappingCoverage('graph_1', 'map_1')
|
|
599
|
+
|
|
600
|
+
expect(result).toEqual({
|
|
601
|
+
totalCoaElements: 150,
|
|
602
|
+
mappedCount: 120,
|
|
603
|
+
unmappedCount: 30,
|
|
604
|
+
coveragePercent: 80.0,
|
|
605
|
+
highConfidence: 100,
|
|
606
|
+
mediumConfidence: 15,
|
|
607
|
+
lowConfidence: 5,
|
|
608
|
+
})
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
it('should default missing fields to 0', async () => {
|
|
612
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({}))
|
|
613
|
+
|
|
614
|
+
const result = await client.getMappingCoverage('graph_1', 'map_1')
|
|
615
|
+
|
|
616
|
+
expect(result.totalCoaElements).toBe(0)
|
|
617
|
+
expect(result.mappedCount).toBe(0)
|
|
618
|
+
expect(result.unmappedCount).toBe(0)
|
|
619
|
+
expect(result.coveragePercent).toBe(0)
|
|
620
|
+
expect(result.highConfidence).toBe(0)
|
|
621
|
+
expect(result.mediumConfidence).toBe(0)
|
|
622
|
+
expect(result.lowConfidence).toBe(0)
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
it('should throw on error', async () => {
|
|
626
|
+
mockFetch.mockResolvedValueOnce(
|
|
627
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
await expect(client.getMappingCoverage('graph_1', 'map_1')).rejects.toThrow(
|
|
631
|
+
'Get mapping coverage failed'
|
|
632
|
+
)
|
|
633
|
+
})
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
describe('createMapping', () => {
|
|
637
|
+
it('should create a mapping association', async () => {
|
|
638
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
639
|
+
|
|
640
|
+
await expect(
|
|
641
|
+
client.createMapping('graph_1', 'map_1', 'from_elem', 'to_elem', 0.9)
|
|
642
|
+
).resolves.toBeUndefined()
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
it('should accept call without explicit confidence', async () => {
|
|
646
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
647
|
+
|
|
648
|
+
await expect(
|
|
649
|
+
client.createMapping('graph_1', 'map_1', 'from_elem', 'to_elem')
|
|
650
|
+
).resolves.toBeUndefined()
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
it('should throw on error', async () => {
|
|
654
|
+
mockFetch.mockResolvedValueOnce(
|
|
655
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
await expect(client.createMapping('graph_1', 'map_1', 'from', 'to')).rejects.toThrow(
|
|
659
|
+
'Create mapping failed'
|
|
660
|
+
)
|
|
661
|
+
})
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
describe('deleteMapping', () => {
|
|
665
|
+
it('should delete a mapping association', async () => {
|
|
666
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
667
|
+
|
|
668
|
+
await expect(client.deleteMapping('graph_1', 'map_1', 'assoc_1')).resolves.toBeUndefined()
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
it('should throw on error', async () => {
|
|
672
|
+
mockFetch.mockResolvedValueOnce(
|
|
673
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
await expect(client.deleteMapping('graph_1', 'map_1', 'assoc_1')).rejects.toThrow(
|
|
677
|
+
'Delete mapping failed'
|
|
678
|
+
)
|
|
679
|
+
})
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
describe('autoMap', () => {
|
|
683
|
+
it('should return operation ID', async () => {
|
|
684
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ operation_id: 'op_automap_1' }))
|
|
685
|
+
|
|
686
|
+
const result = await client.autoMap('graph_1', 'map_1')
|
|
687
|
+
|
|
688
|
+
expect(result.operationId).toBe('op_automap_1')
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
it('should handle response with no operation_id', async () => {
|
|
692
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({}))
|
|
693
|
+
|
|
694
|
+
const result = await client.autoMap('graph_1', 'map_1')
|
|
695
|
+
|
|
696
|
+
expect(result.operationId).toBeUndefined()
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
it('should throw on error', async () => {
|
|
700
|
+
mockFetch.mockResolvedValueOnce(
|
|
701
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
await expect(client.autoMap('graph_1', 'map_1')).rejects.toThrow('Auto-map failed')
|
|
705
|
+
})
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
// ── Schedules ─────────────────────────────────────────────────────────
|
|
709
|
+
|
|
710
|
+
describe('createSchedule', () => {
|
|
711
|
+
const scheduleOptions = {
|
|
712
|
+
name: 'Depreciation - Equipment',
|
|
713
|
+
elementIds: ['elem_1', 'elem_2'],
|
|
714
|
+
periodStart: '2025-01-01',
|
|
715
|
+
periodEnd: '2025-12-31',
|
|
716
|
+
monthlyAmount: 5000,
|
|
717
|
+
entryTemplate: {
|
|
718
|
+
debitElementId: 'debit_elem',
|
|
719
|
+
creditElementId: 'credit_elem',
|
|
720
|
+
entryType: 'adjusting',
|
|
721
|
+
memoTemplate: 'Monthly depreciation',
|
|
722
|
+
},
|
|
723
|
+
taxonomyId: 'tax_usgaap',
|
|
724
|
+
scheduleMetadata: {
|
|
725
|
+
method: 'straight_line',
|
|
726
|
+
originalAmount: 60000,
|
|
727
|
+
residualValue: 0,
|
|
728
|
+
usefulLifeMonths: 12,
|
|
729
|
+
assetElementId: 'asset_elem',
|
|
730
|
+
},
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
it('should create schedule and return transformed result', async () => {
|
|
734
|
+
mockFetch.mockResolvedValueOnce(
|
|
735
|
+
createMockResponse({
|
|
736
|
+
structure_id: 'sched_1',
|
|
737
|
+
name: 'Depreciation - Equipment',
|
|
738
|
+
taxonomy_id: 'tax_usgaap',
|
|
739
|
+
total_periods: 12,
|
|
740
|
+
total_facts: 24,
|
|
741
|
+
})
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
const result = await client.createSchedule('graph_1', scheduleOptions)
|
|
745
|
+
|
|
746
|
+
expect(result).toEqual({
|
|
747
|
+
structureId: 'sched_1',
|
|
748
|
+
name: 'Depreciation - Equipment',
|
|
749
|
+
taxonomyId: 'tax_usgaap',
|
|
750
|
+
totalPeriods: 12,
|
|
751
|
+
totalFacts: 24,
|
|
752
|
+
})
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
it('should handle schedule without metadata', async () => {
|
|
756
|
+
mockFetch.mockResolvedValueOnce(
|
|
757
|
+
createMockResponse({
|
|
758
|
+
structure_id: 'sched_2',
|
|
759
|
+
name: 'Simple',
|
|
760
|
+
taxonomy_id: 'tax',
|
|
761
|
+
total_periods: 6,
|
|
762
|
+
total_facts: 6,
|
|
763
|
+
})
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
const simpleOptions = {
|
|
767
|
+
name: 'Simple',
|
|
768
|
+
elementIds: ['elem_1'],
|
|
769
|
+
periodStart: '2025-01-01',
|
|
770
|
+
periodEnd: '2025-06-30',
|
|
771
|
+
monthlyAmount: 1000,
|
|
772
|
+
entryTemplate: {
|
|
773
|
+
debitElementId: 'debit',
|
|
774
|
+
creditElementId: 'credit',
|
|
775
|
+
},
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const result = await client.createSchedule('graph_1', simpleOptions)
|
|
779
|
+
|
|
780
|
+
expect(result.structureId).toBe('sched_2')
|
|
781
|
+
expect(result.totalPeriods).toBe(6)
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
it('should throw on error', async () => {
|
|
785
|
+
mockFetch.mockResolvedValueOnce(
|
|
786
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
await expect(client.createSchedule('graph_1', scheduleOptions)).rejects.toThrow(
|
|
790
|
+
'Create schedule failed'
|
|
791
|
+
)
|
|
792
|
+
})
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
describe('listSchedules', () => {
|
|
796
|
+
it('should return transformed schedule list', async () => {
|
|
797
|
+
mockFetch.mockResolvedValueOnce(
|
|
798
|
+
createMockResponse({
|
|
799
|
+
schedules: [
|
|
800
|
+
{
|
|
801
|
+
structure_id: 'sched_1',
|
|
802
|
+
name: 'Depreciation',
|
|
803
|
+
taxonomy_name: 'US GAAP',
|
|
804
|
+
entry_template: { debit_element_id: 'd', credit_element_id: 'c' },
|
|
805
|
+
schedule_metadata: { method: 'straight_line' },
|
|
806
|
+
total_periods: 12,
|
|
807
|
+
periods_with_entries: 6,
|
|
808
|
+
},
|
|
809
|
+
],
|
|
810
|
+
})
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
const result = await client.listSchedules('graph_1')
|
|
814
|
+
|
|
815
|
+
expect(result).toHaveLength(1)
|
|
816
|
+
expect(result[0]).toEqual({
|
|
817
|
+
structureId: 'sched_1',
|
|
818
|
+
name: 'Depreciation',
|
|
819
|
+
taxonomyName: 'US GAAP',
|
|
820
|
+
entryTemplate: { debit_element_id: 'd', credit_element_id: 'c' },
|
|
821
|
+
scheduleMetadata: { method: 'straight_line' },
|
|
822
|
+
totalPeriods: 12,
|
|
823
|
+
periodsWithEntries: 6,
|
|
824
|
+
})
|
|
825
|
+
})
|
|
826
|
+
|
|
827
|
+
it('should handle null entry_template and schedule_metadata', async () => {
|
|
828
|
+
mockFetch.mockResolvedValueOnce(
|
|
829
|
+
createMockResponse({
|
|
830
|
+
schedules: [
|
|
831
|
+
{
|
|
832
|
+
structure_id: 'sched_1',
|
|
833
|
+
name: 'Bare',
|
|
834
|
+
taxonomy_name: 'GAAP',
|
|
835
|
+
total_periods: 1,
|
|
836
|
+
periods_with_entries: 0,
|
|
837
|
+
},
|
|
838
|
+
],
|
|
839
|
+
})
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
const result = await client.listSchedules('graph_1')
|
|
843
|
+
|
|
844
|
+
expect(result[0].entryTemplate).toBeNull()
|
|
845
|
+
expect(result[0].scheduleMetadata).toBeNull()
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
it('should return empty array when no schedules', async () => {
|
|
849
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ schedules: [] }))
|
|
850
|
+
|
|
851
|
+
const result = await client.listSchedules('graph_1')
|
|
852
|
+
|
|
853
|
+
expect(result).toEqual([])
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
it('should throw on error', async () => {
|
|
857
|
+
mockFetch.mockResolvedValueOnce(
|
|
858
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
await expect(client.listSchedules('graph_1')).rejects.toThrow('List schedules failed')
|
|
862
|
+
})
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
describe('getScheduleFacts', () => {
|
|
866
|
+
it('should return transformed facts', async () => {
|
|
867
|
+
mockFetch.mockResolvedValueOnce(
|
|
868
|
+
createMockResponse({
|
|
869
|
+
facts: [
|
|
870
|
+
{
|
|
871
|
+
element_id: 'elem_1',
|
|
872
|
+
element_name: 'Depreciation',
|
|
873
|
+
value: 5000,
|
|
874
|
+
period_start: '2025-01-01',
|
|
875
|
+
period_end: '2025-01-31',
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
element_id: 'elem_1',
|
|
879
|
+
element_name: 'Depreciation',
|
|
880
|
+
value: 5000,
|
|
881
|
+
period_start: '2025-02-01',
|
|
882
|
+
period_end: '2025-02-28',
|
|
883
|
+
},
|
|
884
|
+
],
|
|
885
|
+
})
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
const result = await client.getScheduleFacts('graph_1', 'sched_1')
|
|
889
|
+
|
|
890
|
+
expect(result).toHaveLength(2)
|
|
891
|
+
expect(result[0]).toEqual({
|
|
892
|
+
elementId: 'elem_1',
|
|
893
|
+
elementName: 'Depreciation',
|
|
894
|
+
value: 5000,
|
|
895
|
+
periodStart: '2025-01-01',
|
|
896
|
+
periodEnd: '2025-01-31',
|
|
897
|
+
})
|
|
898
|
+
})
|
|
899
|
+
|
|
900
|
+
it('should accept period filters', async () => {
|
|
901
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ facts: [] }))
|
|
902
|
+
|
|
903
|
+
const result = await client.getScheduleFacts('graph_1', 'sched_1', '2025-01-01', '2025-06-30')
|
|
904
|
+
|
|
905
|
+
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
906
|
+
expect(result).toEqual([])
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
it('should throw on error', async () => {
|
|
910
|
+
mockFetch.mockResolvedValueOnce(
|
|
911
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
await expect(client.getScheduleFacts('graph_1', 'sched_1')).rejects.toThrow(
|
|
915
|
+
'Get schedule facts failed'
|
|
916
|
+
)
|
|
917
|
+
})
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
// ── Period Close ──────────────────────────────────────────────────────
|
|
921
|
+
|
|
922
|
+
describe('getPeriodCloseStatus', () => {
|
|
923
|
+
it('should return transformed close status', async () => {
|
|
924
|
+
mockFetch.mockResolvedValueOnce(
|
|
925
|
+
createMockResponse({
|
|
926
|
+
fiscal_period_start: '2025-01-01',
|
|
927
|
+
fiscal_period_end: '2025-03-31',
|
|
928
|
+
period_status: 'open',
|
|
929
|
+
schedules: [
|
|
930
|
+
{
|
|
931
|
+
structure_id: 'sched_1',
|
|
932
|
+
structure_name: 'Depreciation',
|
|
933
|
+
amount: 15000,
|
|
934
|
+
status: 'draft',
|
|
935
|
+
entry_id: 'entry_1',
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
structure_id: 'sched_2',
|
|
939
|
+
structure_name: 'Amortization',
|
|
940
|
+
amount: 3000,
|
|
941
|
+
status: 'posted',
|
|
942
|
+
},
|
|
943
|
+
],
|
|
944
|
+
total_draft: 1,
|
|
945
|
+
total_posted: 1,
|
|
946
|
+
})
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
const result = await client.getPeriodCloseStatus('graph_1', '2025-01-01', '2025-03-31')
|
|
950
|
+
|
|
951
|
+
expect(result.fiscalPeriodStart).toBe('2025-01-01')
|
|
952
|
+
expect(result.fiscalPeriodEnd).toBe('2025-03-31')
|
|
953
|
+
expect(result.periodStatus).toBe('open')
|
|
954
|
+
expect(result.totalDraft).toBe(1)
|
|
955
|
+
expect(result.totalPosted).toBe(1)
|
|
956
|
+
expect(result.schedules).toHaveLength(2)
|
|
957
|
+
expect(result.schedules[0]).toEqual({
|
|
958
|
+
structureId: 'sched_1',
|
|
959
|
+
structureName: 'Depreciation',
|
|
960
|
+
amount: 15000,
|
|
961
|
+
status: 'draft',
|
|
962
|
+
entryId: 'entry_1',
|
|
963
|
+
})
|
|
964
|
+
expect(result.schedules[1].entryId).toBeNull()
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
it('should throw on error', async () => {
|
|
968
|
+
mockFetch.mockResolvedValueOnce(
|
|
969
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
await expect(
|
|
973
|
+
client.getPeriodCloseStatus('graph_1', '2025-01-01', '2025-03-31')
|
|
974
|
+
).rejects.toThrow('Get period close status failed')
|
|
975
|
+
})
|
|
976
|
+
})
|
|
977
|
+
|
|
978
|
+
describe('createClosingEntry', () => {
|
|
979
|
+
it('should create and return closing entry', async () => {
|
|
980
|
+
mockFetch.mockResolvedValueOnce(
|
|
981
|
+
createMockResponse({
|
|
982
|
+
entry_id: 'entry_1',
|
|
983
|
+
status: 'draft',
|
|
984
|
+
posting_date: '2025-03-31',
|
|
985
|
+
memo: 'Q1 depreciation',
|
|
986
|
+
debit_element_id: 'debit_elem',
|
|
987
|
+
credit_element_id: 'credit_elem',
|
|
988
|
+
amount: 15000,
|
|
989
|
+
})
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
const result = await client.createClosingEntry(
|
|
993
|
+
'graph_1',
|
|
994
|
+
'sched_1',
|
|
995
|
+
'2025-03-31',
|
|
996
|
+
'2025-01-01',
|
|
997
|
+
'2025-03-31',
|
|
998
|
+
'Q1 depreciation'
|
|
999
|
+
)
|
|
1000
|
+
|
|
1001
|
+
expect(result).toEqual({
|
|
1002
|
+
entryId: 'entry_1',
|
|
1003
|
+
status: 'draft',
|
|
1004
|
+
postingDate: '2025-03-31',
|
|
1005
|
+
memo: 'Q1 depreciation',
|
|
1006
|
+
debitElementId: 'debit_elem',
|
|
1007
|
+
creditElementId: 'credit_elem',
|
|
1008
|
+
amount: 15000,
|
|
1009
|
+
})
|
|
1010
|
+
})
|
|
1011
|
+
|
|
1012
|
+
it('should work without memo', async () => {
|
|
1013
|
+
mockFetch.mockResolvedValueOnce(
|
|
1014
|
+
createMockResponse({
|
|
1015
|
+
entry_id: 'entry_2',
|
|
1016
|
+
status: 'draft',
|
|
1017
|
+
posting_date: '2025-03-31',
|
|
1018
|
+
memo: '',
|
|
1019
|
+
debit_element_id: 'd',
|
|
1020
|
+
credit_element_id: 'c',
|
|
1021
|
+
amount: 1000,
|
|
1022
|
+
})
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
const result = await client.createClosingEntry(
|
|
1026
|
+
'graph_1',
|
|
1027
|
+
'sched_1',
|
|
1028
|
+
'2025-03-31',
|
|
1029
|
+
'2025-01-01',
|
|
1030
|
+
'2025-03-31'
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
expect(result.entryId).toBe('entry_2')
|
|
1034
|
+
expect(result.amount).toBe(1000)
|
|
1035
|
+
})
|
|
1036
|
+
|
|
1037
|
+
it('should throw on error', async () => {
|
|
1038
|
+
mockFetch.mockResolvedValueOnce(
|
|
1039
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
await expect(
|
|
1043
|
+
client.createClosingEntry('graph_1', 'sched_1', '2025-03-31', '2025-01-01', '2025-03-31')
|
|
1044
|
+
).rejects.toThrow('Create closing entry failed')
|
|
1045
|
+
})
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
// ── Closing Book ──────────────────────────────────────────────────────
|
|
1049
|
+
|
|
1050
|
+
describe('getClosingBookStructures', () => {
|
|
1051
|
+
it('should return closing book structures', async () => {
|
|
1052
|
+
const mockData = {
|
|
1053
|
+
categories: [
|
|
1054
|
+
{ name: 'Statements', structures: [{ id: 'str_1', name: 'Income Statement' }] },
|
|
1055
|
+
{ name: 'Schedules', structures: [{ id: 'sched_1', name: 'Depreciation' }] },
|
|
1056
|
+
],
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockData))
|
|
1060
|
+
|
|
1061
|
+
const result = await client.getClosingBookStructures('graph_1')
|
|
1062
|
+
|
|
1063
|
+
expect(result).toEqual(mockData)
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
it('should throw on error', async () => {
|
|
1067
|
+
mockFetch.mockResolvedValueOnce(
|
|
1068
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
await expect(client.getClosingBookStructures('graph_1')).rejects.toThrow(
|
|
1072
|
+
'Get closing book structures failed'
|
|
1073
|
+
)
|
|
1074
|
+
})
|
|
1075
|
+
})
|
|
1076
|
+
|
|
1077
|
+
describe('getAccountRollups', () => {
|
|
1078
|
+
it('should return account rollups data', async () => {
|
|
1079
|
+
const mockRollups = {
|
|
1080
|
+
rollups: [
|
|
1081
|
+
{
|
|
1082
|
+
element_id: 'gaap_revenue',
|
|
1083
|
+
element_name: 'Revenue',
|
|
1084
|
+
accounts: [{ account_id: 'acc_1', name: 'Sales Revenue', balance: 100000 }],
|
|
1085
|
+
total: 100000,
|
|
1086
|
+
},
|
|
1087
|
+
],
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
mockFetch.mockResolvedValueOnce(createMockResponse(mockRollups))
|
|
1091
|
+
|
|
1092
|
+
const result = await client.getAccountRollups('graph_1', {
|
|
1093
|
+
mappingId: 'map_1',
|
|
1094
|
+
startDate: '2025-01-01',
|
|
1095
|
+
endDate: '2025-03-31',
|
|
1096
|
+
})
|
|
1097
|
+
|
|
1098
|
+
expect(result).toEqual(mockRollups)
|
|
1099
|
+
})
|
|
1100
|
+
|
|
1101
|
+
it('should work without options', async () => {
|
|
1102
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ rollups: [] }))
|
|
1103
|
+
|
|
1104
|
+
const result = await client.getAccountRollups('graph_1')
|
|
1105
|
+
|
|
1106
|
+
expect(result).toEqual({ rollups: [] })
|
|
1107
|
+
})
|
|
1108
|
+
|
|
1109
|
+
it('should throw on error', async () => {
|
|
1110
|
+
mockFetch.mockResolvedValueOnce(
|
|
1111
|
+
createMockResponse({ detail: 'Error' }, { ok: false, status: 500 })
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
await expect(client.getAccountRollups('graph_1')).rejects.toThrow(
|
|
1115
|
+
'Get account rollups failed'
|
|
1116
|
+
)
|
|
1117
|
+
})
|
|
1118
|
+
})
|
|
1119
|
+
|
|
1120
|
+
// ── Constructor ───────────────────────────────────────────────────────
|
|
1121
|
+
|
|
1122
|
+
describe('constructor', () => {
|
|
1123
|
+
it('should create with minimal config', () => {
|
|
1124
|
+
const c = new LedgerClient({ baseUrl: 'http://localhost:8000' })
|
|
1125
|
+
expect(c).toBeInstanceOf(LedgerClient)
|
|
1126
|
+
})
|
|
1127
|
+
|
|
1128
|
+
it('should accept all config options', () => {
|
|
1129
|
+
const c = new LedgerClient({
|
|
1130
|
+
baseUrl: 'http://localhost:8000',
|
|
1131
|
+
credentials: 'include',
|
|
1132
|
+
headers: { Authorization: 'Bearer token' },
|
|
1133
|
+
token: 'jwt-token',
|
|
1134
|
+
})
|
|
1135
|
+
expect(c).toBeInstanceOf(LedgerClient)
|
|
1136
|
+
})
|
|
1137
|
+
})
|
|
1138
|
+
})
|