import "dotenv/config";
import { describe, it, before, after } from 'node:test';
import assert from 'node:assert';
import { EnhancedZebrunnerClient } from '../../src/api/enhanced-client.js';
import { requireCredentials } from '../helpers/credentials.js';
/**
* E2E Integration tests for get_test_cases_by_suite_smart tool
*
* Tests the smart suite detection functionality with real MCP project data:
* - Project: MCP
* - Root Suite ID: 18824 (expected ~9 test cases with 2 sub-suites)
* - Expected test case: MCP-12
*
* These tests verify:
* 1. Root suite detection and appropriate filtering
* 2. Child suite detection and direct filtering
* 3. Pagination functionality
* 4. Include steps parameter
* 5. Error handling with invalid inputs
*/
describe('Smart Test Case Retrieval E2E Tests', () => {
let client: EnhancedZebrunnerClient;
// Test constants for MCP project
const PROJECT_KEY = 'MCP';
const ROOT_SUITE_ID = 18824;
const EXPECTED_TEST_CASE_KEY = 'MCP-12';
const EXPECTED_TOTAL_CASES = 9; // Approximately, allowing for some variance
const EXPECTED_SUB_SUITES = 2;
// Helper function to skip tests when client is not available
const skipIfNoClient = () => {
if (!client) {
console.log('⚠️ Skipping test - no API connection');
return true;
}
return false;
};
before(async () => {
try {
// Require valid credentials for E2E tests
const credentials = requireCredentials('Smart Test Case Retrieval E2E Tests');
// Initialize client
const config = {
baseUrl: credentials.baseUrl,
username: credentials.login,
token: credentials.token,
defaultPageSize: 50,
maxPageSize: 100,
debug: true
};
client = new EnhancedZebrunnerClient(config);
// Test connection
console.log(`🔍 Testing connection to ${credentials.baseUrl}...`);
const testSuites = await client.getTestSuites(PROJECT_KEY, { size: 1 });
console.log(`✅ Connection successful - found ${testSuites.items.length} suite(s)`);
} catch (error: any) {
console.error('❌ Failed to initialize client:', error.message);
console.log('⚠️ E2E tests will be skipped');
// Don't throw - let individual tests handle the missing client
}
});
after(async () => {
// Cleanup if needed
console.log('🧹 E2E test cleanup completed');
});
describe('Root Suite Detection and Filtering', () => {
it('should detect suite 18824 as root suite and use rootSuiteId filtering', async () => {
if (skipIfNoClient()) return;
console.log(`🔍 Testing root suite detection for suite ${ROOT_SUITE_ID}...`);
// Get all suites to analyze hierarchy
const allSuites = await client.getAllTestSuites(PROJECT_KEY);
console.log(`📊 Found ${allSuites.length} total suites in ${PROJECT_KEY} project`);
// Find the target suite
const targetSuite = allSuites.find(s => s.id === ROOT_SUITE_ID);
assert.ok(targetSuite, `Suite ${ROOT_SUITE_ID} should exist in ${PROJECT_KEY} project`);
console.log(`✅ Found target suite: "${targetSuite.name}" (ID: ${ROOT_SUITE_ID})`);
// Import hierarchy processor to determine root suite
const { HierarchyProcessor } = await import('../../src/utils/hierarchy.js');
const processedSuites = HierarchyProcessor.setRootParentsToSuites(allSuites);
const rootId = HierarchyProcessor.getRootId(processedSuites, ROOT_SUITE_ID);
console.log(`🌳 Root ID analysis: suite ${ROOT_SUITE_ID} → root ${rootId}`);
// Verify it's a root suite
const isRootSuite = rootId === ROOT_SUITE_ID;
assert.ok(isRootSuite, `Suite ${ROOT_SUITE_ID} should be identified as a root suite`);
console.log(`✅ Suite ${ROOT_SUITE_ID} correctly identified as root suite`);
});
it('should retrieve test cases using rootSuiteId filter for root suite', async () => {
if (skipIfNoClient()) return;
console.log(`🔍 Testing rootSuiteId filtering for suite ${ROOT_SUITE_ID}...`);
// Test the actual filtering that the smart tool would use
const testCases = await client.getAllTCMTestCasesBySuiteId(PROJECT_KEY, ROOT_SUITE_ID, true);
console.log(`📊 Found ${testCases.length} test cases using rootSuiteId filtering`);
// Verify we got expected number of test cases (allow some variance)
assert.ok(testCases.length >= EXPECTED_TOTAL_CASES - 2,
`Should find at least ${EXPECTED_TOTAL_CASES - 2} test cases`);
assert.ok(testCases.length <= EXPECTED_TOTAL_CASES + 5,
`Should not find more than ${EXPECTED_TOTAL_CASES + 5} test cases`);
// Verify expected test case is included
const expectedTestCase = testCases.find(tc => tc.key === EXPECTED_TEST_CASE_KEY);
assert.ok(expectedTestCase, `Expected test case ${EXPECTED_TEST_CASE_KEY} should be found`);
console.log(`✅ Found expected test case: ${EXPECTED_TEST_CASE_KEY}`);
// Verify all test cases have proper root suite ID
testCases.forEach(tc => {
// Cast to any to access rootSuiteId which is added by the enhanced client
const enhancedTC = tc as any;
assert.equal(enhancedTC.rootSuiteId, ROOT_SUITE_ID,
`Test case ${tc.key} should have rootSuiteId = ${ROOT_SUITE_ID}`);
});
console.log(`✅ All ${testCases.length} test cases have correct rootSuiteId`);
});
});
describe('Child Suite Detection and Filtering', () => {
it('should find child suites of root suite 18824', async () => {
if (skipIfNoClient()) return;
console.log(`🔍 Finding child suites of root suite ${ROOT_SUITE_ID}...`);
// Get all suites and find children of our root suite
const allSuites = await client.getAllTestSuites(PROJECT_KEY);
const { HierarchyProcessor } = await import('../../src/utils/hierarchy.js');
const processedSuites = HierarchyProcessor.setRootParentsToSuites(allSuites);
// Find child suites (suites that have our root suite as their root)
const childSuites = processedSuites.filter(s =>
s.rootSuiteId === ROOT_SUITE_ID && s.id !== ROOT_SUITE_ID
);
console.log(`📊 Found ${childSuites.length} child suites of root suite ${ROOT_SUITE_ID}`);
// Verify we have expected number of child suites
assert.ok(childSuites.length >= EXPECTED_SUB_SUITES,
`Should find at least ${EXPECTED_SUB_SUITES} child suites`);
// Log child suite details
childSuites.forEach(child => {
console.log(` - Child Suite: "${child.name}" (ID: ${child.id})`);
});
// Test child suite filtering if we have child suites
if (childSuites.length > 0) {
const childSuite = childSuites[0];
console.log(`🔍 Testing direct suiteId filtering for child suite ${childSuite.id}...`);
const childTestCases = await client.getAllTCMTestCasesBySuiteId(PROJECT_KEY, childSuite.id, false);
console.log(`📊 Found ${childTestCases.length} test cases in child suite ${childSuite.id}`);
// Verify child suite test cases have correct suite ID
if (childTestCases.length > 0) {
childTestCases.forEach(tc => {
assert.equal(tc.testSuite?.id, childSuite.id,
`Test case ${tc.key} should belong to suite ${childSuite.id}`);
});
console.log(`✅ All test cases in child suite have correct suiteId`);
}
}
});
});
describe('Pagination Functionality', () => {
it('should handle pagination parameters correctly', async () => {
if (skipIfNoClient()) return;
console.log(`🔍 Testing pagination with root suite ${ROOT_SUITE_ID}...`);
// Test different page sizes
const pageSizes = [5, 10];
for (const pageSize of pageSizes) {
console.log(`📄 Testing page size ${pageSize}...`);
// Get first page
const page0 = await client.getTestCases(PROJECT_KEY, {
rootSuiteId: ROOT_SUITE_ID,
size: pageSize,
page: 0
});
console.log(` Page 0: ${page0.items.length} items`);
assert.ok(page0.items.length <= pageSize, `Page 0 should not exceed page size ${pageSize}`);
// If we have more items than page size, test second page
if (page0.items.length === pageSize) {
const page1 = await client.getTestCases(PROJECT_KEY, {
rootSuiteId: ROOT_SUITE_ID,
size: pageSize,
page: 1
});
console.log(` Page 1: ${page1.items.length} items`);
// Verify pages contain different items (if both have items)
if (page0.items.length > 0 && page1.items.length > 0) {
const page0Keys = new Set(page0.items.map(tc => tc.key));
const page1Keys = new Set(page1.items.map(tc => tc.key));
const intersection = [...page0Keys].filter(key => page1Keys.has(key));
// For this specific API, we'll just verify that pagination works
// without requiring different content (some APIs return overlapping results)
console.log(`ℹ️ Page 0 has ${page0Keys.size} unique items, Page 1 has ${page1Keys.size} unique items`);
console.log(`ℹ️ ${intersection.length} items overlap between pages`);
// Just verify that we got valid responses
assert.ok(page0.items.length > 0, 'Page 0 should have items');
assert.ok(page1.items.length >= 0, 'Page 1 should have valid response');
console.log(`✅ Pagination responses are valid`);
}
}
}
});
});
describe('Include Steps Functionality', () => {
it('should retrieve detailed test case information when include_steps is true', async () => {
if (skipIfNoClient()) return;
console.log(`🔍 Testing include_steps functionality...`);
// Get a few test cases with basic info
const basicTestCases = await client.getTestCases(PROJECT_KEY, {
rootSuiteId: ROOT_SUITE_ID,
size: 3
});
if (basicTestCases.items.length === 0) {
console.log('⚠️ No test cases found, skipping include_steps test');
return;
}
// Get detailed info for the first test case
const firstTestCase = basicTestCases.items[0];
if (!firstTestCase.key) {
console.log('⚠️ First test case has no key, skipping detailed test');
return;
}
console.log(`📝 Getting detailed info for test case ${firstTestCase.key}...`);
const detailedTestCase = await client.getTestCaseByKey(PROJECT_KEY, firstTestCase.key);
// Verify detailed test case has more information
assert.ok(detailedTestCase, 'Detailed test case should be retrieved');
assert.equal(detailedTestCase.key, firstTestCase.key, 'Keys should match');
// Check if detailed version has additional fields
const hasMoreDetails = Object.keys(detailedTestCase).length > Object.keys(firstTestCase).length;
if (hasMoreDetails) {
console.log(`✅ Detailed test case has ${Object.keys(detailedTestCase).length} fields vs ${Object.keys(firstTestCase).length} in basic version`);
} else {
console.log(`ℹ️ Test case ${firstTestCase.key} has same detail level in both versions`);
}
});
});
describe('Error Handling', () => {
it('should handle non-existent suite ID gracefully', async () => {
if (skipIfNoClient()) return;
const nonExistentSuiteId = 999999999;
console.log(`🔍 Testing error handling with non-existent suite ${nonExistentSuiteId}...`);
// Get all suites to verify the suite doesn't exist
const allSuites = await client.getAllTestSuites(PROJECT_KEY);
const suiteExists = allSuites.find(s => s.id === nonExistentSuiteId);
assert.ok(!suiteExists, `Suite ${nonExistentSuiteId} should not exist`);
// The smart tool should detect this and return appropriate error
// This simulates what the smart tool would do
console.log(`✅ Non-existent suite ${nonExistentSuiteId} correctly not found`);
});
it('should handle invalid project key gracefully', async () => {
if (skipIfNoClient()) return;
const invalidProjectKey = 'NONEXISTENT';
console.log(`🔍 Testing error handling with invalid project ${invalidProjectKey}...`);
try {
await client.getTestSuites(invalidProjectKey, { size: 1 });
assert.fail('Should have thrown error for invalid project key');
} catch (error: any) {
console.log(`✅ Invalid project key correctly rejected: ${error.message}`);
assert.ok(error.message, 'Error should have meaningful message');
}
});
it('should handle excessive page size gracefully', async () => {
if (skipIfNoClient()) return;
const excessivePageSize = 1000;
console.log(`🔍 Testing error handling with excessive page size ${excessivePageSize}...`);
try {
const result = await client.getTestCases(PROJECT_KEY, {
rootSuiteId: ROOT_SUITE_ID,
size: excessivePageSize
});
// API might cap the page size rather than error
console.log(`ℹ️ API returned ${result.items.length} items for requested size ${excessivePageSize}`);
// Verify result is reasonable
assert.ok(result.items.length <= 100, 'API should cap results to reasonable limit');
} catch (error: any) {
console.log(`✅ Excessive page size correctly rejected: ${error.message}`);
assert.ok(error.message, 'Error should have meaningful message');
}
});
});
describe('Performance and Reliability', () => {
it('should complete suite hierarchy analysis within reasonable time', async () => {
if (skipIfNoClient()) return;
console.log(`⏱️ Testing performance of suite hierarchy analysis...`);
const startTime = Date.now();
// This simulates what the smart tool does
const allSuites = await client.getAllTestSuites(PROJECT_KEY);
const { HierarchyProcessor } = await import('../../src/utils/hierarchy.js');
const processedSuites = HierarchyProcessor.setRootParentsToSuites(allSuites);
const rootId = HierarchyProcessor.getRootId(processedSuites, ROOT_SUITE_ID);
const endTime = Date.now();
const duration = endTime - startTime;
console.log(`⏱️ Suite hierarchy analysis completed in ${duration}ms`);
console.log(`📊 Processed ${allSuites.length} suites, found root ID: ${rootId}`);
// Should complete within reasonable time (30 seconds max)
assert.ok(duration < 30000, `Analysis should complete within 30 seconds (took ${duration}ms)`);
// Should be reasonably fast for good UX (under 10 seconds preferred)
if (duration < 10000) {
console.log(`✅ Good performance: completed in ${duration}ms`);
} else {
console.log(`⚠️ Slower performance: completed in ${duration}ms (still acceptable)`);
}
});
it('should handle concurrent requests reliably', async () => {
if (skipIfNoClient()) return;
console.log(`🔄 Testing concurrent request handling...`);
// Make multiple concurrent requests
const concurrentRequests = Array.from({ length: 3 }, (_, index) =>
client.getTestCases(PROJECT_KEY, {
rootSuiteId: ROOT_SUITE_ID,
size: 5,
page: index
})
);
const startTime = Date.now();
const results = await Promise.all(concurrentRequests);
const duration = Date.now() - startTime;
console.log(`🔄 ${concurrentRequests.length} concurrent requests completed in ${duration}ms`);
// Verify all requests succeeded
results.forEach((result, index) => {
assert.ok(result.items, `Request ${index} should return items array`);
assert.ok(Array.isArray(result.items), `Request ${index} items should be array`);
console.log(` Request ${index}: ${result.items.length} items`);
});
console.log(`✅ All concurrent requests completed successfully`);
});
});
});