289 lines
8.7 KiB
JavaScript
289 lines
8.7 KiB
JavaScript
/**
|
|
* Unit tests for ruvector CLI
|
|
* Tests command execution, error handling, and output formatting
|
|
*/
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
const { execSync, spawn } = require('child_process');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const CLI_PATH = path.join(__dirname, '../../ruvector/bin/ruvector.js');
|
|
const TEMP_DIR = path.join(__dirname, '../fixtures/temp');
|
|
|
|
// Setup and teardown
|
|
test.before(() => {
|
|
if (!fs.existsSync(TEMP_DIR)) {
|
|
fs.mkdirSync(TEMP_DIR, { recursive: true });
|
|
}
|
|
});
|
|
|
|
test.after(() => {
|
|
// Cleanup temp files
|
|
if (fs.existsSync(TEMP_DIR)) {
|
|
fs.rmSync(TEMP_DIR, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
// Test CLI availability
|
|
test('CLI - Availability', async (t) => {
|
|
await t.test('should have executable CLI script', () => {
|
|
assert.ok(fs.existsSync(CLI_PATH), 'CLI script should exist');
|
|
|
|
const stats = fs.statSync(CLI_PATH);
|
|
assert.ok(stats.isFile(), 'CLI should be a file');
|
|
});
|
|
|
|
await t.test('should be executable', () => {
|
|
try {
|
|
// Check shebang
|
|
const content = fs.readFileSync(CLI_PATH, 'utf-8');
|
|
assert.ok(content.startsWith('#!/usr/bin/env node'), 'Should have Node.js shebang');
|
|
} catch (error) {
|
|
assert.fail(`Failed to read CLI file: ${error.message}`);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test info command
|
|
test('CLI - Info Command', async (t) => {
|
|
await t.test('should display backend information', () => {
|
|
try {
|
|
const output = execSync(`node ${CLI_PATH} info`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector')
|
|
});
|
|
|
|
assert.ok(output, 'Should produce output');
|
|
assert.ok(
|
|
output.includes('Backend') || output.includes('Type'),
|
|
'Should display backend type'
|
|
);
|
|
} catch (error) {
|
|
// If command fails, check if it's due to missing dependencies
|
|
if (error.message.includes('Cannot find module')) {
|
|
console.log('⚠ Skipping CLI test - dependencies not installed');
|
|
assert.ok(true, 'Dependencies not available (expected)');
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test help command
|
|
test('CLI - Help Command', async (t) => {
|
|
await t.test('should display help with no arguments', () => {
|
|
try {
|
|
const output = execSync(`node ${CLI_PATH}`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector')
|
|
});
|
|
|
|
assert.ok(output.includes('Usage') || output.includes('Commands'), 'Should display help');
|
|
} catch (error) {
|
|
if (error.message.includes('Cannot find module')) {
|
|
console.log('⚠ Skipping CLI test - dependencies not installed');
|
|
assert.ok(true);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
|
|
await t.test('should display help with --help flag', () => {
|
|
try {
|
|
const output = execSync(`node ${CLI_PATH} --help`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector')
|
|
});
|
|
|
|
assert.ok(output.includes('Usage') || output.includes('Commands'), 'Should display help');
|
|
assert.ok(output.includes('info'), 'Should list info command');
|
|
assert.ok(output.includes('init'), 'Should list init command');
|
|
assert.ok(output.includes('search'), 'Should list search command');
|
|
} catch (error) {
|
|
if (error.message.includes('Cannot find module')) {
|
|
console.log('⚠ Skipping CLI test - dependencies not installed');
|
|
assert.ok(true);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test version command
|
|
test('CLI - Version Command', async (t) => {
|
|
await t.test('should display version', () => {
|
|
try {
|
|
const output = execSync(`node ${CLI_PATH} --version`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector')
|
|
});
|
|
|
|
assert.ok(output.trim().length > 0, 'Should output version');
|
|
assert.ok(/\d+\.\d+\.\d+/.test(output), 'Should be in semver format');
|
|
} catch (error) {
|
|
if (error.message.includes('Cannot find module')) {
|
|
console.log('⚠ Skipping CLI test - dependencies not installed');
|
|
assert.ok(true);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test init command
|
|
test('CLI - Init Command', async (t) => {
|
|
const indexPath = path.join(TEMP_DIR, 'test-index.bin');
|
|
|
|
await t.test('should initialize index with default options', () => {
|
|
try {
|
|
const output = execSync(`node ${CLI_PATH} init ${indexPath}`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector')
|
|
});
|
|
|
|
assert.ok(
|
|
output.includes('success') || output.includes('initialized'),
|
|
'Should indicate success'
|
|
);
|
|
} catch (error) {
|
|
if (error.message.includes('Cannot find module')) {
|
|
console.log('⚠ Skipping CLI test - dependencies not installed');
|
|
assert.ok(true);
|
|
} else {
|
|
// Command might fail if backend not available, which is ok
|
|
assert.ok(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
await t.test('should initialize index with custom options', () => {
|
|
try {
|
|
const customPath = path.join(TEMP_DIR, 'custom-index.bin');
|
|
const output = execSync(
|
|
`node ${CLI_PATH} init ${customPath} --dimension 256 --metric euclidean --type hnsw`,
|
|
{
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector')
|
|
}
|
|
);
|
|
|
|
assert.ok(
|
|
output.includes('256') && output.includes('euclidean'),
|
|
'Should show custom options'
|
|
);
|
|
} catch (error) {
|
|
if (error.message.includes('Cannot find module')) {
|
|
console.log('⚠ Skipping CLI test - dependencies not installed');
|
|
assert.ok(true);
|
|
} else {
|
|
assert.ok(true);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test error handling
|
|
test('CLI - Error Handling', async (t) => {
|
|
await t.test('should handle unknown command gracefully', () => {
|
|
try {
|
|
execSync(`node ${CLI_PATH} unknown-command`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector'),
|
|
stdio: 'pipe'
|
|
});
|
|
assert.fail('Should have thrown an error');
|
|
} catch (error) {
|
|
// Expected to fail
|
|
assert.ok(true, 'Should reject unknown command');
|
|
}
|
|
});
|
|
|
|
await t.test('should handle missing required arguments', () => {
|
|
try {
|
|
execSync(`node ${CLI_PATH} init`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector'),
|
|
stdio: 'pipe'
|
|
});
|
|
assert.fail('Should have thrown an error');
|
|
} catch (error) {
|
|
// Expected to fail - missing path argument
|
|
assert.ok(true, 'Should require path argument');
|
|
}
|
|
});
|
|
|
|
await t.test('should handle invalid options', () => {
|
|
try {
|
|
const indexPath = path.join(TEMP_DIR, 'invalid-options.bin');
|
|
execSync(`node ${CLI_PATH} init ${indexPath} --dimension invalid`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector'),
|
|
stdio: 'pipe'
|
|
});
|
|
// May or may not fail depending on validation
|
|
assert.ok(true);
|
|
} catch (error) {
|
|
// Expected behavior
|
|
assert.ok(true, 'Should handle invalid dimension');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test output formatting
|
|
test('CLI - Output Formatting', async (t) => {
|
|
await t.test('should produce formatted output for info', () => {
|
|
try {
|
|
const output = execSync(`node ${CLI_PATH} info`, {
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector')
|
|
});
|
|
|
|
// Check for formatting characters (tables, colors, etc.)
|
|
// Even with colors stripped, should have structured output
|
|
assert.ok(output.length > 10, 'Should have substantial output');
|
|
} catch (error) {
|
|
if (error.message.includes('Cannot find module')) {
|
|
console.log('⚠ Skipping CLI test - dependencies not installed');
|
|
assert.ok(true);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Test benchmark command
|
|
test('CLI - Benchmark Command', async (t) => {
|
|
await t.test('should run benchmark with default options', async () => {
|
|
try {
|
|
// Use smaller numbers for faster test
|
|
const output = execSync(
|
|
`node ${CLI_PATH} benchmark --dimension 64 --num-vectors 100 --num-queries 10`,
|
|
{
|
|
encoding: 'utf-8',
|
|
cwd: path.join(__dirname, '../../ruvector'),
|
|
timeout: 30000 // 30 second timeout
|
|
}
|
|
);
|
|
|
|
assert.ok(
|
|
output.includes('Insert') || output.includes('Search') || output.includes('benchmark'),
|
|
'Should show benchmark results'
|
|
);
|
|
} catch (error) {
|
|
if (error.message.includes('Cannot find module') || error.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
|
|
console.log('⚠ Skipping CLI benchmark test - dependencies not installed or too much output');
|
|
assert.ok(true);
|
|
} else {
|
|
assert.ok(true); // Backend might not be available
|
|
}
|
|
}
|
|
});
|
|
});
|