masterrecord 0.3.32 ā 0.3.34
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 +3 -1
- package/.github/FUTURE_IMPROVEMENTS.md +138 -0
- package/Entity/entityModel.js +17 -0
- package/Migrations/migrationMySQLQuery.js +14 -2
- package/Migrations/migrationPostgresQuery.js +15 -2
- package/Migrations/migrationSQLiteQuery.js +13 -1
- package/Migrations/migrationTemplate.js +13 -13
- package/package.json +1 -1
- package/readme.md +38 -1
- package/test/index-bug-fix-test.js +188 -0
- package/test/v0.3.34-bug-fixes-test.js +306 -0
- package/test/debug-id-test.js +0 -63
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Future Improvements for MasterRecord
|
|
2
|
+
|
|
3
|
+
## Metadata Property Handling
|
|
4
|
+
|
|
5
|
+
### Context
|
|
6
|
+
The `.index()` bug fix (commit 6e774a7) introduced a blacklist approach to skip metadata properties during schema processing. While this solves the immediate problem, there are architectural improvements to consider for future versions.
|
|
7
|
+
|
|
8
|
+
### Current Solution (v0.3.33)
|
|
9
|
+
```javascript
|
|
10
|
+
for (var key in table) {
|
|
11
|
+
// Skip metadata properties (indexes, __compositeIndexes, __name, etc.)
|
|
12
|
+
if(key === 'indexes' || key.startsWith('__')){
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
// ... process columns
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Status:** ā
Shipped - Works well, well-tested, solves the problem
|
|
20
|
+
|
|
21
|
+
### Future Considerations
|
|
22
|
+
|
|
23
|
+
#### 1. Document the Metadata Convention
|
|
24
|
+
**Priority:** Medium
|
|
25
|
+
**Effort:** Low
|
|
26
|
+
|
|
27
|
+
Add clear documentation explaining that properties starting with `__` are reserved for internal metadata and will be skipped during schema processing. This should be documented in:
|
|
28
|
+
- README.md (Entity Model section)
|
|
29
|
+
- Code comments in `entityModel.js`
|
|
30
|
+
- Migration guide if breaking changes are made
|
|
31
|
+
|
|
32
|
+
**Example documentation:**
|
|
33
|
+
```markdown
|
|
34
|
+
## Reserved Property Names
|
|
35
|
+
- Properties starting with `__` (double underscore) are reserved for internal metadata
|
|
36
|
+
- `indexes` property is reserved for index definitions
|
|
37
|
+
- These properties are automatically skipped during table creation
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### 2. Whitelist Approach (Long-term)
|
|
41
|
+
**Priority:** Low
|
|
42
|
+
**Effort:** Medium
|
|
43
|
+
**Breaking Change:** Potentially
|
|
44
|
+
|
|
45
|
+
Instead of blacklisting specific properties, consider only processing properties that are actually column definitions.
|
|
46
|
+
|
|
47
|
+
**Pros:**
|
|
48
|
+
- More robust if new metadata properties are added
|
|
49
|
+
- Explicit about what gets processed
|
|
50
|
+
- Self-documenting code
|
|
51
|
+
|
|
52
|
+
**Cons:**
|
|
53
|
+
- Requires careful design to not break existing functionality
|
|
54
|
+
- More complex validation logic
|
|
55
|
+
- Could impact performance
|
|
56
|
+
|
|
57
|
+
**Example approach:**
|
|
58
|
+
```javascript
|
|
59
|
+
for (var key in table) {
|
|
60
|
+
if(typeof table[key] !== "object") continue;
|
|
61
|
+
|
|
62
|
+
var col = table[key];
|
|
63
|
+
|
|
64
|
+
// Only process objects that have column-like properties
|
|
65
|
+
if(!col.name || !col.type) continue;
|
|
66
|
+
|
|
67
|
+
// Skip relationship types
|
|
68
|
+
if(col.type === 'hasOne' || col.type === 'hasMany' || col.type === 'hasManyThrough'){
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
queryVar += `${this.#columnMapping(col)}, `;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### 3. Use JavaScript Symbols for Metadata (v1.0+)
|
|
77
|
+
**Priority:** Low
|
|
78
|
+
**Effort:** High
|
|
79
|
+
**Breaking Change:** Yes
|
|
80
|
+
|
|
81
|
+
For future major versions, consider using JavaScript Symbols for all internal metadata. Symbols don't appear in `for...in` loops or `Object.keys()`, eliminating this entire class of bugs.
|
|
82
|
+
|
|
83
|
+
**Benefits:**
|
|
84
|
+
- Metadata becomes truly invisible to iteration
|
|
85
|
+
- No special-case filtering needed
|
|
86
|
+
- Cleaner separation of concerns
|
|
87
|
+
- More idiomatic JavaScript
|
|
88
|
+
|
|
89
|
+
**Migration path:**
|
|
90
|
+
```javascript
|
|
91
|
+
// Current (v0.x)
|
|
92
|
+
table.__name = 'Users';
|
|
93
|
+
table.indexes = ['idx_name'];
|
|
94
|
+
|
|
95
|
+
// Future (v1.0+)
|
|
96
|
+
const META_NAME = Symbol('name');
|
|
97
|
+
const META_INDEXES = Symbol('indexes');
|
|
98
|
+
|
|
99
|
+
table[META_NAME] = 'Users';
|
|
100
|
+
table[META_INDEXES] = ['idx_name'];
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Challenges:**
|
|
104
|
+
- Requires refactoring all metadata access
|
|
105
|
+
- Breaks existing code that accesses `__name`, `__compositeIndexes`, etc.
|
|
106
|
+
- Would need comprehensive migration guide
|
|
107
|
+
- Testing burden is significant
|
|
108
|
+
|
|
109
|
+
**When to consider:**
|
|
110
|
+
- During a major version bump (v1.0)
|
|
111
|
+
- When other breaking changes are planned
|
|
112
|
+
- After gathering user feedback on current metadata usage patterns
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Implementation Notes
|
|
117
|
+
|
|
118
|
+
### Current Fix is Good to Ship ā
|
|
119
|
+
The blacklist approach (`indexes` and `__*` properties) is:
|
|
120
|
+
- Practical and solves the immediate problem
|
|
121
|
+
- Well-tested (5 comprehensive tests)
|
|
122
|
+
- Easy to understand and maintain
|
|
123
|
+
- Not over-engineered
|
|
124
|
+
|
|
125
|
+
### Recommendation
|
|
126
|
+
1. **Ship the current fix** (already committed)
|
|
127
|
+
2. **Add documentation** about the `__` convention in next minor release
|
|
128
|
+
3. **Track whitelist approach** as a potential v0.4+ enhancement
|
|
129
|
+
4. **Consider Symbols** only for v1.0 major version
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Related Issues
|
|
134
|
+
- Index bug fix: commit 6e774a7
|
|
135
|
+
- Test suite: `test/index-bug-fix-test.js`
|
|
136
|
+
|
|
137
|
+
## Feedback Credit
|
|
138
|
+
These suggestions came from code review feedback on the index bug fix implementation.
|
package/Entity/entityModel.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
|
|
3
|
+
Supported column types:
|
|
3
4
|
:binary
|
|
4
5
|
:boolean
|
|
5
6
|
:date
|
|
@@ -15,6 +16,11 @@
|
|
|
15
16
|
:time
|
|
16
17
|
:timestamp
|
|
17
18
|
|
|
19
|
+
Reserved Property Names:
|
|
20
|
+
- Properties starting with '__' (double underscore) are reserved for internal metadata
|
|
21
|
+
- 'indexes' property is reserved for index definitions
|
|
22
|
+
- These properties are automatically skipped during table creation in migration queries
|
|
23
|
+
|
|
18
24
|
*/
|
|
19
25
|
|
|
20
26
|
// version 0.0.5
|
|
@@ -115,6 +121,17 @@ class EntityModel {
|
|
|
115
121
|
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Adds an index to this column definition.
|
|
126
|
+
*
|
|
127
|
+
* Note: The 'indexes' property is metadata and will be automatically skipped
|
|
128
|
+
* during table creation. Properties starting with '__' are also reserved for
|
|
129
|
+
* internal metadata and filtered during schema processing.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} indexName - Optional custom name for the index. If not provided,
|
|
132
|
+
* a default name will be generated.
|
|
133
|
+
* @returns {EntityModel} Returns this for method chaining
|
|
134
|
+
*/
|
|
118
135
|
index(indexName){
|
|
119
136
|
if(!this.obj.indexes){
|
|
120
137
|
this.obj.indexes = [];
|
|
@@ -193,10 +193,22 @@ class migrationMySQLQuery {
|
|
|
193
193
|
var queryVar = "";
|
|
194
194
|
//console.log("Dsfdsfdsf---------", table)
|
|
195
195
|
for (var key in table) {
|
|
196
|
+
// Skip metadata properties (indexes, __compositeIndexes, __name, etc.)
|
|
197
|
+
if(key === 'indexes' || key.startsWith('__')){
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
196
201
|
if(typeof table[key] === "object"){
|
|
202
|
+
var col = table[key];
|
|
203
|
+
|
|
204
|
+
if(col.type !== "hasOne" && col.type !== "hasMany" && col.type !== "hasManyThrough"){
|
|
205
|
+
// Whitelist: Only process objects that look like column definitions
|
|
206
|
+
// Valid columns must have 'name' and 'type' properties
|
|
207
|
+
if(!col.name || !col.type){
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
197
210
|
|
|
198
|
-
|
|
199
|
-
queryVar += `${this.#columnMapping(table[key])}, `;
|
|
211
|
+
queryVar += `${this.#columnMapping(col)}, `;
|
|
200
212
|
}
|
|
201
213
|
}
|
|
202
214
|
}
|
|
@@ -196,9 +196,22 @@ class migrationPostgresQuery {
|
|
|
196
196
|
var queryVar = "";
|
|
197
197
|
|
|
198
198
|
for (var key in table) {
|
|
199
|
+
// Skip metadata properties (indexes, __compositeIndexes, __name, etc.)
|
|
200
|
+
if(key === 'indexes' || key.startsWith('__')){
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
199
204
|
if(typeof table[key] === "object"){
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
var col = table[key];
|
|
206
|
+
|
|
207
|
+
if(col.type !== "hasOne" && col.type !== "hasMany" && col.type !== "hasManyThrough"){
|
|
208
|
+
// Whitelist: Only process objects that look like column definitions
|
|
209
|
+
// Valid columns must have 'name' and 'type' properties
|
|
210
|
+
if(!col.name || !col.type){
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
queryVar += `${this.#columnMapping(col)}, `;
|
|
202
215
|
}
|
|
203
216
|
}
|
|
204
217
|
}
|
|
@@ -147,16 +147,28 @@ class migrationSQLiteQuery {
|
|
|
147
147
|
createTable(table){
|
|
148
148
|
var queryVar = "";
|
|
149
149
|
for (var key in table) {
|
|
150
|
+
// Skip metadata properties (indexes, __compositeIndexes, __name, etc.)
|
|
151
|
+
if(key === 'indexes' || key.startsWith('__')){
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
150
155
|
if(typeof table[key] === "object"){
|
|
151
156
|
var col = table[key];
|
|
152
157
|
// Skip relationship-only fields
|
|
153
158
|
if(col.type === 'hasOne' || col.type === 'hasMany' || col.type === 'hasManyThrough'){
|
|
154
159
|
continue;
|
|
155
160
|
}
|
|
161
|
+
|
|
162
|
+
// Whitelist: Only process objects that look like column definitions
|
|
163
|
+
// Valid columns must have 'name' and 'type' properties
|
|
164
|
+
if(!col.name || !col.type){
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
156
168
|
queryVar += `${this.#columnMapping(col)}, `;
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
|
-
|
|
171
|
+
|
|
160
172
|
return `CREATE TABLE IF NOT EXISTS ${table.__name} (${queryVar.replace(/,\s*$/, "")});`;
|
|
161
173
|
|
|
162
174
|
/*
|
|
@@ -94,10 +94,10 @@ module.exports = ${this.name};
|
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
if(type === "up"){
|
|
97
|
-
this.#up += os.EOL + ` this.createIndex(${indexInfoStr});`
|
|
97
|
+
this.#up += os.EOL + ` await this.createIndex(${indexInfoStr});`
|
|
98
98
|
}
|
|
99
99
|
else{
|
|
100
|
-
this.#down += os.EOL + ` this.dropIndex(${indexInfoStr});`
|
|
100
|
+
this.#down += os.EOL + ` await this.dropIndex(${indexInfoStr});`
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -113,10 +113,10 @@ module.exports = ${this.name};
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
if(type === "up"){
|
|
116
|
-
this.#up += os.EOL + ` this.dropIndex(${indexInfoStr});`
|
|
116
|
+
this.#up += os.EOL + ` await this.dropIndex(${indexInfoStr});`
|
|
117
117
|
}
|
|
118
118
|
else{
|
|
119
|
-
this.#down += os.EOL + ` this.createIndex(${indexInfoStr});`
|
|
119
|
+
this.#down += os.EOL + ` await this.createIndex(${indexInfoStr});`
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -129,10 +129,10 @@ module.exports = ${this.name};
|
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
if(type === "up"){
|
|
132
|
-
this.#up += os.EOL + ` this.createCompositeIndex(${indexInfoStr});`
|
|
132
|
+
this.#up += os.EOL + ` await this.createCompositeIndex(${indexInfoStr});`
|
|
133
133
|
}
|
|
134
134
|
else{
|
|
135
|
-
this.#down += os.EOL + ` this.dropCompositeIndex(${indexInfoStr});`
|
|
135
|
+
this.#down += os.EOL + ` await this.dropCompositeIndex(${indexInfoStr});`
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -145,10 +145,10 @@ module.exports = ${this.name};
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
if(type === "up"){
|
|
148
|
-
this.#up += os.EOL + ` this.dropCompositeIndex(${indexInfoStr});`
|
|
148
|
+
this.#up += os.EOL + ` await this.dropCompositeIndex(${indexInfoStr});`
|
|
149
149
|
}
|
|
150
150
|
else{
|
|
151
|
-
this.#down += os.EOL + ` this.createCompositeIndex(${indexInfoStr});`
|
|
151
|
+
this.#down += os.EOL + ` await this.createCompositeIndex(${indexInfoStr});`
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -187,7 +187,7 @@ module.exports = ${this.name};
|
|
|
187
187
|
|
|
188
188
|
this.#up += os.EOL + ` ];`;
|
|
189
189
|
this.#up += os.EOL + ` for (const record of factoryRecords) {`;
|
|
190
|
-
this.#up += os.EOL + `
|
|
190
|
+
this.#up += os.EOL + ` this.seed('${tableName}', record);`;
|
|
191
191
|
this.#up += os.EOL + ` }`;
|
|
192
192
|
} else {
|
|
193
193
|
// Standard individual inserts for non-factory or small batches
|
|
@@ -214,10 +214,10 @@ module.exports = ${this.name};
|
|
|
214
214
|
const formattedRecord = JSON.stringify(cleanRecord, null, 12)
|
|
215
215
|
.split('\n')
|
|
216
216
|
.join(os.EOL + ' ');
|
|
217
|
-
this.#up += os.EOL + `
|
|
217
|
+
this.#up += os.EOL + ` this.seed('${tableName}', ${formattedRecord});`;
|
|
218
218
|
} else {
|
|
219
219
|
// Single-line format
|
|
220
|
-
this.#up += os.EOL + `
|
|
220
|
+
this.#up += os.EOL + ` this.seed('${tableName}', ${recordStr});`;
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
});
|
|
@@ -236,7 +236,7 @@ module.exports = ${this.name};
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
this.#up += os.EOL + ` {`;
|
|
239
|
-
this.#up += os.EOL + ` const existing = await
|
|
239
|
+
this.#up += os.EOL + ` const existing = await this.context.${tableName}.where(r => r.${conflictKey} == ${JSON.stringify(conflictValue)}).single();`;
|
|
240
240
|
this.#up += os.EOL + ` if (existing) {`;
|
|
241
241
|
|
|
242
242
|
// Update logic
|
|
@@ -257,7 +257,7 @@ module.exports = ${this.name};
|
|
|
257
257
|
|
|
258
258
|
this.#up += os.EOL + ` await existing.save();`;
|
|
259
259
|
this.#up += os.EOL + ` } else {`;
|
|
260
|
-
this.#up += os.EOL + `
|
|
260
|
+
this.#up += os.EOL + ` this.seed('${tableName}', ${JSON.stringify(cleanRecord)});`;
|
|
261
261
|
this.#up += os.EOL + ` }`;
|
|
262
262
|
this.#up += os.EOL + ` }`;
|
|
263
263
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.34",
|
|
4
4
|
"description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
|
|
5
5
|
"main": "MasterRecord.js",
|
|
6
6
|
"bin": {
|
package/readme.md
CHANGED
|
@@ -3367,11 +3367,48 @@ if (ids.length > 0) {
|
|
|
3367
3367
|
user.name = null; // Error if name is { nullable: false }
|
|
3368
3368
|
```
|
|
3369
3369
|
|
|
3370
|
+
## Changelog
|
|
3371
|
+
|
|
3372
|
+
### Version 0.3.34 (2026-02-05)
|
|
3373
|
+
|
|
3374
|
+
#### Bug Fixes
|
|
3375
|
+
- **Fixed**: Seed API migration generation - resolved `table.EntityName.create is not a function` error
|
|
3376
|
+
- Migrations now use `this.seed('TableName', data)` instead of `table.TableName.create(data)`
|
|
3377
|
+
- Ensures compatibility with the migration schema base class
|
|
3378
|
+
- **Fixed**: Eliminated duplicate table creation statements in generated migrations
|
|
3379
|
+
- **Fixed**: Eliminated duplicate seed insertion statements in generated migrations
|
|
3380
|
+
- **Fixed**: Duplicate index creation when using `.index()` in column definitions
|
|
3381
|
+
- Index operations now properly deduplicated during migration generation
|
|
3382
|
+
- **Fixed**: Missing `await` keywords on `createIndex()`, `dropIndex()`, `createCompositeIndex()`, and `dropCompositeIndex()` calls in migrations
|
|
3383
|
+
- All async index operations now properly awaited for consistency
|
|
3384
|
+
|
|
3385
|
+
#### Improvements
|
|
3386
|
+
- **Enhanced**: Query builders now use whitelist validation for column definitions
|
|
3387
|
+
- Validates that objects have both `name` and `type` properties before processing as columns
|
|
3388
|
+
- More robust metadata property filtering in schema processing
|
|
3389
|
+
- Combines blacklist (skip `indexes`, `__*` prefixed) with whitelist (require `name` and `type`) for comprehensive validation
|
|
3390
|
+
- **Documentation**: Enhanced migration generation documentation
|
|
3391
|
+
- **Testing**: Added comprehensive test suite for v0.3.34 bug fixes
|
|
3392
|
+
- Tests whitelist validation across all three database backends
|
|
3393
|
+
- Tests async/await consistency in index operations
|
|
3394
|
+
- Tests seed API correctness and deduplication
|
|
3395
|
+
|
|
3396
|
+
#### Technical Details
|
|
3397
|
+
- Query builders (SQLite, MySQL, PostgreSQL) validate column definitions have required `name` and `type` properties
|
|
3398
|
+
- Migration template generates proper ORM API calls for seed data using `this.seed()` method
|
|
3399
|
+
- Index deduplication logic prevents duplicate CREATE INDEX statements
|
|
3400
|
+
- All migration operations consistently use `await` for async consistency
|
|
3401
|
+
|
|
3402
|
+
#### Migration Notes
|
|
3403
|
+
- Existing migrations generated with older versions will continue to work
|
|
3404
|
+
- New migrations will use the corrected seed API syntax
|
|
3405
|
+
- If you have migrations with `table.EntityName.create()` that haven't been run, regenerate them with this version
|
|
3406
|
+
|
|
3370
3407
|
## Version Compatibility
|
|
3371
3408
|
|
|
3372
3409
|
| Component | Version | Notes |
|
|
3373
3410
|
|---------------|---------------|------------------------------------------|
|
|
3374
|
-
| MasterRecord | 0.3.
|
|
3411
|
+
| MasterRecord | 0.3.34 | Current version with bug fixes and improvements |
|
|
3375
3412
|
| Node.js | 14+ | Async/await support required |
|
|
3376
3413
|
| PostgreSQL | 9.6+ (12+) | Tested with 12, 13, 14, 15, 16 |
|
|
3377
3414
|
| MySQL | 5.7+ (8.0+) | Tested with 8.0+ |
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: Index Bug Fix - createTable should not generate "undefined undefined NOT NULL"
|
|
3
|
+
*
|
|
4
|
+
* Verifies that .index() method doesn't cause malformed column definitions in CREATE TABLE statements
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
|
|
8
|
+
console.log("ā Index Bug Fix Test - createTable() Method ā");
|
|
9
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n");
|
|
10
|
+
|
|
11
|
+
const MigrationSQLiteQuery = require('../Migrations/migrationSQLiteQuery.js');
|
|
12
|
+
const MigrationMySQLQuery = require('../Migrations/migrationMySQLQuery.js');
|
|
13
|
+
const MigrationPostgresQuery = require('../Migrations/migrationPostgresQuery.js');
|
|
14
|
+
|
|
15
|
+
let passed = 0;
|
|
16
|
+
let failed = 0;
|
|
17
|
+
|
|
18
|
+
function assert(condition, message) {
|
|
19
|
+
if (!condition) {
|
|
20
|
+
console.log(`ā FAIL: ${message}`);
|
|
21
|
+
failed++;
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function test(description, fn) {
|
|
28
|
+
try {
|
|
29
|
+
fn();
|
|
30
|
+
console.log(`ā ${description}`);
|
|
31
|
+
passed++;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.log(`ā ${description}`);
|
|
34
|
+
console.log(` Error: ${error.message}`);
|
|
35
|
+
failed++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Mock table object with indexes property (simulates what EntityModel creates)
|
|
40
|
+
const mockTable = {
|
|
41
|
+
__name: 'TestTable',
|
|
42
|
+
id: {
|
|
43
|
+
name: 'id',
|
|
44
|
+
type: 'integer',
|
|
45
|
+
nullable: false,
|
|
46
|
+
primary: true,
|
|
47
|
+
autoIncrement: true
|
|
48
|
+
},
|
|
49
|
+
organization_id: {
|
|
50
|
+
name: 'organization_id',
|
|
51
|
+
type: 'integer',
|
|
52
|
+
nullable: false,
|
|
53
|
+
indexes: ['idx_org'] // This property on the column caused the original bug
|
|
54
|
+
},
|
|
55
|
+
name: {
|
|
56
|
+
name: 'name',
|
|
57
|
+
type: 'string',
|
|
58
|
+
nullable: false
|
|
59
|
+
},
|
|
60
|
+
// This is the problematic property that was being treated as a column
|
|
61
|
+
indexes: ['idx_org']
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
console.log("Testing SQLite Query Builder...\n");
|
|
65
|
+
|
|
66
|
+
test('SQLite createTable should skip indexes property', () => {
|
|
67
|
+
const sqliteQuery = new MigrationSQLiteQuery();
|
|
68
|
+
const sql = sqliteQuery.createTable(mockTable);
|
|
69
|
+
|
|
70
|
+
assert(!sql.includes('undefined undefined'),
|
|
71
|
+
`SQL should not contain "undefined undefined" but got: ${sql}`);
|
|
72
|
+
assert(sql.includes('id'), 'Should include id column');
|
|
73
|
+
assert(sql.includes('organization_id'), 'Should include organization_id column');
|
|
74
|
+
assert(sql.includes('name'), 'Should include name column');
|
|
75
|
+
assert(sql.startsWith('CREATE TABLE IF NOT EXISTS TestTable'),
|
|
76
|
+
'Should start with CREATE TABLE');
|
|
77
|
+
|
|
78
|
+
console.log(` Generated SQL: ${sql}\n`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log("Testing MySQL Query Builder...\n");
|
|
82
|
+
|
|
83
|
+
test('MySQL createTable should skip indexes property', () => {
|
|
84
|
+
const mysqlQuery = new MigrationMySQLQuery();
|
|
85
|
+
const sql = mysqlQuery.createTable(mockTable);
|
|
86
|
+
|
|
87
|
+
assert(!sql.includes('undefined undefined'),
|
|
88
|
+
`SQL should not contain "undefined undefined" but got: ${sql}`);
|
|
89
|
+
assert(sql.includes('id'), 'Should include id column');
|
|
90
|
+
assert(sql.includes('organization_id'), 'Should include organization_id column');
|
|
91
|
+
assert(sql.includes('name'), 'Should include name column');
|
|
92
|
+
assert(sql.startsWith('CREATE TABLE IF NOT EXISTS `TestTable`'),
|
|
93
|
+
'Should start with CREATE TABLE');
|
|
94
|
+
|
|
95
|
+
console.log(` Generated SQL: ${sql}\n`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
console.log("Testing PostgreSQL Query Builder...\n");
|
|
99
|
+
|
|
100
|
+
test('PostgreSQL createTable should skip indexes property', () => {
|
|
101
|
+
const postgresQuery = new MigrationPostgresQuery();
|
|
102
|
+
const sql = postgresQuery.createTable(mockTable);
|
|
103
|
+
|
|
104
|
+
assert(!sql.includes('undefined undefined'),
|
|
105
|
+
`SQL should not contain "undefined undefined" but got: ${sql}`);
|
|
106
|
+
assert(sql.includes('id'), 'Should include id column');
|
|
107
|
+
assert(sql.includes('organization_id'), 'Should include organization_id column');
|
|
108
|
+
assert(sql.includes('name'), 'Should include name column');
|
|
109
|
+
assert(sql.startsWith('CREATE TABLE IF NOT EXISTS "TestTable"'),
|
|
110
|
+
'Should start with CREATE TABLE');
|
|
111
|
+
|
|
112
|
+
console.log(` Generated SQL: ${sql}\n`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
console.log("Testing metadata properties filtering...\n");
|
|
116
|
+
|
|
117
|
+
test('Should skip __compositeIndexes and other __ properties', () => {
|
|
118
|
+
const tableWithMetadata = {
|
|
119
|
+
...mockTable,
|
|
120
|
+
__compositeIndexes: [['col1', 'col2']],
|
|
121
|
+
__someOtherMetadata: 'value'
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const sqliteQuery = new MigrationSQLiteQuery();
|
|
125
|
+
const sql = sqliteQuery.createTable(tableWithMetadata);
|
|
126
|
+
|
|
127
|
+
assert(!sql.includes('undefined undefined'),
|
|
128
|
+
`SQL should not contain "undefined undefined" but got: ${sql}`);
|
|
129
|
+
|
|
130
|
+
const columnCount = (sql.match(/,/g) || []).length + 1;
|
|
131
|
+
assert(columnCount === 3, `Should have exactly 3 columns (id, organization_id, name), got ${columnCount}`);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
console.log("Testing relationships filtering...\n");
|
|
135
|
+
|
|
136
|
+
test('Should handle table with relationships correctly', () => {
|
|
137
|
+
const tableWithRelations = {
|
|
138
|
+
__name: 'User',
|
|
139
|
+
id: {
|
|
140
|
+
name: 'id',
|
|
141
|
+
type: 'integer',
|
|
142
|
+
nullable: false,
|
|
143
|
+
primary: true
|
|
144
|
+
},
|
|
145
|
+
name: {
|
|
146
|
+
name: 'name',
|
|
147
|
+
type: 'string',
|
|
148
|
+
nullable: false
|
|
149
|
+
},
|
|
150
|
+
posts: {
|
|
151
|
+
name: 'posts',
|
|
152
|
+
type: 'hasMany',
|
|
153
|
+
target: 'Post'
|
|
154
|
+
},
|
|
155
|
+
profile: {
|
|
156
|
+
name: 'profile',
|
|
157
|
+
type: 'hasOne',
|
|
158
|
+
target: 'Profile'
|
|
159
|
+
},
|
|
160
|
+
indexes: ['idx_user_name']
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const sqliteQuery = new MigrationSQLiteQuery();
|
|
164
|
+
const sql = sqliteQuery.createTable(tableWithRelations);
|
|
165
|
+
|
|
166
|
+
assert(!sql.includes('hasMany'), 'Should not include hasMany');
|
|
167
|
+
assert(!sql.includes('hasOne'), 'Should not include hasOne');
|
|
168
|
+
assert(!sql.includes('undefined undefined'),
|
|
169
|
+
`SQL should not contain "undefined undefined" but got: ${sql}`);
|
|
170
|
+
assert(sql.includes('id'), 'Should include id column');
|
|
171
|
+
assert(sql.includes('name'), 'Should include name column');
|
|
172
|
+
assert(!sql.includes('posts'), 'Should not include posts relationship');
|
|
173
|
+
assert(!sql.includes('profile'), 'Should not include profile relationship');
|
|
174
|
+
|
|
175
|
+
console.log(` Generated SQL: ${sql}\n`);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
console.log("\n" + "=".repeat(64));
|
|
179
|
+
console.log(`Test Results: ${passed} passed, ${failed} failed`);
|
|
180
|
+
console.log("=".repeat(64));
|
|
181
|
+
|
|
182
|
+
if (failed > 0) {
|
|
183
|
+
console.log("\nā Some tests failed!");
|
|
184
|
+
process.exit(1);
|
|
185
|
+
} else {
|
|
186
|
+
console.log("\nā
All tests passed!");
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Tests for v0.3.34 Bug Fixes
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* 1. Whitelist validation in query builders
|
|
6
|
+
* 2. No duplicate index creation
|
|
7
|
+
* 3. Seed API migration generation (no duplicates, correct API usage)
|
|
8
|
+
* 4. All async operations have await keywords
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
|
|
12
|
+
console.log("ā MasterRecord v0.3.34 Bug Fixes Test ā");
|
|
13
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n");
|
|
14
|
+
|
|
15
|
+
const Migrations = require('../Migrations/migrations');
|
|
16
|
+
const MigrationSQLiteQuery = require('../Migrations/migrationSQLiteQuery');
|
|
17
|
+
const MigrationMySQLQuery = require('../Migrations/migrationMySQLQuery');
|
|
18
|
+
const MigrationPostgresQuery = require('../Migrations/migrationPostgresQuery');
|
|
19
|
+
|
|
20
|
+
let passed = 0;
|
|
21
|
+
let failed = 0;
|
|
22
|
+
|
|
23
|
+
function assert(condition, message) {
|
|
24
|
+
if (!condition) {
|
|
25
|
+
console.log(`ā FAIL: ${message}`);
|
|
26
|
+
failed++;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function test(description, fn) {
|
|
33
|
+
try {
|
|
34
|
+
fn();
|
|
35
|
+
console.log(`ā ${description}`);
|
|
36
|
+
passed++;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.log(`ā FAIL: ${description}`);
|
|
39
|
+
console.log(` Error: ${e.message}`);
|
|
40
|
+
failed++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Test Suite 1: Whitelist Validation
|
|
46
|
+
// =============================================================================
|
|
47
|
+
console.log("\nš Test Suite 1: Whitelist Validation in Query Builders\n");
|
|
48
|
+
|
|
49
|
+
test('SQLite query builder skips objects without name property', () => {
|
|
50
|
+
const queryBuilder = new MigrationSQLiteQuery();
|
|
51
|
+
const table = {
|
|
52
|
+
__name: 'TestTable',
|
|
53
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
54
|
+
weirdMetadata: { foo: 'bar' }, // No name or type - should be skipped
|
|
55
|
+
name: { name: 'name', type: 'string' }
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const query = queryBuilder.createTable(table);
|
|
59
|
+
|
|
60
|
+
assert(!query.includes('undefined'), 'SQLite: Should not contain undefined');
|
|
61
|
+
assert(query.includes('id'), 'SQLite: Should contain id column');
|
|
62
|
+
assert(query.includes('name'), 'SQLite: Should contain name column');
|
|
63
|
+
assert(!query.includes('foo'), 'SQLite: Should not process metadata without name/type');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('MySQL query builder skips objects without name property', () => {
|
|
67
|
+
const queryBuilder = new MigrationMySQLQuery();
|
|
68
|
+
const table = {
|
|
69
|
+
__name: 'TestTable',
|
|
70
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
71
|
+
weirdMetadata: { foo: 'bar' },
|
|
72
|
+
name: { name: 'name', type: 'string' }
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const query = queryBuilder.createTable(table);
|
|
76
|
+
|
|
77
|
+
assert(!query.includes('undefined'), 'MySQL: Should not contain undefined');
|
|
78
|
+
assert(query.includes('id'), 'MySQL: Should contain id column');
|
|
79
|
+
assert(query.includes('name'), 'MySQL: Should contain name column');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('PostgreSQL query builder skips objects without name property', () => {
|
|
83
|
+
const queryBuilder = new MigrationPostgresQuery();
|
|
84
|
+
const table = {
|
|
85
|
+
__name: 'TestTable',
|
|
86
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
87
|
+
weirdMetadata: { foo: 'bar' },
|
|
88
|
+
name: { name: 'name', type: 'string' }
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const query = queryBuilder.createTable(table);
|
|
92
|
+
|
|
93
|
+
assert(!query.includes('undefined'), 'PostgreSQL: Should not contain undefined');
|
|
94
|
+
assert(query.includes('id'), 'PostgreSQL: Should contain id column');
|
|
95
|
+
assert(query.includes('name'), 'PostgreSQL: Should contain name column');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('Query builders correctly skip indexes property', () => {
|
|
99
|
+
const queryBuilder = new MigrationSQLiteQuery();
|
|
100
|
+
const table = {
|
|
101
|
+
__name: 'TestTable',
|
|
102
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
103
|
+
email: { name: 'email', type: 'string', indexes: ['idx_email'] },
|
|
104
|
+
indexes: ['some', 'array'] // This is metadata, should be skipped
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const query = queryBuilder.createTable(table);
|
|
108
|
+
|
|
109
|
+
assert(!query.includes('undefined'), 'Should not contain undefined');
|
|
110
|
+
assert(query.includes('email'), 'Should contain email column');
|
|
111
|
+
assert(!query.match(/some.*NOT NULL/), 'Should not process indexes array as column');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// Test Suite 2: Index Creation with Await
|
|
116
|
+
// =============================================================================
|
|
117
|
+
console.log("\nš Test Suite 2: Index Creation with Await Keywords\n");
|
|
118
|
+
|
|
119
|
+
test('createIndex generates code with await keyword', () => {
|
|
120
|
+
const migrations = new Migrations();
|
|
121
|
+
const oldSchema = [];
|
|
122
|
+
const newSchema = [{
|
|
123
|
+
__name: 'User',
|
|
124
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
125
|
+
email: { name: 'email', type: 'text', indexes: ['idx_user_email'] }
|
|
126
|
+
}];
|
|
127
|
+
|
|
128
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema);
|
|
129
|
+
|
|
130
|
+
const createIndexMatches = migrationCode.match(/await this\.createIndex/g);
|
|
131
|
+
assert(createIndexMatches && createIndexMatches.length > 0, 'createIndex should have await keyword');
|
|
132
|
+
assert(!migrationCode.match(/\n\s+this\.createIndex\(/), 'Should not have createIndex without await');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('createCompositeIndex generates code with await keyword', () => {
|
|
136
|
+
const migrations = new Migrations();
|
|
137
|
+
const oldSchema = [];
|
|
138
|
+
const newSchema = [{
|
|
139
|
+
__name: 'User',
|
|
140
|
+
id: { name: 'id', type: 'integer' },
|
|
141
|
+
__compositeIndexes: [{
|
|
142
|
+
name: 'idx_composite',
|
|
143
|
+
columns: ['col1', 'col2'],
|
|
144
|
+
unique: false
|
|
145
|
+
}]
|
|
146
|
+
}];
|
|
147
|
+
|
|
148
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema);
|
|
149
|
+
|
|
150
|
+
const compositeMatches = migrationCode.match(/await this\.createCompositeIndex/g);
|
|
151
|
+
assert(compositeMatches && compositeMatches.length > 0, 'createCompositeIndex should have await keyword');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// =============================================================================
|
|
155
|
+
// Test Suite 3: Seed API Migration Generation
|
|
156
|
+
// =============================================================================
|
|
157
|
+
console.log("\nš Test Suite 3: Seed API Migration Generation\n");
|
|
158
|
+
|
|
159
|
+
test('Seed data uses correct API (this.seed instead of table.EntityName.create)', () => {
|
|
160
|
+
const migrations = new Migrations();
|
|
161
|
+
const oldSchema = [];
|
|
162
|
+
const newSchema = [{
|
|
163
|
+
__name: 'Settings',
|
|
164
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
165
|
+
disable_rag: { name: 'disable_rag', type: 'integer' }
|
|
166
|
+
}];
|
|
167
|
+
const seedData = {
|
|
168
|
+
Settings: [{ disable_rag: 0 }]
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
|
|
172
|
+
|
|
173
|
+
assert(migrationCode.includes("this.seed('Settings'"), 'Should use this.seed() method');
|
|
174
|
+
assert(!migrationCode.includes('table.Settings.create'), 'Should NOT use table.Settings.create()');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('No duplicate createTable calls for tables with seed data', () => {
|
|
178
|
+
const migrations = new Migrations();
|
|
179
|
+
const oldSchema = [];
|
|
180
|
+
const newSchema = [
|
|
181
|
+
{
|
|
182
|
+
__name: 'Document',
|
|
183
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
__name: 'Settings',
|
|
187
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
const seedData = {
|
|
191
|
+
Settings: [{ id: 1 }]
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
|
|
195
|
+
|
|
196
|
+
const settingsCreateMatches = migrationCode.match(/createTable\(table\.Settings\)/g);
|
|
197
|
+
const settingsCreateCount = settingsCreateMatches ? settingsCreateMatches.length : 0;
|
|
198
|
+
|
|
199
|
+
assert(settingsCreateCount === 1, `Expected 1 createTable for Settings, got ${settingsCreateCount}`);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('No duplicate seed insertion calls', () => {
|
|
203
|
+
const migrations = new Migrations();
|
|
204
|
+
const oldSchema = [];
|
|
205
|
+
const newSchema = [{
|
|
206
|
+
__name: 'Settings',
|
|
207
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
208
|
+
}];
|
|
209
|
+
const seedData = {
|
|
210
|
+
Settings: [{ id: 1, value: 'test' }]
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
|
|
214
|
+
|
|
215
|
+
const seedMatches = migrationCode.match(/this\.seed\('Settings'/g);
|
|
216
|
+
const seedCount = seedMatches ? seedMatches.length : 0;
|
|
217
|
+
|
|
218
|
+
assert(seedCount === 1, `Expected 1 seed call, got ${seedCount}`);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('No duplicate dropTable calls in down migration', () => {
|
|
222
|
+
const migrations = new Migrations();
|
|
223
|
+
const oldSchema = [];
|
|
224
|
+
const newSchema = [{
|
|
225
|
+
__name: 'Settings',
|
|
226
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
227
|
+
}];
|
|
228
|
+
const seedData = {
|
|
229
|
+
Settings: [{ id: 1 }]
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
|
|
233
|
+
|
|
234
|
+
const dropMatches = migrationCode.match(/dropTable\(table\.Settings\)/g);
|
|
235
|
+
const dropCount = dropMatches ? dropMatches.length : 0;
|
|
236
|
+
|
|
237
|
+
assert(dropCount === 1, `Expected 1 dropTable call, got ${dropCount}`);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('Complex scenario: Multiple tables with seed data, indexes, and composite indexes', () => {
|
|
241
|
+
const migrations = new Migrations();
|
|
242
|
+
const oldSchema = [];
|
|
243
|
+
const newSchema = [
|
|
244
|
+
{
|
|
245
|
+
__name: 'Document',
|
|
246
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
247
|
+
embedding_model_id: { name: 'embedding_model_id', type: 'integer', indexes: ['idx_doc_embed'] }
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
__name: 'DocumentChunk',
|
|
251
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
252
|
+
__compositeIndexes: [{
|
|
253
|
+
name: 'idx_chunk_unique',
|
|
254
|
+
columns: ['doc_id', 'chunk_index'],
|
|
255
|
+
unique: true
|
|
256
|
+
}]
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
__name: 'Settings',
|
|
260
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
261
|
+
disable_rag: { name: 'disable_rag', type: 'integer' }
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
__name: 'Job',
|
|
265
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
266
|
+
}
|
|
267
|
+
];
|
|
268
|
+
const seedData = {
|
|
269
|
+
Settings: [{ disable_rag: 0 }]
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const migrationCode = migrations.template('InitialCreate', oldSchema, newSchema, seedData);
|
|
273
|
+
|
|
274
|
+
// Check each table appears exactly once
|
|
275
|
+
const docCreate = (migrationCode.match(/createTable\(table\.Document\)/g) || []).length;
|
|
276
|
+
const chunkCreate = (migrationCode.match(/createTable\(table\.DocumentChunk\)/g) || []).length;
|
|
277
|
+
const settingsCreate = (migrationCode.match(/createTable\(table\.Settings\)/g) || []).length;
|
|
278
|
+
const jobCreate = (migrationCode.match(/createTable\(table\.Job\)/g) || []).length;
|
|
279
|
+
|
|
280
|
+
assert(docCreate === 1, `Document: Expected 1 createTable, got ${docCreate}`);
|
|
281
|
+
assert(chunkCreate === 1, `DocumentChunk: Expected 1 createTable, got ${chunkCreate}`);
|
|
282
|
+
assert(settingsCreate === 1, `Settings: Expected 1 createTable, got ${settingsCreate}`);
|
|
283
|
+
assert(jobCreate === 1, `Job: Expected 1 createTable, got ${jobCreate}`);
|
|
284
|
+
|
|
285
|
+
// Check seed uses correct API
|
|
286
|
+
assert(migrationCode.includes("this.seed('Settings'"), 'Should use this.seed() for Settings');
|
|
287
|
+
assert(!migrationCode.includes('table.Settings.create'), 'Should NOT use table.Settings.create');
|
|
288
|
+
|
|
289
|
+
// Check indexes have await
|
|
290
|
+
assert(migrationCode.includes('await this.createIndex'), 'Indexes should have await');
|
|
291
|
+
assert(migrationCode.includes('await this.createCompositeIndex'), 'Composite indexes should have await');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// =============================================================================
|
|
295
|
+
// Test Results Summary
|
|
296
|
+
// =============================================================================
|
|
297
|
+
console.log("\n" + "=".repeat(68));
|
|
298
|
+
console.log(`\nš Test Results: ${passed} passed, ${failed} failed\n`);
|
|
299
|
+
|
|
300
|
+
if (failed === 0) {
|
|
301
|
+
console.log("ā
All tests passed! v0.3.34 fixes are working correctly.\n");
|
|
302
|
+
process.exit(0);
|
|
303
|
+
} else {
|
|
304
|
+
console.log(`ā ļø ${failed} test(s) failed. Please review the output above.\n`);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
package/test/debug-id-test.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
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);
|