first
This commit is contained in:
554
tests/error-handling/authentication-errors.test.js
Normal file
554
tests/error-handling/authentication-errors.test.js
Normal file
@@ -0,0 +1,554 @@
|
||||
/**
|
||||
* @fileoverview Tests for authentication error handling and edge cases
|
||||
* Tests authentication failures, token expiration, session management, and security scenarios
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
||||
import { mockFactory } from '../mocks/mockFactory.js';
|
||||
|
||||
describe('Authentication Error Handling and Edge Cases', () => {
|
||||
let mockEnv;
|
||||
let toolGenerator;
|
||||
|
||||
beforeEach(() => {
|
||||
mockEnv = mockFactory.createMockEnvironment({
|
||||
authTypes: ['provider', 'patient', 'partner'],
|
||||
enableHttpMocks: true,
|
||||
enableAuthMocks: true
|
||||
});
|
||||
|
||||
toolGenerator = mockEnv.toolGenerator;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFactory.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Authentication Failure Scenarios', () => {
|
||||
test('should handle invalid credentials gracefully', async () => {
|
||||
const toolName = 'provider_create_emrregisterPatient';
|
||||
const parameters = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john@test.com',
|
||||
dateOfBirth: '1990-01-01'
|
||||
};
|
||||
|
||||
// Mock authentication failure
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/register-patients', null, true, {
|
||||
response: {
|
||||
status: 401,
|
||||
data: {
|
||||
error: 'Authentication failed',
|
||||
error_code: 'AUTH_INVALID_CREDENTIALS',
|
||||
message: 'Invalid username or password',
|
||||
retry_allowed: true,
|
||||
lockout_warning: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected authentication error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(401);
|
||||
expect(error.response.data.error_code).toBe('AUTH_INVALID_CREDENTIALS');
|
||||
expect(error.response.data.retry_allowed).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle account lockout scenarios', async () => {
|
||||
const toolName = 'public_create_login';
|
||||
const parameters = {
|
||||
username: 'locked_user',
|
||||
password: 'password'
|
||||
};
|
||||
|
||||
// Mock account lockout
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/login', null, true, {
|
||||
response: {
|
||||
status: 423,
|
||||
data: {
|
||||
error: 'Account locked',
|
||||
error_code: 'AUTH_ACCOUNT_LOCKED',
|
||||
message: 'Account temporarily locked due to multiple failed login attempts',
|
||||
lockout_duration: 900, // 15 minutes in seconds
|
||||
lockout_expiry: new Date(Date.now() + 900000).toISOString(),
|
||||
unlock_methods: ['time_expiry', 'admin_unlock', 'password_reset'],
|
||||
failed_attempts: 5,
|
||||
max_attempts: 5
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected account lockout error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(423);
|
||||
expect(error.response.data.error_code).toBe('AUTH_ACCOUNT_LOCKED');
|
||||
expect(error.response.data.lockout_duration).toBe(900);
|
||||
expect(error.response.data.unlock_methods).toContain('password_reset');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle disabled account scenarios', async () => {
|
||||
const toolName = 'patient_create_patientlogin';
|
||||
const parameters = {
|
||||
email: 'disabled@test.com',
|
||||
password: 'password'
|
||||
};
|
||||
|
||||
// Mock disabled account
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/patient/login', null, true, {
|
||||
response: {
|
||||
status: 403,
|
||||
data: {
|
||||
error: 'Account disabled',
|
||||
error_code: 'AUTH_ACCOUNT_DISABLED',
|
||||
message: 'Account has been disabled by administrator',
|
||||
reason: 'policy_violation',
|
||||
contact_support: true,
|
||||
support_contact: 'support@healthcare.com',
|
||||
appeal_process: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected account disabled error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(403);
|
||||
expect(error.response.data.error_code).toBe('AUTH_ACCOUNT_DISABLED');
|
||||
expect(error.response.data.contact_support).toBe(true);
|
||||
expect(error.response.data.appeal_process).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token Expiration and Session Management', () => {
|
||||
test('should handle expired authentication tokens', async () => {
|
||||
const toolName = 'provider_create_getPatientInfo';
|
||||
const parameters = {
|
||||
patientId: 123
|
||||
};
|
||||
|
||||
// Mock expired token
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/get-patient-info/123', null, true, {
|
||||
response: {
|
||||
status: 401,
|
||||
data: {
|
||||
error: 'Token expired',
|
||||
error_code: 'AUTH_TOKEN_EXPIRED',
|
||||
message: 'Authentication token has expired',
|
||||
expired_at: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago
|
||||
refresh_available: true,
|
||||
refresh_endpoint: '/api/token/refresh',
|
||||
login_required: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected token expired error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(401);
|
||||
expect(error.response.data.error_code).toBe('AUTH_TOKEN_EXPIRED');
|
||||
expect(error.response.data.refresh_available).toBe(true);
|
||||
expect(error.response.data.refresh_endpoint).toBe('/api/token/refresh');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle invalid or malformed tokens', async () => {
|
||||
const toolName = 'provider_create_getPatientInfo';
|
||||
const parameters = {
|
||||
patientId: 123
|
||||
};
|
||||
|
||||
// Mock invalid token
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/get-patient-info/123', null, true, {
|
||||
response: {
|
||||
status: 401,
|
||||
data: {
|
||||
error: 'Invalid token',
|
||||
error_code: 'AUTH_TOKEN_INVALID',
|
||||
message: 'Authentication token is invalid or malformed',
|
||||
token_format_error: true,
|
||||
login_required: true,
|
||||
security_incident: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected invalid token error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(401);
|
||||
expect(error.response.data.error_code).toBe('AUTH_TOKEN_INVALID');
|
||||
expect(error.response.data.token_format_error).toBe(true);
|
||||
expect(error.response.data.login_required).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle concurrent session conflicts', async () => {
|
||||
const toolName = 'patient_create_patientlogin';
|
||||
const parameters = {
|
||||
email: 'patient@test.com',
|
||||
password: 'password'
|
||||
};
|
||||
|
||||
// Mock concurrent session conflict
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/patient/login', null, true, {
|
||||
response: {
|
||||
status: 409,
|
||||
data: {
|
||||
error: 'Session conflict',
|
||||
error_code: 'AUTH_SESSION_CONFLICT',
|
||||
message: 'Maximum concurrent sessions exceeded',
|
||||
current_sessions: 3,
|
||||
max_allowed_sessions: 2,
|
||||
active_sessions: [
|
||||
{
|
||||
session_id: 'session_1',
|
||||
device: 'Desktop Browser',
|
||||
location: 'New York, NY',
|
||||
last_activity: new Date(Date.now() - 300000).toISOString()
|
||||
},
|
||||
{
|
||||
session_id: 'session_2',
|
||||
device: 'Mobile App',
|
||||
location: 'Boston, MA',
|
||||
last_activity: new Date(Date.now() - 600000).toISOString()
|
||||
}
|
||||
],
|
||||
options: ['terminate_oldest_session', 'terminate_specific_session', 'upgrade_plan']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected session conflict error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(409);
|
||||
expect(error.response.data.error_code).toBe('AUTH_SESSION_CONFLICT');
|
||||
expect(error.response.data.current_sessions).toBe(3);
|
||||
expect(error.response.data.active_sessions).toHaveLength(2);
|
||||
expect(error.response.data.options).toContain('terminate_oldest_session');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle session timeout scenarios', async () => {
|
||||
const toolName = 'provider_create_addVital';
|
||||
const parameters = {
|
||||
patientId: 123,
|
||||
provider_id: 456,
|
||||
blood_presssure: '120/80'
|
||||
};
|
||||
|
||||
// Mock session timeout
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/add-vital/123', null, true, {
|
||||
response: {
|
||||
status: 401,
|
||||
data: {
|
||||
error: 'Session timeout',
|
||||
error_code: 'AUTH_SESSION_TIMEOUT',
|
||||
message: 'Session has timed out due to inactivity',
|
||||
timeout_duration: 1800, // 30 minutes
|
||||
last_activity: new Date(Date.now() - 1800000).toISOString(),
|
||||
auto_save_available: true,
|
||||
saved_data: {
|
||||
form_data: parameters,
|
||||
save_timestamp: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected session timeout error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(401);
|
||||
expect(error.response.data.error_code).toBe('AUTH_SESSION_TIMEOUT');
|
||||
expect(error.response.data.auto_save_available).toBe(true);
|
||||
expect(error.response.data.saved_data.form_data).toEqual(parameters);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permission and Authorization Errors', () => {
|
||||
test('should handle insufficient permissions', async () => {
|
||||
const toolName = 'provider_create_emrupdateProviderProfile';
|
||||
const parameters = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe'
|
||||
};
|
||||
|
||||
// Mock insufficient permissions
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/emr/update-provider-profile', null, true, {
|
||||
response: {
|
||||
status: 403,
|
||||
data: {
|
||||
error: 'Insufficient permissions',
|
||||
error_code: 'AUTH_INSUFFICIENT_PERMISSIONS',
|
||||
message: 'User does not have required permissions for this action',
|
||||
required_permissions: ['update:provider_profile', 'admin:user_management'],
|
||||
user_permissions: ['read:patient_data', 'write:patient_data'],
|
||||
missing_permissions: ['update:provider_profile', 'admin:user_management'],
|
||||
request_access_available: true,
|
||||
approval_workflow: 'supervisor_approval'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected insufficient permissions error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(403);
|
||||
expect(error.response.data.error_code).toBe('AUTH_INSUFFICIENT_PERMISSIONS');
|
||||
expect(error.response.data.missing_permissions).toContain('update:provider_profile');
|
||||
expect(error.response.data.request_access_available).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle role-based access violations', async () => {
|
||||
const toolName = 'patient_get_providerAnalytics';
|
||||
const parameters = {
|
||||
provider_id: 'provider_123'
|
||||
};
|
||||
|
||||
// Mock role-based access violation
|
||||
mockFactory.httpMocks.mockRequest('GET', '/api/provider-analytics/provider_123', null, true, {
|
||||
response: {
|
||||
status: 403,
|
||||
data: {
|
||||
error: 'Role-based access violation',
|
||||
error_code: 'AUTH_ROLE_VIOLATION',
|
||||
message: 'Patient role cannot access provider analytics',
|
||||
user_role: 'patient',
|
||||
required_role: 'provider',
|
||||
allowed_roles: ['provider', 'admin', 'supervisor'],
|
||||
resource_type: 'provider_analytics',
|
||||
escalation_available: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected role violation error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(403);
|
||||
expect(error.response.data.error_code).toBe('AUTH_ROLE_VIOLATION');
|
||||
expect(error.response.data.user_role).toBe('patient');
|
||||
expect(error.response.data.required_role).toBe('provider');
|
||||
expect(error.response.data.escalation_available).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle cross-tenant data access violations', async () => {
|
||||
const toolName = 'provider_create_getPatientInfo';
|
||||
const parameters = {
|
||||
patientId: 999 // Patient from different organization
|
||||
};
|
||||
|
||||
// Mock cross-tenant violation
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/get-patient-info/999', null, true, {
|
||||
response: {
|
||||
status: 403,
|
||||
data: {
|
||||
error: 'Cross-tenant access violation',
|
||||
error_code: 'AUTH_CROSS_TENANT_VIOLATION',
|
||||
message: 'Cannot access patient data from different organization',
|
||||
user_organization: 'org_123',
|
||||
patient_organization: 'org_456',
|
||||
data_sharing_agreement: false,
|
||||
hipaa_violation_risk: true,
|
||||
audit_logged: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected cross-tenant violation error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(403);
|
||||
expect(error.response.data.error_code).toBe('AUTH_CROSS_TENANT_VIOLATION');
|
||||
expect(error.response.data.hipaa_violation_risk).toBe(true);
|
||||
expect(error.response.data.audit_logged).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Security Incident Handling', () => {
|
||||
test('should handle suspicious activity detection', async () => {
|
||||
const toolName = 'provider_create_getPatientInfo';
|
||||
const parameters = {
|
||||
patientId: 123
|
||||
};
|
||||
|
||||
// Mock suspicious activity detection
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/get-patient-info/123', null, true, {
|
||||
response: {
|
||||
status: 429,
|
||||
data: {
|
||||
error: 'Suspicious activity detected',
|
||||
error_code: 'SECURITY_SUSPICIOUS_ACTIVITY',
|
||||
message: 'Unusual access patterns detected, temporary restriction applied',
|
||||
detection_triggers: [
|
||||
'rapid_successive_requests',
|
||||
'unusual_access_time',
|
||||
'geographic_anomaly'
|
||||
],
|
||||
restriction_duration: 3600, // 1 hour
|
||||
security_review_required: true,
|
||||
incident_id: 'SEC_001',
|
||||
contact_security: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected suspicious activity error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(429);
|
||||
expect(error.response.data.error_code).toBe('SECURITY_SUSPICIOUS_ACTIVITY');
|
||||
expect(error.response.data.detection_triggers).toContain('rapid_successive_requests');
|
||||
expect(error.response.data.security_review_required).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle potential data breach scenarios', async () => {
|
||||
const toolName = 'provider_bulk_patientExport';
|
||||
const parameters = {
|
||||
patient_ids: Array.from({length: 500}, (_, i) => i + 1),
|
||||
export_format: 'csv'
|
||||
};
|
||||
|
||||
// Mock potential breach detection
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/bulk-export-patients', null, true, {
|
||||
response: {
|
||||
status: 403,
|
||||
data: {
|
||||
error: 'Potential data breach detected',
|
||||
error_code: 'SECURITY_BREACH_RISK',
|
||||
message: 'Large data export request flagged for security review',
|
||||
risk_factors: [
|
||||
'bulk_export_threshold_exceeded',
|
||||
'unusual_user_behavior',
|
||||
'sensitive_data_included'
|
||||
],
|
||||
requested_records: 500,
|
||||
threshold: 100,
|
||||
automatic_block: true,
|
||||
incident_reported: true,
|
||||
incident_id: 'BREACH_001',
|
||||
required_approvals: ['security_officer', 'privacy_officer', 'ciso']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected breach risk error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(403);
|
||||
expect(error.response.data.error_code).toBe('SECURITY_BREACH_RISK');
|
||||
expect(error.response.data.automatic_block).toBe(true);
|
||||
expect(error.response.data.required_approvals).toContain('security_officer');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle compromised account scenarios', async () => {
|
||||
const toolName = 'provider_create_updatePassword';
|
||||
const parameters = {
|
||||
new_password: 'newpassword123'
|
||||
};
|
||||
|
||||
// Mock compromised account detection
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/update-password', null, true, {
|
||||
response: {
|
||||
status: 423,
|
||||
data: {
|
||||
error: 'Account potentially compromised',
|
||||
error_code: 'SECURITY_ACCOUNT_COMPROMISED',
|
||||
message: 'Account locked due to potential security compromise',
|
||||
compromise_indicators: [
|
||||
'login_from_suspicious_location',
|
||||
'password_in_breach_database',
|
||||
'unusual_activity_pattern'
|
||||
],
|
||||
immediate_actions_taken: [
|
||||
'account_locked',
|
||||
'sessions_terminated',
|
||||
'security_team_notified'
|
||||
],
|
||||
recovery_process: {
|
||||
identity_verification_required: true,
|
||||
password_reset_mandatory: true,
|
||||
security_questions_required: true,
|
||||
admin_approval_needed: true
|
||||
},
|
||||
incident_id: 'COMP_001'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected compromised account error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(423);
|
||||
expect(error.response.data.error_code).toBe('SECURITY_ACCOUNT_COMPROMISED');
|
||||
expect(error.response.data.immediate_actions_taken).toContain('account_locked');
|
||||
expect(error.response.data.recovery_process.identity_verification_required).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rate Limiting and Throttling', () => {
|
||||
test('should handle rate limit exceeded scenarios', async () => {
|
||||
const toolName = 'public_create_checkEmail';
|
||||
const parameters = {
|
||||
email: 'test@test.com'
|
||||
};
|
||||
|
||||
// Mock rate limit exceeded
|
||||
mockFactory.httpMocks.mockRequest('POST', '/api/check-email', null, true, {
|
||||
response: {
|
||||
status: 429,
|
||||
data: {
|
||||
error: 'Rate limit exceeded',
|
||||
error_code: 'RATE_LIMIT_EXCEEDED',
|
||||
message: 'Too many requests, please try again later',
|
||||
limit: 100,
|
||||
window: 3600, // 1 hour
|
||||
remaining: 0,
|
||||
reset_time: new Date(Date.now() + 3600000).toISOString(),
|
||||
retry_after: 3600,
|
||||
rate_limit_type: 'user_based'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await toolGenerator.executeTool(toolName, parameters);
|
||||
fail('Expected rate limit error');
|
||||
} catch (error) {
|
||||
expect(error.response.status).toBe(429);
|
||||
expect(error.response.data.error_code).toBe('RATE_LIMIT_EXCEEDED');
|
||||
expect(error.response.data.limit).toBe(100);
|
||||
expect(error.response.data.remaining).toBe(0);
|
||||
expect(error.response.data.retry_after).toBe(3600);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user