mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
Remove the nested v3/ subdirectory by moving all active source code, tests, configs, and assets directly to the project root. Archive v2/ (dead code, 847MB) to v2-archive.tar.gz. Key changes: - Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/ - Move v3/tsconfig.json, v3/vitest.config.ts to root - Merge v3/package.json into root package.json (ESM, scripts, deps, exports) - Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/ - Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes - Fix all init installer path resolution (3-level-up → 2-level-up) - Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe" - Update settings.json ADR/DDD directories to docs/implementation/ - Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks) - Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces - Update 15 guidance shards: v3/src/ → src/ path references - Fix sync interfaces: remove v3/ and v2/ path references - Fix test path calculations (process.cwd() + '..' no longer needed) - Fix flaky perf test threshold (500ms → 1000ms for CI environments) - Fix complexity-analyzer test to match ADR-051 keyword-based eligibility Build passes, 17,901 tests pass, CLI and MCP bundles verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
799 lines
27 KiB
TypeScript
799 lines
27 KiB
TypeScript
/**
|
|
* A2A Discovery Service Integration Tests
|
|
*
|
|
* Tests the discovery service with complete route handler flows.
|
|
* Uses mock HTTP request/response objects that are Express-compatible.
|
|
* Target: 15+ integration tests covering real handler flows.
|
|
*
|
|
* NOTE: These tests use mock request/response objects instead of actual HTTP
|
|
* requests to avoid requiring Express as a dependency. The route handlers
|
|
* are tested with the same interface they would receive from Express.
|
|
*
|
|
* @module tests/integration/adapters/a2a/discovery
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
|
|
|
|
import {
|
|
// Discovery Service
|
|
DiscoveryService,
|
|
createDiscoveryService,
|
|
getDiscoveryRouteDefinitions,
|
|
AuthenticatedRequest,
|
|
HttpRequest,
|
|
HttpResponse,
|
|
RouteDefinition,
|
|
|
|
// Agent Cards
|
|
QEAgentCard,
|
|
createAgentCardGenerator,
|
|
createAgentSkill,
|
|
DEFAULT_QE_PROVIDER,
|
|
DEFAULT_INPUT_MODES,
|
|
DEFAULT_OUTPUT_MODES,
|
|
} from '../../../../src/adapters/a2a/index.js';
|
|
|
|
// ============================================================================
|
|
// Mock HTTP Request/Response
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create a mock HTTP request
|
|
*/
|
|
function createMockRequest(options: {
|
|
params?: Record<string, string>;
|
|
query?: Record<string, string | string[] | undefined>;
|
|
headers?: Record<string, string | string[] | undefined>;
|
|
user?: { id: string; scopes?: string[] };
|
|
}): HttpRequest & { user?: { id: string; scopes?: string[] } } {
|
|
const { params = {}, query = {}, headers = {}, user } = options;
|
|
return {
|
|
params,
|
|
query,
|
|
headers,
|
|
header: (name: string) => {
|
|
const value = headers[name.toLowerCase()];
|
|
return Array.isArray(value) ? value[0] : value;
|
|
},
|
|
user,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Mock response that captures the response data
|
|
*/
|
|
interface MockResponseData {
|
|
statusCode: number;
|
|
body: unknown;
|
|
headers: Record<string, string | number>;
|
|
ended: boolean;
|
|
}
|
|
|
|
function createMockResponse(): HttpResponse & { _getData: () => MockResponseData } {
|
|
const data: MockResponseData = {
|
|
statusCode: 200,
|
|
body: null,
|
|
headers: {},
|
|
ended: false,
|
|
};
|
|
|
|
const res: HttpResponse & { _getData: () => MockResponseData } = {
|
|
status(code: number) {
|
|
data.statusCode = code;
|
|
return this;
|
|
},
|
|
json(body: unknown) {
|
|
data.body = body;
|
|
},
|
|
setHeader(name: string, value: string | number) {
|
|
data.headers[name] = value;
|
|
},
|
|
end() {
|
|
data.ended = true;
|
|
},
|
|
_getData: () => data,
|
|
};
|
|
|
|
return res;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Test Setup
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create a test agent card
|
|
*/
|
|
const createTestAgentCard = (id: string, options: Partial<QEAgentCard> = {}): QEAgentCard => ({
|
|
name: id,
|
|
description: `Test agent ${id} for integration testing`,
|
|
url: `https://example.com/a2a/${id}`,
|
|
version: '3.0.0',
|
|
capabilities: {
|
|
streaming: true,
|
|
pushNotifications: false,
|
|
stateTransitionHistory: true,
|
|
...options.capabilities,
|
|
},
|
|
skills: options.skills ?? [
|
|
{
|
|
id: `${id}-primary`,
|
|
name: `${id} Primary Skill`,
|
|
description: `Primary skill for ${id}`,
|
|
tags: ['testing', 'integration'],
|
|
},
|
|
],
|
|
provider: DEFAULT_QE_PROVIDER,
|
|
defaultInputModes: DEFAULT_INPUT_MODES,
|
|
defaultOutputModes: DEFAULT_OUTPUT_MODES,
|
|
qeMetadata: options.qeMetadata ?? {
|
|
domain: 'test-generation',
|
|
memoryReads: ['aqe/test/*'],
|
|
memoryWrites: ['aqe/results/*'],
|
|
},
|
|
...options,
|
|
});
|
|
|
|
/**
|
|
* Test service setup
|
|
*/
|
|
class TestServiceSetup {
|
|
public readonly discoveryService: DiscoveryService;
|
|
public readonly routes: RouteDefinition[];
|
|
|
|
constructor() {
|
|
const generator = createAgentCardGenerator({
|
|
baseUrl: 'http://localhost:3000',
|
|
});
|
|
|
|
this.discoveryService = createDiscoveryService({
|
|
generator,
|
|
baseUrl: 'http://localhost:3000',
|
|
cacheTtl: 60000, // 1 minute for tests
|
|
enableMetrics: true,
|
|
});
|
|
|
|
// Register test agents
|
|
const cards = new Map<string, QEAgentCard>();
|
|
cards.set('qe-test-architect', createTestAgentCard('qe-test-architect', {
|
|
skills: [
|
|
createAgentSkill('test-generation', 'Test Generation', 'Generate tests', {
|
|
tags: ['testing', 'ai', 'tdd'],
|
|
}),
|
|
createAgentSkill('property-testing', 'Property Testing', 'Property-based tests', {
|
|
tags: ['testing', 'property-based'],
|
|
}),
|
|
],
|
|
qeMetadata: { domain: 'test-generation' },
|
|
}));
|
|
|
|
cards.set('qe-coverage-specialist', createTestAgentCard('qe-coverage-specialist', {
|
|
capabilities: { streaming: true, pushNotifications: true, stateTransitionHistory: true },
|
|
skills: [
|
|
createAgentSkill('coverage-analysis', 'Coverage Analysis', 'Analyze coverage', {
|
|
tags: ['coverage', 'analysis'],
|
|
}),
|
|
],
|
|
qeMetadata: { domain: 'coverage-analysis' },
|
|
}));
|
|
|
|
cards.set('qe-security-scanner', createTestAgentCard('qe-security-scanner', {
|
|
capabilities: { streaming: false, pushNotifications: false, stateTransitionHistory: true },
|
|
skills: [
|
|
createAgentSkill('security-scan', 'Security Scan', 'OWASP scanning', {
|
|
tags: ['security', 'owasp', 'vulnerability'],
|
|
}),
|
|
],
|
|
qeMetadata: { domain: 'security-compliance' },
|
|
}));
|
|
|
|
this.discoveryService.registerCards(cards);
|
|
|
|
// Create mock auth middleware
|
|
const mockAuthMiddleware = (
|
|
req: HttpRequest,
|
|
res: HttpResponse,
|
|
next?: () => void
|
|
): void => {
|
|
const authHeader = req.header('Authorization');
|
|
|
|
if (authHeader?.startsWith('Bearer ')) {
|
|
const token = authHeader.slice(7);
|
|
if (token === 'valid-test-token') {
|
|
(req as AuthenticatedRequest).user = {
|
|
id: 'test-user',
|
|
scopes: ['agent:read', 'agent:extended'],
|
|
};
|
|
}
|
|
}
|
|
|
|
if (next) next();
|
|
};
|
|
|
|
// Get route definitions
|
|
this.routes = getDiscoveryRouteDefinitions({
|
|
discoveryService: this.discoveryService,
|
|
authMiddleware: mockAuthMiddleware,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Find and execute a route handler
|
|
*/
|
|
async executeRoute(
|
|
method: 'get' | 'post' | 'options',
|
|
path: string,
|
|
req: HttpRequest,
|
|
res: HttpResponse
|
|
): Promise<void> {
|
|
// Find matching route
|
|
const route = this.routes.find((r) => {
|
|
if (r.method !== method) return false;
|
|
|
|
// Simple path matching (handles :param patterns)
|
|
const routeParts = r.path.split('/');
|
|
const pathParts = path.split('?')[0].split('/');
|
|
|
|
if (routeParts.length !== pathParts.length) return false;
|
|
|
|
for (let i = 0; i < routeParts.length; i++) {
|
|
if (routeParts[i].startsWith(':')) {
|
|
// Extract param name and value
|
|
const paramName = routeParts[i].slice(1);
|
|
req.params[paramName] = pathParts[i];
|
|
} else if (routeParts[i] !== pathParts[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
if (!route) {
|
|
throw new Error(`No route found for ${method.toUpperCase()} ${path}`);
|
|
}
|
|
|
|
// Execute all handlers in sequence
|
|
for (const handler of route.handlers) {
|
|
let nextCalled = false;
|
|
await handler(req, res, () => {
|
|
nextCalled = true;
|
|
});
|
|
// If next wasn't called and response hasn't been sent, stop
|
|
if (!nextCalled && (res as unknown as { _getData: () => MockResponseData })._getData().body !== null) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Integration Tests
|
|
// ============================================================================
|
|
|
|
describe('A2A Discovery Integration Tests', () => {
|
|
let setup: TestServiceSetup;
|
|
|
|
beforeAll(() => {
|
|
setup = new TestServiceSetup();
|
|
});
|
|
|
|
// ============================================================================
|
|
// Platform Card Endpoint Tests
|
|
// ============================================================================
|
|
|
|
describe('GET /.well-known/agent.json', () => {
|
|
it('should return platform aggregate card', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(200);
|
|
expect((data.body as { name: string }).name).toBe('Agentic QE Platform');
|
|
expect((data.body as { skills: unknown[] }).skills).toBeDefined();
|
|
});
|
|
|
|
it('should return correct Content-Type header', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.headers['Content-Type']).toBe('application/json');
|
|
});
|
|
|
|
it('should return Cache-Control header', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.headers['Cache-Control']).toContain('public');
|
|
expect(data.headers['Cache-Control']).toContain('max-age=');
|
|
});
|
|
|
|
it('should return ETag header', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.headers['ETag']).toBeDefined();
|
|
expect(data.headers['ETag']).toMatch(/^"[a-f0-9]+"$/);
|
|
});
|
|
|
|
it('should support conditional GET with If-None-Match', async () => {
|
|
// First request to get ETag
|
|
const req1 = createMockRequest({});
|
|
const res1 = createMockResponse();
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req1, res1);
|
|
const etag = res1._getData().headers['ETag'];
|
|
|
|
// Second request with If-None-Match
|
|
const req2 = createMockRequest({
|
|
headers: { 'if-none-match': etag as string },
|
|
});
|
|
const res2 = createMockResponse();
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req2, res2);
|
|
|
|
expect(res2._getData().statusCode).toBe(304);
|
|
expect(res2._getData().ended).toBe(true);
|
|
});
|
|
|
|
it('should aggregate capabilities from all agents', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req, res);
|
|
|
|
const card = res._getData().body as { capabilities: { streaming: boolean; pushNotifications: boolean } };
|
|
expect(card.capabilities.streaming).toBe(true);
|
|
expect(card.capabilities.pushNotifications).toBe(true);
|
|
});
|
|
|
|
it('should aggregate skills from all agents', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req, res);
|
|
|
|
const card = res._getData().body as { skills: Array<{ id: string }> };
|
|
const skillIds = card.skills.map((s) => s.id);
|
|
expect(skillIds).toContain('test-generation');
|
|
expect(skillIds).toContain('coverage-analysis');
|
|
expect(skillIds).toContain('security-scan');
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Individual Agent Card Endpoint Tests
|
|
// ============================================================================
|
|
|
|
describe('GET /a2a/:agentId/.well-known/agent.json', () => {
|
|
it('should return individual agent card', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(200);
|
|
expect((data.body as { name: string }).name).toBe('qe-test-architect');
|
|
expect((data.body as { skills: unknown[] }).skills).toHaveLength(2);
|
|
});
|
|
|
|
it('should return 404 for unknown agent', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/unknown-agent/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(404);
|
|
|
|
const error = data.body as { error: { code: number; message: string; data: { agentId: string } } };
|
|
expect(error.error.code).toBe(-32001);
|
|
expect(error.error.message).toBe('Agent not found');
|
|
expect(error.error.data.agentId).toBe('unknown-agent');
|
|
});
|
|
|
|
it('should return correct Content-Type header', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
expect(res._getData().headers['Content-Type']).toBe('application/json');
|
|
});
|
|
|
|
it('should return ETag header', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
expect(res._getData().headers['ETag']).toBeDefined();
|
|
});
|
|
|
|
it('should support conditional GET', async () => {
|
|
const req1 = createMockRequest({ params: {} });
|
|
const res1 = createMockResponse();
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req1, res1);
|
|
const etag = res1._getData().headers['ETag'];
|
|
|
|
const req2 = createMockRequest({
|
|
params: {},
|
|
headers: { 'if-none-match': etag as string },
|
|
});
|
|
const res2 = createMockResponse();
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req2, res2);
|
|
|
|
expect(res2._getData().statusCode).toBe(304);
|
|
});
|
|
|
|
it('should include QE metadata', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
const card = res._getData().body as { qeMetadata: { domain: string } };
|
|
expect(card.qeMetadata).toBeDefined();
|
|
expect(card.qeMetadata.domain).toBe('test-generation');
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Extended Card Endpoint Tests
|
|
// ============================================================================
|
|
|
|
describe('GET /a2a/:agentId/.well-known/agent.json?extended=true', () => {
|
|
it('should return 401 without authentication', async () => {
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(401);
|
|
|
|
const error = data.body as { error: { code: number; message: string } };
|
|
expect(error.error.code).toBe(-32020);
|
|
expect(error.error.message).toContain('Authentication required');
|
|
});
|
|
|
|
it('should return extended card with valid token', async () => {
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
headers: { authorization: 'Bearer valid-test-token' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(200);
|
|
|
|
const card = data.body as { extended: { rateLimits: unknown } };
|
|
expect(card.extended).toBeDefined();
|
|
expect(card.extended.rateLimits).toBeDefined();
|
|
});
|
|
|
|
it('should include rate limits in extended card', async () => {
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
headers: { authorization: 'Bearer valid-test-token' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
const card = res._getData().body as {
|
|
extended: {
|
|
rateLimits: { requestsPerMinute: number; requestsPerHour: number; requestsPerDay: number };
|
|
};
|
|
};
|
|
expect(card.extended.rateLimits.requestsPerMinute).toBe(100);
|
|
expect(card.extended.rateLimits.requestsPerHour).toBe(1000);
|
|
expect(card.extended.rateLimits.requestsPerDay).toBe(10000);
|
|
});
|
|
|
|
it('should include SLA in extended card', async () => {
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
headers: { authorization: 'Bearer valid-test-token' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
const card = res._getData().body as { extended: { sla: { uptimeTarget: number } } };
|
|
expect(card.extended.sla).toBeDefined();
|
|
expect(card.extended.sla.uptimeTarget).toBe(99.9);
|
|
});
|
|
|
|
it('should return 404 for unknown agent even with auth', async () => {
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
headers: { authorization: 'Bearer valid-test-token' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/unknown-agent/.well-known/agent.json', req, res);
|
|
|
|
expect(res._getData().statusCode).toBe(404);
|
|
});
|
|
|
|
it('should have private Cache-Control for extended cards', async () => {
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
headers: { authorization: 'Bearer valid-test-token' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
expect(res._getData().headers['Cache-Control']).toContain('private');
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Search Endpoint Tests
|
|
// ============================================================================
|
|
|
|
describe('GET /a2a/agents', () => {
|
|
it('should list all agents', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(200);
|
|
|
|
const result = data.body as { agents: unknown[]; total: number };
|
|
expect(result.agents).toBeDefined();
|
|
expect(result.total).toBe(3);
|
|
});
|
|
|
|
it('should filter by skill', async () => {
|
|
const req = createMockRequest({
|
|
query: { skill: 'test-generation' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents', req, res);
|
|
|
|
const result = res._getData().body as { agents: Array<{ skills: string[] }> };
|
|
expect(result.agents.every((a) => a.skills.includes('test-generation'))).toBe(true);
|
|
});
|
|
|
|
it('should filter by tag', async () => {
|
|
const req = createMockRequest({
|
|
query: { tag: 'security' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents', req, res);
|
|
|
|
const result = res._getData().body as { agents: Array<{ name: string }>; total: number };
|
|
expect(result.total).toBe(1);
|
|
expect(result.agents[0].name).toBe('qe-security-scanner');
|
|
});
|
|
|
|
it('should filter by domain', async () => {
|
|
const req = createMockRequest({
|
|
query: { domain: 'coverage-analysis' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents', req, res);
|
|
|
|
const result = res._getData().body as { agents: Array<{ name: string }>; total: number };
|
|
expect(result.total).toBe(1);
|
|
expect(result.agents[0].name).toBe('qe-coverage-specialist');
|
|
});
|
|
|
|
it('should filter by streaming capability', async () => {
|
|
const req = createMockRequest({
|
|
query: { streaming: 'false' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents', req, res);
|
|
|
|
const result = res._getData().body as { agents: Array<{ name: string }>; total: number };
|
|
expect(result.total).toBe(1);
|
|
expect(result.agents[0].name).toBe('qe-security-scanner');
|
|
});
|
|
|
|
it('should apply limit', async () => {
|
|
const req = createMockRequest({
|
|
query: { limit: '2' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents', req, res);
|
|
|
|
const result = res._getData().body as { agents: unknown[]; total: number };
|
|
expect(result.agents).toHaveLength(2);
|
|
expect(result.total).toBe(3); // Total without limit
|
|
});
|
|
|
|
it('should return correct Content-Type', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents', req, res);
|
|
|
|
expect(res._getData().headers['Content-Type']).toBe('application/json');
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// CORS Tests
|
|
// ============================================================================
|
|
|
|
describe('CORS Support', () => {
|
|
it('should return CORS headers on platform card', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/.well-known/agent.json', req, res);
|
|
|
|
expect(res._getData().headers['Access-Control-Allow-Origin']).toBe('*');
|
|
});
|
|
|
|
it('should return CORS headers on agent card', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
expect(res._getData().headers['Access-Control-Allow-Origin']).toBe('*');
|
|
});
|
|
|
|
it('should handle OPTIONS preflight request', async () => {
|
|
const req = createMockRequest({});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('options', '/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(204);
|
|
expect(data.headers['Access-Control-Allow-Methods']).toContain('GET');
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Metrics Integration Tests
|
|
// ============================================================================
|
|
|
|
describe('Access Metrics', () => {
|
|
it('should track access to agent cards', async () => {
|
|
const initialMetrics = setup.discoveryService.getAccessMetrics('qe-coverage-specialist');
|
|
const initialCount = initialMetrics?.accessCount ?? 0;
|
|
|
|
// Make requests
|
|
const req1 = createMockRequest({ params: {} });
|
|
const res1 = createMockResponse();
|
|
await setup.executeRoute('get', '/a2a/qe-coverage-specialist/.well-known/agent.json', req1, res1);
|
|
|
|
const req2 = createMockRequest({ params: {} });
|
|
const res2 = createMockResponse();
|
|
await setup.executeRoute('get', '/a2a/qe-coverage-specialist/.well-known/agent.json', req2, res2);
|
|
|
|
const metrics = setup.discoveryService.getAccessMetrics('qe-coverage-specialist');
|
|
expect(metrics).not.toBeNull();
|
|
expect(metrics!.accessCount).toBe(initialCount + 2);
|
|
});
|
|
|
|
it('should track extended card access separately', async () => {
|
|
const initialMetrics = setup.discoveryService.getAccessMetrics('qe-test-architect');
|
|
const initialExtendedCount = initialMetrics?.extendedAccessCount ?? 0;
|
|
|
|
// Make extended request
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
headers: { authorization: 'Bearer valid-test-token' },
|
|
});
|
|
const res = createMockResponse();
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
const metrics = setup.discoveryService.getAccessMetrics('qe-test-architect');
|
|
expect(metrics!.extendedAccessCount).toBe(initialExtendedCount + 1);
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Error Handling Tests
|
|
// ============================================================================
|
|
|
|
describe('Error Handling', () => {
|
|
it('should return JSON error for 404', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/nonexistent/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(404);
|
|
|
|
const error = data.body as { error: { code: number } };
|
|
expect(error.error).toBeDefined();
|
|
expect(error.error.code).toBe(-32001);
|
|
});
|
|
|
|
it('should return JSON error for 401', async () => {
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/qe-test-architect/.well-known/agent.json', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(401);
|
|
|
|
const error = data.body as { error: { code: number } };
|
|
expect(error.error).toBeDefined();
|
|
expect(error.error.code).toBe(-32020);
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Agent Card Alias Tests
|
|
// ============================================================================
|
|
|
|
describe('GET /a2a/agents/:agentId', () => {
|
|
it('should return agent card via alias endpoint', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents/qe-test-architect', req, res);
|
|
|
|
const data = res._getData();
|
|
expect(data.statusCode).toBe(200);
|
|
expect((data.body as { name: string }).name).toBe('qe-test-architect');
|
|
});
|
|
|
|
it('should return 404 for unknown agent via alias', async () => {
|
|
const req = createMockRequest({ params: {} });
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents/unknown-agent', req, res);
|
|
|
|
expect(res._getData().statusCode).toBe(404);
|
|
});
|
|
|
|
it('should support extended card via alias', async () => {
|
|
const req = createMockRequest({
|
|
params: {},
|
|
query: { extended: 'true' },
|
|
headers: { authorization: 'Bearer valid-test-token' },
|
|
});
|
|
const res = createMockResponse();
|
|
|
|
await setup.executeRoute('get', '/a2a/agents/qe-test-architect', req, res);
|
|
|
|
const card = res._getData().body as { extended: unknown };
|
|
expect(card.extended).toBeDefined();
|
|
});
|
|
});
|
|
});
|