@robosystems/client 0.2.15 → 0.2.16
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/FileClient.d.ts +57 -0
- package/extensions/{TableIngestClient.js → FileClient.js} +55 -77
- package/extensions/{TableIngestClient.ts → FileClient.ts} +67 -122
- package/extensions/MaterializationClient.d.ts +51 -0
- package/extensions/MaterializationClient.js +107 -0
- package/extensions/MaterializationClient.ts +163 -0
- package/extensions/TableClient.d.ts +38 -0
- package/extensions/TableClient.js +92 -0
- package/extensions/TableClient.ts +132 -0
- package/extensions/hooks.d.ts +9 -11
- package/extensions/hooks.js +21 -56
- package/extensions/hooks.ts +30 -79
- package/extensions/index.d.ts +14 -4
- package/extensions/index.js +32 -5
- package/extensions/index.test.ts +10 -2
- package/extensions/index.ts +46 -5
- package/package.json +1 -1
- package/sdk-extensions/FileClient.d.ts +57 -0
- package/sdk-extensions/{TableIngestClient.js → FileClient.js} +55 -77
- package/sdk-extensions/{TableIngestClient.ts → FileClient.ts} +67 -122
- package/sdk-extensions/MaterializationClient.d.ts +51 -0
- package/sdk-extensions/MaterializationClient.js +107 -0
- package/sdk-extensions/MaterializationClient.ts +163 -0
- package/sdk-extensions/TableClient.d.ts +38 -0
- package/sdk-extensions/TableClient.js +92 -0
- package/sdk-extensions/TableClient.ts +132 -0
- package/sdk-extensions/hooks.d.ts +9 -11
- package/sdk-extensions/hooks.js +21 -56
- package/sdk-extensions/hooks.ts +30 -79
- package/sdk-extensions/index.d.ts +14 -4
- package/sdk-extensions/index.js +32 -5
- package/sdk-extensions/index.test.ts +10 -2
- package/sdk-extensions/index.ts +46 -5
- package/extensions/TableIngestClient.d.ts +0 -75
- package/extensions/TableIngestClient.test.ts +0 -304
- package/sdk-extensions/TableIngestClient.d.ts +0 -75
- package/sdk-extensions/TableIngestClient.test.ts +0 -304
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import { TableIngestClient } from './TableIngestClient'
|
|
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('TableIngestClient', () => {
|
|
19
|
-
let tableClient: TableIngestClient
|
|
20
|
-
let mockFetch: any
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
tableClient = new TableIngestClient({
|
|
24
|
-
baseUrl: 'http://localhost:8000',
|
|
25
|
-
headers: { 'X-API-Key': 'test-key' },
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
// Mock global fetch
|
|
29
|
-
mockFetch = vi.fn()
|
|
30
|
-
global.fetch = mockFetch
|
|
31
|
-
|
|
32
|
-
// Reset all mocks
|
|
33
|
-
vi.clearAllMocks()
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
describe('uploadParquetFile', () => {
|
|
37
|
-
it('should upload a Buffer successfully', async () => {
|
|
38
|
-
mockFetch
|
|
39
|
-
// 1. Get upload URL
|
|
40
|
-
.mockResolvedValueOnce(
|
|
41
|
-
createMockResponse({
|
|
42
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
43
|
-
file_id: 'file_123',
|
|
44
|
-
})
|
|
45
|
-
)
|
|
46
|
-
// 2. S3 upload
|
|
47
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
48
|
-
// 3. Update metadata
|
|
49
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
50
|
-
|
|
51
|
-
const buffer = Buffer.from('test parquet data')
|
|
52
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', buffer, {
|
|
53
|
-
fileName: 'test.parquet',
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
expect(result.success).toBe(true)
|
|
57
|
-
expect(result.fileId).toBe('file_123')
|
|
58
|
-
expect(result.tableName).toBe('Entity')
|
|
59
|
-
expect(result.fileName).toBe('test.parquet')
|
|
60
|
-
expect(mockFetch).toHaveBeenCalledTimes(3)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('should handle upload URL fetch failure', async () => {
|
|
64
|
-
mockFetch.mockResolvedValueOnce(
|
|
65
|
-
createMockResponse({ detail: 'Failed to get upload URL' }, { ok: false, status: 500 })
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
const buffer = Buffer.from('test data')
|
|
69
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', buffer)
|
|
70
|
-
|
|
71
|
-
expect(result.success).toBe(false)
|
|
72
|
-
expect(result.error).toBeDefined()
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('should handle S3 upload failure', async () => {
|
|
76
|
-
mockFetch
|
|
77
|
-
// Get upload URL succeeds
|
|
78
|
-
.mockResolvedValueOnce(
|
|
79
|
-
createMockResponse({
|
|
80
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
81
|
-
file_id: 'file_123',
|
|
82
|
-
})
|
|
83
|
-
)
|
|
84
|
-
// S3 upload fails
|
|
85
|
-
.mockResolvedValueOnce(createMockResponse(null, { ok: false, status: 500 }))
|
|
86
|
-
|
|
87
|
-
const buffer = Buffer.from('test data')
|
|
88
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', buffer)
|
|
89
|
-
|
|
90
|
-
expect(result.success).toBe(false)
|
|
91
|
-
expect(result.error).toContain('S3 upload failed')
|
|
92
|
-
expect(result.error).toContain('500')
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('should fix LocalStack URLs when enabled', async () => {
|
|
96
|
-
mockFetch
|
|
97
|
-
.mockResolvedValueOnce(
|
|
98
|
-
createMockResponse({
|
|
99
|
-
upload_url: 'http://localstack:4566/bucket/file.parquet',
|
|
100
|
-
file_id: 'file_123',
|
|
101
|
-
})
|
|
102
|
-
)
|
|
103
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
104
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
105
|
-
|
|
106
|
-
const buffer = Buffer.from('test data')
|
|
107
|
-
await tableClient.uploadParquetFile('graph_123', 'Entity', buffer, {
|
|
108
|
-
fixLocalStackUrl: true,
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// Check that the S3 upload (2nd call) used the fixed URL
|
|
112
|
-
const s3Call = mockFetch.mock.calls[1]
|
|
113
|
-
expect(s3Call[0]).toBe('http://localhost:4566/bucket/file.parquet')
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('should call progress callback', async () => {
|
|
117
|
-
mockFetch
|
|
118
|
-
.mockResolvedValueOnce(
|
|
119
|
-
createMockResponse({
|
|
120
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
121
|
-
file_id: 'file_123',
|
|
122
|
-
})
|
|
123
|
-
)
|
|
124
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
125
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
126
|
-
|
|
127
|
-
const progressMessages: string[] = []
|
|
128
|
-
const buffer = Buffer.from('test data')
|
|
129
|
-
|
|
130
|
-
await tableClient.uploadParquetFile('graph_123', 'Entity', buffer, {
|
|
131
|
-
onProgress: (msg) => progressMessages.push(msg),
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
expect(progressMessages.length).toBeGreaterThan(0)
|
|
135
|
-
expect(progressMessages.some((msg) => msg.includes('Getting upload URL'))).toBe(true)
|
|
136
|
-
expect(progressMessages.some((msg) => msg.includes('Uploading'))).toBe(true)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('should handle metadata update failure', async () => {
|
|
140
|
-
mockFetch
|
|
141
|
-
.mockResolvedValueOnce(
|
|
142
|
-
createMockResponse({
|
|
143
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
144
|
-
file_id: 'file_123',
|
|
145
|
-
})
|
|
146
|
-
)
|
|
147
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
148
|
-
.mockResolvedValueOnce(
|
|
149
|
-
createMockResponse({ detail: 'Failed to update' }, { ok: false, status: 500 })
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
const buffer = Buffer.from('test data')
|
|
153
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', buffer)
|
|
154
|
-
|
|
155
|
-
expect(result.success).toBe(false)
|
|
156
|
-
expect(result.error).toContain('Failed to complete file upload')
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
describe('listStagingTables', () => {
|
|
161
|
-
it('should list staging tables successfully', async () => {
|
|
162
|
-
mockFetch.mockResolvedValueOnce(
|
|
163
|
-
createMockResponse({
|
|
164
|
-
tables: [
|
|
165
|
-
{
|
|
166
|
-
table_name: 'Entity',
|
|
167
|
-
row_count: 1000,
|
|
168
|
-
file_count: 2,
|
|
169
|
-
total_size_bytes: 5000000,
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
})
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
const tables = await tableClient.listStagingTables('graph_123')
|
|
176
|
-
|
|
177
|
-
expect(tables).toHaveLength(1)
|
|
178
|
-
expect(tables[0].tableName).toBe('Entity')
|
|
179
|
-
expect(tables[0].rowCount).toBe(1000)
|
|
180
|
-
expect(tables[0].fileCount).toBe(2)
|
|
181
|
-
expect(tables[0].totalSizeBytes).toBe(5000000)
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('should return empty array on error', async () => {
|
|
185
|
-
mockFetch.mockResolvedValueOnce(
|
|
186
|
-
createMockResponse({ detail: 'Failed to list tables' }, { ok: false, status: 500 })
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
const tables = await tableClient.listStagingTables('graph_123')
|
|
190
|
-
|
|
191
|
-
expect(tables).toEqual([])
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
describe('ingestAllTables', () => {
|
|
196
|
-
it('should ingest tables successfully', async () => {
|
|
197
|
-
mockFetch.mockResolvedValueOnce(
|
|
198
|
-
createMockResponse({
|
|
199
|
-
operation_id: 'op_123',
|
|
200
|
-
message: 'Ingestion started',
|
|
201
|
-
})
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
const result = await tableClient.ingestAllTables('graph_123', {
|
|
205
|
-
ignoreErrors: true,
|
|
206
|
-
rebuild: false,
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
expect(result.success).toBe(true)
|
|
210
|
-
expect(result.operationId).toBe('op_123')
|
|
211
|
-
expect(result.message).toBe('Ingestion started')
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('should handle ingestion failure', async () => {
|
|
215
|
-
mockFetch.mockResolvedValueOnce(
|
|
216
|
-
createMockResponse({ detail: 'Ingestion failed' }, { ok: false, status: 500 })
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
const result = await tableClient.ingestAllTables('graph_123')
|
|
220
|
-
|
|
221
|
-
expect(result.success).toBe(false)
|
|
222
|
-
expect(result.error).toBeDefined()
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
it('should call progress callback', async () => {
|
|
226
|
-
mockFetch.mockResolvedValueOnce(
|
|
227
|
-
createMockResponse({
|
|
228
|
-
operation_id: 'op_123',
|
|
229
|
-
message: 'Ingestion started',
|
|
230
|
-
})
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
const progressMessages: string[] = []
|
|
234
|
-
|
|
235
|
-
await tableClient.ingestAllTables('graph_123', {
|
|
236
|
-
onProgress: (msg) => progressMessages.push(msg),
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
expect(progressMessages.length).toBeGreaterThan(0)
|
|
240
|
-
expect(progressMessages.some((msg) => msg.includes('Starting'))).toBe(true)
|
|
241
|
-
expect(progressMessages.some((msg) => msg.includes('completed'))).toBe(true)
|
|
242
|
-
})
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
describe('uploadAndIngest', () => {
|
|
246
|
-
it('should upload and ingest in one operation', async () => {
|
|
247
|
-
mockFetch
|
|
248
|
-
// Upload URL
|
|
249
|
-
.mockResolvedValueOnce(
|
|
250
|
-
createMockResponse({
|
|
251
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
252
|
-
file_id: 'file_123',
|
|
253
|
-
})
|
|
254
|
-
)
|
|
255
|
-
// S3 upload
|
|
256
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
257
|
-
// Update metadata
|
|
258
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
259
|
-
// Ingest
|
|
260
|
-
.mockResolvedValueOnce(createMockResponse({ operation_id: 'op_123' }))
|
|
261
|
-
|
|
262
|
-
const buffer = Buffer.from('test data')
|
|
263
|
-
const result = await tableClient.uploadAndIngest('graph_123', 'Entity', buffer)
|
|
264
|
-
|
|
265
|
-
expect(result.upload.success).toBe(true)
|
|
266
|
-
expect(result.ingest?.success).toBe(true)
|
|
267
|
-
expect(result.ingest?.operationId).toBe('op_123')
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
it('should not ingest if upload fails', async () => {
|
|
271
|
-
mockFetch.mockResolvedValueOnce(
|
|
272
|
-
createMockResponse({ detail: 'Upload failed' }, { ok: false, status: 500 })
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
const buffer = Buffer.from('test data')
|
|
276
|
-
const result = await tableClient.uploadAndIngest('graph_123', 'Entity', buffer)
|
|
277
|
-
|
|
278
|
-
expect(result.upload.success).toBe(false)
|
|
279
|
-
expect(result.ingest).toBeNull()
|
|
280
|
-
// Should only have 1 fetch call (failed upload URL), not the ingest call
|
|
281
|
-
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
282
|
-
})
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
describe('file conversion helpers', () => {
|
|
286
|
-
it('should handle File objects', async () => {
|
|
287
|
-
mockFetch
|
|
288
|
-
.mockResolvedValueOnce(
|
|
289
|
-
createMockResponse({
|
|
290
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
291
|
-
file_id: 'file_123',
|
|
292
|
-
})
|
|
293
|
-
)
|
|
294
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
295
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
296
|
-
|
|
297
|
-
const file = new File(['test data'], 'test.parquet', { type: 'application/x-parquet' })
|
|
298
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', file)
|
|
299
|
-
|
|
300
|
-
expect(result.success).toBe(true)
|
|
301
|
-
expect(result.fileName).toBe('test.parquet')
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
})
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
export interface UploadOptions {
|
|
2
|
-
onProgress?: (message: string) => void;
|
|
3
|
-
fixLocalStackUrl?: boolean;
|
|
4
|
-
fileName?: string;
|
|
5
|
-
}
|
|
6
|
-
export interface IngestOptions {
|
|
7
|
-
ignoreErrors?: boolean;
|
|
8
|
-
rebuild?: boolean;
|
|
9
|
-
onProgress?: (message: string) => void;
|
|
10
|
-
}
|
|
11
|
-
export interface UploadResult {
|
|
12
|
-
fileId: string;
|
|
13
|
-
fileSize: number;
|
|
14
|
-
rowCount: number;
|
|
15
|
-
tableName: string;
|
|
16
|
-
fileName: string;
|
|
17
|
-
success: boolean;
|
|
18
|
-
error?: string;
|
|
19
|
-
}
|
|
20
|
-
export interface TableInfo {
|
|
21
|
-
tableName: string;
|
|
22
|
-
rowCount: number;
|
|
23
|
-
fileCount: number;
|
|
24
|
-
totalSizeBytes: number;
|
|
25
|
-
}
|
|
26
|
-
export interface IngestResult {
|
|
27
|
-
success: boolean;
|
|
28
|
-
operationId?: string;
|
|
29
|
-
message?: string;
|
|
30
|
-
error?: string;
|
|
31
|
-
}
|
|
32
|
-
export type FileInput = File | Blob | Buffer | ReadableStream<Uint8Array>;
|
|
33
|
-
export declare class TableIngestClient {
|
|
34
|
-
private config;
|
|
35
|
-
constructor(config: {
|
|
36
|
-
baseUrl: string;
|
|
37
|
-
credentials?: 'include' | 'same-origin' | 'omit';
|
|
38
|
-
headers?: Record<string, string>;
|
|
39
|
-
token?: string;
|
|
40
|
-
});
|
|
41
|
-
/**
|
|
42
|
-
* Upload a Parquet file to a staging table
|
|
43
|
-
*
|
|
44
|
-
* This method handles the complete 3-step upload process:
|
|
45
|
-
* 1. Get presigned upload URL
|
|
46
|
-
* 2. Upload file to S3
|
|
47
|
-
* 3. Mark file as 'uploaded' (backend validates, calculates size/row count)
|
|
48
|
-
*
|
|
49
|
-
* Supports File (browser), Blob (browser), Buffer (Node.js), and ReadableStream.
|
|
50
|
-
*/
|
|
51
|
-
uploadParquetFile(graphId: string, tableName: string, fileOrBuffer: FileInput, options?: UploadOptions): Promise<UploadResult>;
|
|
52
|
-
/**
|
|
53
|
-
* List all staging tables in a graph
|
|
54
|
-
*/
|
|
55
|
-
listStagingTables(graphId: string): Promise<TableInfo[]>;
|
|
56
|
-
/**
|
|
57
|
-
* Ingest all staging tables into the graph
|
|
58
|
-
*/
|
|
59
|
-
ingestAllTables(graphId: string, options?: IngestOptions): Promise<IngestResult>;
|
|
60
|
-
/**
|
|
61
|
-
* Convenience method to upload a file and immediately ingest it
|
|
62
|
-
*/
|
|
63
|
-
uploadAndIngest(graphId: string, tableName: string, fileOrBuffer: FileInput, uploadOptions?: UploadOptions, ingestOptions?: IngestOptions): Promise<{
|
|
64
|
-
upload: UploadResult;
|
|
65
|
-
ingest: IngestResult | null;
|
|
66
|
-
}>;
|
|
67
|
-
/**
|
|
68
|
-
* Get file name from input or use provided override
|
|
69
|
-
*/
|
|
70
|
-
private getFileName;
|
|
71
|
-
/**
|
|
72
|
-
* Convert various file inputs to ArrayBuffer for upload
|
|
73
|
-
*/
|
|
74
|
-
private getFileContent;
|
|
75
|
-
}
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import { TableIngestClient } from './TableIngestClient'
|
|
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('TableIngestClient', () => {
|
|
19
|
-
let tableClient: TableIngestClient
|
|
20
|
-
let mockFetch: any
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
tableClient = new TableIngestClient({
|
|
24
|
-
baseUrl: 'http://localhost:8000',
|
|
25
|
-
headers: { 'X-API-Key': 'test-key' },
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
// Mock global fetch
|
|
29
|
-
mockFetch = vi.fn()
|
|
30
|
-
global.fetch = mockFetch
|
|
31
|
-
|
|
32
|
-
// Reset all mocks
|
|
33
|
-
vi.clearAllMocks()
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
describe('uploadParquetFile', () => {
|
|
37
|
-
it('should upload a Buffer successfully', async () => {
|
|
38
|
-
mockFetch
|
|
39
|
-
// 1. Get upload URL
|
|
40
|
-
.mockResolvedValueOnce(
|
|
41
|
-
createMockResponse({
|
|
42
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
43
|
-
file_id: 'file_123',
|
|
44
|
-
})
|
|
45
|
-
)
|
|
46
|
-
// 2. S3 upload
|
|
47
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
48
|
-
// 3. Update metadata
|
|
49
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
50
|
-
|
|
51
|
-
const buffer = Buffer.from('test parquet data')
|
|
52
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', buffer, {
|
|
53
|
-
fileName: 'test.parquet',
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
expect(result.success).toBe(true)
|
|
57
|
-
expect(result.fileId).toBe('file_123')
|
|
58
|
-
expect(result.tableName).toBe('Entity')
|
|
59
|
-
expect(result.fileName).toBe('test.parquet')
|
|
60
|
-
expect(mockFetch).toHaveBeenCalledTimes(3)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('should handle upload URL fetch failure', async () => {
|
|
64
|
-
mockFetch.mockResolvedValueOnce(
|
|
65
|
-
createMockResponse({ detail: 'Failed to get upload URL' }, { ok: false, status: 500 })
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
const buffer = Buffer.from('test data')
|
|
69
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', buffer)
|
|
70
|
-
|
|
71
|
-
expect(result.success).toBe(false)
|
|
72
|
-
expect(result.error).toBeDefined()
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('should handle S3 upload failure', async () => {
|
|
76
|
-
mockFetch
|
|
77
|
-
// Get upload URL succeeds
|
|
78
|
-
.mockResolvedValueOnce(
|
|
79
|
-
createMockResponse({
|
|
80
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
81
|
-
file_id: 'file_123',
|
|
82
|
-
})
|
|
83
|
-
)
|
|
84
|
-
// S3 upload fails
|
|
85
|
-
.mockResolvedValueOnce(createMockResponse(null, { ok: false, status: 500 }))
|
|
86
|
-
|
|
87
|
-
const buffer = Buffer.from('test data')
|
|
88
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', buffer)
|
|
89
|
-
|
|
90
|
-
expect(result.success).toBe(false)
|
|
91
|
-
expect(result.error).toContain('S3 upload failed')
|
|
92
|
-
expect(result.error).toContain('500')
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('should fix LocalStack URLs when enabled', async () => {
|
|
96
|
-
mockFetch
|
|
97
|
-
.mockResolvedValueOnce(
|
|
98
|
-
createMockResponse({
|
|
99
|
-
upload_url: 'http://localstack:4566/bucket/file.parquet',
|
|
100
|
-
file_id: 'file_123',
|
|
101
|
-
})
|
|
102
|
-
)
|
|
103
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
104
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
105
|
-
|
|
106
|
-
const buffer = Buffer.from('test data')
|
|
107
|
-
await tableClient.uploadParquetFile('graph_123', 'Entity', buffer, {
|
|
108
|
-
fixLocalStackUrl: true,
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// Check that the S3 upload (2nd call) used the fixed URL
|
|
112
|
-
const s3Call = mockFetch.mock.calls[1]
|
|
113
|
-
expect(s3Call[0]).toBe('http://localhost:4566/bucket/file.parquet')
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('should call progress callback', async () => {
|
|
117
|
-
mockFetch
|
|
118
|
-
.mockResolvedValueOnce(
|
|
119
|
-
createMockResponse({
|
|
120
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
121
|
-
file_id: 'file_123',
|
|
122
|
-
})
|
|
123
|
-
)
|
|
124
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
125
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
126
|
-
|
|
127
|
-
const progressMessages: string[] = []
|
|
128
|
-
const buffer = Buffer.from('test data')
|
|
129
|
-
|
|
130
|
-
await tableClient.uploadParquetFile('graph_123', 'Entity', buffer, {
|
|
131
|
-
onProgress: (msg) => progressMessages.push(msg),
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
expect(progressMessages.length).toBeGreaterThan(0)
|
|
135
|
-
expect(progressMessages.some((msg) => msg.includes('Getting upload URL'))).toBe(true)
|
|
136
|
-
expect(progressMessages.some((msg) => msg.includes('Uploading'))).toBe(true)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('should handle metadata update failure', async () => {
|
|
140
|
-
mockFetch
|
|
141
|
-
.mockResolvedValueOnce(
|
|
142
|
-
createMockResponse({
|
|
143
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
144
|
-
file_id: 'file_123',
|
|
145
|
-
})
|
|
146
|
-
)
|
|
147
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
148
|
-
.mockResolvedValueOnce(
|
|
149
|
-
createMockResponse({ detail: 'Failed to update' }, { ok: false, status: 500 })
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
const buffer = Buffer.from('test data')
|
|
153
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', buffer)
|
|
154
|
-
|
|
155
|
-
expect(result.success).toBe(false)
|
|
156
|
-
expect(result.error).toContain('Failed to complete file upload')
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
describe('listStagingTables', () => {
|
|
161
|
-
it('should list staging tables successfully', async () => {
|
|
162
|
-
mockFetch.mockResolvedValueOnce(
|
|
163
|
-
createMockResponse({
|
|
164
|
-
tables: [
|
|
165
|
-
{
|
|
166
|
-
table_name: 'Entity',
|
|
167
|
-
row_count: 1000,
|
|
168
|
-
file_count: 2,
|
|
169
|
-
total_size_bytes: 5000000,
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
})
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
const tables = await tableClient.listStagingTables('graph_123')
|
|
176
|
-
|
|
177
|
-
expect(tables).toHaveLength(1)
|
|
178
|
-
expect(tables[0].tableName).toBe('Entity')
|
|
179
|
-
expect(tables[0].rowCount).toBe(1000)
|
|
180
|
-
expect(tables[0].fileCount).toBe(2)
|
|
181
|
-
expect(tables[0].totalSizeBytes).toBe(5000000)
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('should return empty array on error', async () => {
|
|
185
|
-
mockFetch.mockResolvedValueOnce(
|
|
186
|
-
createMockResponse({ detail: 'Failed to list tables' }, { ok: false, status: 500 })
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
const tables = await tableClient.listStagingTables('graph_123')
|
|
190
|
-
|
|
191
|
-
expect(tables).toEqual([])
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
describe('ingestAllTables', () => {
|
|
196
|
-
it('should ingest tables successfully', async () => {
|
|
197
|
-
mockFetch.mockResolvedValueOnce(
|
|
198
|
-
createMockResponse({
|
|
199
|
-
operation_id: 'op_123',
|
|
200
|
-
message: 'Ingestion started',
|
|
201
|
-
})
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
const result = await tableClient.ingestAllTables('graph_123', {
|
|
205
|
-
ignoreErrors: true,
|
|
206
|
-
rebuild: false,
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
expect(result.success).toBe(true)
|
|
210
|
-
expect(result.operationId).toBe('op_123')
|
|
211
|
-
expect(result.message).toBe('Ingestion started')
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('should handle ingestion failure', async () => {
|
|
215
|
-
mockFetch.mockResolvedValueOnce(
|
|
216
|
-
createMockResponse({ detail: 'Ingestion failed' }, { ok: false, status: 500 })
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
const result = await tableClient.ingestAllTables('graph_123')
|
|
220
|
-
|
|
221
|
-
expect(result.success).toBe(false)
|
|
222
|
-
expect(result.error).toBeDefined()
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
it('should call progress callback', async () => {
|
|
226
|
-
mockFetch.mockResolvedValueOnce(
|
|
227
|
-
createMockResponse({
|
|
228
|
-
operation_id: 'op_123',
|
|
229
|
-
message: 'Ingestion started',
|
|
230
|
-
})
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
const progressMessages: string[] = []
|
|
234
|
-
|
|
235
|
-
await tableClient.ingestAllTables('graph_123', {
|
|
236
|
-
onProgress: (msg) => progressMessages.push(msg),
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
expect(progressMessages.length).toBeGreaterThan(0)
|
|
240
|
-
expect(progressMessages.some((msg) => msg.includes('Starting'))).toBe(true)
|
|
241
|
-
expect(progressMessages.some((msg) => msg.includes('completed'))).toBe(true)
|
|
242
|
-
})
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
describe('uploadAndIngest', () => {
|
|
246
|
-
it('should upload and ingest in one operation', async () => {
|
|
247
|
-
mockFetch
|
|
248
|
-
// Upload URL
|
|
249
|
-
.mockResolvedValueOnce(
|
|
250
|
-
createMockResponse({
|
|
251
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
252
|
-
file_id: 'file_123',
|
|
253
|
-
})
|
|
254
|
-
)
|
|
255
|
-
// S3 upload
|
|
256
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
257
|
-
// Update metadata
|
|
258
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
259
|
-
// Ingest
|
|
260
|
-
.mockResolvedValueOnce(createMockResponse({ operation_id: 'op_123' }))
|
|
261
|
-
|
|
262
|
-
const buffer = Buffer.from('test data')
|
|
263
|
-
const result = await tableClient.uploadAndIngest('graph_123', 'Entity', buffer)
|
|
264
|
-
|
|
265
|
-
expect(result.upload.success).toBe(true)
|
|
266
|
-
expect(result.ingest?.success).toBe(true)
|
|
267
|
-
expect(result.ingest?.operationId).toBe('op_123')
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
it('should not ingest if upload fails', async () => {
|
|
271
|
-
mockFetch.mockResolvedValueOnce(
|
|
272
|
-
createMockResponse({ detail: 'Upload failed' }, { ok: false, status: 500 })
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
const buffer = Buffer.from('test data')
|
|
276
|
-
const result = await tableClient.uploadAndIngest('graph_123', 'Entity', buffer)
|
|
277
|
-
|
|
278
|
-
expect(result.upload.success).toBe(false)
|
|
279
|
-
expect(result.ingest).toBeNull()
|
|
280
|
-
// Should only have 1 fetch call (failed upload URL), not the ingest call
|
|
281
|
-
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
282
|
-
})
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
describe('file conversion helpers', () => {
|
|
286
|
-
it('should handle File objects', async () => {
|
|
287
|
-
mockFetch
|
|
288
|
-
.mockResolvedValueOnce(
|
|
289
|
-
createMockResponse({
|
|
290
|
-
upload_url: 'https://s3.amazonaws.com/bucket/file.parquet',
|
|
291
|
-
file_id: 'file_123',
|
|
292
|
-
})
|
|
293
|
-
)
|
|
294
|
-
.mockResolvedValueOnce(createMockResponse(null))
|
|
295
|
-
.mockResolvedValueOnce(createMockResponse({ success: true }))
|
|
296
|
-
|
|
297
|
-
const file = new File(['test data'], 'test.parquet', { type: 'application/x-parquet' })
|
|
298
|
-
const result = await tableClient.uploadParquetFile('graph_123', 'Entity', file)
|
|
299
|
-
|
|
300
|
-
expect(result.success).toBe(true)
|
|
301
|
-
expect(result.fileName).toBe('test.parquet')
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
})
|