NODEDC_TASKMANAGER_CODEXAPI/src/scripts/smoke-e2e.ts

200 lines
5.7 KiB
TypeScript

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<string> {
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<void> {
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<string> {
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<string, string>,
payload?: unknown
): Promise<Record<string, any>> {
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}`);
}
}