Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

1603 lines
46 KiB
TypeScript

/**
* RuVector PostgreSQL Integration Tests
*
* Comprehensive integration tests for the RuVector PostgreSQL Bridge plugin.
* Tests can run against a real PostgreSQL database (when available) or use mocks.
*
* Environment variables for real database testing:
* - RUVECTOR_TEST_DB=true - Enable real database tests
* - RUVECTOR_TEST_HOST - PostgreSQL host (default: localhost)
* - RUVECTOR_TEST_PORT - PostgreSQL port (default: 5432)
* - RUVECTOR_TEST_DATABASE - Database name (default: ruvector_test)
* - RUVECTOR_TEST_USER - Database user (default: postgres)
* - RUVECTOR_TEST_PASSWORD - Database password (default: postgres)
*
* @module @claude-flow/plugins/__tests__/ruvector-integration
*/
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, vi, type Mock } from 'vitest';
import {
useRealDatabase,
getTestDatabaseConfig,
createTestConfig,
createTestClientOptions,
randomVector,
normalizedVector,
randomVectors,
generateSimilarVectors,
cosineSimilarity,
euclideanDistance,
createMockSearchResults,
createMockConnectionResult,
createMockIndexStats,
createMockHealthStatus,
createMockStats,
createMockPgPool,
createMockPgClient,
createRandomGraph,
SearchOptionsBuilder,
InsertOptionsBuilder,
IndexOptionsBuilder,
measureAsync,
benchmark,
uniqueTableName,
uniqueIndexName,
assertSortedByScore,
assertSortedByDistance,
type MockPgPool,
type MockPgClient,
} from './utils/ruvector-test-utils.js';
import type {
RuVectorConfig,
RuVectorClientOptions,
VectorSearchOptions,
VectorSearchResult,
VectorInsertOptions,
VectorIndexOptions,
BatchVectorOptions,
ConnectionResult,
HealthStatus,
IndexStats,
IRuVectorClient,
IRuVectorTransaction,
QueryResult,
BatchResult,
RuVectorEventType,
RuVectorEvent,
PoolConfig,
SSLConfig,
RetryConfig,
} from '../src/integrations/ruvector/types.js';
// ============================================================================
// Test Configuration
// ============================================================================
const USE_REAL_DB = useRealDatabase();
const TEST_TIMEOUT = USE_REAL_DB ? 30000 : 5000;
// Skip message for real DB tests when not available
const skipIfNoRealDB = USE_REAL_DB ? describe : describe.skip;
// ============================================================================
// Mock RuVector Client Implementation
// ============================================================================
/**
* Mock RuVector client for unit testing without real database
*/
class MockRuVectorClient implements IRuVectorClient {
private pool: MockPgPool;
private config: RuVectorClientOptions;
private connected: boolean = false;
private eventHandlers: Map<RuVectorEventType, Set<(event: RuVectorEvent) => void>> = new Map();
private connectionInfo: ConnectionResult | null = null;
private queryCount: number = 0;
constructor(config: RuVectorClientOptions) {
this.config = config;
this.pool = createMockPgPool();
}
// Event Emitter Implementation
on<T extends RuVectorEventType>(event: T, handler: (e: RuVectorEvent<T>) => void): () => void {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, new Set());
}
this.eventHandlers.get(event)!.add(handler as (e: RuVectorEvent) => void);
return () => this.off(event, handler);
}
off<T extends RuVectorEventType>(event: T, handler: (e: RuVectorEvent<T>) => void): void {
this.eventHandlers.get(event)?.delete(handler as (e: RuVectorEvent) => void);
}
once<T extends RuVectorEventType>(event: T, handler: (e: RuVectorEvent<T>) => void): () => void {
const wrappedHandler = (e: RuVectorEvent<T>) => {
this.off(event, wrappedHandler);
handler(e);
};
return this.on(event, wrappedHandler);
}
emit<T extends RuVectorEventType>(event: T, data: RuVectorEvent<T>['data']): void {
const handlers = this.eventHandlers.get(event);
if (handlers) {
handlers.forEach((handler) =>
handler({ type: event, timestamp: new Date(), data } as RuVectorEvent)
);
}
}
removeAllListeners(event?: RuVectorEventType): void {
if (event) {
this.eventHandlers.delete(event);
} else {
this.eventHandlers.clear();
}
}
// Connection Management
async connect(): Promise<ConnectionResult> {
this.connected = true;
this.connectionInfo = createMockConnectionResult();
this.emit('connection:open', {
connectionId: this.connectionInfo.connectionId,
host: this.config.host,
port: this.config.port,
database: this.config.database,
});
return this.connectionInfo;
}
async disconnect(): Promise<void> {
this.connected = false;
this.emit('connection:close', {
connectionId: this.connectionInfo?.connectionId || '',
host: this.config.host,
port: this.config.port,
database: this.config.database,
});
this.connectionInfo = null;
}
isConnected(): boolean {
return this.connected;
}
getConnectionInfo(): ConnectionResult | null {
return this.connectionInfo;
}
// Vector Operations
async search(options: VectorSearchOptions): Promise<VectorSearchResult[]> {
this.ensureConnected();
this.queryCount++;
this.emit('search:start', {
searchId: `search-${this.queryCount}`,
tableName: options.tableName || 'vectors',
k: options.k,
metric: options.metric,
hasFilters: !!options.filter,
});
// Simulate search with mock results
const results = createMockSearchResults(Math.min(options.k, 100), {
includeVector: options.includeVector,
includeMetadata: options.includeMetadata,
dimensions: options.query.length,
});
this.emit('search:complete', {
searchId: `search-${this.queryCount}`,
durationMs: 15 + Math.random() * 10,
resultCount: results.length,
scannedCount: 1000,
cacheHit: false,
});
return results;
}
async batchSearch(options: BatchVectorOptions): Promise<{
results: VectorSearchResult[][];
totalDurationMs: number;
avgDurationMs: number;
cacheStats: { hits: number; misses: number; hitRate: number };
}> {
this.ensureConnected();
const results = await Promise.all(
options.queries.map((query) =>
this.search({
query: Array.from(query),
k: options.k,
metric: options.metric,
filter: options.filter,
tableName: options.tableName,
vectorColumn: options.vectorColumn,
})
)
);
const totalDurationMs = results.length * 15;
return {
results,
totalDurationMs,
avgDurationMs: totalDurationMs / options.queries.length,
cacheStats: {
hits: 0,
misses: options.queries.length,
hitRate: 0,
},
};
}
async insert(options: VectorInsertOptions): Promise<BatchResult<string>> {
this.ensureConnected();
this.queryCount++;
const ids = options.vectors.map(
(v, i) => v.id?.toString() || `gen-${Date.now()}-${i}`
);
options.vectors.forEach((v, i) => {
this.emit('vector:inserted', {
tableName: options.tableName,
vectorId: ids[i],
dimensions: Array.isArray(v.vector) ? v.vector.length : v.vector.length,
});
});
const durationMs = options.vectors.length * 5;
return {
total: options.vectors.length,
successful: options.vectors.length,
failed: 0,
results: options.returning ? ids : undefined,
durationMs,
throughput: options.vectors.length / (durationMs / 1000),
};
}
async update(options: { tableName: string; id: string | number; vector?: number[]; metadata?: Record<string, unknown> }): Promise<boolean> {
this.ensureConnected();
this.queryCount++;
this.emit('vector:updated', {
tableName: options.tableName,
vectorId: options.id,
dimensions: options.vector?.length ?? 0,
});
return true;
}
async delete(tableName: string, id: string | number): Promise<boolean> {
this.ensureConnected();
this.queryCount++;
this.emit('vector:deleted', {
tableName,
vectorId: id,
dimensions: 0,
});
return true;
}
async bulkDelete(tableName: string, ids: Array<string | number>): Promise<BatchResult> {
this.ensureConnected();
return {
total: ids.length,
successful: ids.length,
failed: 0,
durationMs: ids.length * 2,
throughput: ids.length / ((ids.length * 2) / 1000),
};
}
// Index Management
async createIndex(options: VectorIndexOptions): Promise<void> {
this.ensureConnected();
this.emit('index:created', {
indexName: options.indexName || `idx_${options.tableName}_${options.columnName}`,
tableName: options.tableName,
columnName: options.columnName,
indexType: options.indexType,
durationMs: 1000 + Math.random() * 2000,
});
}
async dropIndex(indexName: string): Promise<void> {
this.ensureConnected();
this.emit('index:dropped', {
indexName,
tableName: '',
columnName: '',
indexType: 'hnsw',
});
}
async rebuildIndex(indexName: string): Promise<void> {
this.ensureConnected();
this.emit('index:rebuilt', {
indexName,
tableName: '',
columnName: '',
indexType: 'hnsw',
durationMs: 2000 + Math.random() * 3000,
});
}
async getIndexStats(indexName: string): Promise<IndexStats> {
this.ensureConnected();
return createMockIndexStats(indexName);
}
async listIndices(tableName?: string): Promise<IndexStats[]> {
this.ensureConnected();
return [
createMockIndexStats(`idx_${tableName || 'vectors'}_embedding_hnsw`),
createMockIndexStats(`idx_${tableName || 'vectors'}_embedding_ivf`, 'ivfflat'),
];
}
// Attention Operations
async computeAttention(input: { query: number[][]; key: number[][]; value: number[][] }, config: { mechanism: string; numHeads: number; headDim: number; params?: Record<string, unknown> }): Promise<{ output: number[][]; attentionWeights?: number[][][][]; stats?: { computeTimeMs: number; memoryBytes: number; tokensProcessed: number } }> {
this.ensureConnected();
const seqLen = input.query.length;
const outputDim = config.numHeads * config.headDim;
const output = input.query.map(() =>
Array.from({ length: outputDim }, () => Math.random() * 2 - 1)
);
this.emit('attention:computed', {
mechanism: config.mechanism as any,
seqLen,
numHeads: config.numHeads,
durationMs: seqLen * 0.1,
memoryBytes: seqLen * outputDim * 4,
});
return {
output,
attentionWeights: config.params?.checkpointing ? undefined : undefined,
stats: {
computeTimeMs: seqLen * 0.1,
memoryBytes: seqLen * outputDim * 4,
tokensProcessed: seqLen,
},
};
}
// GNN Operations
async runGNNLayer(graph: { nodeFeatures: number[][]; edgeIndex: [number[], number[]] }, layer: { type: string; inputDim: number; outputDim: number }): Promise<{ nodeEmbeddings: number[][]; stats?: { forwardTimeMs: number; numNodes: number; numEdges: number; memoryBytes: number; numIterations: number } }> {
this.ensureConnected();
const numNodes = graph.nodeFeatures.length;
const numEdges = graph.edgeIndex[0].length;
const nodeEmbeddings = graph.nodeFeatures.map(() =>
Array.from({ length: layer.outputDim }, () => Math.random() * 2 - 1)
);
this.emit('gnn:forward', {
layerType: layer.type as any,
numNodes,
numEdges,
durationMs: numNodes * 0.05,
});
return {
nodeEmbeddings,
stats: {
forwardTimeMs: numNodes * 0.05,
numNodes,
numEdges,
memoryBytes: numNodes * layer.outputDim * 4,
numIterations: 1,
},
};
}
buildGraph(nodeFeatures: number[][], edges: [number, number][]): { nodeFeatures: number[][]; edgeIndex: [number[], number[]] } {
return {
nodeFeatures,
edgeIndex: [edges.map((e) => e[0]), edges.map((e) => e[1])],
};
}
// Hyperbolic Operations
async hyperbolicEmbed(input: { points: number[][] }, config: { model: string; curvature: number; dimension: number }): Promise<{ embeddings: number[][]; curvature: number }> {
this.ensureConnected();
const embeddings = input.points.map((point) => {
const norm = Math.sqrt(point.reduce((sum, v) => sum + v * v, 0));
const scale = norm >= 1 ? 0.99 / norm : 1;
return point.map((v) => v * scale);
});
this.emit('hyperbolic:embed', {
model: config.model as any,
operation: 'embed',
numPoints: input.points.length,
durationMs: input.points.length * 0.02,
});
return {
embeddings,
curvature: config.curvature,
};
}
async hyperbolicDistance(a: number[], b: number[], config: { model: string; curvature: number; dimension: number }): Promise<number> {
this.ensureConnected();
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 = Math.max((1 - normA ** 2) * (1 - normB ** 2), 1e-10);
const distance = Math.acosh(1 + numerator / denominator);
this.emit('hyperbolic:distance', {
model: config.model as any,
operation: 'distance',
numPoints: 2,
durationMs: 0.01,
});
return distance;
}
// Embedding Operations
async embed(text: string, model?: string): Promise<{ embedding: number[]; model: string; tokenCount: number; durationMs: number; dimension: number }> {
this.ensureConnected();
return {
embedding: randomVector(384),
model: model || 'default',
tokenCount: Math.ceil(text.length / 4),
durationMs: 50,
dimension: 384,
};
}
async embedBatch(texts: string[], model?: string): Promise<{ embeddings: Array<{ embedding: number[]; model: string; tokenCount: number; durationMs: number; dimension: number }>; totalTokens: number; totalDurationMs: number; throughput: number }> {
this.ensureConnected();
const embeddings = await Promise.all(texts.map((t) => this.embed(t, model)));
const totalTokens = embeddings.reduce((sum, e) => sum + e.tokenCount, 0);
const totalDurationMs = embeddings.reduce((sum, e) => sum + e.durationMs, 0);
return {
embeddings,
totalTokens,
totalDurationMs,
throughput: totalTokens / (totalDurationMs / 1000),
};
}
// Transaction Support
async transaction<T>(fn: (tx: IRuVectorTransaction) => Promise<T>): Promise<{ transactionId: string; committed: boolean; data?: T; durationMs: number; queryCount: number }> {
this.ensureConnected();
const startTime = Date.now();
let txQueryCount = 0;
const tx: IRuVectorTransaction = {
query: async <R = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<QueryResult<R>> => {
txQueryCount++;
return { rows: [] as R[], rowCount: 0, durationMs: 5, command: 'SELECT' };
},
insert: async (options: VectorInsertOptions) => {
txQueryCount++;
return this.insert(options);
},
update: async (options: { tableName: string; id: string | number; vector?: number[]; metadata?: Record<string, unknown> }) => {
txQueryCount++;
return this.update(options);
},
delete: async (tableName: string, id: string | number) => {
txQueryCount++;
return this.delete(tableName, id);
},
commit: async () => {},
rollback: async () => {},
};
try {
const data = await fn(tx);
await tx.commit();
return {
transactionId: `tx-${Date.now()}`,
committed: true,
data,
durationMs: Date.now() - startTime,
queryCount: txQueryCount,
};
} catch (error) {
await tx.rollback();
throw error;
}
}
// Admin Operations
async vacuum(tableName?: string): Promise<void> {
this.ensureConnected();
this.emit('admin:vacuum', {
operation: 'vacuum',
tableName,
durationMs: 500,
});
}
async analyze(tableName?: string): Promise<{ tableName: string; numRows: number; columnStats: Array<{ columnName: string; dataType: string; nullPercent: number; distinctCount: number; avgSizeBytes: number }>; recommendations: string[] }> {
this.ensureConnected();
this.emit('admin:analyze', {
operation: 'analyze',
tableName,
durationMs: 300,
});
return {
tableName: tableName || 'all',
numRows: 10000,
columnStats: [
{
columnName: 'embedding',
dataType: 'vector(384)',
nullPercent: 0,
distinctCount: 10000,
avgSizeBytes: 1536,
},
],
recommendations: ['Consider adding an HNSW index for faster similarity search'],
};
}
async healthCheck(): Promise<HealthStatus> {
return createMockHealthStatus(this.connected);
}
async getStats(): Promise<{ version: string; totalVectors: number; totalSizeBytes: number; numIndices: number; numTables: number; queryStats: { totalQueries: number; avgQueryTimeMs: number; p95QueryTimeMs: number; p99QueryTimeMs: number; cacheHitRate: number }; memoryStats: { usedBytes: number; peakBytes: number; indexBytes: number; cacheBytes: number } }> {
this.ensureConnected();
return createMockStats();
}
private ensureConnected(): void {
if (!this.connected) {
throw new Error('Not connected');
}
}
getPool(): MockPgPool {
return this.pool;
}
getConfig(): RuVectorClientOptions {
return this.config;
}
}
// ============================================================================
// Test Suites
// ============================================================================
describe('RuVector PostgreSQL Integration', () => {
let client: MockRuVectorClient;
let config: RuVectorClientOptions;
beforeEach(() => {
config = createTestClientOptions();
client = new MockRuVectorClient(config);
});
afterEach(async () => {
if (client.isConnected()) {
await client.disconnect();
}
});
// ==========================================================================
// Connection Management Tests
// ==========================================================================
describe('Connection Management', () => {
it('should connect to PostgreSQL', async () => {
const result = await client.connect();
expect(client.isConnected()).toBe(true);
expect(result.ready).toBe(true);
expect(result.connectionId).toBeDefined();
expect(result.serverVersion).toBeDefined();
});
it('should handle connection failures gracefully', async () => {
// Test that operations throw when not connected
await expect(
client.search(new SearchOptionsBuilder().build())
).rejects.toThrow('Not connected');
});
it('should reconnect after connection loss', async () => {
// Connect first
await client.connect();
expect(client.isConnected()).toBe(true);
// Disconnect
await client.disconnect();
expect(client.isConnected()).toBe(false);
// Reconnect
await client.connect();
expect(client.isConnected()).toBe(true);
});
it('should respect pool limits', async () => {
const customConfig = createTestClientOptions({
pool: {
min: 2,
max: 5,
idleTimeoutMs: 10000,
acquireTimeoutMs: 5000,
},
});
const customClient = new MockRuVectorClient(customConfig);
await customClient.connect();
expect(customClient.getConfig().pool?.max).toBe(5);
expect(customClient.getConfig().pool?.min).toBe(2);
await customClient.disconnect();
});
it('should emit connection events', async () => {
const openHandler = vi.fn();
const closeHandler = vi.fn();
client.on('connection:open', openHandler);
client.on('connection:close', closeHandler);
await client.connect();
expect(openHandler).toHaveBeenCalledTimes(1);
await client.disconnect();
expect(closeHandler).toHaveBeenCalledTimes(1);
});
it('should track connection state correctly', async () => {
expect(client.isConnected()).toBe(false);
expect(client.getConnectionInfo()).toBeNull();
await client.connect();
expect(client.isConnected()).toBe(true);
expect(client.getConnectionInfo()).not.toBeNull();
await client.disconnect();
expect(client.isConnected()).toBe(false);
expect(client.getConnectionInfo()).toBeNull();
});
it('should support SSL configuration', async () => {
const sslConfig: SSLConfig = {
enabled: true,
rejectUnauthorized: true,
ca: '/path/to/ca.pem',
cert: '/path/to/cert.pem',
key: '/path/to/key.pem',
};
const sslClient = new MockRuVectorClient({
...config,
ssl: sslConfig,
});
await sslClient.connect();
expect(sslClient.isConnected()).toBe(true);
const clientSsl = sslClient.getConfig().ssl as SSLConfig;
expect(clientSsl.enabled).toBe(true);
expect(clientSsl.rejectUnauthorized).toBe(true);
await sslClient.disconnect();
});
it('should support retry configuration', () => {
const retryConfig: RetryConfig = {
maxAttempts: 5,
initialDelayMs: 100,
maxDelayMs: 5000,
backoffMultiplier: 2,
jitter: true,
retryableErrors: ['ECONNREFUSED', 'ETIMEDOUT'],
};
const retryClient = new MockRuVectorClient({
...config,
retry: retryConfig,
});
expect(retryClient.getConfig().retry?.maxAttempts).toBe(5);
expect(retryClient.getConfig().retry?.backoffMultiplier).toBe(2);
});
});
// ==========================================================================
// Vector Operations Tests
// ==========================================================================
describe('Vector Operations', () => {
beforeEach(async () => {
await client.connect();
});
it('should insert vectors with metadata', async () => {
const options = new InsertOptionsBuilder('test_vectors')
.addRandomVectors(10, 384)
.withReturning(true)
.build();
const result = await client.insert(options);
expect(result.successful).toBe(10);
expect(result.failed).toBe(0);
expect(result.results).toHaveLength(10);
expect(result.throughput).toBeGreaterThan(0);
});
it('should perform cosine similarity search', async () => {
const options = new SearchOptionsBuilder(384)
.withMetric('cosine')
.withK(10)
.withTable('embeddings')
.build();
const results = await client.search(options);
expect(results).toHaveLength(10);
results.forEach((r) => {
expect(r.id).toBeDefined();
expect(r.score).toBeDefined();
expect(r.score).toBeGreaterThanOrEqual(0);
expect(r.score).toBeLessThanOrEqual(1);
});
});
it('should perform euclidean distance search', async () => {
const options = new SearchOptionsBuilder(384)
.withMetric('euclidean')
.withK(5)
.build();
const results = await client.search(options);
expect(results).toHaveLength(5);
results.forEach((r) => {
expect(r.distance).toBeDefined();
});
});
it('should filter by metadata', async () => {
const options = new SearchOptionsBuilder(384)
.withK(10)
.withMetric('cosine')
.withFilter({ category: 'test', active: true })
.includeMetadata(true)
.build();
const results = await client.search(options);
expect(results).toHaveLength(10);
results.forEach((r) => {
expect(r.metadata).toBeDefined();
});
});
it('should handle batch inserts', async () => {
const options = new InsertOptionsBuilder('bulk_vectors')
.addRandomVectors(100, 384)
.withBatchSize(25)
.withReturning(true)
.build();
const result = await client.insert(options);
expect(result.total).toBe(100);
expect(result.successful).toBe(100);
expect(result.failed).toBe(0);
});
it('should update existing vectors', async () => {
const success = await client.update({
tableName: 'vectors',
id: 'test-id-1',
vector: randomVector(384),
metadata: { updated: true, timestamp: Date.now() },
});
expect(success).toBe(true);
});
it('should delete vectors', async () => {
const success = await client.delete('vectors', 'test-id-1');
expect(success).toBe(true);
});
it('should bulk delete vectors', async () => {
const ids = ['id-1', 'id-2', 'id-3', 'id-4', 'id-5'];
const result = await client.bulkDelete('vectors', ids);
expect(result.total).toBe(5);
expect(result.successful).toBe(5);
expect(result.failed).toBe(0);
});
it('should emit vector operation events', async () => {
const insertHandler = vi.fn();
const updateHandler = vi.fn();
const deleteHandler = vi.fn();
client.on('vector:inserted', insertHandler);
client.on('vector:updated', updateHandler);
client.on('vector:deleted', deleteHandler);
await client.insert(
new InsertOptionsBuilder('test')
.addVector(randomVector(384), 'test-1')
.build()
);
await client.update({
tableName: 'test',
id: 'test-1',
vector: randomVector(384),
});
await client.delete('test', 'test-1');
expect(insertHandler).toHaveBeenCalled();
expect(updateHandler).toHaveBeenCalled();
expect(deleteHandler).toHaveBeenCalled();
});
it('should include vector in search results when requested', async () => {
const options = new SearchOptionsBuilder(384)
.withK(5)
.includeVector(true)
.build();
const results = await client.search(options);
results.forEach((r) => {
expect(r.vector).toBeDefined();
expect(r.vector).toHaveLength(384);
});
});
it('should throw when not connected', async () => {
await client.disconnect();
await expect(
client.search(new SearchOptionsBuilder().build())
).rejects.toThrow('Not connected');
});
});
// ==========================================================================
// Index Management Tests
// ==========================================================================
describe('Index Management', () => {
beforeEach(async () => {
await client.connect();
});
it('should create HNSW index', async () => {
const options = new IndexOptionsBuilder('test_vectors', 'embedding')
.withType('hnsw')
.withHNSWParams(16, 200)
.withMetric('cosine')
.build();
await expect(client.createIndex(options)).resolves.toBeUndefined();
});
it('should create IVFFlat index', async () => {
const options = new IndexOptionsBuilder('test_vectors', 'embedding')
.withType('ivfflat')
.withIVFParams(100)
.withMetric('euclidean')
.build();
await expect(client.createIndex(options)).resolves.toBeUndefined();
});
it('should report index statistics', async () => {
const stats = await client.getIndexStats('idx_test_embedding');
expect(stats.indexName).toBe('idx_test_embedding');
expect(stats.indexType).toBeDefined();
expect(stats.numVectors).toBeGreaterThan(0);
expect(stats.sizeBytes).toBeGreaterThan(0);
});
it('should handle concurrent index creation', async () => {
const options = new IndexOptionsBuilder('test_vectors', 'embedding')
.withType('hnsw')
.concurrent(true)
.build();
await expect(client.createIndex(options)).resolves.toBeUndefined();
});
it('should list all indices', async () => {
const indices = await client.listIndices();
expect(indices.length).toBeGreaterThan(0);
indices.forEach((idx) => {
expect(idx.indexName).toBeDefined();
expect(idx.indexType).toBeDefined();
});
});
it('should list indices for specific table', async () => {
const indices = await client.listIndices('vectors');
expect(indices.length).toBeGreaterThan(0);
});
it('should rebuild index', async () => {
const rebuiltHandler = vi.fn();
client.on('index:rebuilt', rebuiltHandler);
await client.rebuildIndex('idx_test');
expect(rebuiltHandler).toHaveBeenCalled();
});
it('should drop index', async () => {
const droppedHandler = vi.fn();
client.on('index:dropped', droppedHandler);
await client.dropIndex('idx_test');
expect(droppedHandler).toHaveBeenCalled();
});
it('should emit index events', async () => {
const createdHandler = vi.fn();
client.on('index:created', createdHandler);
await client.createIndex(
new IndexOptionsBuilder('test', 'embedding')
.withType('hnsw')
.build()
);
expect(createdHandler).toHaveBeenCalled();
expect(createdHandler.mock.calls[0][0].data.indexType).toBe('hnsw');
});
});
// ==========================================================================
// Transactions Tests
// ==========================================================================
describe('Transactions', () => {
beforeEach(async () => {
await client.connect();
});
it('should commit successful transactions', async () => {
const result = await client.transaction(async (tx) => {
await tx.insert({
tableName: 'test',
vectors: [{ vector: randomVector(384) }],
});
await tx.update({
tableName: 'test',
id: '1',
vector: randomVector(384),
});
return { success: true };
});
expect(result.committed).toBe(true);
expect(result.data?.success).toBe(true);
expect(result.queryCount).toBe(2);
});
it('should rollback on error', async () => {
await expect(
client.transaction(async (tx) => {
await tx.insert({
tableName: 'test',
vectors: [{ vector: randomVector(384) }],
});
throw new Error('Simulated error');
})
).rejects.toThrow('Simulated error');
});
it('should support savepoints', async () => {
const result = await client.transaction(async (tx) => {
await tx.insert({
tableName: 'test',
vectors: [{ id: 'sp-1', vector: randomVector(384) }],
});
// Simulated savepoint - actual implementation would be more complex
try {
await tx.insert({
tableName: 'test',
vectors: [{ id: 'sp-2', vector: randomVector(384) }],
});
} catch {
// Rollback to savepoint would happen here
}
return { completed: true };
});
expect(result.committed).toBe(true);
});
it('should handle isolation levels', async () => {
const result = await client.transaction(async (tx) => {
// In a real implementation, we would set isolation level here
await tx.query('SELECT 1');
return true;
});
expect(result.committed).toBe(true);
});
it('should track transaction metrics', async () => {
const result = await client.transaction(async (tx) => {
for (let i = 0; i < 5; i++) {
await tx.query('SELECT 1');
}
return 'done';
});
expect(result.transactionId).toBeDefined();
expect(result.durationMs).toBeGreaterThanOrEqual(0);
expect(result.queryCount).toBe(5);
});
});
// ==========================================================================
// Performance Tests
// ==========================================================================
describe('Performance', () => {
beforeEach(async () => {
await client.connect();
});
it('should achieve target insert throughput', async () => {
const { result, durationMs } = await measureAsync(async () => {
return client.insert(
new InsertOptionsBuilder('perf_test')
.addRandomVectors(100, 384)
.build()
);
});
expect(result.successful).toBe(100);
expect(result.throughput).toBeGreaterThan(10); // At least 10 vectors/sec
});
it('should achieve target query latency', async () => {
const { durationMs } = await measureAsync(async () => {
return client.search(
new SearchOptionsBuilder(384).withK(10).build()
);
});
expect(durationMs).toBeLessThan(1000); // Less than 1 second
});
it('should handle concurrent queries', async () => {
const queries = Array.from({ length: 10 }, () =>
client.search(new SearchOptionsBuilder(384).withK(5).build())
);
const results = await Promise.all(queries);
expect(results).toHaveLength(10);
results.forEach((r) => {
expect(r).toHaveLength(5);
});
});
it('should measure batch search performance', async () => {
const { result, durationMs } = await measureAsync(async () => {
return client.batchSearch({
queries: randomVectors(20, 384),
k: 10,
metric: 'cosine',
});
});
expect(result.results).toHaveLength(20);
expect(result.avgDurationMs).toBeGreaterThan(0);
});
});
// ==========================================================================
// Health and Stats Tests
// ==========================================================================
describe('Health and Statistics', () => {
beforeEach(async () => {
await client.connect();
});
it('should report healthy status when connected', async () => {
const health = await client.healthCheck();
expect(health.status).toBe('healthy');
expect(health.components.database.healthy).toBe(true);
expect(health.issues).toHaveLength(0);
});
it('should report unhealthy status when disconnected', async () => {
await client.disconnect();
const health = await client.healthCheck();
expect(health.status).toBe('unhealthy');
expect(health.issues.length).toBeGreaterThan(0);
});
it('should return system statistics', async () => {
const stats = await client.getStats();
expect(stats.version).toBeDefined();
expect(stats.totalVectors).toBeGreaterThan(0);
expect(stats.queryStats).toBeDefined();
expect(stats.memoryStats).toBeDefined();
});
it('should analyze tables', async () => {
const analysis = await client.analyze('test_table');
expect(analysis.tableName).toBe('test_table');
expect(analysis.numRows).toBeGreaterThan(0);
expect(analysis.columnStats).toBeDefined();
expect(analysis.recommendations).toBeDefined();
});
it('should perform vacuum operation', async () => {
const vacuumHandler = vi.fn();
client.on('admin:vacuum', vacuumHandler);
await client.vacuum('test_table');
expect(vacuumHandler).toHaveBeenCalled();
});
});
// ==========================================================================
// Event System Tests
// ==========================================================================
describe('Event System', () => {
it('should register and unregister event handlers', async () => {
const handler = vi.fn();
const unsubscribe = client.on('connection:open', handler);
await client.connect();
expect(handler).toHaveBeenCalledTimes(1);
unsubscribe();
await client.disconnect();
await client.connect();
expect(handler).toHaveBeenCalledTimes(1);
});
it('should handle once events', async () => {
const handler = vi.fn();
client.once('connection:open', handler);
await client.connect();
await client.disconnect();
await client.connect();
expect(handler).toHaveBeenCalledTimes(1);
});
it('should remove all listeners', async () => {
const handler1 = vi.fn();
const handler2 = vi.fn();
client.on('connection:open', handler1);
client.on('connection:close', handler2);
client.removeAllListeners();
await client.connect();
await client.disconnect();
expect(handler1).not.toHaveBeenCalled();
expect(handler2).not.toHaveBeenCalled();
});
it('should remove listeners for specific event', async () => {
const openHandler = vi.fn();
const closeHandler = vi.fn();
client.on('connection:open', openHandler);
client.on('connection:close', closeHandler);
client.removeAllListeners('connection:open');
await client.connect();
await client.disconnect();
expect(openHandler).not.toHaveBeenCalled();
expect(closeHandler).toHaveBeenCalled();
});
});
// ==========================================================================
// Advanced Features Tests
// ==========================================================================
describe('Advanced Features', () => {
beforeEach(async () => {
await client.connect();
});
describe('Attention Mechanisms', () => {
it('should compute multi-head attention', async () => {
const seqLen = 10;
const dim = 64;
const input = {
query: randomVectors(seqLen, dim),
key: randomVectors(seqLen, dim),
value: randomVectors(seqLen, dim),
};
const config = {
mechanism: 'multi_head',
numHeads: 8,
headDim: 64,
};
const result = await client.computeAttention(input, config);
expect(result.output).toHaveLength(seqLen);
expect(result.output[0]).toHaveLength(config.numHeads * config.headDim);
});
it('should emit attention events', async () => {
const handler = vi.fn();
client.on('attention:computed', handler);
await client.computeAttention(
{
query: randomVectors(5, 32),
key: randomVectors(5, 32),
value: randomVectors(5, 32),
},
{ mechanism: 'multi_head', numHeads: 4, headDim: 32 }
);
expect(handler).toHaveBeenCalled();
});
});
describe('GNN Operations', () => {
it('should execute GCN layer', async () => {
const graph = createRandomGraph(50, 100, 64);
const result = await client.runGNNLayer(graph, {
type: 'gcn',
inputDim: 64,
outputDim: 32,
});
expect(result.nodeEmbeddings).toHaveLength(50);
expect(result.nodeEmbeddings[0]).toHaveLength(32);
});
it('should emit GNN events', async () => {
const handler = vi.fn();
client.on('gnn:forward', handler);
const graph = createRandomGraph(20, 40, 32);
await client.runGNNLayer(graph, {
type: 'gcn',
inputDim: 32,
outputDim: 16,
});
expect(handler).toHaveBeenCalled();
});
it('should build graph from node features and edges', () => {
const nodeFeatures = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
const edges: [number, number][] = [
[0, 1],
[1, 2],
[2, 0],
];
const graph = client.buildGraph(nodeFeatures, edges);
expect(graph.nodeFeatures).toEqual(nodeFeatures);
expect(graph.edgeIndex[0]).toEqual([0, 1, 2]);
expect(graph.edgeIndex[1]).toEqual([1, 2, 0]);
});
});
describe('Hyperbolic Embeddings', () => {
it('should embed points in Poincare ball', async () => {
const config = {
model: 'poincare',
curvature: -1,
dimension: 16,
};
const input = {
points: randomVectors(10, 16),
};
const result = await client.hyperbolicEmbed(input, config);
expect(result.embeddings).toHaveLength(10);
expect(result.curvature).toBe(-1);
// All points should be inside Poincare ball
result.embeddings.forEach((emb) => {
const norm = Math.sqrt(emb.reduce((sum, v) => sum + v * v, 0));
expect(norm).toBeLessThan(1);
});
});
it('should compute hyperbolic distance', async () => {
const config = {
model: 'poincare',
curvature: -1,
dimension: 8,
};
const a = [0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2];
const b = [0.3, 0.1, 0.3, 0.1, 0.3, 0.1, 0.3, 0.1];
const distance = await client.hyperbolicDistance(a, b, config);
expect(distance).toBeGreaterThanOrEqual(0);
expect(Number.isFinite(distance)).toBe(true);
});
it('should emit hyperbolic events', async () => {
const embedHandler = vi.fn();
const distanceHandler = vi.fn();
client.on('hyperbolic:embed', embedHandler);
client.on('hyperbolic:distance', distanceHandler);
const config = {
model: 'poincare',
curvature: -1,
dimension: 8,
};
await client.hyperbolicEmbed(
{ points: [[0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2]] },
config
);
await client.hyperbolicDistance(
[0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2],
[0.3, 0.1, 0.3, 0.1, 0.3, 0.1, 0.3, 0.1],
config
);
expect(embedHandler).toHaveBeenCalled();
expect(distanceHandler).toHaveBeenCalled();
});
});
describe('Embedding Operations', () => {
it('should embed single text', async () => {
const result = await client.embed('Hello, world!');
expect(result.embedding).toBeDefined();
expect(result.embedding).toHaveLength(384);
expect(result.tokenCount).toBeGreaterThan(0);
});
it('should embed batch of texts', async () => {
const texts = ['First text', 'Second text', 'Third text'];
const result = await client.embedBatch(texts);
expect(result.embeddings).toHaveLength(3);
expect(result.totalTokens).toBeGreaterThan(0);
expect(result.throughput).toBeGreaterThan(0);
});
});
});
});
// ============================================================================
// Edge Cases and Error Handling Tests
// ============================================================================
describe('Edge Cases and Error Handling', () => {
let client: MockRuVectorClient;
beforeEach(async () => {
client = new MockRuVectorClient(createTestClientOptions());
await client.connect();
});
afterEach(async () => {
await client.disconnect();
});
describe('Boundary Values', () => {
it('should handle empty vector search (k=0)', async () => {
const results = await client.search(
new SearchOptionsBuilder(384).withK(0).build()
);
expect(results).toHaveLength(0);
});
it('should handle large batch sizes', async () => {
const result = await client.batchSearch({
queries: randomVectors(100, 384),
k: 10,
metric: 'cosine',
});
expect(result.results).toHaveLength(100);
});
it('should handle high-dimensional vectors', async () => {
const results = await client.search(
new SearchOptionsBuilder(4096).withK(5).build()
);
expect(results.length).toBeGreaterThan(0);
});
it('should handle very small k values', async () => {
const results = await client.search(
new SearchOptionsBuilder(384).withK(1).build()
);
expect(results).toHaveLength(1);
});
});
describe('Empty/Null Cases', () => {
it('should handle empty batch insert', async () => {
const result = await client.insert({
tableName: 'test',
vectors: [],
});
expect(result.total).toBe(0);
expect(result.successful).toBe(0);
});
it('should handle graph with no edges', async () => {
const graph = client.buildGraph(
[[1, 2, 3], [4, 5, 6]],
[]
);
const result = await client.runGNNLayer(graph, {
type: 'gcn',
inputDim: 3,
outputDim: 2,
});
expect(result.nodeEmbeddings).toHaveLength(2);
});
});
describe('Concurrent Operations', () => {
it('should handle concurrent searches', async () => {
const searches = Array.from({ length: 10 }, () =>
client.search(new SearchOptionsBuilder(384).withK(5).build())
);
const results = await Promise.all(searches);
expect(results).toHaveLength(10);
results.forEach((r) => {
expect(r).toHaveLength(5);
});
});
it('should handle concurrent inserts', async () => {
const inserts = Array.from({ length: 5 }, (_, i) =>
client.insert({
tableName: 'test',
vectors: [
{
id: `concurrent-${i}`,
vector: randomVector(384),
},
],
})
);
const results = await Promise.all(inserts);
results.forEach((r) => {
expect(r.successful).toBe(1);
});
});
it('should handle mixed concurrent operations', async () => {
const operations = [
client.search(new SearchOptionsBuilder(384).withK(5).build()),
client.insert({
tableName: 'test',
vectors: [{ vector: randomVector(384) }],
}),
client.search(new SearchOptionsBuilder(384).withK(3).build()),
client.getStats(),
client.healthCheck(),
];
const results = await Promise.all(operations);
expect(results).toHaveLength(5);
});
});
});
// ============================================================================
// Real Database Tests (Only run when configured)
// ============================================================================
skipIfNoRealDB('Real Database Integration', () => {
// These tests would run against a real PostgreSQL database
// Implementation would be similar but using actual database connections
it('should connect to real PostgreSQL', async () => {
const config = getTestDatabaseConfig();
// Actual implementation would create real client and connect
expect(config.host).toBeDefined();
});
it('should create actual HNSW index', async () => {
// Would test real index creation
});
it('should perform actual vector search', async () => {
// Would test real search with pgvector
});
});