ruvector/npm/tests/unit/core.test.js
rUv eefcc5322b feat: Add multi-platform GitHub Actions workflow for native module builds
Phase 2: Multi-Platform Native Builds

This commit adds comprehensive GitHub Actions CI/CD for building native
NAPI modules across all major platforms:

 Features:
- GitHub Actions workflow with 5-platform matrix build:
  - Linux (x64, ARM64)
  - macOS (x64 Intel, ARM64 Apple Silicon)
  - Windows (x64)
- Parallel builds complete in 7-10 minutes
- Automated artifact uploads and publishing
- Platform-specific npm packages with smart detection

📦 Package Structure:
- @ruvector/core - Main package with platform detection
- @ruvector/core-{platform} - Platform-specific binaries
- Smart loader with automatic platform selection
- Optional dependencies ensure minimal install size

🔧 Developer Tools:
- scripts/publish-platforms.js - Automated publishing
- Comprehensive TypeScript definitions
- Smoke tests for each platform
- Local build support with napi build

📚 Documentation:
- docs/BUILD_PROCESS.md - Complete build guide
- docs/PHASE2_MULTIPLATFORM_COMPLETE.md - Phase summary
- README for @ruvector/core package
- Troubleshooting and cross-compilation guides

🚀 Publishing Workflow:
1. Tag release (git tag v0.1.1)
2. Push to GitHub
3. CI builds all platforms
4. Publishes platform packages
5. Publishes main packages

Next: Phase 3 - WASM support with architectural refactoring

🤖 Generated with Claude Code
2025-11-21 13:19:13 +00:00

274 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');
});
});