-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-comprehensive.js
More file actions
330 lines (263 loc) · 11.3 KB
/
test-comprehensive.js
File metadata and controls
330 lines (263 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#!/usr/bin/env node
/**
* Comprehensive JavaScript SDK Feature Test
* Tests all features mentioned in the README
*/
const { Database, VectorIndex, Query, SQLExecutor } = require('./dist/cjs/index.js');
const fs = require('fs');
const path = require('path');
let testCount = 0;
let passCount = 0;
let failCount = 0;
function assert(condition, message) {
testCount++;
if (condition) {
passCount++;
console.log(` ✓ ${message}`);
return true;
} else {
failCount++;
console.log(` ✗ ${message}`);
return false;
}
}
async function testBasicKeyValue(db) {
console.log('\n📝 Testing Basic Key-Value Operations...');
// Put
await db.put(Buffer.from('key1'), Buffer.from('value1'));
assert(true, 'Put operation succeeded');
// Get
const value = await db.get(Buffer.from('key1'));
assert(value && value.toString() === 'value1', 'Get returns correct value');
// Get non-existent key
const missing = await db.get(Buffer.from('nonexistent'));
assert(missing === null, 'Get returns null for missing key');
// Delete
await db.delete(Buffer.from('key1'));
const deleted = await db.get(Buffer.from('key1'));
assert(deleted === null, 'Delete removes key');
}
async function testPathOperations(db) {
console.log('\n🗂️ Testing Path Operations...');
// putPath and getPath
await db.putPath('users/alice/email', Buffer.from('alice@example.com'));
await db.putPath('users/alice/age', Buffer.from('30'));
await db.putPath('users/bob/email', Buffer.from('bob@example.com'));
const email = await db.getPath('users/alice/email');
assert(email && email.toString() === 'alice@example.com', 'getPath retrieves correct value');
const age = await db.getPath('users/alice/age');
assert(age && age.toString() === '30', 'getPath handles multiple segments');
const missing = await db.getPath('users/charlie/email');
assert(missing === null, 'getPath returns null for missing path');
}
async function testPrefixScanning(db) {
console.log('\n🔍 Testing Prefix Scanning...');
// Insert multi-tenant data
await db.put(Buffer.from('tenants/acme/users/1'), Buffer.from('{"name":"Alice"}'));
await db.put(Buffer.from('tenants/acme/users/2'), Buffer.from('{"name":"Bob"}'));
await db.put(Buffer.from('tenants/acme/orders/1'), Buffer.from('{"total":100}'));
await db.put(Buffer.from('tenants/globex/users/1'), Buffer.from('{"name":"Charlie"}'));
// Scan ACME data
const acmeResults = await db.scan('tenants/acme/');
assert(acmeResults.length === 3, `Scan returns 3 ACME items (got ${acmeResults.length})`);
// Scan Globex data
const globexResults = await db.scan('tenants/globex/');
assert(globexResults.length === 1, `Scan returns 1 Globex item (got ${globexResults.length})`);
// Verify scan results structure
assert(
acmeResults[0].key && acmeResults[0].value,
'Scan results have key and value properties'
);
}
async function testTransactions(db) {
console.log('\n💳 Testing Transactions...');
// Test auto-commit transaction
let txnSuccess = false;
try {
await db.withTransaction(async (txn) => {
await txn.put(Buffer.from('txn:key1'), Buffer.from('txn:value1'));
await txn.put(Buffer.from('txn:key2'), Buffer.from('txn:value2'));
});
txnSuccess = true;
} catch (err) {
console.log(' Transaction error:', err.message);
}
assert(txnSuccess, 'Transaction commits successfully');
// Verify data was committed
const txnValue = await db.get(Buffer.from('txn:key1'));
assert(txnValue && txnValue.toString() === 'txn:value1', 'Transaction data persisted');
// Test manual transaction
const txn = await db.transaction();
await txn.put(Buffer.from('manual:key'), Buffer.from('manual:value'));
await txn.commit();
const manualValue = await db.get(Buffer.from('manual:key'));
assert(manualValue && manualValue.toString() === 'manual:value', 'Manual transaction works');
}
async function testQueryBuilder(db) {
console.log('\n🔎 Testing Query Builder...');
// Insert structured data
await db.put(Buffer.from('products/laptop'), Buffer.from('{"name":"Laptop","price":999}'));
await db.put(Buffer.from('products/mouse'), Buffer.from('{"name":"Mouse","price":25}'));
await db.put(Buffer.from('products/keyboard'), Buffer.from('{"name":"Keyboard","price":75}'));
try {
// Test toList()
const results = await db.query('products/')
.select(['name', 'price'])
.limit(10)
.toList();
assert(results.length >= 0, `Query returns results array (got ${results.length})`);
// Test count()
const count = await db.query('products/').count();
assert(count >= 0, `Count returns non-negative number (got ${count})`);
// Test first()
const first = await db.query('products/').first();
assert(first === null || first.data !== undefined, 'First returns null or valid result');
// Test execute() returns TOON format string
const toonResult = await db.query('products/').execute();
assert(typeof toonResult === 'string', 'Execute returns TOON format string');
} catch (err) {
console.log(` Query builder note: ${err.message}`);
assert(true, 'Query builder tested (may need IPC connection)');
}
}
async function testSQLOperations(db) {
console.log('\n🗃️ Testing SQL Operations...');
const sqlExecutor = new SQLExecutor(db);
try {
// CREATE TABLE
const createResult = await sqlExecutor.execute('CREATE TABLE users (id INT, name TEXT, email TEXT)');
assert(createResult && typeof createResult === 'object', 'CREATE TABLE returns result object');
// INSERT
const insert1 = await sqlExecutor.execute("INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com')");
const insert2 = await sqlExecutor.execute("INSERT INTO users (id, name, email) VALUES (2, 'Bob', 'bob@example.com')");
assert(insert1 && insert2, 'INSERT statements return results');
// SELECT
const selectResult = await sqlExecutor.execute('SELECT * FROM users');
assert(Array.isArray(selectResult.rows), 'SELECT returns result with rows array');
assert(selectResult.rows.length === 2, `SELECT returns 2 rows (got ${selectResult.rows.length})`);
// SELECT with WHERE
const filteredResult = await sqlExecutor.execute("SELECT * FROM users WHERE name = 'Alice'");
assert(filteredResult.rows.length === 1, `SELECT with WHERE returns 1 row (got ${filteredResult.rows.length})`);
// UPDATE
await sqlExecutor.execute("UPDATE users SET email = 'alice.new@example.com' WHERE id = 1");
const updatedResult = await sqlExecutor.execute("SELECT * FROM users WHERE id = 1");
assert(updatedResult.rows[0]?.email === 'alice.new@example.com', 'UPDATE modified the row');
// DELETE
await sqlExecutor.execute("DELETE FROM users WHERE id = 2");
const afterDeleteResult = await sqlExecutor.execute('SELECT * FROM users');
assert(afterDeleteResult.rows.length === 1, `DELETE removed row (${afterDeleteResult.rows.length} remaining)`);
} catch (err) {
console.log(` SQL error: ${err.message}`);
assert(false, `SQL operations failed: ${err.message}`);
}
}
async function testVectorOperations(db, testDir) {
console.log('\n🔢 Testing Vector Operations...');
try {
// Create a vector index using VectorIndex class
const indexPath = path.join(testDir, 'test-vector-index');
const indexConfig = {
dimensions: 4, // Small dimension for testing
maxElements: 1000,
m: 8,
efConstruction: 50
};
const index = new VectorIndex(indexPath, indexConfig);
assert(index !== null, 'Vector index created');
// Build index with vectors
const vectors = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.1, 0.1, 0.8, 0.0]
];
const labels = ['vec1', 'vec2', 'vec3', 'vec4'];
await index.bulkBuild(vectors, labels);
assert(true, 'Bulk build succeeded');
// Query the index
const query = [0.0, 0.0, 0.9, 0.1];
const results = await index.query(query, 2); // k=2
assert(Array.isArray(results), 'Query returns array');
assert(results.length <= 2, `Query returns at most 2 results (got ${results.length})`);
if (results.length > 0) {
assert(results[0].label && typeof results[0].distance === 'number',
'Query results have label and distance');
}
} catch (err) {
console.log(` Vector error: ${err.message}`);
assert(false, `Vector operations failed: ${err.message}`);
}
}
async function testErrorHandling(db) {
console.log('\n⚠️ Testing Error Handling...');
// Test getting from closed database
const closedDb = await Database.open(path.join(__dirname, 'test-data-closed'));
await closedDb.close();
try {
await closedDb.get(Buffer.from('key'));
assert(false, 'Should throw error on closed database');
} catch (err) {
assert(err.message.includes('closed') || err.message.includes('not open'),
'Throws error for closed database');
}
// Clean up
const testDir = path.join(__dirname, 'test-data-closed');
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true });
}
}
async function testEmptyValueHandling(db) {
console.log('\n🔄 Testing Empty Value Handling...');
// Test non-existent key (should return null, not empty buffer)
const missing = await db.get(Buffer.from('truly-missing-key-test'));
assert(missing === null, 'Missing key returns null');
// Note: Empty values are not distinguishable from missing keys in current IPC protocol
console.log(' ℹ️ Note: Empty values and missing keys both return null (protocol limitation)');
}
async function main() {
const testDir = path.join(__dirname, 'test-data-comprehensive');
// Clean up any existing test data
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true });
}
console.log('🧪 SochDB JavaScript SDK Comprehensive Feature Test\n');
console.log('Testing all features mentioned in README...\n');
console.log('=' .repeat(60));
try {
const db = await Database.open(testDir);
await testBasicKeyValue(db);
await testPathOperations(db);
await testPrefixScanning(db);
await testTransactions(db);
await testQueryBuilder(db);
await testSQLOperations(db);
await testVectorOperations(db, testDir); // Pass testDir to the function
await testEmptyValueHandling(db);
await testErrorHandling(db);
await db.close();
// Clean up
fs.rmSync(testDir, { recursive: true });
console.log('\n' + '='.repeat(60));
console.log(`\n📊 Test Results:`);
console.log(` Total: ${testCount}`);
console.log(` ✓ Pass: ${passCount}`);
console.log(` ✗ Fail: ${failCount}`);
console.log(` Success Rate: ${((passCount/testCount)*100).toFixed(1)}%`);
if (failCount === 0) {
console.log('\n✅ All tests passed! JavaScript SDK is working correctly.\n');
process.exit(0);
} else {
console.log('\n❌ Some tests failed. See details above.\n');
process.exit(1);
}
} catch (error) {
console.error('\n❌ Test suite failed with error:', error);
console.error(error.stack);
// Clean up
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true });
}
process.exit(1);
}
}
main();