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.
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Test: Entity Instance Methods
3
+ * Tests: .toObject(), .toJSON(), .delete(), .reload(), .clone()
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("║ Entity Instance 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
+ Tags(db) {
29
+ db.hasMany('Tag', 'user_id').nullable();
30
+ }
31
+ }
32
+
33
+ class Tag {
34
+ id(db) {
35
+ db.integer().primary().auto();
36
+ }
37
+ name(db) {
38
+ db.string();
39
+ }
40
+ user_id(db) {
41
+ db.integer();
42
+ }
43
+ User(db) {
44
+ db.belongsTo('User', 'user_id').nullable();
45
+ }
46
+ }
47
+
48
+ // Create test context
49
+ class TestContext extends masterrecord.context {
50
+ constructor() {
51
+ super();
52
+ }
53
+ onConfig(db) {
54
+ this.dbset(User);
55
+ this.dbset(Tag);
56
+ }
57
+ }
58
+
59
+ // Clean test database
60
+ const dbPath = path.join(__dirname, '..', 'database', 'testEntityMethods.db');
61
+ if (fs.existsSync(dbPath)) {
62
+ fs.unlinkSync(dbPath);
63
+ }
64
+
65
+ async function runTests() {
66
+ const db = new TestContext();
67
+
68
+ // Initialize SQLite database
69
+ const SQLLiteEngine = require('../SQLLiteEngine');
70
+ const sqlite3 = require('better-sqlite3');
71
+ db.isSQLite = true;
72
+ db.isMySQL = false;
73
+ db.isPostgres = false;
74
+ db._SQLEngine = new SQLLiteEngine();
75
+ db.db = new sqlite3(dbPath);
76
+ db._SQLEngine.setDB(db.db, 'better-sqlite3');
77
+
78
+ db.onConfig();
79
+
80
+ // Create tables
81
+ db.db.exec('CREATE TABLE IF NOT EXISTS User (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT)');
82
+ db.db.exec('CREATE TABLE IF NOT EXISTS Tag (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, user_id INTEGER)');
83
+
84
+ // Test 1: .toObject() converts entity to plain object
85
+ console.log("📝 Test 1: .toObject() converts entity to plain object");
86
+ console.log("──────────────────────────────────────────────────");
87
+ try {
88
+ const user = db.User.new();
89
+ user.name = 'Alice';
90
+ user.email = 'alice@example.com';
91
+ await user.save();
92
+
93
+ const plain = user.toObject();
94
+
95
+ if (plain.name === 'Alice' &&
96
+ plain.email === 'alice@example.com' &&
97
+ plain.__context === undefined &&
98
+ plain.__entity === undefined) {
99
+ console.log(" ✓ .toObject() converts entity to plain object");
100
+ console.log(" ✓ Internal properties stripped");
101
+ passed++;
102
+ } else {
103
+ console.log(` ✗ .toObject() failed - unexpected output`);
104
+ failed++;
105
+ }
106
+ } catch(err) {
107
+ console.log(` ✗ Error: ${err.message}`);
108
+ failed++;
109
+ }
110
+
111
+ // Test 2: .toJSON() works with JSON.stringify
112
+ console.log("\n📝 Test 2: .toJSON() works with JSON.stringify");
113
+ console.log("──────────────────────────────────────────────────");
114
+ try {
115
+ const user = db.User.new();
116
+ user.name = 'Bob';
117
+ user.email = 'bob@example.com';
118
+ await user.save();
119
+
120
+ const json = JSON.stringify(user);
121
+ const parsed = JSON.parse(json);
122
+
123
+ if (parsed.name === 'Bob' &&
124
+ parsed.email === 'bob@example.com' &&
125
+ parsed.__context === undefined) {
126
+ console.log(" ✓ JSON.stringify() works without errors");
127
+ console.log(" ✓ Circular references prevented");
128
+ passed++;
129
+ } else {
130
+ console.log(` ✗ JSON.stringify() failed`);
131
+ failed++;
132
+ }
133
+ } catch(err) {
134
+ console.log(` ✗ Error: ${err.message}`);
135
+ failed++;
136
+ }
137
+
138
+ // Test 3: .delete() removes entity
139
+ console.log("\n📝 Test 3: .delete() removes entity from database");
140
+ console.log("──────────────────────────────────────────────────");
141
+ try {
142
+ const user = db.User.new();
143
+ user.name = 'ToDelete';
144
+ user.email = 'delete@example.com';
145
+ await user.save();
146
+
147
+ const id = user.id;
148
+ if (!id) {
149
+ console.log(` ✗ .delete() test skipped - user.id is undefined after save`);
150
+ failed++;
151
+ } else {
152
+ await user.delete();
153
+
154
+ const check = await db.User.findById(id);
155
+
156
+ if (check === null) {
157
+ console.log(" ✓ .delete() removes entity from database");
158
+ passed++;
159
+ } else {
160
+ console.log(` ✗ .delete() failed - entity still exists`);
161
+ failed++;
162
+ }
163
+ }
164
+ } catch(err) {
165
+ console.log(` ✗ Error: ${err.message}`);
166
+ failed++;
167
+ }
168
+
169
+ // Test 4: .reload() refreshes entity from database
170
+ console.log("\n📝 Test 4: .reload() refreshes entity from database");
171
+ console.log("──────────────────────────────────────────────────");
172
+ try {
173
+ const user = db.User.new();
174
+ user.name = 'Original';
175
+ user.email = 'original@example.com';
176
+ await user.save();
177
+
178
+ // Modify in-memory
179
+ user.name = 'Modified';
180
+
181
+ // Reload
182
+ await user.reload();
183
+
184
+ if (user.name === 'Original' && user.__dirtyFields.length === 0) {
185
+ console.log(" ✓ .reload() refreshes entity from database");
186
+ console.log(" ✓ Dirty fields reset");
187
+ passed++;
188
+ } else {
189
+ console.log(` ✗ .reload() failed - name: ${user.name}`);
190
+ failed++;
191
+ }
192
+ } catch(err) {
193
+ console.log(` ✗ Error: ${err.message}`);
194
+ failed++;
195
+ }
196
+
197
+ // Test 5: .clone() creates copy without primary key
198
+ console.log("\n📝 Test 5: .clone() creates copy without primary key");
199
+ console.log("──────────────────────────────────────────────────");
200
+ try {
201
+ const user = db.User.new();
202
+ user.name = 'Original';
203
+ user.email = 'original@example.com';
204
+ await user.save();
205
+
206
+ const originalId = user.id;
207
+ const cloned = user.clone();
208
+ cloned.name = 'Cloned';
209
+ cloned.email = 'cloned@example.com';
210
+ await cloned.save();
211
+
212
+ if (cloned.id !== originalId && cloned.name === 'Cloned') {
213
+ console.log(" ✓ .clone() creates copy with new primary key");
214
+ console.log(" ✓ Original entity unchanged");
215
+ passed++;
216
+ } else {
217
+ console.log(` ✗ .clone() failed`);
218
+ failed++;
219
+ }
220
+ } catch(err) {
221
+ console.log(` ✗ Error: ${err.message}`);
222
+ failed++;
223
+ }
224
+
225
+ // Test 6: .toObject() with relationships
226
+ console.log("\n📝 Test 6: .toObject() with relationships");
227
+ console.log("──────────────────────────────────────────────────");
228
+ try {
229
+ const user = db.User.new();
230
+ user.name = 'Charlie';
231
+ user.email = 'charlie@example.com';
232
+ await user.save();
233
+
234
+ const tag = db.Tag.new();
235
+ tag.name = 'Important';
236
+ tag.user_id = user.id;
237
+ await tag.save();
238
+
239
+ const freshUser = await db.User.findById(user.id);
240
+ const tags = await freshUser.Tags;
241
+
242
+ const plain = freshUser.toObject({ includeRelationships: true });
243
+
244
+ if (Array.isArray(plain.Tags) && plain.Tags.length === 1 && plain.Tags[0].name === 'Important') {
245
+ console.log(" ✓ .toObject() includes relationships when requested");
246
+ passed++;
247
+ } else {
248
+ console.log(` ✗ .toObject() failed to include relationships`);
249
+ failed++;
250
+ }
251
+ } catch(err) {
252
+ console.log(` ✗ Error: ${err.message}`);
253
+ failed++;
254
+ }
255
+
256
+ // Summary
257
+ console.log("\n" + "=".repeat(64));
258
+ console.log(`Test Results: ${passed} passed, ${failed} failed`);
259
+ console.log("=".repeat(64));
260
+
261
+ if (failed > 0) {
262
+ process.exit(1);
263
+ }
264
+ }
265
+
266
+ runTests().catch(err => {
267
+ console.error('Fatal error:', err);
268
+ process.exit(1);
269
+ });
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Validation Test: Auto-Increment ID Setting
3
+ *
4
+ * Confirms the critical bug fix: IDs are properly set back on entities after insert
5
+ * Tests both single and batch insert paths
6
+ */
7
+
8
+ const masterrecord = require('../MasterRecord.js');
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+
12
+ console.log("╔════════════════════════════════════════════════════════════════╗");
13
+ console.log("║ Auto-Increment ID Setting Validation Test ║");
14
+ console.log("╚════════════════════════════════════════════════════════════════╝\n");
15
+
16
+ let passed = 0;
17
+ let failed = 0;
18
+
19
+ class User {
20
+ id(db) {
21
+ db.integer().primary().auto();
22
+ }
23
+ name(db) {
24
+ db.string();
25
+ }
26
+ }
27
+
28
+ class TestContext extends masterrecord.context {
29
+ constructor() {
30
+ super();
31
+ }
32
+ onConfig(db) {
33
+ this.dbset(User);
34
+ }
35
+ }
36
+
37
+ const dbPath = path.join(__dirname, '..', 'database', 'idValidation.db');
38
+ if (fs.existsSync(dbPath)) {
39
+ fs.unlinkSync(dbPath);
40
+ }
41
+
42
+ async function runTests() {
43
+ const db = new TestContext();
44
+
45
+ // Initialize SQLite database
46
+ const SQLLiteEngine = require('../SQLLiteEngine');
47
+ const sqlite3 = require('better-sqlite3');
48
+ db.isSQLite = true;
49
+ db.isMySQL = false;
50
+ db.isPostgres = false;
51
+ db._SQLEngine = new SQLLiteEngine();
52
+ db.db = new sqlite3(dbPath);
53
+ db._SQLEngine.setDB(db.db, 'better-sqlite3');
54
+
55
+ db.onConfig();
56
+
57
+ // Create table
58
+ db.db.exec('CREATE TABLE IF NOT EXISTS User (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)');
59
+
60
+ // Test 1: Single insert sets ID
61
+ console.log("📝 Test 1: Single insert sets ID (insertManager path)");
62
+ console.log("──────────────────────────────────────────────────");
63
+ try {
64
+ const user1 = db.User.new();
65
+ user1.name = 'User 1';
66
+ await user1.save();
67
+
68
+ if (user1.id === 1) {
69
+ console.log(" ✓ Single insert: ID = 1");
70
+ passed++;
71
+ } else {
72
+ console.log(` ✗ Single insert: ID = ${user1.id} (expected 1)`);
73
+ failed++;
74
+ }
75
+ } catch(err) {
76
+ console.log(` ✗ Error: ${err.message}`);
77
+ failed++;
78
+ }
79
+
80
+ // Test 2: Batch insert sets IDs (2 entities triggers bulkInsert path)
81
+ console.log("\n📝 Test 2: Batch insert sets IDs (bulkInsert path)");
82
+ console.log("──────────────────────────────────────────────────");
83
+ try {
84
+ const user2 = db.User.new();
85
+ user2.name = 'User 2';
86
+
87
+ const user3 = db.User.new();
88
+ user3.name = 'User 3';
89
+
90
+ await db.saveChanges(); // Batch insert
91
+
92
+ if (user2.id === 2 && user3.id === 3) {
93
+ console.log(" ✓ Batch insert: IDs = 2, 3");
94
+ passed++;
95
+ } else {
96
+ console.log(` ✗ Batch insert: IDs = ${user2.id}, ${user3.id} (expected 2, 3)`);
97
+ failed++;
98
+ }
99
+ } catch(err) {
100
+ console.log(` ✗ Error: ${err.message}`);
101
+ failed++;
102
+ }
103
+
104
+ // Test 3: Multiple batch inserts
105
+ console.log("\n📝 Test 3: Multiple batch inserts maintain sequence");
106
+ console.log("──────────────────────────────────────────────────");
107
+ try {
108
+ const users = [];
109
+ for (let i = 0; i < 5; i++) {
110
+ const user = db.User.new();
111
+ user.name = `Batch User ${i}`;
112
+ users.push(user);
113
+ }
114
+
115
+ await db.saveChanges();
116
+
117
+ const ids = users.map(u => u.id);
118
+ const expectedIds = [4, 5, 6, 7, 8];
119
+ const allCorrect = ids.every((id, idx) => id === expectedIds[idx]);
120
+
121
+ if (allCorrect) {
122
+ console.log(" ✓ Batch insert: IDs = [4, 5, 6, 7, 8]");
123
+ passed++;
124
+ } else {
125
+ console.log(` ✗ Batch insert: IDs = [${ids.join(', ')}] (expected [4, 5, 6, 7, 8])`);
126
+ failed++;
127
+ }
128
+ } catch(err) {
129
+ console.log(` ✗ Error: ${err.message}`);
130
+ failed++;
131
+ }
132
+
133
+ // Test 4: ID available immediately after save()
134
+ console.log("\n📝 Test 4: ID available immediately after save()");
135
+ console.log("──────────────────────────────────────────────────");
136
+ try {
137
+ const user = db.User.new();
138
+ user.name = 'Immediate ID Test';
139
+
140
+ const idBefore = user.id;
141
+ await user.save();
142
+ const idAfter = user.id;
143
+
144
+ if (idBefore === undefined && idAfter === 9) {
145
+ console.log(" ✓ ID undefined before save, set to 9 after save");
146
+ passed++;
147
+ } else {
148
+ console.log(` ✗ ID before: ${idBefore}, after: ${idAfter} (expected undefined, 9)`);
149
+ failed++;
150
+ }
151
+ } catch(err) {
152
+ console.log(` ✗ Error: ${err.message}`);
153
+ failed++;
154
+ }
155
+
156
+ // Test 5: Can use ID immediately for relationships
157
+ console.log("\n📝 Test 5: Can use ID immediately for relationships");
158
+ console.log("──────────────────────────────────────────────────");
159
+ try {
160
+ const parent = db.User.new();
161
+ parent.name = 'Parent';
162
+ await parent.save();
163
+
164
+ // Should be able to use parent.id immediately
165
+ const parentId = parent.id;
166
+
167
+ if (typeof parentId === 'number' && parentId === 10) {
168
+ console.log(" ✓ ID available for immediate use: 10");
169
+ passed++;
170
+ } else {
171
+ console.log(` ✗ ID not available: ${parentId}`);
172
+ failed++;
173
+ }
174
+ } catch(err) {
175
+ console.log(` ✗ Error: ${err.message}`);
176
+ failed++;
177
+ }
178
+
179
+ // Summary
180
+ console.log("\n" + "=".repeat(64));
181
+ console.log(`Test Results: ${passed} passed, ${failed} failed`);
182
+ console.log("=".repeat(64));
183
+
184
+ if (failed === 0) {
185
+ console.log("\n✅ ALL TESTS PASSED - ID setting bug is FIXED!");
186
+ console.log("Auto-increment IDs are properly set for:");
187
+ console.log(" • Single inserts (insertManager path)");
188
+ console.log(" • Batch inserts (bulkInsert path)");
189
+ console.log(" • Multiple sequential operations");
190
+ } else {
191
+ console.log("\n❌ SOME TESTS FAILED - Review output above");
192
+ }
193
+
194
+ if (failed > 0) {
195
+ process.exit(1);
196
+ }
197
+ }
198
+
199
+ runTests().catch(err => {
200
+ console.error('Fatal error:', err);
201
+ process.exit(1);
202
+ });
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Test to check what insert() returns
3
+ */
4
+
5
+ const SQLLiteEngine = require('../SQLLiteEngine');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ const dbPath = path.join(__dirname, '..', 'database', 'insertReturnTest.db');
10
+ if (fs.existsSync(dbPath)) {
11
+ fs.unlinkSync(dbPath);
12
+ }
13
+
14
+ const engine = new SQLLiteEngine({ database: dbPath, prefix: '' });
15
+
16
+ // Create table
17
+ engine._run(`CREATE TABLE test_users (
18
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19
+ name TEXT
20
+ )`);
21
+
22
+ // Test insert
23
+ const queryObject = {
24
+ __entity: {
25
+ __name: 'test_users',
26
+ id: { type: 'integer', primary: true, auto: true },
27
+ name: { type: 'string' }
28
+ },
29
+ name: 'Test User'
30
+ };
31
+
32
+ async function test() {
33
+ const result = await engine.insert(queryObject);
34
+ console.log('Insert result:', result);
35
+ console.log('result.id:', result.id);
36
+ console.log('typeof result.id:', typeof result.id);
37
+ }
38
+
39
+ test().catch(console.error);