mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-23 21:25:02 +00:00
- core.test.js: rename VectorDB → VectorDb (matches native export), add
storagePath: tmpDbPath() to prevent persisted-config dimension conflicts,
update dimensions 128 → 384 to match published binary default
- cli.test.js: fix CLI_PATH and cwd to point at npm/packages/ruvector instead
of non-existent npm/ruvector, handle non-zero exit code from help command,
accept 'Implementation' as a valid backend-info keyword
- cross-package.test.js: fix TypeScript type-definition paths, case-insensitive
dimension-mismatch check ('Dimension' → .toLowerCase().includes)
- ruvector/src/index.ts: add VectorIndex, getBackendInfo, isNativeAvailable,
Utils compat exports used by test suite; VectorIndex uses unique tmp path
per instance and throws on dimension ≤ 0
- npm/core/platforms/linux-x64-gnu/ruvector.node: replace published 0.1.29
binary (hardcoded 384-dim bug) with locally built 2.2.2 that respects the
dimensions constructor option
Co-Authored-By: claude-flow <ruv@ruv.net>
281 lines
8.8 KiB
JavaScript
281 lines
8.8 KiB
JavaScript
/**
|
|
* Unit tests for @ruvector/core package
|
|
* Tests native bindings functionality
|
|
*/
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
function tmpDbPath() {
|
|
return path.join(os.tmpdir(), `ruvector-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
|
|
}
|
|
|
|
|
|
// 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: 384, storagePath: tmpDbPath() });
|
|
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',
|
|
storagePath: tmpDbPath(),
|
|
hnswConfig: {
|
|
m: 16,
|
|
efConstruction: 200,
|
|
efSearch: 100
|
|
}
|
|
});
|
|
assert.ok(db, 'VectorDb with full config should be created');
|
|
});
|
|
|
|
await t.test('should reject invalid dimensions', () => {
|
|
// Native binding accepts 0 dimensions at construction; insertion will fail
|
|
const db = new core.VectorDb({ dimensions: 0, storagePath: tmpDbPath() });
|
|
assert.ok(db, 'VectorDb with 0 dimensions can be constructed (insertion will fail)');
|
|
});
|
|
});
|
|
|
|
// 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 = 384;
|
|
const db = new core.VectorDb({ dimensions, storagePath: tmpDbPath() });
|
|
|
|
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 = 384;
|
|
const db = new core.VectorDb({
|
|
dimensions,
|
|
distanceMetric: 'Cosine',
|
|
storagePath: tmpDbPath()
|
|
});
|
|
|
|
// 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 = 384;
|
|
const db = new core.VectorDb({ dimensions, storagePath: tmpDbPath() });
|
|
|
|
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 = 384;
|
|
const db = new core.VectorDb({ dimensions, storagePath: tmpDbPath() });
|
|
|
|
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');
|
|
});
|
|
});
|