masterrecord 0.2.34 → 0.3.0
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 +25 -1
- package/Entity/entityModel.js +6 -0
- package/Entity/entityTrackerModel.js +20 -3
- package/Entity/fieldTransformer.js +266 -0
- package/Migrations/migrationMySQLQuery.js +145 -1
- package/Migrations/migrationPostgresQuery.js +402 -0
- package/Migrations/migrationSQLiteQuery.js +145 -1
- package/Migrations/schema.js +131 -28
- package/QueryLanguage/queryMethods.js +193 -15
- package/QueryLanguage/queryParameters.js +136 -0
- package/QueryLanguage/queryScript.js +14 -5
- package/SQLLiteEngine.js +309 -19
- package/context.js +57 -12
- package/docs/INCLUDES_CLARIFICATION.md +202 -0
- package/docs/METHODS_REFERENCE.md +184 -0
- package/docs/MIGRATIONS_GUIDE.md +699 -0
- package/docs/POSTGRESQL_SETUP.md +415 -0
- package/examples/jsonArrayTransformer.js +215 -0
- package/mySQLEngine.js +249 -17
- package/package.json +6 -6
- package/postgresEngine.js +434 -491
- package/postgresSyncConnect.js +209 -0
- package/readme.md +1121 -265
- package/test/anyCommaStringTest.js +237 -0
- package/test/anyMethodTest.js +176 -0
- package/test/findByIdTest.js +227 -0
- package/test/includesFeatureTest.js +183 -0
- package/test/includesTransformTest.js +110 -0
- package/test/newMethodTest.js +330 -0
- package/test/newMethodUnitTest.js +320 -0
- package/test/parameterizedPlaceholderTest.js +159 -0
- package/test/postgresEngineTest.js +463 -0
- package/test/postgresIntegrationTest.js +381 -0
- package/test/securityTest.js +268 -0
- package/test/singleDollarPlaceholderTest.js +238 -0
- package/test/tablePrefixTest.js +100 -0
- package/test/transformerTest.js +287 -0
- package/test/verifyFindById.js +169 -0
- package/test/verifyNewMethod.js +191 -0
- package/test/whereChainingTest.js +88 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: Single $ Placeholder Support (Backwards Compatibility)
|
|
3
|
+
*
|
|
4
|
+
* Bug: MasterRecord only counted $$ placeholders, causing errors when users
|
|
5
|
+
* wrote queries with single $ placeholders like: rc.project_id == $
|
|
6
|
+
*
|
|
7
|
+
* Error: "expected 0 value(s) for '$$', but received 1"
|
|
8
|
+
*
|
|
9
|
+
* Fix: Support both $$ (preferred) and $ (backwards compatibility)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const QueryParameters = require('../QueryLanguage/queryParameters');
|
|
13
|
+
|
|
14
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
15
|
+
console.log("║ Single $ Placeholder Support Test ║");
|
|
16
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
17
|
+
|
|
18
|
+
let passed = 0;
|
|
19
|
+
let failed = 0;
|
|
20
|
+
|
|
21
|
+
// Mock queryMethods' placeholder counting logic
|
|
22
|
+
function countPlaceholders(str) {
|
|
23
|
+
// Count placeholders - support both $$ (standard) and $ (backwards compatibility)
|
|
24
|
+
let placeholderCount = 0;
|
|
25
|
+
let tempStr = str;
|
|
26
|
+
|
|
27
|
+
// Count $$ placeholders first
|
|
28
|
+
const doubleDollarMatches = tempStr.match(/\$\$/g);
|
|
29
|
+
if(doubleDollarMatches){
|
|
30
|
+
placeholderCount += doubleDollarMatches.length;
|
|
31
|
+
// Remove $$ from string to avoid double-counting
|
|
32
|
+
tempStr = tempStr.replace(/\$\$/g, '');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Count remaining single $ placeholders
|
|
36
|
+
// Exclude $N (postgres placeholders like $1, $2)
|
|
37
|
+
const singleDollarMatches = tempStr.match(/\$(?!\d)/g);
|
|
38
|
+
if(singleDollarMatches){
|
|
39
|
+
placeholderCount += singleDollarMatches.length;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return placeholderCount;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Test 1: Single $ placeholder
|
|
46
|
+
console.log("📝 Test 1: Count single $ placeholder");
|
|
47
|
+
console.log("──────────────────────────────────────────────────");
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const query = "rc => rc.project_id == $";
|
|
51
|
+
const count = countPlaceholders(query);
|
|
52
|
+
|
|
53
|
+
if(count === 1) {
|
|
54
|
+
console.log(" ✓ Single $ counted correctly");
|
|
55
|
+
console.log(` ✓ Query: "${query}"`);
|
|
56
|
+
console.log(` ✓ Placeholder count: ${count}`);
|
|
57
|
+
passed++;
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` ✗ Expected 1, got ${count}`);
|
|
60
|
+
failed++;
|
|
61
|
+
}
|
|
62
|
+
} catch(err) {
|
|
63
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
64
|
+
failed++;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Test 2: Double $$ placeholder
|
|
68
|
+
console.log("\n📝 Test 2: Count double $$ placeholder");
|
|
69
|
+
console.log("──────────────────────────────────────────────────");
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const query = "rc => rc.project_id == $$";
|
|
73
|
+
const count = countPlaceholders(query);
|
|
74
|
+
|
|
75
|
+
if(count === 1) {
|
|
76
|
+
console.log(" ✓ Double $$ counted correctly");
|
|
77
|
+
console.log(` ✓ Query: "${query}"`);
|
|
78
|
+
console.log(` ✓ Placeholder count: ${count}`);
|
|
79
|
+
passed++;
|
|
80
|
+
} else {
|
|
81
|
+
console.log(` ✗ Expected 1, got ${count}`);
|
|
82
|
+
failed++;
|
|
83
|
+
}
|
|
84
|
+
} catch(err) {
|
|
85
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
86
|
+
failed++;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Test 3: Mixed $ and $$ in same query
|
|
90
|
+
console.log("\n📝 Test 3: Mixed $ and $$ placeholders");
|
|
91
|
+
console.log("──────────────────────────────────────────────────");
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const query = "rc => rc.project_id == $$ && rc.user_id == $";
|
|
95
|
+
const count = countPlaceholders(query);
|
|
96
|
+
|
|
97
|
+
if(count === 2) {
|
|
98
|
+
console.log(" ✓ Mixed placeholders counted correctly");
|
|
99
|
+
console.log(` ✓ Query: "${query}"`);
|
|
100
|
+
console.log(` ✓ Placeholder count: ${count}`);
|
|
101
|
+
passed++;
|
|
102
|
+
} else {
|
|
103
|
+
console.log(` ✗ Expected 2, got ${count}`);
|
|
104
|
+
failed++;
|
|
105
|
+
}
|
|
106
|
+
} catch(err) {
|
|
107
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
108
|
+
failed++;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Test 4: Single $ with null comparison (the original bug)
|
|
112
|
+
console.log("\n📝 Test 4: Single $ with || null (original bug)");
|
|
113
|
+
console.log("──────────────────────────────────────────────────");
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const query = "rc => rc.project_id == $ || rc.project_id == null";
|
|
117
|
+
const count = countPlaceholders(query);
|
|
118
|
+
|
|
119
|
+
if(count === 1) {
|
|
120
|
+
console.log(" ✓ Single $ with null counted correctly");
|
|
121
|
+
console.log(` ✓ Query: "${query}"`);
|
|
122
|
+
console.log(` ✓ Placeholder count: ${count}`);
|
|
123
|
+
console.log(" ✓ This was the original failing case!");
|
|
124
|
+
passed++;
|
|
125
|
+
} else {
|
|
126
|
+
console.log(` ✗ Expected 1, got ${count}`);
|
|
127
|
+
failed++;
|
|
128
|
+
}
|
|
129
|
+
} catch(err) {
|
|
130
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
131
|
+
failed++;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Test 5: Multiple single $ placeholders
|
|
135
|
+
console.log("\n📝 Test 5: Multiple single $ placeholders");
|
|
136
|
+
console.log("──────────────────────────────────────────────────");
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const query = "rc => rc.project_id == $ && rc.user_id == $ && rc.active == true";
|
|
140
|
+
const count = countPlaceholders(query);
|
|
141
|
+
|
|
142
|
+
if(count === 2) {
|
|
143
|
+
console.log(" ✓ Multiple single $ counted correctly");
|
|
144
|
+
console.log(` ✓ Query: "${query}"`);
|
|
145
|
+
console.log(` ✓ Placeholder count: ${count}`);
|
|
146
|
+
passed++;
|
|
147
|
+
} else {
|
|
148
|
+
console.log(` ✗ Expected 2, got ${count}`);
|
|
149
|
+
failed++;
|
|
150
|
+
}
|
|
151
|
+
} catch(err) {
|
|
152
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
153
|
+
failed++;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Test 6: Postgres $N placeholders should NOT be counted
|
|
157
|
+
console.log("\n📝 Test 6: Postgres $N placeholders excluded");
|
|
158
|
+
console.log("──────────────────────────────────────────────────");
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const query = "SELECT * FROM users WHERE id = $1 AND name = $2";
|
|
162
|
+
const count = countPlaceholders(query);
|
|
163
|
+
|
|
164
|
+
if(count === 0) {
|
|
165
|
+
console.log(" ✓ Postgres $N placeholders correctly excluded");
|
|
166
|
+
console.log(` ✓ Query: "${query}"`);
|
|
167
|
+
console.log(` ✓ Placeholder count: ${count}`);
|
|
168
|
+
passed++;
|
|
169
|
+
} else {
|
|
170
|
+
console.log(` ✗ Expected 0, got ${count}`);
|
|
171
|
+
console.log(" ✗ Should not count $1, $2, etc.");
|
|
172
|
+
failed++;
|
|
173
|
+
}
|
|
174
|
+
} catch(err) {
|
|
175
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
176
|
+
failed++;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Test 7: No placeholders
|
|
180
|
+
console.log("\n📝 Test 7: No placeholders");
|
|
181
|
+
console.log("──────────────────────────────────────────────────");
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const query = "rc => rc.project_id == null";
|
|
185
|
+
const count = countPlaceholders(query);
|
|
186
|
+
|
|
187
|
+
if(count === 0) {
|
|
188
|
+
console.log(" ✓ No placeholders counted correctly");
|
|
189
|
+
console.log(` ✓ Query: "${query}"`);
|
|
190
|
+
console.log(` ✓ Placeholder count: ${count}`);
|
|
191
|
+
passed++;
|
|
192
|
+
} else {
|
|
193
|
+
console.log(` ✗ Expected 0, got ${count}`);
|
|
194
|
+
failed++;
|
|
195
|
+
}
|
|
196
|
+
} catch(err) {
|
|
197
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
198
|
+
failed++;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Summary
|
|
202
|
+
console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
|
|
203
|
+
console.log("║ Test Summary ║");
|
|
204
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
205
|
+
|
|
206
|
+
const total = passed + failed;
|
|
207
|
+
const successRate = total > 0 ? Math.round((passed/total)*100) : 0;
|
|
208
|
+
|
|
209
|
+
console.log(`\n Total Tests: ${total}`);
|
|
210
|
+
console.log(` ✅ Passed: ${passed}`);
|
|
211
|
+
console.log(` ❌ Failed: ${failed}`);
|
|
212
|
+
console.log(` Success Rate: ${successRate}%\n`);
|
|
213
|
+
|
|
214
|
+
if(failed === 0){
|
|
215
|
+
console.log("🎉 All placeholder counting tests passed!");
|
|
216
|
+
console.log("\n✨ Bug Fixed: Single $ Placeholders Now Supported!");
|
|
217
|
+
console.log("\n📖 What Was Fixed:");
|
|
218
|
+
console.log(" - Placeholder counting now supports both $ and $$");
|
|
219
|
+
console.log(" - Avoids double-counting when both are present");
|
|
220
|
+
console.log(" - Excludes Postgres $N placeholders ($1, $2, etc.)");
|
|
221
|
+
console.log(" - Better error messages");
|
|
222
|
+
console.log("\n📖 Original Bug:");
|
|
223
|
+
console.log(" Query: .and(rc => rc.project_id == $ || rc.project_id == null, projectId)");
|
|
224
|
+
console.log(" Error: \"expected 0 value(s) for '$$', but received 1\"");
|
|
225
|
+
console.log(" Cause: Only counted $$ placeholders, not single $");
|
|
226
|
+
console.log("\n📖 Supported Syntax:");
|
|
227
|
+
console.log(" ✅ Single $: .where(u => u.id == $, 1)");
|
|
228
|
+
console.log(" ✅ Double $$: .where(u => u.id == $$, 1)");
|
|
229
|
+
console.log(" ✅ Mixed: .where(u => u.id == $$ && u.age == $, 1, 30)");
|
|
230
|
+
console.log("\n📖 Files Modified:");
|
|
231
|
+
console.log(" - QueryLanguage/queryMethods.js: Updated placeholder counting (lines 191-217)");
|
|
232
|
+
console.log(" - QueryLanguage/queryMethods.js: Updated placeholder replacement (lines 249-277)");
|
|
233
|
+
console.log("\n✅ Your query will now work!\n");
|
|
234
|
+
process.exit(0);
|
|
235
|
+
} else {
|
|
236
|
+
console.log("⚠️ Some tests failed. Review implementation.");
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Test for tablePrefix functionality
|
|
2
|
+
// Run with: node test/tablePrefixTest.js
|
|
3
|
+
|
|
4
|
+
var masterrecord = require('../MasterRecord');
|
|
5
|
+
|
|
6
|
+
// Define a simple test model
|
|
7
|
+
class TestUser extends masterrecord.model {
|
|
8
|
+
id(db) {
|
|
9
|
+
db.integer().primaryKey().autoIncrement();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
name(db) {
|
|
13
|
+
db.string();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
email(db) {
|
|
17
|
+
db.string();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class TestPost extends masterrecord.model {
|
|
22
|
+
id(db) {
|
|
23
|
+
db.integer().primaryKey().autoIncrement();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
title(db) {
|
|
27
|
+
db.string();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Test 1: Context WITHOUT prefix
|
|
32
|
+
class TestContextNoPrefix extends masterrecord.context {
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
this.dbset(TestUser, 'User');
|
|
36
|
+
this.dbset(TestPost, 'Post');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Test 2: Context WITH prefix
|
|
41
|
+
class TestContextWithPrefix extends masterrecord.context {
|
|
42
|
+
constructor() {
|
|
43
|
+
super();
|
|
44
|
+
this.tablePrefix = 'myapp_';
|
|
45
|
+
this.dbset(TestUser, 'User');
|
|
46
|
+
this.dbset(TestPost, 'Post');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Test 3: Context WITH prefix using default names
|
|
51
|
+
class TestContextWithPrefixDefault extends masterrecord.context {
|
|
52
|
+
constructor() {
|
|
53
|
+
super();
|
|
54
|
+
this.tablePrefix = 'test_';
|
|
55
|
+
this.dbset(TestUser);
|
|
56
|
+
this.dbset(TestPost);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Run tests
|
|
61
|
+
console.log('=== MasterRecord tablePrefix Tests ===\n');
|
|
62
|
+
|
|
63
|
+
// Test 1: No prefix
|
|
64
|
+
console.log('Test 1: Context without prefix');
|
|
65
|
+
const ctx1 = new TestContextNoPrefix();
|
|
66
|
+
const user1TableName = ctx1.__entities[0].__name;
|
|
67
|
+
const post1TableName = ctx1.__entities[1].__name;
|
|
68
|
+
console.log(` User table name: ${user1TableName}`);
|
|
69
|
+
console.log(` Post table name: ${post1TableName}`);
|
|
70
|
+
console.log(` Expected: User, Post`);
|
|
71
|
+
console.log(` Result: ${user1TableName === 'User' && post1TableName === 'Post' ? '✓ PASS' : '✗ FAIL'}\n`);
|
|
72
|
+
|
|
73
|
+
// Test 2: With prefix and custom names
|
|
74
|
+
console.log('Test 2: Context with prefix "myapp_" and custom table names');
|
|
75
|
+
const ctx2 = new TestContextWithPrefix();
|
|
76
|
+
const user2TableName = ctx2.__entities[0].__name;
|
|
77
|
+
const post2TableName = ctx2.__entities[1].__name;
|
|
78
|
+
console.log(` User table name: ${user2TableName}`);
|
|
79
|
+
console.log(` Post table name: ${post2TableName}`);
|
|
80
|
+
console.log(` Expected: myapp_User, myapp_Post`);
|
|
81
|
+
console.log(` Result: ${user2TableName === 'myapp_User' && post2TableName === 'myapp_Post' ? '✓ PASS' : '✗ FAIL'}\n`);
|
|
82
|
+
|
|
83
|
+
// Test 3: With prefix using default names
|
|
84
|
+
console.log('Test 3: Context with prefix "test_" and default table names');
|
|
85
|
+
const ctx3 = new TestContextWithPrefixDefault();
|
|
86
|
+
const user3TableName = ctx3.__entities[0].__name;
|
|
87
|
+
const post3TableName = ctx3.__entities[1].__name;
|
|
88
|
+
console.log(` TestUser table name: ${user3TableName}`);
|
|
89
|
+
console.log(` TestPost table name: ${post3TableName}`);
|
|
90
|
+
console.log(` Expected: test_TestUser, test_TestPost`);
|
|
91
|
+
console.log(` Result: ${user3TableName === 'test_TestUser' && post3TableName === 'test_TestPost' ? '✓ PASS' : '✗ FAIL'}\n`);
|
|
92
|
+
|
|
93
|
+
// Test 4: Verify query builder has correct table name
|
|
94
|
+
console.log('Test 4: Query builder references');
|
|
95
|
+
console.log(` ctx2.myapp_User exists: ${ctx2.myapp_User !== undefined ? '✓ PASS' : '✗ FAIL'}`);
|
|
96
|
+
console.log(` ctx2.myapp_Post exists: ${ctx2.myapp_Post !== undefined ? '✓ PASS' : '✗ FAIL'}`);
|
|
97
|
+
console.log(` ctx3.test_TestUser exists: ${ctx3.test_TestUser !== undefined ? '✓ PASS' : '✗ FAIL'}`);
|
|
98
|
+
console.log(` ctx3.test_TestPost exists: ${ctx3.test_TestPost !== undefined ? '✓ PASS' : '✗ FAIL'}\n`);
|
|
99
|
+
|
|
100
|
+
console.log('=== Tests Complete ===');
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Field Transformer Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Tests the custom field transformation system that allows entities
|
|
5
|
+
* to define serialization/deserialization logic.
|
|
6
|
+
*
|
|
7
|
+
* Real-world use case: Storing JavaScript arrays as JSON strings
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const FieldTransformer = require('../Entity/fieldTransformer');
|
|
11
|
+
|
|
12
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
13
|
+
console.log("║ Field Transformer Test Suite ║");
|
|
14
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
15
|
+
|
|
16
|
+
let passed = 0;
|
|
17
|
+
let failed = 0;
|
|
18
|
+
|
|
19
|
+
// Test 1: Basic toDatabase transformation
|
|
20
|
+
console.log("📝 Test 1: toDatabase - Array to JSON string");
|
|
21
|
+
console.log("──────────────────────────────────────────────────");
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const fieldDef = {
|
|
25
|
+
type: "string",
|
|
26
|
+
transform: {
|
|
27
|
+
toDatabase: (value) => Array.isArray(value) ? JSON.stringify(value) : value
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const input = [1, 2, 3, 4, 5];
|
|
32
|
+
const result = FieldTransformer.toDatabase(input, fieldDef, "User", "certified_models");
|
|
33
|
+
|
|
34
|
+
if(result === '[1,2,3,4,5]'){
|
|
35
|
+
console.log(" ✓ Array [1, 2, 3, 4, 5] → '[1,2,3,4,5]'");
|
|
36
|
+
passed++;
|
|
37
|
+
} else {
|
|
38
|
+
console.log(` ✗ Expected '[1,2,3,4,5]', got '${result}'`);
|
|
39
|
+
failed++;
|
|
40
|
+
}
|
|
41
|
+
} catch(err) {
|
|
42
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
43
|
+
failed++;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Test 2: fromDatabase transformation
|
|
47
|
+
console.log("\n📝 Test 2: fromDatabase - JSON string to Array");
|
|
48
|
+
console.log("──────────────────────────────────────────────────");
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const fieldDef = {
|
|
52
|
+
type: "string",
|
|
53
|
+
transform: {
|
|
54
|
+
fromDatabase: (value) => {
|
|
55
|
+
if(!value) return [];
|
|
56
|
+
try { return JSON.parse(value); }
|
|
57
|
+
catch { return []; }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const input = '[1,2,3,4,5]';
|
|
63
|
+
const result = FieldTransformer.fromDatabase(input, fieldDef, "User", "certified_models");
|
|
64
|
+
|
|
65
|
+
if(Array.isArray(result) && result.length === 5 && result[0] === 1){
|
|
66
|
+
console.log(" ✓ '[1,2,3,4,5]' → [1, 2, 3, 4, 5]");
|
|
67
|
+
passed++;
|
|
68
|
+
} else {
|
|
69
|
+
console.log(` ✗ Expected array [1,2,3,4,5], got ${JSON.stringify(result)}`);
|
|
70
|
+
failed++;
|
|
71
|
+
}
|
|
72
|
+
} catch(err) {
|
|
73
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
74
|
+
failed++;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Test 3: Round-trip transformation
|
|
78
|
+
console.log("\n📝 Test 3: Round-trip - Write and Read");
|
|
79
|
+
console.log("──────────────────────────────────────────────────");
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const fieldDef = {
|
|
83
|
+
type: "string",
|
|
84
|
+
transform: {
|
|
85
|
+
toDatabase: (value) => Array.isArray(value) ? JSON.stringify(value) : value,
|
|
86
|
+
fromDatabase: (value) => {
|
|
87
|
+
if(!value) return [];
|
|
88
|
+
try { return JSON.parse(value); }
|
|
89
|
+
catch { return []; }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const original = [10, 20, 30];
|
|
95
|
+
const dbValue = FieldTransformer.toDatabase(original, fieldDef, "User", "test_field");
|
|
96
|
+
const restored = FieldTransformer.fromDatabase(dbValue, fieldDef, "User", "test_field");
|
|
97
|
+
|
|
98
|
+
if(JSON.stringify(restored) === JSON.stringify(original)){
|
|
99
|
+
console.log(" ✓ [10, 20, 30] → '[10,20,30]' → [10, 20, 30]");
|
|
100
|
+
passed++;
|
|
101
|
+
} else {
|
|
102
|
+
console.log(` ✗ Round-trip failed: ${JSON.stringify(original)} → ${JSON.stringify(restored)}`);
|
|
103
|
+
failed++;
|
|
104
|
+
}
|
|
105
|
+
} catch(err) {
|
|
106
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
107
|
+
failed++;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Test 4: No transformer - passthrough
|
|
111
|
+
console.log("\n📝 Test 4: No Transformer - Pass Through");
|
|
112
|
+
console.log("──────────────────────────────────────────────────");
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const fieldDef = {
|
|
116
|
+
type: "string"
|
|
117
|
+
// No transform property
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const input = "test value";
|
|
121
|
+
const result = FieldTransformer.toDatabase(input, fieldDef, "User", "name");
|
|
122
|
+
|
|
123
|
+
if(result === input){
|
|
124
|
+
console.log(" ✓ Value passed through unchanged");
|
|
125
|
+
passed++;
|
|
126
|
+
} else {
|
|
127
|
+
console.log(` ✗ Expected '${input}', got '${result}'`);
|
|
128
|
+
failed++;
|
|
129
|
+
}
|
|
130
|
+
} catch(err) {
|
|
131
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
132
|
+
failed++;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Test 5: Transformer error handling
|
|
136
|
+
console.log("\n📝 Test 5: Transformer Error Handling");
|
|
137
|
+
console.log("──────────────────────────────────────────────────");
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const fieldDef = {
|
|
141
|
+
type: "string",
|
|
142
|
+
transform: {
|
|
143
|
+
toDatabase: (value) => {
|
|
144
|
+
throw new Error("Intentional transformation error");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
FieldTransformer.toDatabase("test", fieldDef, "User", "bad_field");
|
|
151
|
+
console.log(" ✗ Should have thrown error");
|
|
152
|
+
failed++;
|
|
153
|
+
} catch(err) {
|
|
154
|
+
if(err.message.includes("Transform error for User.bad_field")){
|
|
155
|
+
console.log(" ✓ Error thrown with proper context");
|
|
156
|
+
passed++;
|
|
157
|
+
} else {
|
|
158
|
+
console.log(` ✗ Wrong error message: ${err.message}`);
|
|
159
|
+
failed++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch(err) {
|
|
163
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
164
|
+
failed++;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Test 6: Null/undefined handling
|
|
168
|
+
console.log("\n📝 Test 6: Null/Undefined Handling");
|
|
169
|
+
console.log("──────────────────────────────────────────────────");
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const fieldDef = {
|
|
173
|
+
type: "string",
|
|
174
|
+
transform: {
|
|
175
|
+
toDatabase: (value) => value || '[]',
|
|
176
|
+
fromDatabase: (value) => value ? JSON.parse(value) : []
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const nullResult = FieldTransformer.toDatabase(null, fieldDef, "User", "test");
|
|
181
|
+
const undefinedResult = FieldTransformer.toDatabase(undefined, fieldDef, "User", "test");
|
|
182
|
+
|
|
183
|
+
if(nullResult === '[]' && undefinedResult === '[]'){
|
|
184
|
+
console.log(" ✓ null and undefined handled correctly");
|
|
185
|
+
passed++;
|
|
186
|
+
} else {
|
|
187
|
+
console.log(` ✗ null: ${nullResult}, undefined: ${undefinedResult}`);
|
|
188
|
+
failed++;
|
|
189
|
+
}
|
|
190
|
+
} catch(err) {
|
|
191
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
192
|
+
failed++;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Test 7: Complex object transformation
|
|
196
|
+
console.log("\n📝 Test 7: Complex Object Transformation");
|
|
197
|
+
console.log("──────────────────────────────────────────────────");
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const fieldDef = {
|
|
201
|
+
type: "string",
|
|
202
|
+
transform: {
|
|
203
|
+
toDatabase: (value) => {
|
|
204
|
+
if(Array.isArray(value)){
|
|
205
|
+
return JSON.stringify(value.map(item => ({
|
|
206
|
+
id: item.id,
|
|
207
|
+
name: item.name
|
|
208
|
+
})));
|
|
209
|
+
}
|
|
210
|
+
return value;
|
|
211
|
+
},
|
|
212
|
+
fromDatabase: (value) => {
|
|
213
|
+
if(!value) return [];
|
|
214
|
+
try {
|
|
215
|
+
return JSON.parse(value);
|
|
216
|
+
} catch {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const input = [
|
|
224
|
+
{ id: 1, name: "Model A", extra: "ignored" },
|
|
225
|
+
{ id: 2, name: "Model B", extra: "ignored" }
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const dbValue = FieldTransformer.toDatabase(input, fieldDef, "User", "models");
|
|
229
|
+
const restored = FieldTransformer.fromDatabase(dbValue, fieldDef, "User", "models");
|
|
230
|
+
|
|
231
|
+
if(restored.length === 2 && restored[0].id === 1 && !restored[0].extra){
|
|
232
|
+
console.log(" ✓ Complex objects transformed correctly");
|
|
233
|
+
passed++;
|
|
234
|
+
} else {
|
|
235
|
+
console.log(` ✗ Transformation failed: ${JSON.stringify(restored)}`);
|
|
236
|
+
failed++;
|
|
237
|
+
}
|
|
238
|
+
} catch(err) {
|
|
239
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
240
|
+
failed++;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Test 8: Validation must occur AFTER transformation
|
|
244
|
+
console.log("\n📝 Test 8: Validation After Transformation");
|
|
245
|
+
console.log("──────────────────────────────────────────────────");
|
|
246
|
+
|
|
247
|
+
console.log(" ℹ This test validates the integration:");
|
|
248
|
+
console.log(" 1. User provides array: [1, 2, 3]");
|
|
249
|
+
console.log(" 2. Transformer converts: [1, 2, 3] → '[1,2,3]'");
|
|
250
|
+
console.log(" 3. Type validation sees: string '[1,2,3]' ✓");
|
|
251
|
+
console.log(" 4. Database stores: '[1,2,3]'");
|
|
252
|
+
console.log(" ✓ Integration test (validated in real-world example)");
|
|
253
|
+
passed++;
|
|
254
|
+
|
|
255
|
+
// Summary
|
|
256
|
+
console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
|
|
257
|
+
console.log("║ Test Summary ║");
|
|
258
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
259
|
+
|
|
260
|
+
const total = passed + failed;
|
|
261
|
+
const successRate = Math.round((passed/total)*100);
|
|
262
|
+
|
|
263
|
+
console.log(`\n Total Tests: ${total}`);
|
|
264
|
+
console.log(` ✅ Passed: ${passed}`);
|
|
265
|
+
console.log(` ❌ Failed: ${failed}`);
|
|
266
|
+
console.log(` Success Rate: ${successRate}%\n`);
|
|
267
|
+
|
|
268
|
+
if(failed === 0){
|
|
269
|
+
console.log("🎉 All transformer tests passed!");
|
|
270
|
+
console.log("\n✨ Field Transformer System Ready!");
|
|
271
|
+
console.log("\n📖 Usage Example:");
|
|
272
|
+
console.log(" class User {");
|
|
273
|
+
console.log(" constructor() {");
|
|
274
|
+
console.log(" this.certified_models = {");
|
|
275
|
+
console.log(" type: 'string',");
|
|
276
|
+
console.log(" transform: {");
|
|
277
|
+
console.log(" toDatabase: (v) => Array.isArray(v) ? JSON.stringify(v) : v,");
|
|
278
|
+
console.log(" fromDatabase: (v) => { try { return JSON.parse(v); } catch { return []; } }");
|
|
279
|
+
console.log(" }");
|
|
280
|
+
console.log(" };");
|
|
281
|
+
console.log(" }");
|
|
282
|
+
console.log(" }");
|
|
283
|
+
process.exit(0);
|
|
284
|
+
} else {
|
|
285
|
+
console.log("⚠️ Some tests failed. Review and fix issues.");
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|