Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
285
vendor/ruvector/npm/tests/integration/cross-package.test.js
vendored
Normal file
285
vendor/ruvector/npm/tests/integration/cross-package.test.js
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* Integration tests for cross-package compatibility
|
||||
* Tests that all packages work together correctly
|
||||
*/
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
|
||||
// Test that main package correctly loads backends
|
||||
test('Integration - Backend Loading', async (t) => {
|
||||
const ruvector = require('ruvector');
|
||||
|
||||
await t.test('should load a working backend', () => {
|
||||
const info = ruvector.getBackendInfo();
|
||||
assert.ok(info, 'Should get backend info');
|
||||
assert.ok(['native', 'wasm'].includes(info.type), 'Should have valid backend type');
|
||||
});
|
||||
|
||||
await t.test('should create VectorIndex with loaded backend', () => {
|
||||
const index = new ruvector.VectorIndex({ dimension: 128 });
|
||||
assert.ok(index, 'Should create index with backend');
|
||||
});
|
||||
|
||||
await t.test('backend type should match availability', () => {
|
||||
const info = ruvector.getBackendInfo();
|
||||
const hasNative = ruvector.isNativeAvailable();
|
||||
|
||||
if (hasNative) {
|
||||
assert.strictEqual(info.type, 'native', 'Should use native when available');
|
||||
} else {
|
||||
assert.strictEqual(info.type, 'wasm', 'Should use WASM as fallback');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Test API compatibility between backends
|
||||
test('Integration - API Compatibility', async (t) => {
|
||||
const ruvector = require('ruvector');
|
||||
const dimension = 128;
|
||||
|
||||
await t.test('insert and search should work consistently', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension, metric: 'cosine' });
|
||||
|
||||
// Insert test data
|
||||
const vectors = Array.from({ length: 20 }, (_, i) => ({
|
||||
id: `api-test-${i}`,
|
||||
values: Array.from({ length: dimension }, () => Math.random())
|
||||
}));
|
||||
|
||||
await index.insertBatch(vectors);
|
||||
|
||||
// Search
|
||||
const query = Array.from({ length: dimension }, () => Math.random());
|
||||
const results = await index.search(query, { k: 5 });
|
||||
|
||||
assert.ok(Array.isArray(results), 'Search should return array');
|
||||
assert.ok(results.length > 0, 'Should find results');
|
||||
assert.ok(results.length <= 5, 'Should respect k parameter');
|
||||
|
||||
// Verify result structure
|
||||
results.forEach(result => {
|
||||
assert.ok(result.id, 'Result should have ID');
|
||||
assert.strictEqual(typeof result.score, 'number', 'Score should be number');
|
||||
});
|
||||
});
|
||||
|
||||
await t.test('delete and get should work consistently', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension });
|
||||
|
||||
const testId = 'delete-get-test';
|
||||
const vector = {
|
||||
id: testId,
|
||||
values: Array.from({ length: dimension }, () => Math.random())
|
||||
};
|
||||
|
||||
await index.insert(vector);
|
||||
|
||||
// Get
|
||||
const retrieved = await index.get(testId);
|
||||
assert.ok(retrieved, 'Should get inserted vector');
|
||||
assert.strictEqual(retrieved.id, testId, 'ID should match');
|
||||
|
||||
// Delete
|
||||
const deleted = await index.delete(testId);
|
||||
assert.strictEqual(deleted, true, 'Should delete successfully');
|
||||
|
||||
// Verify deletion
|
||||
const afterDelete = await index.get(testId);
|
||||
assert.strictEqual(afterDelete, null, 'Vector should be deleted');
|
||||
});
|
||||
|
||||
await t.test('stats should work consistently', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension });
|
||||
|
||||
await index.insert({
|
||||
id: 'stats-test',
|
||||
values: Array.from({ length: dimension }, () => Math.random())
|
||||
});
|
||||
|
||||
const stats = await index.stats();
|
||||
|
||||
assert.ok(stats, 'Should return stats');
|
||||
assert.ok(typeof stats.vectorCount === 'number', 'vectorCount should be number');
|
||||
assert.strictEqual(stats.dimension, dimension, 'Dimension should match');
|
||||
});
|
||||
});
|
||||
|
||||
// Test data consistency across operations
|
||||
test('Integration - Data Consistency', async (t) => {
|
||||
const ruvector = require('ruvector');
|
||||
const dimension = 256;
|
||||
|
||||
await t.test('inserted vectors should be searchable', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension, metric: 'cosine' });
|
||||
|
||||
const testVector = {
|
||||
id: 'consistency-test',
|
||||
values: Array.from({ length: dimension }, () => Math.random())
|
||||
};
|
||||
|
||||
await index.insert(testVector);
|
||||
|
||||
// Search with the exact same vector
|
||||
const results = await index.search(testVector.values, { k: 1 });
|
||||
|
||||
assert.strictEqual(results.length, 1, 'Should find the vector');
|
||||
assert.strictEqual(results[0].id, testVector.id, 'Should find the correct vector');
|
||||
assert.ok(results[0].score < 0.01, 'Score should be very close to 0 (exact match)');
|
||||
});
|
||||
|
||||
await t.test('batch insert should maintain order and IDs', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension });
|
||||
|
||||
const vectors = Array.from({ length: 10 }, (_, i) => ({
|
||||
id: `order-${i}`,
|
||||
values: Array.from({ length: dimension }, () => Math.random())
|
||||
}));
|
||||
|
||||
await index.insertBatch(vectors);
|
||||
|
||||
// Verify all vectors were inserted
|
||||
for (const vector of vectors) {
|
||||
const retrieved = await index.get(vector.id);
|
||||
assert.ok(retrieved, `Vector ${vector.id} should be retrievable`);
|
||||
assert.strictEqual(retrieved.id, vector.id, 'ID should match');
|
||||
}
|
||||
});
|
||||
|
||||
await t.test('search results should be deterministic', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension, metric: 'cosine' });
|
||||
|
||||
// Insert fixed vectors
|
||||
const vectors = Array.from({ length: 20 }, (_, i) => ({
|
||||
id: `det-${i}`,
|
||||
values: Array.from({ length: dimension }, (_, j) => (i + j) / 100)
|
||||
}));
|
||||
|
||||
await index.insertBatch(vectors);
|
||||
|
||||
// Search with fixed query
|
||||
const query = Array.from({ length: dimension }, (_, i) => i / 100);
|
||||
const results1 = await index.search(query, { k: 5 });
|
||||
const results2 = await index.search(query, { k: 5 });
|
||||
|
||||
assert.strictEqual(results1.length, results2.length, 'Should return same number of results');
|
||||
|
||||
for (let i = 0; i < results1.length; i++) {
|
||||
assert.strictEqual(results1[i].id, results2[i].id, 'IDs should match');
|
||||
assert.strictEqual(results1[i].score, results2[i].score, 'Scores should match');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Test performance across backends
|
||||
test('Integration - Performance Comparison', async (t) => {
|
||||
const ruvector = require('ruvector');
|
||||
const dimension = 128;
|
||||
const numVectors = 100;
|
||||
|
||||
await t.test('insert performance should be reasonable', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension });
|
||||
|
||||
const vectors = Array.from({ length: numVectors }, (_, i) => ({
|
||||
id: `perf-${i}`,
|
||||
values: Array.from({ length: dimension }, () => Math.random())
|
||||
}));
|
||||
|
||||
const start = Date.now();
|
||||
await index.insertBatch(vectors);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
const throughput = numVectors / (duration / 1000);
|
||||
|
||||
console.log(` Insert throughput: ${throughput.toFixed(0)} vectors/sec`);
|
||||
assert.ok(throughput > 10, 'Should insert at least 10 vectors/sec');
|
||||
});
|
||||
|
||||
await t.test('search performance should be reasonable', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension });
|
||||
|
||||
// Insert test data
|
||||
const vectors = Array.from({ length: numVectors }, (_, i) => ({
|
||||
id: `search-perf-${i}`,
|
||||
values: Array.from({ length: dimension }, () => Math.random())
|
||||
}));
|
||||
await index.insertBatch(vectors);
|
||||
|
||||
// Run searches
|
||||
const numQueries = 50;
|
||||
const queries = Array.from(
|
||||
{ length: numQueries },
|
||||
() => Array.from({ length: dimension }, () => Math.random())
|
||||
);
|
||||
|
||||
const start = Date.now();
|
||||
for (const query of queries) {
|
||||
await index.search(query, { k: 10 });
|
||||
}
|
||||
const duration = Date.now() - start;
|
||||
|
||||
const throughput = numQueries / (duration / 1000);
|
||||
|
||||
console.log(` Search throughput: ${throughput.toFixed(0)} queries/sec`);
|
||||
assert.ok(throughput > 5, 'Should search at least 5 queries/sec');
|
||||
});
|
||||
});
|
||||
|
||||
// Test error handling consistency
|
||||
test('Integration - Error Handling', async (t) => {
|
||||
const ruvector = require('ruvector');
|
||||
|
||||
await t.test('should handle invalid dimensions', () => {
|
||||
assert.throws(
|
||||
() => new ruvector.VectorIndex({ dimension: -1 }),
|
||||
'Should reject negative dimensions'
|
||||
);
|
||||
});
|
||||
|
||||
await t.test('should handle dimension mismatch', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension: 128 });
|
||||
|
||||
const wrongVector = {
|
||||
id: 'wrong-dim',
|
||||
values: Array.from({ length: 64 }, () => Math.random())
|
||||
};
|
||||
|
||||
try {
|
||||
await index.insert(wrongVector);
|
||||
// Some backends might auto-handle this, others might throw
|
||||
assert.ok(true);
|
||||
} catch (error) {
|
||||
assert.ok(error.message.includes('dimension'), 'Error should mention dimension');
|
||||
}
|
||||
});
|
||||
|
||||
await t.test('should handle empty search', async () => {
|
||||
const index = new ruvector.VectorIndex({ dimension: 128 });
|
||||
|
||||
const query = Array.from({ length: 128 }, () => Math.random());
|
||||
const results = await index.search(query, { k: 10 });
|
||||
|
||||
assert.ok(Array.isArray(results), 'Should return empty array');
|
||||
assert.strictEqual(results.length, 0, 'Should have no results');
|
||||
});
|
||||
});
|
||||
|
||||
// Test TypeScript types compatibility
|
||||
test('Integration - TypeScript Types', async (t) => {
|
||||
await t.test('should have type definitions available', () => {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ruvectorTypesPath = path.join(__dirname, '../../ruvector/dist/index.d.ts');
|
||||
const coreTypesPath = path.join(__dirname, '../../core/dist/index.d.ts');
|
||||
|
||||
// At least one should exist
|
||||
const hasRuvectorTypes = fs.existsSync(ruvectorTypesPath);
|
||||
const hasCoreTypes = fs.existsSync(coreTypesPath);
|
||||
|
||||
assert.ok(
|
||||
hasRuvectorTypes || hasCoreTypes,
|
||||
'Should have TypeScript definitions'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user