/** * Integration tests for Marketplace endpoints. * Uses a real Postgres test DB and Redis test instance. * * Routes covered: * GET /api/v1/marketplace — list public agents * GET /api/v1/marketplace/:id — get a specific public agent * * Marketplace endpoints are unauthenticated (public listing). */ 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 { closePool } from '../../src/db/pool'; import { closeRedisClient } from '../../src/cache/redis'; const ORG_ID = uuidv4(); const PUBLIC_AGENT_ID = uuidv4(); const PRIVATE_AGENT_ID = uuidv4(); describe('Marketplace Endpoints Integration Tests', () => { let app: Application; let pool: Pool; 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( `INSERT INTO organizations (organization_id, name) VALUES ($1, $2) ON CONFLICT DO NOTHING`, [ORG_ID, 'Test Marketplace Org'], ); // Insert a public agent await pool.query( `INSERT INTO agents (agent_id, organization_id, email, agent_type, version, capabilities, owner, deployment_env, status, is_public) VALUES ($1, $2, $3, 'screener', 'v1.0.0', '{resume:read}', 'test-team', 'production', 'active', true) ON CONFLICT DO NOTHING`, [PUBLIC_AGENT_ID, ORG_ID, `public-${PUBLIC_AGENT_ID}@test.com`], ); // Insert a private agent await pool.query( `INSERT INTO agents (agent_id, organization_id, email, agent_type, version, capabilities, owner, deployment_env, status, is_public) VALUES ($1, $2, $3, 'screener', 'v1.0.0', '{resume:read}', 'test-team', 'production', 'active', false) ON CONFLICT DO NOTHING`, [PRIVATE_AGENT_ID, ORG_ID, `private-${PRIVATE_AGENT_ID}@test.com`], ); }); afterAll(async () => { await pool.query(`DELETE FROM agents WHERE organization_id = $1`, [ORG_ID]); await pool.query(`DELETE FROM organizations WHERE organization_id = $1`, [ORG_ID]); await pool.end(); await closePool(); await closeRedisClient(); }); // ─── GET /marketplace ──────────────────────────────────────────────────────── describe('GET /api/v1/marketplace', () => { it('should return 200 with a list of public agents', async () => { const res = await request(app).get('/api/v1/marketplace'); expect(res.status).toBe(200); const items: unknown[] = res.body.data ?? res.body; expect(Array.isArray(items)).toBe(true); }); it('should not expose private agents in the listing', async () => { const res = await request(app).get('/api/v1/marketplace'); expect(res.status).toBe(200); const items = (res.body.data ?? res.body) as Array<{ agentId: string }>; const privateIds = items.map((a) => a.agentId); expect(privateIds).not.toContain(PRIVATE_AGENT_ID); }); it('should support pagination via ?page= and ?limit=', async () => { const res = await request(app).get('/api/v1/marketplace?page=1&limit=5'); expect(res.status).toBe(200); }); }); // ─── GET /marketplace/:id ──────────────────────────────────────────────────── describe('GET /api/v1/marketplace/:id', () => { it('should return 200 with the public agent card', async () => { const res = await request(app).get(`/api/v1/marketplace/${PUBLIC_AGENT_ID}`); expect(res.status).toBe(200); expect(res.body.agentId).toBe(PUBLIC_AGENT_ID); }); it('should return 404 for a private agent', async () => { const res = await request(app).get(`/api/v1/marketplace/${PRIVATE_AGENT_ID}`); expect(res.status).toBe(404); }); it('should return 404 for a non-existent agent', async () => { const res = await request(app).get(`/api/v1/marketplace/${uuidv4()}`); expect(res.status).toBe(404); }); }); });