masterrecord 0.3.8 → 0.3.9

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,303 @@
1
+ /**
2
+ * Test: Attach Detached Entities
3
+ *
4
+ * Verifies that detached entities can be re-attached and tracked
5
+ * Like Entity Framework's context.Update() or Hibernate's session.merge()
6
+ */
7
+
8
+ console.log("╔════════════════════════════════════════════════════════════════╗");
9
+ console.log("║ Detached Entity Attachment Test ║");
10
+ console.log("╚════════════════════════════════════════════════════════════════╝\n");
11
+
12
+ let passed = 0;
13
+ let failed = 0;
14
+
15
+ // Simulate a context with attach functionality
16
+ class SimulatedContext {
17
+ constructor() {
18
+ this.__trackedEntities = [];
19
+ this.__trackedEntitiesMap = new Map();
20
+ }
21
+
22
+ __track(model) {
23
+ if (!model.__ID) {
24
+ model.__ID = Math.floor((Math.random() * 100000) + 1);
25
+ }
26
+
27
+ if (!this.__trackedEntitiesMap.has(model.__ID)) {
28
+ this.__trackedEntities.push(model);
29
+ this.__trackedEntitiesMap.set(model.__ID, model);
30
+ }
31
+
32
+ return model;
33
+ }
34
+
35
+ attach(entity, changes = null) {
36
+ if (!entity) {
37
+ throw new Error('Cannot attach null or undefined entity');
38
+ }
39
+
40
+ if (!entity.__entity || !entity.__entity.__name) {
41
+ throw new Error('Entity must have __entity metadata');
42
+ }
43
+
44
+ // Mark entity as modified
45
+ entity.__state = 'modified';
46
+
47
+ // If specific changes provided, mark only those fields as dirty
48
+ if (changes) {
49
+ entity.__dirtyFields = entity.__dirtyFields || [];
50
+ for (const fieldName in changes) {
51
+ entity[fieldName] = changes[fieldName];
52
+ if (!entity.__dirtyFields.includes(fieldName)) {
53
+ entity.__dirtyFields.push(fieldName);
54
+ }
55
+ }
56
+ } else {
57
+ // Mark all fields as potentially modified
58
+ entity.__dirtyFields = entity.__dirtyFields || [];
59
+
60
+ if (entity.__dirtyFields.length === 0) {
61
+ for (const fieldName in entity.__entity) {
62
+ if (!fieldName.startsWith('__') &&
63
+ entity.__entity[fieldName].type !== 'hasMany' &&
64
+ entity.__entity[fieldName].type !== 'hasOne') {
65
+ entity.__dirtyFields.push(fieldName);
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ entity.__context = this;
72
+ this.__track(entity);
73
+ return entity;
74
+ }
75
+
76
+ attachAll(entities) {
77
+ if (!Array.isArray(entities)) {
78
+ throw new Error('attachAll() requires an array');
79
+ }
80
+ return entities.map(entity => this.attach(entity));
81
+ }
82
+ }
83
+
84
+ // Create mock entity
85
+ function createMockEntity(id, name, status) {
86
+ return {
87
+ __ID: id,
88
+ __entity: {
89
+ __name: 'Task',
90
+ id: { type: 'integer', primary: true },
91
+ name: { type: 'string' },
92
+ status: { type: 'string' }
93
+ },
94
+ __dirtyFields: [],
95
+ __state: 'track',
96
+ id: id,
97
+ name: name,
98
+ status: status
99
+ };
100
+ }
101
+
102
+ // Test 1: Attach detached entity
103
+ console.log("📝 Test 1: Attach detached entity");
104
+ console.log("──────────────────────────────────────────────────");
105
+
106
+ try {
107
+ const ctx = new SimulatedContext();
108
+ const task = createMockEntity(1, 'Task 1', 'pending');
109
+
110
+ // Simulate: entity loaded in different context (detached)
111
+ task.status = 'completed';
112
+
113
+ // Attach to current context
114
+ ctx.attach(task);
115
+
116
+ if (ctx.__trackedEntities.includes(task) &&
117
+ task.__state === 'modified' &&
118
+ task.__dirtyFields.length > 0) {
119
+ console.log(" ✓ Entity attached to context");
120
+ console.log(" ✓ Entity marked as 'modified'");
121
+ console.log(` ✓ Dirty fields marked: ${task.__dirtyFields.join(', ')}`);
122
+ passed++;
123
+ } else {
124
+ console.log(` ✗ Entity not properly attached`);
125
+ failed++;
126
+ }
127
+ } catch(err) {
128
+ console.log(` ✗ Error: ${err.message}`);
129
+ failed++;
130
+ }
131
+
132
+ // Test 2: Attach with specific field changes
133
+ console.log("\n📝 Test 2: Attach with specific field changes");
134
+ console.log("──────────────────────────────────────────────────");
135
+
136
+ try {
137
+ const ctx = new SimulatedContext();
138
+ const task = createMockEntity(2, 'Task 2', 'pending');
139
+
140
+ // Attach with specific changes
141
+ ctx.attach(task, {
142
+ status: 'completed',
143
+ completed_at: new Date()
144
+ });
145
+
146
+ if (task.status === 'completed' &&
147
+ task.__dirtyFields.includes('status') &&
148
+ task.__dirtyFields.includes('completed_at') &&
149
+ task.__state === 'modified') {
150
+ console.log(" ✓ Specific fields applied");
151
+ console.log(" ✓ Only specified fields marked dirty");
152
+ console.log(` ✓ Dirty fields: ${task.__dirtyFields.join(', ')}`);
153
+ passed++;
154
+ } else {
155
+ console.log(` ✗ Specific changes not applied correctly`);
156
+ failed++;
157
+ }
158
+ } catch(err) {
159
+ console.log(` ✗ Error: ${err.message}`);
160
+ failed++;
161
+ }
162
+
163
+ // Test 3: attachAll() multiple entities
164
+ console.log("\n📝 Test 3: Attach multiple entities");
165
+ console.log("──────────────────────────────────────────────────");
166
+
167
+ try {
168
+ const ctx = new SimulatedContext();
169
+ const tasks = [
170
+ createMockEntity(3, 'Task 3', 'pending'),
171
+ createMockEntity(4, 'Task 4', 'pending'),
172
+ createMockEntity(5, 'Task 5', 'pending')
173
+ ];
174
+
175
+ // Modify all
176
+ tasks.forEach(t => t.status = 'completed');
177
+
178
+ // Attach all
179
+ ctx.attachAll(tasks);
180
+
181
+ const allAttached = tasks.every(t =>
182
+ ctx.__trackedEntities.includes(t) &&
183
+ t.__state === 'modified'
184
+ );
185
+
186
+ if (allAttached && ctx.__trackedEntities.length === 3) {
187
+ console.log(" ✓ All entities attached");
188
+ console.log(` ✓ Tracked count: ${ctx.__trackedEntities.length}`);
189
+ console.log(" ✓ All marked as modified");
190
+ passed++;
191
+ } else {
192
+ console.log(` ✗ Not all entities attached correctly`);
193
+ failed++;
194
+ }
195
+ } catch(err) {
196
+ console.log(` ✗ Error: ${err.message}`);
197
+ failed++;
198
+ }
199
+
200
+ // Test 4: Attach throws error for invalid entity
201
+ console.log("\n📝 Test 4: Error handling for invalid entities");
202
+ console.log("──────────────────────────────────────────────────");
203
+
204
+ try {
205
+ const ctx = new SimulatedContext();
206
+
207
+ let error1 = null;
208
+ let error2 = null;
209
+
210
+ // Test null
211
+ try {
212
+ ctx.attach(null);
213
+ } catch(e) {
214
+ error1 = e.message;
215
+ }
216
+
217
+ // Test entity without metadata
218
+ try {
219
+ ctx.attach({ id: 1, name: 'Test' });
220
+ } catch(e) {
221
+ error2 = e.message;
222
+ }
223
+
224
+ if (error1 && error2) {
225
+ console.log(" ✓ Null entity rejected");
226
+ console.log(" ✓ Entity without metadata rejected");
227
+ console.log(` ✓ Error messages provided`);
228
+ passed++;
229
+ } else {
230
+ console.log(` ✗ Invalid entities should throw errors`);
231
+ failed++;
232
+ }
233
+ } catch(err) {
234
+ console.log(` ✗ Error: ${err.message}`);
235
+ failed++;
236
+ }
237
+
238
+ // Test 5: Attach doesn't duplicate entities
239
+ console.log("\n📝 Test 5: No duplicate tracking");
240
+ console.log("──────────────────────────────────────────────────");
241
+
242
+ try {
243
+ const ctx = new SimulatedContext();
244
+ const task = createMockEntity(6, 'Task 6', 'pending');
245
+
246
+ // Attach twice
247
+ ctx.attach(task);
248
+ ctx.attach(task);
249
+
250
+ if (ctx.__trackedEntities.length === 1) {
251
+ console.log(" ✓ Entity not duplicated in tracking");
252
+ console.log(` ✓ Tracked count: ${ctx.__trackedEntities.length}`);
253
+ passed++;
254
+ } else {
255
+ console.log(` ✗ Entity duplicated: ${ctx.__trackedEntities.length} entries`);
256
+ failed++;
257
+ }
258
+ } catch(err) {
259
+ console.log(` ✗ Error: ${err.message}`);
260
+ failed++;
261
+ }
262
+
263
+ // Test 6: Attach preserves entity reference
264
+ console.log("\n📝 Test 6: Entity reference preserved");
265
+ console.log("──────────────────────────────────────────────────");
266
+
267
+ try {
268
+ const ctx = new SimulatedContext();
269
+ const task = createMockEntity(7, 'Task 7', 'pending');
270
+
271
+ const returned = ctx.attach(task);
272
+
273
+ if (returned === task) {
274
+ console.log(" ✓ Same entity reference returned");
275
+ console.log(" ✓ No entity cloning");
276
+ passed++;
277
+ } else {
278
+ console.log(` ✗ Different entity reference returned`);
279
+ failed++;
280
+ }
281
+ } catch(err) {
282
+ console.log(` ✗ Error: ${err.message}`);
283
+ failed++;
284
+ }
285
+
286
+ // Summary
287
+ console.log("\n╔════════════════════════════════════════════════════════════════╗");
288
+ console.log("║ Test Summary ║");
289
+ console.log("╚════════════════════════════════════════════════════════════════╝");
290
+ console.log(`\n ✓ Passed: ${passed}`);
291
+ console.log(` ✗ Failed: ${failed}`);
292
+ console.log(` 📊 Total: ${passed + failed}\n`);
293
+
294
+ if(failed === 0) {
295
+ console.log(" 🎉 All tests passed!\n");
296
+ console.log(" ✅ Detached entity attachment works");
297
+ console.log(" ✅ Like Entity Framework's context.Update()");
298
+ console.log(" ✅ Like Hibernate's session.merge()\n");
299
+ process.exit(0);
300
+ } else {
301
+ console.log(" ❌ Some tests failed\n");
302
+ process.exit(1);
303
+ }