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,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single user ID test
|
|
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
|
+
}
|
|
22
|
+
onConfig(db) {
|
|
23
|
+
this.dbset(User);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Clean
|
|
28
|
+
const dbPath = path.join(__dirname, '..', 'database', 'singleUserTest.db');
|
|
29
|
+
if (fs.existsSync(dbPath)) {
|
|
30
|
+
fs.unlinkSync(dbPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function test() {
|
|
34
|
+
const db = new TestContext();
|
|
35
|
+
|
|
36
|
+
// Manually initialize SQLite for testing
|
|
37
|
+
const SQLLiteEngine = require('../SQLLiteEngine');
|
|
38
|
+
const sqlite3 = require('better-sqlite3');
|
|
39
|
+
|
|
40
|
+
db.isSQLite = true;
|
|
41
|
+
db.isMySQL = false;
|
|
42
|
+
db.isPostgres = false;
|
|
43
|
+
db._SQLEngine = new SQLLiteEngine();
|
|
44
|
+
db.db = new sqlite3(dbPath);
|
|
45
|
+
db._SQLEngine.setDB(db.db, 'better-sqlite3');
|
|
46
|
+
|
|
47
|
+
db.onConfig();
|
|
48
|
+
|
|
49
|
+
// Create table
|
|
50
|
+
db.db.exec('CREATE TABLE IF NOT EXISTS User (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)');
|
|
51
|
+
|
|
52
|
+
const user = db.User.new();
|
|
53
|
+
user.name = 'Test';
|
|
54
|
+
|
|
55
|
+
console.log('Before save - user.id:', user.id);
|
|
56
|
+
console.log('Before save - tracked:', db.__trackedEntities.length);
|
|
57
|
+
|
|
58
|
+
await user.save();
|
|
59
|
+
|
|
60
|
+
console.log('After save - user.id:', user.id);
|
|
61
|
+
console.log('After save - user.__proto__._id:', user.__proto__._id);
|
|
62
|
+
|
|
63
|
+
if (user.id) {
|
|
64
|
+
console.log('✓ SUCCESS - ID was set to:', user.id);
|
|
65
|
+
} else {
|
|
66
|
+
console.log('✗ FAIL - ID is still undefined');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
test().catch(console.error);
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: Business Logic Validation
|
|
3
|
+
* Tests: required, email, minLength, maxLength, pattern, min, max, custom
|
|
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("║ Business Logic Validation Test ║");
|
|
12
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
|
|
17
|
+
class User {
|
|
18
|
+
id(db) {
|
|
19
|
+
db.integer().primary().auto();
|
|
20
|
+
}
|
|
21
|
+
name(db) {
|
|
22
|
+
db.string().required('Name is required').minLength(3, 'Name must be at least 3 characters');
|
|
23
|
+
}
|
|
24
|
+
email(db) {
|
|
25
|
+
db.string().required().email('Invalid email format');
|
|
26
|
+
}
|
|
27
|
+
password(db) {
|
|
28
|
+
db.string().minLength(8, 'Password must be at least 8 characters').maxLength(100);
|
|
29
|
+
}
|
|
30
|
+
username(db) {
|
|
31
|
+
db.string().pattern(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores');
|
|
32
|
+
}
|
|
33
|
+
age(db) {
|
|
34
|
+
db.integer().min(18, 'Must be at least 18 years old').max(120, 'Age cannot exceed 120');
|
|
35
|
+
}
|
|
36
|
+
status(db) {
|
|
37
|
+
db.string().custom((value) => {
|
|
38
|
+
return ['active', 'inactive', 'pending'].includes(value);
|
|
39
|
+
}, 'Status must be active, inactive, or pending');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class TestContext extends masterrecord.context {
|
|
44
|
+
constructor() {
|
|
45
|
+
super();
|
|
46
|
+
}
|
|
47
|
+
onConfig(db) {
|
|
48
|
+
this.dbset(User);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dbPath = path.join(__dirname, '..', 'database', 'validation.db');
|
|
53
|
+
if (fs.existsSync(dbPath)) {
|
|
54
|
+
fs.unlinkSync(dbPath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function runTests() {
|
|
58
|
+
const db = new TestContext();
|
|
59
|
+
|
|
60
|
+
// Initialize SQLite database
|
|
61
|
+
const SQLLiteEngine = require('../SQLLiteEngine');
|
|
62
|
+
const sqlite3 = require('better-sqlite3');
|
|
63
|
+
db.isSQLite = true;
|
|
64
|
+
db.isMySQL = false;
|
|
65
|
+
db.isPostgres = false;
|
|
66
|
+
db._SQLEngine = new SQLLiteEngine();
|
|
67
|
+
db.db = new sqlite3(dbPath);
|
|
68
|
+
db._SQLEngine.setDB(db.db, 'better-sqlite3');
|
|
69
|
+
|
|
70
|
+
db.onConfig();
|
|
71
|
+
|
|
72
|
+
// Create table
|
|
73
|
+
db.db.exec('CREATE TABLE IF NOT EXISTS User (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT, password TEXT, username TEXT, age INTEGER, status TEXT)');
|
|
74
|
+
|
|
75
|
+
// Test 1: required() validation
|
|
76
|
+
console.log("📝 Test 1: required() validation");
|
|
77
|
+
console.log("──────────────────────────────────────────────────");
|
|
78
|
+
try {
|
|
79
|
+
const user = db.User.new();
|
|
80
|
+
try {
|
|
81
|
+
user.name = '';
|
|
82
|
+
console.log(` ✗ Should have thrown validation error`);
|
|
83
|
+
failed++;
|
|
84
|
+
} catch (validationError) {
|
|
85
|
+
if (validationError.message.includes('Name is required')) {
|
|
86
|
+
console.log(" ✓ required() prevents empty values");
|
|
87
|
+
passed++;
|
|
88
|
+
} else {
|
|
89
|
+
console.log(` ✗ Wrong error: ${validationError.message}`);
|
|
90
|
+
failed++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch(err) {
|
|
94
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
95
|
+
failed++;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Test 2: email() validation
|
|
99
|
+
console.log("\n📝 Test 2: email() validation");
|
|
100
|
+
console.log("──────────────────────────────────────────────────");
|
|
101
|
+
try {
|
|
102
|
+
const user = db.User.new();
|
|
103
|
+
try {
|
|
104
|
+
user.email = 'not-an-email';
|
|
105
|
+
console.log(` ✗ Should have thrown validation error`);
|
|
106
|
+
failed++;
|
|
107
|
+
} catch (validationError) {
|
|
108
|
+
if (validationError.message.includes('Invalid email format')) {
|
|
109
|
+
console.log(" ✓ email() rejects invalid format");
|
|
110
|
+
passed++;
|
|
111
|
+
} else {
|
|
112
|
+
console.log(` ✗ Wrong error: ${validationError.message}`);
|
|
113
|
+
failed++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch(err) {
|
|
117
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
118
|
+
failed++;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Test 3: minLength() validation
|
|
122
|
+
console.log("\n📝 Test 3: minLength() validation");
|
|
123
|
+
console.log("──────────────────────────────────────────────────");
|
|
124
|
+
try {
|
|
125
|
+
const user = db.User.new();
|
|
126
|
+
try {
|
|
127
|
+
user.name = 'AB'; // Too short
|
|
128
|
+
console.log(` ✗ Should have thrown validation error`);
|
|
129
|
+
failed++;
|
|
130
|
+
} catch (validationError) {
|
|
131
|
+
if (validationError.message.includes('at least 3 characters')) {
|
|
132
|
+
console.log(" ✓ minLength() enforces minimum length");
|
|
133
|
+
passed++;
|
|
134
|
+
} else {
|
|
135
|
+
console.log(` ✗ Wrong error: ${validationError.message}`);
|
|
136
|
+
failed++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch(err) {
|
|
140
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
141
|
+
failed++;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Test 4: maxLength() validation
|
|
145
|
+
console.log("\n📝 Test 4: maxLength() validation");
|
|
146
|
+
console.log("──────────────────────────────────────────────────");
|
|
147
|
+
try {
|
|
148
|
+
const user = db.User.new();
|
|
149
|
+
try {
|
|
150
|
+
user.password = 'a'.repeat(101); // Too long
|
|
151
|
+
console.log(` ✗ Should have thrown validation error`);
|
|
152
|
+
failed++;
|
|
153
|
+
} catch (validationError) {
|
|
154
|
+
if (validationError.message.includes('at most 100 characters')) {
|
|
155
|
+
console.log(" ✓ maxLength() enforces maximum length");
|
|
156
|
+
passed++;
|
|
157
|
+
} else {
|
|
158
|
+
console.log(` ✗ Wrong error: ${validationError.message}`);
|
|
159
|
+
failed++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch(err) {
|
|
163
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
164
|
+
failed++;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Test 5: pattern() validation
|
|
168
|
+
console.log("\n📝 Test 5: pattern() validation");
|
|
169
|
+
console.log("──────────────────────────────────────────────────");
|
|
170
|
+
try {
|
|
171
|
+
const user = db.User.new();
|
|
172
|
+
try {
|
|
173
|
+
user.username = 'user@name'; // Invalid character @
|
|
174
|
+
console.log(` ✗ Should have thrown validation error`);
|
|
175
|
+
failed++;
|
|
176
|
+
} catch (validationError) {
|
|
177
|
+
if (validationError.message.includes('letters, numbers, and underscores')) {
|
|
178
|
+
console.log(" ✓ pattern() enforces regex pattern");
|
|
179
|
+
passed++;
|
|
180
|
+
} else {
|
|
181
|
+
console.log(` ✗ Wrong error: ${validationError.message}`);
|
|
182
|
+
failed++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch(err) {
|
|
186
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
187
|
+
failed++;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Test 6: min() validation for numbers
|
|
191
|
+
console.log("\n📝 Test 6: min() validation for numbers");
|
|
192
|
+
console.log("──────────────────────────────────────────────────");
|
|
193
|
+
try {
|
|
194
|
+
const user = db.User.new();
|
|
195
|
+
try {
|
|
196
|
+
user.age = 17; // Too young
|
|
197
|
+
console.log(` ✗ Should have thrown validation error`);
|
|
198
|
+
failed++;
|
|
199
|
+
} catch (validationError) {
|
|
200
|
+
if (validationError.message.includes('at least 18')) {
|
|
201
|
+
console.log(" ✓ min() enforces minimum value");
|
|
202
|
+
passed++;
|
|
203
|
+
} else {
|
|
204
|
+
console.log(` ✗ Wrong error: ${validationError.message}`);
|
|
205
|
+
failed++;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch(err) {
|
|
209
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
210
|
+
failed++;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Test 7: max() validation for numbers
|
|
214
|
+
console.log("\n📝 Test 7: max() validation for numbers");
|
|
215
|
+
console.log("──────────────────────────────────────────────────");
|
|
216
|
+
try {
|
|
217
|
+
const user = db.User.new();
|
|
218
|
+
try {
|
|
219
|
+
user.age = 121; // Too old
|
|
220
|
+
console.log(` ✗ Should have thrown validation error`);
|
|
221
|
+
failed++;
|
|
222
|
+
} catch (validationError) {
|
|
223
|
+
if (validationError.message.includes('cannot exceed 120')) {
|
|
224
|
+
console.log(" ✓ max() enforces maximum value");
|
|
225
|
+
passed++;
|
|
226
|
+
} else {
|
|
227
|
+
console.log(` ✗ Wrong error: ${validationError.message}`);
|
|
228
|
+
failed++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch(err) {
|
|
232
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
233
|
+
failed++;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Test 8: custom() validation
|
|
237
|
+
console.log("\n📝 Test 8: custom() validation");
|
|
238
|
+
console.log("──────────────────────────────────────────────────");
|
|
239
|
+
try {
|
|
240
|
+
const user = db.User.new();
|
|
241
|
+
try {
|
|
242
|
+
user.status = 'invalid-status';
|
|
243
|
+
console.log(` ✗ Should have thrown validation error`);
|
|
244
|
+
failed++;
|
|
245
|
+
} catch (validationError) {
|
|
246
|
+
if (validationError.message.includes('active, inactive, or pending')) {
|
|
247
|
+
console.log(" ✓ custom() executes custom validation function");
|
|
248
|
+
passed++;
|
|
249
|
+
} else {
|
|
250
|
+
console.log(` ✗ Wrong error: ${validationError.message}`);
|
|
251
|
+
failed++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch(err) {
|
|
255
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
256
|
+
failed++;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Test 9: Valid data passes all validations
|
|
260
|
+
console.log("\n📝 Test 9: Valid data passes all validations");
|
|
261
|
+
console.log("──────────────────────────────────────────────────");
|
|
262
|
+
try {
|
|
263
|
+
// Clear tracked entities from previous tests (they have invalid/missing values)
|
|
264
|
+
db.__trackedEntities = [];
|
|
265
|
+
db.__trackedEntitiesMap = new Map();
|
|
266
|
+
|
|
267
|
+
const user = db.User.new();
|
|
268
|
+
user.name = 'John Doe';
|
|
269
|
+
user.email = 'john@example.com';
|
|
270
|
+
user.password = 'secure-password-123';
|
|
271
|
+
user.username = 'john_doe_123';
|
|
272
|
+
user.age = 25;
|
|
273
|
+
user.status = 'active';
|
|
274
|
+
await user.save();
|
|
275
|
+
|
|
276
|
+
if (user.id) {
|
|
277
|
+
console.log(" ✓ Valid data passes all validations");
|
|
278
|
+
console.log(` ✓ Entity saved with ID: ${user.id}`);
|
|
279
|
+
passed++;
|
|
280
|
+
} else {
|
|
281
|
+
console.log(` ✗ Entity not saved`);
|
|
282
|
+
failed++;
|
|
283
|
+
}
|
|
284
|
+
} catch(err) {
|
|
285
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
286
|
+
failed++;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Summary
|
|
290
|
+
console.log("\n" + "=".repeat(64));
|
|
291
|
+
console.log(`Test Results: ${passed} passed, ${failed} failed`);
|
|
292
|
+
console.log("=".repeat(64));
|
|
293
|
+
|
|
294
|
+
if (failed > 0) {
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
runTests().catch(err => {
|
|
300
|
+
console.error('Fatal error:', err);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
});
|