From 418914fefd63c9f20b51b9873c6f340c701219a3 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Thu, 14 May 2026 19:44:05 +0300 Subject: [PATCH] TEST - CODEX AGENTS: real Tasker e2e smoke harness --- package.json | 1 + src/scripts/smoke-e2e.ts | 182 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 src/scripts/smoke-e2e.ts diff --git a/package.json b/package.json index 9735c82..89c3f30 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "check": "tsc --noEmit -p tsconfig.json", "migrate": "tsx src/scripts/migrate.ts", "migrate:dist": "node dist/scripts/migrate.js", + "smoke:e2e": "tsx src/scripts/smoke-e2e.ts", "smoke:gateway": "tsx src/scripts/smoke-gateway.ts", "start": "node dist/server.js" }, diff --git a/src/scripts/smoke-e2e.ts b/src/scripts/smoke-e2e.ts new file mode 100644 index 0000000..92573b8 --- /dev/null +++ b/src/scripts/smoke-e2e.ts @@ -0,0 +1,182 @@ +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 issue = await requestJson("POST", "/api/v1/tools/issues", authHeaders, { + 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 issueId = issue.issue.id as string; + await requestJson("POST", `/api/v1/tools/issues/${issueId}/comments`, authHeaders, { + 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, { + 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, + 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; +}