fix(vv): resolve all 6 V&V issues — field trial unblocked

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>
This commit is contained in:
SentryAgent.ai Developer
2026-04-07 04:52:47 +00:00
parent d216096dfb
commit 7441c9f298
49 changed files with 8954 additions and 70 deletions

View File

@@ -0,0 +1,200 @@
/**
* Unit tests for src/services/OIDCTrustPolicyService.ts
*/
import { Pool } from 'pg';
import {
OIDCTrustPolicyService,
TrustPolicyNotFoundError,
TrustPolicyViolationError,
} from '../../../src/services/OIDCTrustPolicyService';
import { ValidationError } from '../../../src/utils/errors';
jest.mock('pg');
const MockPool = Pool as jest.MockedClass<typeof Pool>;
function makePool(): jest.Mocked<Pool> {
const pool = new MockPool() as jest.Mocked<Pool>;
pool.query = jest.fn();
return pool;
}
function makePolicyRow(overrides: Record<string, unknown> = {}): Record<string, unknown> {
return {
id: 'policy-001',
provider: 'github',
repository: 'acme/my-repo',
branch: null,
agent_id: 'agent-001',
created_at: new Date('2026-01-01'),
updated_at: new Date('2026-01-01'),
...overrides,
};
}
describe('OIDCTrustPolicyService', () => {
let service: OIDCTrustPolicyService;
let pool: jest.Mocked<Pool>;
beforeEach(() => {
jest.clearAllMocks();
pool = makePool();
service = new OIDCTrustPolicyService(pool);
});
describe('createTrustPolicy()', () => {
it('should create a trust policy successfully', async () => {
pool.query = jest.fn()
.mockResolvedValueOnce({ rows: [{ agent_id: 'agent-001' }], rowCount: 1 })
.mockResolvedValueOnce({ rows: [makePolicyRow()], rowCount: 1 });
const result = await service.createTrustPolicy({
provider: 'github',
repository: 'acme/my-repo',
branch: null,
agentId: 'agent-001',
});
expect(result.provider).toBe('github');
expect(result.repository).toBe('acme/my-repo');
expect(result.branch).toBeNull();
});
it('should throw ValidationError for non-github provider', async () => {
await expect(
service.createTrustPolicy({
provider: 'gitlab' as never,
repository: 'acme/my-repo',
branch: null,
agentId: 'agent-001',
}),
).rejects.toThrow(ValidationError);
});
it('should throw ValidationError for malformed repository', async () => {
await expect(
service.createTrustPolicy({
provider: 'github',
repository: 'no-slash-here',
branch: null,
agentId: 'agent-001',
}),
).rejects.toThrow(ValidationError);
});
it('should throw ValidationError when agentId is empty', async () => {
await expect(
service.createTrustPolicy({
provider: 'github',
repository: 'acme/my-repo',
branch: null,
agentId: '',
}),
).rejects.toThrow(ValidationError);
});
it('should throw ValidationError when agent not found', async () => {
pool.query = jest.fn().mockResolvedValueOnce({ rows: [], rowCount: 0 });
await expect(
service.createTrustPolicy({
provider: 'github',
repository: 'acme/my-repo',
branch: null,
agentId: 'nonexistent',
}),
).rejects.toThrow(ValidationError);
});
});
describe('listTrustPoliciesForAgent()', () => {
it('should return mapped policies', async () => {
pool.query = jest.fn().mockResolvedValue({
rows: [makePolicyRow(), makePolicyRow({ id: 'policy-002' })],
rowCount: 2,
});
const policies = await service.listTrustPoliciesForAgent('agent-001');
expect(policies).toHaveLength(2);
expect(policies[0].id).toBe('policy-001');
});
it('should return an empty array when no policies exist', async () => {
pool.query = jest.fn().mockResolvedValue({ rows: [], rowCount: 0 });
const policies = await service.listTrustPoliciesForAgent('agent-001');
expect(policies).toHaveLength(0);
});
});
describe('deleteTrustPolicy()', () => {
it('should delete a policy successfully', async () => {
pool.query = jest.fn().mockResolvedValue({ rowCount: 1 });
await expect(service.deleteTrustPolicy('policy-001')).resolves.toBeUndefined();
});
it('should throw TrustPolicyNotFoundError when policy does not exist', async () => {
pool.query = jest.fn().mockResolvedValue({ rowCount: 0 });
await expect(service.deleteTrustPolicy('nonexistent')).rejects.toThrow(TrustPolicyNotFoundError);
});
});
describe('enforceTrustPolicy()', () => {
it('should pass when a wildcard branch policy exists (branch: null)', async () => {
pool.query = jest.fn().mockResolvedValue({
rows: [makePolicyRow({ branch: null })],
rowCount: 1,
});
await expect(
service.enforceTrustPolicy('github', 'acme/my-repo', 'refs/heads/main', 'agent-001'),
).resolves.toBeUndefined();
});
it('should pass when branch matches exactly', async () => {
pool.query = jest.fn().mockResolvedValue({
rows: [makePolicyRow({ branch: 'main' })],
rowCount: 1,
});
await expect(
service.enforceTrustPolicy('github', 'acme/my-repo', 'main', 'agent-001'),
).resolves.toBeUndefined();
});
it('should normalize refs/heads/ prefix and match', async () => {
pool.query = jest.fn().mockResolvedValue({
rows: [makePolicyRow({ branch: 'main' })],
rowCount: 1,
});
await expect(
service.enforceTrustPolicy('github', 'acme/my-repo', 'refs/heads/main', 'agent-001'),
).resolves.toBeUndefined();
});
it('should throw TrustPolicyViolationError when no policies exist', async () => {
pool.query = jest.fn().mockResolvedValue({ rows: [], rowCount: 0 });
await expect(
service.enforceTrustPolicy('github', 'acme/my-repo', 'main', 'agent-001'),
).rejects.toThrow(TrustPolicyViolationError);
});
it('should throw TrustPolicyViolationError when branch does not match constrained policy', async () => {
pool.query = jest.fn().mockResolvedValue({
rows: [makePolicyRow({ branch: 'main' })],
rowCount: 1,
});
await expect(
service.enforceTrustPolicy('github', 'acme/my-repo', 'feature/evil', 'agent-001'),
).rejects.toThrow(TrustPolicyViolationError);
});
});
});