198 lines
5.0 KiB
TypeScript
198 lines
5.0 KiB
TypeScript
/**
|
|
* In-Memory Health Check Adapter
|
|
*
|
|
* Simulates API health check responses for testing purposes.
|
|
* This adapter allows controlled testing of health check scenarios
|
|
* without making actual HTTP requests.
|
|
*/
|
|
|
|
import {
|
|
HealthCheckQuery,
|
|
ConnectionStatus,
|
|
ConnectionHealth,
|
|
HealthCheckResult,
|
|
} from '../../../../core/health/ports/HealthCheckQuery';
|
|
|
|
export interface HealthCheckResponse {
|
|
healthy: boolean;
|
|
responseTime: number;
|
|
error?: string;
|
|
timestamp: Date;
|
|
}
|
|
|
|
export class InMemoryHealthCheckAdapter implements HealthCheckQuery {
|
|
private responses: Map<string, HealthCheckResponse> = new Map();
|
|
public shouldFail: boolean = false;
|
|
public failError: string = 'Network error';
|
|
private responseTime: number = 50;
|
|
private health: ConnectionHealth = {
|
|
status: 'disconnected',
|
|
lastCheck: null,
|
|
lastSuccess: null,
|
|
lastFailure: null,
|
|
consecutiveFailures: 0,
|
|
totalRequests: 0,
|
|
successfulRequests: 0,
|
|
failedRequests: 0,
|
|
averageResponseTime: 0,
|
|
};
|
|
|
|
/**
|
|
* Configure the adapter to return a specific response
|
|
*/
|
|
configureResponse(endpoint: string, response: HealthCheckResponse): void {
|
|
this.responses.set(endpoint, response);
|
|
}
|
|
|
|
/**
|
|
* Configure the adapter to fail all requests
|
|
*/
|
|
setShouldFail(shouldFail: boolean, error?: string): void {
|
|
this.shouldFail = shouldFail;
|
|
if (error) {
|
|
this.failError = error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the response time for health checks
|
|
*/
|
|
setResponseTime(time: number): void {
|
|
this.responseTime = time;
|
|
}
|
|
|
|
/**
|
|
* Perform a health check against an endpoint
|
|
*/
|
|
async performHealthCheck(): Promise<HealthCheckResult> {
|
|
// Simulate network delay
|
|
await new Promise(resolve => setTimeout(resolve, this.responseTime));
|
|
|
|
if (this.shouldFail) {
|
|
this.recordFailure(this.failError);
|
|
return {
|
|
healthy: false,
|
|
responseTime: this.responseTime,
|
|
error: this.failError,
|
|
timestamp: new Date(),
|
|
};
|
|
}
|
|
|
|
// Default successful response
|
|
this.recordSuccess(this.responseTime);
|
|
return {
|
|
healthy: true,
|
|
responseTime: this.responseTime,
|
|
timestamp: new Date(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get current connection status
|
|
*/
|
|
getStatus(): ConnectionStatus {
|
|
return this.health.status;
|
|
}
|
|
|
|
/**
|
|
* Get detailed health information
|
|
*/
|
|
getHealth(): ConnectionHealth {
|
|
return { ...this.health };
|
|
}
|
|
|
|
/**
|
|
* Get reliability percentage
|
|
*/
|
|
getReliability(): number {
|
|
if (this.health.totalRequests === 0) return 0;
|
|
return (this.health.successfulRequests / this.health.totalRequests) * 100;
|
|
}
|
|
|
|
/**
|
|
* Check if API is currently available
|
|
*/
|
|
isAvailable(): boolean {
|
|
return this.health.status === 'connected' || this.health.status === 'degraded';
|
|
}
|
|
|
|
/**
|
|
* Record a successful health check
|
|
*/
|
|
private recordSuccess(responseTime: number): void {
|
|
this.health.totalRequests++;
|
|
this.health.successfulRequests++;
|
|
this.health.consecutiveFailures = 0;
|
|
this.health.lastSuccess = new Date();
|
|
this.health.lastCheck = new Date();
|
|
|
|
// Update average response time
|
|
const total = this.health.successfulRequests;
|
|
this.health.averageResponseTime =
|
|
((this.health.averageResponseTime * (total - 1)) + responseTime) / total;
|
|
|
|
this.updateStatus();
|
|
}
|
|
|
|
/**
|
|
* Record a failed health check
|
|
*/
|
|
private recordFailure(error: string): void {
|
|
this.health.totalRequests++;
|
|
this.health.failedRequests++;
|
|
this.health.consecutiveFailures++;
|
|
this.health.lastFailure = new Date();
|
|
this.health.lastCheck = new Date();
|
|
|
|
this.updateStatus();
|
|
}
|
|
|
|
/**
|
|
* Update connection status based on current metrics
|
|
*/
|
|
private updateStatus(): void {
|
|
const reliability = this.health.totalRequests > 0
|
|
? this.health.successfulRequests / this.health.totalRequests
|
|
: 0;
|
|
|
|
// More nuanced status determination
|
|
if (this.health.totalRequests === 0) {
|
|
// No requests yet - don't assume disconnected
|
|
this.health.status = 'checking';
|
|
} else if (this.health.consecutiveFailures >= 3) {
|
|
// Multiple consecutive failures indicates real connectivity issue
|
|
this.health.status = 'disconnected';
|
|
} else if (reliability < 0.7 && this.health.totalRequests >= 5) {
|
|
// Only degrade if we have enough samples and reliability is low
|
|
this.health.status = 'degraded';
|
|
} else if (reliability >= 0.7 || this.health.successfulRequests > 0) {
|
|
// If we have any successes, we're connected
|
|
this.health.status = 'connected';
|
|
} else {
|
|
// Default to checking if uncertain
|
|
this.health.status = 'checking';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all configured responses and settings
|
|
*/
|
|
clear(): void {
|
|
this.responses.clear();
|
|
this.shouldFail = false;
|
|
this.failError = 'Network error';
|
|
this.responseTime = 50;
|
|
this.health = {
|
|
status: 'disconnected',
|
|
lastCheck: null,
|
|
lastSuccess: null,
|
|
lastFailure: null,
|
|
consecutiveFailures: 0,
|
|
totalRequests: 0,
|
|
successfulRequests: 0,
|
|
failedRequests: 0,
|
|
averageResponseTime: 0,
|
|
};
|
|
}
|
|
}
|