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
JavaScript
/**
* 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