All findings from the inaugural LeadValidator audit resolved and confirmed. Release gate: PASS. VV_ISSUE_002 (BLOCKER): 15 OpenAPI specs verified present covering all 20 route groups (46 endpoints documented in docs/openapi/) VV_ISSUE_003 (MAJOR): Remove any types from src/db/pool.ts — replaced pool.query shim with unknown[] + Object.defineProperty, zero any types, eslint-disable suppressions removed VV_ISSUE_004 (MAJOR): Remove raw Pool from ScaffoldController and HealthDetailedController — injected AgentRepository/CredentialRepository and DbProbe interface respectively; added CredentialRepository.findActiveClientId() VV_ISSUE_005 (MAJOR): Add unit tests for 5 untested services — ComplianceStatusStore, EventPublisher, MarketplaceService, OIDCTrustPolicyService, UsageService VV_ISSUE_006 (MAJOR): Add integration tests for 7 missing route groups — analytics, billing, tiers, webhooks, marketplace, oidc-trust-policies, oidc-token-exchange VV_ISSUE_001 (MINOR): Create missing design.md and tasks.md in 4 OpenSpec archives — all archives now complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
/**
|
|
* Unit tests for src/services/MarketplaceService.ts
|
|
*/
|
|
|
|
import { MarketplaceService } from '../../../src/services/MarketplaceService';
|
|
import { AgentRepository } from '../../../src/repositories/AgentRepository';
|
|
import { AgentNotFoundError } from '../../../src/utils/errors';
|
|
import { IAgent, IMarketplaceFilters } from '../../../src/types/index';
|
|
|
|
jest.mock('../../../src/repositories/AgentRepository');
|
|
|
|
const MockAgentRepo = AgentRepository as jest.MockedClass<typeof AgentRepository>;
|
|
|
|
const BASE_FILTERS: IMarketplaceFilters = { page: 1, limit: 10 };
|
|
|
|
function makeAgent(overrides: Partial<IAgent> = {}): IAgent {
|
|
return {
|
|
agentId: 'agent-001',
|
|
organizationId: 'org-001',
|
|
email: 'agent@example.com',
|
|
agentType: 'screener',
|
|
version: 'v1.0.0',
|
|
capabilities: ['resume:read'],
|
|
owner: 'test-team',
|
|
deploymentEnv: 'production',
|
|
status: 'active',
|
|
isPublic: true,
|
|
createdAt: new Date('2026-01-01'),
|
|
updatedAt: new Date('2026-01-02'),
|
|
did: null,
|
|
didCreatedAt: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('MarketplaceService', () => {
|
|
let service: MarketplaceService;
|
|
let agentRepo: jest.Mocked<AgentRepository>;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
agentRepo = new MockAgentRepo({} as never) as jest.Mocked<AgentRepository>;
|
|
service = new MarketplaceService(agentRepo);
|
|
});
|
|
|
|
describe('listPublicAgents()', () => {
|
|
it('should return mapped agent cards', async () => {
|
|
const agent = makeAgent();
|
|
agentRepo.findPublicAgents = jest.fn().mockResolvedValue({ agents: [agent], total: 1 });
|
|
|
|
const result = await service.listPublicAgents(BASE_FILTERS);
|
|
|
|
expect(result.data).toHaveLength(1);
|
|
expect(result.data[0].agentId).toBe('agent-001');
|
|
expect(result.total).toBe(1);
|
|
expect(result.page).toBe(1);
|
|
expect(result.limit).toBe(10);
|
|
});
|
|
|
|
it('should strip private fields (email, organizationId) from cards', async () => {
|
|
const agent = makeAgent();
|
|
agentRepo.findPublicAgents = jest.fn().mockResolvedValue({ agents: [agent], total: 1 });
|
|
|
|
const result = await service.listPublicAgents(BASE_FILTERS);
|
|
const card = result.data[0] as Record<string, unknown>;
|
|
|
|
expect(card['email']).toBeUndefined();
|
|
expect(card['organizationId']).toBeUndefined();
|
|
});
|
|
|
|
it('should include a minimal DID document when agent has a DID', async () => {
|
|
const agent = makeAgent({ did: 'did:web:sentryagent.ai:agents:agent-001' });
|
|
agentRepo.findPublicAgents = jest.fn().mockResolvedValue({ agents: [agent], total: 1 });
|
|
|
|
const result = await service.listPublicAgents(BASE_FILTERS);
|
|
|
|
expect(result.data[0].didDocument).not.toBeNull();
|
|
expect(result.data[0].didDocument?.id).toBe('did:web:sentryagent.ai:agents:agent-001');
|
|
});
|
|
|
|
it('should return null DID document when agent has no DID', async () => {
|
|
const agent = makeAgent({ did: null });
|
|
agentRepo.findPublicAgents = jest.fn().mockResolvedValue({ agents: [agent], total: 1 });
|
|
|
|
const result = await service.listPublicAgents(BASE_FILTERS);
|
|
|
|
expect(result.data[0].didDocument).toBeNull();
|
|
});
|
|
|
|
it('should return empty data array when no public agents exist', async () => {
|
|
agentRepo.findPublicAgents = jest.fn().mockResolvedValue({ agents: [], total: 0 });
|
|
|
|
const result = await service.listPublicAgents(BASE_FILTERS);
|
|
|
|
expect(result.data).toHaveLength(0);
|
|
expect(result.total).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getPublicAgent()', () => {
|
|
it('should return a card for a public agent', async () => {
|
|
const agent = makeAgent();
|
|
agentRepo.findPublicById = jest.fn().mockResolvedValue(agent);
|
|
|
|
const card = await service.getPublicAgent('agent-001');
|
|
|
|
expect(card.agentId).toBe('agent-001');
|
|
expect(card.owner).toBe('test-team');
|
|
});
|
|
|
|
it('should throw AgentNotFoundError when agent is not found', async () => {
|
|
agentRepo.findPublicById = jest.fn().mockResolvedValue(null);
|
|
|
|
await expect(service.getPublicAgent('nonexistent')).rejects.toThrow(AgentNotFoundError);
|
|
});
|
|
});
|
|
});
|