@smartbear/mcp
Version:
MCP server for interacting SmartBear Products
172 lines (171 loc) • 7.06 kB
JavaScript
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../../common/info.js";
import { QMETRY_DEFAULTS } from "../config/constants.js";
import { QMETRY_PATHS } from "../config/rest-endpoints.js";
import { DEFAULT_IMPORT_AUTOMATION_PAYLOAD } from "../types/automation.js";
import { qmetryRequest } from "./api/client-api.js";
import { handleQMetryApiError } from "./api/error-handler.js";
/**
* Imports automation test results into QMetry
*
* This function handles file upload via FormData to import automation test results
* from various frameworks (TestNG, JUnit, Cucumber, Robot, etc.)
*
* CRITICAL NOTES:
* - User MUST provide a valid result file (.json, .xml, or .zip up to 30 MB)
* - File should be base64 encoded or provided as file path
* - EntityType is required and determines the framework format
* - Various parameters control test case/suite creation and linking behavior
*
* @param token - QMetry API authentication token
* @param baseUrl - QMetry instance base URL
* @param project - Project key (used in header, can be overridden by projectID in payload)
* @param payload - Import configuration including file data, entityType, and optional parameters
* @returns Promise resolving to import result with test suite and execution details
*/
export async function importAutomationResults(token, baseUrl, project, payload) {
// Merge with defaults
const finalPayload = {
...DEFAULT_IMPORT_AUTOMATION_PAYLOAD,
...payload,
};
// Create FormData for multipart/form-data upload
const formData = new FormData();
// Handle file upload
// The file should be provided as base64 string or file content
let fileBlob;
if (finalPayload.file.startsWith("data:")) {
// Base64 data URI format
const base64Data = finalPayload.file.split(",")[1];
const binaryData = atob(base64Data);
const bytes = new Uint8Array(binaryData.length);
for (let i = 0; i < binaryData.length; i++) {
bytes[i] = binaryData.charCodeAt(i);
}
fileBlob = new Blob([bytes]);
}
else if (finalPayload.file.startsWith("/") ||
finalPayload.file.includes(":\\")) {
// File path - read file from filesystem (Node.js only)
try {
const fs = await import("node:fs/promises");
const fileBuffer = await fs.readFile(finalPayload.file);
fileBlob = new Blob([new Uint8Array(fileBuffer)]);
}
catch (error) {
throw new Error(`Failed to read file from path: ${finalPayload.file}. Error: ${error instanceof Error ? error.message : String(error)}`);
}
}
else {
// Assume raw base64 string
try {
const binaryData = atob(finalPayload.file);
const bytes = new Uint8Array(binaryData.length);
for (let i = 0; i < binaryData.length; i++) {
bytes[i] = binaryData.charCodeAt(i);
}
fileBlob = new Blob([bytes]);
}
catch (_error) {
throw new Error("Invalid file format. Please provide base64 encoded file content or file path.");
}
}
// Add file to FormData
formData.append("file", fileBlob, finalPayload.fileName);
// Add required entityType
formData.append("entityType", finalPayload.entityType);
// Add optional parameters
if (finalPayload.automationHierarchy) {
formData.append("automationHierarchy", finalPayload.automationHierarchy);
}
if (finalPayload.testsuiteName) {
formData.append("testsuiteName", finalPayload.testsuiteName);
}
if (finalPayload.testsuiteId) {
formData.append("testsuiteId", finalPayload.testsuiteId);
}
if (finalPayload.tsFolderPath) {
formData.append("tsFolderPath", finalPayload.tsFolderPath);
}
if (finalPayload.tcFolderPath) {
formData.append("tcFolderPath", finalPayload.tcFolderPath);
}
if (finalPayload.platformID) {
formData.append("platformID", finalPayload.platformID);
}
if (finalPayload.projectID) {
formData.append("projectID", finalPayload.projectID);
}
if (finalPayload.releaseID) {
formData.append("releaseID", finalPayload.releaseID);
}
if (finalPayload.cycleID) {
formData.append("cycleID", finalPayload.cycleID);
}
if (finalPayload.buildID) {
formData.append("buildID", finalPayload.buildID);
}
if (finalPayload.testcase_fields) {
formData.append("testcase_fields", finalPayload.testcase_fields);
}
if (finalPayload.testsuite_fields) {
formData.append("testsuite_fields", finalPayload.testsuite_fields);
}
if (finalPayload.skipWarning) {
formData.append("skipWarning", finalPayload.skipWarning);
}
if (finalPayload.is_matching_required) {
formData.append("is_matching_required", finalPayload.is_matching_required);
}
// Make API request
const url = `${baseUrl || QMETRY_DEFAULTS.BASE_URL}${QMETRY_PATHS.AUTOMATION.IMPORT_RESULTS}`;
const headers = {
apikey: token,
project: finalPayload.projectID || project || QMETRY_DEFAULTS.PROJECT_KEY,
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
// Note: Content-Type will be set automatically by fetch for FormData
};
let res;
try {
res = await fetch(url, {
method: "POST",
headers,
body: formData,
});
}
catch (error) {
throw new Error(`Failed to import automation results. Network error: ${error instanceof Error ? error.message : String(error)}\n\nPlease check:\n- QMetry server is accessible\n- File size is under 30 MB\n- File format is .json, .xml, or .zip`);
}
if (!res.ok) {
await handleQMetryApiError(res, baseUrl || QMETRY_DEFAULTS.BASE_URL, project || QMETRY_DEFAULTS.PROJECT_KEY, QMETRY_PATHS.AUTOMATION.IMPORT_RESULTS);
}
return await res.json();
}
/**
* Fetches automation status by request ID from QMetry
* @param token - QMetry API authentication token
* @param baseUrl - QMetry instance base URL
* @param requestID - Numeric request ID from import automation response
* @returns Promise resolving to automation status
*/
export async function getAutomationStatus(token, baseUrl, project, requestID) {
let numericRequestID;
if (typeof requestID === "object" &&
requestID !== null &&
"requestID" in requestID &&
typeof requestID.requestID !== "undefined") {
numericRequestID = Number(requestID.requestID);
}
else {
numericRequestID = Number(requestID);
}
if (!numericRequestID || Number.isNaN(numericRequestID)) {
throw new Error("requestID must be a valid number");
}
return qmetryRequest({
method: "GET",
path: QMETRY_PATHS.AUTOMATION.GET_STATUS.replace(":requestID", String(numericRequestID)),
token,
baseUrl: baseUrl || QMETRY_DEFAULTS.BASE_URL,
project: project || QMETRY_DEFAULTS.PROJECT_KEY,
});
}