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

360 lines
12 KiB
TypeScript

/**
* DiscoveryService Tests
*
* Tests the actual DiscoveryService implementation against the real API.
* Uses simple dependency injection for signManifest/verifyManifest,
* and vi.fn() only for the onPeerDiscovered callback.
*/
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import {
DiscoveryService,
type DiscoveryServiceDeps,
type FederationManifest,
} from '../../src/domain/services/discovery-service.js';
import { TrustLevel } from '../../src/domain/entities/trust-level.js';
function makeManifest(overrides: Partial<FederationManifest> = {}): FederationManifest {
return {
nodeId: 'node-1',
publicKey: 'pk-1',
endpoint: 'https://peer1.example.com',
capabilities: {
agentTypes: ['coder'],
maxConcurrentSessions: 5,
supportedProtocols: ['websocket', 'http'],
complianceModes: ['hipaa'],
},
version: '1.0.0',
signature: 'valid',
timestamp: new Date().toISOString(),
...overrides,
};
}
function createDeps(overrides: Partial<DiscoveryServiceDeps> = {}): DiscoveryServiceDeps {
return {
signManifest: async () => 'test-signature',
verifyManifest: async (m) => m.signature === 'valid',
...overrides,
};
}
describe('DiscoveryService', () => {
let service: DiscoveryService;
let deps: DiscoveryServiceDeps;
beforeEach(() => {
deps = createDeps();
service = new DiscoveryService(deps);
});
afterEach(() => {
service.stopPeriodicDiscovery();
});
// ---------- publishManifest ----------
describe('publishManifest', () => {
it('should sign the manifest using the signManifest dep and return it with signature', async () => {
const input = {
nodeId: 'local-node',
publicKey: 'local-pk',
endpoint: 'https://local.example.com',
capabilities: {
agentTypes: ['coder', 'tester'],
maxConcurrentSessions: 10,
supportedProtocols: ['websocket'] as readonly string[],
complianceModes: [] as readonly string[],
},
version: '2.0.0',
timestamp: new Date().toISOString(),
};
const result = await service.publishManifest(input);
expect(result.signature).toBe('test-signature');
expect(result.nodeId).toBe('local-node');
expect(result.publicKey).toBe('local-pk');
expect(result.version).toBe('2.0.0');
});
it('should store the manifest so getLocalManifest returns it', async () => {
const input = {
nodeId: 'local-node',
publicKey: 'local-pk',
endpoint: 'https://local.example.com',
capabilities: {
agentTypes: [] as readonly string[],
maxConcurrentSessions: 1,
supportedProtocols: ['http'] as readonly string[],
complianceModes: [] as readonly string[],
},
version: '1.0.0',
timestamp: new Date().toISOString(),
};
await service.publishManifest(input);
const stored = service.getLocalManifest();
expect(stored).not.toBeNull();
expect(stored!.nodeId).toBe('local-node');
expect(stored!.signature).toBe('test-signature');
});
});
// ---------- getLocalManifest ----------
describe('getLocalManifest', () => {
it('should return null before any manifest is published', () => {
expect(service.getLocalManifest()).toBeNull();
});
it('should return the manifest after publishing', async () => {
await service.publishManifest({
nodeId: 'n',
publicKey: 'pk',
endpoint: 'https://n.example.com',
capabilities: {
agentTypes: [] as readonly string[],
maxConcurrentSessions: 1,
supportedProtocols: [] as readonly string[],
complianceModes: [] as readonly string[],
},
version: '1.0.0',
timestamp: new Date().toISOString(),
});
const manifest = service.getLocalManifest();
expect(manifest).not.toBeNull();
expect(manifest!.nodeId).toBe('n');
});
});
// ---------- addStaticPeer ----------
describe('addStaticPeer', () => {
it('should add a peer with UNTRUSTED trust level when no manifest is provided', async () => {
const node = await service.addStaticPeer('https://peer1.example.com');
expect(node).toBeDefined();
expect(node.endpoint).toBe('https://peer1.example.com');
expect(node.trustLevel).toBe(TrustLevel.UNTRUSTED);
});
it('should add a peer with VERIFIED trust level when a valid manifest is provided', async () => {
const manifest = makeManifest({ signature: 'valid' });
const node = await service.addStaticPeer('https://peer1.example.com', manifest);
expect(node.trustLevel).toBe(TrustLevel.VERIFIED);
expect(node.nodeId).toBe('node-1');
});
it('should throw when provided an invalid manifest', async () => {
const manifest = makeManifest({ signature: 'invalid' });
await expect(
service.addStaticPeer('https://peer1.example.com', manifest),
).rejects.toThrow(/invalid manifest/i);
});
it('should mark existing peer as seen and return it without creating a duplicate', async () => {
const manifest = makeManifest({ signature: 'valid' });
const first = await service.addStaticPeer('https://peer1.example.com', manifest);
const firstLastSeen = first.lastSeen;
// Small delay to ensure lastSeen changes
await new Promise((r) => setTimeout(r, 5));
const second = await service.addStaticPeer('https://peer1.example.com', manifest);
expect(second.nodeId).toBe(first.nodeId);
expect(second.lastSeen.getTime()).toBeGreaterThanOrEqual(firstLastSeen.getTime());
expect(service.listPeers()).toHaveLength(1);
});
it('should fire onPeerDiscovered callback when adding a new peer', async () => {
const onPeerDiscovered = vi.fn();
const svc = new DiscoveryService(createDeps({ onPeerDiscovered }));
await svc.addStaticPeer('https://peer1.example.com');
expect(onPeerDiscovered).toHaveBeenCalledTimes(1);
expect(onPeerDiscovered.mock.calls[0][0].endpoint).toBe('https://peer1.example.com');
});
it('should NOT fire onPeerDiscovered for duplicate adds', async () => {
const onPeerDiscovered = vi.fn();
const svc = new DiscoveryService(createDeps({ onPeerDiscovered }));
await svc.addStaticPeer('https://peer1.example.com');
await svc.addStaticPeer('https://peer1.example.com');
expect(onPeerDiscovered).toHaveBeenCalledTimes(1);
});
});
// ---------- discoverPeers ----------
describe('discoverPeers', () => {
it('should return an empty array when no static peers are configured', async () => {
const result = await service.discoverPeers();
expect(result).toEqual([]);
});
it('should discover nodes from staticPeers config', async () => {
const svc = new DiscoveryService(deps, {
staticPeers: ['https://a.example.com', 'https://b.example.com'],
});
const discovered = await svc.discoverPeers();
expect(discovered).toHaveLength(2);
const endpoints = discovered.map((n) => n.endpoint);
expect(endpoints).toContain('https://a.example.com');
expect(endpoints).toContain('https://b.example.com');
});
it('should fire onPeerDiscovered for each newly discovered peer', async () => {
const onPeerDiscovered = vi.fn();
const svc = new DiscoveryService(createDeps({ onPeerDiscovered }), {
staticPeers: ['https://a.example.com', 'https://b.example.com'],
});
await svc.discoverPeers();
expect(onPeerDiscovered).toHaveBeenCalledTimes(2);
});
it('should skip peers that are already known (no duplicates)', async () => {
const svc = new DiscoveryService(deps, {
staticPeers: ['https://a.example.com'],
});
const first = await svc.discoverPeers();
expect(first).toHaveLength(1);
const second = await svc.discoverPeers();
expect(second).toHaveLength(0);
expect(svc.listPeers()).toHaveLength(1);
});
});
// ---------- removePeer ----------
describe('removePeer', () => {
it('should remove a known peer and return true', async () => {
const node = await service.addStaticPeer('https://peer1.example.com');
const removed = service.removePeer(node.nodeId);
expect(removed).toBe(true);
expect(service.listPeers()).toHaveLength(0);
});
it('should return false when removing a non-existent peer', () => {
const removed = service.removePeer('non-existent-id');
expect(removed).toBe(false);
});
});
// ---------- getPeer ----------
describe('getPeer', () => {
it('should return undefined for an unknown nodeId', () => {
expect(service.getPeer('unknown')).toBeUndefined();
});
it('should return the node for a known nodeId', async () => {
const node = await service.addStaticPeer('https://peer1.example.com');
const retrieved = service.getPeer(node.nodeId);
expect(retrieved).toBeDefined();
expect(retrieved!.nodeId).toBe(node.nodeId);
});
});
// ---------- listPeers ----------
describe('listPeers', () => {
it('should return an empty array when no peers exist', () => {
expect(service.listPeers()).toEqual([]);
});
it('should return all added peers', async () => {
await service.addStaticPeer('https://a.example.com');
await service.addStaticPeer('https://b.example.com');
expect(service.listPeers()).toHaveLength(2);
});
});
// ---------- listActivePeers ----------
describe('listActivePeers', () => {
it('should return only peers within the stale threshold', async () => {
// Use a very short stale threshold so we can test staleness
const svc = new DiscoveryService(deps, { staleThresholdMs: 50 });
await svc.addStaticPeer('https://a.example.com');
// Immediately, peer should be active
expect(svc.listActivePeers()).toHaveLength(1);
// Wait for it to become stale
await new Promise((r) => setTimeout(r, 60));
expect(svc.listActivePeers()).toHaveLength(0);
});
});
// ---------- pruneStale ----------
describe('pruneStale', () => {
it('should remove stale peers and return their IDs', async () => {
const svc = new DiscoveryService(deps, { staleThresholdMs: 50 });
const node = await svc.addStaticPeer('https://a.example.com');
// Wait for it to become stale
await new Promise((r) => setTimeout(r, 60));
const pruned = svc.pruneStale();
expect(pruned).toContain(node.nodeId);
expect(svc.listPeers()).toHaveLength(0);
});
it('should return an empty array when no peers are stale', async () => {
await service.addStaticPeer('https://a.example.com');
const pruned = service.pruneStale();
expect(pruned).toEqual([]);
expect(service.listPeers()).toHaveLength(1);
});
});
// ---------- startPeriodicDiscovery / stopPeriodicDiscovery ----------
describe('periodic discovery', () => {
it('should start periodic discovery without throwing', () => {
const svc = new DiscoveryService(deps, { discoveryIntervalMs: 100_000 });
expect(() => svc.startPeriodicDiscovery()).not.toThrow();
svc.stopPeriodicDiscovery();
});
it('should stop periodic discovery without throwing', () => {
const svc = new DiscoveryService(deps, { discoveryIntervalMs: 100_000 });
svc.startPeriodicDiscovery();
expect(() => svc.stopPeriodicDiscovery()).not.toThrow();
});
it('should not create multiple timers when called multiple times', () => {
const svc = new DiscoveryService(deps, { discoveryIntervalMs: 100_000 });
svc.startPeriodicDiscovery();
svc.startPeriodicDiscovery(); // second call is a no-op
svc.stopPeriodicDiscovery();
// No error means the guard worked
});
});
});