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,235 @@
1
+ /**
2
+ * Test: Bulk Operations API
3
+ * Tests: bulkCreate, bulkUpdate, bulkDelete
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("║ Bulk Operations API 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();
23
+ }
24
+ email(db) {
25
+ db.string();
26
+ }
27
+ status(db) {
28
+ db.string();
29
+ }
30
+ }
31
+
32
+ class TestContext extends masterrecord.context {
33
+ constructor() {
34
+ super();
35
+ }
36
+ onConfig(db) {
37
+ this.dbset(User);
38
+ }
39
+ }
40
+
41
+ const dbPath = path.join(__dirname, '..', 'database', 'bulkOperations.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, status TEXT)');
63
+
64
+ // Test 1: bulkCreate creates multiple entities
65
+ console.log("📝 Test 1: bulkCreate creates multiple entities");
66
+ console.log("──────────────────────────────────────────────────");
67
+ try {
68
+ const users = await db.bulkCreate('User', [
69
+ { name: 'Alice', email: 'alice@example.com', status: 'active' },
70
+ { name: 'Bob', email: 'bob@example.com', status: 'active' },
71
+ { name: 'Charlie', email: 'charlie@example.com', status: 'inactive' },
72
+ { name: 'Dave', email: 'dave@example.com', status: 'active' },
73
+ { name: 'Eve', email: 'eve@example.com', status: 'pending' }
74
+ ]);
75
+
76
+ if (users.length === 5 && users.every(u => u.id)) {
77
+ console.log(" ✓ Created 5 entities");
78
+ console.log(` ✓ All entities have IDs: ${users.map(u => u.id).join(', ')}`);
79
+ passed++;
80
+ } else {
81
+ console.log(` ✗ bulkCreate failed: ${users.length} entities created`);
82
+ failed++;
83
+ }
84
+ } catch(err) {
85
+ console.log(` ✗ Error: ${err.message}`);
86
+ failed++;
87
+ }
88
+
89
+ // Test 2: bulkCreate returns entities in order
90
+ console.log("\n📝 Test 2: bulkCreate returns entities in order");
91
+ console.log("──────────────────────────────────────────────────");
92
+ try {
93
+ const users = await db.User.toList();
94
+ const names = users.map(u => u.name);
95
+
96
+ if (names[0] === 'Alice' && names[4] === 'Eve') {
97
+ console.log(" ✓ Entities created in correct order");
98
+ passed++;
99
+ } else {
100
+ console.log(` ✗ Order incorrect: ${names.join(', ')}`);
101
+ failed++;
102
+ }
103
+ } catch(err) {
104
+ console.log(` ✗ Error: ${err.message}`);
105
+ failed++;
106
+ }
107
+
108
+ // Test 3: bulkUpdate updates multiple entities
109
+ console.log("\n📝 Test 3: bulkUpdate updates multiple entities");
110
+ console.log("──────────────────────────────────────────────────");
111
+ try {
112
+ await db.bulkUpdate('User', [
113
+ { id: 1, status: 'inactive' },
114
+ { id: 2, status: 'inactive' },
115
+ { id: 4, status: 'inactive' }
116
+ ]);
117
+
118
+ const user1 = await db.User.findById(1);
119
+ const user2 = await db.User.findById(2);
120
+ const user4 = await db.User.findById(4);
121
+
122
+ if (user1.status === 'inactive' && user2.status === 'inactive' && user4.status === 'inactive') {
123
+ console.log(" ✓ Updated 3 entities");
124
+ console.log(" ✓ Status changed to: inactive");
125
+ passed++;
126
+ } else {
127
+ console.log(` ✗ Update failed: statuses are ${user1.status}, ${user2.status}, ${user4.status}`);
128
+ failed++;
129
+ }
130
+ } catch(err) {
131
+ console.log(` ✗ Error: ${err.message}`);
132
+ failed++;
133
+ }
134
+
135
+ // Test 4: bulkUpdate leaves other fields unchanged
136
+ console.log("\n📝 Test 4: bulkUpdate leaves other fields unchanged");
137
+ console.log("──────────────────────────────────────────────────");
138
+ try {
139
+ const user1 = await db.User.findById(1);
140
+
141
+ if (user1.name === 'Alice' && user1.email === 'alice@example.com') {
142
+ console.log(" ✓ Other fields unchanged");
143
+ passed++;
144
+ } else {
145
+ console.log(` ✗ Fields changed: name=${user1.name}, email=${user1.email}`);
146
+ failed++;
147
+ }
148
+ } catch(err) {
149
+ console.log(` ✗ Error: ${err.message}`);
150
+ failed++;
151
+ }
152
+
153
+ // Test 5: bulkDelete deletes multiple entities
154
+ console.log("\n📝 Test 5: bulkDelete deletes multiple entities");
155
+ console.log("──────────────────────────────────────────────────");
156
+ try {
157
+ await db.bulkDelete('User', [3, 5]);
158
+
159
+ const user3 = await db.User.findById(3);
160
+ const user5 = await db.User.findById(5);
161
+ const remaining = await db.User.toList();
162
+
163
+ if (user3 === null && user5 === null && remaining.length === 3) {
164
+ console.log(" ✓ Deleted 2 entities");
165
+ console.log(` ✓ Remaining entities: ${remaining.length}`);
166
+ passed++;
167
+ } else {
168
+ console.log(` ✗ Delete failed: user3=${user3}, user5=${user5}, remaining=${remaining.length}`);
169
+ failed++;
170
+ }
171
+ } catch(err) {
172
+ console.log(` ✗ Error: ${err.message}`);
173
+ failed++;
174
+ }
175
+
176
+ // Test 6: bulkCreate with empty array throws error
177
+ console.log("\n📝 Test 6: bulkCreate with empty array throws error");
178
+ console.log("──────────────────────────────────────────────────");
179
+ try {
180
+ await db.bulkCreate('User', []);
181
+ console.log(` ✗ Should have thrown error`);
182
+ failed++;
183
+ } catch(err) {
184
+ if (err.message.includes('non-empty array')) {
185
+ console.log(" ✓ Empty array rejected");
186
+ passed++;
187
+ } else {
188
+ console.log(` ✗ Wrong error: ${err.message}`);
189
+ failed++;
190
+ }
191
+ }
192
+
193
+ // Test 7: bulkUpdate with invalid entity throws error
194
+ console.log("\n📝 Test 7: bulkUpdate with invalid entity throws error");
195
+ console.log("──────────────────────────────────────────────────");
196
+ try {
197
+ await db.bulkUpdate('NonExistentEntity', [{ id: 1, name: 'Test' }]);
198
+ console.log(` ✗ Should have thrown error`);
199
+ failed++;
200
+ } catch(err) {
201
+ if (err.message.includes('not found')) {
202
+ console.log(" ✓ Invalid entity name rejected");
203
+ passed++;
204
+ } else {
205
+ console.log(` ✗ Wrong error: ${err.message}`);
206
+ failed++;
207
+ }
208
+ }
209
+
210
+ // Test 8: bulkDelete with non-existent IDs doesn't throw
211
+ console.log("\n📝 Test 8: bulkDelete with non-existent IDs doesn't throw");
212
+ console.log("──────────────────────────────────────────────────");
213
+ try {
214
+ await db.bulkDelete('User', [999, 1000, 1001]);
215
+ console.log(" ✓ Non-existent IDs handled gracefully");
216
+ passed++;
217
+ } catch(err) {
218
+ console.log(` ✗ Error: ${err.message}`);
219
+ failed++;
220
+ }
221
+
222
+ // Summary
223
+ console.log("\n" + "=".repeat(64));
224
+ console.log(`Test Results: ${passed} passed, ${failed} failed`);
225
+ console.log("=".repeat(64));
226
+
227
+ if (failed > 0) {
228
+ process.exit(1);
229
+ }
230
+ }
231
+
232
+ runTests().catch(err => {
233
+ console.error('Fatal error:', err);
234
+ process.exit(1);
235
+ });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Test to verify caching properly handles entity reconstruction
3
+ */
4
+ const assert = require('assert');
5
+ const context = require('../context');
6
+
7
+ describe('Cache Entity Reconstruction Test', function() {
8
+ let db;
9
+
10
+ before(async function() {
11
+ // Create in-memory SQLite database
12
+ db = new context({ filename: ":memory:", type: "SQLLite" });
13
+
14
+ // Define a test entity
15
+ db.TestEntity = {
16
+ __name: "TestEntity",
17
+ id: db.EntityModel("id").integer().primary().auto(),
18
+ name: db.EntityModel("name").string(),
19
+ description: db.EntityModel("description").string()
20
+ };
21
+
22
+ await db.initialize();
23
+ });
24
+
25
+ after(async function() {
26
+ if (db && db.close) {
27
+ await db.close();
28
+ }
29
+ });
30
+
31
+ it('should properly return cached single entity with toObject method', async function() {
32
+ // Insert test data
33
+ const entity = db.TestEntity.create();
34
+ entity.name = "Test Item";
35
+ entity.description = "Test Description";
36
+ await db.saveChanges();
37
+
38
+ // First query (cache miss) - with caching enabled
39
+ const result1 = await db.TestEntity.cache().where(t => t.id === entity.id).single();
40
+
41
+ assert(result1, 'First query should return result');
42
+ assert.strictEqual(result1.name, "Test Item");
43
+ assert(typeof result1.toObject === 'function', 'Result should have toObject method');
44
+
45
+ // Convert to plain object should work
46
+ const plain1 = result1.toObject();
47
+ assert.strictEqual(plain1.name, "Test Item");
48
+
49
+ // Second query (cache hit) - should return entity with methods
50
+ const result2 = await db.TestEntity.cache().where(t => t.id === entity.id).single();
51
+
52
+ assert(result2, 'Cached query should return result');
53
+ assert.strictEqual(result2.name, "Test Item");
54
+ assert(typeof result2.toObject === 'function', 'Cached result should have toObject method');
55
+
56
+ // toObject should work on cached entity
57
+ const plain2 = result2.toObject();
58
+ assert.strictEqual(plain2.name, "Test Item");
59
+ });
60
+
61
+ it('should properly return cached list with toObject methods', async function() {
62
+ // Clear any existing data
63
+ const existing = await db.TestEntity.toList();
64
+ for (const item of existing) {
65
+ await item.delete();
66
+ }
67
+ await db.saveChanges();
68
+
69
+ // Insert test data
70
+ const entity1 = db.TestEntity.create();
71
+ entity1.name = "Item 1";
72
+ const entity2 = db.TestEntity.create();
73
+ entity2.name = "Item 2";
74
+ await db.saveChanges();
75
+
76
+ // First query (cache miss)
77
+ const list1 = await db.TestEntity.cache().toList();
78
+
79
+ assert(Array.isArray(list1), 'First query should return array');
80
+ assert.strictEqual(list1.length, 2);
81
+ assert(typeof list1[0].toObject === 'function', 'First item should have toObject method');
82
+ assert(typeof list1[1].toObject === 'function', 'Second item should have toObject method');
83
+
84
+ const plain1 = list1[0].toObject();
85
+ assert.strictEqual(plain1.name, "Item 1");
86
+
87
+ // Second query (cache hit)
88
+ const list2 = await db.TestEntity.cache().toList();
89
+
90
+ assert(Array.isArray(list2), 'Cached query should return array');
91
+ assert.strictEqual(list2.length, 2, 'Cached array should have correct length');
92
+ assert(typeof list2[0].toObject === 'function', 'Cached first item should have toObject method');
93
+ assert(typeof list2[1].toObject === 'function', 'Cached second item should have toObject method');
94
+
95
+ const plain2 = list2[0].toObject();
96
+ assert.strictEqual(plain2.name, "Item 1");
97
+ });
98
+
99
+ it('should handle null results properly', async function() {
100
+ // Query for non-existent entity
101
+ const result = await db.TestEntity.cache().where(t => t.id === 99999).single();
102
+
103
+ assert.strictEqual(result, null, 'Non-existent entity should return null');
104
+ });
105
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Debug test to trace ID setting
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', 'debugIdTest.db');
22
+ }
23
+ onConfig(db) {
24
+ this.dbset(User);
25
+ }
26
+ }
27
+
28
+ // Clean
29
+ if (fs.existsSync(path.join(__dirname, '..', 'database', 'debugIdTest.db'))) {
30
+ fs.unlinkSync(path.join(__dirname, '..', 'database', 'debugIdTest.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('\n=== Manual ID set test ===');
41
+ console.log('Before manual set - user.id:', user.id);
42
+
43
+ user.id = 123;
44
+ console.log('After user.id = 123 - user.id:', user.id);
45
+ console.log('After manual set - user.__proto__._id:', user.__proto__._id);
46
+
47
+ user.id = 456;
48
+ console.log('After user.id = 456 - user.id:', user.id);
49
+
50
+ console.log('\n=== Now test with save ===');
51
+ const user2 = db.User.new();
52
+ user2.name = 'Test2';
53
+
54
+ console.log('Before save - tracked entities:', db.__trackedEntities.length);
55
+ console.log('Before save - user2.__state:', user2.__state);
56
+
57
+ await user2.save();
58
+
59
+ console.log('After save - user2.id:', user2.id);
60
+ console.log('After save - user2.__proto__._id:', user2.__proto__._id);
61
+ }
62
+
63
+ test().catch(console.error);
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Test for double WHERE clause bug fix
3
+ * Verifies that multiple queries don't accumulate parameters
4
+ */
5
+
6
+ // Note: This test doesn't actually run queries, just tests query building
7
+ // No database needed
8
+ const queryMethods = require('../QueryLanguage/queryMethods');
9
+ const modelBuilder = require('../Entity/entityModelBuilder');
10
+
11
+ // Mock entity
12
+ const mockEntity = {
13
+ __name: 'TestEntity',
14
+ id: { type: 'number' },
15
+ name: { type: 'string' }
16
+ };
17
+
18
+ // Create test context
19
+ class TestContext extends context {
20
+ constructor() {
21
+ super();
22
+ this.dbset(mockEntity);
23
+ }
24
+ }
25
+
26
+ console.log('=== Testing double WHERE clause bug fix ===\n');
27
+
28
+ try {
29
+ const ctx = new TestContext();
30
+
31
+ // Access the entity multiple times and build queries
32
+ console.log('Test 1: Multiple separate queries should not share state');
33
+ const query1 = ctx.TestEntity.where(e => e.id == $$, 1);
34
+ const query2 = ctx.TestEntity.where(e => e.id == $$, 2);
35
+
36
+ // Check that they have different queryObject instances
37
+ const haveDifferentInstances = query1.__queryObject !== query2.__queryObject;
38
+ console.log(` ✓ Query instances are separate: ${haveDifferentInstances}`);
39
+
40
+ // Check parameter accumulation
41
+ const params1 = query1.__queryObject.parameters.get();
42
+ const params2 = query2.__queryObject.parameters.get();
43
+
44
+ console.log(` Query 1 parameters: [${params1}]`);
45
+ console.log(` Query 2 parameters: [${params2}]`);
46
+
47
+ if (params1.length === 1 && params1[0] === 1 && params2.length === 1 && params2[0] === 2) {
48
+ console.log(' ✓ Parameters are correctly isolated\n');
49
+ } else {
50
+ console.error(' ✗ FAIL: Parameters are not isolated!\n');
51
+ process.exit(1);
52
+ }
53
+
54
+ console.log('Test 2: Accessing entity property multiple times returns fresh instances');
55
+ const instance1 = ctx.TestEntity;
56
+ const instance2 = ctx.TestEntity;
57
+ const areDifferent = instance1 !== instance2;
58
+ console.log(` ✓ Each access returns a new instance: ${areDifferent}\n`);
59
+
60
+ if (!areDifferent) {
61
+ console.error(' ✗ FAIL: Instances are being reused!\n');
62
+ process.exit(1);
63
+ }
64
+
65
+ console.log('✅ All tests passed! Double WHERE bug is fixed.\n');
66
+
67
+ } catch (error) {
68
+ console.error('❌ Test failed with error:', error.message);
69
+ console.error(error.stack);
70
+ process.exit(1);
71
+ }