@malloydata/db-publisher 0.0.270-dev250429163414
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/dist/client/api.d.ts +1537 -0
- package/dist/client/api.js +1572 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/base.d.ts +66 -0
- package/dist/client/base.js +69 -0
- package/dist/client/base.js.map +1 -0
- package/dist/client/common.d.ts +65 -0
- package/dist/client/common.js +147 -0
- package/dist/client/common.js.map +1 -0
- package/dist/client/configuration.d.ts +91 -0
- package/dist/client/configuration.js +50 -0
- package/dist/client/configuration.js.map +1 -0
- package/dist/client/index.d.ts +15 -0
- package/dist/client/index.js +32 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/publisher_connection.d.ts +29 -0
- package/dist/publisher_connection.js +119 -0
- package/dist/publisher_connection.js.map +1 -0
- package/dist/publisher_connection.spec.d.ts +1 -0
- package/dist/publisher_connection.spec.js +806 -0
- package/dist/publisher_connection.spec.js.map +1 -0
- package/openapitools.json +7 -0
- package/package.json +32 -0
- package/publisher-api-doc.yaml +1026 -0
- package/src/client/.openapi-generator/FILES +9 -0
- package/src/client/.openapi-generator/VERSION +1 -0
- package/src/client/.openapi-generator-ignore +23 -0
- package/src/client/api.ts +2342 -0
- package/src/client/base.ts +86 -0
- package/src/client/common.ts +150 -0
- package/src/client/configuration.ts +115 -0
- package/src/client/git_push.sh +57 -0
- package/src/client/index.ts +19 -0
- package/src/index.ts +8 -0
- package/src/publisher_connection.spec.ts +1118 -0
- package/src/publisher_connection.ts +223 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
25
|
+
if (mod && mod.__esModule) return mod;
|
|
26
|
+
var result = {};
|
|
27
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
28
|
+
__setModuleDefault(result, mod);
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
const malloy = __importStar(require("@malloydata/malloy"));
|
|
33
|
+
const test_1 = require("@malloydata/malloy/test");
|
|
34
|
+
const publisher_connection_1 = require("./publisher_connection");
|
|
35
|
+
const url_1 = require("url");
|
|
36
|
+
const util = __importStar(require("util"));
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const client_1 = require("./client");
|
|
39
|
+
// mocks client code for testing
|
|
40
|
+
jest.mock('./client', () => {
|
|
41
|
+
const mockConnectionsApi = {
|
|
42
|
+
getConnection: jest.fn(),
|
|
43
|
+
getTest: jest.fn(),
|
|
44
|
+
getTablesource: jest.fn(),
|
|
45
|
+
getSqlsource: jest.fn(),
|
|
46
|
+
getQuerydata: jest.fn(),
|
|
47
|
+
getTemporarytable: jest.fn(),
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
Configuration: jest.fn().mockImplementation(() => ({
|
|
51
|
+
basePath: 'http://test.com/api/v0',
|
|
52
|
+
})),
|
|
53
|
+
ConnectionsApi: jest.fn().mockImplementation(() => mockConnectionsApi),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
const [describe] = (0, test_1.describeIfDatabaseAvailable)(['publisher']);
|
|
57
|
+
describe('db:Publisher', () => {
|
|
58
|
+
describe('unit', () => {
|
|
59
|
+
describe('create', () => {
|
|
60
|
+
let mockConnectionsApi;
|
|
61
|
+
let _mockConfiguration;
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
// Get fresh instances of the mocks
|
|
64
|
+
mockConnectionsApi = new client_1.ConnectionsApi(new client_1.Configuration());
|
|
65
|
+
_mockConfiguration = new client_1.Configuration();
|
|
66
|
+
});
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
jest.clearAllMocks();
|
|
69
|
+
});
|
|
70
|
+
it('should create a connection successfully', async () => {
|
|
71
|
+
const mockConnectionAttributes = {
|
|
72
|
+
dialectName: 'bigquery',
|
|
73
|
+
isPool: false,
|
|
74
|
+
canPersist: true,
|
|
75
|
+
canStream: true,
|
|
76
|
+
};
|
|
77
|
+
const mockConnectionResponse = {
|
|
78
|
+
data: {
|
|
79
|
+
attributes: mockConnectionAttributes,
|
|
80
|
+
},
|
|
81
|
+
status: 200,
|
|
82
|
+
statusText: 'OK',
|
|
83
|
+
headers: {},
|
|
84
|
+
config: {},
|
|
85
|
+
};
|
|
86
|
+
const mockTestResponse = {
|
|
87
|
+
data: undefined,
|
|
88
|
+
status: 200,
|
|
89
|
+
statusText: 'OK',
|
|
90
|
+
headers: {},
|
|
91
|
+
config: {},
|
|
92
|
+
};
|
|
93
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
94
|
+
mockConnectionsApi.getTest.mockResolvedValueOnce(mockTestResponse);
|
|
95
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
96
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
97
|
+
accessToken: 'test-token',
|
|
98
|
+
});
|
|
99
|
+
expect(connection).toBeInstanceOf(publisher_connection_1.PublisherConnection);
|
|
100
|
+
expect(connection.name).toBe('test-connection');
|
|
101
|
+
expect(connection.projectName).toBe('test-project');
|
|
102
|
+
expect(connection.dialectName).toBe('bigquery');
|
|
103
|
+
expect(connection.isPool()).toBe(false);
|
|
104
|
+
expect(connection.canPersist()).toBe(true);
|
|
105
|
+
expect(connection.canStream()).toBe(true);
|
|
106
|
+
expect(mockConnectionsApi.getConnection).toHaveBeenCalledWith('test-project', 'test-connection', {
|
|
107
|
+
headers: {
|
|
108
|
+
Authorization: 'Bearer test-token',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
it('should throw error for invalid connection URI format', async () => {
|
|
113
|
+
await expect(publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
114
|
+
connectionUri: 'http://test.com/invalid/path',
|
|
115
|
+
accessToken: 'test-token',
|
|
116
|
+
})).rejects.toThrow('Invalid connection URI');
|
|
117
|
+
});
|
|
118
|
+
it('should throw error for connection name mismatch', async () => {
|
|
119
|
+
await expect(publisher_connection_1.PublisherConnection.create('different-name', {
|
|
120
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
121
|
+
accessToken: 'test-token',
|
|
122
|
+
})).rejects.toThrow('Connection name mismatch');
|
|
123
|
+
});
|
|
124
|
+
it('should handle no access token', async () => {
|
|
125
|
+
const mockConnectionAttributes = {
|
|
126
|
+
dialectName: 'bigquery',
|
|
127
|
+
isPool: false,
|
|
128
|
+
canPersist: true,
|
|
129
|
+
canStream: true,
|
|
130
|
+
};
|
|
131
|
+
const mockConnectionResponse = {
|
|
132
|
+
data: {
|
|
133
|
+
attributes: mockConnectionAttributes,
|
|
134
|
+
},
|
|
135
|
+
status: 200,
|
|
136
|
+
statusText: 'OK',
|
|
137
|
+
headers: {},
|
|
138
|
+
config: {},
|
|
139
|
+
};
|
|
140
|
+
const mockTestResponse = {
|
|
141
|
+
data: undefined,
|
|
142
|
+
status: 200,
|
|
143
|
+
statusText: 'OK',
|
|
144
|
+
headers: {},
|
|
145
|
+
config: {},
|
|
146
|
+
};
|
|
147
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
148
|
+
mockConnectionsApi.getTest.mockResolvedValueOnce(mockTestResponse);
|
|
149
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
150
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
151
|
+
});
|
|
152
|
+
expect(connection).toBeInstanceOf(publisher_connection_1.PublisherConnection);
|
|
153
|
+
expect(mockConnectionsApi.getConnection).toHaveBeenCalledWith('test-project', 'test-connection', {
|
|
154
|
+
headers: {},
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('fetchTableSchema', () => {
|
|
159
|
+
let mockConnectionsApi;
|
|
160
|
+
let _mockConfiguration;
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
// Get fresh instances of the mocks
|
|
163
|
+
mockConnectionsApi = new client_1.ConnectionsApi(new client_1.Configuration());
|
|
164
|
+
_mockConfiguration = new client_1.Configuration();
|
|
165
|
+
});
|
|
166
|
+
afterEach(() => {
|
|
167
|
+
jest.clearAllMocks();
|
|
168
|
+
});
|
|
169
|
+
it('should fetch table schema successfully', async () => {
|
|
170
|
+
const mockTableSchema = {
|
|
171
|
+
type: 'table',
|
|
172
|
+
name: 'test_table',
|
|
173
|
+
tablePath: 'test_path',
|
|
174
|
+
connection: 'test-connection',
|
|
175
|
+
dialect: 'bigquery',
|
|
176
|
+
fields: [
|
|
177
|
+
{ name: 'id', type: 'number' },
|
|
178
|
+
{ name: 'name', type: 'string' },
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
const mockConnectionResponse = {
|
|
182
|
+
data: {
|
|
183
|
+
attributes: {
|
|
184
|
+
dialectName: 'bigquery',
|
|
185
|
+
isPool: false,
|
|
186
|
+
canPersist: true,
|
|
187
|
+
canStream: true,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
status: 200,
|
|
191
|
+
statusText: 'OK',
|
|
192
|
+
headers: {},
|
|
193
|
+
config: {},
|
|
194
|
+
};
|
|
195
|
+
const mockTableResponse = {
|
|
196
|
+
data: {
|
|
197
|
+
source: JSON.stringify(mockTableSchema),
|
|
198
|
+
},
|
|
199
|
+
status: 200,
|
|
200
|
+
statusText: 'OK',
|
|
201
|
+
headers: {},
|
|
202
|
+
config: {},
|
|
203
|
+
};
|
|
204
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
205
|
+
mockConnectionsApi.getTablesource.mockResolvedValueOnce(mockTableResponse);
|
|
206
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
207
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
208
|
+
accessToken: 'test-token',
|
|
209
|
+
});
|
|
210
|
+
const schema = await connection.fetchTableSchema('test_key', 'test_path');
|
|
211
|
+
expect(schema).toEqual(mockTableSchema);
|
|
212
|
+
expect(mockConnectionsApi.getTablesource).toHaveBeenCalledWith('test-project', 'test-connection', 'test_key', 'test_path', {
|
|
213
|
+
headers: {
|
|
214
|
+
Authorization: 'Bearer test-token',
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
it('should handle API errors', async () => {
|
|
219
|
+
await setupAndTestApiError(mockConnectionsApi, 'getTablesource', connection => connection.fetchTableSchema('test_key', 'test_path'));
|
|
220
|
+
});
|
|
221
|
+
it('should handle invalid JSON response', async () => {
|
|
222
|
+
await setupAndTestInvalidJsonResponse(mockConnectionsApi, 'getTablesource', connection => connection.fetchTableSchema('test_key', 'test_path'));
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
describe('fetchSelectSchema', () => {
|
|
226
|
+
let mockConnectionsApi;
|
|
227
|
+
let _mockConfiguration;
|
|
228
|
+
beforeEach(() => {
|
|
229
|
+
// Get fresh instances of the mocks
|
|
230
|
+
mockConnectionsApi = new client_1.ConnectionsApi(new client_1.Configuration());
|
|
231
|
+
_mockConfiguration = new client_1.Configuration();
|
|
232
|
+
});
|
|
233
|
+
afterEach(() => {
|
|
234
|
+
jest.clearAllMocks();
|
|
235
|
+
});
|
|
236
|
+
it('should fetch SQL schema successfully', async () => {
|
|
237
|
+
const mockSqlSchema = {
|
|
238
|
+
type: 'sql_select',
|
|
239
|
+
name: 'test_query',
|
|
240
|
+
selectStr: 'SELECT * FROM test_table',
|
|
241
|
+
connection: 'test-connection',
|
|
242
|
+
dialect: 'bigquery',
|
|
243
|
+
fields: [
|
|
244
|
+
{ name: 'id', type: 'number' },
|
|
245
|
+
{ name: 'name', type: 'string' },
|
|
246
|
+
],
|
|
247
|
+
};
|
|
248
|
+
const mockConnectionResponse = {
|
|
249
|
+
data: {
|
|
250
|
+
attributes: {
|
|
251
|
+
dialectName: 'bigquery',
|
|
252
|
+
isPool: false,
|
|
253
|
+
canPersist: true,
|
|
254
|
+
canStream: true,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
status: 200,
|
|
258
|
+
statusText: 'OK',
|
|
259
|
+
headers: {},
|
|
260
|
+
config: {},
|
|
261
|
+
};
|
|
262
|
+
const mockSqlResponse = {
|
|
263
|
+
data: {
|
|
264
|
+
source: JSON.stringify(mockSqlSchema),
|
|
265
|
+
},
|
|
266
|
+
status: 200,
|
|
267
|
+
statusText: 'OK',
|
|
268
|
+
headers: {},
|
|
269
|
+
config: {},
|
|
270
|
+
};
|
|
271
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
272
|
+
mockConnectionsApi.getSqlsource.mockResolvedValueOnce(mockSqlResponse);
|
|
273
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
274
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
275
|
+
accessToken: 'test-token',
|
|
276
|
+
});
|
|
277
|
+
const schema = await connection.fetchSelectSchema({
|
|
278
|
+
selectStr: 'SELECT * FROM test_table',
|
|
279
|
+
connection: 'test-connection',
|
|
280
|
+
});
|
|
281
|
+
expect(schema).toEqual(mockSqlSchema);
|
|
282
|
+
expect(mockConnectionsApi.getSqlsource).toHaveBeenCalledWith('test-project', 'test-connection', 'SELECT * FROM test_table', {
|
|
283
|
+
headers: {
|
|
284
|
+
Authorization: 'Bearer test-token',
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
it('should handle API errors', async () => {
|
|
289
|
+
await setupAndTestApiError(mockConnectionsApi, 'getSqlsource', connection => connection.fetchSelectSchema({
|
|
290
|
+
selectStr: 'SELECT * FROM test_table',
|
|
291
|
+
connection: 'test-connection',
|
|
292
|
+
}));
|
|
293
|
+
});
|
|
294
|
+
it('should handle invalid JSON response', async () => {
|
|
295
|
+
await setupAndTestInvalidJsonResponse(mockConnectionsApi, 'getSqlsource', connection => connection.fetchSelectSchema({
|
|
296
|
+
selectStr: 'SELECT * FROM test_table',
|
|
297
|
+
connection: 'test-connection',
|
|
298
|
+
}));
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
describe('runSQL', () => {
|
|
302
|
+
let mockConnectionsApi;
|
|
303
|
+
let _mockConfiguration;
|
|
304
|
+
beforeEach(() => {
|
|
305
|
+
// Get fresh instances of the mocks
|
|
306
|
+
mockConnectionsApi = new client_1.ConnectionsApi(new client_1.Configuration());
|
|
307
|
+
_mockConfiguration = new client_1.Configuration();
|
|
308
|
+
});
|
|
309
|
+
afterEach(() => {
|
|
310
|
+
jest.clearAllMocks();
|
|
311
|
+
});
|
|
312
|
+
it('should run SQL query successfully', async () => {
|
|
313
|
+
const mockQueryData = {
|
|
314
|
+
rows: [
|
|
315
|
+
{ id: 1, name: 'test1' },
|
|
316
|
+
{ id: 2, name: 'test2' },
|
|
317
|
+
],
|
|
318
|
+
totalRows: 2,
|
|
319
|
+
};
|
|
320
|
+
const mockConnectionResponse = {
|
|
321
|
+
data: {
|
|
322
|
+
attributes: {
|
|
323
|
+
dialectName: 'bigquery',
|
|
324
|
+
isPool: false,
|
|
325
|
+
canPersist: true,
|
|
326
|
+
canStream: true,
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
status: 200,
|
|
330
|
+
statusText: 'OK',
|
|
331
|
+
headers: {},
|
|
332
|
+
config: {},
|
|
333
|
+
};
|
|
334
|
+
const mockQueryResponse = {
|
|
335
|
+
data: {
|
|
336
|
+
data: JSON.stringify(mockQueryData),
|
|
337
|
+
},
|
|
338
|
+
status: 200,
|
|
339
|
+
statusText: 'OK',
|
|
340
|
+
headers: {},
|
|
341
|
+
config: {},
|
|
342
|
+
};
|
|
343
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
344
|
+
mockConnectionsApi.getQuerydata.mockResolvedValueOnce(mockQueryResponse);
|
|
345
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
346
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
347
|
+
accessToken: 'test-token',
|
|
348
|
+
});
|
|
349
|
+
const result = await connection.runSQL('SELECT * FROM test_table');
|
|
350
|
+
expect(result).toEqual(mockQueryData);
|
|
351
|
+
expect(mockConnectionsApi.getQuerydata).toHaveBeenCalledWith('test-project', 'test-connection', 'SELECT * FROM test_table', '{}', {
|
|
352
|
+
headers: {
|
|
353
|
+
Authorization: 'Bearer test-token',
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
it('should run SQL query with options', async () => {
|
|
358
|
+
const mockQueryData = {
|
|
359
|
+
rows: [
|
|
360
|
+
{ id: 1, name: 'test1' },
|
|
361
|
+
{ id: 2, name: 'test2' },
|
|
362
|
+
],
|
|
363
|
+
totalRows: 2,
|
|
364
|
+
};
|
|
365
|
+
const mockConnectionResponse = {
|
|
366
|
+
data: {
|
|
367
|
+
attributes: {
|
|
368
|
+
dialectName: 'bigquery',
|
|
369
|
+
isPool: false,
|
|
370
|
+
canPersist: true,
|
|
371
|
+
canStream: true,
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
status: 200,
|
|
375
|
+
statusText: 'OK',
|
|
376
|
+
headers: {},
|
|
377
|
+
config: {},
|
|
378
|
+
};
|
|
379
|
+
const mockQueryResponse = {
|
|
380
|
+
data: {
|
|
381
|
+
data: JSON.stringify(mockQueryData),
|
|
382
|
+
},
|
|
383
|
+
status: 200,
|
|
384
|
+
statusText: 'OK',
|
|
385
|
+
headers: {},
|
|
386
|
+
config: {},
|
|
387
|
+
};
|
|
388
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
389
|
+
mockConnectionsApi.getQuerydata.mockResolvedValueOnce(mockQueryResponse);
|
|
390
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
391
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
392
|
+
accessToken: 'test-token',
|
|
393
|
+
});
|
|
394
|
+
const options = {
|
|
395
|
+
rowLimit: 100,
|
|
396
|
+
timeoutMs: 5000,
|
|
397
|
+
};
|
|
398
|
+
const result = await connection.runSQL('SELECT * FROM test_table', options);
|
|
399
|
+
expect(result).toEqual(mockQueryData);
|
|
400
|
+
expect(mockConnectionsApi.getQuerydata).toHaveBeenCalledWith('test-project', 'test-connection', 'SELECT * FROM test_table', JSON.stringify(options), {
|
|
401
|
+
headers: {
|
|
402
|
+
Authorization: 'Bearer test-token',
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
it('should handle API errors', async () => {
|
|
407
|
+
await setupAndTestApiError(mockConnectionsApi, 'getQuerydata', connection => connection.runSQL('SELECT * FROM test_table'));
|
|
408
|
+
});
|
|
409
|
+
it('should handle invalid JSON response', async () => {
|
|
410
|
+
await setupAndTestInvalidJsonResponse(mockConnectionsApi, 'getQuerydata', connection => connection.runSQL('SELECT * FROM test_table'), { data: 'invalid json' });
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
describe('runSQLStream', () => {
|
|
414
|
+
let mockConnectionsApi;
|
|
415
|
+
let _mockConfiguration;
|
|
416
|
+
beforeEach(() => {
|
|
417
|
+
// Get fresh instances of the mocks
|
|
418
|
+
mockConnectionsApi = new client_1.ConnectionsApi(new client_1.Configuration());
|
|
419
|
+
_mockConfiguration = new client_1.Configuration();
|
|
420
|
+
});
|
|
421
|
+
afterEach(() => {
|
|
422
|
+
jest.clearAllMocks();
|
|
423
|
+
});
|
|
424
|
+
it('should stream SQL query results successfully', async () => {
|
|
425
|
+
const mockQueryData = {
|
|
426
|
+
rows: [
|
|
427
|
+
{ id: 1, name: 'test1' },
|
|
428
|
+
{ id: 2, name: 'test2' },
|
|
429
|
+
],
|
|
430
|
+
totalRows: 2,
|
|
431
|
+
};
|
|
432
|
+
const mockConnectionResponse = {
|
|
433
|
+
data: {
|
|
434
|
+
attributes: {
|
|
435
|
+
dialectName: 'bigquery',
|
|
436
|
+
isPool: false,
|
|
437
|
+
canPersist: true,
|
|
438
|
+
canStream: true,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
status: 200,
|
|
442
|
+
statusText: 'OK',
|
|
443
|
+
headers: {},
|
|
444
|
+
config: {},
|
|
445
|
+
};
|
|
446
|
+
const mockQueryResponse = {
|
|
447
|
+
data: {
|
|
448
|
+
data: JSON.stringify(mockQueryData),
|
|
449
|
+
},
|
|
450
|
+
status: 200,
|
|
451
|
+
statusText: 'OK',
|
|
452
|
+
headers: {},
|
|
453
|
+
config: {},
|
|
454
|
+
};
|
|
455
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
456
|
+
mockConnectionsApi.getQuerydata.mockResolvedValueOnce(mockQueryResponse);
|
|
457
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
458
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
459
|
+
accessToken: 'test-token',
|
|
460
|
+
});
|
|
461
|
+
const stream = connection.runSQLStream('SELECT * FROM test_table');
|
|
462
|
+
const results = [];
|
|
463
|
+
for await (const row of stream) {
|
|
464
|
+
results.push(row);
|
|
465
|
+
}
|
|
466
|
+
expect(results).toEqual(mockQueryData.rows);
|
|
467
|
+
expect(mockConnectionsApi.getQuerydata).toHaveBeenCalledWith('test-project', 'test-connection', 'SELECT * FROM test_table', '{}', {
|
|
468
|
+
headers: {
|
|
469
|
+
Authorization: 'Bearer test-token',
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
it('should stream SQL query results with options', async () => {
|
|
474
|
+
const mockQueryData = {
|
|
475
|
+
rows: [
|
|
476
|
+
{ id: 1, name: 'test1' },
|
|
477
|
+
{ id: 2, name: 'test2' },
|
|
478
|
+
],
|
|
479
|
+
totalRows: 2,
|
|
480
|
+
};
|
|
481
|
+
const mockConnectionResponse = {
|
|
482
|
+
data: {
|
|
483
|
+
attributes: {
|
|
484
|
+
dialectName: 'bigquery',
|
|
485
|
+
isPool: false,
|
|
486
|
+
canPersist: true,
|
|
487
|
+
canStream: true,
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
status: 200,
|
|
491
|
+
statusText: 'OK',
|
|
492
|
+
headers: {},
|
|
493
|
+
config: {},
|
|
494
|
+
};
|
|
495
|
+
const mockQueryResponse = {
|
|
496
|
+
data: {
|
|
497
|
+
data: JSON.stringify(mockQueryData),
|
|
498
|
+
},
|
|
499
|
+
status: 200,
|
|
500
|
+
statusText: 'OK',
|
|
501
|
+
headers: {},
|
|
502
|
+
config: {},
|
|
503
|
+
};
|
|
504
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
505
|
+
mockConnectionsApi.getQuerydata.mockResolvedValueOnce(mockQueryResponse);
|
|
506
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
507
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
508
|
+
accessToken: 'test-token',
|
|
509
|
+
});
|
|
510
|
+
const options = {
|
|
511
|
+
rowLimit: 100,
|
|
512
|
+
timeoutMs: 5000,
|
|
513
|
+
};
|
|
514
|
+
const stream = connection.runSQLStream('SELECT * FROM test_table', options);
|
|
515
|
+
const results = [];
|
|
516
|
+
for await (const row of stream) {
|
|
517
|
+
results.push(row);
|
|
518
|
+
}
|
|
519
|
+
expect(results).toEqual(mockQueryData.rows);
|
|
520
|
+
expect(mockConnectionsApi.getQuerydata).toHaveBeenCalledWith('test-project', 'test-connection', 'SELECT * FROM test_table', JSON.stringify(options), {
|
|
521
|
+
headers: {
|
|
522
|
+
Authorization: 'Bearer test-token',
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
it('should handle API errors', async () => {
|
|
527
|
+
await setupAndTestApiError(mockConnectionsApi, 'getQuerydata', async (connection) => {
|
|
528
|
+
const stream = connection.runSQLStream('SELECT * FROM test_table');
|
|
529
|
+
const results = [];
|
|
530
|
+
for await (const row of stream) {
|
|
531
|
+
results.push(row);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
it('should handle invalid JSON response', async () => {
|
|
536
|
+
await setupAndTestInvalidJsonResponse(mockConnectionsApi, 'getQuerydata', async (connection) => {
|
|
537
|
+
const stream = connection.runSQLStream('SELECT * FROM test_table');
|
|
538
|
+
const results = [];
|
|
539
|
+
for await (const row of stream) {
|
|
540
|
+
results.push(row);
|
|
541
|
+
}
|
|
542
|
+
}, { data: 'invalid json' });
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
describe('manifestTemporaryTable', () => {
|
|
546
|
+
let mockConnectionsApi;
|
|
547
|
+
let _mockConfiguration;
|
|
548
|
+
beforeEach(() => {
|
|
549
|
+
// Get fresh instances of the mocks
|
|
550
|
+
mockConnectionsApi = new client_1.ConnectionsApi(new client_1.Configuration());
|
|
551
|
+
_mockConfiguration = new client_1.Configuration();
|
|
552
|
+
});
|
|
553
|
+
afterEach(() => {
|
|
554
|
+
jest.clearAllMocks();
|
|
555
|
+
});
|
|
556
|
+
it('should create temporary table successfully', async () => {
|
|
557
|
+
const mockConnectionResponse = {
|
|
558
|
+
data: {
|
|
559
|
+
attributes: {
|
|
560
|
+
dialectName: 'bigquery',
|
|
561
|
+
isPool: false,
|
|
562
|
+
canPersist: true,
|
|
563
|
+
canStream: true,
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
status: 200,
|
|
567
|
+
statusText: 'OK',
|
|
568
|
+
headers: {},
|
|
569
|
+
config: {},
|
|
570
|
+
};
|
|
571
|
+
const mockTableResponse = {
|
|
572
|
+
data: {
|
|
573
|
+
table: 'temp_table_123',
|
|
574
|
+
},
|
|
575
|
+
status: 200,
|
|
576
|
+
statusText: 'OK',
|
|
577
|
+
headers: {},
|
|
578
|
+
config: {},
|
|
579
|
+
};
|
|
580
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
581
|
+
mockConnectionsApi.getTemporarytable.mockResolvedValueOnce(mockTableResponse);
|
|
582
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
583
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
584
|
+
accessToken: 'test-token',
|
|
585
|
+
});
|
|
586
|
+
const tableName = await connection.manifestTemporaryTable('SELECT * FROM test_table');
|
|
587
|
+
expect(tableName).toBe('temp_table_123');
|
|
588
|
+
expect(mockConnectionsApi.getTemporarytable).toHaveBeenCalledWith('test-project', 'test-connection', 'SELECT * FROM test_table', {
|
|
589
|
+
headers: {
|
|
590
|
+
Authorization: 'Bearer test-token',
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
it('should handle API errors', async () => {
|
|
595
|
+
await setupAndTestApiError(mockConnectionsApi, 'getTemporarytable', connection => connection.manifestTemporaryTable('SELECT * FROM test_table'));
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
describe('test', () => {
|
|
599
|
+
let mockConnectionsApi;
|
|
600
|
+
let _mockConfiguration;
|
|
601
|
+
beforeEach(() => {
|
|
602
|
+
// Get fresh instances of the mocks
|
|
603
|
+
mockConnectionsApi = new client_1.ConnectionsApi(new client_1.Configuration());
|
|
604
|
+
_mockConfiguration = new client_1.Configuration();
|
|
605
|
+
});
|
|
606
|
+
afterEach(() => {
|
|
607
|
+
jest.clearAllMocks();
|
|
608
|
+
});
|
|
609
|
+
it('should test connection successfully', async () => {
|
|
610
|
+
const mockConnectionResponse = {
|
|
611
|
+
data: {
|
|
612
|
+
attributes: {
|
|
613
|
+
dialectName: 'bigquery',
|
|
614
|
+
isPool: false,
|
|
615
|
+
canPersist: true,
|
|
616
|
+
canStream: true,
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
status: 200,
|
|
620
|
+
statusText: 'OK',
|
|
621
|
+
headers: {},
|
|
622
|
+
config: {},
|
|
623
|
+
};
|
|
624
|
+
const mockTestResponse = {
|
|
625
|
+
data: undefined,
|
|
626
|
+
status: 200,
|
|
627
|
+
statusText: 'OK',
|
|
628
|
+
headers: {},
|
|
629
|
+
config: {},
|
|
630
|
+
};
|
|
631
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
632
|
+
mockConnectionsApi.getTest.mockResolvedValueOnce(mockTestResponse);
|
|
633
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
634
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
635
|
+
accessToken: 'test-token',
|
|
636
|
+
});
|
|
637
|
+
await expect(connection.test()).resolves.not.toThrow();
|
|
638
|
+
expect(mockConnectionsApi.getTest).toHaveBeenCalledWith('test-project', 'test-connection', {
|
|
639
|
+
headers: {
|
|
640
|
+
Authorization: 'Bearer test-token',
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
describe.skip('integration', () => {
|
|
647
|
+
let conn;
|
|
648
|
+
let runtime;
|
|
649
|
+
beforeEach(async () => {
|
|
650
|
+
conn = await publisher_connection_1.PublisherConnection.create('bigquery',
|
|
651
|
+
//{
|
|
652
|
+
//connectionUri: 'http://localhost:4000/api/v0/projects/malloy-samples/connections/bigquery',
|
|
653
|
+
//}
|
|
654
|
+
{
|
|
655
|
+
connectionUri: 'http://demo.data.pathways.localhost:8000/api/v0/projects/malloy-samples/connections/bigquery',
|
|
656
|
+
accessToken: 'xyz',
|
|
657
|
+
});
|
|
658
|
+
const files = {
|
|
659
|
+
readURL: async (url) => {
|
|
660
|
+
const filePath = (0, url_1.fileURLToPath)(url);
|
|
661
|
+
return await util.promisify(fs.readFile)(filePath, 'utf8');
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
runtime = new malloy.Runtime({
|
|
665
|
+
urlReader: files,
|
|
666
|
+
connection: conn,
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
afterEach(async () => {
|
|
670
|
+
await conn.close();
|
|
671
|
+
});
|
|
672
|
+
it('tests the connection', async () => {
|
|
673
|
+
await conn.test();
|
|
674
|
+
});
|
|
675
|
+
it('correctly identifies the dialect', () => {
|
|
676
|
+
expect(conn.dialectName).toBe('standardsql');
|
|
677
|
+
});
|
|
678
|
+
it('correctly identifies the connection as a pooled connection', () => {
|
|
679
|
+
expect(conn.isPool()).toBe(false);
|
|
680
|
+
});
|
|
681
|
+
it('correctly identifies the connection as a streaming connection', () => {
|
|
682
|
+
expect(conn.canStream()).toBe(true);
|
|
683
|
+
});
|
|
684
|
+
it('correctly identifies the connection as a persistSQLResults connection', () => {
|
|
685
|
+
expect(conn.canPersist()).toBe(true);
|
|
686
|
+
});
|
|
687
|
+
it('fetches the table schema', async () => {
|
|
688
|
+
const schema = await conn.fetchTableSchema('bigquery', 'bigquery-public-data.hacker_news.full');
|
|
689
|
+
expect(schema.type).toBe('table');
|
|
690
|
+
expect(schema.dialect).toBe('standardsql');
|
|
691
|
+
expect(schema.tablePath).toBe('bigquery-public-data.hacker_news.full');
|
|
692
|
+
expect(schema.fields.length).toBe(14);
|
|
693
|
+
expect(schema.fields[0].name).toBe('title');
|
|
694
|
+
expect(schema.fields[0].type).toBe('string');
|
|
695
|
+
});
|
|
696
|
+
it('fetches the sql source schema', async () => {
|
|
697
|
+
const schema = await conn.fetchSelectSchema({
|
|
698
|
+
connection: 'bigquery',
|
|
699
|
+
selectStr: 'SELECT * FROM bigquery-public-data.hacker_news.full',
|
|
700
|
+
});
|
|
701
|
+
expect(schema.type).toBe('sql_select');
|
|
702
|
+
expect(schema.dialect).toBe('standardsql');
|
|
703
|
+
expect(schema.fields.length).toBe(14);
|
|
704
|
+
expect(schema.fields[0].name).toBe('title');
|
|
705
|
+
expect(schema.fields[0].type).toBe('string');
|
|
706
|
+
});
|
|
707
|
+
it('runs a SQL query', async () => {
|
|
708
|
+
const res = await conn.runSQL('SELECT 1 as T');
|
|
709
|
+
expect(res.rows[0]['T']).toBe(1);
|
|
710
|
+
});
|
|
711
|
+
it('runs a Malloy query', async () => {
|
|
712
|
+
var _a;
|
|
713
|
+
const sql = await runtime
|
|
714
|
+
.loadModel("source: stories is bigquery.table('bigquery-public-data.hacker_news.full')")
|
|
715
|
+
.loadQuery('run: stories -> { aggregate: cnt is count() group_by: `by` order_by: cnt desc limit: 10 }')
|
|
716
|
+
.getSQL();
|
|
717
|
+
const res = await conn.runSQL(sql);
|
|
718
|
+
expect(res.totalRows).toBe(10);
|
|
719
|
+
let total = 0;
|
|
720
|
+
for (const row of res.rows) {
|
|
721
|
+
total += +((_a = row['cnt']) !== null && _a !== void 0 ? _a : 0);
|
|
722
|
+
}
|
|
723
|
+
expect(total).toBe(1836679);
|
|
724
|
+
});
|
|
725
|
+
it('runs a Malloy query on an sql source', async () => {
|
|
726
|
+
const sql = await runtime
|
|
727
|
+
.loadModel("source: stories is bigquery.sql('SELECT * FROM bigquery-public-data.hacker_news.full')")
|
|
728
|
+
.loadQuery('run: stories -> { aggregate: cnt is count() group_by: `by` order_by: cnt desc limit: 20 }')
|
|
729
|
+
.getSQL();
|
|
730
|
+
const res = await conn.runSQL(sql);
|
|
731
|
+
expect(res.totalRows).toBe(20);
|
|
732
|
+
expect(res.rows[0]['cnt']).toBe(1346912);
|
|
733
|
+
});
|
|
734
|
+
it('get temporary table name', async () => {
|
|
735
|
+
const sql = 'SELECT 1 as T';
|
|
736
|
+
const tempTableName = await conn.manifestTemporaryTable(sql);
|
|
737
|
+
expect(tempTableName).toBeDefined();
|
|
738
|
+
expect(tempTableName.startsWith('lofty-complex-452701')).toBe(true);
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
// helper function for handling API errors test cases
|
|
743
|
+
async function testErrorHandling(connection, operation, errorMessage) {
|
|
744
|
+
if (errorMessage) {
|
|
745
|
+
await expect(operation()).rejects.toThrow(errorMessage);
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
await expect(operation()).rejects.toThrow();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
// handles API errors test cases
|
|
752
|
+
async function setupAndTestApiError(mockConnectionsApi, apiMethod, operation, errorMessage = 'API Error') {
|
|
753
|
+
const mockConnectionResponse = {
|
|
754
|
+
data: {
|
|
755
|
+
attributes: {
|
|
756
|
+
dialectName: 'bigquery',
|
|
757
|
+
isPool: false,
|
|
758
|
+
canPersist: true,
|
|
759
|
+
canStream: true,
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
status: 200,
|
|
763
|
+
statusText: 'OK',
|
|
764
|
+
headers: {},
|
|
765
|
+
config: {},
|
|
766
|
+
};
|
|
767
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
768
|
+
mockConnectionsApi[apiMethod].mockRejectedValueOnce(new Error(errorMessage));
|
|
769
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
770
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
771
|
+
accessToken: 'test-token',
|
|
772
|
+
});
|
|
773
|
+
await testErrorHandling(connection, () => operation(connection), errorMessage);
|
|
774
|
+
}
|
|
775
|
+
// handles invalid JSON response test cases
|
|
776
|
+
async function setupAndTestInvalidJsonResponse(mockConnectionsApi, apiMethod, operation, responseData = { source: 'invalid json' }) {
|
|
777
|
+
const mockConnectionResponse = {
|
|
778
|
+
data: {
|
|
779
|
+
attributes: {
|
|
780
|
+
dialectName: 'bigquery',
|
|
781
|
+
isPool: false,
|
|
782
|
+
canPersist: true,
|
|
783
|
+
canStream: true,
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
status: 200,
|
|
787
|
+
statusText: 'OK',
|
|
788
|
+
headers: {},
|
|
789
|
+
config: {},
|
|
790
|
+
};
|
|
791
|
+
const mockInvalidResponse = {
|
|
792
|
+
data: responseData,
|
|
793
|
+
status: 200,
|
|
794
|
+
statusText: 'OK',
|
|
795
|
+
headers: {},
|
|
796
|
+
config: {},
|
|
797
|
+
};
|
|
798
|
+
mockConnectionsApi.getConnection.mockResolvedValueOnce(mockConnectionResponse);
|
|
799
|
+
mockConnectionsApi[apiMethod].mockResolvedValueOnce(mockInvalidResponse);
|
|
800
|
+
const connection = await publisher_connection_1.PublisherConnection.create('test-connection', {
|
|
801
|
+
connectionUri: 'http://test.com/api/v0/projects/test-project/connections/test-connection',
|
|
802
|
+
accessToken: 'test-token',
|
|
803
|
+
});
|
|
804
|
+
await testErrorHandling(connection, () => operation(connection));
|
|
805
|
+
}
|
|
806
|
+
//# sourceMappingURL=publisher_connection.spec.js.map
|