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
JavaScript
;
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,
}));
}