Files
ruvnet__ruflo/plugins/ruflo-graph-intelligence/tests/adapter-registry.test.ts
ruv 126c23a0fc feat(graph-intelligence): #2044 ADR-123 Phase 1 — ruflo-graph-intelligence plugin foundation
Scaffolds plugins/ruflo-graph-intelligence/ — the RuFlo Graph Intelligence
Engine. Built on sublinear-time-solver@^1.7.0. Three-layer architecture:
Neural / Graph Intelligence / Complexity.

Phase 1 deliverables:
- package.json @ 0.1.0-alpha.1
- domain/types.ts — SparseMatrix, ComplexityClass (12 tiers + fitsBudget +
  isEdgeSafe), CoherenceReport, PageRankQuery/Result, SolveQuery/Result,
  SolveOnChangeQuery, structured GraphIntelError taxonomy
- domain/adapter.ts — SublinearAdapter interface, AdapterRegistry (singleton)
- infrastructure/solver-bridge.ts — coherenceScore, singleEntryPageRank
  (forward-push), conjugateGradient, neumann, solveOnChange, hashResult,
  observedComplexity, runPageRank/runSolve/runSolveOnChange entry points
- mcp-tools/index.ts — 6 MCP tools under sublinear/*:
  page-rank-entry / solve / solve-on-change / analyze / feasibility / jl-embed
  All threaded with maxComplexityClass + coherenceThreshold budget gates.
- tests: 37/37 passing — solver-bridge (coherence, PR, CG, Neumann,
  solve-on-change, hash determinism), adapter-registry (CRUD + singleton),
  mcp-tools (happy path, graph-not-found, coherence-rejected,
  complexity-budget-exceeded)

CI guard (#2044 follow-up):
- .github/workflows/v3-ci.yml — adds plugins/ruflo-graph-intelligence/**
  to both push and pull_request path filters
- New job: ruflo-graph-intelligence build + test smoke. Installs (npm ci
  with --legacy-peer-deps because the plugin is outside the v3 pnpm
  workspace), typechecks, builds, runs vitest. Cross-platform via Node 22.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-18 21:44:51 -04:00

70 lines
1.9 KiB
TypeScript

/**
* ruflo-graph-intelligence — Adapter Registry Tests (ADR-123 Phase 1)
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { AdapterRegistry, getRegistry, resetRegistry } from '../src/domain/adapter.js';
import type { SublinearAdapter } from '../src/domain/adapter.js';
import type { SparseMatrix } from '../src/domain/types.js';
function fakeAdapter(graphId: string): SublinearAdapter {
return {
graphId,
ownerPlugin: 'test',
async exportAsSparseMatrix(): Promise<SparseMatrix> {
return {
graphId,
size: 1,
entries: [{ row: 0, col: 0, value: 1 }],
nodeIndex: { only: 0 },
indexNode: ['only'],
capturedAt: 't',
};
},
};
}
describe('AdapterRegistry', () => {
beforeEach(() => resetRegistry());
it('registers + retrieves an adapter', () => {
const r = new AdapterRegistry();
const a = fakeAdapter('test:graph');
r.register(a);
expect(r.get('test:graph')).toBe(a);
});
it('throws on duplicate registration', () => {
const r = new AdapterRegistry();
r.register(fakeAdapter('test:graph'));
expect(() => r.register(fakeAdapter('test:graph'))).toThrow(/already registered/);
});
it('unregister removes the adapter', () => {
const r = new AdapterRegistry();
r.register(fakeAdapter('test:graph'));
expect(r.unregister('test:graph')).toBe(true);
expect(r.get('test:graph')).toBeUndefined();
});
it('list returns all adapters', () => {
const r = new AdapterRegistry();
r.register(fakeAdapter('a'));
r.register(fakeAdapter('b'));
expect(r.list()).toHaveLength(2);
});
it('getRegistry returns a singleton', () => {
const r1 = getRegistry();
const r2 = getRegistry();
expect(r1).toBe(r2);
});
it('resetRegistry forces a new instance', () => {
const r1 = getRegistry();
resetRegistry();
const r2 = getRegistry();
expect(r1).not.toBe(r2);
});
});