UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

1,136 lines (1,083 loc) 47.4 kB
/** * Mock Coverage Analyzer Handler * * Detects unmocked GraphQL queries, suggests missing implementations, * and validates mock responses match schema. * * Essential for debugging async processes that can't find GraphQL mocks. */ import { BaseToolHandler } from './base-handler.js'; export class MockCoverageAnalyzerHandler extends BaseToolHandler { tools = [ { name: 'analyze_mock_coverage', description: `🔍 MOCK COVERAGE ANALYZER: Detect unmocked GraphQL queries, suggest missing implementations, and validate mock responses match schema. Essential for debugging async processes that can't find GraphQL mocks. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, analysisDepth: { type: 'string', enum: ['shallow', 'deep', 'comprehensive'], default: 'deep', description: 'Depth of mock coverage analysis' }, mockEndpoint: { type: 'string', description: 'URL of the mock GraphQL endpoint (if available)' }, schemaUrl: { type: 'string', description: 'URL to GraphQL schema for validation (optional)' }, validateResponseTypes: { type: 'boolean', default: true, description: 'Validate that mock responses match expected types' }, suggestMockImplementations: { type: 'boolean', default: true, description: 'Generate mock implementation suggestions' }, trackAsyncProcessAccess: { type: 'boolean', default: true, description: 'Track which async processes can access mocks' } }, required: ['sessionId'] } }, { name: 'validate_mock_responses', description: `✅ MOCK RESPONSE VALIDATOR: Validate that mock responses match GraphQL schema and expected types. Ensures mock data quality and prevents type mismatches. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, mockEndpoint: { type: 'string', description: 'URL of the mock GraphQL endpoint' }, testQueries: { type: 'array', items: { type: 'object', properties: { operationName: { type: 'string' }, query: { type: 'string' }, variables: { type: 'object' }, expectedSchema: { type: 'object' } } }, description: 'Specific queries to validate against mocks' }, validateNullHandling: { type: 'boolean', default: true, description: 'Validate how mocks handle null values' }, checkErrorHandling: { type: 'boolean', default: true, description: 'Validate error response formats' }, performanceThreshold: { type: 'number', default: 1000, description: 'Alert if mock responses exceed this threshold (ms)' } }, required: ['sessionId', 'mockEndpoint'] } }, { name: 'generate_missing_mocks', description: `🛠️ MISSING MOCK GENERATOR: Generate mock implementations for unmocked GraphQL operations. Creates realistic mock responses based on schema and usage patterns. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, unmockedOperations: { type: 'array', items: { type: 'string' }, description: 'List of operation names that need mocks' }, mockFormat: { type: 'string', enum: ['json', 'javascript', 'typescript', 'elixir'], default: 'json', description: 'Format for generated mock implementations' }, includeRealisticData: { type: 'boolean', default: true, description: 'Generate realistic data values instead of placeholders' }, handleErrorCases: { type: 'boolean', default: true, description: 'Include error case mock responses' }, baseOnRealQueries: { type: 'boolean', default: true, description: 'Base mock structure on captured real queries' } }, required: ['sessionId', 'unmockedOperations'] } }, { name: 'diagnose_mock_accessibility', description: `🔗 MOCK ACCESSIBILITY DIAGNOSIS: Diagnose why async processes can't access mocks and suggest fixes. Identifies process isolation issues and mock scope problems. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, processContext: { type: 'string', enum: ['browser', 'worker', 'liveview', 'genserver', 'task'], description: 'Type of process having mock access issues' }, mockingFramework: { type: 'string', enum: ['msw', 'sinon', 'jest', 'mox', 'bypass', 'mirage'], description: 'Mocking framework being used' }, analyzeScope: { type: 'boolean', default: true, description: 'Analyze mock scope and visibility' }, checkConfiguration: { type: 'boolean', default: true, description: 'Check mock framework configuration' }, suggestFixes: { type: 'boolean', default: true, description: 'Suggest specific fixes for access issues' } }, required: ['sessionId'] } } ]; async handle(toolName, args, sessions) { // Validate session exists const session = sessions.get(args.sessionId); if (!session) { return { content: [{ type: 'text', text: `❌ No active debug session found with ID: ${args.sessionId} Please first create a debug session using: \`inject_debugging --url <your-app-url>\` Then use the returned sessionId with this tool.` }] }; } switch (toolName) { case 'analyze_mock_coverage': return this.analyzeMockCoverage(args, session); case 'validate_mock_responses': return this.validateMockResponses(args, session); case 'generate_missing_mocks': return this.generateMissingMocks(args, session); case 'diagnose_mock_accessibility': return this.diagnoseMockAccessibility(args, session); default: throw new Error(`Unknown mock coverage analyzer tool: ${toolName}`); } } async analyzeMockCoverage(args, session) { const { analysisDepth = 'deep', mockEndpoint, schemaUrl, validateResponseTypes = true, suggestMockImplementations = true, trackAsyncProcessAccess = true } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } const operations = []; const unmockedQueries = []; const mockValidationErrors = []; // Inject mock coverage analysis script await page.addInitScript((config) => { window.__mockCoverage = { operations: [], requests: [], responses: [], mockHits: new Set(), unmockedHits: new Set() }; // Override fetch to track GraphQL requests and mock coverage const originalFetch = window.fetch; window.fetch = async function (url, options = {}) { const isGraphQL = url.toString().includes('graphql'); if (isGraphQL) { const coverage = window.__mockCoverage; let operationName = 'UnknownOperation'; let operationType = 'query'; let variables = {}; try { if (options.body) { const body = JSON.parse(options.body); operationName = body.operationName || 'UnknownOperation'; // Simple operation type detection if (body.query?.includes('mutation')) { operationType = 'mutation'; } else if (body.query?.includes('subscription')) { operationType = 'subscription'; } variables = body.variables || {}; coverage.operations.push({ operationName, operationType, query: body.query, variables, timestamp: Date.now(), url: url.toString() }); } } catch (e) { // Could not parse request } try { const response = await originalFetch(url, options); const responseData = await response.clone().json(); // Track if this was a mock or real response const isMockResponse = response.headers.get('x-mock-response') === 'true' || url.toString().includes('mock') || response.status === 200 && responseData.data && !responseData.errors; if (isMockResponse) { coverage.mockHits.add(operationName); } else if (response.status >= 400 || responseData.errors) { coverage.unmockedHits.add(operationName); } coverage.responses.push({ operationName, status: response.status, data: responseData, isMocked: isMockResponse, timestamp: Date.now() }); return response; } catch (error) { coverage.unmockedHits.add(operationName); throw error; } } return originalFetch(url, options); }; }, { mockEndpoint, validateResponseTypes }); // Monitor for analysis period based on depth const monitorDuration = analysisDepth === 'shallow' ? 10000 : analysisDepth === 'deep' ? 30000 : 60000; await new Promise(resolve => setTimeout(resolve, monitorDuration)); // Get coverage data from browser const coverageData = await page.evaluate(() => { const coverage = window.__mockCoverage || { operations: [], requests: [], responses: [], mockHits: new Set(), unmockedHits: new Set() }; return { operations: coverage.operations, responses: coverage.responses, mockHits: Array.from(coverage.mockHits), unmockedHits: Array.from(coverage.unmockedHits) }; }); // Analyze coverage const totalQueries = new Set(coverageData.operations.map((op) => op.operationName)).size; const mockedQueries = coverageData.mockHits.length; const unmocked = coverageData.unmockedHits; // Generate validation errors if requested let validationErrors = []; if (validateResponseTypes) { validationErrors = this.validateResponseTypes(coverageData.responses); } // Generate suggestions let suggestions = []; if (suggestMockImplementations) { suggestions = this.generateMockSuggestions(unmocked, coverageData.operations); } const coveragePercentage = totalQueries > 0 ? Math.round((mockedQueries / totalQueries) * 100) : 0; return { content: [{ type: 'text', text: `## 🔍 Mock Coverage Analysis Report ### Coverage Summary - **Analysis Depth**: ${analysisDepth} - **Total Operations**: ${totalQueries} - **Mocked Operations**: ${mockedQueries} - **Unmocked Operations**: ${unmocked.length} - **Coverage Percentage**: ${coveragePercentage}% - **Monitoring Duration**: ${monitorDuration}ms ### Coverage Status ${coveragePercentage >= 80 ? '✅ **GOOD COVERAGE**' : coveragePercentage >= 50 ? '⚠️ **MODERATE COVERAGE**' : '❌ **POOR COVERAGE**'} ### Mocked Operations ${coverageData.mockHits.length > 0 ? coverageData.mockHits.map((op) => `✅ ${op}`).join('\n') : 'No mocked operations detected'} ### Unmocked Operations ${unmocked.length > 0 ? unmocked.map((op) => `❌ ${op}`).join('\n') : '✅ All operations are mocked'} ### GraphQL Operations Detected ${coverageData.operations.length > 0 ? coverageData.operations.slice(0, 10).map((op) => `- **${op.operationName}** (${op.operationType}) - ${op.url.includes('mock') ? 'Mock' : 'Real'} endpoint`).join('\n') : 'No GraphQL operations detected during monitoring'} ${coverageData.operations.length > 10 ? `\n... and ${coverageData.operations.length - 10} more operations` : ''} ### Mock Validation Errors ${validationErrors.length > 0 ? validationErrors.map(error => `⚠️ **${error.operationName}**: ${error.message} (${error.field})`).join('\n') : '✅ No validation errors detected'} ### Async Process Access Analysis ${trackAsyncProcessAccess ? 'Process access tracking enabled - some operations may come from async processes that can\'t access mocks' : 'Process access tracking disabled'} ### Mock Implementation Suggestions ${suggestions.length > 0 ? suggestions.map(suggestion => `- ${suggestion}`).join('\n') : 'No specific suggestions generated'} ### Recommendations ${this.generateCoverageRecommendations(coveragePercentage, unmocked, validationErrors)} ### Next Steps 1. **Implement missing mocks** using \`generate_missing_mocks\` tool 2. **Validate mock responses** using \`validate_mock_responses\` tool 3. **Diagnose async access issues** using \`diagnose_mock_accessibility\` tool 4. **Re-run coverage analysis** after implementing fixes` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ Mock Coverage Analysis Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'} ### Troubleshooting - Ensure your application is making GraphQL requests during analysis - Check that mock endpoints are configured correctly - Verify the analysis duration is sufficient for your use case` }] }; } } async validateMockResponses(args, session) { const { mockEndpoint, testQueries = [], validateNullHandling = true, checkErrorHandling = true, performanceThreshold = 1000 } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } const validationResults = []; const performanceIssues = []; // Test each provided query for (const testQuery of testQueries) { const startTime = Date.now(); try { const response = await page.evaluate(async (query, endpoint) => { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: query.query, variables: query.variables, operationName: query.operationName }) }); return { status: response.status, data: await response.json(), headers: {} // Simplified for compatibility }; }, testQuery, mockEndpoint); const responseTime = Date.now() - startTime; if (responseTime > performanceThreshold) { performanceIssues.push(`${testQuery.operationName}: ${responseTime}ms`); } const validation = this.validateSingleResponse(testQuery, response, validateNullHandling); validation.responseTime = responseTime; validationResults.push(validation); } catch (error) { validationResults.push({ operationName: testQuery.operationName, valid: false, errors: [`Request failed: ${error instanceof Error ? error.message : 'Unknown error'}`], responseTime: Date.now() - startTime }); } } // Test error handling if requested let errorHandlingResults = []; if (checkErrorHandling) { errorHandlingResults = await this.testErrorHandling(page, mockEndpoint); } const totalTests = validationResults.length; const passedTests = validationResults.filter(r => r.valid).length; const validationScore = totalTests > 0 ? Math.round((passedTests / totalTests) * 100) : 0; return { content: [{ type: 'text', text: `## ✅ Mock Response Validation Report ### Validation Summary - **Mock Endpoint**: ${mockEndpoint} - **Total Tests**: ${totalTests} - **Passed Tests**: ${passedTests} - **Failed Tests**: ${totalTests - passedTests} - **Validation Score**: ${validationScore}% ### Test Results ${validationResults.length > 0 ? validationResults.map((result, i) => ` #### ${i + 1}. ${result.operationName} - **Status**: ${result.valid ? '✅ PASS' : '❌ FAIL'} - **Response Time**: ${result.responseTime}ms ${result.errors && result.errors.length > 0 ? `- **Errors**: ${result.errors.join(', ')}` : '- **Errors**: None'} ${result.warnings && result.warnings.length > 0 ? `- **Warnings**: ${result.warnings.join(', ')}` : ''} `).join('\n') : 'No test queries provided'} ### Performance Issues ${performanceIssues.length > 0 ? performanceIssues.map(issue => `⚠️ Slow response: ${issue}`).join('\n') : '✅ All responses within performance threshold'} ### Error Handling Validation ${checkErrorHandling ? errorHandlingResults.length > 0 ? errorHandlingResults.map(result => `${result.valid ? '✅' : '❌'} ${result.testCase}: ${result.message}`).join('\n') : '✅ Error handling validation passed' : 'Error handling validation disabled'} ### Null Value Handling ${validateNullHandling ? 'Null value handling validation enabled - check individual test results' : 'Null value handling validation disabled'} ### Recommendations ${this.generateValidationRecommendations(validationResults, performanceIssues)} ### Next Steps - Fix any failed validations in your mock implementations - Address performance issues for slow responses - Use \`analyze_mock_coverage\` to check overall coverage - Use \`generate_missing_mocks\` to create additional mock responses` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ Mock Response Validation Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'} ### Troubleshooting - Verify the mock endpoint URL is correct and accessible - Ensure the mock server is running - Check that test queries are valid GraphQL` }] }; } } async generateMissingMocks(args, session) { const { unmockedOperations, mockFormat = 'json', includeRealisticData = true, handleErrorCases = true, baseOnRealQueries = true } = args; try { const generatedMocks = []; for (const operationName of unmockedOperations) { const mockImplementation = this.generateMockImplementation(operationName, mockFormat, includeRealisticData, handleErrorCases); generatedMocks.push(mockImplementation); } return { content: [{ type: 'text', text: `## 🛠️ Generated Mock Implementations ### Generation Summary - **Operations**: ${unmockedOperations.length} - **Format**: ${mockFormat} - **Realistic Data**: ${includeRealisticData ? 'Yes' : 'No'} - **Error Cases**: ${handleErrorCases ? 'Yes' : 'No'} ### Generated Mocks ${generatedMocks.map((mock, i) => ` #### ${i + 1}. ${mock.operationName} **${mockFormat.toUpperCase()} Implementation:** \`\`\`${mockFormat} ${mock.implementation} \`\`\` ${handleErrorCases ? ` **Error Case:** \`\`\`${mockFormat} ${mock.errorImplementation} \`\`\` ` : ''} **Usage Notes:** ${mock.usageNotes.map((note) => `- ${note}`).join('\n')} `).join('\n')} ### Integration Instructions ${this.getIntegrationInstructions(mockFormat)} ### Best Practices - **Keep mocks realistic** - Use data that matches your production patterns - **Handle edge cases** - Include null values, empty arrays, and error states - **Version your mocks** - Track changes as your schema evolves - **Test mock responses** - Use \`validate_mock_responses\` to verify quality - **Document mock behavior** - Make it clear what each mock represents ### Next Steps 1. **Copy the generated mock implementations** into your mock framework 2. **Customize the data** to match your specific use cases 3. **Test the mocks** using \`validate_mock_responses\` 4. **Re-run coverage analysis** using \`analyze_mock_coverage\` 5. **Document the mocks** for your team` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ Mock Generation Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } async diagnoseMockAccessibility(args, session) { const { processContext, mockingFramework, analyzeScope = true, checkConfiguration = true, suggestFixes = true } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } // Analyze mock accessibility based on context const accessibilityIssues = []; const configurationIssues = []; const suggestedFixes = []; // Check if mocks are properly initialized in the current context const mockStatus = await page.evaluate((framework) => { const checks = { mswAvailable: typeof window.msw !== 'undefined', jestMocksAvailable: typeof jest !== 'undefined', fetchMocked: window.fetch.toString().includes('mock'), serviceWorkerActive: 'serviceWorker' in navigator }; return { framework: framework || 'unknown', checks, currentUrl: window.location.href, userAgent: navigator.userAgent }; }, mockingFramework); // Analyze scope issues if (analyzeScope) { const scopeAnalysis = this.analyzeMockScope(processContext, mockingFramework, mockStatus); accessibilityIssues.push(...scopeAnalysis.issues); suggestedFixes.push(...scopeAnalysis.fixes); } // Check configuration if (checkConfiguration) { const configAnalysis = this.analyzeConfiguration(mockingFramework, mockStatus); configurationIssues.push(...configAnalysis.issues); suggestedFixes.push(...configAnalysis.fixes); } return { content: [{ type: 'text', text: `## 🔗 Mock Accessibility Diagnosis ### Context Analysis - **Process Context**: ${processContext || 'Not specified'} - **Mocking Framework**: ${mockingFramework || 'Not specified'} - **Current URL**: ${mockStatus.currentUrl} ### Mock Status Checks ${Object.entries(mockStatus.checks).map(([check, result]) => `${result ? '✅' : '❌'} **${check}**: ${result ? 'Available' : 'Not Available'}`).join('\n')} ### Accessibility Issues ${accessibilityIssues.length > 0 ? accessibilityIssues.map(issue => `❌ ${issue}`).join('\n') : '✅ No accessibility issues detected'} ### Configuration Issues ${configurationIssues.length > 0 ? configurationIssues.map(issue => `⚠️ ${issue}`).join('\n') : '✅ Configuration appears correct'} ### Process-Specific Analysis ${this.getProcessSpecificAnalysis(processContext, mockingFramework)} ### Suggested Fixes ${suggestFixes && suggestedFixes.length > 0 ? suggestedFixes.map(fix => `🔧 ${fix}`).join('\n') : 'No specific fixes suggested'} ### Framework-Specific Recommendations ${this.getFrameworkRecommendations(mockingFramework)} ### Common Solutions by Process Type ${this.getProcessSolutions(processContext)} ### Next Steps 1. **Apply suggested fixes** based on your specific setup 2. **Test mock accessibility** by running failing operations 3. **Re-run coverage analysis** using \`analyze_mock_coverage\` 4. **Verify with real queries** using \`validate_mock_responses\` 5. **Document the solution** for your team` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ Mock Accessibility Diagnosis Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } // Helper methods validateResponseTypes(responses) { const errors = []; responses.forEach(response => { if (response.data && response.data.errors) { response.data.errors.forEach((error) => { errors.push({ operationName: response.operationName, field: error.path?.join('.') || 'unknown', expectedType: 'valid response', actualType: 'error', message: error.message }); }); } }); return errors; } generateMockSuggestions(unmocked, operations) { const suggestions = []; if (unmocked.length > 0) { suggestions.push(`Implement mocks for ${unmocked.length} unmocked operations`); unmocked.forEach(op => { const operation = operations.find((o) => o.operationName === op); if (operation) { if (operation.operationType === 'mutation') { suggestions.push(`Create mutation mock for ${op} with success/error variants`); } else if (operation.operationType === 'subscription') { suggestions.push(`Set up subscription mock for ${op} with real-time updates`); } else { suggestions.push(`Add query mock for ${op} with realistic data structure`); } } }); } return suggestions; } generateCoverageRecommendations(coverage, unmocked, errors) { const recommendations = []; if (coverage < 50) { recommendations.push('**Critical**: Mock coverage is below 50%. Implement mocks for core operations first.'); } else if (coverage < 80) { recommendations.push('**Moderate**: Aim for 80%+ coverage for reliable testing.'); } else { recommendations.push('**Good**: Mock coverage is above 80%. Focus on quality and edge cases.'); } if (unmocked.length > 0) { recommendations.push(`Prioritize mocking: ${unmocked.slice(0, 3).join(', ')}${unmocked.length > 3 ? '...' : ''}`); } if (errors.length > 0) { recommendations.push('Fix validation errors to improve mock quality'); } return recommendations.join('\n- '); } validateSingleResponse(testQuery, response, validateNulls) { const validation = { operationName: testQuery.operationName, valid: true, errors: [], warnings: [] }; if (response.status !== 200) { validation.valid = false; validation.errors.push(`HTTP ${response.status}`); } if (response.data.errors && response.data.errors.length > 0) { validation.valid = false; validation.errors.push(`GraphQL errors: ${response.data.errors.map((e) => e.message).join(', ')}`); } if (!response.data.data) { validation.valid = false; validation.errors.push('No data field in response'); } if (validateNulls && response.data.data) { const nullFields = this.findNullFields(response.data.data); if (nullFields.length > 0) { validation.warnings.push(`Null fields: ${nullFields.join(', ')}`); } } return validation; } async testErrorHandling(page, mockEndpoint) { const errorTests = [ { testCase: 'Invalid Query Syntax', query: 'invalid query syntax', expectedError: true }, { testCase: 'Missing Operation Name', query: 'query { nonExistentField }', expectedError: true } ]; const results = []; for (const test of errorTests) { try { const response = await page.evaluate(async (query, endpoint) => { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }) }); return { status: response.status, data: await response.json() }; }, test.query, mockEndpoint); const hasError = response.status >= 400 || (response.data.errors && response.data.errors.length > 0); results.push({ testCase: test.testCase, valid: test.expectedError ? hasError : !hasError, message: test.expectedError ? (hasError ? 'Correctly returned error' : 'Should have returned error') : (hasError ? 'Unexpectedly returned error' : 'Correctly handled request') }); } catch (error) { results.push({ testCase: test.testCase, valid: test.expectedError, message: `Request failed: ${error instanceof Error ? error.message : 'Unknown error'}` }); } } return results; } generateValidationRecommendations(results, performanceIssues) { const recommendations = []; const failedTests = results.filter(r => !r.valid); if (failedTests.length > 0) { recommendations.push('Fix failing mock validations'); recommendations.push('Review mock response structure and data types'); } if (performanceIssues.length > 0) { recommendations.push('Optimize slow mock responses'); recommendations.push('Consider reducing mock response payload size'); } const allPassed = results.every(r => r.valid); if (allPassed && performanceIssues.length === 0) { recommendations.push('Mock validation passed - focus on expanding coverage'); } return recommendations.length > 0 ? recommendations.map(r => `- ${r}`).join('\n') : '- All validations passed successfully'; } generateMockImplementation(operationName, format, realistic, includeErrors) { const mockData = realistic ? this.generateRealisticData(operationName) : this.generatePlaceholderData(operationName); let implementation = ''; let errorImplementation = ''; const usageNotes = []; switch (format) { case 'json': implementation = JSON.stringify({ data: mockData }, null, 2); if (includeErrors) { errorImplementation = JSON.stringify({ errors: [{ message: `Mock error for ${operationName}`, extensions: { code: 'MOCK_ERROR' } }] }, null, 2); } usageNotes.push('Use in MSW or similar JSON-based mock frameworks'); break; case 'javascript': implementation = ` export const ${operationName}Mock = { data: ${JSON.stringify(mockData, null, 2)} };`; if (includeErrors) { errorImplementation = ` export const ${operationName}ErrorMock = { errors: [{ message: "Mock error for ${operationName}", extensions: { code: "MOCK_ERROR" } }] };`; } usageNotes.push('Import and use in Jest or Sinon mocks'); break; case 'elixir': implementation = ` def ${this.toSnakeCase(operationName)}_mock do %{ data: ${this.toElixirMap(mockData)} } end`; if (includeErrors) { errorImplementation = ` def ${this.toSnakeCase(operationName)}_error_mock do %{ errors: [%{ message: "Mock error for ${operationName}", extensions: %{code: "MOCK_ERROR"} }] } end`; } usageNotes.push('Use in Mox or Bypass for Elixir/Phoenix tests'); break; default: implementation = JSON.stringify({ data: mockData }, null, 2); usageNotes.push('Generic JSON format'); } return { operationName, implementation, errorImplementation, usageNotes }; } generateRealisticData(operationName) { // Generate realistic mock data based on operation name patterns if (operationName.toLowerCase().includes('user')) { return { user: { id: "user-123", name: "John Doe", email: "john.doe@example.com", avatar: "https://example.com/avatar.jpg", createdAt: new Date().toISOString() } }; } else if (operationName.toLowerCase().includes('post')) { return { posts: [ { id: "post-1", title: "Sample Post Title", content: "This is sample post content for testing.", author: { id: "user-123", name: "John Doe" }, createdAt: new Date().toISOString() } ] }; } else { return { [operationName.toLowerCase()]: { id: "sample-id-123", name: "Sample Data", status: "active", updatedAt: new Date().toISOString() } }; } } generatePlaceholderData(operationName) { return { [operationName.toLowerCase()]: { id: "placeholder-id", field1: "placeholder-value-1", field2: "placeholder-value-2", timestamp: new Date().toISOString() } }; } getIntegrationInstructions(format) { switch (format) { case 'json': return ` ### MSW Integration \`\`\`javascript rest.post('/graphql', (req, res, ctx) => { const { operationName } = req.body; switch (operationName) { case 'YourOperation': return res(ctx.json(YourOperationMock)); default: return res(ctx.status(404)); } }); \`\`\``; case 'javascript': return ` ### Jest Integration \`\`\`javascript jest.mock('./graphql-client', () => ({ request: jest.fn().mockImplementation(({ operationName }) => { switch (operationName) { case 'YourOperation': return Promise.resolve(YourOperationMock); default: return Promise.reject(new Error('Unmocked operation')); } }) })); \`\`\``; case 'elixir': return ` ### Mox Integration \`\`\`elixir # In your test MockClient |> expect(:request, fn %{operation_name: "YourOperation"} -> {:ok, your_operation_mock()} end) \`\`\``; default: return 'Refer to your mocking framework documentation for integration instructions.'; } } findNullFields(obj, path = '') { const nullFields = []; for (const [key, value] of Object.entries(obj)) { const currentPath = path ? `${path}.${key}` : key; if (value === null) { nullFields.push(currentPath); } else if (typeof value === 'object' && value !== null) { nullFields.push(...this.findNullFields(value, currentPath)); } } return nullFields; } analyzeMockScope(processContext, framework, status) { const issues = []; const fixes = []; if (processContext === 'liveview' && framework === 'msw') { issues.push('MSW service worker may not be accessible from LiveView processes'); fixes.push('Configure MSW to handle server-side requests or use a different mocking approach'); } if (processContext === 'worker' && !status.checks.serviceWorkerActive) { issues.push('Service worker not active for web worker context'); fixes.push('Ensure service worker is registered before worker initialization'); } return { issues, fixes }; } analyzeConfiguration(framework, status) { const issues = []; const fixes = []; if (framework === 'msw' && !status.checks.serviceWorkerActive) { issues.push('MSW service worker not detected'); fixes.push('Register MSW service worker before making requests'); } if (!status.checks.fetchMocked && framework !== 'bypass') { issues.push('Fetch API not mocked - requests may go to real endpoints'); fixes.push('Ensure mocking framework is properly initialized'); } return { issues, fixes }; } getProcessSpecificAnalysis(processContext, framework) { switch (processContext) { case 'liveview': return 'LiveView processes run on the server - ensure mocks are available in the Elixir/Phoenix context, not just the browser.'; case 'genserver': return 'GenServer processes are isolated - use Mox or similar to provide mocks in the process scope.'; case 'task': return 'Task processes may not inherit mock setup - explicitly configure mocks for async tasks.'; case 'worker': return 'Web workers have isolated contexts - ensure mocks are available in worker scope.'; default: return 'No specific analysis available for this process context.'; } } getFrameworkRecommendations(framework) { switch (framework) { case 'msw': return ` ### MSW Recommendations - Ensure service worker is registered early in application startup - Use server-side MSW for Node.js/Phoenix requests - Check network tab for service worker installation success`; case 'mox': return ` ### Mox Recommendations - Use \`set_mox_from_context\` in test setup - Ensure mocks are available in the correct process context - Consider using \`allow\` for cross-process mock access`; case 'bypass': return ` ### Bypass Recommendations - Configure Bypass port to match your GraphQL endpoint - Ensure Bypass is started before tests run - Use Bypass.pass for unmocked endpoints`; default: return 'No specific recommendations available for this framework.'; } } getProcessSolutions(processContext) { switch (processContext) { case 'liveview': return ` - **Solution 1**: Use Mox with \`allow/3\` to share mocks across processes - **Solution 2**: Set up Bypass to handle HTTP requests from LiveView - **Solution 3**: Configure test to use real endpoints with test data`; case 'genserver': return ` - **Solution 1**: Use \`Mox.allow/3\` to grant mock access to GenServer process - **Solution 2**: Set up mocks in GenServer init callback - **Solution 3**: Use \`setup\` callback to configure mocks for the test process`; default: return 'No specific solutions available for this process context.'; } } toSnakeCase(str) { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`).slice(1); } toElixirMap(obj) { if (typeof obj === 'string') { return `"${obj}"`; } else if (typeof obj === 'number') { return obj.toString(); } else if (obj === null) { return 'nil'; } else if (Array.isArray(obj)) { return `[${obj.map(item => this.toElixirMap(item)).join(', ')}]`; } else if (typeof obj === 'object') { const entries = Object.entries(obj).map(([key, value]) => `${key}: ${this.toElixirMap(value)}`); return `%{${entries.join(', ')}}`; } return 'nil'; } } //# sourceMappingURL=mock-coverage-analyzer-handler.js.map