@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
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
createSchedule,
|
|
16
16
|
createStructure,
|
|
17
17
|
deleteMappingAssociation,
|
|
18
|
+
getAccountRollups,
|
|
19
|
+
getClosingBookStructures,
|
|
18
20
|
getLedgerAccountTree,
|
|
19
21
|
getLedgerEntity,
|
|
20
22
|
getLedgerSummary,
|
|
@@ -35,7 +37,9 @@ import {
|
|
|
35
37
|
} from '../sdk.gen'
|
|
36
38
|
import type {
|
|
37
39
|
AccountListResponse,
|
|
40
|
+
AccountRollupsResponse,
|
|
38
41
|
AccountTreeResponse,
|
|
42
|
+
ClosingBookStructuresResponse,
|
|
39
43
|
ClosingEntryResponse,
|
|
40
44
|
CreateClosingEntryRequest,
|
|
41
45
|
CreateScheduleRequest,
|
|
@@ -750,4 +754,47 @@ export class LedgerClient {
|
|
|
750
754
|
amount: data.amount,
|
|
751
755
|
}
|
|
752
756
|
}
|
|
757
|
+
|
|
758
|
+
// ── Closing Book ─────────────────────────────────────────────────────
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Get all closing book structure categories for the sidebar.
|
|
762
|
+
* Returns statements, account rollups, schedules, trial balance,
|
|
763
|
+
* and period close grouped into categories.
|
|
764
|
+
*/
|
|
765
|
+
async getClosingBookStructures(graphId: string): Promise<ClosingBookStructuresResponse> {
|
|
766
|
+
const response = await getClosingBookStructures({
|
|
767
|
+
path: { graph_id: graphId },
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
if (response.error) {
|
|
771
|
+
throw new Error(`Get closing book structures failed: ${JSON.stringify(response.error)}`)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return response.data as ClosingBookStructuresResponse
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Get account rollups — CoA accounts grouped by reporting element with balances.
|
|
779
|
+
* Shows how company-specific accounts roll up to standardized reporting lines.
|
|
780
|
+
*/
|
|
781
|
+
async getAccountRollups(
|
|
782
|
+
graphId: string,
|
|
783
|
+
options?: { mappingId?: string; startDate?: string; endDate?: string }
|
|
784
|
+
): Promise<AccountRollupsResponse> {
|
|
785
|
+
const response = await getAccountRollups({
|
|
786
|
+
path: { graph_id: graphId },
|
|
787
|
+
query: {
|
|
788
|
+
mapping_id: options?.mappingId,
|
|
789
|
+
start_date: options?.startDate,
|
|
790
|
+
end_date: options?.endDate,
|
|
791
|
+
},
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
if (response.error) {
|
|
795
|
+
throw new Error(`Get account rollups failed: ${JSON.stringify(response.error)}`)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return response.data as AccountRollupsResponse
|
|
799
|
+
}
|
|
753
800
|
}
|
|
@@ -1,5 +1,60 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import { OperationClient } from './OperationClient'
|
|
3
|
+
import { EventType } from './SSEClient'
|
|
4
|
+
|
|
5
|
+
// Mock EventSource for SSE tests
|
|
6
|
+
class MockEventSource {
|
|
7
|
+
url: string
|
|
8
|
+
withCredentials: boolean
|
|
9
|
+
readyState: number = 0
|
|
10
|
+
onopen: ((event: any) => void) | null = null
|
|
11
|
+
onerror: ((event: any) => void) | null = null
|
|
12
|
+
onmessage: ((event: any) => void) | null = null
|
|
13
|
+
private eventListeners: Map<string, Set<(event: any) => void>> = new Map()
|
|
14
|
+
|
|
15
|
+
static CONNECTING = 0
|
|
16
|
+
static OPEN = 1
|
|
17
|
+
static CLOSED = 2
|
|
18
|
+
|
|
19
|
+
constructor(url: string, options?: { withCredentials?: boolean }) {
|
|
20
|
+
this.url = url
|
|
21
|
+
this.withCredentials = options?.withCredentials ?? false
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
this.readyState = MockEventSource.OPEN
|
|
24
|
+
if (this.onopen) {
|
|
25
|
+
this.onopen({ type: 'open' })
|
|
26
|
+
}
|
|
27
|
+
}, 0)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
addEventListener(event: string, listener: (event: any) => void) {
|
|
31
|
+
if (!this.eventListeners.has(event)) {
|
|
32
|
+
this.eventListeners.set(event, new Set())
|
|
33
|
+
}
|
|
34
|
+
this.eventListeners.get(event)!.add(listener)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
removeEventListener(event: string, listener: (event: any) => void) {
|
|
38
|
+
const listeners = this.eventListeners.get(event)
|
|
39
|
+
if (listeners) listeners.delete(listener)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
dispatchEvent(event: any) {
|
|
43
|
+
const listeners = this.eventListeners.get(event.type)
|
|
44
|
+
if (listeners) listeners.forEach((l) => l(event))
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
close() {
|
|
49
|
+
this.readyState = MockEventSource.CLOSED
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
simulateMessage(eventType: string, data: any) {
|
|
53
|
+
const event = { type: eventType, data: JSON.stringify(data), lastEventId: '' }
|
|
54
|
+
const listeners = this.eventListeners.get(eventType)
|
|
55
|
+
if (listeners) listeners.forEach((l) => l(event))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
3
58
|
|
|
4
59
|
// Helper to create proper mock Response objects
|
|
5
60
|
function createMockResponse(data: any, options: { ok?: boolean; status?: number } = {}) {
|
|
@@ -123,6 +178,149 @@ describe('OperationClient', () => {
|
|
|
123
178
|
})
|
|
124
179
|
})
|
|
125
180
|
|
|
181
|
+
describe('monitorOperation', () => {
|
|
182
|
+
beforeEach(() => {
|
|
183
|
+
global.EventSource = MockEventSource as any
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should resolve with success on OPERATION_COMPLETED', async () => {
|
|
187
|
+
const resultPromise = operationClient.monitorOperation('op_sse_1')
|
|
188
|
+
|
|
189
|
+
// Wait for SSE connection to establish
|
|
190
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
191
|
+
|
|
192
|
+
// Get the SSE client and simulate completion
|
|
193
|
+
const sseClients = (operationClient as any).sseClients as Map<string, any>
|
|
194
|
+
const sseClient = sseClients.get('op_sse_1')
|
|
195
|
+
const eventSource = (sseClient as any).eventSource as MockEventSource
|
|
196
|
+
eventSource.simulateMessage(EventType.OPERATION_COMPLETED, {
|
|
197
|
+
result: { graph_id: 'graph_done' },
|
|
198
|
+
metadata: { took: 5000 },
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const result = await resultPromise
|
|
202
|
+
|
|
203
|
+
expect(result.success).toBe(true)
|
|
204
|
+
expect(result.result).toEqual({ graph_id: 'graph_done' })
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should resolve with error on OPERATION_ERROR', async () => {
|
|
208
|
+
const resultPromise = operationClient.monitorOperation('op_sse_err')
|
|
209
|
+
|
|
210
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
211
|
+
|
|
212
|
+
const sseClients = (operationClient as any).sseClients as Map<string, any>
|
|
213
|
+
const sseClient = sseClients.get('op_sse_err')
|
|
214
|
+
const eventSource = (sseClient as any).eventSource as MockEventSource
|
|
215
|
+
eventSource.simulateMessage(EventType.OPERATION_ERROR, {
|
|
216
|
+
message: 'Processing failed',
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
const result = await resultPromise
|
|
220
|
+
|
|
221
|
+
expect(result.success).toBe(false)
|
|
222
|
+
expect(result.error).toBe('Processing failed')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should resolve with cancelled on OPERATION_CANCELLED', async () => {
|
|
226
|
+
const resultPromise = operationClient.monitorOperation('op_sse_cancel')
|
|
227
|
+
|
|
228
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
229
|
+
|
|
230
|
+
const sseClients = (operationClient as any).sseClients as Map<string, any>
|
|
231
|
+
const sseClient = sseClients.get('op_sse_cancel')
|
|
232
|
+
const eventSource = (sseClient as any).eventSource as MockEventSource
|
|
233
|
+
eventSource.simulateMessage(EventType.OPERATION_CANCELLED, {})
|
|
234
|
+
|
|
235
|
+
const result = await resultPromise
|
|
236
|
+
|
|
237
|
+
expect(result.success).toBe(false)
|
|
238
|
+
expect(result.error).toBe('Operation cancelled')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('should call onProgress callback', async () => {
|
|
242
|
+
const progressUpdates: any[] = []
|
|
243
|
+
|
|
244
|
+
const resultPromise = operationClient.monitorOperation('op_progress', {
|
|
245
|
+
onProgress: (p) => progressUpdates.push(p),
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
249
|
+
|
|
250
|
+
const sseClients = (operationClient as any).sseClients as Map<string, any>
|
|
251
|
+
const sseClient = sseClients.get('op_progress')
|
|
252
|
+
const eventSource = (sseClient as any).eventSource as MockEventSource
|
|
253
|
+
|
|
254
|
+
// Send progress then completion
|
|
255
|
+
eventSource.simulateMessage(EventType.OPERATION_PROGRESS, {
|
|
256
|
+
message: 'Step 1 of 3',
|
|
257
|
+
progress_percent: 33,
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
eventSource.simulateMessage(EventType.OPERATION_COMPLETED, {
|
|
261
|
+
result: { done: true },
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
await resultPromise
|
|
265
|
+
|
|
266
|
+
expect(progressUpdates).toHaveLength(1)
|
|
267
|
+
expect(progressUpdates[0].message).toBe('Step 1 of 3')
|
|
268
|
+
expect(progressUpdates[0].progressPercent).toBe(33)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('should timeout after specified duration', async () => {
|
|
272
|
+
const resultPromise = operationClient.monitorOperation('op_timeout', {
|
|
273
|
+
timeout: 100,
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
await expect(resultPromise).rejects.toThrow('Operation timeout after 100ms')
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
describe('waitForOperation', () => {
|
|
281
|
+
beforeEach(() => {
|
|
282
|
+
global.EventSource = MockEventSource as any
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should return result on success', async () => {
|
|
286
|
+
const resultPromise = operationClient.waitForOperation('op_wait')
|
|
287
|
+
|
|
288
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
289
|
+
|
|
290
|
+
const sseClients = (operationClient as any).sseClients as Map<string, any>
|
|
291
|
+
const sseClient = sseClients.get('op_wait')
|
|
292
|
+
const eventSource = (sseClient as any).eventSource as MockEventSource
|
|
293
|
+
eventSource.simulateMessage(EventType.OPERATION_COMPLETED, {
|
|
294
|
+
result: { data: 'done' },
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
const result = await resultPromise
|
|
298
|
+
|
|
299
|
+
expect(result).toEqual({ data: 'done' })
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('should throw on failure', async () => {
|
|
303
|
+
const resultPromise = operationClient.waitForOperation('op_wait_fail')
|
|
304
|
+
|
|
305
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
306
|
+
|
|
307
|
+
const sseClients = (operationClient as any).sseClients as Map<string, any>
|
|
308
|
+
const sseClient = sseClients.get('op_wait_fail')
|
|
309
|
+
const eventSource = (sseClient as any).eventSource as MockEventSource
|
|
310
|
+
eventSource.simulateMessage(EventType.OPERATION_ERROR, {
|
|
311
|
+
message: 'Server error',
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
await expect(resultPromise).rejects.toThrow('Server error')
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('should timeout with specified duration', async () => {
|
|
318
|
+
await expect(operationClient.waitForOperation('op_wait_timeout', 100)).rejects.toThrow(
|
|
319
|
+
'Operation timeout'
|
|
320
|
+
)
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
126
324
|
describe('configuration', () => {
|
|
127
325
|
it('should accept JWT token in config', () => {
|
|
128
326
|
const client = new OperationClient({
|
|
@@ -237,10 +237,166 @@ describe('QueryClient', () => {
|
|
|
237
237
|
})
|
|
238
238
|
})
|
|
239
239
|
|
|
240
|
+
describe('executeQuery - stream mode with NDJSON', () => {
|
|
241
|
+
it('should parse NDJSON streaming response', async () => {
|
|
242
|
+
const ndjsonBody = [
|
|
243
|
+
'{"columns":["name","age"],"rows":[["Alice",30],["Bob",25]]}',
|
|
244
|
+
'{"rows":[["Charlie",35]]}',
|
|
245
|
+
'',
|
|
246
|
+
].join('\n')
|
|
247
|
+
|
|
248
|
+
// Create a ReadableStream from the NDJSON string
|
|
249
|
+
const stream = new ReadableStream({
|
|
250
|
+
start(controller) {
|
|
251
|
+
controller.enqueue(new TextEncoder().encode(ndjsonBody))
|
|
252
|
+
controller.close()
|
|
253
|
+
},
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
const mockStreamResponse = {
|
|
257
|
+
data: undefined,
|
|
258
|
+
error: undefined,
|
|
259
|
+
response: {
|
|
260
|
+
ok: true,
|
|
261
|
+
status: 200,
|
|
262
|
+
headers: new Headers({ 'content-type': 'application/x-ndjson' }),
|
|
263
|
+
body: stream,
|
|
264
|
+
json: async () => ({}),
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Mock the SDK function via fetch
|
|
269
|
+
mockFetch.mockResolvedValueOnce({
|
|
270
|
+
ok: true,
|
|
271
|
+
status: 200,
|
|
272
|
+
headers: new Headers({ 'content-type': 'application/x-ndjson' }),
|
|
273
|
+
body: stream,
|
|
274
|
+
json: async () => mockStreamResponse,
|
|
275
|
+
text: async () => JSON.stringify(mockStreamResponse),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
// We can't easily test the full stream mode through the SDK mock
|
|
279
|
+
// because the SDK parses the response. Test the NDJSON parser directly instead.
|
|
280
|
+
const parser = (queryClient as any).parseNDJSONResponse.bind(queryClient)
|
|
281
|
+
|
|
282
|
+
const ndjsonStream = new ReadableStream({
|
|
283
|
+
start(controller) {
|
|
284
|
+
controller.enqueue(
|
|
285
|
+
new TextEncoder().encode(
|
|
286
|
+
'{"columns":["name"],"rows":[["Alice"],["Bob"]]}\n{"rows":[["Charlie"]]}\n'
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
controller.close()
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const mockResp = {
|
|
294
|
+
body: ndjsonStream,
|
|
295
|
+
headers: new Headers({ 'content-type': 'application/x-ndjson' }),
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const result = await parser(mockResp, 'graph_1')
|
|
299
|
+
|
|
300
|
+
expect(result.columns).toEqual(['name'])
|
|
301
|
+
expect(result.data).toEqual([['Alice'], ['Bob'], ['Charlie']])
|
|
302
|
+
expect(result.row_count).toBe(3)
|
|
303
|
+
expect(result.graph_id).toBe('graph_1')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should handle NDJSON with data key instead of rows', async () => {
|
|
307
|
+
const parser = (queryClient as any).parseNDJSONResponse.bind(queryClient)
|
|
308
|
+
|
|
309
|
+
const stream = new ReadableStream({
|
|
310
|
+
start(controller) {
|
|
311
|
+
controller.enqueue(
|
|
312
|
+
new TextEncoder().encode(
|
|
313
|
+
'{"columns":["id"],"data":[{"id":1},{"id":2}],"execution_time_ms":50}\n'
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
controller.close()
|
|
317
|
+
},
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
const result = await parser({ body: stream }, 'graph_1')
|
|
321
|
+
|
|
322
|
+
expect(result.data).toEqual([{ id: 1 }, { id: 2 }])
|
|
323
|
+
expect(result.row_count).toBe(2)
|
|
324
|
+
expect(result.execution_time_ms).toBe(50)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('should throw on NDJSON parse errors', async () => {
|
|
328
|
+
const parser = (queryClient as any).parseNDJSONResponse.bind(queryClient)
|
|
329
|
+
|
|
330
|
+
const stream = new ReadableStream({
|
|
331
|
+
start(controller) {
|
|
332
|
+
controller.enqueue(new TextEncoder().encode('not valid json\n'))
|
|
333
|
+
controller.close()
|
|
334
|
+
},
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
await expect(parser({ body: stream }, 'graph_1')).rejects.toThrow('NDJSON parsing failed')
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('should throw when body is not readable', async () => {
|
|
341
|
+
const parser = (queryClient as any).parseNDJSONResponse.bind(queryClient)
|
|
342
|
+
|
|
343
|
+
await expect(parser({ body: null }, 'graph_1')).rejects.toThrow(
|
|
344
|
+
'Response body is not readable'
|
|
345
|
+
)
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('should track max execution_time_ms across chunks', async () => {
|
|
349
|
+
const parser = (queryClient as any).parseNDJSONResponse.bind(queryClient)
|
|
350
|
+
|
|
351
|
+
const stream = new ReadableStream({
|
|
352
|
+
start(controller) {
|
|
353
|
+
controller.enqueue(
|
|
354
|
+
new TextEncoder().encode(
|
|
355
|
+
'{"columns":["x"],"rows":[[1]],"execution_time_ms":10}\n' +
|
|
356
|
+
'{"rows":[[2]],"execution_time_ms":50}\n' +
|
|
357
|
+
'{"rows":[[3]],"execution_time_ms":25}\n'
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
controller.close()
|
|
361
|
+
},
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const result = await parser({ body: stream }, 'graph_1')
|
|
365
|
+
|
|
366
|
+
expect(result.execution_time_ms).toBe(50) // max across chunks
|
|
367
|
+
expect(result.row_count).toBe(3)
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
describe('streamQuery', () => {
|
|
372
|
+
it('should yield all data items when result is non-streaming', async () => {
|
|
373
|
+
mockFetch.mockResolvedValueOnce(
|
|
374
|
+
createMockResponse({
|
|
375
|
+
data: [{ id: 1 }, { id: 2 }, { id: 3 }],
|
|
376
|
+
columns: ['id'],
|
|
377
|
+
row_count: 3,
|
|
378
|
+
execution_time_ms: 10,
|
|
379
|
+
})
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
const items: any[] = []
|
|
383
|
+
for await (const item of queryClient.streamQuery('graph_1', 'MATCH (n) RETURN n')) {
|
|
384
|
+
items.push(item)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
expect(items).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }])
|
|
388
|
+
})
|
|
389
|
+
})
|
|
390
|
+
|
|
240
391
|
describe('close', () => {
|
|
241
392
|
it('should close without error when no SSE client exists', () => {
|
|
242
393
|
expect(() => queryClient.close()).not.toThrow()
|
|
243
394
|
})
|
|
395
|
+
|
|
396
|
+
it('should be safe to call multiple times', () => {
|
|
397
|
+
queryClient.close()
|
|
398
|
+
expect(() => queryClient.close()).not.toThrow()
|
|
399
|
+
})
|
|
244
400
|
})
|
|
245
401
|
|
|
246
402
|
describe('QueuedQueryError', () => {
|
|
@@ -259,5 +415,18 @@ describe('QueryClient', () => {
|
|
|
259
415
|
expect(error.name).toBe('QueuedQueryError')
|
|
260
416
|
expect(error.queueInfo).toEqual(queueInfo)
|
|
261
417
|
})
|
|
418
|
+
|
|
419
|
+
it('should be instanceof Error', () => {
|
|
420
|
+
const error = new QueuedQueryError({
|
|
421
|
+
status: 'queued',
|
|
422
|
+
operation_id: 'op_1',
|
|
423
|
+
queue_position: 1,
|
|
424
|
+
estimated_wait_seconds: 5,
|
|
425
|
+
message: 'Queued',
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
expect(error).toBeInstanceOf(Error)
|
|
429
|
+
expect(error).toBeInstanceOf(QueuedQueryError)
|
|
430
|
+
})
|
|
262
431
|
})
|
|
263
432
|
})
|