git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
275 lines
8.4 KiB
JavaScript
275 lines
8.4 KiB
JavaScript
/**
|
|
* Unit tests for @ruvector/core package
|
|
* Tests native bindings functionality
|
|
*/
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
|
|
// Test platform detection and loading
|
|
test('@ruvector/core - Platform Detection', async (t) => {
|
|
await t.test('should detect current platform correctly', () => {
|
|
const os = require('node:os');
|
|
const platform = os.platform();
|
|
const arch = os.arch();
|
|
|
|
assert.ok(['linux', 'darwin', 'win32'].includes(platform),
|
|
`Platform ${platform} should be supported`);
|
|
assert.ok(['x64', 'arm64'].includes(arch),
|
|
`Architecture ${arch} should be supported`);
|
|
});
|
|
|
|
await t.test('should load native binding for current platform', () => {
|
|
try {
|
|
const core = require('@ruvector/core');
|
|
assert.ok(core, 'Core module should load');
|
|
assert.ok(core.VectorDB, 'VectorDB class should be exported');
|
|
assert.ok(typeof core.version === 'function', 'version function should be exported');
|
|
assert.ok(typeof core.hello === 'function', 'hello function should be exported');
|
|
} catch (error) {
|
|
if (error.code === 'MODULE_NOT_FOUND') {
|
|
assert.ok(true, 'Native binding not available (expected in some environments)');
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test VectorDB creation and basic operations
|
|
test('@ruvector/core - VectorDB Creation', async (t) => {
|
|
let core;
|
|
|
|
try {
|
|
core = require('@ruvector/core');
|
|
} catch (error) {
|
|
console.log('⚠ Skipping core tests - native binding not available');
|
|
return;
|
|
}
|
|
|
|
await t.test('should create VectorDB with dimensions', () => {
|
|
const db = new core.VectorDB({ dimensions: 128 });
|
|
assert.ok(db, 'VectorDB instance should be created');
|
|
});
|
|
|
|
await t.test('should create VectorDB with full options', () => {
|
|
const db = new core.VectorDB({
|
|
dimensions: 256,
|
|
distanceMetric: 'Cosine',
|
|
hnswConfig: {
|
|
m: 16,
|
|
efConstruction: 200,
|
|
efSearch: 100
|
|
}
|
|
});
|
|
assert.ok(db, 'VectorDB with full config should be created');
|
|
});
|
|
|
|
await t.test('should reject invalid dimensions', () => {
|
|
assert.throws(
|
|
() => new core.VectorDB({ dimensions: 0 }),
|
|
/invalid.*dimension/i,
|
|
'Should throw on zero dimensions'
|
|
);
|
|
});
|
|
});
|
|
|
|
// Test vector operations
|
|
test('@ruvector/core - Vector Operations', async (t) => {
|
|
let core;
|
|
|
|
try {
|
|
core = require('@ruvector/core');
|
|
} catch (error) {
|
|
console.log('⚠ Skipping core tests - native binding not available');
|
|
return;
|
|
}
|
|
|
|
const dimensions = 128;
|
|
const db = new core.VectorDB({ dimensions });
|
|
|
|
await t.test('should insert vector and return ID', async () => {
|
|
const vector = new Float32Array(dimensions).fill(0.5);
|
|
const id = await db.insert({ vector });
|
|
|
|
assert.ok(id, 'Should return an ID');
|
|
assert.strictEqual(typeof id, 'string', 'ID should be a string');
|
|
});
|
|
|
|
await t.test('should insert vector with custom ID', async () => {
|
|
const vector = new Float32Array(dimensions).fill(0.3);
|
|
const customId = 'custom-id-123';
|
|
const id = await db.insert({ id: customId, vector });
|
|
|
|
assert.strictEqual(id, customId, 'Should use custom ID');
|
|
});
|
|
|
|
await t.test('should insert batch of vectors', async () => {
|
|
const vectors = Array.from({ length: 10 }, (_, i) => ({
|
|
id: `batch-${i}`,
|
|
vector: new Float32Array(dimensions).fill(i / 10)
|
|
}));
|
|
|
|
const ids = await db.insertBatch(vectors);
|
|
|
|
assert.strictEqual(ids.length, 10, 'Should return 10 IDs');
|
|
assert.deepStrictEqual(ids, vectors.map(v => v.id), 'IDs should match');
|
|
});
|
|
|
|
await t.test('should get vector count', async () => {
|
|
const count = await db.len();
|
|
assert.ok(count >= 12, `Should have at least 12 vectors, got ${count}`);
|
|
});
|
|
|
|
await t.test('should check if empty', async () => {
|
|
const isEmpty = await db.isEmpty();
|
|
assert.strictEqual(isEmpty, false, 'Should not be empty');
|
|
});
|
|
});
|
|
|
|
// Test search operations
|
|
test('@ruvector/core - Search Operations', async (t) => {
|
|
let core;
|
|
|
|
try {
|
|
core = require('@ruvector/core');
|
|
} catch (error) {
|
|
console.log('⚠ Skipping core tests - native binding not available');
|
|
return;
|
|
}
|
|
|
|
const dimensions = 128;
|
|
const db = new core.VectorDB({
|
|
dimensions,
|
|
distanceMetric: 'Cosine'
|
|
});
|
|
|
|
// Insert test vectors
|
|
const testVectors = Array.from({ length: 100 }, (_, i) => ({
|
|
id: `vec-${i}`,
|
|
vector: new Float32Array(dimensions).map(() => Math.random())
|
|
}));
|
|
await db.insertBatch(testVectors);
|
|
|
|
await t.test('should search and return results', async () => {
|
|
const query = new Float32Array(dimensions).fill(0.5);
|
|
const results = await db.search({ vector: query, k: 10 });
|
|
|
|
assert.ok(Array.isArray(results), 'Results should be an array');
|
|
assert.ok(results.length > 0, 'Should return results');
|
|
assert.ok(results.length <= 10, 'Should return at most k results');
|
|
});
|
|
|
|
await t.test('search results should have correct structure', async () => {
|
|
const query = new Float32Array(dimensions).fill(0.5);
|
|
const results = await db.search({ vector: query, k: 5 });
|
|
|
|
results.forEach(result => {
|
|
assert.ok(result.id, 'Result should have ID');
|
|
assert.strictEqual(typeof result.score, 'number', 'Score should be a number');
|
|
assert.ok(result.score >= 0, 'Score should be non-negative');
|
|
});
|
|
});
|
|
|
|
await t.test('should respect k parameter', async () => {
|
|
const query = new Float32Array(dimensions).fill(0.5);
|
|
const results = await db.search({ vector: query, k: 3 });
|
|
|
|
assert.ok(results.length <= 3, 'Should return at most 3 results');
|
|
});
|
|
|
|
await t.test('results should be sorted by score', async () => {
|
|
const query = new Float32Array(dimensions).fill(0.5);
|
|
const results = await db.search({ vector: query, k: 10 });
|
|
|
|
for (let i = 0; i < results.length - 1; i++) {
|
|
assert.ok(
|
|
results[i].score <= results[i + 1].score,
|
|
'Results should be sorted by increasing distance'
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test delete operations
|
|
test('@ruvector/core - Delete Operations', async (t) => {
|
|
let core;
|
|
|
|
try {
|
|
core = require('@ruvector/core');
|
|
} catch (error) {
|
|
console.log('⚠ Skipping core tests - native binding not available');
|
|
return;
|
|
}
|
|
|
|
const dimensions = 128;
|
|
const db = new core.VectorDB({ dimensions });
|
|
|
|
await t.test('should delete existing vector', async () => {
|
|
const vector = new Float32Array(dimensions).fill(0.5);
|
|
const id = await db.insert({ id: 'to-delete', vector });
|
|
|
|
const deleted = await db.delete(id);
|
|
assert.strictEqual(deleted, true, 'Should return true for deleted vector');
|
|
});
|
|
|
|
await t.test('should return false for non-existent vector', async () => {
|
|
const deleted = await db.delete('non-existent-id');
|
|
assert.strictEqual(deleted, false, 'Should return false for non-existent vector');
|
|
});
|
|
});
|
|
|
|
// Test get operations
|
|
test('@ruvector/core - Get Operations', async (t) => {
|
|
let core;
|
|
|
|
try {
|
|
core = require('@ruvector/core');
|
|
} catch (error) {
|
|
console.log('⚠ Skipping core tests - native binding not available');
|
|
return;
|
|
}
|
|
|
|
const dimensions = 128;
|
|
const db = new core.VectorDB({ dimensions });
|
|
|
|
await t.test('should get existing vector', async () => {
|
|
const vector = new Float32Array(dimensions).fill(0.7);
|
|
const id = await db.insert({ id: 'get-test', vector });
|
|
|
|
const entry = await db.get(id);
|
|
assert.ok(entry, 'Should return entry');
|
|
assert.strictEqual(entry.id, id, 'ID should match');
|
|
assert.ok(entry.vector, 'Should have vector');
|
|
});
|
|
|
|
await t.test('should return null for non-existent vector', async () => {
|
|
const entry = await db.get('non-existent-id');
|
|
assert.strictEqual(entry, null, 'Should return null for non-existent vector');
|
|
});
|
|
});
|
|
|
|
// Test version and utility functions
|
|
test('@ruvector/core - Utility Functions', async (t) => {
|
|
let core;
|
|
|
|
try {
|
|
core = require('@ruvector/core');
|
|
} catch (error) {
|
|
console.log('⚠ Skipping core tests - native binding not available');
|
|
return;
|
|
}
|
|
|
|
await t.test('version should return string', () => {
|
|
const version = core.version();
|
|
assert.strictEqual(typeof version, 'string', 'Version should be a string');
|
|
assert.ok(version.length > 0, 'Version should not be empty');
|
|
});
|
|
|
|
await t.test('hello should return string', () => {
|
|
const greeting = core.hello();
|
|
assert.strictEqual(typeof greeting, 'string', 'Hello should return a string');
|
|
assert.ok(greeting.length > 0, 'Greeting should not be empty');
|
|
});
|
|
});
|