import { Pool } from "pg"; import { buildApp } from "../app.js"; import { loadConfig } from "../config.js"; import { runMigrations } from "../db/migrations.js"; const config = loadConfig(); const workspaceSlug = readRequiredEnv("SMOKE_WORKSPACE_SLUG"); const projectId = readRequiredEnv("SMOKE_PROJECT_ID"); if (!config.DATABASE_URL) { throw new Error("DATABASE_URL is required for e2e smoke test."); } if (!config.NODEDC_INTERNAL_ACCESS_TOKEN) { throw new Error("NODEDC_INTERNAL_ACCESS_TOKEN is required for e2e smoke test."); } const migrationPool = new Pool({ connectionString: config.DATABASE_URL }); await runMigrations(migrationPool); await migrationPool.end(); const app = await buildApp({ ...config, LOG_LEVEL: process.env.LOG_LEVEL === "debug" ? "debug" : "silent", }); try { const suffix = Date.now().toString(36); const agentId = await createAgent(suffix); await upsertGrant(agentId); const token = await createToken(agentId); const authHeaders = { Authorization: `Bearer ${token}` }; const projects = await requestJson("GET", "/api/v1/tools/projects", authHeaders); const context = await requestJson( "GET", `/api/v1/tools/projects/${projectId}/context?workspace_slug=${encodeURIComponent(workspaceSlug)}`, authHeaders ); const createIssuePayload = { project_id: projectId, workspace_slug: workspaceSlug, title: `NODE.DC Codex API smoke ${suffix}`, description: "Created by Agent Gateway e2e smoke test.", priority: "medium", structured_blocks: [ { id: "current-architecture", type: "text", title: "Текущая архитектура", body: "Gateway создал карточку через реальный Tasker internal adapter.", }, { id: "checker-1", type: "checker", title: "Чекер smoke", items: [ { id: "auth", text: "Agent token accepted", checked: true, }, { id: "tasker", text: "Tasker adapter created issue", checked: true, }, ], }, ], }; const createHeaders = { ...authHeaders, "Idempotency-Key": `rest-create-${suffix}` }; const issue = await requestJson("POST", "/api/v1/tools/issues", createHeaders, createIssuePayload); const replayedIssue = await requestJson("POST", "/api/v1/tools/issues", createHeaders, createIssuePayload); const issueId = issue.issue.id as string; assert(replayedIssue.issue.id === issueId, "idempotent REST create returns the original issue"); await requestJson("POST", `/api/v1/tools/issues/${issueId}/comments`, { ...authHeaders, "Idempotency-Key": `rest-comment-${suffix}` }, { project_id: projectId, workspace_slug: workspaceSlug, body: "Smoke comment from Agent Gateway.", }); const states = Array.isArray(context.states) ? context.states : []; const targetState = states.find((state) => typeof state?.id === "string"); if (targetState) { await requestJson( "POST", `/api/v1/tools/issues/${issueId}/move`, { ...authHeaders, "Idempotency-Key": `rest-move-${suffix}` }, { project_id: projectId, workspace_slug: workspaceSlug, state_id: targetState.id, } ); } console.log( JSON.stringify( { ok: true, tasker_url: config.NODEDC_TASKER_INTERNAL_URL, workspace_slug: workspaceSlug, project_id: projectId, visible_projects: Array.isArray(projects.projects) ? projects.projects.length : null, issue_id: issueId, idempotent_replay: "passed", moved: Boolean(targetState), }, null, 2 ) ); } finally { await app.close(); } async function createAgent(suffix: string): Promise { const payload = await requestJson("POST", "/api/v1/agents", undefined, { owner_user_id: `e2e-owner-${suffix}`, owner_email: `e2e-${suffix}@example.test`, display_name: `E2E Codex ${suffix}`, }); return payload.agent.id as string; } async function upsertGrant(agentId: string): Promise { await requestJson("POST", `/api/v1/agents/${agentId}/grants`, undefined, { workspace_slug: workspaceSlug, project_id: projectId, scopes: [ "workspace:read", "project:read", "issue:read", "issue:create", "issue:update", "issue:move", "issue:comment", "issue:label", "issue:assign", "issue:structured_blocks:write", ], mode: "voluntary", created_by_user_id: "e2e-smoke-admin", }); } async function createToken(agentId: string): Promise { const payload = await requestJson("POST", `/api/v1/agents/${agentId}/tokens`, undefined, { name: "E2E smoke token", }); return payload.token as string; } async function requestJson( method: "GET" | "POST", url: string, headers?: Record, payload?: unknown ): Promise> { const injectOptions: any = { method, url, headers, }; if (payload !== undefined) { injectOptions.payload = payload; } const response = await app.inject(injectOptions); const body = response.body ? JSON.parse(response.body) : {}; if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error(`HTTP ${response.statusCode} for ${method} ${url}: ${JSON.stringify(body)}`); } return body; } function readRequiredEnv(key: string): string { const value = process.env[key]?.trim(); if (!value) { throw new Error(`${key} is required for e2e smoke test.`); } return value; } function assert(condition: unknown, message: string): void { if (!condition) { throw new Error(`E2E smoke assertion failed: ${message}`); } }