@memberjunction/server 5.4.1 → 5.6.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/agents/skip-sdk.js +2 -2
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/auth/AuthProviderFactory.d.ts +9 -0
- package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
- package/dist/auth/AuthProviderFactory.js +27 -1
- package/dist/auth/AuthProviderFactory.js.map +1 -1
- package/dist/auth/index.d.ts +6 -4
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +10 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +52 -15
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +944 -843
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +5269 -6194
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +9 -0
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +3 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +25 -6
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +181 -22
- package/dist/index.js.map +1 -1
- package/dist/resolvers/ActionResolver.js +4 -4
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +5 -4
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +3 -2
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +3 -3
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +4 -3
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +4 -3
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +4 -3
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/package.json +58 -53
- package/src/__tests__/databaseAbstraction.test.ts +816 -0
- package/src/agents/skip-sdk.ts +2 -2
- package/src/auth/AuthProviderFactory.ts +31 -1
- package/src/auth/__tests__/backward-compatibility.test.ts +114 -0
- package/src/auth/index.ts +14 -9
- package/src/config.ts +9 -0
- package/src/context.ts +65 -20
- package/src/generated/generated.ts +5056 -6164
- package/src/generic/ResolverBase.ts +9 -0
- package/src/generic/RunViewResolver.ts +24 -7
- package/src/index.ts +207 -23
- package/src/resolvers/ActionResolver.ts +4 -4
- package/src/resolvers/ComponentRegistryResolver.ts +8 -7
- package/src/resolvers/FileResolver.ts +3 -2
- package/src/resolvers/RunAIAgentResolver.ts +3 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +8 -7
- package/src/resolvers/SyncRolesUsersResolver.ts +4 -3
- package/src/resolvers/UserFavoriteResolver.ts +4 -3
- package/src/rest/OAuthCallbackHandler.ts +4 -3
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// --------------------------------------------------------------------------
|
|
4
|
+
// Mocks – must be declared before any import that touches them
|
|
5
|
+
// --------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
// Mock the config module (loaded at module-scope by orm.ts, util.ts, context.ts, index.ts)
|
|
8
|
+
vi.mock('../config', () => ({
|
|
9
|
+
configInfo: {
|
|
10
|
+
authProviders: [],
|
|
11
|
+
databaseSettings: {
|
|
12
|
+
connectionTimeout: 45000,
|
|
13
|
+
requestTimeout: 30000,
|
|
14
|
+
metadataCacheRefreshInterval: 180000,
|
|
15
|
+
connectionPool: {
|
|
16
|
+
max: 50,
|
|
17
|
+
min: 5,
|
|
18
|
+
idleTimeoutMillis: 30000,
|
|
19
|
+
acquireTimeoutMillis: 30000,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
graphqlPort: 4000,
|
|
23
|
+
baseUrl: 'http://localhost',
|
|
24
|
+
graphqlRootPath: '/',
|
|
25
|
+
},
|
|
26
|
+
dbHost: 'localhost',
|
|
27
|
+
dbPort: 1433,
|
|
28
|
+
dbUsername: 'sa',
|
|
29
|
+
dbPassword: 'TestPassword!',
|
|
30
|
+
dbDatabase: 'TestDB',
|
|
31
|
+
dbInstanceName: undefined,
|
|
32
|
+
dbTrustServerCertificate: undefined,
|
|
33
|
+
mj_core_schema: '__mj',
|
|
34
|
+
userEmailMap: undefined,
|
|
35
|
+
apiKey: 'test-api-key',
|
|
36
|
+
dbReadOnlyUsername: undefined,
|
|
37
|
+
dbReadOnlyPassword: undefined,
|
|
38
|
+
___codeGenAPIURL: 'http://localhost',
|
|
39
|
+
___codeGenAPIPort: 3999,
|
|
40
|
+
___codeGenAPISubmissionDelay: 5000,
|
|
41
|
+
graphqlPort: 4000,
|
|
42
|
+
graphqlRootPath: '/',
|
|
43
|
+
baseUrl: 'http://localhost',
|
|
44
|
+
publicUrl: '',
|
|
45
|
+
enableIntrospection: false,
|
|
46
|
+
websiteRunFromPackage: undefined,
|
|
47
|
+
RESTApiOptions: { enabled: false },
|
|
48
|
+
DEFAULT_SERVER_CONFIG: {},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// Re-export aliased types used in util.ts / types.ts
|
|
52
|
+
vi.mock('mssql', () => {
|
|
53
|
+
// Minimal mock that satisfies import sql from 'mssql'
|
|
54
|
+
const ConnectionPool = vi.fn();
|
|
55
|
+
const Request = vi.fn(() => ({
|
|
56
|
+
input: vi.fn(),
|
|
57
|
+
query: vi.fn().mockResolvedValue({ recordset: [] }),
|
|
58
|
+
}));
|
|
59
|
+
return {
|
|
60
|
+
default: { ConnectionPool, Request },
|
|
61
|
+
ConnectionPool,
|
|
62
|
+
Request,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// We must also mock the auth module and its transitive deps so that importing
|
|
67
|
+
// individual source files does not trigger type-graphql or reflect-metadata.
|
|
68
|
+
vi.mock('../auth/index', () => ({
|
|
69
|
+
getSigningKeys: vi.fn(),
|
|
70
|
+
getSystemUser: vi.fn(),
|
|
71
|
+
getValidationOptions: vi.fn(),
|
|
72
|
+
verifyUserRecord: vi.fn(),
|
|
73
|
+
extractUserInfoFromPayload: vi.fn(),
|
|
74
|
+
TokenExpiredError: class TokenExpiredError extends Error {},
|
|
75
|
+
AuthProviderFactory: { getInstance: vi.fn(() => ({ getByIssuer: vi.fn() })) },
|
|
76
|
+
IAuthProvider: {},
|
|
77
|
+
initializeAuthProviders: vi.fn(),
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
vi.mock('../auth/AuthProviderFactory', () => ({
|
|
81
|
+
AuthProviderFactory: { getInstance: vi.fn(() => ({ getByIssuer: vi.fn() })) },
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
vi.mock('../cache', () => ({
|
|
85
|
+
authCache: new Map(),
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
vi.mock('@memberjunction/core', () => ({
|
|
89
|
+
DatabaseProviderBase: class {},
|
|
90
|
+
Metadata: class { Provider = { Entities: [] } },
|
|
91
|
+
LogError: vi.fn(),
|
|
92
|
+
LogStatus: vi.fn(),
|
|
93
|
+
DatabasePlatform: {},
|
|
94
|
+
SetProvider: vi.fn(),
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
|
|
98
|
+
SQLServerDataProvider: vi.fn(),
|
|
99
|
+
SQLServerProviderConfigData: vi.fn(),
|
|
100
|
+
setupSQLServerClient: vi.fn(),
|
|
101
|
+
UserCache: { Instance: { Users: [], GetSystemUser: vi.fn() } },
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
vi.mock('@memberjunction/api-keys', () => ({
|
|
105
|
+
GetAPIKeyEngine: vi.fn(),
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
// Import the units under test AFTER the mocks are in place.
|
|
109
|
+
// Import from individual source files to avoid pulling in type-graphql decorators via index.ts.
|
|
110
|
+
import createMSSQLConfig from '../orm.js';
|
|
111
|
+
import { DataSourceInfo, ProviderInfo } from '../types.js';
|
|
112
|
+
import {
|
|
113
|
+
GetReadOnlyDataSource,
|
|
114
|
+
GetReadWriteDataSource,
|
|
115
|
+
GetReadOnlyProvider,
|
|
116
|
+
GetReadWriteProvider,
|
|
117
|
+
} from '../util.js';
|
|
118
|
+
|
|
119
|
+
// getDbType is a pure function in index.ts, but importing index.ts triggers
|
|
120
|
+
// type-graphql. Replicate the logic here for isolated testing.
|
|
121
|
+
function getDbType(): 'sqlserver' | 'postgresql' {
|
|
122
|
+
const dbType = process.env.DB_TYPE?.toLowerCase();
|
|
123
|
+
if (dbType === 'postgresql' || dbType === 'postgres' || dbType === 'pg') {
|
|
124
|
+
return 'postgresql';
|
|
125
|
+
}
|
|
126
|
+
return 'sqlserver';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --------------------------------------------------------------------------
|
|
130
|
+
// Helpers to build typed mock objects without `any`
|
|
131
|
+
// --------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
interface MockConnectionPool {
|
|
134
|
+
connected: boolean;
|
|
135
|
+
query: (sql: string) => Promise<{ recordset: Record<string, unknown>[] }>;
|
|
136
|
+
request: () => { query: (sql: string) => Promise<{ recordset: Record<string, unknown>[] }> };
|
|
137
|
+
_pgPool?: unknown;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createMockPool(overrides?: Partial<MockConnectionPool>): MockConnectionPool {
|
|
141
|
+
return {
|
|
142
|
+
connected: true,
|
|
143
|
+
query: vi.fn().mockResolvedValue({ recordset: [] }),
|
|
144
|
+
request: () => ({ query: vi.fn().mockResolvedValue({ recordset: [] }) }),
|
|
145
|
+
...overrides,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface MockDatabaseProvider {
|
|
150
|
+
Config: ReturnType<typeof vi.fn>;
|
|
151
|
+
Entities: unknown[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function createMockProvider(): MockDatabaseProvider {
|
|
155
|
+
return {
|
|
156
|
+
Config: vi.fn().mockResolvedValue(undefined),
|
|
157
|
+
Entities: [],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
type DataSourceType = 'Admin' | 'Read-Write' | 'Read-Only' | 'Other';
|
|
162
|
+
|
|
163
|
+
function createDataSourceInfo(type: DataSourceType): DataSourceInfo {
|
|
164
|
+
const pool = createMockPool();
|
|
165
|
+
return new DataSourceInfo({
|
|
166
|
+
dataSource: pool as unknown as import('mssql').ConnectionPool,
|
|
167
|
+
type,
|
|
168
|
+
host: 'localhost',
|
|
169
|
+
port: type === 'Read-Only' ? 1434 : 1433,
|
|
170
|
+
database: 'TestDB',
|
|
171
|
+
userName: type === 'Read-Only' ? 'readonly_user' : 'sa',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function createProviderInfo(type: ProviderInfo['type']): ProviderInfo {
|
|
176
|
+
const info = new ProviderInfo();
|
|
177
|
+
info.provider = createMockProvider() as unknown as import('@memberjunction/core').DatabaseProviderBase;
|
|
178
|
+
info.type = type;
|
|
179
|
+
return info;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --------------------------------------------------------------------------
|
|
183
|
+
// Test suites
|
|
184
|
+
// --------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
describe('Database Abstraction Layer', () => {
|
|
187
|
+
const originalEnv = process.env;
|
|
188
|
+
|
|
189
|
+
beforeEach(() => {
|
|
190
|
+
// Shallow-clone so mutations do not leak between tests
|
|
191
|
+
process.env = { ...originalEnv };
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
afterEach(() => {
|
|
195
|
+
process.env = originalEnv;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ======================================================================
|
|
199
|
+
// 1. DB_TYPE detection
|
|
200
|
+
// ======================================================================
|
|
201
|
+
describe('getDbType – DB_TYPE environment detection', () => {
|
|
202
|
+
it('should return "sqlserver" when DB_TYPE is not set', () => {
|
|
203
|
+
delete process.env.DB_TYPE;
|
|
204
|
+
expect(getDbType()).toBe('sqlserver');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should return "postgresql" when DB_TYPE is "postgresql"', () => {
|
|
208
|
+
process.env.DB_TYPE = 'postgresql';
|
|
209
|
+
expect(getDbType()).toBe('postgresql');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should return "postgresql" when DB_TYPE is "postgres"', () => {
|
|
213
|
+
process.env.DB_TYPE = 'postgres';
|
|
214
|
+
expect(getDbType()).toBe('postgresql');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should return "postgresql" when DB_TYPE is "pg"', () => {
|
|
218
|
+
process.env.DB_TYPE = 'pg';
|
|
219
|
+
expect(getDbType()).toBe('postgresql');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should be case-insensitive for DB_TYPE values', () => {
|
|
223
|
+
process.env.DB_TYPE = 'PostgreSQL';
|
|
224
|
+
expect(getDbType()).toBe('postgresql');
|
|
225
|
+
|
|
226
|
+
process.env.DB_TYPE = 'POSTGRES';
|
|
227
|
+
expect(getDbType()).toBe('postgresql');
|
|
228
|
+
|
|
229
|
+
process.env.DB_TYPE = 'PG';
|
|
230
|
+
expect(getDbType()).toBe('postgresql');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should return "sqlserver" for unrecognized DB_TYPE values', () => {
|
|
234
|
+
process.env.DB_TYPE = 'mysql';
|
|
235
|
+
expect(getDbType()).toBe('sqlserver');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should return "sqlserver" for empty-string DB_TYPE', () => {
|
|
239
|
+
process.env.DB_TYPE = '';
|
|
240
|
+
expect(getDbType()).toBe('sqlserver');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should return "sqlserver" when DB_TYPE is "sqlserver"', () => {
|
|
244
|
+
process.env.DB_TYPE = 'sqlserver';
|
|
245
|
+
expect(getDbType()).toBe('sqlserver');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ======================================================================
|
|
250
|
+
// 2. MSSQL pool configuration (createMSSQLConfig)
|
|
251
|
+
// ======================================================================
|
|
252
|
+
describe('createMSSQLConfig – SQL Server connection pool config', () => {
|
|
253
|
+
it('should build a config with host, port, user, password, and database from config module', () => {
|
|
254
|
+
const cfg = createMSSQLConfig();
|
|
255
|
+
expect(cfg.server).toBe('localhost');
|
|
256
|
+
expect(cfg.port).toBe(1433);
|
|
257
|
+
expect(cfg.user).toBe('sa');
|
|
258
|
+
expect(cfg.password).toBe('TestPassword!');
|
|
259
|
+
expect(cfg.database).toBe('TestDB');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should include connection and request timeouts from databaseSettings', () => {
|
|
263
|
+
const cfg = createMSSQLConfig();
|
|
264
|
+
expect(cfg.requestTimeout).toBe(30000);
|
|
265
|
+
expect(cfg.connectionTimeout).toBe(45000);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should include pool settings from databaseSettings.connectionPool', () => {
|
|
269
|
+
const cfg = createMSSQLConfig();
|
|
270
|
+
expect(cfg.pool).toEqual({
|
|
271
|
+
max: 50,
|
|
272
|
+
min: 5,
|
|
273
|
+
idleTimeoutMillis: 30000,
|
|
274
|
+
acquireTimeoutMillis: 30000,
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should set options.encrypt to true and options.enableArithAbort to true', () => {
|
|
279
|
+
const cfg = createMSSQLConfig();
|
|
280
|
+
expect(cfg.options?.encrypt).toBe(true);
|
|
281
|
+
expect(cfg.options?.enableArithAbort).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should not include instanceName when dbInstanceName is undefined', () => {
|
|
285
|
+
const cfg = createMSSQLConfig();
|
|
286
|
+
expect(cfg.options?.instanceName).toBeUndefined();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should not include trustServerCertificate when dbTrustServerCertificate is undefined', () => {
|
|
290
|
+
const cfg = createMSSQLConfig();
|
|
291
|
+
expect(cfg.options?.trustServerCertificate).toBeUndefined();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// ======================================================================
|
|
296
|
+
// 3. DataSourceInfo class
|
|
297
|
+
// ======================================================================
|
|
298
|
+
describe('DataSourceInfo – data source metadata wrapper', () => {
|
|
299
|
+
it('should store all constructor properties', () => {
|
|
300
|
+
const pool = createMockPool();
|
|
301
|
+
const ds = new DataSourceInfo({
|
|
302
|
+
dataSource: pool as unknown as import('mssql').ConnectionPool,
|
|
303
|
+
type: 'Read-Write',
|
|
304
|
+
host: 'db.example.com',
|
|
305
|
+
port: 5432,
|
|
306
|
+
database: 'mydb',
|
|
307
|
+
userName: 'appuser',
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
expect(ds.host).toBe('db.example.com');
|
|
311
|
+
expect(ds.port).toBe(5432);
|
|
312
|
+
expect(ds.database).toBe('mydb');
|
|
313
|
+
expect(ds.userName).toBe('appuser');
|
|
314
|
+
expect(ds.type).toBe('Read-Write');
|
|
315
|
+
expect(ds.dataSource).toBe(pool);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should accept all four type literals', () => {
|
|
319
|
+
const types: DataSourceType[] = ['Admin', 'Read-Write', 'Read-Only', 'Other'];
|
|
320
|
+
for (const t of types) {
|
|
321
|
+
const ds = createDataSourceInfo(t);
|
|
322
|
+
expect(ds.type).toBe(t);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ======================================================================
|
|
328
|
+
// 4. ProviderInfo class
|
|
329
|
+
// ======================================================================
|
|
330
|
+
describe('ProviderInfo – provider metadata wrapper', () => {
|
|
331
|
+
it('should store provider and type', () => {
|
|
332
|
+
const pi = createProviderInfo('Read-Write');
|
|
333
|
+
expect(pi.type).toBe('Read-Write');
|
|
334
|
+
expect(pi.provider).toBeDefined();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should accept all four type literals', () => {
|
|
338
|
+
const types: ProviderInfo['type'][] = ['Admin', 'Read-Write', 'Read-Only', 'Other'];
|
|
339
|
+
for (const t of types) {
|
|
340
|
+
const pi = createProviderInfo(t);
|
|
341
|
+
expect(pi.type).toBe(t);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// ======================================================================
|
|
347
|
+
// 5. GetReadOnlyDataSource / GetReadWriteDataSource
|
|
348
|
+
// ======================================================================
|
|
349
|
+
describe('GetReadOnlyDataSource – read-only data source lookup', () => {
|
|
350
|
+
it('should return the Read-Only data source when one exists', () => {
|
|
351
|
+
const rw = createDataSourceInfo('Read-Write');
|
|
352
|
+
const ro = createDataSourceInfo('Read-Only');
|
|
353
|
+
const result = GetReadOnlyDataSource([rw, ro]);
|
|
354
|
+
// The returned pool wraps the Read-Only pool
|
|
355
|
+
expect(result).toBeDefined();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should fall back to Read-Write when no Read-Only and allowFallbackToReadWrite is true', () => {
|
|
359
|
+
const rw = createDataSourceInfo('Read-Write');
|
|
360
|
+
const result = GetReadOnlyDataSource([rw], { allowFallbackToReadWrite: true });
|
|
361
|
+
expect(result).toBeDefined();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should fall back to Read-Write by default when options are not provided', () => {
|
|
365
|
+
const rw = createDataSourceInfo('Read-Write');
|
|
366
|
+
// No options → default behavior is to fall back
|
|
367
|
+
const result = GetReadOnlyDataSource([rw]);
|
|
368
|
+
expect(result).toBeDefined();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should throw when no Read-Only and allowFallbackToReadWrite is false', () => {
|
|
372
|
+
const rw = createDataSourceInfo('Read-Write');
|
|
373
|
+
expect(() =>
|
|
374
|
+
GetReadOnlyDataSource([rw], { allowFallbackToReadWrite: false })
|
|
375
|
+
).toThrow('No suitable data source found');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should throw when data sources array is empty', () => {
|
|
379
|
+
expect(() => GetReadOnlyDataSource([])).toThrow('No suitable data source found');
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe('GetReadWriteDataSource – read-write data source lookup', () => {
|
|
384
|
+
it('should return the Read-Write data source when one exists', () => {
|
|
385
|
+
const rw = createDataSourceInfo('Read-Write');
|
|
386
|
+
const result = GetReadWriteDataSource([rw]);
|
|
387
|
+
expect(result).toBeDefined();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('should throw when no Read-Write data source is present', () => {
|
|
391
|
+
const ro = createDataSourceInfo('Read-Only');
|
|
392
|
+
expect(() => GetReadWriteDataSource([ro])).toThrow('No suitable read-write data source found');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should throw when data sources array is empty', () => {
|
|
396
|
+
expect(() => GetReadWriteDataSource([])).toThrow('No suitable read-write data source found');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should ignore non-Read-Write types', () => {
|
|
400
|
+
const admin = createDataSourceInfo('Admin');
|
|
401
|
+
const other = createDataSourceInfo('Other');
|
|
402
|
+
expect(() => GetReadWriteDataSource([admin, other])).toThrow(
|
|
403
|
+
'No suitable read-write data source found'
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// ======================================================================
|
|
409
|
+
// 6. GetReadOnlyProvider / GetReadWriteProvider
|
|
410
|
+
// ======================================================================
|
|
411
|
+
describe('GetReadOnlyProvider – read-only provider lookup', () => {
|
|
412
|
+
it('should return the Read-Only provider when one exists', () => {
|
|
413
|
+
const rw = createProviderInfo('Read-Write');
|
|
414
|
+
const ro = createProviderInfo('Read-Only');
|
|
415
|
+
const result = GetReadOnlyProvider([rw, ro]);
|
|
416
|
+
expect(result).toBe(ro.provider);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should return the first provider when allowFallbackToReadWrite is true and no Read-Only', () => {
|
|
420
|
+
const rw = createProviderInfo('Read-Write');
|
|
421
|
+
const result = GetReadOnlyProvider([rw], { allowFallbackToReadWrite: true });
|
|
422
|
+
expect(result).toBe(rw.provider);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should return null when no Read-Only provider and fallback is not allowed', () => {
|
|
426
|
+
const rw = createProviderInfo('Read-Write');
|
|
427
|
+
const result = GetReadOnlyProvider([rw]);
|
|
428
|
+
expect(result).toBeNull();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should return null when no Read-Only and options.allowFallbackToReadWrite is false', () => {
|
|
432
|
+
const rw = createProviderInfo('Read-Write');
|
|
433
|
+
const result = GetReadOnlyProvider([rw], { allowFallbackToReadWrite: false });
|
|
434
|
+
expect(result).toBeNull();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('should return null when providers array is empty', () => {
|
|
438
|
+
const result = GetReadOnlyProvider([]);
|
|
439
|
+
expect(result).toBeNull();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('should return null when providers is null', () => {
|
|
443
|
+
const result = GetReadOnlyProvider(null as unknown as ProviderInfo[]);
|
|
444
|
+
expect(result).toBeNull();
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('GetReadWriteProvider – read-write provider lookup', () => {
|
|
449
|
+
it('should return the Read-Write provider when one exists', () => {
|
|
450
|
+
const rw = createProviderInfo('Read-Write');
|
|
451
|
+
const ro = createProviderInfo('Read-Only');
|
|
452
|
+
const result = GetReadWriteProvider([rw, ro]);
|
|
453
|
+
expect(result).toBe(rw.provider);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should fall back to Read-Only when allowFallbackToReadOnly is true', () => {
|
|
457
|
+
const ro = createProviderInfo('Read-Only');
|
|
458
|
+
const result = GetReadWriteProvider([ro], { allowFallbackToReadOnly: true });
|
|
459
|
+
expect(result).toBe(ro.provider);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('should return null when no Read-Write and fallback is not allowed', () => {
|
|
463
|
+
const ro = createProviderInfo('Read-Only');
|
|
464
|
+
const result = GetReadWriteProvider([ro]);
|
|
465
|
+
expect(result).toBeNull();
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('should return null when providers is empty', () => {
|
|
469
|
+
const result = GetReadWriteProvider([]);
|
|
470
|
+
expect(result).toBeNull();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('should return null when providers is null', () => {
|
|
474
|
+
const result = GetReadWriteProvider(null as unknown as ProviderInfo[]);
|
|
475
|
+
expect(result).toBeNull();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should return null when only Admin and Other types exist', () => {
|
|
479
|
+
const admin = createProviderInfo('Admin');
|
|
480
|
+
const other = createProviderInfo('Other');
|
|
481
|
+
const result = GetReadWriteProvider([admin, other]);
|
|
482
|
+
expect(result).toBeNull();
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// ======================================================================
|
|
487
|
+
// 7. Context creation – database type branching in contextFunction
|
|
488
|
+
// ======================================================================
|
|
489
|
+
describe('Database type branching for context creation', () => {
|
|
490
|
+
it('should identify postgres when DB_TYPE is "postgresql"', () => {
|
|
491
|
+
process.env.DB_TYPE = 'postgresql';
|
|
492
|
+
const dbType = process.env.DB_TYPE?.toLowerCase();
|
|
493
|
+
const isPostgres = dbType === 'postgresql' || dbType === 'postgres' || dbType === 'pg';
|
|
494
|
+
expect(isPostgres).toBe(true);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('should identify postgres when DB_TYPE is "pg"', () => {
|
|
498
|
+
process.env.DB_TYPE = 'pg';
|
|
499
|
+
const dbType = process.env.DB_TYPE?.toLowerCase();
|
|
500
|
+
const isPostgres = dbType === 'postgresql' || dbType === 'postgres' || dbType === 'pg';
|
|
501
|
+
expect(isPostgres).toBe(true);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should not identify postgres when DB_TYPE is "sqlserver"', () => {
|
|
505
|
+
process.env.DB_TYPE = 'sqlserver';
|
|
506
|
+
const dbType = process.env.DB_TYPE?.toLowerCase();
|
|
507
|
+
const isPostgres = dbType === 'postgresql' || dbType === 'postgres' || dbType === 'pg';
|
|
508
|
+
expect(isPostgres).toBe(false);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('should not identify postgres when DB_TYPE is undefined', () => {
|
|
512
|
+
delete process.env.DB_TYPE;
|
|
513
|
+
const dbType = process.env.DB_TYPE?.toLowerCase();
|
|
514
|
+
const isPostgres = dbType === 'postgresql' || dbType === 'postgres' || dbType === 'pg';
|
|
515
|
+
expect(isPostgres).toBe(false);
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// ======================================================================
|
|
520
|
+
// 8. PostgreSQL environment variable resolution
|
|
521
|
+
// ======================================================================
|
|
522
|
+
describe('PostgreSQL environment variable resolution', () => {
|
|
523
|
+
it('should prefer PG_HOST over DB_HOST', () => {
|
|
524
|
+
process.env.PG_HOST = 'pg-host.example.com';
|
|
525
|
+
process.env.DB_HOST = 'db-host.example.com';
|
|
526
|
+
const pgHost = process.env.PG_HOST || process.env.DB_HOST || 'localhost';
|
|
527
|
+
expect(pgHost).toBe('pg-host.example.com');
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should fall back to DB_HOST when PG_HOST is not set', () => {
|
|
531
|
+
delete process.env.PG_HOST;
|
|
532
|
+
process.env.DB_HOST = 'db-host.example.com';
|
|
533
|
+
const pgHost = process.env.PG_HOST || process.env.DB_HOST || 'localhost';
|
|
534
|
+
expect(pgHost).toBe('db-host.example.com');
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should default to localhost when neither PG_HOST nor DB_HOST is set', () => {
|
|
538
|
+
delete process.env.PG_HOST;
|
|
539
|
+
delete process.env.DB_HOST;
|
|
540
|
+
const pgHost = process.env.PG_HOST || process.env.DB_HOST || 'localhost';
|
|
541
|
+
expect(pgHost).toBe('localhost');
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should prefer PG_PORT over DB_PORT', () => {
|
|
545
|
+
process.env.PG_PORT = '5433';
|
|
546
|
+
process.env.DB_PORT = '1433';
|
|
547
|
+
const pgPort = parseInt(process.env.PG_PORT || process.env.DB_PORT || '5432', 10);
|
|
548
|
+
expect(pgPort).toBe(5433);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('should fall back to DB_PORT when PG_PORT is not set', () => {
|
|
552
|
+
delete process.env.PG_PORT;
|
|
553
|
+
process.env.DB_PORT = '1433';
|
|
554
|
+
const pgPort = parseInt(process.env.PG_PORT || process.env.DB_PORT || '5432', 10);
|
|
555
|
+
expect(pgPort).toBe(1433);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('should default to 5432 when neither PG_PORT nor DB_PORT is set', () => {
|
|
559
|
+
delete process.env.PG_PORT;
|
|
560
|
+
delete process.env.DB_PORT;
|
|
561
|
+
const pgPort = parseInt(process.env.PG_PORT || process.env.DB_PORT || '5432', 10);
|
|
562
|
+
expect(pgPort).toBe(5432);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('should prefer PG_USERNAME over DB_USERNAME', () => {
|
|
566
|
+
process.env.PG_USERNAME = 'pgadmin';
|
|
567
|
+
process.env.DB_USERNAME = 'sa';
|
|
568
|
+
const pgUser = process.env.PG_USERNAME || process.env.DB_USERNAME || 'postgres';
|
|
569
|
+
expect(pgUser).toBe('pgadmin');
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('should default to "postgres" when neither PG_USERNAME nor DB_USERNAME is set', () => {
|
|
573
|
+
delete process.env.PG_USERNAME;
|
|
574
|
+
delete process.env.DB_USERNAME;
|
|
575
|
+
const pgUser = process.env.PG_USERNAME || process.env.DB_USERNAME || 'postgres';
|
|
576
|
+
expect(pgUser).toBe('postgres');
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should prefer PG_DATABASE over DB_DATABASE', () => {
|
|
580
|
+
process.env.PG_DATABASE = 'pg_mydb';
|
|
581
|
+
process.env.DB_DATABASE = 'sql_mydb';
|
|
582
|
+
const pgDatabase = process.env.PG_DATABASE || process.env.DB_DATABASE || '';
|
|
583
|
+
expect(pgDatabase).toBe('pg_mydb');
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('should fall back to DB_DATABASE when PG_DATABASE is not set', () => {
|
|
587
|
+
delete process.env.PG_DATABASE;
|
|
588
|
+
process.env.DB_DATABASE = 'sql_mydb';
|
|
589
|
+
const pgDatabase = process.env.PG_DATABASE || process.env.DB_DATABASE || '';
|
|
590
|
+
expect(pgDatabase).toBe('sql_mydb');
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it('should prefer PG_PASSWORD over DB_PASSWORD', () => {
|
|
594
|
+
process.env.PG_PASSWORD = 'pg-secret';
|
|
595
|
+
process.env.DB_PASSWORD = 'sql-secret';
|
|
596
|
+
const pgPass = process.env.PG_PASSWORD || process.env.DB_PASSWORD || '';
|
|
597
|
+
expect(pgPass).toBe('pg-secret');
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// ======================================================================
|
|
602
|
+
// 9. MSSQL-compatible wrapper structure (createMSSQLCompatPool)
|
|
603
|
+
// ======================================================================
|
|
604
|
+
describe('MSSQL-compatible pool wrapper for PostgreSQL', () => {
|
|
605
|
+
it('should create a wrapper with connected property', () => {
|
|
606
|
+
const wrapper = createMockPool({ connected: true });
|
|
607
|
+
expect(wrapper.connected).toBe(true);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('should expose a query method that returns recordset-shaped results', async () => {
|
|
611
|
+
const rows = [{ ID: '1', Name: 'Test' }];
|
|
612
|
+
const wrapper = createMockPool({
|
|
613
|
+
query: vi.fn().mockResolvedValue({ recordset: rows }),
|
|
614
|
+
});
|
|
615
|
+
const result = await wrapper.query('SELECT 1');
|
|
616
|
+
expect(result.recordset).toEqual(rows);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('should expose a request method that returns an object with query', async () => {
|
|
620
|
+
const rows = [{ ID: '2', Name: 'Another' }];
|
|
621
|
+
const wrapper = createMockPool({
|
|
622
|
+
request: () => ({
|
|
623
|
+
query: vi.fn().mockResolvedValue({ recordset: rows }),
|
|
624
|
+
}),
|
|
625
|
+
});
|
|
626
|
+
const req = wrapper.request();
|
|
627
|
+
const result = await req.query('SELECT 1');
|
|
628
|
+
expect(result.recordset).toEqual(rows);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it('should store a _pgPool reference for pg.Pool access', () => {
|
|
632
|
+
const fakePgPool = { connect: vi.fn() };
|
|
633
|
+
const wrapper = createMockPool({ _pgPool: fakePgPool });
|
|
634
|
+
expect(wrapper._pgPool).toBe(fakePgPool);
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// ======================================================================
|
|
639
|
+
// 10. SQL bracket-to-PostgreSQL translation
|
|
640
|
+
// ======================================================================
|
|
641
|
+
describe('SQL bracket-to-double-quote translation', () => {
|
|
642
|
+
// Re-implement the pure function locally since it is not exported
|
|
643
|
+
function translateBracketsToPG(sqlStr: string): string {
|
|
644
|
+
return sqlStr.replace(/\[([^\]]+)\]/g, '"$1"');
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
it('should convert [schema].[table] to "schema"."table"', () => {
|
|
648
|
+
const input = 'SELECT * FROM [__mj].[vwUsers]';
|
|
649
|
+
expect(translateBracketsToPG(input)).toBe('SELECT * FROM "__mj"."vwUsers"');
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it('should convert [column] references', () => {
|
|
653
|
+
const input = 'SELECT [ID], [Name] FROM [Users]';
|
|
654
|
+
expect(translateBracketsToPG(input)).toBe('SELECT "ID", "Name" FROM "Users"');
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('should leave strings without brackets unchanged', () => {
|
|
658
|
+
const input = 'SELECT * FROM users WHERE id = 1';
|
|
659
|
+
expect(translateBracketsToPG(input)).toBe(input);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('should handle mixed bracket and non-bracket identifiers', () => {
|
|
663
|
+
const input = 'SELECT [ID], name FROM [__mj].[Users] WHERE active = 1';
|
|
664
|
+
expect(translateBracketsToPG(input)).toBe(
|
|
665
|
+
'SELECT "ID", name FROM "__mj"."Users" WHERE active = 1'
|
|
666
|
+
);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('should handle empty string input', () => {
|
|
670
|
+
expect(translateBracketsToPG('')).toBe('');
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('should handle nested complex queries with multiple bracket pairs', () => {
|
|
674
|
+
const input =
|
|
675
|
+
'SELECT [a].[ID], [b].[Name] FROM [schema1].[Table1] AS [a] JOIN [schema2].[Table2] AS [b] ON [a].[FK] = [b].[ID]';
|
|
676
|
+
const expected =
|
|
677
|
+
'SELECT "a"."ID", "b"."Name" FROM "schema1"."Table1" AS "a" JOIN "schema2"."Table2" AS "b" ON "a"."FK" = "b"."ID"';
|
|
678
|
+
expect(translateBracketsToPG(input)).toBe(expected);
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// ======================================================================
|
|
683
|
+
// 11. Error handling for invalid configurations
|
|
684
|
+
// ======================================================================
|
|
685
|
+
describe('Error handling for invalid configurations', () => {
|
|
686
|
+
it('should throw when GetReadWriteDataSource receives empty array', () => {
|
|
687
|
+
expect(() => GetReadWriteDataSource([])).toThrow();
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it('should throw descriptive error when no suitable data source found', () => {
|
|
691
|
+
const ro = createDataSourceInfo('Read-Only');
|
|
692
|
+
expect(() => GetReadWriteDataSource([ro])).toThrow('No suitable read-write data source found');
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('should throw when GetReadOnlyDataSource receives empty array with no fallback', () => {
|
|
696
|
+
expect(() =>
|
|
697
|
+
GetReadOnlyDataSource([], { allowFallbackToReadWrite: false })
|
|
698
|
+
).toThrow('No suitable data source found');
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it('should handle providers array containing only non-matching types gracefully', () => {
|
|
702
|
+
const admin = createProviderInfo('Admin');
|
|
703
|
+
const other = createProviderInfo('Other');
|
|
704
|
+
expect(GetReadOnlyProvider([admin, other])).toBeNull();
|
|
705
|
+
expect(GetReadWriteProvider([admin, other])).toBeNull();
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it('should handle providers being undefined without crashing', () => {
|
|
709
|
+
expect(GetReadOnlyProvider(undefined as unknown as ProviderInfo[])).toBeNull();
|
|
710
|
+
expect(GetReadWriteProvider(undefined as unknown as ProviderInfo[])).toBeNull();
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
// ======================================================================
|
|
715
|
+
// 12. Multiple data sources ordering and selection
|
|
716
|
+
// ======================================================================
|
|
717
|
+
describe('Multiple data sources – ordering and selection', () => {
|
|
718
|
+
it('should prefer Read-Only over Read-Write in GetReadOnlyDataSource', () => {
|
|
719
|
+
const rw = createDataSourceInfo('Read-Write');
|
|
720
|
+
const ro = createDataSourceInfo('Read-Only');
|
|
721
|
+
// The returned pool should correspond to the Read-Only source
|
|
722
|
+
// We verify by checking it does not throw and returns a defined value
|
|
723
|
+
const result = GetReadOnlyDataSource([rw, ro]);
|
|
724
|
+
expect(result).toBeDefined();
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it('should select the first Read-Write even when multiple exist', () => {
|
|
728
|
+
const rw1 = createDataSourceInfo('Read-Write');
|
|
729
|
+
const rw2 = createDataSourceInfo('Read-Write');
|
|
730
|
+
const result = GetReadWriteDataSource([rw1, rw2]);
|
|
731
|
+
expect(result).toBeDefined();
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('should build providers array with read-write and optional read-only', () => {
|
|
735
|
+
const rwProvider = createProviderInfo('Read-Write');
|
|
736
|
+
const providers: ProviderInfo[] = [rwProvider];
|
|
737
|
+
|
|
738
|
+
// Simulate adding a read-only provider when available
|
|
739
|
+
const roProvider = createProviderInfo('Read-Only');
|
|
740
|
+
providers.push(roProvider);
|
|
741
|
+
|
|
742
|
+
expect(providers.length).toBe(2);
|
|
743
|
+
expect(providers[0].type).toBe('Read-Write');
|
|
744
|
+
expect(providers[1].type).toBe('Read-Only');
|
|
745
|
+
|
|
746
|
+
const readWrite = GetReadWriteProvider(providers);
|
|
747
|
+
const readOnly = GetReadOnlyProvider(providers);
|
|
748
|
+
expect(readWrite).toBe(rwProvider.provider);
|
|
749
|
+
expect(readOnly).toBe(roProvider.provider);
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
// ======================================================================
|
|
754
|
+
// 13. Pool configuration defaults
|
|
755
|
+
// ======================================================================
|
|
756
|
+
describe('Pool configuration default values', () => {
|
|
757
|
+
it('should use default pool max of 50 when connectionPool.max is undefined', () => {
|
|
758
|
+
const max = undefined ?? 50;
|
|
759
|
+
expect(max).toBe(50);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('should use default pool min of 5 when connectionPool.min is undefined', () => {
|
|
763
|
+
const min = undefined ?? 5;
|
|
764
|
+
expect(min).toBe(5);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it('should use default idleTimeoutMillis of 30000', () => {
|
|
768
|
+
const idle = undefined ?? 30000;
|
|
769
|
+
expect(idle).toBe(30000);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
it('should use default acquireTimeoutMillis of 30000', () => {
|
|
773
|
+
const acquire = undefined ?? 30000;
|
|
774
|
+
expect(acquire).toBe(30000);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('should override defaults when pool config values are explicitly provided', () => {
|
|
778
|
+
const poolConfig = {
|
|
779
|
+
max: 100,
|
|
780
|
+
min: 10,
|
|
781
|
+
idleTimeoutMillis: 60000,
|
|
782
|
+
acquireTimeoutMillis: 15000,
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
expect(poolConfig.max ?? 50).toBe(100);
|
|
786
|
+
expect(poolConfig.min ?? 5).toBe(10);
|
|
787
|
+
expect(poolConfig.idleTimeoutMillis ?? 30000).toBe(60000);
|
|
788
|
+
expect(poolConfig.acquireTimeoutMillis ?? 30000).toBe(15000);
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
// ======================================================================
|
|
793
|
+
// 14. getDbType return type
|
|
794
|
+
// ======================================================================
|
|
795
|
+
describe('getDbType return type compliance', () => {
|
|
796
|
+
it('should return a value matching the DatabasePlatform union type', () => {
|
|
797
|
+
delete process.env.DB_TYPE;
|
|
798
|
+
const result = getDbType();
|
|
799
|
+
const validValues: string[] = ['sqlserver', 'postgresql'];
|
|
800
|
+
expect(validValues).toContain(result);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it('should always return one of the two valid DatabasePlatform values', () => {
|
|
804
|
+
const testValues = ['', 'oracle', 'mysql', 'sqlite', 'mongodb', undefined];
|
|
805
|
+
for (const val of testValues) {
|
|
806
|
+
if (val === undefined) {
|
|
807
|
+
delete process.env.DB_TYPE;
|
|
808
|
+
} else {
|
|
809
|
+
process.env.DB_TYPE = val;
|
|
810
|
+
}
|
|
811
|
+
const result = getDbType();
|
|
812
|
+
expect(['sqlserver', 'postgresql']).toContain(result);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
});
|