UNPKG

testrail-mcp-server

Version:

A Model Context Protocol (MCP) server that provides TestRail integration tools for AI assistants like Cursor

637 lines (636 loc) 23.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCase = getCase; exports.addCase = addCase; exports.updateCase = updateCase; exports.getProjects = getProjects; exports.getProject = getProject; exports.getSuite = getSuite; exports.getSuites = getSuites; exports.getCases = getCases; exports.addAttachmentToCase = addAttachmentToCase; exports.getSections = getSections; exports.getRuns = getRuns; exports.getTests = getTests; exports.getTest = getTest; exports.updateTest = updateTest; exports.getRun = getRun; exports.updateRun = updateRun; exports.addResult = addResult; exports.getCaseFields = getCaseFields; const testrailClient_1 = require("../clients/testrailClient"); async function getCase(caseId) { const data = await testrailClient_1.testRailClient.getCase(caseId); const { id, title, section_id, type_id, priority_id, refs, created_on, updated_on, ...rest } = data; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { id, title, section_id, type_id, priority_id, refs: refs ?? null, created_on, updated_on, custom: Object.keys(custom).length ? custom : undefined, }; } async function addCase(payload) { // Transform the payload to match TestRail API format const createPayload = { title: payload.title, section_id: payload.section_id, type_id: payload.type_id, priority_id: payload.priority_id, refs: payload.refs, }; // Add custom fields with proper naming convention if (payload.custom) { for (const [key, value] of Object.entries(payload.custom)) { // Ensure custom field keys have the 'custom_' prefix const fieldKey = key.startsWith('custom_') ? key : `custom_${key}`; createPayload[fieldKey] = value; } } const data = await testrailClient_1.testRailClient.addCase(payload.section_id, createPayload); // Normalize the response using the same logic as getCase const { id, title, section_id, type_id, priority_id, refs, created_on, updated_on, ...rest } = data; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { id, title, section_id, type_id, priority_id, refs: refs ?? null, created_on, updated_on, custom: Object.keys(custom).length ? custom : undefined, }; } async function updateCase(caseId, updates) { // Transform the payload to match TestRail API format const updatePayload = { title: updates.title, section_id: updates.section_id, type_id: updates.type_id, priority_id: updates.priority_id, refs: updates.refs, }; // Add custom fields with proper naming convention if (updates.custom) { for (const [key, value] of Object.entries(updates.custom)) { // Ensure custom field keys have the 'custom_' prefix const fieldKey = key.startsWith('custom_') ? key : `custom_${key}`; updatePayload[fieldKey] = value; } } const data = await testrailClient_1.testRailClient.updateCase(caseId, updatePayload); // Normalize the response using the same logic as getCase const { id, title, section_id, type_id, priority_id, refs, created_on, updated_on, ...rest } = data; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { id, title, section_id, type_id, priority_id, refs: refs ?? null, created_on, updated_on, custom: Object.keys(custom).length ? custom : undefined, }; } async function getProjects() { const projects = await testrailClient_1.testRailClient.getProjects(); return projects.map((project) => { const { id, name, announcement, show_announcement, is_completed, completed_on, suite_mode, url, created_on, created_by, ...rest } = project; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { id, name, announcement, show_announcement, is_completed, completed_on, suite_mode, url, created_on, created_by, custom: Object.keys(custom).length ? custom : undefined, }; }); } async function getProject(projectId) { const project = await testrailClient_1.testRailClient.getProject(projectId); const { id, name, announcement, show_announcement, is_completed, completed_on, suite_mode, url, created_on, created_by, ...rest } = project; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { id, name, announcement, show_announcement, is_completed, completed_on, suite_mode, url, created_on, created_by, custom: Object.keys(custom).length ? custom : undefined, }; } async function getSuite(suiteId) { const suite = await testrailClient_1.testRailClient.getSuite(suiteId); const { id, name, description, project_id, url, is_baseline, is_master, is_completed, completed_on, created_on, created_by, updated_on, updated_by, ...rest } = suite; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { id, name, description, project_id, url, is_baseline, is_master, is_completed, completed_on, created_on, created_by, updated_on, updated_by, custom: Object.keys(custom).length ? custom : undefined, }; } async function getSuites(projectId) { const suites = await testrailClient_1.testRailClient.getSuites(projectId); return suites.map((suite) => { const { id, name, description, project_id, url, is_baseline, is_master, is_completed, completed_on, created_on, created_by, updated_on, updated_by, ...rest } = suite; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { id, name, description, project_id, url, is_baseline, is_master, is_completed, completed_on, created_on, created_by, updated_on, updated_by, custom: Object.keys(custom).length ? custom : undefined, }; }); } async function getCases(filters) { // Transform service filters to client parameters const clientParams = { project_id: filters.project_id, suite_id: filters.suite_id, created_after: filters.created_after, created_before: filters.created_before, created_by: filters.created_by, filter: filters.filter, limit: filters.limit, milestone_id: filters.milestone_id, offset: filters.offset, priority_id: filters.priority_id, refs: filters.refs, section_id: filters.section_id, template_id: filters.template_id, type_id: filters.type_id, updated_after: filters.updated_after, updated_before: filters.updated_before, updated_by: filters.updated_by, label_id: filters.label_id, }; const response = await testrailClient_1.testRailClient.getCases(clientParams); // Transform each case using the same logic as getCase const transformedCases = response.cases.map((caseData) => { const { id, title, section_id, type_id, priority_id, refs, created_on, updated_on, ...rest } = caseData; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { id, title, section_id, type_id, priority_id, refs: refs ?? null, created_on, updated_on, custom: Object.keys(custom).length ? custom : undefined, }; }); return { offset: response.offset, limit: response.limit, size: response.size, _links: response._links, cases: transformedCases, }; } async function addAttachmentToCase(caseId, filePath) { const response = await testrailClient_1.testRailClient.addAttachmentToCase(caseId, filePath); return { attachment_id: response.attachment_id, }; } async function getSections(filters) { // Transform service filters to client parameters const clientParams = { project_id: filters.project_id, suite_id: filters.suite_id, limit: filters.limit, offset: filters.offset, }; const response = await testrailClient_1.testRailClient.getSections(clientParams); // Transform each section using the same pattern as other entities const transformedSections = response.sections.map((sectionData) => { const { depth, display_order, id, name, parent_id, suite_id, ...rest } = sectionData; const custom = {}; for (const [key, value] of Object.entries(rest)) { if (key.startsWith('custom_')) custom[key] = value; } return { depth, display_order, id, name, parent_id, suite_id, custom: Object.keys(custom).length ? custom : undefined, }; }); return { offset: response.offset, limit: response.limit, size: response.size, _links: response._links, sections: transformedSections, }; } async function getRuns(filters) { // Transform service filters to client parameters const clientParams = { project_id: filters.project_id, created_after: filters.created_after, created_before: filters.created_before, created_by: filters.created_by, is_completed: filters.is_completed, limit: filters.limit, milestone_id: filters.milestone_id, offset: filters.offset, refs_filter: filters.refs_filter, suite_id: filters.suite_id, }; const response = await testrailClient_1.testRailClient.getRuns(clientParams); // Transform the response to normalize custom fields const transformedRuns = response.runs.map((run) => { const { id, name, ...customFields } = run; return { id, name, custom: Object.keys(customFields).length > 0 ? customFields : undefined, }; }); return { offset: response.offset, limit: response.limit, size: response.size, _links: response._links, runs: transformedRuns, }; } async function getTests(filters) { // Transform service filters to client parameters const clientParams = { run_id: filters.run_id, status_id: filters.status_id, limit: filters.limit, offset: filters.offset, label_id: filters.label_id, }; const response = await testrailClient_1.testRailClient.getTests(clientParams); // Transform tests to normalized format const transformedTests = response.tests.map((test) => { // Extract custom fields (any fields not in the standard interface) const standardFields = ['id', 'title']; const custom = {}; Object.keys(test).forEach((key) => { if (!standardFields.includes(key)) { custom[key] = test[key]; } }); return { id: test.id, title: test.title, custom: Object.keys(custom).length > 0 ? custom : undefined, }; }); return { offset: response.offset, limit: response.limit, size: response.size, _links: response._links, tests: transformedTests, }; } async function getTest(filters) { // Transform service filters to client parameters const clientParams = { test_id: filters.test_id, with_data: filters.with_data, }; const response = await testrailClient_1.testRailClient.getTest(clientParams); // Extract custom fields (any fields not in the standard interface) const standardFields = [ 'id', 'title', 'assignedto_id', 'case_id', 'custom_expected', 'custom_preconds', 'custom_steps_separated', 'estimate', 'estimate_forecast', 'priority_id', 'run_id', 'status_id', 'type_id', 'milestone_id', 'refs', 'labels' ]; const custom = {}; Object.keys(response).forEach((key) => { if (!standardFields.includes(key)) { custom[key] = response[key]; } }); return { id: response.id, title: response.title, assignedto_id: response.assignedto_id, case_id: response.case_id, custom_expected: response.custom_expected, custom_preconds: response.custom_preconds, custom_steps_separated: response.custom_steps_separated, estimate: response.estimate, estimate_forecast: response.estimate_forecast, priority_id: response.priority_id, run_id: response.run_id, status_id: response.status_id, type_id: response.type_id, milestone_id: response.milestone_id, refs: response.refs, labels: response.labels, custom: Object.keys(custom).length > 0 ? custom : undefined, }; } async function updateTest(testId, updates) { // Transform the payload to match TestRail API format const updatePayload = { labels: updates.labels, }; // Add custom fields with proper naming convention if (updates.custom) { for (const [key, value] of Object.entries(updates.custom)) { // Ensure custom field keys have the 'custom_' prefix const fieldKey = key.startsWith('custom_') ? key : `custom_${key}`; updatePayload[fieldKey] = value; } } const data = await testrailClient_1.testRailClient.updateTest(testId, updatePayload); // Normalize the response using the same logic as getTest const standardFields = [ 'id', 'title', 'assignedto_id', 'case_id', 'custom_expected', 'custom_preconds', 'custom_steps_separated', 'estimate', 'estimate_forecast', 'priority_id', 'run_id', 'status_id', 'type_id', 'milestone_id', 'refs', 'labels' ]; const custom = {}; Object.keys(data).forEach((key) => { if (!standardFields.includes(key)) { custom[key] = data[key]; } }); return { id: data.id, title: data.title, assignedto_id: data.assignedto_id, case_id: data.case_id, custom_expected: data.custom_expected, custom_preconds: data.custom_preconds, custom_steps_separated: data.custom_steps_separated, estimate: data.estimate, estimate_forecast: data.estimate_forecast, priority_id: data.priority_id, run_id: data.run_id, status_id: data.status_id, type_id: data.type_id, milestone_id: data.milestone_id, refs: data.refs, labels: data.labels, custom: Object.keys(custom).length > 0 ? custom : undefined, }; } async function getRun(runId) { const response = await testrailClient_1.testRailClient.getRun(runId); // Extract custom fields (any fields not in the standard interface) const standardFields = [ 'id', 'name', 'description', 'suite_id', 'milestone_id', 'assignedto_id', 'include_all', 'is_completed', 'completed_on', 'config', 'config_ids', 'passed_count', 'blocked_count', 'untested_count', 'retest_count', 'failed_count', 'custom_status1_count', 'custom_status2_count', 'custom_status3_count', 'custom_status4_count', 'custom_status5_count', 'custom_status6_count', 'custom_status7_count', 'project_id', 'plan_id', 'created_on', 'updated_on', 'refs', 'start_on', 'due_on', 'url' ]; const custom = {}; Object.keys(response).forEach(key => { if (!standardFields.includes(key)) { custom[key] = response[key]; } }); return { id: response.id, name: response.name, description: response.description, suite_id: response.suite_id, milestone_id: response.milestone_id, assignedto_id: response.assignedto_id, include_all: response.include_all, is_completed: response.is_completed, completed_on: response.completed_on, config: response.config, config_ids: response.config_ids, passed_count: response.passed_count, blocked_count: response.blocked_count, untested_count: response.untested_count, retest_count: response.retest_count, failed_count: response.failed_count, custom_status1_count: response.custom_status1_count, custom_status2_count: response.custom_status2_count, custom_status3_count: response.custom_status3_count, custom_status4_count: response.custom_status4_count, custom_status5_count: response.custom_status5_count, custom_status6_count: response.custom_status6_count, custom_status7_count: response.custom_status7_count, project_id: response.project_id, plan_id: response.plan_id, created_on: response.created_on, updated_on: response.updated_on, refs: response.refs, start_on: response.start_on, due_on: response.due_on, url: response.url, custom: Object.keys(custom).length > 0 ? custom : undefined, }; } async function updateRun(runId, updates) { // Transform the payload to match TestRail API format const updatePayload = { name: updates.name, description: updates.description, milestone_id: updates.milestone_id, include_all: updates.include_all, case_ids: updates.case_ids, config: updates.config, config_ids: updates.config_ids, refs: updates.refs, start_on: updates.start_on, due_on: updates.due_on, }; // Add custom fields with proper naming convention if (updates.custom) { for (const [key, value] of Object.entries(updates.custom)) { // Ensure custom field keys have the 'custom_' prefix const fieldKey = key.startsWith('custom_') ? key : `custom_${key}`; updatePayload[fieldKey] = value; } } const data = await testrailClient_1.testRailClient.updateRun(runId, updatePayload); // Normalize the response using the same logic as getRun const standardFields = [ 'id', 'name', 'description', 'suite_id', 'milestone_id', 'assignedto_id', 'include_all', 'is_completed', 'completed_on', 'config', 'config_ids', 'passed_count', 'blocked_count', 'untested_count', 'retest_count', 'failed_count', 'custom_status1_count', 'custom_status2_count', 'custom_status3_count', 'custom_status4_count', 'custom_status5_count', 'custom_status6_count', 'custom_status7_count', 'project_id', 'plan_id', 'created_on', 'updated_on', 'refs', 'start_on', 'due_on', 'url' ]; const custom = {}; Object.keys(data).forEach(key => { if (!standardFields.includes(key)) { custom[key] = data[key]; } }); return { id: data.id, name: data.name, description: data.description, suite_id: data.suite_id, milestone_id: data.milestone_id, assignedto_id: data.assignedto_id, include_all: data.include_all, is_completed: data.is_completed, completed_on: data.completed_on, config: data.config, config_ids: data.config_ids, passed_count: data.passed_count, blocked_count: data.blocked_count, untested_count: data.untested_count, retest_count: data.retest_count, failed_count: data.failed_count, custom_status1_count: data.custom_status1_count, custom_status2_count: data.custom_status2_count, custom_status3_count: data.custom_status3_count, custom_status4_count: data.custom_status4_count, custom_status5_count: data.custom_status5_count, custom_status6_count: data.custom_status6_count, custom_status7_count: data.custom_status7_count, project_id: data.project_id, plan_id: data.plan_id, created_on: data.created_on, updated_on: data.updated_on, refs: data.refs, start_on: data.start_on, due_on: data.due_on, url: data.url, custom: Object.keys(custom).length > 0 ? custom : undefined, }; } async function addResult(filters) { // Transform service filters to client parameters const clientParams = { test_id: filters.test_id, status_id: filters.status_id, comment: filters.comment, version: filters.version, elapsed: filters.elapsed, defects: filters.defects, assignedto_id: filters.assignedto_id, custom_step_results: filters.custom_step_results, custom: filters.custom, }; const response = await testrailClient_1.testRailClient.addResult(clientParams); // Extract custom fields (any fields not in the standard interface) const standardFields = [ 'id', 'test_id', 'status_id', 'created_by', 'created_on', 'assignedto_id', 'comment', 'version', 'elapsed', 'defects', 'custom_step_results' ]; const custom = {}; Object.keys(response).forEach((key) => { if (!standardFields.includes(key)) { custom[key] = response[key]; } }); return { id: response.id, test_id: response.test_id, status_id: response.status_id, created_by: response.created_by, created_on: response.created_on, assignedto_id: response.assignedto_id, comment: response.comment, version: response.version, elapsed: response.elapsed, defects: response.defects, custom_step_results: response.custom_step_results, custom: Object.keys(custom).length > 0 ? custom : undefined, }; } async function getCaseFields() { const fields = await testrailClient_1.testRailClient.getCaseFields(); return fields.map((field) => ({ id: field.id, label: field.label, name: field.name, system_name: field.system_name, type_id: field.type_id, description: field.description, display_order: field.display_order, configs: field.configs, })); }