import type { Workspace, Workflow, CreateTaskResponse, ListWorkflowsResponse, ListWorkflowStepsResponse, } from "../../lib/types/http"; import type { Agent } from "../../lib/types/http-agents"; // --- GitHub Mock Types --- export type MockPR = { number: number; title: string; state: string; head_branch: string; head_sha?: string; base_branch: string; author_login: string; repo_owner: string; repo_name: string; html_url?: string; url?: string; body?: string; draft?: boolean; additions?: number; deletions?: number; requested_reviewers?: Array<{ login: string; type: string }>; }; export type MockOrg = { login: string; avatar_url?: string; }; export type MockRepo = { full_name: string; owner: string; name: string; private?: boolean; }; export type MockReview = { id: number; author: string; author_avatar?: string; state: string; body?: string; created_at?: string; }; export type MockCheckRun = { name: string; source?: string; status: string; conclusion?: string; html_url?: string; }; /** * HTTP API client for seeding test data via the backend REST API. */ export class ApiClient { constructor(private baseUrl: string) {} private async request(method: string, path: string, body?: unknown): Promise { const res = await fetch(`${this.baseUrl}${path}`, { method, headers: body ? { "Content-Type": "application/json" } : undefined, body: body ? JSON.stringify(body) : undefined, }); if (!res.ok) { const text = await res.text(); throw new Error(`API ${method} ${path} failed (${res.status}): ${text}`); } return res.json() as Promise; } async healthCheck(): Promise { await this.request("GET", "/health"); } async createWorkspace(name: string): Promise { return this.request("POST", "/api/v1/workspaces", { name }); } async createWorkflow(workspaceId: string, name: string, templateId?: string): Promise { return this.request("POST", "/api/v1/workflows", { workspace_id: workspaceId, name, ...(templateId ? { workflow_template_id: templateId } : {}), }); } async createTask( workspaceId: string, title: string, opts?: { description?: string; workflow_id?: string; workflow_step_id?: string; /** Stored in task.Metadata so auto_start_agent can pick it up on on_enter. */ agent_profile_id?: string; /** Repository IDs to associate with the task (required for agent execution). */ repository_ids?: string[]; /** When true, task is placed at position 0 regardless of is_start_step. */ plan_mode?: boolean; }, ): Promise { return this.request("POST", "/api/v1/tasks", { workspace_id: workspaceId, title, description: opts?.description ?? "", ...(opts?.workflow_id ? { workflow_id: opts.workflow_id } : {}), ...(opts?.workflow_step_id ? { workflow_step_id: opts.workflow_step_id } : {}), ...(opts?.agent_profile_id ? { metadata: { agent_profile_id: opts.agent_profile_id } } : {}), ...(opts?.repository_ids ? { repositories: opts.repository_ids.map((id) => ({ repository_id: id })) } : {}), ...(opts?.plan_mode ? { plan_mode: true } : {}), }); } async listAgents(): Promise<{ agents: Agent[]; total: number }> { return this.request("GET", "/api/v1/agents"); } async deleteAgentProfile(profileId: string): Promise { await this.request("DELETE", `/api/v1/agent-profiles/${profileId}`); } /** Delete all agent profiles except the ones in keepIds. */ async cleanupTestProfiles(keepIds: string[]): Promise { const { agents } = await this.listAgents(); for (const agent of agents) { for (const profile of agent.profiles ?? []) { if (!keepIds.includes(profile.id)) { await this.deleteAgentProfile(profile.id); } } } } async createAgentProfile( agentId: string, name: string, opts: { model: string; auto_approve?: boolean; cli_passthrough?: boolean; }, ): Promise<{ id: string }> { return this.request("POST", `/api/v1/agents/${agentId}/profiles`, { name, model: opts.model, auto_approve: opts.auto_approve ?? true, cli_passthrough: opts.cli_passthrough ?? false, }); } async createTaskWithAgent( workspaceId: string, title: string, agentProfileId: string, opts?: { description?: string; workflow_id?: string; workflow_step_id?: string; repository_ids?: string[]; executor_id?: string; }, ): Promise { return this.request("POST", "/api/v1/tasks", { workspace_id: workspaceId, title, description: opts?.description ?? "", start_agent: true, agent_profile_id: agentProfileId, ...(opts?.workflow_id ? { workflow_id: opts.workflow_id } : {}), ...(opts?.workflow_step_id ? { workflow_step_id: opts.workflow_step_id } : {}), ...(opts?.repository_ids ? { repositories: opts.repository_ids.map((id) => ({ repository_id: id })) } : {}), ...(opts?.executor_id ? { executor_id: opts.executor_id } : {}), }); } async listWorkflows(workspaceId: string): Promise { return this.request("GET", `/api/v1/workspaces/${workspaceId}/workflows`); } async listWorkflowSteps(workflowId: string): Promise { return this.request("GET", `/api/v1/workflows/${workflowId}/workflow/steps`); } async createWorkflowStep( workflowId: string, name: string, position: number, opts?: { is_start_step?: boolean }, ): Promise<{ id: string }> { return this.request("POST", `/api/v1/workflow/steps`, { workflow_id: workflowId, name, position, ...(opts?.is_start_step != null ? { is_start_step: opts.is_start_step } : {}), }); } async createRepository( workspaceId: string, localPath: string, defaultBranch = "main", opts?: { name?: string; provider?: string; provider_owner?: string; provider_name?: string; }, ): Promise<{ id: string }> { return this.request("POST", `/api/v1/workspaces/${workspaceId}/repositories`, { name: opts?.name ?? "E2E Repo", source_type: "local", local_path: localPath, default_branch: defaultBranch, ...(opts?.provider ? { provider: opts.provider } : {}), ...(opts?.provider_owner ? { provider_owner: opts.provider_owner } : {}), ...(opts?.provider_name ? { provider_name: opts.provider_name } : {}), }); } async createExecutor( name: string, type: string, ): Promise<{ id: string; name: string; type: string }> { return this.request("POST", "/api/v1/executors", { name, type }); } async updateWorkspace( workspaceId: string, updates: { default_executor_id?: string }, ): Promise { await this.request("PATCH", `/api/v1/workspaces/${workspaceId}`, updates); } async listExecutors(): Promise<{ executors: Array<{ id: string; name: string; type: string; profiles?: Array<{ id: string; name: string }>; }>; }> { return this.request("GET", "/api/v1/executors"); } async saveUserSettings(settings: { enable_preview_on_click?: boolean; workspace_id?: string; workflow_filter_id?: string; }): Promise { await this.request("PATCH", "/api/v1/user/settings", settings); } async moveTask(taskId: string, workflowId: string, workflowStepId: string): Promise { await this.request("POST", `/api/v1/tasks/${taskId}/move`, { workflow_id: workflowId, workflow_step_id: workflowStepId, }); } async updateWorkflowStep( stepId: string, updates: { prompt?: string; events?: { on_enter?: Array<{ type: string; config?: Record }>; on_turn_start?: Array<{ type: string; config?: Record }>; on_turn_complete?: Array<{ type: string; config?: Record }>; on_exit?: Array<{ type: string; config?: Record }>; }; }, ): Promise { await this.request("PUT", `/api/v1/workflow/steps/${stepId}`, { id: stepId, ...updates }); } // --- E2E Test Reset --- async e2eReset(workspaceId: string, keepWorkflowIds?: string[]): Promise { const params = keepWorkflowIds?.length ? `?keep_workflows=${keepWorkflowIds.join(",")}` : ""; await this.request("DELETE", `/api/v1/e2e/reset/${workspaceId}${params}`); } // --- GitHub Mock Control --- async mockGitHubReset(): Promise { await this.request("DELETE", "/api/v1/github/mock/reset"); } async mockGitHubSetUser(username: string): Promise { await this.request("PUT", "/api/v1/github/mock/user", { username }); } async mockGitHubAddPRs(prs: MockPR[]): Promise { await this.request("POST", "/api/v1/github/mock/prs", { prs }); } async mockGitHubAddOrgs(orgs: MockOrg[]): Promise { await this.request("POST", "/api/v1/github/mock/orgs", { orgs }); } async mockGitHubAddRepos(org: string, repos: MockRepo[]): Promise { await this.request("POST", "/api/v1/github/mock/repos", { org, repos }); } async mockGitHubAddReviews( owner: string, repo: string, number: number, reviews: MockReview[], ): Promise { await this.request("POST", "/api/v1/github/mock/reviews", { owner, repo, number, reviews, }); } async mockGitHubAddCheckRuns( owner: string, repo: string, ref: string, checks: MockCheckRun[], ): Promise { await this.request("POST", "/api/v1/github/mock/checks", { owner, repo, ref, checks, }); } async mockGitHubAddPRFiles( owner: string, repo: string, number: number, files: Array<{ filename: string; status: string; additions: number; deletions: number; patch?: string; }>, ): Promise { await this.request("POST", "/api/v1/github/mock/files", { owner, repo, number, files, }); } async mockGitHubAddPRCommits( owner: string, repo: string, number: number, commits: Array<{ sha: string; message: string; author_login: string; author_date: string; }>, ): Promise { await this.request("POST", "/api/v1/github/mock/commits", { owner, repo, number, commits, }); } async mockGitHubAddBranches( owner: string, repo: string, branches: Array<{ name: string }>, ): Promise { await this.request("POST", "/api/v1/github/mock/branches", { owner, repo, branches, }); } async mockGitHubAssociateTaskPR(data: { task_id: string; owner: string; repo: string; pr_number: number; pr_url: string; pr_title: string; head_branch: string; base_branch: string; author_login: string; state?: string; additions?: number; deletions?: number; }): Promise { await this.request("POST", "/api/v1/github/mock/task-prs", data); } async mockGitHubGetStatus(): Promise<{ authenticated: boolean; username: string; auth_method: string; }> { return this.request("GET", "/api/v1/github/status"); } // --- Session --- async listSessionMessages( sessionId: string, ): Promise<{ messages: Array<{ id: string; content: string; author_type: string }> }> { return this.request("GET", `/api/v1/sessions/${sessionId}/messages`); } }