first
This commit is contained in:
603
tests/error-handling/api-network-errors.test.js
Normal file
603
tests/error-handling/api-network-errors.test.js
Normal file
@@ -0,0 +1,603 @@
|
||||
/**
|
||||
* @fileoverview Tests for API and network error handling
|
||||
* Tests network failures, API errors, timeout scenarios, and resilience patterns
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
||||
import { mockFactory } from '../mocks/mockFactory.js';
|
||||
|
||||
describe('API and Network Error Handling Tests', () => {
|
||||
let mockEnv;
|
||||
let toolGenerator;
|
||||
|
||||
beforeEach(() => {
|
||||
mockEnv = mockFactory.createMockEnvironment({
|
||||
authTypes: ['provider', 'patient'],
|
||||
enableHttpMocks: true,
|
||||
enableAuthMocks: true
|
||||
});
|
||||
|
||||
toolGenerator = mockEnv.toolGenerator;
|
||||
|
||||
// Setup authentication
|
||||
mockFactory.authMocks.setMockCredentials('provider', {
|
||||
username: 'test_provider',
|
||||
password: 'test_password'
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFactory.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Network Connectivity Issues', () => {
|
||||
test('should handle network timeout errors', async () => {
|
||||
const toolName = 'provider_create_getPatientInfo';
|
||||
const parameters = {
|
||||
patientId: 123
|
||||
};
|
||||
|
||||
// Mock network timeout
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/get-patient-info/123', null, true, {
|
||||
code: 'ECONNABORTED',
|
||||
message: 'timeout of 5000ms exceeded',
|
||||
config: {
|
||||
timeout: 5000,
|
||||
url: '/api/get-patient-info/123',
|
||||
method: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected timeout error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe('ECONNABORTED');
|
||||
expect(error.message).toContain('timeout');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle connection refused errors', async () => {
|
||||
const toolName = 'provider_create_emrregisterPatient';
|
||||
const parameters = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john@test.com',
|
||||
dateOfBirth: '1990-01-01'
|
||||
};
|
||||
|
||||
// Mock connection refused
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/register-patients', null, true, {
|
||||
code: 'ECONNREFUSED',
|
||||
message: 'connect ECONNREFUSED 127.0.0.1:80',
|
||||
errno: -61,
|
||||
syscall: 'connect',
|
||||
address: '127.0.0.1',
|
||||
port: 80
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected connection refused error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe('ECONNREFUSED');
|
||||
expect(error.message).toContain('ECONNREFUSED');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle DNS resolution failures', async () => {
|
||||
const toolName = 'provider_create_addVital';
|
||||
const parameters = {
|
||||
patientId: 123,
|
||||
provider_id: 456,
|
||||
blood_presssure: '120/80'
|
||||
};
|
||||
|
||||
// Mock DNS failure
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/add-vital/123', null, true, {
|
||||
code: 'ENOTFOUND',
|
||||
message: 'getaddrinfo ENOTFOUND api.healthcare.com',
|
||||
errno: -3008,
|
||||
syscall: 'getaddrinfo',
|
||||
hostname: 'api.healthcare.com'
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected DNS error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe('ENOTFOUND');
|
||||
expect(error.message).toContain('ENOTFOUND');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle SSL/TLS certificate errors', async () => {
|
||||
const toolName = 'provider_create_prescriptionstore';
|
||||
const parameters = {
|
||||
patient_id: 'patient_123',
|
||||
medication_data: {
|
||||
medication_name: 'Lisinopril',
|
||||
strength: '10mg'
|
||||
}
|
||||
};
|
||||
|
||||
// Mock SSL certificate error
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/prescription/store/patient_123', null, true, {
|
||||
code: 'CERT_UNTRUSTED',
|
||||
message: 'certificate verify failed: self signed certificate',
|
||||
errno: -67,
|
||||
syscall: 'connect'
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected SSL certificate error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe('CERT_UNTRUSTED');
|
||||
expect(error.message).toContain('certificate verify failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('HTTP Status Code Errors', () => {
|
||||
test('should handle 400 Bad Request errors', async () => {
|
||||
const toolName = 'provider_create_emrregisterPatient';
|
||||
const parameters = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'invalid-email', // Invalid email format
|
||||
dateOfBirth: '1990-01-01'
|
||||
};
|
||||
|
||||
// Mock bad request
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/register-patients', null, true, {
|
||||
response: {
|
||||
status: 400,
|
||||
data: {
|
||||
error: 'Bad Request',
|
||||
error_code: 'VALIDATION_ERROR',
|
||||
message: 'Request validation failed',
|
||||
validation_errors: [
|
||||
{
|
||||
field: 'email',
|
||||
message: 'Invalid email format',
|
||||
code: 'INVALID_EMAIL'
|
||||
}
|
||||
],
|
||||
request_id: 'req_123'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected bad request error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(400);
|
||||
expect(error.response.data.error_code).toBe('VALIDATION_ERROR');
|
||||
expect(error.response.data.validation_errors[0].field).toBe('email');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle 404 Not Found errors', async () => {
|
||||
const toolName = 'provider_create_getPatientInfo';
|
||||
const parameters = {
|
||||
patientId: 999999 // Non-existent patient
|
||||
};
|
||||
|
||||
// Mock not found
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/get-patient-info/999999', null, true, {
|
||||
response: {
|
||||
status: 404,
|
||||
data: {
|
||||
error: 'Not Found',
|
||||
error_code: 'PATIENT_NOT_FOUND',
|
||||
message: 'Patient with ID 999999 not found',
|
||||
resource_type: 'patient',
|
||||
resource_id: 999999,
|
||||
suggestions: [
|
||||
'Verify patient ID is correct',
|
||||
'Check if patient exists in system',
|
||||
'Contact system administrator'
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected not found error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(404);
|
||||
expect(error.response.data.error_code).toBe('PATIENT_NOT_FOUND');
|
||||
expect(error.response.data.suggestions).toContain('Verify patient ID is correct');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle 409 Conflict errors', async () => {
|
||||
const toolName = 'provider_create_emrregisterPatient';
|
||||
const parameters = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'existing@test.com', // Email already exists
|
||||
dateOfBirth: '1990-01-01'
|
||||
};
|
||||
|
||||
// Mock conflict
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/register-patients', null, true, {
|
||||
response: {
|
||||
status: 409,
|
||||
data: {
|
||||
error: 'Conflict',
|
||||
error_code: 'PATIENT_ALREADY_EXISTS',
|
||||
message: 'Patient with email existing@test.com already exists',
|
||||
conflicting_resource: {
|
||||
type: 'patient',
|
||||
id: 'patient_456',
|
||||
email: 'existing@test.com'
|
||||
},
|
||||
resolution_options: [
|
||||
'use_existing_patient',
|
||||
'update_existing_patient',
|
||||
'use_different_email'
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected conflict error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(409);
|
||||
expect(error.response.data.error_code).toBe('PATIENT_ALREADY_EXISTS');
|
||||
expect(error.response.data.resolution_options).toContain('use_existing_patient');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle 422 Unprocessable Entity errors', async () => {
|
||||
const toolName = 'provider_create_addVital';
|
||||
const parameters = {
|
||||
patientId: 123,
|
||||
provider_id: 456,
|
||||
blood_presssure: '300/200', // Invalid vital signs
|
||||
temperature: 150 // Invalid temperature
|
||||
};
|
||||
|
||||
// Mock unprocessable entity
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/add-vital/123', null, true, {
|
||||
response: {
|
||||
status: 422,
|
||||
data: {
|
||||
error: 'Unprocessable Entity',
|
||||
error_code: 'INVALID_VITAL_SIGNS',
|
||||
message: 'Vital signs values are outside acceptable ranges',
|
||||
validation_errors: [
|
||||
{
|
||||
field: 'blood_presssure',
|
||||
value: '300/200',
|
||||
message: 'Blood pressure values are dangerously high',
|
||||
acceptable_range: '80/50 - 200/120'
|
||||
},
|
||||
{
|
||||
field: 'temperature',
|
||||
value: 150,
|
||||
message: 'Temperature value is not physiologically possible',
|
||||
acceptable_range: '95.0 - 110.0 °F'
|
||||
}
|
||||
],
|
||||
clinical_review_required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected unprocessable entity error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(422);
|
||||
expect(error.response.data.error_code).toBe('INVALID_VITAL_SIGNS');
|
||||
expect(error.response.data.clinical_review_required).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle 500 Internal Server Error', async () => {
|
||||
const toolName = 'provider_create_medicalRecordscreate';
|
||||
const parameters = {
|
||||
patient_id: 'patient_123',
|
||||
record_type: 'progress_note',
|
||||
diagnosis: 'Test diagnosis'
|
||||
};
|
||||
|
||||
// Mock internal server error
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/medical-records/create', null, true, {
|
||||
response: {
|
||||
status: 500,
|
||||
data: {
|
||||
error: 'Internal Server Error',
|
||||
error_code: 'SERVER_ERROR',
|
||||
message: 'An unexpected error occurred while processing your request',
|
||||
incident_id: 'INC_001',
|
||||
timestamp: new Date().toISOString(),
|
||||
support_contact: 'support@healthcare.com',
|
||||
retry_recommended: true,
|
||||
retry_after: 300 // 5 minutes
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected internal server error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(500);
|
||||
expect(error.response.data.error_code).toBe('SERVER_ERROR');
|
||||
expect(error.response.data.retry_recommended).toBe(true);
|
||||
expect(error.response.data.incident_id).toBe('INC_001');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle 502 Bad Gateway errors', async () => {
|
||||
const toolName = 'provider_create_labscreate';
|
||||
const parameters = {
|
||||
lab_data: {
|
||||
test_type: 'CBC',
|
||||
patient_id: 'patient_123'
|
||||
}
|
||||
};
|
||||
|
||||
// Mock bad gateway
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/labs/create', null, true, {
|
||||
response: {
|
||||
status: 502,
|
||||
data: {
|
||||
error: 'Bad Gateway',
|
||||
error_code: 'UPSTREAM_SERVICE_ERROR',
|
||||
message: 'Lab service is temporarily unavailable',
|
||||
upstream_service: 'lab_integration_service',
|
||||
service_status: 'degraded',
|
||||
estimated_recovery: '15 minutes',
|
||||
alternative_actions: [
|
||||
'retry_later',
|
||||
'use_manual_lab_order',
|
||||
'contact_lab_directly'
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected bad gateway error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(502);
|
||||
expect(error.response.data.error_code).toBe('UPSTREAM_SERVICE_ERROR');
|
||||
expect(error.response.data.alternative_actions).toContain('retry_later');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle 503 Service Unavailable errors', async () => {
|
||||
const toolName = 'provider_create_emrcreateAppointment';
|
||||
const parameters = {
|
||||
patient_id: 'patient_123',
|
||||
practitioner_id: 'provider_456',
|
||||
appointment_date: '2025-07-15',
|
||||
appointment_time: '10:00'
|
||||
};
|
||||
|
||||
// Mock service unavailable
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/create-appointment', null, true, {
|
||||
response: {
|
||||
status: 503,
|
||||
data: {
|
||||
error: 'Service Unavailable',
|
||||
error_code: 'MAINTENANCE_MODE',
|
||||
message: 'System is temporarily unavailable for scheduled maintenance',
|
||||
maintenance_window: {
|
||||
start: '2025-07-09T02:00:00Z',
|
||||
end: '2025-07-09T04:00:00Z',
|
||||
duration: '2 hours'
|
||||
},
|
||||
retry_after: 7200, // 2 hours
|
||||
emergency_contact: 'emergency@healthcare.com',
|
||||
status_page: 'https://status.healthcare.com'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected service unavailable error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(503);
|
||||
expect(error.response.data.error_code).toBe('MAINTENANCE_MODE');
|
||||
expect(error.response.data.retry_after).toBe(7200);
|
||||
expect(error.response.data.emergency_contact).toBeDefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data Validation and Format Errors', () => {
|
||||
test('should handle malformed JSON responses', async () => {
|
||||
const toolName = 'provider_create_getPatientInfo';
|
||||
const parameters = {
|
||||
patientId: 123
|
||||
};
|
||||
|
||||
// Mock malformed JSON
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/get-patient-info/123', {
|
||||
status: 200,
|
||||
data: 'invalid json response {malformed'
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected JSON parse error');
|
||||
} catch (error) {
|
||||
expect(error.message).toContain('JSON');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle missing required response fields', async () => {
|
||||
const toolName = 'provider_create_emrregisterPatient';
|
||||
const parameters = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john@test.com',
|
||||
dateOfBirth: '1990-01-01'
|
||||
};
|
||||
|
||||
// Mock incomplete response
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/register-patients', {
|
||||
status: 201,
|
||||
data: {
|
||||
// Missing success field and patient data
|
||||
message: 'Patient registered'
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected incomplete response error');
|
||||
} catch (error) {
|
||||
expect(error.message).toContain('incomplete');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle unexpected response structure', async () => {
|
||||
const toolName = 'provider_create_addVital';
|
||||
const parameters = {
|
||||
patientId: 123,
|
||||
provider_id: 456,
|
||||
blood_presssure: '120/80'
|
||||
};
|
||||
|
||||
// Mock unexpected response structure
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/add-vital/123', {
|
||||
status: 200,
|
||||
data: {
|
||||
unexpected_field: 'value',
|
||||
different_structure: true
|
||||
// Missing expected fields
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected unexpected response error');
|
||||
} catch (error) {
|
||||
expect(error.message).toContain('unexpected');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Retry and Resilience Patterns', () => {
|
||||
test('should implement exponential backoff for retries', async () => {
|
||||
const toolName = 'provider_create_getPatientInfo';
|
||||
const parameters = {
|
||||
patientId: 123
|
||||
};
|
||||
|
||||
// Mock temporary failure followed by success
|
||||
let attemptCount = 0;
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/get-patient-info/123', () => {
|
||||
attemptCount++;
|
||||
if (attemptCount < 3) {
|
||||
throw {
|
||||
response: {
|
||||
status: 503,
|
||||
data: { error: 'Service temporarily unavailable' }
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
data: {
|
||||
success: true,
|
||||
patient: mockFactory.healthcareMocks.createHIPAACompliantData('patient', 'provider')
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const result = await toolGenerator.executeTool(toolName, parameters);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(attemptCount).toBe(3); // Should have retried twice before success
|
||||
});
|
||||
|
||||
test('should handle circuit breaker pattern', async () => {
|
||||
const toolName = 'provider_create_labscreate';
|
||||
const parameters = {
|
||||
lab_data: {
|
||||
test_type: 'CBC',
|
||||
patient_id: 'patient_123'
|
||||
}
|
||||
};
|
||||
|
||||
// Mock circuit breaker open
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/labs/create', null, true, {
|
||||
response: {
|
||||
status: 503,
|
||||
data: {
|
||||
error: 'Circuit breaker open',
|
||||
error_code: 'CIRCUIT_BREAKER_OPEN',
|
||||
message: 'Lab service circuit breaker is open due to repeated failures',
|
||||
circuit_state: 'open',
|
||||
failure_count: 10,
|
||||
failure_threshold: 5,
|
||||
next_attempt_allowed: new Date(Date.now() + 300000).toISOString(), // 5 minutes
|
||||
fallback_available: true,
|
||||
fallback_action: 'manual_lab_order'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected circuit breaker error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(503);
|
||||
expect(error.response.data.error_code).toBe('CIRCUIT_BREAKER_OPEN');
|
||||
expect(error.response.data.fallback_available).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle graceful degradation', async () => {
|
||||
const toolName = 'provider_create_emrcreateAppointment';
|
||||
const parameters = {
|
||||
patient_id: 'patient_123',
|
||||
practitioner_id: 'provider_456',
|
||||
appointment_date: '2025-07-15',
|
||||
appointment_time: '10:00'
|
||||
};
|
||||
|
||||
// Mock degraded service response
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/create-appointment', {
|
||||
status: 200,
|
||||
data: {
|
||||
success: true,
|
||||
appointment: mockFactory.healthcareMocks.generateMockAppointment(),
|
||||
service_status: 'degraded',
|
||||
degraded_features: [
|
||||
'email_notifications',
|
||||
'calendar_sync',
|
||||
'reminder_service'
|
||||
],
|
||||
available_features: [
|
||||
'appointment_creation',
|
||||
'basic_scheduling',
|
||||
'patient_notification'
|
||||
],
|
||||
estimated_full_service_restoration: '2025-07-09T16:00:00Z'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await toolGenerator.executeTool(toolName, parameters);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.service_status).toBe('degraded');
|
||||
expect(result.data.degraded_features).toContain('email_notifications');
|
||||
expect(result.data.available_features).toContain('appointment_creation');
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user