@lightdash/warehouses 2.228.0 → 2.229.0
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/.tsbuildinfo.build +1 -0
- package/package.json +4 -4
- package/dist/.tsbuildinfo +0 -1
- package/dist/ssh/sshTunnelFailure.test.d.ts +0 -2
- package/dist/ssh/sshTunnelFailure.test.d.ts.map +0 -1
- package/dist/ssh/sshTunnelFailure.test.js +0 -90
- package/dist/validateWarehouseColumnReferences.test.d.ts +0 -2
- package/dist/validateWarehouseColumnReferences.test.d.ts.map +0 -1
- package/dist/validateWarehouseColumnReferences.test.js +0 -168
- package/dist/warehouseClients/AthenaWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/AthenaWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/AthenaWarehouseClient.test.js +0 -261
- package/dist/warehouseClients/BigqueryWarehouseClient.mock.d.ts +0 -83
- package/dist/warehouseClients/BigqueryWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/BigqueryWarehouseClient.mock.js +0 -130
- package/dist/warehouseClients/BigqueryWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/BigqueryWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/BigqueryWarehouseClient.test.js +0 -493
- package/dist/warehouseClients/ClickhouseWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/ClickhouseWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/ClickhouseWarehouseClient.test.js +0 -115
- package/dist/warehouseClients/DatabricksWarehouseClient.mock.d.ts +0 -17
- package/dist/warehouseClients/DatabricksWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/DatabricksWarehouseClient.mock.js +0 -111
- package/dist/warehouseClients/DatabricksWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/DatabricksWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/DatabricksWarehouseClient.test.js +0 -592
- package/dist/warehouseClients/DatabricksWarehouseStartupRetry.test.d.ts +0 -2
- package/dist/warehouseClients/DatabricksWarehouseStartupRetry.test.d.ts.map +0 -1
- package/dist/warehouseClients/DatabricksWarehouseStartupRetry.test.js +0 -129
- package/dist/warehouseClients/DuckdbWarehouseClient.embedded.test.d.ts +0 -2
- package/dist/warehouseClients/DuckdbWarehouseClient.embedded.test.d.ts.map +0 -1
- package/dist/warehouseClients/DuckdbWarehouseClient.embedded.test.js +0 -92
- package/dist/warehouseClients/DuckdbWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/DuckdbWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/DuckdbWarehouseClient.test.js +0 -1749
- package/dist/warehouseClients/MotherduckErrorClassifier.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckErrorClassifier.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckErrorClassifier.test.js +0 -31
- package/dist/warehouseClients/MotherduckInstanceCache.catalog.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckInstanceCache.catalog.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckInstanceCache.catalog.test.js +0 -52
- package/dist/warehouseClients/MotherduckInstanceCache.integration.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckInstanceCache.integration.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckInstanceCache.integration.test.js +0 -172
- package/dist/warehouseClients/MotherduckInstanceCache.security.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckInstanceCache.security.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckInstanceCache.security.test.js +0 -192
- package/dist/warehouseClients/MotherduckInstanceCache.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckInstanceCache.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckInstanceCache.test.js +0 -302
- package/dist/warehouseClients/PostgresWarehouseClient.mock.d.ts +0 -8
- package/dist/warehouseClients/PostgresWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/PostgresWarehouseClient.mock.js +0 -66
- package/dist/warehouseClients/PostgresWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/PostgresWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/PostgresWarehouseClient.test.js +0 -371
- package/dist/warehouseClients/RedshiftWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/RedshiftWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/RedshiftWarehouseClient.test.js +0 -126
- package/dist/warehouseClients/SnowflakeWarehouseClient.mock.d.ts +0 -41
- package/dist/warehouseClients/SnowflakeWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/SnowflakeWarehouseClient.mock.js +0 -184
- package/dist/warehouseClients/SnowflakeWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/SnowflakeWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/SnowflakeWarehouseClient.test.js +0 -777
- package/dist/warehouseClients/TrinoWarehouseClient.mock.d.ts +0 -23
- package/dist/warehouseClients/TrinoWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/TrinoWarehouseClient.mock.js +0 -102
- package/dist/warehouseClients/TrinoWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/TrinoWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/TrinoWarehouseClient.test.js +0 -230
- package/dist/warehouseClients/WarehouseClient.mock.d.ts +0 -13
- package/dist/warehouseClients/WarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/WarehouseClient.mock.js +0 -51
- package/dist/warehouseClients/numericKinds.test.d.ts +0 -2
- package/dist/warehouseClients/numericKinds.test.d.ts.map +0 -1
- package/dist/warehouseClients/numericKinds.test.js +0 -168
- package/dist/warehouseClients/pgProtocolGuard.integration.test.d.ts +0 -2
- package/dist/warehouseClients/pgProtocolGuard.integration.test.d.ts.map +0 -1
- package/dist/warehouseClients/pgProtocolGuard.integration.test.js +0 -93
- package/dist/warehouseClients/pgProtocolGuard.test.d.ts +0 -2
- package/dist/warehouseClients/pgProtocolGuard.test.d.ts.map +0 -1
- package/dist/warehouseClients/pgProtocolGuard.test.js +0 -48
- package/dist/warehouseClients/redshiftIamCredentials.test.d.ts +0 -2
- package/dist/warehouseClients/redshiftIamCredentials.test.d.ts.map +0 -1
- package/dist/warehouseClients/redshiftIamCredentials.test.js +0 -210
- package/dist/warehouseClients/timestampDomain.test.d.ts +0 -2
- package/dist/warehouseClients/timestampDomain.test.d.ts.map +0 -1
- package/dist/warehouseClients/timestampDomain.test.js +0 -101
|
@@ -1,777 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const common_1 = require("@lightdash/common");
|
|
4
|
-
const snowflake_sdk_1 = require("snowflake-sdk");
|
|
5
|
-
const stream_1 = require("stream");
|
|
6
|
-
const SnowflakeWarehouseClient_1 = require("./SnowflakeWarehouseClient");
|
|
7
|
-
const SnowflakeWarehouseClient_mock_1 = require("./SnowflakeWarehouseClient.mock");
|
|
8
|
-
const WarehouseClient_mock_1 = require("./WarehouseClient.mock");
|
|
9
|
-
const mockStreamRows = () => new stream_1.Readable({
|
|
10
|
-
objectMode: true,
|
|
11
|
-
read() {
|
|
12
|
-
this.push(SnowflakeWarehouseClient_mock_1.expectedRow);
|
|
13
|
-
this.push(null);
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
const executeMock = vi.fn(({ sqlText, complete }) => {
|
|
17
|
-
complete(undefined, {
|
|
18
|
-
streamRows: mockStreamRows,
|
|
19
|
-
getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock,
|
|
20
|
-
getQueryId: () => 'queryId',
|
|
21
|
-
getSqlText: () => sqlText,
|
|
22
|
-
}, []);
|
|
23
|
-
});
|
|
24
|
-
const getResultsFromQueryIdMock = vi.fn(({ sqlText, queryId }) => ({
|
|
25
|
-
streamRows: mockStreamRows,
|
|
26
|
-
getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock,
|
|
27
|
-
getQueryId: () => queryId,
|
|
28
|
-
getNumRows: () => 1,
|
|
29
|
-
}));
|
|
30
|
-
const oauthAccessToken = (expiresAtSeconds) => `header.${Buffer.from(JSON.stringify({ exp: expiresAtSeconds })).toString('base64url')}.signature`;
|
|
31
|
-
const interactiveConnectionMock = (execute) => ({
|
|
32
|
-
connect: vi.fn((callback) => callback(undefined, {})),
|
|
33
|
-
connectAsync: vi.fn((callback) => {
|
|
34
|
-
callback(undefined, {});
|
|
35
|
-
return Promise.resolve();
|
|
36
|
-
}),
|
|
37
|
-
execute,
|
|
38
|
-
destroy: vi.fn((callback) => callback(undefined, {})),
|
|
39
|
-
});
|
|
40
|
-
const getConfiguredCredentialManager = () => {
|
|
41
|
-
const manager = vi.mocked(snowflake_sdk_1.configure).mock.lastCall?.[0]?.customCredentialManager;
|
|
42
|
-
if (!manager ||
|
|
43
|
-
typeof manager !== 'object' ||
|
|
44
|
-
!('write' in manager) ||
|
|
45
|
-
typeof manager.write !== 'function') {
|
|
46
|
-
throw new Error('OAuth credential manager was not configured');
|
|
47
|
-
}
|
|
48
|
-
return manager;
|
|
49
|
-
};
|
|
50
|
-
vi.mock('snowflake-sdk', async () => ({
|
|
51
|
-
...(await vi.importActual('snowflake-sdk')).default,
|
|
52
|
-
configure: vi.fn(),
|
|
53
|
-
createConnection: vi.fn(() => ({
|
|
54
|
-
connect: vi.fn((callback) => callback(null, {})),
|
|
55
|
-
execute: executeMock,
|
|
56
|
-
destroy: vi.fn((callback) => callback(null, {})),
|
|
57
|
-
getResultsFromQueryId: getResultsFromQueryIdMock,
|
|
58
|
-
getQueryStatus: vi.fn(() => 'SUCCESS'),
|
|
59
|
-
isStillRunning: vi.fn(() => false),
|
|
60
|
-
})),
|
|
61
|
-
}));
|
|
62
|
-
describe('SnowflakeWarehouseClient', () => {
|
|
63
|
-
beforeEach(() => {
|
|
64
|
-
vi.clearAllMocks();
|
|
65
|
-
});
|
|
66
|
-
it('caps result chunk size via CLIENT_RESULT_CHUNK_SIZE session parameter', async () => {
|
|
67
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient(SnowflakeWarehouseClient_mock_1.credentials);
|
|
68
|
-
await warehouse.streamQuery('SELECT 1', () => { }, {});
|
|
69
|
-
const alterSessionSql = executeMock.mock.calls
|
|
70
|
-
.map((call) => call[0].sqlText)
|
|
71
|
-
.find((sql) => sql.startsWith('ALTER SESSION SET'));
|
|
72
|
-
expect(alterSessionSql).toContain('CLIENT_RESULT_CHUNK_SIZE = 16');
|
|
73
|
-
});
|
|
74
|
-
it.each(['', null, undefined, NaN])('falls back to the default STATEMENT_TIMEOUT_IN_SECONDS when timeoutSeconds is %p', async (timeoutSeconds) => {
|
|
75
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
76
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
77
|
-
timeoutSeconds: timeoutSeconds,
|
|
78
|
-
});
|
|
79
|
-
await warehouse.streamQuery('SELECT 1', () => { }, {});
|
|
80
|
-
const alterSessionSql = executeMock.mock.calls
|
|
81
|
-
.map((call) => call[0].sqlText)
|
|
82
|
-
.find((sql) => sql.startsWith('ALTER SESSION SET'));
|
|
83
|
-
expect(alterSessionSql).toContain('STATEMENT_TIMEOUT_IN_SECONDS = 300');
|
|
84
|
-
});
|
|
85
|
-
it('keeps an explicit STATEMENT_TIMEOUT_IN_SECONDS of 0 (no timeout)', async () => {
|
|
86
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
87
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
88
|
-
timeoutSeconds: 0,
|
|
89
|
-
});
|
|
90
|
-
await warehouse.streamQuery('SELECT 1', () => { }, {});
|
|
91
|
-
const alterSessionSql = executeMock.mock.calls
|
|
92
|
-
.map((call) => call[0].sqlText)
|
|
93
|
-
.find((sql) => sql.startsWith('ALTER SESSION SET'));
|
|
94
|
-
expect(alterSessionSql).toContain('STATEMENT_TIMEOUT_IN_SECONDS = 0,');
|
|
95
|
-
});
|
|
96
|
-
it('configures the local application authorization-code flow with SDK defaults', () => {
|
|
97
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
98
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
99
|
-
authenticationType: common_1.SnowflakeAuthenticationType.OAUTH_AUTHORIZATION_CODE,
|
|
100
|
-
});
|
|
101
|
-
expect(warehouse.connectionOptions).toEqual(expect.objectContaining({
|
|
102
|
-
account: SnowflakeWarehouseClient_mock_1.credentials.account,
|
|
103
|
-
authenticator: 'OAUTH_AUTHORIZATION_CODE',
|
|
104
|
-
clientStoreTemporaryCredential: true,
|
|
105
|
-
browserActionTimeout: 300000,
|
|
106
|
-
}));
|
|
107
|
-
expect(warehouse.connectionOptions).not.toHaveProperty('username');
|
|
108
|
-
expect(warehouse.connectionOptions).not.toHaveProperty('oauthClientId');
|
|
109
|
-
expect(warehouse.connectionOptions).not.toHaveProperty('oauthAuthorizationUrl');
|
|
110
|
-
expect(warehouse.connectionOptions).not.toHaveProperty('oauthTokenRequestUrl');
|
|
111
|
-
expect(warehouse.connectionOptions).not.toHaveProperty('oauthRedirectUri');
|
|
112
|
-
expect(warehouse.connectionOptions).not.toHaveProperty('oauthEnableSingleUseRefreshTokens');
|
|
113
|
-
expect(warehouse.connectionOptions).not.toHaveProperty('oauthClientSecret');
|
|
114
|
-
expect(vi.mocked(snowflake_sdk_1.configure)).toHaveBeenCalledWith({
|
|
115
|
-
customCredentialManager: expect.any(Object),
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
it('exposes authorization-code tokens captured by the SDK credential manager', async () => {
|
|
119
|
-
const expiresAtSeconds = 2000000000;
|
|
120
|
-
const accessToken = oauthAccessToken(expiresAtSeconds);
|
|
121
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
122
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
123
|
-
authenticationType: common_1.SnowflakeAuthenticationType.OAUTH_AUTHORIZATION_CODE,
|
|
124
|
-
});
|
|
125
|
-
const customCredentialManager = getConfiguredCredentialManager();
|
|
126
|
-
await customCredentialManager.write('{HOST}:{USER}:{OAUTH_AUTHORIZATION_CODE_ACCESS_TOKEN}', accessToken);
|
|
127
|
-
await customCredentialManager.write('{HOST}:{USER}:{OAUTH_AUTHORIZATION_CODE_REFRESH_TOKEN}', 'refresh-token');
|
|
128
|
-
expect(warehouse.getOAuthTokens()).toEqual({
|
|
129
|
-
accessToken,
|
|
130
|
-
refreshToken: 'refresh-token',
|
|
131
|
-
expiresAt: new Date(expiresAtSeconds * 1000),
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
it('inspects key fingerprints and changes only the selected user key slot', async () => {
|
|
135
|
-
const execute = vi.fn(({ sqlText, complete, }) => {
|
|
136
|
-
const rows = sqlText.startsWith('DESCRIBE USER')
|
|
137
|
-
? [
|
|
138
|
-
{
|
|
139
|
-
property: 'RSA_PUBLIC_KEY_FP',
|
|
140
|
-
value: 'SHA256:occupied',
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
property: 'RSA_PUBLIC_KEY_2_FP',
|
|
144
|
-
value: 'null',
|
|
145
|
-
},
|
|
146
|
-
]
|
|
147
|
-
: [];
|
|
148
|
-
complete(undefined, { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock }, rows);
|
|
149
|
-
});
|
|
150
|
-
vi.mocked(snowflake_sdk_1.createConnection).mockImplementationOnce(() => interactiveConnectionMock(execute));
|
|
151
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
152
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
153
|
-
user: 'Mixed User',
|
|
154
|
-
authenticationType: common_1.SnowflakeAuthenticationType.OAUTH_AUTHORIZATION_CODE,
|
|
155
|
-
});
|
|
156
|
-
await expect(warehouse.getUserPublicKeySlots('Mixed User')).resolves.toEqual({
|
|
157
|
-
RSA_PUBLIC_KEY: 'SHA256:occupied',
|
|
158
|
-
RSA_PUBLIC_KEY_2: null,
|
|
159
|
-
});
|
|
160
|
-
await warehouse.setUserPublicKey('Mixed User', 'RSA_PUBLIC_KEY_2', 'dGVzdA==');
|
|
161
|
-
await warehouse.unsetUserPublicKey('Mixed User', 'RSA_PUBLIC_KEY_2');
|
|
162
|
-
expect(snowflake_sdk_1.createConnection).toHaveBeenCalledTimes(1);
|
|
163
|
-
expect(execute).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
|
164
|
-
sqlText: 'DESCRIBE USER "Mixed User"',
|
|
165
|
-
}));
|
|
166
|
-
expect(execute).toHaveBeenNthCalledWith(2, expect.objectContaining({
|
|
167
|
-
sqlText: 'ALTER USER "Mixed User" SET RSA_PUBLIC_KEY_2 = \'dGVzdA==\'',
|
|
168
|
-
}));
|
|
169
|
-
expect(execute).toHaveBeenNthCalledWith(3, expect.objectContaining({
|
|
170
|
-
sqlText: 'ALTER USER "Mixed User" UNSET RSA_PUBLIC_KEY_2',
|
|
171
|
-
}));
|
|
172
|
-
});
|
|
173
|
-
it.each([
|
|
174
|
-
common_1.SnowflakeAuthenticationType.EXTERNAL_BROWSER,
|
|
175
|
-
common_1.SnowflakeAuthenticationType.OAUTH_AUTHORIZATION_CODE,
|
|
176
|
-
])('replaces an existing named PAT for a %s session and scopes the new token to a role', async (authenticationType) => {
|
|
177
|
-
const execute = vi.fn(({ sqlText, complete, }) => {
|
|
178
|
-
let rows = [];
|
|
179
|
-
if (sqlText.startsWith('SHOW USER')) {
|
|
180
|
-
rows = [
|
|
181
|
-
{
|
|
182
|
-
name: 'LIGHTDASH_ONBOARDING_11111111',
|
|
183
|
-
status: 'ACTIVE',
|
|
184
|
-
},
|
|
185
|
-
];
|
|
186
|
-
}
|
|
187
|
-
else if (sqlText.startsWith('ALTER USER ADD')) {
|
|
188
|
-
rows = [{ TOKEN_SECRET: 'pat-secret' }];
|
|
189
|
-
}
|
|
190
|
-
complete(undefined, { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock }, rows);
|
|
191
|
-
});
|
|
192
|
-
vi.mocked(snowflake_sdk_1.createConnection).mockImplementationOnce(() => interactiveConnectionMock(execute));
|
|
193
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
194
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
195
|
-
authenticationType,
|
|
196
|
-
});
|
|
197
|
-
await expect(warehouse.createProgrammaticAccessToken('LIGHTDASH_ONBOARDING_11111111', 365, 1440, "ANALYTICS'ROLE")).resolves.toEqual({
|
|
198
|
-
tokenName: 'LIGHTDASH_ONBOARDING_11111111',
|
|
199
|
-
tokenSecret: 'pat-secret',
|
|
200
|
-
});
|
|
201
|
-
expect(execute).toHaveBeenNthCalledWith(2, expect.objectContaining({
|
|
202
|
-
sqlText: 'ALTER USER REMOVE PROGRAMMATIC ACCESS TOKEN LIGHTDASH_ONBOARDING_11111111',
|
|
203
|
-
}));
|
|
204
|
-
expect(execute).toHaveBeenNthCalledWith(3, expect.objectContaining({
|
|
205
|
-
sqlText: expect.stringContaining("ROLE_RESTRICTION = 'ANALYTICS''ROLE'"),
|
|
206
|
-
}));
|
|
207
|
-
});
|
|
208
|
-
it('prunes only non-active Lightdash onboarding PATs', async () => {
|
|
209
|
-
const execute = vi.fn(({ sqlText, complete, }) => {
|
|
210
|
-
let rows = [];
|
|
211
|
-
if (sqlText.startsWith('SHOW USER')) {
|
|
212
|
-
rows = [
|
|
213
|
-
{
|
|
214
|
-
name: 'LIGHTDASH_ONBOARDING_EXPIRED',
|
|
215
|
-
status: 'EXPIRED',
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
name: 'LIGHTDASH_ONBOARDING_DISABLED',
|
|
219
|
-
status: 'DISABLED',
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
name: 'LIGHTDASH_ONBOARDING_ACTIVE',
|
|
223
|
-
status: 'ACTIVE',
|
|
224
|
-
},
|
|
225
|
-
{ name: 'OTHER_EXPIRED', status: 'EXPIRED' },
|
|
226
|
-
{ name: 'OTHER_ACTIVE', status: 'ACTIVE' },
|
|
227
|
-
];
|
|
228
|
-
}
|
|
229
|
-
else if (sqlText.startsWith('ALTER USER ADD')) {
|
|
230
|
-
rows = [{ TOKEN_SECRET: 'pat-secret' }];
|
|
231
|
-
}
|
|
232
|
-
complete(undefined, { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock }, rows);
|
|
233
|
-
});
|
|
234
|
-
vi.mocked(snowflake_sdk_1.createConnection).mockImplementationOnce(() => interactiveConnectionMock(execute));
|
|
235
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
236
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
237
|
-
authenticationType: common_1.SnowflakeAuthenticationType.OAUTH_AUTHORIZATION_CODE,
|
|
238
|
-
});
|
|
239
|
-
await expect(warehouse.createProgrammaticAccessToken('LIGHTDASH_ONBOARDING_NEW')).resolves.toEqual({
|
|
240
|
-
tokenName: 'LIGHTDASH_ONBOARDING_NEW',
|
|
241
|
-
tokenSecret: 'pat-secret',
|
|
242
|
-
});
|
|
243
|
-
expect(execute).toHaveBeenCalledWith(expect.objectContaining({
|
|
244
|
-
sqlText: 'ALTER USER REMOVE PROGRAMMATIC ACCESS TOKEN LIGHTDASH_ONBOARDING_EXPIRED',
|
|
245
|
-
}));
|
|
246
|
-
expect(execute).toHaveBeenCalledWith(expect.objectContaining({
|
|
247
|
-
sqlText: 'ALTER USER REMOVE PROGRAMMATIC ACCESS TOKEN LIGHTDASH_ONBOARDING_DISABLED',
|
|
248
|
-
}));
|
|
249
|
-
expect(execute).not.toHaveBeenCalledWith(expect.objectContaining({
|
|
250
|
-
sqlText: 'ALTER USER REMOVE PROGRAMMATIC ACCESS TOKEN LIGHTDASH_ONBOARDING_ACTIVE',
|
|
251
|
-
}));
|
|
252
|
-
expect(execute).not.toHaveBeenCalledWith(expect.objectContaining({
|
|
253
|
-
sqlText: 'ALTER USER REMOVE PROGRAMMATIC ACCESS TOKEN OTHER_EXPIRED',
|
|
254
|
-
}));
|
|
255
|
-
expect(execute).not.toHaveBeenCalledWith(expect.objectContaining({
|
|
256
|
-
sqlText: 'ALTER USER REMOVE PROGRAMMATIC ACCESS TOKEN OTHER_ACTIVE',
|
|
257
|
-
}));
|
|
258
|
-
});
|
|
259
|
-
it('creates a PAT when pruning an inactive PAT fails', async () => {
|
|
260
|
-
const execute = vi.fn(({ sqlText, complete, }) => {
|
|
261
|
-
const statement = { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock };
|
|
262
|
-
if (sqlText.startsWith('SHOW USER')) {
|
|
263
|
-
complete(undefined, statement, [
|
|
264
|
-
{
|
|
265
|
-
name: 'LIGHTDASH_ONBOARDING_EXPIRED',
|
|
266
|
-
status: 'EXPIRED',
|
|
267
|
-
},
|
|
268
|
-
]);
|
|
269
|
-
}
|
|
270
|
-
else if (sqlText.startsWith('ALTER USER REMOVE')) {
|
|
271
|
-
complete(new Error('Failed to remove PAT'), statement, []);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
complete(undefined, statement, [
|
|
275
|
-
{ TOKEN_SECRET: 'pat-secret' },
|
|
276
|
-
]);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
vi.mocked(snowflake_sdk_1.createConnection).mockImplementationOnce(() => interactiveConnectionMock(execute));
|
|
280
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
281
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
282
|
-
authenticationType: common_1.SnowflakeAuthenticationType.OAUTH_AUTHORIZATION_CODE,
|
|
283
|
-
});
|
|
284
|
-
await expect(warehouse.createProgrammaticAccessToken('LIGHTDASH_ONBOARDING_NEW')).resolves.toEqual({
|
|
285
|
-
tokenName: 'LIGHTDASH_ONBOARDING_NEW',
|
|
286
|
-
tokenSecret: 'pat-secret',
|
|
287
|
-
});
|
|
288
|
-
expect(execute).toHaveBeenCalledWith(expect.objectContaining({
|
|
289
|
-
sqlText: expect.stringMatching(/^ALTER USER ADD PROGRAMMATIC ACCESS TOKEN LIGHTDASH_ONBOARDING_NEW/),
|
|
290
|
-
}));
|
|
291
|
-
});
|
|
292
|
-
it('discovers session defaults and capped inventory for interactive sessions', async () => {
|
|
293
|
-
const execute = vi.fn(({ sqlText, complete, }) => {
|
|
294
|
-
let rows = [];
|
|
295
|
-
if (sqlText.startsWith('SELECT CURRENT_USER')) {
|
|
296
|
-
rows = [
|
|
297
|
-
{
|
|
298
|
-
USER: 'user.name@example.com',
|
|
299
|
-
ROLE: 'ANALYST',
|
|
300
|
-
WAREHOUSE: 'TRANSFORMING',
|
|
301
|
-
DATABASE: 'ANALYTICS',
|
|
302
|
-
SCHEMA: 'PUBLIC',
|
|
303
|
-
},
|
|
304
|
-
];
|
|
305
|
-
}
|
|
306
|
-
else if (sqlText.startsWith('SHOW DATABASES')) {
|
|
307
|
-
rows = Array.from({ length: 125 }, (_, index) => ({
|
|
308
|
-
name: `DATABASE_${index}`,
|
|
309
|
-
comment: index === 0 ? 'Primary analytics' : '',
|
|
310
|
-
kind: index === 1 ? 'IMPORTED DATABASE' : 'STANDARD',
|
|
311
|
-
}));
|
|
312
|
-
}
|
|
313
|
-
else if (sqlText.startsWith('SHOW WAREHOUSES')) {
|
|
314
|
-
rows = [
|
|
315
|
-
{
|
|
316
|
-
name: 'TRANSFORMING',
|
|
317
|
-
size: 'X-Small',
|
|
318
|
-
state: 'SUSPENDED',
|
|
319
|
-
auto_suspend: '300',
|
|
320
|
-
},
|
|
321
|
-
];
|
|
322
|
-
}
|
|
323
|
-
else if (sqlText.startsWith('SHOW GRANTS')) {
|
|
324
|
-
rows = [{ role: 'ANALYST' }, { role: 'REPORTER' }];
|
|
325
|
-
}
|
|
326
|
-
else if (sqlText.startsWith('SHOW SCHEMAS')) {
|
|
327
|
-
rows = [
|
|
328
|
-
{ name: 'PUBLIC', database_name: 'ANALYTICS' },
|
|
329
|
-
{ name: 'JAFFLE', database_name: 'ANALYTICS' },
|
|
330
|
-
{
|
|
331
|
-
name: 'INFORMATION_SCHEMA',
|
|
332
|
-
database_name: 'ANALYTICS',
|
|
333
|
-
},
|
|
334
|
-
];
|
|
335
|
-
}
|
|
336
|
-
else if (sqlText.startsWith('SELECT SUM(bytes)')) {
|
|
337
|
-
if (sqlText.includes('DATABASE_0.')) {
|
|
338
|
-
rows = [{ total_bytes: 1250 }];
|
|
339
|
-
}
|
|
340
|
-
else if (sqlText.includes('DATABASE_1.')) {
|
|
341
|
-
rows = [{ total_bytes: 500 }];
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
rows = [{ total_bytes: null }];
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
complete(undefined, { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock }, rows);
|
|
348
|
-
});
|
|
349
|
-
vi.mocked(snowflake_sdk_1.createConnection).mockImplementationOnce(() => interactiveConnectionMock(execute));
|
|
350
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
351
|
-
...SnowflakeWarehouseClient_mock_1.credentials,
|
|
352
|
-
authenticationType: common_1.SnowflakeAuthenticationType.OAUTH_AUTHORIZATION_CODE,
|
|
353
|
-
});
|
|
354
|
-
await expect(warehouse.getSessionDiscovery('user.name@example.com')).resolves.toEqual({
|
|
355
|
-
user: 'user.name@example.com',
|
|
356
|
-
defaults: {
|
|
357
|
-
role: 'ANALYST',
|
|
358
|
-
warehouse: 'TRANSFORMING',
|
|
359
|
-
database: 'ANALYTICS',
|
|
360
|
-
schema: 'PUBLIC',
|
|
361
|
-
},
|
|
362
|
-
inventory: {
|
|
363
|
-
databases: Array.from({ length: 100 }, (_, index) => ({
|
|
364
|
-
name: `DATABASE_${index}`,
|
|
365
|
-
comment: index === 0 ? 'Primary analytics' : null,
|
|
366
|
-
kind: index === 1 ? 'IMPORTED DATABASE' : 'STANDARD',
|
|
367
|
-
sizeBytes: [1250, 500][index] ?? null,
|
|
368
|
-
})),
|
|
369
|
-
warehouses: [
|
|
370
|
-
{
|
|
371
|
-
name: 'TRANSFORMING',
|
|
372
|
-
size: 'X-Small',
|
|
373
|
-
state: 'SUSPENDED',
|
|
374
|
-
autoSuspendSeconds: 300,
|
|
375
|
-
},
|
|
376
|
-
],
|
|
377
|
-
roles: [
|
|
378
|
-
{ name: 'ANALYST', isDefault: true },
|
|
379
|
-
{ name: 'REPORTER', isDefault: false },
|
|
380
|
-
{ name: 'PUBLIC', isDefault: false },
|
|
381
|
-
],
|
|
382
|
-
schemas: [
|
|
383
|
-
{ databaseName: 'ANALYTICS', name: 'PUBLIC' },
|
|
384
|
-
{ databaseName: 'ANALYTICS', name: 'JAFFLE' },
|
|
385
|
-
],
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
expect(execute).toHaveBeenCalledWith(expect.objectContaining({
|
|
389
|
-
sqlText: 'SHOW SCHEMAS IN ACCOUNT LIMIT 1000',
|
|
390
|
-
}));
|
|
391
|
-
expect(execute).toHaveBeenCalledWith(expect.objectContaining({
|
|
392
|
-
sqlText: `SELECT SUM(bytes) AS "total_bytes" FROM DATABASE_0.INFORMATION_SCHEMA.TABLES WHERE table_type = 'BASE TABLE'`,
|
|
393
|
-
}));
|
|
394
|
-
expect(execute.mock.calls.filter(([statement]) => statement.sqlText.startsWith('SELECT SUM(bytes)'))).toHaveLength(25);
|
|
395
|
-
expect(execute.mock.calls.some(([statement]) => statement.sqlText.startsWith('USE WAREHOUSE'))).toBe(false);
|
|
396
|
-
expect(execute).toHaveBeenCalledWith(expect.objectContaining({
|
|
397
|
-
sqlText: 'SHOW GRANTS TO USER "user.name@example.com" LIMIT 100',
|
|
398
|
-
}));
|
|
399
|
-
});
|
|
400
|
-
it('returns null database sizes when size discovery fails', async () => {
|
|
401
|
-
const execute = vi.fn(({ sqlText, complete, }) => {
|
|
402
|
-
if (sqlText.startsWith('SELECT SUM(bytes)')) {
|
|
403
|
-
complete(new Error('Insufficient privileges'), { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock }, []);
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
let rows = [];
|
|
407
|
-
if (sqlText.startsWith('SELECT CURRENT_USER')) {
|
|
408
|
-
rows = [{ USER: 'test-user' }];
|
|
409
|
-
}
|
|
410
|
-
else if (sqlText.startsWith('SHOW DATABASES')) {
|
|
411
|
-
rows = [{ name: 'ANALYTICS' }, { name: 'RAW' }];
|
|
412
|
-
}
|
|
413
|
-
else if (sqlText.startsWith('SHOW WAREHOUSES')) {
|
|
414
|
-
rows = [
|
|
415
|
-
{ name: 'WH_BIG', size: 'Large', state: 'SUSPENDED' },
|
|
416
|
-
{
|
|
417
|
-
name: 'WH_SMALL',
|
|
418
|
-
size: 'X-Small',
|
|
419
|
-
state: 'STARTED',
|
|
420
|
-
},
|
|
421
|
-
];
|
|
422
|
-
}
|
|
423
|
-
complete(undefined, { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock }, rows);
|
|
424
|
-
});
|
|
425
|
-
vi.mocked(snowflake_sdk_1.createConnection).mockImplementationOnce(() => interactiveConnectionMock(execute));
|
|
426
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient(SnowflakeWarehouseClient_mock_1.credentials);
|
|
427
|
-
const discovery = await warehouse.getSessionDiscovery('test-user');
|
|
428
|
-
expect(discovery.inventory.databases).toEqual([
|
|
429
|
-
{
|
|
430
|
-
name: 'ANALYTICS',
|
|
431
|
-
comment: null,
|
|
432
|
-
kind: null,
|
|
433
|
-
sizeBytes: null,
|
|
434
|
-
},
|
|
435
|
-
{ name: 'RAW', comment: null, kind: null, sizeBytes: null },
|
|
436
|
-
]);
|
|
437
|
-
expect(execute).toHaveBeenCalledWith(expect.objectContaining({
|
|
438
|
-
sqlText: 'USE WAREHOUSE WH_SMALL',
|
|
439
|
-
}));
|
|
440
|
-
});
|
|
441
|
-
it('opens, checks, and closes a diagnostic connection', async () => {
|
|
442
|
-
const execute = vi.fn(({ sqlText, complete, }) => complete(undefined, { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock }, []));
|
|
443
|
-
const connection = interactiveConnectionMock(execute);
|
|
444
|
-
vi.mocked(snowflake_sdk_1.createConnection).mockImplementationOnce(() => connection);
|
|
445
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient(SnowflakeWarehouseClient_mock_1.credentials);
|
|
446
|
-
const session = await warehouse.openDiagnosticConnection();
|
|
447
|
-
await warehouse.selectOneDiagnosticConnection(session);
|
|
448
|
-
await warehouse.closeDiagnosticConnection(session);
|
|
449
|
-
expect(execute).toHaveBeenCalledWith(expect.objectContaining({ sqlText: 'SELECT 1' }));
|
|
450
|
-
expect(connection.destroy).toHaveBeenCalledOnce();
|
|
451
|
-
});
|
|
452
|
-
it('expect query rows', async () => {
|
|
453
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient(SnowflakeWarehouseClient_mock_1.credentials);
|
|
454
|
-
const results = await warehouse.runQuery('fake sql');
|
|
455
|
-
expect(results.fields).toEqual({
|
|
456
|
-
...SnowflakeWarehouseClient_mock_1.expectedFields,
|
|
457
|
-
// NUMBER with scale 0 at the source is an integer
|
|
458
|
-
MYNUMBERCOLUMN: {
|
|
459
|
-
...SnowflakeWarehouseClient_mock_1.expectedFields.MYNUMBERCOLUMN,
|
|
460
|
-
numericKind: { kind: 'integer' },
|
|
461
|
-
},
|
|
462
|
-
});
|
|
463
|
-
expect(results.rows[0]).toEqual(SnowflakeWarehouseClient_mock_1.expectedRow);
|
|
464
|
-
});
|
|
465
|
-
it('escapes single quotes in query tags for session SQL', () => {
|
|
466
|
-
expect(SnowflakeWarehouseClient_1.SnowflakeWarehouseClient.formatQueryTag({
|
|
467
|
-
user_attribute_company: "O'Reilly Media",
|
|
468
|
-
})).toBe('{"user_attribute_company":"O\'\'Reilly Media"}');
|
|
469
|
-
});
|
|
470
|
-
it('limits query tag value to Snowflake maximum length', () => {
|
|
471
|
-
expect(SnowflakeWarehouseClient_1.SnowflakeWarehouseClient.formatQueryTag({
|
|
472
|
-
user_attribute_company: 'x'.repeat(3000),
|
|
473
|
-
}).length).toBe(2000);
|
|
474
|
-
});
|
|
475
|
-
it('keeps long escaped query tags safe for Snowflake session SQL', () => {
|
|
476
|
-
const result = SnowflakeWarehouseClient_1.SnowflakeWarehouseClient.formatQueryTag({
|
|
477
|
-
user_attribute_company: "'".repeat(3000),
|
|
478
|
-
});
|
|
479
|
-
const singleQuoteRuns = result.match(/'+/g) ?? [];
|
|
480
|
-
expect(result.length).toBeGreaterThan(2000);
|
|
481
|
-
expect(result.replace(/''/g, "'")).toHaveLength(2000);
|
|
482
|
-
expect(singleQuoteRuns.every((run) => run.length % 2 === 0)).toBe(true);
|
|
483
|
-
});
|
|
484
|
-
it('expect schema with snowflake types mapped to dimension types', async () => {
|
|
485
|
-
snowflake_sdk_1.createConnection.mockImplementationOnce(() => ({
|
|
486
|
-
connect: vi.fn((callback) => callback(null, {})),
|
|
487
|
-
execute: vi.fn(({ sqlText, complete }) => {
|
|
488
|
-
complete(undefined, { getColumns: () => SnowflakeWarehouseClient_mock_1.queryColumnsMock }, SnowflakeWarehouseClient_mock_1.columns);
|
|
489
|
-
}),
|
|
490
|
-
destroy: vi.fn((callback) => callback(null, {})),
|
|
491
|
-
}));
|
|
492
|
-
const warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient(SnowflakeWarehouseClient_mock_1.credentials);
|
|
493
|
-
expect(await warehouse.getCatalog(WarehouseClient_mock_1.config)).toEqual(SnowflakeWarehouseClient_mock_1.expectedWarehouseSchema);
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
describe('mapSnowflakeDiagnosticError', () => {
|
|
497
|
-
it.each([
|
|
498
|
-
[
|
|
499
|
-
{ code: 'ENOTFOUND', message: 'getaddrinfo ENOTFOUND host' },
|
|
500
|
-
'account_identifier',
|
|
501
|
-
],
|
|
502
|
-
[
|
|
503
|
-
{
|
|
504
|
-
code: 250001,
|
|
505
|
-
message: 'Incorrect username or password was specified',
|
|
506
|
-
},
|
|
507
|
-
'authentication',
|
|
508
|
-
],
|
|
509
|
-
[
|
|
510
|
-
{
|
|
511
|
-
code: 'ERR_OSSL_PEM_BAD_BASE64_DECODE',
|
|
512
|
-
message: 'PEM routines failed',
|
|
513
|
-
},
|
|
514
|
-
'private_key',
|
|
515
|
-
],
|
|
516
|
-
[
|
|
517
|
-
{ code: 390144, message: 'Authentication token rejected' },
|
|
518
|
-
'private_key',
|
|
519
|
-
],
|
|
520
|
-
[
|
|
521
|
-
{ code: 390422, message: 'Incoming request rejected' },
|
|
522
|
-
'network_policy',
|
|
523
|
-
],
|
|
524
|
-
[
|
|
525
|
-
{
|
|
526
|
-
code: '002003',
|
|
527
|
-
message: "Database 'ANALYTICS' does not exist or not authorized",
|
|
528
|
-
},
|
|
529
|
-
'database_access',
|
|
530
|
-
],
|
|
531
|
-
[
|
|
532
|
-
{
|
|
533
|
-
code: '002043',
|
|
534
|
-
message: "Warehouse 'COMPUTE' does not exist or not authorized",
|
|
535
|
-
},
|
|
536
|
-
'warehouse_access',
|
|
537
|
-
],
|
|
538
|
-
[{ code: 'UNKNOWN', message: 'socket closed unexpectedly' }, 'unknown'],
|
|
539
|
-
])('maps %o to %s without exposing its raw message', (error, category) => {
|
|
540
|
-
const result = (0, SnowflakeWarehouseClient_1.mapSnowflakeDiagnosticError)(error);
|
|
541
|
-
expect(result.category).toBe(category);
|
|
542
|
-
expect(result.code).toBe(String(error.code));
|
|
543
|
-
expect(result.sanitizedMessage).not.toContain(error.message);
|
|
544
|
-
});
|
|
545
|
-
it('keeps the raw error non-enumerable for internal inspection', () => {
|
|
546
|
-
const rawError = new Error('sensitive socket detail');
|
|
547
|
-
const error = new SnowflakeWarehouseClient_1.SnowflakeDiagnosticError(rawError);
|
|
548
|
-
expect(error.getRawError()).toBe(rawError);
|
|
549
|
-
expect(JSON.stringify(error)).not.toContain('sensitive socket detail');
|
|
550
|
-
});
|
|
551
|
-
});
|
|
552
|
-
describe('SnowflakeTypeParsing', () => {
|
|
553
|
-
it('expect NUMBER(x,x) to be a number', () => {
|
|
554
|
-
expect((0, SnowflakeWarehouseClient_1.mapFieldType)('NUMBER(12,10)')).toEqual(common_1.DimensionType.NUMBER);
|
|
555
|
-
});
|
|
556
|
-
it('expect VARCHAR(x) to be a string', () => {
|
|
557
|
-
expect((0, SnowflakeWarehouseClient_1.mapFieldType)('VARCHAR(12)')).toEqual(common_1.DimensionType.STRING);
|
|
558
|
-
});
|
|
559
|
-
});
|
|
560
|
-
describe('SnowflakeErrorParsing', () => {
|
|
561
|
-
let warehouse;
|
|
562
|
-
const originalEnv = process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE;
|
|
563
|
-
beforeEach(() => {
|
|
564
|
-
warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient(SnowflakeWarehouseClient_mock_1.credentials);
|
|
565
|
-
});
|
|
566
|
-
afterEach(() => {
|
|
567
|
-
// Restore original environment variable
|
|
568
|
-
if (originalEnv) {
|
|
569
|
-
process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE = originalEnv;
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
delete process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE;
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
it('should return custom error message when SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE is set', () => {
|
|
576
|
-
// Set environment variable
|
|
577
|
-
process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE =
|
|
578
|
-
"You don't have access to the {snowflakeTable} table. Please go to 'analytics_{snowflakeSchema}' in sailpoint and request access";
|
|
579
|
-
const error = {
|
|
580
|
-
message: "SQL compilation error: Object 'SNOWFLAKE_DATABASE_STAGING.JAFFLE.EVENTS' does not exist or not authorized.",
|
|
581
|
-
code: 'COMPILATION',
|
|
582
|
-
data: { type: 'COMPILATION' },
|
|
583
|
-
};
|
|
584
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
585
|
-
const result = warehouse.parseError(error);
|
|
586
|
-
expect(result.message).toBe("You don't have access to the EVENTS table. Please go to 'analytics_JAFFLE' in sailpoint and request access");
|
|
587
|
-
});
|
|
588
|
-
it('should return original error message when SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE is not set', () => {
|
|
589
|
-
// Make sure environment variable is not set
|
|
590
|
-
delete process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE;
|
|
591
|
-
const error = {
|
|
592
|
-
message: "SQL compilation error: Object 'SNOWFLAKE_DATABASE_STAGING.JAFFLE.EVENTS' does not exist or not authorized.",
|
|
593
|
-
code: 'COMPILATION',
|
|
594
|
-
data: { type: 'COMPILATION' },
|
|
595
|
-
};
|
|
596
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
597
|
-
const result = warehouse.parseError(error);
|
|
598
|
-
expect(result.message).toBe("SQL compilation error: Object 'SNOWFLAKE_DATABASE_STAGING.JAFFLE.EVENTS' does not exist or not authorized.");
|
|
599
|
-
});
|
|
600
|
-
it('should return original error message for non-unauthorized errors', () => {
|
|
601
|
-
// Set environment variable
|
|
602
|
-
process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE =
|
|
603
|
-
"You don't have access to the {snowflakeTable} table. Please go to 'analytics_{snowflakeSchema}' in sailpoint and request access";
|
|
604
|
-
const error = {
|
|
605
|
-
message: 'Some other SQL error',
|
|
606
|
-
code: 'COMPILATION',
|
|
607
|
-
data: { type: 'COMPILATION' },
|
|
608
|
-
};
|
|
609
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
610
|
-
const result = warehouse.parseError(error);
|
|
611
|
-
expect(result.message).toBe('Some other SQL error');
|
|
612
|
-
});
|
|
613
|
-
it('should handle table names with different formats', () => {
|
|
614
|
-
process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE =
|
|
615
|
-
'Access denied for {snowflakeTable} in {snowflakeSchema}';
|
|
616
|
-
const error = {
|
|
617
|
-
message: "Object 'DB.MY_SCHEMA.MY_TABLE' does not exist or not authorized.",
|
|
618
|
-
code: 'COMPILATION',
|
|
619
|
-
data: { type: 'COMPILATION' },
|
|
620
|
-
};
|
|
621
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
622
|
-
const result = warehouse.parseError(error);
|
|
623
|
-
expect(result.message).toBe('Access denied for MY_TABLE in MY_SCHEMA');
|
|
624
|
-
});
|
|
625
|
-
it('should extract schema name from Schema-level errors', () => {
|
|
626
|
-
process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE =
|
|
627
|
-
"You don't have access to the {snowflakeTable} table. Please go to 'analytics_{snowflakeSchema}' in sailpoint and request access";
|
|
628
|
-
const error = {
|
|
629
|
-
message: "SQL compilation error: Schema 'ANALYTICS_DB.RPT_VERIFICATION' does not exist or not authorized.",
|
|
630
|
-
code: 'COMPILATION',
|
|
631
|
-
data: { type: 'COMPILATION' },
|
|
632
|
-
};
|
|
633
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
634
|
-
const result = warehouse.parseError(error);
|
|
635
|
-
expect(result.message).toBe("You don't have access to the RPT_VERIFICATION table. Please go to 'analytics_RPT_VERIFICATION' in sailpoint and request access");
|
|
636
|
-
});
|
|
637
|
-
it('should handle Schema errors with different formats', () => {
|
|
638
|
-
process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE =
|
|
639
|
-
'Access denied for {snowflakeTable} in {snowflakeSchema}';
|
|
640
|
-
const error = {
|
|
641
|
-
message: "Schema 'DB.MY_SCHEMA' does not exist or not authorized.",
|
|
642
|
-
code: 'COMPILATION',
|
|
643
|
-
data: { type: 'COMPILATION' },
|
|
644
|
-
};
|
|
645
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
646
|
-
const result = warehouse.parseError(error);
|
|
647
|
-
expect(result.message).toBe('Access denied for MY_SCHEMA in MY_SCHEMA');
|
|
648
|
-
});
|
|
649
|
-
it('should handle errors without table information gracefully', () => {
|
|
650
|
-
process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE =
|
|
651
|
-
"You don't have access to the {snowflakeTable} table. Please go to 'analytics_{snowflakeSchema}' in sailpoint and request access";
|
|
652
|
-
const error = {
|
|
653
|
-
message: "Object 'INCOMPLETE_TABLE_NAME' does not exist or not authorized.",
|
|
654
|
-
code: 'COMPILATION',
|
|
655
|
-
data: { type: 'COMPILATION' },
|
|
656
|
-
};
|
|
657
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
658
|
-
const result = warehouse.parseError(error);
|
|
659
|
-
// Should still use custom message but without variable replacement
|
|
660
|
-
expect(result.message).toBe("You don't have access to the {snowflakeTable} table. Please go to 'analytics_{snowflakeSchema}' in sailpoint and request access");
|
|
661
|
-
});
|
|
662
|
-
it('should convert literal \\n escape sequences into real newlines', () => {
|
|
663
|
-
process.env.SNOWFLAKE_UNAUTHORIZED_ERROR_MESSAGE =
|
|
664
|
-
'No access to {snowflakeTable}.\\n1) Step one\\n2) Step two';
|
|
665
|
-
const error = {
|
|
666
|
-
message: "Object 'DB.MY_SCHEMA.MY_TABLE' does not exist or not authorized.",
|
|
667
|
-
code: 'COMPILATION',
|
|
668
|
-
data: { type: 'COMPILATION' },
|
|
669
|
-
};
|
|
670
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
671
|
-
const result = warehouse.parseError(error);
|
|
672
|
-
expect(result.message).toBe('No access to MY_TABLE.\n1) Step one\n2) Step two');
|
|
673
|
-
expect(result.message).not.toContain('\\n');
|
|
674
|
-
});
|
|
675
|
-
});
|
|
676
|
-
describe('SnowflakeWarehouseClient.parseError - warehouse access errors', () => {
|
|
677
|
-
let warehouse;
|
|
678
|
-
beforeEach(() => {
|
|
679
|
-
const mockConnectionOptions = {
|
|
680
|
-
account: 'test-account',
|
|
681
|
-
user: 'test-user',
|
|
682
|
-
password: 'test-password',
|
|
683
|
-
warehouse: 'TEST_WAREHOUSE',
|
|
684
|
-
database: 'test-database',
|
|
685
|
-
};
|
|
686
|
-
warehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient(mockConnectionOptions);
|
|
687
|
-
});
|
|
688
|
-
afterEach(() => {
|
|
689
|
-
const originalEnv = process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE;
|
|
690
|
-
if (originalEnv !== undefined) {
|
|
691
|
-
process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE = originalEnv;
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
delete process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE;
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
it('should return custom warehouse error message when SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE is set', () => {
|
|
698
|
-
// Set environment variable
|
|
699
|
-
process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE =
|
|
700
|
-
"You don't have access to warehouse {warehouseName}. Please reach out to your admin.";
|
|
701
|
-
const error = {
|
|
702
|
-
message: "No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command",
|
|
703
|
-
code: 'SESSION_ERROR',
|
|
704
|
-
data: { type: 'SESSION_ERROR' },
|
|
705
|
-
};
|
|
706
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
707
|
-
const result = warehouse.parseError(error);
|
|
708
|
-
expect(result.message).toBe("You don't have access to warehouse TEST_WAREHOUSE. Please reach out to your admin.");
|
|
709
|
-
});
|
|
710
|
-
it('should append the configured warehouse to the original error when SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE is not set', () => {
|
|
711
|
-
// Ensure environment variable is not set
|
|
712
|
-
delete process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE;
|
|
713
|
-
const error = {
|
|
714
|
-
message: "No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command",
|
|
715
|
-
code: 'SESSION_ERROR',
|
|
716
|
-
data: { type: 'SESSION_ERROR' },
|
|
717
|
-
};
|
|
718
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
719
|
-
const result = warehouse.parseError(error);
|
|
720
|
-
expect(result.message).toBe(`No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command (configured warehouse: "TEST_WAREHOUSE")`);
|
|
721
|
-
});
|
|
722
|
-
it('should flag missing warehouse credentials when no warehouse is configured on the connection', () => {
|
|
723
|
-
delete process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE;
|
|
724
|
-
const warehouseWithoutWarehouse = new SnowflakeWarehouseClient_1.SnowflakeWarehouseClient({
|
|
725
|
-
account: 'test-account',
|
|
726
|
-
user: 'test-user',
|
|
727
|
-
password: 'test-password',
|
|
728
|
-
database: 'test-database',
|
|
729
|
-
});
|
|
730
|
-
const error = {
|
|
731
|
-
message: "No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command",
|
|
732
|
-
code: 'SESSION_ERROR',
|
|
733
|
-
data: { type: 'SESSION_ERROR' },
|
|
734
|
-
};
|
|
735
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
736
|
-
const result = warehouseWithoutWarehouse.parseError(error);
|
|
737
|
-
expect(result.message).toBe("No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command (no warehouse was configured on the connection — credentials may be missing this field)");
|
|
738
|
-
});
|
|
739
|
-
it('should handle warehouse errors without warehouse name replacement when template has no placeholder', () => {
|
|
740
|
-
process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE =
|
|
741
|
-
'You do not have warehouse access. Please contact your administrator.';
|
|
742
|
-
const error = {
|
|
743
|
-
message: "No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command",
|
|
744
|
-
code: 'SESSION_ERROR',
|
|
745
|
-
data: { type: 'SESSION_ERROR' },
|
|
746
|
-
};
|
|
747
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
748
|
-
const result = warehouse.parseError(error);
|
|
749
|
-
expect(result.message).toBe('You do not have warehouse access. Please contact your administrator.');
|
|
750
|
-
});
|
|
751
|
-
it('should return original error message for non-warehouse access errors', () => {
|
|
752
|
-
// Set environment variable
|
|
753
|
-
process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE =
|
|
754
|
-
"You don't have access to warehouse {warehouseName}. Please reach out to your admin.";
|
|
755
|
-
const error = {
|
|
756
|
-
message: 'Some other SQL error',
|
|
757
|
-
code: 'COMPILATION',
|
|
758
|
-
data: { type: 'COMPILATION' },
|
|
759
|
-
};
|
|
760
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
761
|
-
const result = warehouse.parseError(error);
|
|
762
|
-
expect(result.message).toBe('Some other SQL error');
|
|
763
|
-
});
|
|
764
|
-
it('should convert literal \\n escape sequences into real newlines', () => {
|
|
765
|
-
process.env.SNOWFLAKE_WAREHOUSE_ERROR_MESSAGE =
|
|
766
|
-
'No access to warehouse {warehouseName}.\\n1) Step one\\n2) Step two';
|
|
767
|
-
const error = {
|
|
768
|
-
message: "No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command",
|
|
769
|
-
code: 'SESSION_ERROR',
|
|
770
|
-
data: { type: 'SESSION_ERROR' },
|
|
771
|
-
};
|
|
772
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
773
|
-
const result = warehouse.parseError(error);
|
|
774
|
-
expect(result.message).toBe('No access to warehouse TEST_WAREHOUSE.\n1) Step one\n2) Step two');
|
|
775
|
-
expect(result.message).not.toContain('\\n');
|
|
776
|
-
});
|
|
777
|
-
});
|