/** * Integration tests for the scaffold endpoint. * Tests GET /sdk/scaffold/:agentId */ import request from 'supertest'; import express from 'express'; import { Pool } from 'pg'; import { AuditService } from '../../src/services/AuditService'; import { ScaffoldService } from '../../src/services/ScaffoldService'; import { ScaffoldController } from '../../src/controllers/ScaffoldController'; import { createScaffoldRouter } from '../../src/routes/scaffold'; jest.mock('../../src/services/AuditService'); const MockAuditService = AuditService as jest.MockedClass; const mockQuery = jest.fn(); const mockPool = { query: mockQuery } as unknown as Pool; const AGENT_ID = 'agent-uuid-integration-test'; const TENANT_ID = 'org_system'; const CLIENT_ID = 'credential-client-id'; // Auth middleware injecting a test user that owns the agent const testAuth = (req: express.Request, _res: express.Response, next: express.NextFunction) => { req.user = { sub: AGENT_ID, client_id: CLIENT_ID, scope: 'agents:read', jti: 'jti-test', iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600, organization_id: TENANT_ID, }; next(); }; // Auth middleware for a different tenant (forbidden) const otherTenantAuth = (req: express.Request, _res: express.Response, next: express.NextFunction) => { req.user = { sub: 'other-agent-uuid', client_id: 'other-client', scope: 'agents:read', jti: 'jti-other', iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600, organization_id: 'other-org', }; next(); }; function buildApp(authMiddleware = testAuth) { const app = express(); app.use(express.json()); const auditService = new MockAuditService({} as never) as jest.Mocked; auditService.logEvent = jest.fn().mockResolvedValue({}); const scaffoldService = new ScaffoldService(); const controller = new ScaffoldController(scaffoldService, mockPool, auditService); app.use('/api/v1', createScaffoldRouter(controller, authMiddleware)); app.use( ( err: { httpStatus?: number; code?: string; message?: string }, _req: express.Request, res: express.Response, _next: express.NextFunction, ) => { res.status(err.httpStatus ?? 500).json({ code: err.code ?? 'ERROR', message: err.message }); }, ); return app; } describe('Scaffold Endpoint', () => { beforeEach(() => { jest.clearAllMocks(); process.env['API_URL'] = 'https://api.sentryagent.ai'; }); afterEach(() => { delete process.env['API_URL']; }); describe('GET /api/v1/sdk/scaffold/:agentId', () => { it('returns a TypeScript scaffold ZIP with correct Content-Type and Content-Disposition', async () => { // Agent exists and belongs to tenant mockQuery .mockResolvedValueOnce({ rows: [{ agent_id: AGENT_ID, email: 'my-agent@test.com', organization_id: TENANT_ID }], }) // Credential lookup .mockResolvedValueOnce({ rows: [{ client_id: CLIENT_ID }] }); const app = buildApp(); const res = await request(app).get(`/api/v1/sdk/scaffold/${AGENT_ID}?language=typescript`); expect(res.status).toBe(200); expect(res.headers['content-type']).toBe('application/zip'); expect(res.headers['content-disposition']).toMatch(/attachment; filename=".*typescript\.zip"/); // Response body should be a non-empty buffer (ZIP magic bytes PK) expect(res.body).toBeDefined(); }); it('returns a Python scaffold ZIP', async () => { mockQuery .mockResolvedValueOnce({ rows: [{ agent_id: AGENT_ID, email: 'my-agent@test.com', organization_id: TENANT_ID }], }) .mockResolvedValueOnce({ rows: [{ client_id: CLIENT_ID }] }); const app = buildApp(); const res = await request(app).get(`/api/v1/sdk/scaffold/${AGENT_ID}?language=python`); expect(res.status).toBe(200); expect(res.headers['content-type']).toBe('application/zip'); }); it('returns HTTP 400 for an invalid language', async () => { const app = buildApp(); const res = await request(app).get(`/api/v1/sdk/scaffold/${AGENT_ID}?language=cobol`); expect(res.status).toBe(400); }); it('returns HTTP 404 when agent does not exist', async () => { mockQuery.mockResolvedValueOnce({ rows: [] }); const app = buildApp(); const res = await request(app).get(`/api/v1/sdk/scaffold/${AGENT_ID}?language=typescript`); expect(res.status).toBe(404); }); it('returns HTTP 403 when agent belongs to a different tenant', async () => { mockQuery.mockResolvedValueOnce({ rows: [{ agent_id: AGENT_ID, email: 'my-agent@test.com', organization_id: TENANT_ID }], }); const app = buildApp(otherTenantAuth); const res = await request(app).get(`/api/v1/sdk/scaffold/${AGENT_ID}?language=typescript`); expect(res.status).toBe(403); }); }); });