feat(hardening): M-Hard 6 + 7 — PostgreSQL backend + production server infra
M-Hard 6: PostgreSQL Storage Backend - @shade/storage-postgres with PostgresStorage + PostgresPrekeyStore - Drizzle-style raw SQL ensureClientTables / ensurePrekeyServerTables - All tables prefixed `shade_` to avoid collisions in shared databases - DELETE ... FOR UPDATE SKIP LOCKED for concurrent OTPK consumption - Tests skip gracefully without SHADE_TEST_PG_URL, run against real PG when set M-Hard 7: Production Server Infrastructure - Structured JSON logger (logger.ts) — SHADE_LOG_LEVEL configurable - Health endpoints (/health, /healthz, /ready) — Kubernetes-friendly - Prometheus metrics (/metrics) — counters, histograms, middleware - Graceful shutdown with SIGTERM/SIGINT handlers + store close - Production Dockerfile with non-root user, healthcheck, multi-stage build - docker-compose.yml example for Dokploy with optional PostgreSQL 193 tests passing, 0 failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
66
packages/shade-server/tests/health-metrics.test.ts
Normal file
66
packages/shade-server/tests/health-metrics.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { Hono } from 'hono';
|
||||
import { createHealthRoutes } from '../src/health.js';
|
||||
import { createMetricsRoutes, metricsMiddleware, metrics } from '../src/metrics.js';
|
||||
import { MemoryPrekeyStore } from '../src/memory-store.js';
|
||||
|
||||
describe('Health endpoints', () => {
|
||||
test('GET /health returns ok with version and uptime', async () => {
|
||||
const store = new MemoryPrekeyStore();
|
||||
const app = createHealthRoutes(store, '1.2.3');
|
||||
|
||||
const res = await app.request('/health');
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.status).toBe('ok');
|
||||
expect(body.version).toBe('1.2.3');
|
||||
expect(body.uptimeSeconds).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('GET /healthz returns text "ok"', async () => {
|
||||
const store = new MemoryPrekeyStore();
|
||||
const app = createHealthRoutes(store, '1.0.0');
|
||||
const res = await app.request('/healthz');
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('ok');
|
||||
});
|
||||
|
||||
test('GET /ready returns text "ready"', async () => {
|
||||
const store = new MemoryPrekeyStore();
|
||||
const app = createHealthRoutes(store, '1.0.0');
|
||||
const res = await app.request('/ready');
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('ready');
|
||||
});
|
||||
|
||||
test('returns 503 if store throws', async () => {
|
||||
const broken = {
|
||||
getOneTimePreKeyCount: async () => { throw new Error('db down'); },
|
||||
} as any;
|
||||
const app = createHealthRoutes(broken, '1.0.0');
|
||||
const res = await app.request('/health');
|
||||
expect(res.status).toBe(503);
|
||||
const body = await res.json();
|
||||
expect(body.status).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metrics endpoint', () => {
|
||||
test('returns Prometheus exposition format', async () => {
|
||||
// Trigger some metrics
|
||||
const app = new Hono();
|
||||
app.use('*', metricsMiddleware());
|
||||
app.get('/test', (c) => c.text('hello'));
|
||||
app.route('/', createMetricsRoutes());
|
||||
|
||||
await app.request('/test');
|
||||
await app.request('/test');
|
||||
|
||||
const res = await app.request('/metrics');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toMatch(/text\/plain/);
|
||||
const body = await res.text();
|
||||
expect(body).toContain('shade_requests_total');
|
||||
expect(body).toContain('shade_request_duration_ms');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user