feat: 添加API运行时健康检查
This commit is contained in:
parent
ce20c350cd
commit
0f47644fd6
@ -1009,50 +1009,77 @@ Add `RuntimeModule` to `imports` after `CommonModule`:
|
||||
|
||||
```ts
|
||||
import { RuntimeHealthService } from '../../src/runtime/health/runtime-health.service';
|
||||
import type { RuntimeSafeConfigSnapshot } from '../../src/runtime/config/runtime-config.types';
|
||||
|
||||
function createSnapshot(
|
||||
checks: RuntimeSafeConfigSnapshot['checks'],
|
||||
): RuntimeSafeConfigSnapshot {
|
||||
return {
|
||||
app: { nodeEnv: 'test', port: 48085 },
|
||||
database: {
|
||||
host: 'mysql',
|
||||
port: 3306,
|
||||
database: 'kt',
|
||||
username: 'root',
|
||||
synchronize: false,
|
||||
},
|
||||
loki: {
|
||||
transportEnabled: true,
|
||||
httpRequestPushEnabled: true,
|
||||
queryConfigured: true,
|
||||
host: 'https://loki-push.example.test',
|
||||
queryHost: 'https://loki-query.example.test',
|
||||
environment: 'test',
|
||||
tenantId: 'kt',
|
||||
username: 'loki-user',
|
||||
passwordConfigured: true,
|
||||
},
|
||||
minio: {
|
||||
endpoint: 'minio',
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
accessKey: 'mi***ey',
|
||||
bucket: 'kt-template-online',
|
||||
},
|
||||
wordpress: {
|
||||
baseUrl: 'https://blog.example.test',
|
||||
hostHeader: 'blog.example.test',
|
||||
adminUsername: 'wordpress-admin',
|
||||
passwordConfigured: true,
|
||||
timeoutMs: 15000,
|
||||
loginTimeoutMs: 3000,
|
||||
availabilityTtlMs: 60000,
|
||||
},
|
||||
qqbot: {
|
||||
reverseWsPath: '/qqbot/onebot/reverse',
|
||||
reverseWsToken: 'qq***en',
|
||||
napcatRoot: '/vol1/docker/napcat',
|
||||
napcatContainerMode: 'ssh',
|
||||
napcatSshTarget: 'nas',
|
||||
napcatSshPort: 2202,
|
||||
napcatSshKeyPath: '/home/kt/.ssh/napcat',
|
||||
napcatReverseWsBase: 'ws://api.example.test/onebot',
|
||||
napcatWebuiBaseUrl: 'http://127.0.0.1:6099',
|
||||
napcatWebuiToken: 'na***en',
|
||||
},
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
||||
function createService(snapshot: RuntimeSafeConfigSnapshot) {
|
||||
return new RuntimeHealthService({
|
||||
getSafeSnapshot: jest.fn(() => snapshot),
|
||||
} as any);
|
||||
}
|
||||
|
||||
describe('RuntimeHealthService', () => {
|
||||
it('returns ready when required config is present and optional config is present', () => {
|
||||
const service = new RuntimeHealthService({
|
||||
getSafeSnapshot: () => ({
|
||||
app: { nodeEnv: 'test', port: 48085 },
|
||||
database: {
|
||||
host: 'mysql',
|
||||
port: 3306,
|
||||
database: 'kt',
|
||||
username: 'root',
|
||||
synchronize: false,
|
||||
},
|
||||
loki: {
|
||||
transportEnabled: false,
|
||||
httpRequestPushEnabled: false,
|
||||
queryConfigured: false,
|
||||
host: '',
|
||||
queryHost: '',
|
||||
environment: 'test',
|
||||
tenantId: '',
|
||||
username: '',
|
||||
passwordConfigured: false,
|
||||
},
|
||||
minio: {
|
||||
endpoint: 'minio',
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
accessKey: 'mi***ey',
|
||||
},
|
||||
wordpress: { endpoint: 'https://example.test', username: 'wo***er' },
|
||||
qqbot: {
|
||||
reverseWsUrl: 'ws://127.0.0.1:3001',
|
||||
napcatDataRoot: '/data/napcat',
|
||||
napcatSshHost: 'nas',
|
||||
napcatSshPort: 22,
|
||||
napcatSshUser: 'root',
|
||||
},
|
||||
checks: [
|
||||
{ key: 'DB_HOST', level: 'required', present: true },
|
||||
{ key: 'MINIO_ENDPOINT', level: 'optional', present: true },
|
||||
],
|
||||
}),
|
||||
} as any);
|
||||
it('returns ready when required and optional checks are present', () => {
|
||||
const service = createService(
|
||||
createSnapshot([
|
||||
{ key: 'DB_HOST', level: 'required', present: true },
|
||||
{ key: 'MINIO_ENDPOINT', level: 'optional', present: true },
|
||||
]),
|
||||
);
|
||||
|
||||
expect(service.getRuntimeHealth()).toEqual(
|
||||
expect.objectContaining({
|
||||
@ -1063,52 +1090,17 @@ describe('RuntimeHealthService', () => {
|
||||
});
|
||||
|
||||
it('returns blocked when required config is missing', () => {
|
||||
const service = new RuntimeHealthService({
|
||||
getSafeSnapshot: () => ({
|
||||
app: { nodeEnv: 'test', port: 48085 },
|
||||
database: {
|
||||
host: '',
|
||||
port: 3306,
|
||||
database: '',
|
||||
username: '',
|
||||
synchronize: false,
|
||||
const service = createService(
|
||||
createSnapshot([
|
||||
{
|
||||
key: 'DB_PASSWORD',
|
||||
level: 'required',
|
||||
present: false,
|
||||
message: 'DB_PASSWORD is not configured',
|
||||
},
|
||||
loki: {
|
||||
transportEnabled: false,
|
||||
httpRequestPushEnabled: false,
|
||||
queryConfigured: false,
|
||||
host: '',
|
||||
queryHost: '',
|
||||
environment: 'test',
|
||||
tenantId: '',
|
||||
username: '',
|
||||
passwordConfigured: false,
|
||||
},
|
||||
minio: { endpoint: '', port: 9000, useSSL: false, accessKey: '' },
|
||||
wordpress: { endpoint: '', username: '' },
|
||||
qqbot: {
|
||||
reverseWsUrl: '',
|
||||
napcatDataRoot: '',
|
||||
napcatSshHost: '',
|
||||
napcatSshPort: 22,
|
||||
napcatSshUser: '',
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
key: 'DB_PASSWORD',
|
||||
level: 'required',
|
||||
present: false,
|
||||
message: 'DB_PASSWORD is not configured',
|
||||
},
|
||||
{
|
||||
key: 'MINIO_ENDPOINT',
|
||||
level: 'optional',
|
||||
present: false,
|
||||
message: 'MINIO_ENDPOINT is not configured',
|
||||
},
|
||||
],
|
||||
}),
|
||||
} as any);
|
||||
{ key: 'MINIO_ENDPOINT', level: 'optional', present: true },
|
||||
]),
|
||||
);
|
||||
|
||||
const report = service.getRuntimeHealth();
|
||||
|
||||
@ -1121,6 +1113,31 @@ describe('RuntimeHealthService', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns degraded when only optional config is missing', () => {
|
||||
const service = createService(
|
||||
createSnapshot([
|
||||
{ key: 'DB_HOST', level: 'required', present: true },
|
||||
{
|
||||
key: 'LOKI_PASSWORD',
|
||||
level: 'optional',
|
||||
present: false,
|
||||
message: 'LOKI_PASSWORD is not configured',
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const report = service.getRuntimeHealth();
|
||||
|
||||
expect(report.status).toBe('degraded');
|
||||
expect(report.checks).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: 'config:LOKI_PASSWORD',
|
||||
status: 'degraded',
|
||||
critical: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@ -1128,10 +1145,11 @@ describe('RuntimeHealthService', () => {
|
||||
|
||||
```ts
|
||||
import { RuntimeHealthController } from '../../src/runtime/health/runtime-health.controller';
|
||||
import type { RuntimeHealthReport } from '../../src/runtime/health/runtime-health.types';
|
||||
|
||||
describe('RuntimeHealthController', () => {
|
||||
it('returns the runtime health report from the service', () => {
|
||||
const report = {
|
||||
const report: RuntimeHealthReport = {
|
||||
service: 'kt-template-online-api',
|
||||
checkedAt: '2026-06-13T00:00:00.000Z',
|
||||
status: 'ready',
|
||||
@ -1156,18 +1174,37 @@ describe('RuntimeHealthController', () => {
|
||||
username: '',
|
||||
passwordConfigured: false,
|
||||
},
|
||||
minio: { endpoint: '', port: 9000, useSSL: false, accessKey: '' },
|
||||
wordpress: { endpoint: '', username: '' },
|
||||
minio: {
|
||||
endpoint: 'minio',
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
accessKey: 'mi***ey',
|
||||
bucket: 'kt-template-online',
|
||||
},
|
||||
wordpress: {
|
||||
baseUrl: 'https://blog.example.test',
|
||||
hostHeader: 'blog.example.test',
|
||||
adminUsername: 'wordpress-admin',
|
||||
passwordConfigured: true,
|
||||
timeoutMs: 15000,
|
||||
loginTimeoutMs: 3000,
|
||||
availabilityTtlMs: 60000,
|
||||
},
|
||||
qqbot: {
|
||||
reverseWsUrl: '',
|
||||
napcatDataRoot: '',
|
||||
napcatSshHost: '',
|
||||
napcatSshPort: 22,
|
||||
napcatSshUser: '',
|
||||
reverseWsPath: '/qqbot/onebot/reverse',
|
||||
reverseWsToken: 'qq***en',
|
||||
napcatRoot: '/vol1/docker/napcat',
|
||||
napcatContainerMode: 'ssh',
|
||||
napcatSshTarget: 'nas',
|
||||
napcatSshPort: 2202,
|
||||
napcatSshKeyPath: '/home/kt/.ssh/napcat',
|
||||
napcatReverseWsBase: 'ws://api.example.test/onebot',
|
||||
napcatWebuiBaseUrl: 'http://127.0.0.1:6099',
|
||||
napcatWebuiToken: 'na***en',
|
||||
},
|
||||
checks: [],
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
const service = { getRuntimeHealth: jest.fn(() => report) };
|
||||
const controller = new RuntimeHealthController(service as any);
|
||||
|
||||
@ -1193,7 +1230,7 @@ pnpm run typecheck
|
||||
|
||||
```powershell
|
||||
git status --short
|
||||
git add src/runtime src/app.module.ts src/main.ts test/runtime
|
||||
git add src/runtime src/app.module.ts src/main.ts test/runtime docs/superpowers/plans/2026-06-13-api-runtime-foundation.md
|
||||
git diff --cached --check
|
||||
git commit -m "feat: 添加API运行时健康检查"
|
||||
```
|
||||
|
||||
@ -18,6 +18,7 @@ import { AdminModule } from './admin/admin.module';
|
||||
import { BlogModule } from './blog/blog.module';
|
||||
import { WordpressModule } from './wordpress/wordpress.module';
|
||||
import { QqbotModule } from './qqbot/qqbot.module';
|
||||
import { RuntimeModule } from './runtime';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -64,6 +65,7 @@ import { QqbotModule } from './qqbot/qqbot.module';
|
||||
}),
|
||||
MinioClientModule,
|
||||
CommonModule,
|
||||
RuntimeModule,
|
||||
AdminModule,
|
||||
BlogModule,
|
||||
WordpressModule,
|
||||
|
||||
@ -41,7 +41,8 @@ const swaggerGroups: SwaggerDocumentGroup[] = [
|
||||
path: 'api/wordpress',
|
||||
},
|
||||
{
|
||||
matcher: (path) => path === '/' || path.startsWith('/minio'),
|
||||
matcher: (path) =>
|
||||
path === '/' || path.startsWith('/minio') || path.startsWith('/health'),
|
||||
name: '基础能力',
|
||||
path: 'api/basic',
|
||||
},
|
||||
|
||||
16
src/runtime/health/runtime-health.controller.ts
Normal file
16
src/runtime/health/runtime-health.controller.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { RuntimeHealthService } from './runtime-health.service';
|
||||
import type { RuntimeHealthReport } from './runtime-health.types';
|
||||
|
||||
@ApiTags('Runtime Health')
|
||||
@Controller('health')
|
||||
export class RuntimeHealthController {
|
||||
constructor(private readonly runtimeHealthService: RuntimeHealthService) {}
|
||||
|
||||
@Get('runtime')
|
||||
@ApiOperation({ summary: 'Get machine-readable API runtime health' })
|
||||
getRuntimeHealth(): RuntimeHealthReport {
|
||||
return this.runtimeHealthService.getRuntimeHealth();
|
||||
}
|
||||
}
|
||||
64
src/runtime/health/runtime-health.service.ts
Normal file
64
src/runtime/health/runtime-health.service.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { RuntimeConfigService } from '../config/runtime-config.service';
|
||||
import {
|
||||
RuntimeHealthCheck,
|
||||
RuntimeHealthReport,
|
||||
RuntimeHealthStatus,
|
||||
} from './runtime-health.types';
|
||||
|
||||
@Injectable()
|
||||
export class RuntimeHealthService {
|
||||
constructor(private readonly runtimeConfigService: RuntimeConfigService) {}
|
||||
|
||||
getRuntimeHealth(): RuntimeHealthReport {
|
||||
const config = this.runtimeConfigService.getSafeSnapshot();
|
||||
const checks: RuntimeHealthCheck[] = [
|
||||
{
|
||||
name: 'process',
|
||||
status: 'live',
|
||||
critical: true,
|
||||
message: 'NestJS process answered runtime health request',
|
||||
},
|
||||
...config.checks.map((check) => ({
|
||||
name: `config:${check.key}`,
|
||||
status: this.getConfigCheckStatus(check.present, check.level),
|
||||
critical: check.level === 'required',
|
||||
message: check.present
|
||||
? `${check.key} is configured`
|
||||
: check.message ?? `${check.key} is not configured`,
|
||||
})),
|
||||
];
|
||||
|
||||
return {
|
||||
service: 'kt-template-online-api',
|
||||
checkedAt: new Date().toISOString(),
|
||||
status: this.aggregateStatus(checks),
|
||||
checks,
|
||||
config,
|
||||
};
|
||||
}
|
||||
|
||||
private getConfigCheckStatus(
|
||||
present: boolean,
|
||||
level: 'required' | 'optional',
|
||||
): RuntimeHealthStatus {
|
||||
if (present) return 'ready';
|
||||
return level === 'required' ? 'blocked' : 'degraded';
|
||||
}
|
||||
|
||||
private aggregateStatus(checks: RuntimeHealthCheck[]): RuntimeHealthStatus {
|
||||
if (checks.some((check) => check.critical && check.status === 'blocked')) {
|
||||
return 'blocked';
|
||||
}
|
||||
|
||||
if (checks.some((check) => check.status === 'degraded')) {
|
||||
return 'degraded';
|
||||
}
|
||||
|
||||
if (checks.every((check) => check.status === 'live')) {
|
||||
return 'live';
|
||||
}
|
||||
|
||||
return 'ready';
|
||||
}
|
||||
}
|
||||
19
src/runtime/health/runtime-health.types.ts
Normal file
19
src/runtime/health/runtime-health.types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { RuntimeSafeConfigSnapshot } from '../config/runtime-config.types';
|
||||
|
||||
export type RuntimeHealthStatus = 'live' | 'ready' | 'degraded' | 'blocked';
|
||||
|
||||
export interface RuntimeHealthCheck {
|
||||
name: string;
|
||||
status: RuntimeHealthStatus;
|
||||
critical: boolean;
|
||||
message: string;
|
||||
detail?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface RuntimeHealthReport {
|
||||
service: 'kt-template-online-api';
|
||||
checkedAt: string;
|
||||
status: RuntimeHealthStatus;
|
||||
checks: RuntimeHealthCheck[];
|
||||
config: RuntimeSafeConfigSnapshot;
|
||||
}
|
||||
9
src/runtime/index.ts
Normal file
9
src/runtime/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export * from './config/runtime-config.service';
|
||||
export * from './config/runtime-config.types';
|
||||
export * from './errors/runtime-error.types';
|
||||
export * from './evidence/runtime-evidence.service';
|
||||
export * from './evidence/runtime-evidence.types';
|
||||
export * from './health/runtime-health.controller';
|
||||
export * from './health/runtime-health.service';
|
||||
export * from './health/runtime-health.types';
|
||||
export * from './runtime.module';
|
||||
19
src/runtime/runtime.module.ts
Normal file
19
src/runtime/runtime.module.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { CommonModule } from '../common';
|
||||
import { RuntimeConfigService } from './config/runtime-config.service';
|
||||
import { RuntimeEvidenceService } from './evidence/runtime-evidence.service';
|
||||
import { RuntimeHealthController } from './health/runtime-health.controller';
|
||||
import { RuntimeHealthService } from './health/runtime-health.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, CommonModule],
|
||||
controllers: [RuntimeHealthController],
|
||||
providers: [
|
||||
RuntimeConfigService,
|
||||
RuntimeEvidenceService,
|
||||
RuntimeHealthService,
|
||||
],
|
||||
exports: [RuntimeConfigService, RuntimeEvidenceService, RuntimeHealthService],
|
||||
})
|
||||
export class RuntimeModule {}
|
||||
68
test/runtime/runtime-health.controller.spec.ts
Normal file
68
test/runtime/runtime-health.controller.spec.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { RuntimeHealthController } from '../../src/runtime/health/runtime-health.controller';
|
||||
import type { RuntimeHealthReport } from '../../src/runtime/health/runtime-health.types';
|
||||
|
||||
describe('RuntimeHealthController', () => {
|
||||
it('returns the service report and calls the service once', () => {
|
||||
const report: RuntimeHealthReport = {
|
||||
service: 'kt-template-online-api',
|
||||
checkedAt: '2026-06-13T00:00:00.000Z',
|
||||
status: 'ready',
|
||||
checks: [],
|
||||
config: {
|
||||
app: { nodeEnv: 'test', port: 48085 },
|
||||
database: {
|
||||
host: 'mysql',
|
||||
port: 3306,
|
||||
database: 'kt',
|
||||
username: 'root',
|
||||
synchronize: false,
|
||||
},
|
||||
loki: {
|
||||
transportEnabled: false,
|
||||
httpRequestPushEnabled: false,
|
||||
queryConfigured: false,
|
||||
host: '',
|
||||
queryHost: '',
|
||||
environment: 'test',
|
||||
tenantId: '',
|
||||
username: '',
|
||||
passwordConfigured: false,
|
||||
},
|
||||
minio: {
|
||||
endpoint: 'minio',
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
accessKey: 'mi***ey',
|
||||
bucket: 'kt-template-online',
|
||||
},
|
||||
wordpress: {
|
||||
baseUrl: 'https://blog.example.test',
|
||||
hostHeader: 'blog.example.test',
|
||||
adminUsername: 'wordpress-admin',
|
||||
passwordConfigured: true,
|
||||
timeoutMs: 15000,
|
||||
loginTimeoutMs: 3000,
|
||||
availabilityTtlMs: 60000,
|
||||
},
|
||||
qqbot: {
|
||||
reverseWsPath: '/qqbot/onebot/reverse',
|
||||
reverseWsToken: 'qq***en',
|
||||
napcatRoot: '/vol1/docker/napcat',
|
||||
napcatContainerMode: 'ssh',
|
||||
napcatSshTarget: 'nas',
|
||||
napcatSshPort: 2202,
|
||||
napcatSshKeyPath: '/home/kt/.ssh/napcat',
|
||||
napcatReverseWsBase: 'ws://api.example.test/onebot',
|
||||
napcatWebuiBaseUrl: 'http://127.0.0.1:6099',
|
||||
napcatWebuiToken: 'na***en',
|
||||
},
|
||||
checks: [],
|
||||
},
|
||||
};
|
||||
const service = { getRuntimeHealth: jest.fn(() => report) };
|
||||
const controller = new RuntimeHealthController(service as any);
|
||||
|
||||
expect(controller.getRuntimeHealth()).toBe(report);
|
||||
expect(service.getRuntimeHealth).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
161
test/runtime/runtime-health.service.spec.ts
Normal file
161
test/runtime/runtime-health.service.spec.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { RuntimeHealthService } from '../../src/runtime/health/runtime-health.service';
|
||||
import type { RuntimeSafeConfigSnapshot } from '../../src/runtime/config/runtime-config.types';
|
||||
|
||||
function createSnapshot(
|
||||
checks: RuntimeSafeConfigSnapshot['checks'],
|
||||
): RuntimeSafeConfigSnapshot {
|
||||
return {
|
||||
app: { nodeEnv: 'test', port: 48085 },
|
||||
database: {
|
||||
host: 'mysql',
|
||||
port: 3306,
|
||||
database: 'kt',
|
||||
username: 'root',
|
||||
synchronize: false,
|
||||
},
|
||||
loki: {
|
||||
transportEnabled: true,
|
||||
httpRequestPushEnabled: true,
|
||||
queryConfigured: true,
|
||||
host: 'https://loki-push.example.test',
|
||||
queryHost: 'https://loki-query.example.test',
|
||||
environment: 'test',
|
||||
tenantId: 'kt',
|
||||
username: 'loki-user',
|
||||
passwordConfigured: true,
|
||||
},
|
||||
minio: {
|
||||
endpoint: 'minio',
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
accessKey: 'mi***ey',
|
||||
bucket: 'kt-template-online',
|
||||
},
|
||||
wordpress: {
|
||||
baseUrl: 'https://blog.example.test',
|
||||
hostHeader: 'blog.example.test',
|
||||
adminUsername: 'wordpress-admin',
|
||||
passwordConfigured: true,
|
||||
timeoutMs: 15000,
|
||||
loginTimeoutMs: 3000,
|
||||
availabilityTtlMs: 60000,
|
||||
},
|
||||
qqbot: {
|
||||
reverseWsPath: '/qqbot/onebot/reverse',
|
||||
reverseWsToken: 'qq***en',
|
||||
napcatRoot: '/vol1/docker/napcat',
|
||||
napcatContainerMode: 'ssh',
|
||||
napcatSshTarget: 'nas',
|
||||
napcatSshPort: 2202,
|
||||
napcatSshKeyPath: '/home/kt/.ssh/napcat',
|
||||
napcatReverseWsBase: 'ws://api.example.test/onebot',
|
||||
napcatWebuiBaseUrl: 'http://127.0.0.1:6099',
|
||||
napcatWebuiToken: 'na***en',
|
||||
},
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
||||
function createService(snapshot: RuntimeSafeConfigSnapshot) {
|
||||
return new RuntimeHealthService({
|
||||
getSafeSnapshot: jest.fn(() => snapshot),
|
||||
} as any);
|
||||
}
|
||||
|
||||
describe('RuntimeHealthService', () => {
|
||||
it('returns ready when required and optional checks are present', () => {
|
||||
const service = createService(
|
||||
createSnapshot([
|
||||
{ key: 'DB_HOST', level: 'required', present: true },
|
||||
{ key: 'MINIO_ENDPOINT', level: 'optional', present: true },
|
||||
]),
|
||||
);
|
||||
|
||||
const report = service.getRuntimeHealth();
|
||||
|
||||
expect(report).toEqual(
|
||||
expect.objectContaining({
|
||||
service: 'kt-template-online-api',
|
||||
status: 'ready',
|
||||
config: expect.objectContaining({
|
||||
minio: expect.objectContaining({ bucket: 'kt-template-online' }),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(new Date(report.checkedAt).toISOString()).toBe(report.checkedAt);
|
||||
expect(report.checks).toContainEqual({
|
||||
name: 'process',
|
||||
status: 'live',
|
||||
critical: true,
|
||||
message: 'NestJS process answered runtime health request',
|
||||
});
|
||||
expect(report.checks).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: 'config:DB_HOST',
|
||||
status: 'ready',
|
||||
critical: true,
|
||||
message: 'DB_HOST is configured',
|
||||
}),
|
||||
);
|
||||
expect(report.checks).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: 'config:MINIO_ENDPOINT',
|
||||
status: 'ready',
|
||||
critical: false,
|
||||
message: 'MINIO_ENDPOINT is configured',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns blocked when required config is missing', () => {
|
||||
const service = createService(
|
||||
createSnapshot([
|
||||
{
|
||||
key: 'DB_PASSWORD',
|
||||
level: 'required',
|
||||
present: false,
|
||||
message: 'DB_PASSWORD is not configured',
|
||||
},
|
||||
{ key: 'MINIO_ENDPOINT', level: 'optional', present: true },
|
||||
]),
|
||||
);
|
||||
|
||||
const report = service.getRuntimeHealth();
|
||||
|
||||
expect(report.status).toBe('blocked');
|
||||
expect(report.checks).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: 'config:DB_PASSWORD',
|
||||
status: 'blocked',
|
||||
critical: true,
|
||||
message: 'DB_PASSWORD is not configured',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns degraded when only optional config is missing', () => {
|
||||
const service = createService(
|
||||
createSnapshot([
|
||||
{ key: 'DB_HOST', level: 'required', present: true },
|
||||
{
|
||||
key: 'LOKI_PASSWORD',
|
||||
level: 'optional',
|
||||
present: false,
|
||||
message: 'LOKI_PASSWORD is not configured',
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const report = service.getRuntimeHealth();
|
||||
|
||||
expect(report.status).toBe('degraded');
|
||||
expect(report.checks).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: 'config:LOKI_PASSWORD',
|
||||
status: 'degraded',
|
||||
critical: false,
|
||||
message: 'LOKI_PASSWORD is not configured',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user