Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/health-endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,13 @@ Nested grouping follows the same convention:
- `database`: `status`, `responseTime` (when connected)
- `syncing`: `status`, `latestIndexedLedger`, `observedHeadLedger`, `syncLagLedgers`
- `services`: ordered as `API Server`, `Database`, `Chain Sync`
- `timeouts`: `database_timeout_ms`, `cache_timeout_ms`

### Detailed health fields

The `/api/v1/health/detailed` response also includes an explicit `timeouts` object with public-safe dependency timeout values:

- `database_timeout_ms` (number) — configured database query timeout in milliseconds.
- `cache_timeout_ms` (number) — configured public cache timeout in milliseconds.

These values are intentionally numeric-only and do not include connection strings, hostnames, credentials, or other internal topology details.
16 changes: 16 additions & 0 deletions src/modules/health/health.controllers.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ jest.mock('../../config', () => ({
MODE: 'production',
PORT: 3000,
INDEXER_HEARTBEAT_STALE_THRESHOLD_MS: 300000,
DB_QUERY_TIMEOUT_MS: 5000,
},
appConfig: {
allowedOrigins: [],
Expand Down Expand Up @@ -164,4 +165,19 @@ describe('healthCheck() — simulated database failure in production mode', () =
expect(dbService).toBeDefined();
expect(dbService.status).toBe('unhealthy');
});

it('includes public-safe timeout metadata in detailed health output', async () => {
queryRawMock.mockResolvedValue([{ '?column?': 1 }]);

const res = mockResponse();
await healthCheck(mockRequest(), res);

expect(res.body).toHaveProperty('timeouts');
expect(res.body.timeouts).toEqual({
database_timeout_ms: 5000,
cache_timeout_ms: 300000,
});
expect(typeof res.body.timeouts.database_timeout_ms).toBe('number');
expect(typeof res.body.timeouts.cache_timeout_ms).toBe('number');
});
});
25 changes: 25 additions & 0 deletions src/modules/health/health.controllers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ jest.mock('../../config', () => ({
MODE: 'test',
PORT: 3000,
INDEXER_HEARTBEAT_STALE_THRESHOLD_MS: 300000,
DB_QUERY_TIMEOUT_MS: 5000,
},
appConfig: {
allowedOrigins: [],
Expand All @@ -24,13 +25,15 @@ jest.mock('../../utils/indexer-cursor-staleness.utils', () => ({
}));

import {
healthCheck,
indexerHeartbeatCheck,
recordIndexerHeartbeat,
readinessCheck,
} from './health.controllers';
import { indexerHeartbeat } from '../../utils/heartbeat.service';
import { checkIndexerCursorStalenessFromStore } from '../../utils/indexer-cursor-staleness.utils';
import { prisma } from '../../utils/prisma.utils';
import { PUBLIC_ENDPOINT_CACHE_SECONDS } from '../../constants/public-endpoint-cache.constants';

const checkCursorStalenessMock =
checkIndexerCursorStalenessFromStore as jest.MockedFunction<
Expand Down Expand Up @@ -211,4 +214,26 @@ describe('Readiness Controller', () => {
expect(typeof res.body.latencyMs).toBe('number');
});
});

describe('healthCheck()', () => {
beforeEach(() => {
queryRawMock.mockReset();
});

it('includes sanitized dependency timeout metadata in detailed health output', async () => {
queryRawMock.mockResolvedValue([{ '?column?': 1 }]);
const res = mockResponse();

await healthCheck(mockRequest(), res);

expect(res.statusCode).toBe(200);
expect(res.body).toHaveProperty('timeouts');
expect(res.body.timeouts).toEqual({
database_timeout_ms: 5000,
cache_timeout_ms: PUBLIC_ENDPOINT_CACHE_SECONDS.short * 1000,
});
expect(typeof res.body.timeouts.database_timeout_ms).toBe('number');
expect(typeof res.body.timeouts.cache_timeout_ms).toBe('number');
});
});
});
12 changes: 12 additions & 0 deletions src/modules/health/health.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ interface ReadinessCheck {
error?: string;
}

interface HealthTimeouts {
database_timeout_ms: number;
cache_timeout_ms: number;
}

interface HealthStatus {
success: boolean;
message: string;
Expand All @@ -57,6 +62,7 @@ interface HealthStatus {
platform: string;
nodeVersion: string;
};
timeouts: HealthTimeouts;
database?: {
status: 'connected' | 'disconnected';
responseTime?: number;
Expand Down Expand Up @@ -98,6 +104,11 @@ export const healthCheck = async (_: Request, res: Response): Promise<void> => {

const syncStatus = await getChainSyncStatus();

const healthTimeouts: HealthTimeouts = {
database_timeout_ms: envConfig.DB_QUERY_TIMEOUT_MS,
cache_timeout_ms: PUBLIC_ENDPOINT_CACHE_SECONDS.short * 1000,
};

const healthData: HealthStatus = {
success: true,
message: 'Access Layer server is running',
Expand All @@ -117,6 +128,7 @@ export const healthCheck = async (_: Request, res: Response): Promise<void> => {
platform: process.platform,
nodeVersion: process.version,
},
timeouts: healthTimeouts,
database: dbStatus,
syncing: syncStatus || undefined,
services: [
Expand Down
Loading