masterrecord 0.3.26 → 0.3.29
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/.claude/settings.local.json +21 -1
- package/Entity/entityModel.js +136 -0
- package/Entity/entityModelBuilder.js +21 -3
- package/Entity/entityTrackerModel.js +251 -1
- package/QueryLanguage/queryMethods.js +330 -4
- package/SQLLiteEngine.js +4 -0
- package/Tools.js +15 -2
- package/context.js +198 -5
- package/mySQLEngine.js +11 -1
- package/package.json +1 -1
- package/postgresEngine.js +6 -1
- package/readme.md +1070 -102
- package/test/bulk-operations-test.js +235 -0
- package/test/cache-toObject-test.js +105 -0
- package/test/debug-id-test.js +63 -0
- package/test/double-where-bug-test.js +71 -0
- package/test/entity-methods-test.js +269 -0
- package/test/id-setting-validation.js +202 -0
- package/test/insert-return-test.js +39 -0
- package/test/lifecycle-hooks-test.js +258 -0
- package/test/query-helpers-test.js +258 -0
- package/test/query-isolation-test.js +59 -0
- package/test/simple-id-test.js +61 -0
- package/test/single-user-id-test.js +70 -0
- package/test/validation-test.js +302 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: Lifecycle Hooks
|
|
3
|
+
* Tests: beforeSave, afterSave, beforeDelete, afterDelete
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const masterrecord = require('../MasterRecord.js');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
11
|
+
console.log("║ Lifecycle Hooks Test ║");
|
|
12
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
|
|
17
|
+
// Track hook executions
|
|
18
|
+
const hookLog = [];
|
|
19
|
+
|
|
20
|
+
class User {
|
|
21
|
+
id(db) {
|
|
22
|
+
db.integer().primary().auto();
|
|
23
|
+
}
|
|
24
|
+
name(db) {
|
|
25
|
+
db.string();
|
|
26
|
+
}
|
|
27
|
+
email(db) {
|
|
28
|
+
db.string();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
beforeSave() {
|
|
32
|
+
hookLog.push('beforeSave');
|
|
33
|
+
// Automatically set email domain if missing
|
|
34
|
+
if (this.email && !this.email.includes('@')) {
|
|
35
|
+
this.email = this.email + '@example.com';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
afterSave() {
|
|
40
|
+
hookLog.push('afterSave');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
beforeDelete() {
|
|
44
|
+
hookLog.push('beforeDelete');
|
|
45
|
+
// Prevent deletion of admin users
|
|
46
|
+
if (this.name === 'admin') {
|
|
47
|
+
throw new Error('Cannot delete admin user');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
afterDelete() {
|
|
52
|
+
hookLog.push('afterDelete');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class TestContext extends masterrecord.context {
|
|
57
|
+
constructor() {
|
|
58
|
+
super();
|
|
59
|
+
}
|
|
60
|
+
onConfig(db) {
|
|
61
|
+
this.dbset(User);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const dbPath = path.join(__dirname, '..', 'database', 'lifecycleHooks.db');
|
|
66
|
+
if (fs.existsSync(dbPath)) {
|
|
67
|
+
fs.unlinkSync(dbPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function runTests() {
|
|
71
|
+
const db = new TestContext();
|
|
72
|
+
|
|
73
|
+
// Initialize SQLite database
|
|
74
|
+
const SQLLiteEngine = require('../SQLLiteEngine');
|
|
75
|
+
const sqlite3 = require('better-sqlite3');
|
|
76
|
+
db.isSQLite = true;
|
|
77
|
+
db.isMySQL = false;
|
|
78
|
+
db.isPostgres = false;
|
|
79
|
+
db._SQLEngine = new SQLLiteEngine();
|
|
80
|
+
db.db = new sqlite3(dbPath);
|
|
81
|
+
db._SQLEngine.setDB(db.db, 'better-sqlite3');
|
|
82
|
+
|
|
83
|
+
db.onConfig();
|
|
84
|
+
|
|
85
|
+
// Create table
|
|
86
|
+
db.db.exec('CREATE TABLE IF NOT EXISTS User (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT)');
|
|
87
|
+
|
|
88
|
+
// Test 1: beforeSave hook executes on insert
|
|
89
|
+
console.log("📝 Test 1: beforeSave hook executes on insert");
|
|
90
|
+
console.log("──────────────────────────────────────────────────");
|
|
91
|
+
try {
|
|
92
|
+
hookLog.length = 0;
|
|
93
|
+
|
|
94
|
+
const user = db.User.new();
|
|
95
|
+
user.name = 'Alice';
|
|
96
|
+
user.email = 'alice'; // No @ - should be fixed by hook
|
|
97
|
+
await user.save();
|
|
98
|
+
|
|
99
|
+
if (hookLog.includes('beforeSave') && user.email === 'alice@example.com') {
|
|
100
|
+
console.log(" ✓ beforeSave executed before insert");
|
|
101
|
+
console.log(" ✓ Email auto-completed to: alice@example.com");
|
|
102
|
+
passed++;
|
|
103
|
+
} else {
|
|
104
|
+
console.log(` ✗ Hook failed: ${hookLog.join(', ')}, email: ${user.email}`);
|
|
105
|
+
failed++;
|
|
106
|
+
}
|
|
107
|
+
} catch(err) {
|
|
108
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
109
|
+
failed++;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Test 2: afterSave hook executes after insert
|
|
113
|
+
console.log("\n📝 Test 2: afterSave hook executes after insert");
|
|
114
|
+
console.log("──────────────────────────────────────────────────");
|
|
115
|
+
try {
|
|
116
|
+
if (hookLog.includes('afterSave') && hookLog.indexOf('afterSave') > hookLog.indexOf('beforeSave')) {
|
|
117
|
+
console.log(" ✓ afterSave executed after insert");
|
|
118
|
+
console.log(" ✓ Hooks executed in order: beforeSave → afterSave");
|
|
119
|
+
passed++;
|
|
120
|
+
} else {
|
|
121
|
+
console.log(` ✗ Hook order incorrect: ${hookLog.join(' → ')}`);
|
|
122
|
+
failed++;
|
|
123
|
+
}
|
|
124
|
+
} catch(err) {
|
|
125
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
126
|
+
failed++;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Test 3: beforeSave hook executes on update
|
|
130
|
+
console.log("\n📝 Test 3: beforeSave hook executes on update");
|
|
131
|
+
console.log("──────────────────────────────────────────────────");
|
|
132
|
+
try {
|
|
133
|
+
hookLog.length = 0;
|
|
134
|
+
|
|
135
|
+
const user = await db.User.findById(1);
|
|
136
|
+
user.name = 'Alice Updated';
|
|
137
|
+
await user.save();
|
|
138
|
+
|
|
139
|
+
if (hookLog.includes('beforeSave')) {
|
|
140
|
+
console.log(" ✓ beforeSave executed on update");
|
|
141
|
+
passed++;
|
|
142
|
+
} else {
|
|
143
|
+
console.log(` ✗ beforeSave not called on update`);
|
|
144
|
+
failed++;
|
|
145
|
+
}
|
|
146
|
+
} catch(err) {
|
|
147
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
148
|
+
failed++;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Test 4: beforeDelete hook can prevent deletion
|
|
152
|
+
console.log("\n📝 Test 4: beforeDelete hook can prevent deletion");
|
|
153
|
+
console.log("──────────────────────────────────────────────────");
|
|
154
|
+
try {
|
|
155
|
+
const admin = db.User.new();
|
|
156
|
+
admin.name = 'admin';
|
|
157
|
+
admin.email = 'admin@example.com';
|
|
158
|
+
await admin.save();
|
|
159
|
+
|
|
160
|
+
const adminId = admin.id;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
await admin.delete();
|
|
164
|
+
console.log(` ✗ Deletion should have been prevented`);
|
|
165
|
+
failed++;
|
|
166
|
+
} catch (hookError) {
|
|
167
|
+
if (hookError.message.includes('Cannot delete admin user')) {
|
|
168
|
+
// Check that admin still exists
|
|
169
|
+
const check = await db.User.findById(adminId);
|
|
170
|
+
if (check && check.name === 'admin') {
|
|
171
|
+
console.log(" ✓ beforeDelete prevented deletion of admin user");
|
|
172
|
+
passed++;
|
|
173
|
+
} else {
|
|
174
|
+
console.log(` ✗ Admin was deleted despite hook`);
|
|
175
|
+
failed++;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
console.log(` ✗ Unexpected error: ${hookError.message}`);
|
|
179
|
+
failed++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch(err) {
|
|
183
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
184
|
+
failed++;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Test 5: afterDelete hook executes after successful deletion
|
|
188
|
+
console.log("\n📝 Test 5: afterDelete hook executes after deletion");
|
|
189
|
+
console.log("──────────────────────────────────────────────────");
|
|
190
|
+
try {
|
|
191
|
+
hookLog.length = 0;
|
|
192
|
+
|
|
193
|
+
const user = db.User.new();
|
|
194
|
+
user.name = 'ToDelete';
|
|
195
|
+
user.email = 'delete@example.com';
|
|
196
|
+
await user.save();
|
|
197
|
+
|
|
198
|
+
await user.delete();
|
|
199
|
+
|
|
200
|
+
if (hookLog.includes('beforeDelete') && hookLog.includes('afterDelete')) {
|
|
201
|
+
console.log(" ✓ beforeDelete and afterDelete executed");
|
|
202
|
+
console.log(" ✓ Hooks executed in order: beforeDelete → afterDelete");
|
|
203
|
+
passed++;
|
|
204
|
+
} else {
|
|
205
|
+
console.log(` ✗ Delete hooks missing: ${hookLog.join(', ')}`);
|
|
206
|
+
failed++;
|
|
207
|
+
}
|
|
208
|
+
} catch(err) {
|
|
209
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
210
|
+
failed++;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Test 6: Batch operations execute hooks for all entities
|
|
214
|
+
console.log("\n📝 Test 6: Batch operations execute hooks for all entities");
|
|
215
|
+
console.log("──────────────────────────────────────────────────");
|
|
216
|
+
try {
|
|
217
|
+
hookLog.length = 0;
|
|
218
|
+
|
|
219
|
+
const user1 = db.User.new();
|
|
220
|
+
user1.name = 'Batch1';
|
|
221
|
+
user1.email = 'batch1';
|
|
222
|
+
|
|
223
|
+
const user2 = db.User.new();
|
|
224
|
+
user2.name = 'Batch2';
|
|
225
|
+
user2.email = 'batch2';
|
|
226
|
+
|
|
227
|
+
await db.saveChanges();
|
|
228
|
+
|
|
229
|
+
const beforeSaveCount = hookLog.filter(h => h === 'beforeSave').length;
|
|
230
|
+
const afterSaveCount = hookLog.filter(h => h === 'afterSave').length;
|
|
231
|
+
|
|
232
|
+
if (beforeSaveCount === 2 && afterSaveCount === 2) {
|
|
233
|
+
console.log(" ✓ Hooks executed for all entities in batch");
|
|
234
|
+
console.log(` ✓ beforeSave: ${beforeSaveCount}x, afterSave: ${afterSaveCount}x`);
|
|
235
|
+
passed++;
|
|
236
|
+
} else {
|
|
237
|
+
console.log(` ✗ Hook counts incorrect: beforeSave=${beforeSaveCount}, afterSave=${afterSaveCount}`);
|
|
238
|
+
failed++;
|
|
239
|
+
}
|
|
240
|
+
} catch(err) {
|
|
241
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
242
|
+
failed++;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Summary
|
|
246
|
+
console.log("\n" + "=".repeat(64));
|
|
247
|
+
console.log(`Test Results: ${passed} passed, ${failed} failed`);
|
|
248
|
+
console.log("=".repeat(64));
|
|
249
|
+
|
|
250
|
+
if (failed > 0) {
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
runTests().catch(err => {
|
|
256
|
+
console.error('Fatal error:', err);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
});
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: Query Helper Methods
|
|
3
|
+
* Tests: .first(), .last(), .exists(), .pluck()
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const masterrecord = require('../MasterRecord.js');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
11
|
+
console.log("║ Query Helper Methods Test ║");
|
|
12
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
|
|
17
|
+
// Define test entities
|
|
18
|
+
class User {
|
|
19
|
+
id(db) {
|
|
20
|
+
db.integer().primary().auto();
|
|
21
|
+
}
|
|
22
|
+
name(db) {
|
|
23
|
+
db.string();
|
|
24
|
+
}
|
|
25
|
+
email(db) {
|
|
26
|
+
db.string();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Create test context
|
|
31
|
+
class TestContext extends masterrecord.context {
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
}
|
|
35
|
+
onConfig(db) {
|
|
36
|
+
this.dbset(User);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Clean test database
|
|
41
|
+
const dbPath = path.join(__dirname, '..', 'database', 'testQueryHelpers.db');
|
|
42
|
+
if (fs.existsSync(dbPath)) {
|
|
43
|
+
fs.unlinkSync(dbPath);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function runTests() {
|
|
47
|
+
const db = new TestContext();
|
|
48
|
+
|
|
49
|
+
// Initialize SQLite database
|
|
50
|
+
const SQLLiteEngine = require('../SQLLiteEngine');
|
|
51
|
+
const sqlite3 = require('better-sqlite3');
|
|
52
|
+
db.isSQLite = true;
|
|
53
|
+
db.isMySQL = false;
|
|
54
|
+
db.isPostgres = false;
|
|
55
|
+
db._SQLEngine = new SQLLiteEngine();
|
|
56
|
+
db.db = new sqlite3(dbPath);
|
|
57
|
+
db._SQLEngine.setDB(db.db, 'better-sqlite3');
|
|
58
|
+
|
|
59
|
+
db.onConfig();
|
|
60
|
+
|
|
61
|
+
// Create table
|
|
62
|
+
db.db.exec('CREATE TABLE IF NOT EXISTS User (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT)');
|
|
63
|
+
|
|
64
|
+
// Create test data
|
|
65
|
+
const users = [
|
|
66
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
67
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
68
|
+
{ name: 'Charlie', email: 'charlie@example.com' },
|
|
69
|
+
{ name: 'Dave', email: 'dave@example.com' },
|
|
70
|
+
{ name: 'Eve', email: 'eve@example.com' }
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
for (const userData of users) {
|
|
74
|
+
const user = db.User.new();
|
|
75
|
+
user.name = userData.name;
|
|
76
|
+
user.email = userData.email;
|
|
77
|
+
await user.save();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Test 1: .first() returns first record
|
|
81
|
+
console.log("📝 Test 1: .first() returns first record");
|
|
82
|
+
console.log("──────────────────────────────────────────────────");
|
|
83
|
+
try {
|
|
84
|
+
const first = await db.User.first();
|
|
85
|
+
|
|
86
|
+
if (first && first.name === 'Alice') {
|
|
87
|
+
console.log(" ✓ .first() returns first record ordered by primary key");
|
|
88
|
+
passed++;
|
|
89
|
+
} else {
|
|
90
|
+
console.log(` ✗ .first() failed - got: ${first?.name}`);
|
|
91
|
+
failed++;
|
|
92
|
+
}
|
|
93
|
+
} catch(err) {
|
|
94
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
95
|
+
failed++;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Test 2: .last() returns last record
|
|
99
|
+
console.log("\n📝 Test 2: .last() returns last record");
|
|
100
|
+
console.log("──────────────────────────────────────────────────");
|
|
101
|
+
try {
|
|
102
|
+
const last = await db.User.last();
|
|
103
|
+
|
|
104
|
+
if (last && last.name === 'Eve') {
|
|
105
|
+
console.log(" ✓ .last() returns last record ordered by primary key descending");
|
|
106
|
+
passed++;
|
|
107
|
+
} else {
|
|
108
|
+
console.log(` ✗ .last() failed - got: ${last?.name}`);
|
|
109
|
+
failed++;
|
|
110
|
+
}
|
|
111
|
+
} catch(err) {
|
|
112
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
113
|
+
failed++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Test 3: .exists() returns true for existing records
|
|
117
|
+
console.log("\n📝 Test 3: .exists() checks record existence");
|
|
118
|
+
console.log("──────────────────────────────────────────────────");
|
|
119
|
+
try {
|
|
120
|
+
const exists = await db.User
|
|
121
|
+
.where(u => u.email == $$, 'alice@example.com')
|
|
122
|
+
.exists();
|
|
123
|
+
|
|
124
|
+
const notExists = await db.User
|
|
125
|
+
.where(u => u.email == $$, 'nonexistent@example.com')
|
|
126
|
+
.exists();
|
|
127
|
+
|
|
128
|
+
if (exists === true && notExists === false) {
|
|
129
|
+
console.log(" ✓ .exists() returns true for existing records");
|
|
130
|
+
console.log(" ✓ .exists() returns false for non-existing records");
|
|
131
|
+
passed++;
|
|
132
|
+
} else {
|
|
133
|
+
console.log(` ✗ .exists() failed - exists: ${exists}, notExists: ${notExists}`);
|
|
134
|
+
failed++;
|
|
135
|
+
}
|
|
136
|
+
} catch(err) {
|
|
137
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
138
|
+
failed++;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Test 4: .pluck() extracts column values
|
|
142
|
+
console.log("\n📝 Test 4: .pluck() extracts column values");
|
|
143
|
+
console.log("──────────────────────────────────────────────────");
|
|
144
|
+
try {
|
|
145
|
+
const emails = await db.User.pluck('email');
|
|
146
|
+
|
|
147
|
+
if (Array.isArray(emails) &&
|
|
148
|
+
emails.length === 5 &&
|
|
149
|
+
emails.includes('alice@example.com') &&
|
|
150
|
+
emails.includes('eve@example.com')) {
|
|
151
|
+
console.log(" ✓ .pluck() extracts column as array");
|
|
152
|
+
console.log(" ✓ All values included");
|
|
153
|
+
passed++;
|
|
154
|
+
} else {
|
|
155
|
+
console.log(` ✗ .pluck() failed - got: ${emails?.length} items`);
|
|
156
|
+
failed++;
|
|
157
|
+
}
|
|
158
|
+
} catch(err) {
|
|
159
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
160
|
+
failed++;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Test 5: .pluck() with where clause
|
|
164
|
+
console.log("\n📝 Test 5: .pluck() respects where clause");
|
|
165
|
+
console.log("──────────────────────────────────────────────────");
|
|
166
|
+
try {
|
|
167
|
+
const emails = await db.User
|
|
168
|
+
.where(u => u.name == $$, 'Alice')
|
|
169
|
+
.pluck('email');
|
|
170
|
+
|
|
171
|
+
if (Array.isArray(emails) &&
|
|
172
|
+
emails.length === 1 &&
|
|
173
|
+
emails[0] === 'alice@example.com') {
|
|
174
|
+
console.log(" ✓ .pluck() respects where clause");
|
|
175
|
+
passed++;
|
|
176
|
+
} else {
|
|
177
|
+
console.log(` ✗ .pluck() with where failed`);
|
|
178
|
+
failed++;
|
|
179
|
+
}
|
|
180
|
+
} catch(err) {
|
|
181
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
182
|
+
failed++;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Test 6: .first() with where clause
|
|
186
|
+
console.log("\n📝 Test 6: .first() respects where clause");
|
|
187
|
+
console.log("──────────────────────────────────────────────────");
|
|
188
|
+
try {
|
|
189
|
+
const first = await db.User
|
|
190
|
+
.where(u => u.name == $$, 'Charlie')
|
|
191
|
+
.first();
|
|
192
|
+
|
|
193
|
+
if (first && first.name === 'Charlie') {
|
|
194
|
+
console.log(" ✓ .first() respects where clause");
|
|
195
|
+
passed++;
|
|
196
|
+
} else {
|
|
197
|
+
console.log(` ✗ .first() with where failed`);
|
|
198
|
+
failed++;
|
|
199
|
+
}
|
|
200
|
+
} catch(err) {
|
|
201
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
202
|
+
failed++;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Test 7: .first() returns null if no records
|
|
206
|
+
console.log("\n📝 Test 7: .first() returns null if no records");
|
|
207
|
+
console.log("──────────────────────────────────────────────────");
|
|
208
|
+
try {
|
|
209
|
+
const first = await db.User
|
|
210
|
+
.where(u => u.name == $$, 'NonExistent')
|
|
211
|
+
.first();
|
|
212
|
+
|
|
213
|
+
if (first === null) {
|
|
214
|
+
console.log(" ✓ .first() returns null if no records");
|
|
215
|
+
passed++;
|
|
216
|
+
} else {
|
|
217
|
+
console.log(` ✗ .first() should return null`);
|
|
218
|
+
failed++;
|
|
219
|
+
}
|
|
220
|
+
} catch(err) {
|
|
221
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
222
|
+
failed++;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Test 8: .pluck() with ordering
|
|
226
|
+
console.log("\n📝 Test 8: .pluck() respects ordering");
|
|
227
|
+
console.log("──────────────────────────────────────────────────");
|
|
228
|
+
try {
|
|
229
|
+
const names = await db.User
|
|
230
|
+
.orderBy(u => u.name)
|
|
231
|
+
.pluck('name');
|
|
232
|
+
|
|
233
|
+
if (names[0] === 'Alice' && names[4] === 'Eve') {
|
|
234
|
+
console.log(" ✓ .pluck() respects orderBy");
|
|
235
|
+
passed++;
|
|
236
|
+
} else {
|
|
237
|
+
console.log(` ✗ .pluck() ordering failed`);
|
|
238
|
+
failed++;
|
|
239
|
+
}
|
|
240
|
+
} catch(err) {
|
|
241
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
242
|
+
failed++;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Summary
|
|
246
|
+
console.log("\n" + "=".repeat(64));
|
|
247
|
+
console.log(`Test Results: ${passed} passed, ${failed} failed`);
|
|
248
|
+
console.log("=".repeat(64));
|
|
249
|
+
|
|
250
|
+
if (failed > 0) {
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
runTests().catch(err => {
|
|
256
|
+
console.error('Fatal error:', err);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test for query instance isolation
|
|
3
|
+
* Verifies that accessing an entity multiple times returns fresh queryMethods instances
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const queryMethods = require('../QueryLanguage/queryMethods');
|
|
7
|
+
const queryScript = require('../QueryLanguage/queryScript');
|
|
8
|
+
|
|
9
|
+
console.log('=== Testing Query Instance Isolation ===\n');
|
|
10
|
+
|
|
11
|
+
// Mock entity
|
|
12
|
+
const mockEntity = {
|
|
13
|
+
__name: 'TestEntity',
|
|
14
|
+
id: { type: 'number' },
|
|
15
|
+
name: { type: 'string' }
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Mock context
|
|
19
|
+
const mockContext = {
|
|
20
|
+
__track: () => {},
|
|
21
|
+
__entities: []
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
console.log('Test 1: Creating separate query instances');
|
|
25
|
+
const query1 = new queryMethods(mockEntity, mockContext);
|
|
26
|
+
const query2 = new queryMethods(mockEntity, mockContext);
|
|
27
|
+
|
|
28
|
+
console.log(` Query 1 instance ID: ${query1.__queryObject !== undefined ? 'exists' : 'undefined'}`);
|
|
29
|
+
console.log(` Query 2 instance ID: ${query2.__queryObject !== undefined ? 'exists' : 'undefined'}`);
|
|
30
|
+
console.log(` ✓ Instances are separate: ${query1 !== query2}`);
|
|
31
|
+
console.log(` ✓ QueryObjects are separate: ${query1.__queryObject !== query2.__queryObject}\n`);
|
|
32
|
+
|
|
33
|
+
console.log('Test 2: Building queries with same parameters in different instances');
|
|
34
|
+
query1.where(e => e.id == $$, 1);
|
|
35
|
+
query2.where(e => e.id == $$, 2);
|
|
36
|
+
|
|
37
|
+
const params1 = query1.__queryObject.parameters.get();
|
|
38
|
+
const params2 = query2.__queryObject.parameters.get();
|
|
39
|
+
|
|
40
|
+
console.log(` Query 1 parameters: [${params1}]`);
|
|
41
|
+
console.log(` Query 2 parameters: [${params2}]`);
|
|
42
|
+
|
|
43
|
+
if (params1.length === 1 && params1[0] === 1) {
|
|
44
|
+
console.log(' ✓ Query 1 has correct parameters');
|
|
45
|
+
} else {
|
|
46
|
+
console.error(' ✗ FAIL: Query 1 parameters are wrong!');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (params2.length === 1 && params2[0] === 2) {
|
|
51
|
+
console.log(' ✓ Query 2 has correct parameters\n');
|
|
52
|
+
} else {
|
|
53
|
+
console.error(' ✗ FAIL: Query 2 parameters are wrong!');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log('✅ All tests passed! Query instances are properly isolated.\n');
|
|
58
|
+
console.log('Note: This confirms the fix will work. The getter in context.js');
|
|
59
|
+
console.log('now creates a new queryMethods instance each time an entity is accessed.');
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple test to check if ID is set after save
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const masterrecord = require('../MasterRecord.js');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
|
|
9
|
+
class User {
|
|
10
|
+
id(db) {
|
|
11
|
+
db.integer().primary().auto();
|
|
12
|
+
}
|
|
13
|
+
name(db) {
|
|
14
|
+
db.string();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class TestContext extends masterrecord.context {
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
this.database = path.join(__dirname, '..', 'database', 'simpleIdTest.db');
|
|
22
|
+
}
|
|
23
|
+
onConfig(db) {
|
|
24
|
+
this.dbset(User);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Clean
|
|
29
|
+
if (fs.existsSync(path.join(__dirname, '..', 'database', 'simpleIdTest.db'))) {
|
|
30
|
+
fs.unlinkSync(path.join(__dirname, '..', 'database', 'simpleIdTest.db'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function test() {
|
|
34
|
+
const db = new TestContext();
|
|
35
|
+
db.onConfig();
|
|
36
|
+
|
|
37
|
+
const user = db.User.new();
|
|
38
|
+
user.name = 'Test';
|
|
39
|
+
|
|
40
|
+
console.log('Before save - user.id:', user.id);
|
|
41
|
+
console.log('Before save - user.__proto__:', Object.keys(user.__proto__));
|
|
42
|
+
|
|
43
|
+
await user.save();
|
|
44
|
+
|
|
45
|
+
console.log('After save - user.id:', user.id);
|
|
46
|
+
console.log('After save - user.__proto__:', Object.keys(user.__proto__));
|
|
47
|
+
console.log('After save - user.__proto__._id:', user.__proto__._id);
|
|
48
|
+
|
|
49
|
+
// Try direct access
|
|
50
|
+
console.log('Direct access test:');
|
|
51
|
+
for (const key in user) {
|
|
52
|
+
if (key === 'id') {
|
|
53
|
+
console.log('Found id property via for-in');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log('Has own property id?', user.hasOwnProperty('id'));
|
|
58
|
+
console.log('Keys:', Object.keys(user));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test().catch(console.error);
|