Files
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
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>
2026-03-01 11:35:41 +00:00

375 lines
12 KiB
TypeScript

/**
* Agentic QE v3 - Contract Validator Unit Tests
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { ContractValidatorService } from '../../../../src/domains/contract-testing/services/contract-validator';
import type { MemoryBackend } from '../../../../src/kernel/interfaces';
import type { ApiContract, SchemaDefinition } from '../../../../src/domains/contract-testing/interfaces';
import { Version } from '../../../../src/shared/value-objects';
// Mock MemoryBackend
function createMockMemoryBackend(): MemoryBackend {
return {
initialize: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn().mockResolvedValue(undefined),
set: vi.fn().mockResolvedValue(undefined),
get: vi.fn().mockResolvedValue(undefined),
delete: vi.fn().mockResolvedValue(true),
has: vi.fn().mockResolvedValue(false),
search: vi.fn().mockResolvedValue([]),
vectorSearch: vi.fn().mockResolvedValue([]),
storeVector: vi.fn().mockResolvedValue(undefined),
};
}
// Factory for creating test contracts
function createTestContract(overrides: Partial<ApiContract> = {}): ApiContract {
return {
id: 'test-contract-1',
name: 'Test Contract',
version: Version.create(1, 0, 0),
type: 'rest',
provider: { name: 'UserService', version: '1.0.0', team: 'platform' },
consumers: [{ name: 'WebApp', version: '2.0.0' }],
endpoints: [
{
path: '/users',
method: 'GET',
requestSchema: 'get-users-request',
responseSchema: 'get-users-response',
examples: [
{ name: 'success', request: {}, response: { users: [] }, statusCode: 200 },
],
},
],
schemas: [
{
id: 'get-users-request',
name: 'GetUsersRequest',
type: 'json-schema',
content: JSON.stringify({ type: 'object', properties: {} }),
},
{
id: 'get-users-response',
name: 'GetUsersResponse',
type: 'json-schema',
content: JSON.stringify({
type: 'object',
properties: { users: { type: 'array', items: { type: 'object' } } },
}),
},
],
...overrides,
};
}
describe('ContractValidatorService', () => {
let service: ContractValidatorService;
let mockMemory: MemoryBackend;
beforeEach(() => {
mockMemory = createMockMemoryBackend();
service = new ContractValidatorService({ memory: mockMemory });
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('validateContract', () => {
it('should validate a valid contract successfully', async () => {
const contract = createTestContract();
const result = await service.validateContract(contract);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(true);
expect(result.value.errors).toHaveLength(0);
}
});
it('should return errors for contract with missing id', async () => {
const contract = createTestContract({ id: '' });
const result = await service.validateContract(contract);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.some((e) => e.path === 'id')).toBe(true);
}
});
it('should return errors for contract with missing name', async () => {
const contract = createTestContract({ name: '' });
const result = await service.validateContract(contract);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.some((e) => e.code === 'REQUIRED_FIELD')).toBe(true);
}
});
it('should return errors for empty endpoints in strict mode', async () => {
const strictService = new ContractValidatorService({ memory: mockMemory }, { strictMode: true });
const contract = createTestContract({ endpoints: [] });
const result = await strictService.validateContract(contract);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.some((e) => e.code === 'EMPTY_ENDPOINTS')).toBe(true);
}
});
it('should return warning for empty endpoints in non-strict mode', async () => {
const nonStrictService = new ContractValidatorService({ memory: mockMemory }, { strictMode: false });
const contract = createTestContract({ endpoints: [] });
const result = await nonStrictService.validateContract(contract);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.warnings.some((w) => w.includes('no endpoints'))).toBe(true);
}
});
it('should validate schema references and report missing schemas', async () => {
const contract = createTestContract({
endpoints: [
{
path: '/test',
method: 'POST',
requestSchema: 'nonexistent-schema',
responseSchema: 'get-users-response',
examples: [],
},
],
});
const result = await service.validateContract(contract);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.some((e) => e.code === 'INVALID_REFERENCE')).toBe(true);
}
});
it('should cache validation results when caching is enabled', async () => {
const cachingService = new ContractValidatorService({ memory: mockMemory }, { cacheValidations: true });
const contract = createTestContract();
const result1 = await cachingService.validateContract(contract);
const result2 = await cachingService.validateContract(contract);
expect(result1.success).toBe(true);
expect(result2.success).toBe(true);
// Memory.set should only be called once for caching
expect(mockMemory.set).toHaveBeenCalledTimes(1);
});
it('should warn when contract has no consumers', async () => {
const contract = createTestContract({ consumers: [] });
const result = await service.validateContract(contract);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.warnings.some((w) => w.includes('No consumers'))).toBe(true);
}
});
});
describe('validateRequest', () => {
it('should validate request against JSON schema successfully', async () => {
const schema: SchemaDefinition = {
id: 'user-schema',
name: 'UserSchema',
type: 'json-schema',
content: JSON.stringify({
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
},
required: ['name'],
}),
};
const result = await service.validateRequest({ name: 'John', age: 30 }, schema);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(true);
}
});
it('should return errors for invalid request data', async () => {
const schema: SchemaDefinition = {
id: 'user-schema',
name: 'UserSchema',
type: 'json-schema',
content: JSON.stringify({
type: 'object',
properties: {
name: { type: 'string' },
},
required: ['name'],
}),
};
const result = await service.validateRequest({ name: 123 }, schema);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.length).toBeGreaterThan(0);
}
});
it('should handle unsupported schema types', async () => {
const schema: SchemaDefinition = {
id: 'custom-schema',
name: 'CustomSchema',
type: 'protobuf' as const,
content: 'message Test {}',
};
const result = await service.validateRequest({ test: true }, schema);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.some((e) => e.keyword === 'unsupported')).toBe(true);
}
});
});
describe('validateResponse', () => {
it('should validate response against JSON schema', async () => {
const schema: SchemaDefinition = {
id: 'response-schema',
name: 'ResponseSchema',
type: 'json-schema',
content: JSON.stringify({
type: 'object',
properties: {
data: { type: 'array' },
total: { type: 'number' },
},
}),
};
const result = await service.validateResponse({ data: [1, 2, 3], total: 3 }, schema);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(true);
}
});
});
describe('validateOpenAPI', () => {
it('should validate OpenAPI 3.x specification', async () => {
const spec = JSON.stringify({
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
paths: {
'/users': {
get: { responses: { '200': { description: 'Success' } } },
},
},
components: {
schemas: { User: { type: 'object' } },
},
});
const result = await service.validateOpenAPI(spec);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(true);
expect(result.value.specVersion).toBe('3.0.0');
expect(result.value.endpointCount).toBe(1);
expect(result.value.schemaCount).toBe(1);
}
});
it('should validate Swagger 2.x specification', async () => {
const spec = JSON.stringify({
swagger: '2.0',
info: { title: 'Test API', version: '1.0.0' },
paths: {
'/items': {
get: { responses: { '200': { description: 'Success' } } },
post: { responses: { '201': { description: 'Created' } } },
},
},
definitions: {
Item: { type: 'object' },
Error: { type: 'object' },
},
});
const result = await service.validateOpenAPI(spec);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.specVersion).toBe('swagger-2.0');
expect(result.value.endpointCount).toBe(2);
expect(result.value.schemaCount).toBe(2);
}
});
it('should return errors for invalid JSON', async () => {
const invalidSpec = 'not valid json {{{';
const result = await service.validateOpenAPI(invalidSpec);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.some((e) => e.code === 'PARSE_ERROR')).toBe(true);
}
});
it('should return errors for missing required OpenAPI fields', async () => {
const spec = JSON.stringify({
openapi: '3.0.0',
// Missing 'info' object
paths: {},
});
const result = await service.validateOpenAPI(spec);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.some((e) => e.path === 'info')).toBe(true);
}
});
it('should return errors for unsupported OpenAPI version', async () => {
const spec = JSON.stringify({
openapi: '4.0.0',
info: { title: 'Test', version: '1.0.0' },
paths: {},
});
const result = await service.validateOpenAPI(spec);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.isValid).toBe(false);
expect(result.value.errors.some((e) => e.code === 'UNSUPPORTED_VERSION')).toBe(true);
}
});
});
});