23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
861 lines
21 KiB
TypeScript
861 lines
21 KiB
TypeScript
/**
|
|
* RuVector Test Utilities
|
|
*
|
|
* Shared utilities for RuVector integration tests including:
|
|
* - Random vector generation
|
|
* - Mock data factories
|
|
* - Test database helpers
|
|
* - Performance measurement utilities
|
|
*
|
|
* @module @claude-flow/plugins/__tests__/utils/ruvector-test-utils
|
|
*/
|
|
|
|
import { vi, type Mock } from 'vitest';
|
|
import type {
|
|
RuVectorConfig,
|
|
RuVectorClientOptions,
|
|
VectorSearchOptions,
|
|
VectorSearchResult,
|
|
VectorInsertOptions,
|
|
VectorUpdateOptions,
|
|
VectorIndexOptions,
|
|
BatchVectorOptions,
|
|
GraphData,
|
|
GNNLayer,
|
|
AttentionConfig,
|
|
AttentionInput,
|
|
HyperbolicEmbedding,
|
|
HyperbolicInput,
|
|
DistanceMetric,
|
|
VectorIndexType,
|
|
IndexStats,
|
|
QueryResult,
|
|
BatchResult,
|
|
ConnectionResult,
|
|
HealthStatus,
|
|
RuVectorStats,
|
|
AnalysisResult,
|
|
MigrationResult,
|
|
} from '../../src/integrations/ruvector/types.js';
|
|
|
|
// ============================================================================
|
|
// Environment Detection
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Check if real database tests should be run
|
|
*/
|
|
export function useRealDatabase(): boolean {
|
|
return process.env.RUVECTOR_TEST_DB === 'true';
|
|
}
|
|
|
|
/**
|
|
* Get test database configuration from environment
|
|
*/
|
|
export function getTestDatabaseConfig(): RuVectorConfig {
|
|
return {
|
|
host: process.env.RUVECTOR_TEST_HOST ?? 'localhost',
|
|
port: parseInt(process.env.RUVECTOR_TEST_PORT ?? '5432', 10),
|
|
database: process.env.RUVECTOR_TEST_DATABASE ?? 'ruvector_test',
|
|
user: process.env.RUVECTOR_TEST_USER ?? 'postgres',
|
|
password: process.env.RUVECTOR_TEST_PASSWORD ?? 'postgres',
|
|
poolSize: 5,
|
|
connectionTimeoutMs: 5000,
|
|
queryTimeoutMs: 30000,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Vector Generation Utilities
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Generate a random vector with specified dimensions
|
|
*/
|
|
export function randomVector(dimensions: number = 384): number[] {
|
|
return Array.from({ length: dimensions }, () => Math.random() * 2 - 1);
|
|
}
|
|
|
|
/**
|
|
* Generate a normalized random vector (unit length)
|
|
*/
|
|
export function normalizedVector(dimensions: number = 384): number[] {
|
|
const vec = randomVector(dimensions);
|
|
const magnitude = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
|
|
return vec.map(v => v / magnitude);
|
|
}
|
|
|
|
/**
|
|
* Generate a vector within Poincare ball (norm < 1)
|
|
*/
|
|
export function poincareVector(dimensions: number = 32): number[] {
|
|
const vec = randomVector(dimensions);
|
|
const magnitude = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
|
|
const scale = Math.random() * 0.95 / Math.max(magnitude, 0.001);
|
|
return vec.map(v => v * scale);
|
|
}
|
|
|
|
/**
|
|
* Generate multiple random vectors
|
|
*/
|
|
export function randomVectors(count: number, dimensions: number = 384): number[][] {
|
|
return Array.from({ length: count }, () => randomVector(dimensions));
|
|
}
|
|
|
|
/**
|
|
* Generate vectors with known similarities for testing search accuracy
|
|
*/
|
|
export function generateSimilarVectors(
|
|
base: number[],
|
|
count: number,
|
|
noise: number = 0.1
|
|
): number[][] {
|
|
return Array.from({ length: count }, () =>
|
|
base.map(v => v + (Math.random() - 0.5) * noise * 2)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Generate orthogonal vectors for testing
|
|
*/
|
|
export function orthogonalVectors(dimensions: number, count: number): number[][] {
|
|
// Simple Gram-Schmidt orthogonalization
|
|
const vectors: number[][] = [];
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
let v = randomVector(dimensions);
|
|
|
|
// Subtract projections onto previous vectors
|
|
for (const u of vectors) {
|
|
const dot = v.reduce((sum, val, idx) => sum + val * u[idx], 0);
|
|
v = v.map((val, idx) => val - dot * u[idx]);
|
|
}
|
|
|
|
// Normalize
|
|
const mag = Math.sqrt(v.reduce((sum, val) => sum + val * val, 0));
|
|
if (mag > 0.001) {
|
|
vectors.push(v.map(val => val / mag));
|
|
}
|
|
}
|
|
|
|
return vectors;
|
|
}
|
|
|
|
/**
|
|
* Calculate cosine similarity between two vectors
|
|
*/
|
|
export function cosineSimilarity(a: number[], b: number[]): number {
|
|
const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);
|
|
const magA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
|
|
const magB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
|
|
return dot / (magA * magB);
|
|
}
|
|
|
|
/**
|
|
* Calculate Euclidean distance between two vectors
|
|
*/
|
|
export function euclideanDistance(a: number[], b: number[]): number {
|
|
return Math.sqrt(a.reduce((sum, v, i) => sum + (v - b[i]) ** 2, 0));
|
|
}
|
|
|
|
/**
|
|
* Calculate Poincare distance in hyperbolic space
|
|
*/
|
|
export function poincareDistance(a: number[], b: number[]): number {
|
|
const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
|
|
const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
|
|
const diffNorm = Math.sqrt(a.reduce((sum, v, i) => sum + (v - b[i]) ** 2, 0));
|
|
|
|
const numerator = 2 * diffNorm ** 2;
|
|
const denominator = (1 - normA ** 2) * (1 - normB ** 2);
|
|
|
|
return Math.acosh(1 + numerator / Math.max(denominator, 1e-10));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Mock Data Factories
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create a test configuration with optional overrides
|
|
*/
|
|
export function createTestConfig(overrides: Partial<RuVectorConfig> = {}): RuVectorConfig {
|
|
return {
|
|
host: 'localhost',
|
|
port: 5432,
|
|
database: 'test_db',
|
|
user: 'test_user',
|
|
password: 'test_password',
|
|
poolSize: 10,
|
|
connectionTimeoutMs: 5000,
|
|
queryTimeoutMs: 30000,
|
|
schema: 'public',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create test client options
|
|
*/
|
|
export function createTestClientOptions(
|
|
overrides: Partial<RuVectorClientOptions> = {}
|
|
): RuVectorClientOptions {
|
|
return {
|
|
...createTestConfig(),
|
|
autoReconnect: true,
|
|
maxReconnectAttempts: 3,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create mock search results
|
|
*/
|
|
export function createMockSearchResults(
|
|
count: number,
|
|
options: { includeVector?: boolean; includeMetadata?: boolean; dimensions?: number } = {}
|
|
): VectorSearchResult[] {
|
|
return Array.from({ length: count }, (_, i) => ({
|
|
id: `result-${i}`,
|
|
score: 1 - i * (1 / count),
|
|
distance: i * (1 / count),
|
|
rank: i + 1,
|
|
retrievedAt: new Date(),
|
|
...(options.includeVector && { vector: randomVector(options.dimensions ?? 384) }),
|
|
...(options.includeMetadata && { metadata: { index: i, label: `item-${i}` } }),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Create mock connection result
|
|
*/
|
|
export function createMockConnectionResult(): ConnectionResult {
|
|
return {
|
|
connectionId: `conn-${Date.now()}`,
|
|
ready: true,
|
|
serverVersion: 'PostgreSQL 15.0',
|
|
ruVectorVersion: '1.0.0',
|
|
parameters: {
|
|
server_encoding: 'UTF8',
|
|
client_encoding: 'UTF8',
|
|
server_version: '15.0',
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create mock index stats
|
|
*/
|
|
export function createMockIndexStats(
|
|
indexName: string,
|
|
indexType: VectorIndexType = 'hnsw'
|
|
): IndexStats {
|
|
return {
|
|
indexName,
|
|
indexType,
|
|
numVectors: 10000 + Math.floor(Math.random() * 90000),
|
|
sizeBytes: 1024 * 1024 * (50 + Math.floor(Math.random() * 200)),
|
|
buildTimeMs: 5000 + Math.floor(Math.random() * 10000),
|
|
lastRebuild: new Date(),
|
|
params: {
|
|
m: 16,
|
|
efConstruction: 200,
|
|
ef_search: 100,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create mock health status
|
|
*/
|
|
export function createMockHealthStatus(healthy: boolean = true): HealthStatus {
|
|
return {
|
|
status: healthy ? 'healthy' : 'unhealthy',
|
|
components: {
|
|
database: {
|
|
name: 'PostgreSQL',
|
|
healthy,
|
|
latencyMs: healthy ? 5 : undefined,
|
|
error: healthy ? undefined : 'Connection failed',
|
|
},
|
|
ruvector: {
|
|
name: 'RuVector Extension',
|
|
healthy,
|
|
latencyMs: healthy ? 1 : undefined,
|
|
},
|
|
pool: {
|
|
name: 'Connection Pool',
|
|
healthy: true,
|
|
latencyMs: 0,
|
|
},
|
|
},
|
|
lastCheck: new Date(),
|
|
issues: healthy ? [] : ['Database connection failed'],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create mock stats
|
|
*/
|
|
export function createMockStats(): RuVectorStats {
|
|
return {
|
|
version: '1.0.0',
|
|
totalVectors: 100000,
|
|
totalSizeBytes: 1024 * 1024 * 500,
|
|
numIndices: 3,
|
|
numTables: 5,
|
|
queryStats: {
|
|
totalQueries: 50000,
|
|
avgQueryTimeMs: 15,
|
|
p95QueryTimeMs: 50,
|
|
p99QueryTimeMs: 100,
|
|
cacheHitRate: 0.85,
|
|
},
|
|
memoryStats: {
|
|
usedBytes: 1024 * 1024 * 256,
|
|
peakBytes: 1024 * 1024 * 512,
|
|
indexBytes: 1024 * 1024 * 150,
|
|
cacheBytes: 1024 * 1024 * 50,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create mock analysis result
|
|
*/
|
|
export function createMockAnalysisResult(tableName: string = 'vectors'): AnalysisResult {
|
|
return {
|
|
tableName,
|
|
numRows: 10000,
|
|
columnStats: [
|
|
{
|
|
columnName: 'id',
|
|
dataType: 'uuid',
|
|
nullPercent: 0,
|
|
distinctCount: 10000,
|
|
avgSizeBytes: 16,
|
|
},
|
|
{
|
|
columnName: 'embedding',
|
|
dataType: 'vector(384)',
|
|
nullPercent: 0,
|
|
distinctCount: 10000,
|
|
avgSizeBytes: 1536,
|
|
},
|
|
{
|
|
columnName: 'metadata',
|
|
dataType: 'jsonb',
|
|
nullPercent: 5,
|
|
distinctCount: 9500,
|
|
avgSizeBytes: 256,
|
|
},
|
|
],
|
|
recommendations: [
|
|
'Consider adding an HNSW index for faster similarity search',
|
|
'Metadata column could benefit from a GIN index',
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create mock migration result
|
|
*/
|
|
export function createMockMigrationResult(
|
|
name: string,
|
|
direction: 'up' | 'down' = 'up',
|
|
success: boolean = true
|
|
): MigrationResult {
|
|
return {
|
|
name,
|
|
success,
|
|
direction,
|
|
durationMs: 500 + Math.floor(Math.random() * 2000),
|
|
affectedTables: ['vectors', 'vector_indices'],
|
|
error: success ? undefined : 'Migration failed: table already exists',
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Graph Data Factories
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create a random graph for GNN testing
|
|
*/
|
|
export function createRandomGraph(
|
|
numNodes: number,
|
|
numEdges: number,
|
|
featureDim: number
|
|
): GraphData {
|
|
const nodeFeatures = randomVectors(numNodes, featureDim);
|
|
const edges: [number[], number[]] = [[], []];
|
|
|
|
for (let i = 0; i < numEdges; i++) {
|
|
const source = Math.floor(Math.random() * numNodes);
|
|
const target = Math.floor(Math.random() * numNodes);
|
|
edges[0].push(source);
|
|
edges[1].push(target);
|
|
}
|
|
|
|
return {
|
|
nodeFeatures,
|
|
edgeIndex: edges,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a complete graph (all nodes connected)
|
|
*/
|
|
export function createCompleteGraph(numNodes: number, featureDim: number): GraphData {
|
|
const nodeFeatures = randomVectors(numNodes, featureDim);
|
|
const edges: [number[], number[]] = [[], []];
|
|
|
|
for (let i = 0; i < numNodes; i++) {
|
|
for (let j = 0; j < numNodes; j++) {
|
|
if (i !== j) {
|
|
edges[0].push(i);
|
|
edges[1].push(j);
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
nodeFeatures,
|
|
edgeIndex: edges,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a chain graph (linear sequence)
|
|
*/
|
|
export function createChainGraph(numNodes: number, featureDim: number): GraphData {
|
|
const nodeFeatures = randomVectors(numNodes, featureDim);
|
|
const edges: [number[], number[]] = [[], []];
|
|
|
|
for (let i = 0; i < numNodes - 1; i++) {
|
|
edges[0].push(i);
|
|
edges[1].push(i + 1);
|
|
// Bidirectional
|
|
edges[0].push(i + 1);
|
|
edges[1].push(i);
|
|
}
|
|
|
|
return {
|
|
nodeFeatures,
|
|
edgeIndex: edges,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Mock Database Interfaces
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Mock PostgreSQL client interface
|
|
*/
|
|
export interface MockPgClient {
|
|
connect: Mock;
|
|
query: Mock;
|
|
release: Mock;
|
|
end: Mock;
|
|
on: Mock;
|
|
off: Mock;
|
|
}
|
|
|
|
/**
|
|
* Mock PostgreSQL pool interface
|
|
*/
|
|
export interface MockPgPool {
|
|
connect: Mock;
|
|
query: Mock;
|
|
end: Mock;
|
|
on: Mock;
|
|
totalCount: number;
|
|
idleCount: number;
|
|
waitingCount: number;
|
|
}
|
|
|
|
/**
|
|
* Create a mock PostgreSQL client
|
|
*/
|
|
export function createMockPgClient(): MockPgClient {
|
|
return {
|
|
connect: vi.fn().mockResolvedValue(undefined),
|
|
query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
|
|
release: vi.fn(),
|
|
end: vi.fn().mockResolvedValue(undefined),
|
|
on: vi.fn(),
|
|
off: vi.fn(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a mock PostgreSQL pool
|
|
*/
|
|
export function createMockPgPool(): MockPgPool {
|
|
const mockClient = createMockPgClient();
|
|
return {
|
|
connect: vi.fn().mockResolvedValue(mockClient),
|
|
query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
|
|
end: vi.fn().mockResolvedValue(undefined),
|
|
on: vi.fn(),
|
|
totalCount: 10,
|
|
idleCount: 5,
|
|
waitingCount: 0,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Performance Testing Utilities
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Measure execution time of an async function
|
|
*/
|
|
export async function measureAsync<T>(
|
|
fn: () => Promise<T>
|
|
): Promise<{ result: T; durationMs: number }> {
|
|
const start = performance.now();
|
|
const result = await fn();
|
|
const durationMs = performance.now() - start;
|
|
return { result, durationMs };
|
|
}
|
|
|
|
/**
|
|
* Run a function multiple times and return statistics
|
|
*/
|
|
export async function benchmark<T>(
|
|
fn: () => Promise<T>,
|
|
iterations: number = 100
|
|
): Promise<{
|
|
iterations: number;
|
|
totalMs: number;
|
|
avgMs: number;
|
|
minMs: number;
|
|
maxMs: number;
|
|
p95Ms: number;
|
|
p99Ms: number;
|
|
}> {
|
|
const times: number[] = [];
|
|
|
|
for (let i = 0; i < iterations; i++) {
|
|
const { durationMs } = await measureAsync(fn);
|
|
times.push(durationMs);
|
|
}
|
|
|
|
times.sort((a, b) => a - b);
|
|
|
|
return {
|
|
iterations,
|
|
totalMs: times.reduce((sum, t) => sum + t, 0),
|
|
avgMs: times.reduce((sum, t) => sum + t, 0) / iterations,
|
|
minMs: times[0],
|
|
maxMs: times[times.length - 1],
|
|
p95Ms: times[Math.floor(iterations * 0.95)],
|
|
p99Ms: times[Math.floor(iterations * 0.99)],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate test data for throughput testing
|
|
*/
|
|
export function generateBulkInsertData(
|
|
count: number,
|
|
dimensions: number = 384
|
|
): VectorInsertOptions['vectors'] {
|
|
return Array.from({ length: count }, (_, i) => ({
|
|
id: `bulk-${Date.now()}-${i}`,
|
|
vector: randomVector(dimensions),
|
|
metadata: {
|
|
index: i,
|
|
timestamp: Date.now(),
|
|
batch: Math.floor(i / 100),
|
|
},
|
|
}));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Test Data Builders
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Builder for VectorSearchOptions
|
|
*/
|
|
export class SearchOptionsBuilder {
|
|
private options: VectorSearchOptions;
|
|
|
|
constructor(dimensions: number = 384) {
|
|
this.options = {
|
|
query: randomVector(dimensions),
|
|
k: 10,
|
|
metric: 'cosine',
|
|
};
|
|
}
|
|
|
|
withQuery(query: number[]): this {
|
|
this.options = { ...this.options, query };
|
|
return this;
|
|
}
|
|
|
|
withK(k: number): this {
|
|
this.options = { ...this.options, k };
|
|
return this;
|
|
}
|
|
|
|
withMetric(metric: DistanceMetric): this {
|
|
this.options = { ...this.options, metric };
|
|
return this;
|
|
}
|
|
|
|
withFilter(filter: Record<string, unknown>): this {
|
|
this.options = { ...this.options, filter };
|
|
return this;
|
|
}
|
|
|
|
withThreshold(threshold: number): this {
|
|
this.options = { ...this.options, threshold };
|
|
return this;
|
|
}
|
|
|
|
withTable(tableName: string): this {
|
|
this.options = { ...this.options, tableName };
|
|
return this;
|
|
}
|
|
|
|
includeVector(include: boolean = true): this {
|
|
this.options = { ...this.options, includeVector: include };
|
|
return this;
|
|
}
|
|
|
|
includeMetadata(include: boolean = true): this {
|
|
this.options = { ...this.options, includeMetadata: include };
|
|
return this;
|
|
}
|
|
|
|
build(): VectorSearchOptions {
|
|
return { ...this.options };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builder for VectorInsertOptions
|
|
*/
|
|
export class InsertOptionsBuilder {
|
|
private options: VectorInsertOptions;
|
|
|
|
constructor(tableName: string = 'vectors') {
|
|
this.options = {
|
|
tableName,
|
|
vectors: [],
|
|
};
|
|
}
|
|
|
|
addVector(
|
|
vector: number[],
|
|
id?: string,
|
|
metadata?: Record<string, unknown>
|
|
): this {
|
|
this.options = {
|
|
...this.options,
|
|
vectors: [...this.options.vectors, { id, vector, metadata }],
|
|
};
|
|
return this;
|
|
}
|
|
|
|
addRandomVectors(count: number, dimensions: number = 384): this {
|
|
const vectors = Array.from({ length: count }, (_, i) => ({
|
|
id: `gen-${Date.now()}-${i}`,
|
|
vector: randomVector(dimensions),
|
|
metadata: { generated: true, index: i },
|
|
}));
|
|
this.options = {
|
|
...this.options,
|
|
vectors: [...this.options.vectors, ...vectors],
|
|
};
|
|
return this;
|
|
}
|
|
|
|
withUpsert(upsert: boolean = true): this {
|
|
this.options = { ...this.options, upsert };
|
|
return this;
|
|
}
|
|
|
|
withBatchSize(batchSize: number): this {
|
|
this.options = { ...this.options, batchSize };
|
|
return this;
|
|
}
|
|
|
|
withReturning(returning: boolean = true): this {
|
|
this.options = { ...this.options, returning };
|
|
return this;
|
|
}
|
|
|
|
build(): VectorInsertOptions {
|
|
return { ...this.options };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builder for VectorIndexOptions
|
|
*/
|
|
export class IndexOptionsBuilder {
|
|
private options: VectorIndexOptions;
|
|
|
|
constructor(tableName: string, columnName: string = 'embedding') {
|
|
this.options = {
|
|
tableName,
|
|
columnName,
|
|
indexType: 'hnsw',
|
|
};
|
|
}
|
|
|
|
withType(indexType: VectorIndexType): this {
|
|
this.options = { ...this.options, indexType };
|
|
return this;
|
|
}
|
|
|
|
withName(indexName: string): this {
|
|
this.options = { ...this.options, indexName };
|
|
return this;
|
|
}
|
|
|
|
withMetric(metric: DistanceMetric): this {
|
|
this.options = { ...this.options, metric };
|
|
return this;
|
|
}
|
|
|
|
withHNSWParams(m: number, efConstruction: number): this {
|
|
this.options = { ...this.options, m, efConstruction };
|
|
return this;
|
|
}
|
|
|
|
withIVFParams(lists: number): this {
|
|
this.options = { ...this.options, lists };
|
|
return this;
|
|
}
|
|
|
|
concurrent(concurrent: boolean = true): this {
|
|
this.options = { ...this.options, concurrent };
|
|
return this;
|
|
}
|
|
|
|
replace(replace: boolean = true): this {
|
|
this.options = { ...this.options, replace };
|
|
return this;
|
|
}
|
|
|
|
build(): VectorIndexOptions {
|
|
return { ...this.options };
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Assertion Helpers
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Assert that results are sorted by score descending
|
|
*/
|
|
export function assertSortedByScore(results: VectorSearchResult[]): void {
|
|
for (let i = 1; i < results.length; i++) {
|
|
if (results[i].score > results[i - 1].score) {
|
|
throw new Error(
|
|
`Results not sorted by score: ${results[i - 1].score} > ${results[i].score}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert that results are sorted by distance ascending
|
|
*/
|
|
export function assertSortedByDistance(results: VectorSearchResult[]): void {
|
|
for (let i = 1; i < results.length; i++) {
|
|
if (
|
|
results[i].distance !== undefined &&
|
|
results[i - 1].distance !== undefined &&
|
|
results[i].distance! < results[i - 1].distance!
|
|
) {
|
|
throw new Error(
|
|
`Results not sorted by distance: ${results[i - 1].distance} < ${results[i].distance}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert that all vectors are normalized (unit length)
|
|
*/
|
|
export function assertNormalized(vectors: number[][], tolerance: number = 0.001): void {
|
|
for (const vec of vectors) {
|
|
const magnitude = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
|
|
if (Math.abs(magnitude - 1) > tolerance) {
|
|
throw new Error(`Vector not normalized: magnitude = ${magnitude}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert that all vectors are inside Poincare ball
|
|
*/
|
|
export function assertInPoincareBall(
|
|
vectors: number[][],
|
|
maxNorm: number = 0.99
|
|
): void {
|
|
for (const vec of vectors) {
|
|
const norm = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
|
|
if (norm >= maxNorm) {
|
|
throw new Error(`Vector outside Poincare ball: norm = ${norm}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Cleanup Utilities
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Generate unique table name for tests
|
|
*/
|
|
export function uniqueTableName(prefix: string = 'test'): string {
|
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
}
|
|
|
|
/**
|
|
* Generate unique index name for tests
|
|
*/
|
|
export function uniqueIndexName(tableName: string, columnName: string = 'embedding'): string {
|
|
return `idx_${tableName}_${columnName}_${Math.random().toString(36).slice(2, 8)}`;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Type Exports
|
|
// ============================================================================
|
|
|
|
export type {
|
|
RuVectorConfig,
|
|
RuVectorClientOptions,
|
|
VectorSearchOptions,
|
|
VectorSearchResult,
|
|
VectorInsertOptions,
|
|
VectorUpdateOptions,
|
|
VectorIndexOptions,
|
|
BatchVectorOptions,
|
|
GraphData,
|
|
GNNLayer,
|
|
AttentionConfig,
|
|
AttentionInput,
|
|
HyperbolicEmbedding,
|
|
HyperbolicInput,
|
|
DistanceMetric,
|
|
VectorIndexType,
|
|
IndexStats,
|
|
QueryResult,
|
|
BatchResult,
|
|
ConnectionResult,
|
|
HealthStatus,
|
|
RuVectorStats,
|
|
AnalysisResult,
|
|
MigrationResult,
|
|
};
|