UNPKG

integration-test-mcp

Version:

MCP服务器用于集成测试

402 lines (346 loc) 11.1 kB
/** * 集成测试 MCP 服务器工具函数 */ import os from 'os'; import axios, { AxiosInstance } from 'axios'; import puppeteer from 'puppeteer-core'; // 全局配置类型定义 interface Config { loginUrl: string; api: { baseUrl: string; featureList: string; submitTest: string; pipelineBranches: string; pipelineIntegrate: string; headers: { 'content-type': string; 'group-id': string; [key: string]: string; }; pipeline: { taskId: string; pipelineId: string; stageId: string; }; }; credentials: { selectors: { usernameInput: string; passwordInput: string; submitButton: string; }; }; cookieFields: string[]; loginSuccessCondition: { urlIncludes?: string; elementExists?: string; }; } // 响应类型定义 export interface ApiResponse<T = any> { code: number; msg: string; data: T; } export interface FeatureItem { id: string; name: string; branch_name: string; status: string; [key: string]: any; } export interface FeatureListResponse { items: FeatureItem[]; total: number; [key: string]: any; } export interface PipelineBranch { id: string; name: string; status: string; [key: string]: any; } export interface PipelineBranchesResponse { items: PipelineBranch[]; total: number; [key: string]: any; } export interface IntegrationTestResult { success: boolean; branch?: string; feature?: { id: string; name: string; branch_name: string; status: string; }; submit?: { code: number; msg: string; success: any; }; pipeline?: { id: string; name: string; status: string; }; integrate?: { code: number; msg: string; success: any; }; error?: string; } // 全局配置 export const CONFIG: Config = { // 登录页面URL loginUrl: process.env.MCP_API_BASE_URL || '', // API配置 api: { baseUrl: process.env.MCP_API_BASE_URL || '', featureList: "/brapi/feature/list", submitTest: "/brapi/feature/submit_test", pipelineBranches: "/plapi/branch/get_feature_list", pipelineIntegrate: "/plapi/stage/integrate", headers: { "content-type": "application/json", "group-id": "3a079c60-d2ec-9e36-0135-6112ed2fe6ed", }, pipeline: { taskId: "3a1277fd-8c79-6c90-b9a7-ae75a80b472a", pipelineId: "3a1277f8-a6c1-1c75-7428-4ecd9f1617e7", stageId: "3a1277fd-8c5f-3fca-12f1-81799faf345d", }, }, // 登录凭证 credentials: { selectors: { usernameInput: ".login-user", passwordInput: ".login-pwd", submitButton: ".login-button", }, }, // 需要获取的cookie字段 cookieFields: ["token"], // 登录成功后的判断条件 loginSuccessCondition: { urlIncludes: "/feature", elementExists: ".ant-avatar", }, }; /** * 获取登录凭证 * @returns 返回用户名和密码 * @throws 如果环境变量未设置则抛出错误 */ export function getCredentials(): { username: string; password: string } { const username = process.env.MCP_USERNAME; const password = process.env.MCP_PASSWORD; const baseUrl = process.env.MCP_API_BASE_URL; if (!username || !password) { throw new Error('环境变量 MCP_USERNAME 或 MCP_PASSWORD 未设置,请先设置环境变量'); } if (!baseUrl) { throw new Error('环境变量 MCP_API_BASE_URL 未设置,请先设置环境变量'); } return { username, password }; } export function createApiClient(token: string): AxiosInstance { const client = axios.create({ baseURL: CONFIG.api.baseUrl, headers: { ...CONFIG.api.headers, Cookie: `token=${token}`, }, }); client.interceptors.response.use( (response) => response.data, (error) => { throw new Error( `API请求失败: ${error.response?.data?.msg || error.message}` ); } ); return client; } export async function getFeatureList(apiClient: AxiosInstance, branchName: string): Promise<ApiResponse<FeatureListResponse>> { const params = { page_size: 100, page: 1, show_myself: 1, name: branchName, }; const response = await apiClient.get<any, ApiResponse<FeatureListResponse>>(CONFIG.api.featureList, { params }); if (response.code !== 0) { throw new Error(response.msg || "获取特性列表失败"); } if (!response.data.items?.length) { throw new Error(`未找到分支 ${branchName} 对应的特性数据`); } return response; } export async function submitFeatureToTest(apiClient: AxiosInstance, featureId: string): Promise<ApiResponse<any>> { const data = { id: featureId, action: "submit_test", info: "1", }; const response = await apiClient.post<any, ApiResponse<any>>(CONFIG.api.submitTest, data); if (response.code === 10000) { throw new Error(response.msg || "提测失败"); } if (response.code !== 0) { throw new Error(response.msg || "提交特性到测试失败"); } return response; } export async function getPipelineBranches(apiClient: AxiosInstance, branchName: string): Promise<ApiResponse<PipelineBranchesResponse>> { const params = { task_id: CONFIG.api.pipeline.taskId, pipeline_id: CONFIG.api.pipeline.pipelineId, page: 1, page_size: 10, name: branchName, }; const response = await apiClient.get<any, ApiResponse<PipelineBranchesResponse>>(CONFIG.api.pipelineBranches, { params, }); if (response.code !== 0) { throw new Error(response.msg || "获取流水线分支列表失败"); } if (!response.data.items?.length) { throw new Error(`未找到分支 ${branchName} 对应的流水线数据`); } return response; } export async function integratePipeline(apiClient: AxiosInstance, featureId: string): Promise<ApiResponse<any>> { const data = { pipeline_id: CONFIG.api.pipeline.pipelineId, stage_id: CONFIG.api.pipeline.stageId, task_id: CONFIG.api.pipeline.taskId, feature_ids: featureId, }; const response = await apiClient.post<any, ApiResponse<any>>(CONFIG.api.pipelineIntegrate, data); if (response.code !== 0) { throw new Error(response.msg || "执行流水线集成失败"); } return response; } export function getEdgePath(): string { const platform = os.platform(); switch (platform) { case "win32": return "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"; case "darwin": return "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"; case "linux": return "/usr/bin/microsoft-edge"; default: throw new Error(`不支持的操作系统: ${platform}`); } } export async function loginAndGetCookies(): Promise<Record<string, string>> { let browserInstance = null; try { const launchOptions = { headless: true, defaultViewport: { width: 1920, height: 1080, }, args: [ "--disable-gpu", "--disable-dev-shm-usage", "--disable-setuid-sandbox", "--no-sandbox", ], executablePath: getEdgePath(), }; // 获取登录凭证 const { username, password } = getCredentials(); browserInstance = await puppeteer.launch(launchOptions); const page = await browserInstance.newPage(); await page.goto(CONFIG.loginUrl, { waitUntil: "networkidle2" }); await page.type( CONFIG.credentials.selectors.usernameInput, username ); await page.type( CONFIG.credentials.selectors.passwordInput, password ); await page.click(CONFIG.credentials.selectors.submitButton); if (CONFIG.loginSuccessCondition.urlIncludes) { await page.waitForFunction( (expectedUrl: string) => window.location.href.includes(expectedUrl), {}, CONFIG.loginSuccessCondition.urlIncludes ); } if (CONFIG.loginSuccessCondition.elementExists) { await page.waitForSelector(CONFIG.loginSuccessCondition.elementExists); } const cookies = await page.cookies(); const result: Record<string, string> = {}; for (const cookie of cookies) { if (CONFIG.cookieFields.includes(cookie.name)) { result[cookie.name] = cookie.value; } } return result; } catch (error) { throw new Error(`登录过程中发生错误: ${(error as Error).message}`); } finally { if (browserInstance) { try { await browserInstance.close(); } catch (closeError) { console.error("关闭浏览器时发生错误:", (closeError as Error).message); } } } } // 主要业务逻辑 export async function runIntegrationTest(branchName: string): Promise<IntegrationTestResult> { try { const cookies = await loginAndGetCookies(); const apiClient = createApiClient(cookies.token); const featureList = await getFeatureList(apiClient, branchName); const feature = featureList.data.items[0]; const submitResult = await submitFeatureToTest(apiClient, feature.id); const pipelineBranches = await getPipelineBranches(apiClient, branchName); const branch = pipelineBranches.data.items[0]; const integrateResult = await integratePipeline(apiClient, branch.id); return { success: true, branch: branchName, feature: { id: feature.id, name: feature.name, branch_name: feature.branch_name, status: feature.status, }, submit: { code: submitResult.code, msg: submitResult.msg, success: submitResult.data, }, pipeline: { id: branch.id, name: branch.name, status: branch.status, }, integrate: { code: integrateResult.code, msg: integrateResult.msg, success: integrateResult.data, }, }; } catch (error) { return { success: false, error: (error as Error).message, }; } }