masterrecord 0.3.50 → 0.3.52
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/QueryLanguage/queryMethods.js +25 -15
- package/context.js +53 -7
- package/package.json +1 -1
- package/test/ensure-ready-test.js +251 -0
|
@@ -171,6 +171,8 @@ class queryMethods{
|
|
|
171
171
|
this.__queryObject.count(str, this.__entity.__name);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
await this.__context._ensureReady();
|
|
175
|
+
|
|
174
176
|
if(this.__context.isSQLite){
|
|
175
177
|
// trying to match string select and relace with select Count(*);
|
|
176
178
|
var entityValue = await this.__context._SQLEngine.getCount(this.__queryObject, this.__entity, this.__context);
|
|
@@ -178,22 +180,24 @@ class queryMethods{
|
|
|
178
180
|
this.__reset();
|
|
179
181
|
return val;
|
|
180
182
|
}
|
|
181
|
-
|
|
182
|
-
if(this.__context.isMySQL){
|
|
183
|
+
else if(this.__context.isMySQL){
|
|
183
184
|
// trying to match string select and relace with select Count(*);
|
|
184
185
|
var entityValue = await this.__context._SQLEngine.getCount(this.__queryObject, this.__entity, this.__context);
|
|
185
186
|
var val = entityValue[Object.keys(entityValue)[0]];
|
|
186
187
|
this.__reset();
|
|
187
188
|
return val;
|
|
188
189
|
}
|
|
189
|
-
|
|
190
|
-
if(this.__context.isPostgres){
|
|
190
|
+
else if(this.__context.isPostgres){
|
|
191
191
|
// trying to match string select and relace with select Count(*);
|
|
192
192
|
var entityValue = await this.__context._SQLEngine.getCount(this.__queryObject, this.__entity, this.__context);
|
|
193
193
|
var val = entityValue[Object.keys(entityValue)[0]];
|
|
194
194
|
this.__reset();
|
|
195
195
|
return val;
|
|
196
196
|
}
|
|
197
|
+
else {
|
|
198
|
+
this.__reset();
|
|
199
|
+
throw new Error('No database type configured. Ensure context.env() or context.useMySql()/useSqlite() has been called and awaited.');
|
|
200
|
+
}
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
/**
|
|
@@ -315,7 +319,7 @@ class queryMethods{
|
|
|
315
319
|
// Get database type from context
|
|
316
320
|
const dbType = this.__context.isSQLite ? 'sqlite' :
|
|
317
321
|
this.__context.isMySQL ? 'mysql' :
|
|
318
|
-
this.__context.isPostgres ? 'postgres' : '
|
|
322
|
+
this.__context.isPostgres ? 'postgres' : 'unknown';
|
|
319
323
|
|
|
320
324
|
// Replace $$ with ? placeholders and collect parameter values
|
|
321
325
|
if(args){
|
|
@@ -436,20 +440,23 @@ class queryMethods{
|
|
|
436
440
|
}
|
|
437
441
|
|
|
438
442
|
// Cache miss - execute query
|
|
443
|
+
await this.__context._ensureReady();
|
|
439
444
|
var result = null;
|
|
440
445
|
if(this.__context.isSQLite){
|
|
441
446
|
var entityValue = await this.__context._SQLEngine.get(this.__queryObject.script, this.__entity, this.__context);
|
|
442
447
|
result = this.__singleEntityBuilder(entityValue);
|
|
443
448
|
}
|
|
444
|
-
|
|
445
|
-
if(this.__context.isMySQL){
|
|
449
|
+
else if(this.__context.isMySQL){
|
|
446
450
|
var entityValue = await this.__context._SQLEngine.get(this.__queryObject.script, this.__entity, this.__context);
|
|
447
|
-
result = this.__singleEntityBuilder(entityValue
|
|
451
|
+
result = this.__singleEntityBuilder(entityValue);
|
|
448
452
|
}
|
|
449
|
-
|
|
450
|
-
if(this.__context.isPostgres){
|
|
453
|
+
else if(this.__context.isPostgres){
|
|
451
454
|
var entityValue = await this.__context._SQLEngine.get(this.__queryObject.script, this.__entity, this.__context);
|
|
452
|
-
result = this.__singleEntityBuilder(entityValue
|
|
455
|
+
result = this.__singleEntityBuilder(entityValue);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
this.__reset();
|
|
459
|
+
throw new Error('No database type configured. Ensure context.env() or context.useMySql()/useSqlite() has been called and awaited.');
|
|
453
460
|
}
|
|
454
461
|
|
|
455
462
|
// Store in cache
|
|
@@ -486,21 +493,24 @@ class queryMethods{
|
|
|
486
493
|
}
|
|
487
494
|
|
|
488
495
|
// Cache miss - execute query
|
|
496
|
+
await this.__context._ensureReady();
|
|
489
497
|
var result = [];
|
|
490
498
|
if(this.__context.isSQLite){
|
|
491
499
|
var entityValue = await this.__context._SQLEngine.all(this.__queryObject.script, this.__entity, this.__context);
|
|
492
500
|
result = this.__multipleEntityBuilder(entityValue);
|
|
493
501
|
}
|
|
494
|
-
|
|
495
|
-
if(this.__context.isMySQL){
|
|
502
|
+
else if(this.__context.isMySQL){
|
|
496
503
|
var entityValue = await this.__context._SQLEngine.all(this.__queryObject.script, this.__entity, this.__context);
|
|
497
504
|
result = this.__multipleEntityBuilder(entityValue);
|
|
498
505
|
}
|
|
499
|
-
|
|
500
|
-
if(this.__context.isPostgres){
|
|
506
|
+
else if(this.__context.isPostgres){
|
|
501
507
|
var entityValue = await this.__context._SQLEngine.all(this.__queryObject.script, this.__entity, this.__context);
|
|
502
508
|
result = this.__multipleEntityBuilder(entityValue);
|
|
503
509
|
}
|
|
510
|
+
else {
|
|
511
|
+
this.__reset();
|
|
512
|
+
throw new Error('No database type configured. Ensure context.env() or context.useMySql()/useSqlite() has been called and awaited.');
|
|
513
|
+
}
|
|
504
514
|
|
|
505
515
|
// Store in cache
|
|
506
516
|
if (this.__useCache && result) {
|
package/context.js
CHANGED
|
@@ -184,6 +184,9 @@ class context {
|
|
|
184
184
|
isMySQL = false;
|
|
185
185
|
isPostgres = false;
|
|
186
186
|
|
|
187
|
+
// Async readiness flag — set by _ensureReady() after _initPromise resolves
|
|
188
|
+
_ready = false;
|
|
189
|
+
|
|
187
190
|
// Static shared cache - all context instances share the same cache
|
|
188
191
|
static _sharedQueryCache = null;
|
|
189
192
|
|
|
@@ -233,6 +236,33 @@ class context {
|
|
|
233
236
|
this._queryCache = context._sharedQueryCache;
|
|
234
237
|
}
|
|
235
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Ensure the database engine is initialized and ready for queries.
|
|
241
|
+
*
|
|
242
|
+
* If an async init is in flight (_initPromise), awaits it.
|
|
243
|
+
* If _SQLEngine is still null after that, throws a clear error.
|
|
244
|
+
* Subsequent calls are a single boolean check (no-op).
|
|
245
|
+
*
|
|
246
|
+
* @throws {DatabaseConnectionError} If the engine failed to initialize
|
|
247
|
+
*/
|
|
248
|
+
async _ensureReady() {
|
|
249
|
+
if (this._ready) return;
|
|
250
|
+
if (this._initPromise) {
|
|
251
|
+
await this._initPromise;
|
|
252
|
+
}
|
|
253
|
+
if (!this._SQLEngine) {
|
|
254
|
+
const dbType = this.isMySQL ? 'MySQL' :
|
|
255
|
+
this.isPostgres ? 'PostgreSQL' :
|
|
256
|
+
this.isSQLite ? 'SQLite' : 'unknown';
|
|
257
|
+
throw new DatabaseConnectionError(
|
|
258
|
+
'Database engine not initialized. Ensure you have awaited env() or the appropriate use*() method before querying.',
|
|
259
|
+
dbType,
|
|
260
|
+
{ hasInitPromise: !!this._initPromise, isSQLite: this.isSQLite, isMySQL: this.isMySQL, isPostgres: this.isPostgres }
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
this._ready = true;
|
|
264
|
+
}
|
|
265
|
+
|
|
236
266
|
/**
|
|
237
267
|
* Parse integer environment variable with validation
|
|
238
268
|
*
|
|
@@ -769,8 +799,10 @@ class context {
|
|
|
769
799
|
// Note: engine is already set in __mysqlInit
|
|
770
800
|
return this;
|
|
771
801
|
})();
|
|
772
|
-
// Prevent unhandled rejection crash — _ensureReady() will
|
|
773
|
-
this._initPromise.catch(() => {
|
|
802
|
+
// Prevent unhandled rejection crash — _ensureReady() will re-throw on query
|
|
803
|
+
this._initPromise.catch((err) => {
|
|
804
|
+
console.error(`[MasterRecord] Database initialization failed: ${err.message || err}`);
|
|
805
|
+
});
|
|
774
806
|
return this._initPromise;
|
|
775
807
|
}
|
|
776
808
|
|
|
@@ -790,8 +822,10 @@ class context {
|
|
|
790
822
|
// Note: engine is already set in __postgresInit
|
|
791
823
|
return this;
|
|
792
824
|
})();
|
|
793
|
-
// Prevent unhandled rejection crash — _ensureReady() will
|
|
794
|
-
this._initPromise.catch(() => {
|
|
825
|
+
// Prevent unhandled rejection crash — _ensureReady() will re-throw on query
|
|
826
|
+
this._initPromise.catch((err) => {
|
|
827
|
+
console.error(`[MasterRecord] Database initialization failed: ${err.message || err}`);
|
|
828
|
+
});
|
|
795
829
|
return this._initPromise;
|
|
796
830
|
}
|
|
797
831
|
|
|
@@ -879,7 +913,7 @@ class context {
|
|
|
879
913
|
);
|
|
880
914
|
}
|
|
881
915
|
|
|
882
|
-
this.
|
|
916
|
+
this.validateDatabaseOptions(options);
|
|
883
917
|
|
|
884
918
|
// Resolve database path using extracted method (eliminates duplicate code)
|
|
885
919
|
const dbPath = this._resolveDatabasePath(options.connection, file.rootFolder, contextName);
|
|
@@ -931,7 +965,7 @@ class context {
|
|
|
931
965
|
* @param {object} options - Database configuration options
|
|
932
966
|
* @throws {ConfigurationError} If options are invalid
|
|
933
967
|
*/
|
|
934
|
-
|
|
968
|
+
validateDatabaseOptions(options) {
|
|
935
969
|
if (!options || typeof options !== 'object') {
|
|
936
970
|
throw new ConfigurationError('Configuration object is missing or invalid');
|
|
937
971
|
}
|
|
@@ -1005,6 +1039,11 @@ class context {
|
|
|
1005
1039
|
);
|
|
1006
1040
|
}
|
|
1007
1041
|
|
|
1042
|
+
/** @deprecated Use validateDatabaseOptions() */
|
|
1043
|
+
validateSQLiteOptions(options) {
|
|
1044
|
+
return this.validateDatabaseOptions(options);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1008
1047
|
/**
|
|
1009
1048
|
* Initialize MySQL database connection using environment file
|
|
1010
1049
|
*
|
|
@@ -1039,7 +1078,7 @@ class context {
|
|
|
1039
1078
|
);
|
|
1040
1079
|
}
|
|
1041
1080
|
|
|
1042
|
-
this.
|
|
1081
|
+
this.validateDatabaseOptions(options);
|
|
1043
1082
|
this.db = await this.__mysqlInit(options, 'mysql2');
|
|
1044
1083
|
// Note: engine is already set in __mysqlInit
|
|
1045
1084
|
return this;
|
|
@@ -1708,6 +1747,7 @@ class context {
|
|
|
1708
1747
|
* db.saveChanges();
|
|
1709
1748
|
*/
|
|
1710
1749
|
async saveChanges() {
|
|
1750
|
+
await this._ensureReady();
|
|
1711
1751
|
try {
|
|
1712
1752
|
const tracked = this.__trackedEntities;
|
|
1713
1753
|
|
|
@@ -1773,6 +1813,12 @@ class context {
|
|
|
1773
1813
|
* context._execute('CREATE INDEX idx_user_email ON User(email)');
|
|
1774
1814
|
*/
|
|
1775
1815
|
_execute(query) {
|
|
1816
|
+
if (!this._SQLEngine) {
|
|
1817
|
+
throw new DatabaseConnectionError(
|
|
1818
|
+
'Cannot execute query: database engine not initialized. Ensure you have awaited env() before running queries.',
|
|
1819
|
+
this.isMySQL ? 'MySQL' : this.isPostgres ? 'PostgreSQL' : 'SQLite'
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1776
1822
|
return this._SQLEngine._execute(query);
|
|
1777
1823
|
}
|
|
1778
1824
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.52",
|
|
4
4
|
"description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
|
|
5
5
|
"main": "MasterRecord.js",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: _ensureReady(), validateDatabaseOptions rename, and async guards
|
|
3
|
+
*
|
|
4
|
+
* Verifies:
|
|
5
|
+
* - Query before env() resolves → throws clear DatabaseConnectionError
|
|
6
|
+
* - Query after await env() → works normally (no regression)
|
|
7
|
+
* - SQLite works without _initPromise (fast path)
|
|
8
|
+
* - validateDatabaseOptions works, validateSQLiteOptions alias works
|
|
9
|
+
* - _execute() throws when engine is null
|
|
10
|
+
* - saveChanges() throws when engine is null
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const context = require('../context');
|
|
14
|
+
|
|
15
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
16
|
+
console.log("║ _ensureReady + Rename + Async Guards Test ║");
|
|
17
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
18
|
+
|
|
19
|
+
let passed = 0;
|
|
20
|
+
let failed = 0;
|
|
21
|
+
|
|
22
|
+
function assert(condition, passMsg, failMsg) {
|
|
23
|
+
if (condition) {
|
|
24
|
+
console.log(` ✓ ${passMsg}`);
|
|
25
|
+
passed++;
|
|
26
|
+
} else {
|
|
27
|
+
console.log(` ✗ ${failMsg}`);
|
|
28
|
+
failed++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Test 1: _ensureReady() throws when engine is null and no init promise
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
console.log("📝 Test 1: _ensureReady() throws DatabaseConnectionError when engine is null");
|
|
36
|
+
console.log("──────────────────────────────────────────────────────────────────");
|
|
37
|
+
|
|
38
|
+
(async () => {
|
|
39
|
+
try {
|
|
40
|
+
const ctx = new context();
|
|
41
|
+
// No env() called — _SQLEngine is null, no _initPromise
|
|
42
|
+
try {
|
|
43
|
+
await ctx._ensureReady();
|
|
44
|
+
assert(false, "", "_ensureReady() did not throw when engine is null");
|
|
45
|
+
} catch (err) {
|
|
46
|
+
assert(
|
|
47
|
+
err.name === 'DatabaseConnectionError',
|
|
48
|
+
`Throws DatabaseConnectionError (got: ${err.name})`,
|
|
49
|
+
`Expected DatabaseConnectionError, got: ${err.name}`
|
|
50
|
+
);
|
|
51
|
+
assert(
|
|
52
|
+
err.message.includes('Database engine not initialized'),
|
|
53
|
+
`Error message is descriptive: "${err.message.substring(0, 60)}..."`,
|
|
54
|
+
`Unexpected error message: ${err.message}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
59
|
+
failed++;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Test 2: _ensureReady() is a no-op after _ready is set
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
console.log("\n📝 Test 2: _ensureReady() fast path — no-op when _ready is true");
|
|
66
|
+
console.log("──────────────────────────────────────────────────────────────────");
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const ctx = new context();
|
|
70
|
+
ctx._SQLEngine = {}; // Fake engine
|
|
71
|
+
ctx.isSQLite = true;
|
|
72
|
+
await ctx._ensureReady(); // Should set _ready = true
|
|
73
|
+
assert(ctx._ready === true, "_ready is true after first call", "_ready was not set");
|
|
74
|
+
|
|
75
|
+
// Second call should be instant (no-op)
|
|
76
|
+
await ctx._ensureReady();
|
|
77
|
+
assert(true, "Second call completes without error (fast path)", "");
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
80
|
+
failed++;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Test 3: _ensureReady() awaits _initPromise if present
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
console.log("\n📝 Test 3: _ensureReady() awaits _initPromise");
|
|
87
|
+
console.log("──────────────────────────────────────────────────────────────────");
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const ctx = new context();
|
|
91
|
+
ctx.isMySQL = true;
|
|
92
|
+
// Simulate async init that sets _SQLEngine after a tick
|
|
93
|
+
ctx._initPromise = new Promise((resolve) => {
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
ctx._SQLEngine = {}; // Fake engine
|
|
96
|
+
resolve();
|
|
97
|
+
}, 10);
|
|
98
|
+
});
|
|
99
|
+
// Prevent unhandled rejection
|
|
100
|
+
ctx._initPromise.catch(() => {});
|
|
101
|
+
|
|
102
|
+
await ctx._ensureReady();
|
|
103
|
+
assert(
|
|
104
|
+
ctx._SQLEngine !== null,
|
|
105
|
+
"_SQLEngine is set after _ensureReady() awaits _initPromise",
|
|
106
|
+
"_SQLEngine is still null"
|
|
107
|
+
);
|
|
108
|
+
assert(ctx._ready === true, "_ready is true", "_ready was not set");
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
111
|
+
failed++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Test 4: _ensureReady() propagates _initPromise rejection
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
console.log("\n📝 Test 4: _ensureReady() propagates _initPromise rejection");
|
|
118
|
+
console.log("──────────────────────────────────────────────────────────────────");
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const ctx = new context();
|
|
122
|
+
ctx.isMySQL = true;
|
|
123
|
+
ctx._initPromise = Promise.reject(new Error('Connection refused'));
|
|
124
|
+
// Prevent unhandled rejection warning
|
|
125
|
+
ctx._initPromise.catch(() => {});
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await ctx._ensureReady();
|
|
129
|
+
assert(false, "", "_ensureReady() did not throw when _initPromise rejected");
|
|
130
|
+
} catch (err) {
|
|
131
|
+
assert(
|
|
132
|
+
err.message === 'Connection refused',
|
|
133
|
+
`Propagates original rejection: "${err.message}"`,
|
|
134
|
+
`Unexpected error: ${err.message}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
139
|
+
failed++;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Test 5: validateDatabaseOptions exists and works
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
console.log("\n📝 Test 5: validateDatabaseOptions() exists and works");
|
|
146
|
+
console.log("──────────────────────────────────────────────────────────────────");
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const ctx = new context();
|
|
150
|
+
assert(
|
|
151
|
+
typeof ctx.validateDatabaseOptions === 'function',
|
|
152
|
+
"validateDatabaseOptions is a function",
|
|
153
|
+
`validateDatabaseOptions is ${typeof ctx.validateDatabaseOptions}`
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Test with a valid SQLite config
|
|
157
|
+
const options = { type: 'sqlite', connection: './test.db' };
|
|
158
|
+
ctx.validateDatabaseOptions(options);
|
|
159
|
+
assert(true, "validateDatabaseOptions accepts valid SQLite config", "");
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
162
|
+
failed++;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Test 6: validateSQLiteOptions deprecated alias works
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
console.log("\n📝 Test 6: validateSQLiteOptions() deprecated alias works");
|
|
169
|
+
console.log("──────────────────────────────────────────────────────────────────");
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const ctx = new context();
|
|
173
|
+
assert(
|
|
174
|
+
typeof ctx.validateSQLiteOptions === 'function',
|
|
175
|
+
"validateSQLiteOptions alias exists",
|
|
176
|
+
`validateSQLiteOptions is ${typeof ctx.validateSQLiteOptions}`
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Should delegate to validateDatabaseOptions
|
|
180
|
+
const options = { type: 'sqlite', connection: './test.db' };
|
|
181
|
+
ctx.validateSQLiteOptions(options);
|
|
182
|
+
assert(true, "validateSQLiteOptions alias delegates correctly", "");
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
185
|
+
failed++;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Test 7: _execute() throws when _SQLEngine is null
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
console.log("\n📝 Test 7: _execute() throws when _SQLEngine is null");
|
|
192
|
+
console.log("──────────────────────────────────────────────────────────────────");
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const ctx = new context();
|
|
196
|
+
// _SQLEngine is null by default
|
|
197
|
+
try {
|
|
198
|
+
ctx._execute('SELECT 1');
|
|
199
|
+
assert(false, "", "_execute() did not throw when engine is null");
|
|
200
|
+
} catch (err) {
|
|
201
|
+
assert(
|
|
202
|
+
err.name === 'DatabaseConnectionError',
|
|
203
|
+
`Throws DatabaseConnectionError (got: ${err.name})`,
|
|
204
|
+
`Expected DatabaseConnectionError, got: ${err.name}`
|
|
205
|
+
);
|
|
206
|
+
assert(
|
|
207
|
+
err.message.includes('database engine not initialized'),
|
|
208
|
+
`Error message is descriptive`,
|
|
209
|
+
`Unexpected error message: ${err.message}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
214
|
+
failed++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Test 8: saveChanges() throws when engine is null (via _ensureReady)
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
console.log("\n📝 Test 8: saveChanges() throws when engine is null");
|
|
221
|
+
console.log("──────────────────────────────────────────────────────────────────");
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const ctx = new context();
|
|
225
|
+
// No env() called — should throw via _ensureReady()
|
|
226
|
+
try {
|
|
227
|
+
await ctx.saveChanges();
|
|
228
|
+
// saveChanges returns early if no tracked entities — that's OK
|
|
229
|
+
// The _ensureReady check happens before the tracked-entities check
|
|
230
|
+
assert(false, "", "saveChanges() did not throw when engine is null");
|
|
231
|
+
} catch (err) {
|
|
232
|
+
assert(
|
|
233
|
+
err.name === 'DatabaseConnectionError',
|
|
234
|
+
`Throws DatabaseConnectionError (got: ${err.name})`,
|
|
235
|
+
`Expected DatabaseConnectionError, got: ${err.name}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
240
|
+
failed++;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Summary
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
console.log("\n══════════════════════════════════════════════════════════════════");
|
|
247
|
+
console.log(`Results: ${passed} passed, ${failed} failed out of ${passed + failed} assertions`);
|
|
248
|
+
console.log("══════════════════════════════════════════════════════════════════\n");
|
|
249
|
+
|
|
250
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
251
|
+
})();
|