Skip to main content
Glama
smart-test-case-retrieval.test.ts16.7 kB
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`); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/maksimsarychau/mcp-zebrunner'

If you have feedback or need assistance with the MCP directory API, please join our Discord server