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:
207
tests/integration/oidc-trust-policies.test.ts
Normal file
207
tests/integration/oidc-trust-policies.test.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Integration tests for OIDC Trust Policy endpoints.
|
||||
* Uses a real Postgres test DB and Redis test instance.
|
||||
*
|
||||
* Routes covered:
|
||||
* POST /api/v1/oidc/trust-policies — create a trust policy
|
||||
* GET /api/v1/oidc/trust-policies/:agentId — list trust policies for an agent
|
||||
* DELETE /api/v1/oidc/trust-policies/:id — delete a trust policy
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
import request from 'supertest';
|
||||
import { Application } from 'express';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
||||
});
|
||||
|
||||
process.env['DATABASE_URL'] =
|
||||
process.env['TEST_DATABASE_URL'] ??
|
||||
'postgresql://sentryagent:sentryagent@localhost:5432/sentryagent_idp_test';
|
||||
process.env['REDIS_URL'] = process.env['TEST_REDIS_URL'] ?? 'redis://localhost:6379/1';
|
||||
process.env['JWT_PRIVATE_KEY'] = privateKey;
|
||||
process.env['JWT_PUBLIC_KEY'] = publicKey;
|
||||
process.env['NODE_ENV'] = 'test';
|
||||
process.env['DEFAULT_ORG_ID'] = 'org_system';
|
||||
|
||||
import { createApp } from '../../src/app';
|
||||
import { signToken } from '../../src/utils/jwt';
|
||||
import { closePool } from '../../src/db/pool';
|
||||
import { closeRedisClient } from '../../src/cache/redis';
|
||||
|
||||
const ORG_ID = uuidv4();
|
||||
const AGENT_ID = uuidv4();
|
||||
const SCOPE = 'agents:write';
|
||||
|
||||
function makeToken(sub: string = AGENT_ID, scope: string = SCOPE, orgId: string = ORG_ID): string {
|
||||
return signToken({ sub, client_id: sub, scope, organization_id: orgId, jti: uuidv4() }, privateKey);
|
||||
}
|
||||
|
||||
describe('OIDC Trust Policy Endpoints Integration Tests', () => {
|
||||
let app: Application;
|
||||
let pool: Pool;
|
||||
let createdPolicyId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createApp();
|
||||
pool = new Pool({ connectionString: process.env['DATABASE_URL'] });
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS organizations (
|
||||
organization_id VARCHAR(40) PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
plan VARCHAR(20) NOT NULL DEFAULT 'free',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS agents (
|
||||
agent_id VARCHAR(40) PRIMARY KEY,
|
||||
organization_id VARCHAR(40) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
agent_type VARCHAR(50) NOT NULL,
|
||||
version VARCHAR(20) NOT NULL,
|
||||
capabilities TEXT[] NOT NULL DEFAULT '{}',
|
||||
owner VARCHAR(100) NOT NULL,
|
||||
deployment_env VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
is_public BOOLEAN NOT NULL DEFAULT false,
|
||||
did TEXT,
|
||||
did_created_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS oidc_trust_policies (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
provider VARCHAR(20) NOT NULL,
|
||||
repository VARCHAR(255) NOT NULL,
|
||||
branch VARCHAR(100),
|
||||
agent_id VARCHAR(40) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO organizations (organization_id, name) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
|
||||
[ORG_ID, 'Test OIDC Org'],
|
||||
);
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO agents
|
||||
(agent_id, organization_id, email, agent_type, version, capabilities, owner, deployment_env)
|
||||
VALUES ($1, $2, $3, 'ci-runner', 'v1.0.0', '{}', 'ci-team', 'production')
|
||||
ON CONFLICT DO NOTHING`,
|
||||
[AGENT_ID, ORG_ID, `oidc-agent-${AGENT_ID}@test.com`],
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await pool.query(`DELETE FROM oidc_trust_policies WHERE agent_id = $1`, [AGENT_ID]);
|
||||
await pool.query(`DELETE FROM agents WHERE agent_id = $1`, [AGENT_ID]);
|
||||
await pool.query(`DELETE FROM organizations WHERE organization_id = $1`, [ORG_ID]);
|
||||
await pool.end();
|
||||
await closePool();
|
||||
await closeRedisClient();
|
||||
});
|
||||
|
||||
// ─── POST /oidc/trust-policies ───────────────────────────────────────────────
|
||||
|
||||
describe('POST /api/v1/oidc/trust-policies', () => {
|
||||
it('should create a trust policy and return 201', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/v1/oidc/trust-policies')
|
||||
.set('Authorization', `Bearer ${makeToken()}`)
|
||||
.send({
|
||||
provider: 'github',
|
||||
repository: 'acme/my-repo',
|
||||
branch: 'main',
|
||||
agentId: AGENT_ID,
|
||||
});
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(res.body).toHaveProperty('id');
|
||||
createdPolicyId = res.body.id as string;
|
||||
});
|
||||
|
||||
it('should return 401 when no token provided', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/v1/oidc/trust-policies')
|
||||
.send({ provider: 'github', repository: 'acme/repo', agentId: AGENT_ID });
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('should return 422 for invalid provider', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/v1/oidc/trust-policies')
|
||||
.set('Authorization', `Bearer ${makeToken()}`)
|
||||
.send({ provider: 'gitlab', repository: 'acme/repo', agentId: AGENT_ID });
|
||||
|
||||
expect([400, 422]).toContain(res.status);
|
||||
});
|
||||
|
||||
it('should return 422 for malformed repository', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/v1/oidc/trust-policies')
|
||||
.set('Authorization', `Bearer ${makeToken()}`)
|
||||
.send({ provider: 'github', repository: 'no-slash', agentId: AGENT_ID });
|
||||
|
||||
expect([400, 422]).toContain(res.status);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── GET /oidc/trust-policies/:agentId ──────────────────────────────────────
|
||||
|
||||
describe('GET /api/v1/oidc/trust-policies/:agentId', () => {
|
||||
it('should return 200 with list of trust policies', async () => {
|
||||
const res = await request(app)
|
||||
.get(`/api/v1/oidc/trust-policies/${AGENT_ID}`)
|
||||
.set('Authorization', `Bearer ${makeToken()}`);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return 401 when no token provided', async () => {
|
||||
const res = await request(app).get(`/api/v1/oidc/trust-policies/${AGENT_ID}`);
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── DELETE /oidc/trust-policies/:id ────────────────────────────────────────
|
||||
|
||||
describe('DELETE /api/v1/oidc/trust-policies/:id', () => {
|
||||
it('should return 204 when deleting an existing policy', async () => {
|
||||
if (!createdPolicyId) return;
|
||||
const res = await request(app)
|
||||
.delete(`/api/v1/oidc/trust-policies/${createdPolicyId}`)
|
||||
.set('Authorization', `Bearer ${makeToken()}`);
|
||||
|
||||
expect(res.status).toBe(204);
|
||||
});
|
||||
|
||||
it('should return 404 when policy does not exist', async () => {
|
||||
const res = await request(app)
|
||||
.delete(`/api/v1/oidc/trust-policies/${uuidv4()}`)
|
||||
.set('Authorization', `Bearer ${makeToken()}`);
|
||||
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it('should return 401 when no token provided', async () => {
|
||||
const res = await request(app).delete(`/api/v1/oidc/trust-policies/${uuidv4()}`);
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user