This commit is contained in:
Oleg Maslov
2026-09-02 10:14:22 +02:00
parent 0c3e2ead3b
commit b20b138fe4
771 changed files with 161561 additions and 9027 deletions

View File

@@ -1,6 +1,7 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { BaseConnector, type ConnectorAction, type ConnectorResult, type WaggleConnector } from '../src/connector-sdk.js';
import { ConnectorRegistry, type AuditLogger } from '../src/connector-registry.js';
import { needsConfirmationWithAutonomy } from '../src/confirmation.js';
import type { VaultStore } from '@waggle/core';
import type { ConnectorHealth, ConnectorStatus } from '@waggle/shared';
@@ -82,6 +83,14 @@ function createMockVault(credentials: Record<string, { value: string; isExpired:
} as unknown as VaultStore;
}
async function registerAndHydrate(
registry: ConnectorRegistry,
connector: WaggleConnector,
): Promise<void> {
registry.register(connector);
expect(await registry.hydrate(connector.id)).toBe(true);
}
// ─── WaggleConnector Interface ───────────────────────────────────────────
describe('WaggleConnector interface', () => {
@@ -155,42 +164,42 @@ describe('ConnectorRegistry', () => {
expect(registry.getAll()).toHaveLength(2);
});
it('getConnected() returns only connectors with valid vault credentials', () => {
it('getConnected() returns only connectors with valid vault credentials', async () => {
vault = createMockVault({ mock: { value: 'token123', isExpired: false } });
registry = new ConnectorRegistry(vault);
registry.register(new MockConnector());
await registerAndHydrate(registry, new MockConnector());
const connected = registry.getConnected();
expect(connected).toHaveLength(1);
expect(connected[0].id).toBe('mock');
});
it('getConnected() excludes connectors with expired credentials', () => {
it('getConnected() excludes connectors with expired credentials', async () => {
vault = createMockVault({ mock: { value: 'token123', isExpired: true } });
registry = new ConnectorRegistry(vault);
registry.register(new MockConnector());
await registerAndHydrate(registry, new MockConnector());
expect(registry.getConnected()).toHaveLength(0);
});
it('getConnected() excludes connectors without credentials', () => {
registry.register(new MockConnector());
it('getConnected() excludes connectors without credentials', async () => {
await registerAndHydrate(registry, new MockConnector());
expect(registry.getConnected()).toHaveLength(0);
});
it('generateTools() returns ToolDefinition[] only for connected connectors', () => {
it('generateTools() returns ToolDefinition[] only for connected connectors', async () => {
vault = createMockVault({ mock: { value: 'token123', isExpired: false } });
registry = new ConnectorRegistry(vault);
registry.register(new MockConnector());
await registerAndHydrate(registry, new MockConnector());
const tools = registry.generateTools();
expect(tools).toHaveLength(3); // 3 actions = 3 tools
});
it('generateTools() creates tools named connector_<id>_<action>', () => {
it('generateTools() creates tools named connector_<id>_<action>', async () => {
vault = createMockVault({ mock: { value: 'token123', isExpired: false } });
registry = new ConnectorRegistry(vault);
registry.register(new MockConnector());
await registerAndHydrate(registry, new MockConnector());
const tools = registry.generateTools();
const names = tools.map(t => t.name);
@@ -201,8 +210,37 @@ describe('ConnectorRegistry', () => {
]);
});
it('generateTools() returns empty array for disconnected connectors', () => {
registry.register(new MockConnector());
it('propagates trusted action risk so declared-high actions stay gated at YOLO', async () => {
vault = createMockVault({ mock: { value: 'token123', isExpired: false } });
registry = new ConnectorRegistry(vault);
const connector = new MockConnector();
connector.actions.splice(0, connector.actions.length,
{
name: 'read_action',
description: 'Read harmless data',
inputSchema: {},
riskLevel: 'low',
},
{
name: 'execute_action',
description: 'Execute a provider action',
inputSchema: {},
riskLevel: 'high',
},
);
await registerAndHydrate(registry, connector);
const tools = registry.generateTools();
const readTool = tools.find(tool => tool.name === 'connector_mock_read_action')!;
const executeTool = tools.find(tool => tool.name === 'connector_mock_execute_action')!;
expect(needsConfirmationWithAutonomy(executeTool.name, {}, 'yolo', executeTool.riskLevel)).toBe(true);
expect(needsConfirmationWithAutonomy(readTool.name, {}, 'yolo', readTool.riskLevel)).toBe(false);
expect(tools.map(tool => tool.riskLevel)).toEqual(['low', 'high']);
});
it('generateTools() returns empty array for disconnected connectors', async () => {
await registerAndHydrate(registry, new MockConnector());
expect(registry.generateTools()).toEqual([]);
});
@@ -229,10 +267,10 @@ describe('ConnectorRegistry', () => {
expect(registry.getAll()).toHaveLength(0);
});
it('getDefinitions() returns definitions with live status', () => {
it('getDefinitions() returns definitions with live status', async () => {
vault = createMockVault({ mock: { value: 'tok', isExpired: false } });
registry = new ConnectorRegistry(vault);
registry.register(new MockConnector());
await registerAndHydrate(registry, new MockConnector());
const defs = registry.getDefinitions();
expect(defs).toHaveLength(1);
@@ -248,7 +286,7 @@ describe('Dynamic tool generation', () => {
const vault = createMockVault({ mock: { value: 'tok', isExpired: false } });
const registry = new ConnectorRegistry(vault);
const connector = new MockConnector();
registry.register(connector);
await registerAndHydrate(registry, connector);
const tools = registry.generateTools();
const listTool = tools.find(t => t.name === 'connector_mock_list_items')!;
@@ -260,10 +298,10 @@ describe('Dynamic tool generation', () => {
expect(parsed.data.params).toEqual({ limit: 10 });
});
it('tool input_schema matches ConnectorAction.inputSchema', () => {
it('tool input_schema matches ConnectorAction.inputSchema', async () => {
const vault = createMockVault({ mock: { value: 'tok', isExpired: false } });
const registry = new ConnectorRegistry(vault);
registry.register(new MockConnector());
await registerAndHydrate(registry, new MockConnector());
const tools = registry.generateTools();
const listTool = tools.find(t => t.name === 'connector_mock_list_items')!;
@@ -273,12 +311,13 @@ describe('Dynamic tool generation', () => {
});
});
it('tool parameters do NOT include _connectorMeta (security: prevents LLM injection)', () => {
it('tool parameters do NOT include _connectorMeta (security: prevents LLM injection)', async () => {
const vault = createMockVault({ mock: { value: 'tok', isExpired: false } });
const registry = new ConnectorRegistry(vault);
registry.register(new MockConnector());
await registerAndHydrate(registry, new MockConnector());
const tools = registry.generateTools();
expect(tools).toHaveLength(3);
// No tool should have _connectorMeta in its schema (risk is determined by tool name, not args)
for (const tool of tools) {
expect(tool.parameters._connectorMeta).toBeUndefined();
@@ -290,7 +329,7 @@ describe('Dynamic tool generation', () => {
const auditLog = vi.fn();
const auditLogger: AuditLogger = { log: auditLog };
const registry = new ConnectorRegistry(vault, auditLogger);
registry.register(new MockConnector());
await registerAndHydrate(registry, new MockConnector());
const tools = registry.generateTools();
const createTool = tools.find(t => t.name === 'connector_mock_create_item')!;
@@ -310,7 +349,7 @@ describe('Dynamic tool generation', () => {
// Create a connector that throws
const connector = new MockConnector();
connector.execute = async () => { throw new Error('API timeout'); };
registry.register(connector);
await registerAndHydrate(registry, connector);
const tools = registry.generateTools();
const listTool = tools.find(t => t.name === 'connector_mock_list_items')!;
@@ -323,7 +362,7 @@ describe('Dynamic tool generation', () => {
const vault = createMockVault({ mock: { value: 'tok', isExpired: false } });
const registry = new ConnectorRegistry(vault);
const connector = new MockConnector();
registry.register(connector);
await registerAndHydrate(registry, connector);
const tools = registry.generateTools();
const createTool = tools.find(t => t.name === 'connector_mock_create_item')!;