From 76c3e72b3ea9a7a2d1600ab65bb86a198a7dbe05 Mon Sep 17 00:00:00 2001 From: Timothy Koech Date: Mon, 27 Jul 2026 13:01:34 +0300 Subject: [PATCH 1/3] feat: add health check functionality with controller, service, and routes --- src/backend/configure.ts | 12 ++ src/backend/index.ts | 1 + src/backend/services/health_service.ts | 20 ++ .../stubs/controllers/health_controller.stub | 56 ++++++ src/backend/stubs/routes/health.stub | 10 + src/backend/stubs/routes/routes.stub | 2 + .../stubs/tests/functional/health.stub | 173 ++++++++++++++++++ src/backend/stubs/tests/rest.stub | 11 +- .../stubs/tests/unit/health_service.stub | 52 ++++++ 9 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 src/backend/services/health_service.ts create mode 100644 src/backend/stubs/controllers/health_controller.stub create mode 100644 src/backend/stubs/routes/health.stub create mode 100644 src/backend/stubs/tests/functional/health.stub create mode 100644 src/backend/stubs/tests/unit/health_service.stub diff --git a/src/backend/configure.ts b/src/backend/configure.ts index 88107a7f..b2fb61c8 100644 --- a/src/backend/configure.ts +++ b/src/backend/configure.ts @@ -120,12 +120,14 @@ export async function configure(command: Configure) { await codemods.makeUsingStub(stubsRoot, 'controllers/users_controller.stub', {}); await codemods.makeUsingStub(stubsRoot, 'controllers/settings_controller.stub', {}); await codemods.makeUsingStub(stubsRoot, 'controllers/locale_controller.stub', {}); + await codemods.makeUsingStub(stubsRoot, 'controllers/health_controller.stub', {}); await codemods.makeUsingStub(stubsRoot, 'inertia/middleware.stub', {}); await codemods.makeUsingStub(stubsRoot, 'routes/users.stub', {}); await codemods.makeUsingStub(stubsRoot, 'routes/settings.stub', {}); await codemods.makeUsingStub(stubsRoot, 'routes/auth.stub', {}); + await codemods.makeUsingStub(stubsRoot, 'routes/health.stub', {}); await codemods.makeUsingStub(stubsRoot, 'routes/routes.stub', {}); await codemods.makeUsingStub(stubsRoot, 'routes/invitations.stub', {}); await codemods.makeUsingStub(stubsRoot, 'routes/dashboard.stub', {}); @@ -161,6 +163,7 @@ export async function configure(command: Configure) { await codemods.makeUsingStub(stubsRoot, 'tests/rest.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/ui.rest.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/functional/draft.stub', {}); + await codemods.makeUsingStub(stubsRoot, 'tests/functional/health.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/ui_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/invitation_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/page_service.stub', {}); @@ -168,6 +171,7 @@ export async function configure(command: Configure) { await codemods.makeUsingStub(stubsRoot, 'tests/unit/story_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/stream_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/user_service.stub', {}); + await codemods.makeUsingStub(stubsRoot, 'tests/unit/health_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/progress_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/language_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/locale_service.stub', {}); @@ -197,6 +201,7 @@ export async function configure(command: Configure) { OPENAI_API_KEY: 'redacted', GOOGLE_APPLICATION_CREDENTIALS_JSON: 'redacted', FIREBASE_SERVICE_ACCOUNT_KEY_JSON: 'redacted', + HEALTH_CHECK_TOKEN: '', }); /** @@ -254,6 +259,13 @@ export async function configure(command: Configure) { leadingComment: 'Configuration for the Firebase service account key', }); + await codemods.defineEnvValidations({ + variables: { + HEALTH_CHECK_TOKEN: `Env.schema.string.optional(),`, + }, + leadingComment: 'Variables for configuring the Health check token', + }); + /** * Register providers */ diff --git a/src/backend/index.ts b/src/backend/index.ts index 0ca3236b..722796a9 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -30,6 +30,7 @@ export * from './factories/story_localisation_factory.js'; export * from './factories/invitation_factory.js'; export * from './factories/user_factory.js'; export * from './factories/resource_factory.js'; +export * from './services/health_service.js'; export * from './services/helpers.js'; export * from './services/ai_service.js'; export * from './services/analytics_service.js'; diff --git a/src/backend/services/health_service.ts b/src/backend/services/health_service.ts new file mode 100644 index 00000000..8ab4d174 --- /dev/null +++ b/src/backend/services/health_service.ts @@ -0,0 +1,20 @@ +import db from '@adonisjs/lucid/services/db'; +import { DateTime } from 'luxon'; + +export interface DatabaseHealth { + database: 'ok'; + databaseCheckedAt: string; +} + +export class HealthService { + public async checkDatabase( + runQuery: () => Promise = () => db.rawQuery('select 1'), + ): Promise { + await runQuery(); + + return { + database: 'ok', + databaseCheckedAt: DateTime.utc().toISO() as string, + }; + } +} diff --git a/src/backend/stubs/controllers/health_controller.stub b/src/backend/stubs/controllers/health_controller.stub new file mode 100644 index 00000000..c916befe --- /dev/null +++ b/src/backend/stubs/controllers/health_controller.stub @@ -0,0 +1,56 @@ +{{{ + exports({ to: app.makePath('app/controllers/health_controller.ts') }) +}}} +import { inject } from '@adonisjs/core'; +import type { HttpContext } from '@adonisjs/core/http'; +import { DateTime } from 'luxon'; +import { HealthService } from '@story-cms/kit'; +import env from '#start/env'; + +@inject() +export default class HealthController { + constructor(private readonly healthService: HealthService) {} + + public async index({ request, response, logger }: HttpContext) { + if (request.input('extend') !== 'db') { + return response.ok({ status: 'ok' }); + } + + const healthCheckToken = env.get('HEALTH_CHECK_TOKEN'); + if ( + !healthCheckToken || + request.header('x-health-check-token') !== healthCheckToken + ) { + return response.notFound({ status: 'not_found' }); + } + + try { + const { database, databaseCheckedAt } = await this.healthService.checkDatabase(); + + return response.ok({ + status: 'ok', + database, + databaseCheckedAt, + }); + } catch (error) { + const databaseCheckedAt = DateTime.utc().toISO() as string; + + logger.error( + { + err: error, + component: 'database', + provider: 'neon', + healthCheck: 'extended', + databaseCheckedAt, + }, + 'Database health check failed', + ); + + return response.serviceUnavailable({ + status: 'error', + database: 'error', + databaseCheckedAt, + }); + } + } +} diff --git a/src/backend/stubs/routes/health.stub b/src/backend/stubs/routes/health.stub new file mode 100644 index 00000000..b79c4074 --- /dev/null +++ b/src/backend/stubs/routes/health.stub @@ -0,0 +1,10 @@ +{{{ + exports({ to: app.makePath('start/routes/health.ts') }) +}}} +import router from '@adonisjs/core/services/router'; +import { middleware } from '#start/kernel'; +const HealthController = () => import('#controllers/health_controller'); + +export default () => { + router.get('/health', [HealthController, 'index']).use(middleware.noIndex()); +}; diff --git a/src/backend/stubs/routes/routes.stub b/src/backend/stubs/routes/routes.stub index 5feb9e47..19f48fb0 100644 --- a/src/backend/stubs/routes/routes.stub +++ b/src/backend/stubs/routes/routes.stub @@ -15,6 +15,7 @@ import { middleware } from '#start/kernel'; import api from '#start/routes/api'; import audience from '#start/routes/audience'; import auth from '#start/routes/auth'; +import health from '#start/routes/health'; import invitations from '#start/routes/invitations'; import dashboard from '#start/routes/dashboard'; import pages from '#start/routes/pages'; @@ -30,6 +31,7 @@ import settings from '#start/routes/settings'; auth(); api(); +health(); uiTools(); // Private routes diff --git a/src/backend/stubs/tests/functional/health.stub b/src/backend/stubs/tests/functional/health.stub new file mode 100644 index 00000000..a3309f96 --- /dev/null +++ b/src/backend/stubs/tests/functional/health.stub @@ -0,0 +1,173 @@ +{{{ + exports({ to: app.makePath('tests/functional/health.spec.ts') }) +}}} +import { test } from '@japa/runner'; +import app from '@adonisjs/core/services/app'; +import env from '#start/env'; +import { HealthService, type DatabaseHealth } from '@story-cms/kit'; + +const HEALTH_CHECK_TOKEN = 'test-health-check-token'; +const DATABASE_CHECKED_AT = '2026-07-24T10:00:00.000Z'; + +class FakeHealthService extends HealthService { + constructor(private readonly check: () => Promise) { + super(); + } + + public override checkDatabase(): Promise { + return this.check(); + } +} + +function swapHealthService(check: () => Promise) { + app.container.swap(HealthService, () => new FakeHealthService(check)); +} + +test.group('GET /health', (group) => { + let previousHealthCheckToken: string | undefined; + + group.each.setup(() => { + previousHealthCheckToken = env.get('HEALTH_CHECK_TOKEN'); + env.set('HEALTH_CHECK_TOKEN', HEALTH_CHECK_TOKEN); + }); + + group.each.teardown(() => { + app.container.restore(HealthService); + env.set('HEALTH_CHECK_TOKEN', previousHealthCheckToken ?? ''); + }); + + test('returns application liveness without checking the database', async ({ + assert, + client, + }) => { + let databaseChecks = 0; + swapHealthService(async () => { + databaseChecks++; + throw new Error('The basic health check must not query the database'); + }); + + const response = await client.get('/health'); + + response.assertStatus(200); + assert.deepEqual(response.body(), { status: 'ok' }); + assert.equal(databaseChecks, 0); + }); + + test('rejects an unauthenticated database check without querying the database', async ({ + assert, + client, + }) => { + let databaseChecks = 0; + swapHealthService(async () => { + databaseChecks++; + throw new Error('An unauthenticated check must not query the database'); + }); + + const response = await client.get('/health?extend=db'); + + response.assertStatus(404); + assert.deepEqual(response.body(), { status: 'not_found' }); + assert.equal(databaseChecks, 0); + }); + + test('rejects a wrong token without querying the database', async ({ + assert, + client, + }) => { + let databaseChecks = 0; + swapHealthService(async () => { + databaseChecks++; + throw new Error('A wrong token must not query the database'); + }); + + const response = await client + .get('/health?extend=db') + .header('x-health-check-token', 'wrong-token'); + + response.assertStatus(404); + assert.deepEqual(response.body(), { status: 'not_found' }); + assert.equal(databaseChecks, 0); + }); + + test('rejects extend=db when HEALTH_CHECK_TOKEN is not configured', async ({ + assert, + client, + }) => { + env.set('HEALTH_CHECK_TOKEN', ''); + + let databaseChecks = 0; + swapHealthService(async () => { + databaseChecks++; + throw new Error('Must not query when token env is missing'); + }); + + const response = await client.get('/health?extend=db'); + + response.assertStatus(404); + assert.deepEqual(response.body(), { status: 'not_found' }); + assert.equal(databaseChecks, 0); + }); + + test('runs an authenticated database check', async ({ assert, client }) => { + let databaseChecks = 0; + swapHealthService(async () => { + databaseChecks++; + return { + database: 'ok', + databaseCheckedAt: DATABASE_CHECKED_AT, + }; + }); + + const response = await client + .get('/health?extend=db') + .header('x-health-check-token', HEALTH_CHECK_TOKEN); + + response.assertStatus(200); + assert.deepEqual(response.body(), { + status: 'ok', + database: 'ok', + databaseCheckedAt: DATABASE_CHECKED_AT, + }); + assert.equal(databaseChecks, 1); + }); + + test('returns 503 when the authenticated database check fails', async ({ + assert, + client, + }) => { + swapHealthService(async () => { + throw new Error('connection refused'); + }); + + const response = await client + .get('/health?extend=db') + .header('x-health-check-token', HEALTH_CHECK_TOKEN); + + response.assertStatus(503); + assert.equal(response.body().status, 'error'); + assert.equal(response.body().database, 'error'); + assert.isString(response.body().databaseCheckedAt); + }); + + test('does not cache authenticated database checks', async ({ assert, client }) => { + let databaseChecks = 0; + swapHealthService(async () => { + databaseChecks++; + return { + database: 'ok', + databaseCheckedAt: DATABASE_CHECKED_AT, + }; + }); + + const first = await client + .get('/health?extend=db') + .header('x-health-check-token', HEALTH_CHECK_TOKEN); + const second = await client + .get('/health?extend=db') + .header('x-health-check-token', HEALTH_CHECK_TOKEN); + + first.assertStatus(200); + second.assertStatus(200); + assert.equal(databaseChecks, 2); + }); +}); diff --git a/src/backend/stubs/tests/rest.stub b/src/backend/stubs/tests/rest.stub index bd285fec..afa9df55 100644 --- a/src/backend/stubs/tests/rest.stub +++ b/src/backend/stubs/tests/rest.stub @@ -35,4 +35,13 @@ Accept: application/json ### GET \{\{ authority \}\}/api/v1/locale HTTP/1.1 -Accept: application/json \ No newline at end of file +Accept: application/json + +### +GET \{\{ authority \}\}/health HTTP/1.1 +Accept: application/json + +### +GET \{\{ authority \}\}/health?extend=db HTTP/1.1 +Accept: application/json +X-Health-Check-Token: \{\{ healthCheckToken \}\} \ No newline at end of file diff --git a/src/backend/stubs/tests/unit/health_service.stub b/src/backend/stubs/tests/unit/health_service.stub new file mode 100644 index 00000000..38da007c --- /dev/null +++ b/src/backend/stubs/tests/unit/health_service.stub @@ -0,0 +1,52 @@ +{{{ + exports({ to: app.makePath('tests/unit/health_service.spec.ts') }) +}}} +import { test } from '@japa/runner'; +import { HealthService } from '@story-cms/kit'; + +test.group('HealthService.checkDatabase', () => { + test('runs one lightweight query and returns its check time', async ({ assert }) => { + const service = new HealthService(); + let queryCalls = 0; + + const result = await service.checkDatabase(async () => { + queryCalls++; + }); + + assert.equal(queryCalls, 1); + assert.equal(result.database, 'ok'); + assert.isString(result.databaseCheckedAt); + }); + + test('preserves the database error for the controller to report', async ({ + assert, + }) => { + const service = new HealthService(); + const databaseError = new Error('connection refused'); + let caughtError: unknown; + + try { + await service.checkDatabase(async () => { + throw databaseError; + }); + } catch (error) { + caughtError = error; + } + + assert.equal(caughtError, databaseError); + }); + + test('does not cache database checks in process memory', async ({ assert }) => { + const service = new HealthService(); + let queryCalls = 0; + + await service.checkDatabase(async () => { + queryCalls++; + }); + await service.checkDatabase(async () => { + queryCalls++; + }); + + assert.equal(queryCalls, 2); + }); +}); From 014f2c9204faeb59b9a188db55b5262a9b2dcc19 Mon Sep 17 00:00:00 2001 From: Timothy Koech Date: Mon, 27 Jul 2026 13:13:06 +0300 Subject: [PATCH 2/3] fix: update resource service test to use 'id' instead of 'storyId' --- src/backend/stubs/tests/unit/resource_service.stub | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/stubs/tests/unit/resource_service.stub b/src/backend/stubs/tests/unit/resource_service.stub index 9b68e8c5..77494207 100644 --- a/src/backend/stubs/tests/unit/resource_service.stub +++ b/src/backend/stubs/tests/unit/resource_service.stub @@ -148,8 +148,8 @@ test.group('Resource service', (group) => { assert.lengthOf(usages, 2); assert.deepEqual(usages, [ - { storyId: alphaStory.id, title: 'Alpha Story' }, - { storyId: zebraStory.id, title: 'Zebra Story' }, + { id: alphaStory.id, title: 'Alpha Story' }, + { id: zebraStory.id, title: 'Zebra Story' }, ]); }); From 2c2acf3c8d2da1356afc1c93f6ccac6de5b4728a Mon Sep 17 00:00:00 2001 From: Timothy Koech Date: Tue, 28 Jul 2026 10:08:02 +0300 Subject: [PATCH 3/3] refactor: remove HealthService and related references from the codebase --- src/backend/configure.ts | 1 - src/backend/index.ts | 1 - src/backend/services/health_service.ts | 20 ------- .../stubs/controllers/health_controller.stub | 12 ++-- .../stubs/tests/functional/health.stub | 58 ++++++++----------- .../stubs/tests/unit/health_service.stub | 52 ----------------- 6 files changed, 27 insertions(+), 117 deletions(-) delete mode 100644 src/backend/services/health_service.ts delete mode 100644 src/backend/stubs/tests/unit/health_service.stub diff --git a/src/backend/configure.ts b/src/backend/configure.ts index b2fb61c8..28b2500a 100644 --- a/src/backend/configure.ts +++ b/src/backend/configure.ts @@ -171,7 +171,6 @@ export async function configure(command: Configure) { await codemods.makeUsingStub(stubsRoot, 'tests/unit/story_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/stream_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/user_service.stub', {}); - await codemods.makeUsingStub(stubsRoot, 'tests/unit/health_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/progress_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/language_service.stub', {}); await codemods.makeUsingStub(stubsRoot, 'tests/unit/locale_service.stub', {}); diff --git a/src/backend/index.ts b/src/backend/index.ts index 722796a9..0ca3236b 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -30,7 +30,6 @@ export * from './factories/story_localisation_factory.js'; export * from './factories/invitation_factory.js'; export * from './factories/user_factory.js'; export * from './factories/resource_factory.js'; -export * from './services/health_service.js'; export * from './services/helpers.js'; export * from './services/ai_service.js'; export * from './services/analytics_service.js'; diff --git a/src/backend/services/health_service.ts b/src/backend/services/health_service.ts deleted file mode 100644 index 8ab4d174..00000000 --- a/src/backend/services/health_service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import db from '@adonisjs/lucid/services/db'; -import { DateTime } from 'luxon'; - -export interface DatabaseHealth { - database: 'ok'; - databaseCheckedAt: string; -} - -export class HealthService { - public async checkDatabase( - runQuery: () => Promise = () => db.rawQuery('select 1'), - ): Promise { - await runQuery(); - - return { - database: 'ok', - databaseCheckedAt: DateTime.utc().toISO() as string, - }; - } -} diff --git a/src/backend/stubs/controllers/health_controller.stub b/src/backend/stubs/controllers/health_controller.stub index c916befe..71d27a70 100644 --- a/src/backend/stubs/controllers/health_controller.stub +++ b/src/backend/stubs/controllers/health_controller.stub @@ -1,16 +1,12 @@ {{{ exports({ to: app.makePath('app/controllers/health_controller.ts') }) }}} -import { inject } from '@adonisjs/core'; import type { HttpContext } from '@adonisjs/core/http'; +import db from '@adonisjs/lucid/services/db'; import { DateTime } from 'luxon'; -import { HealthService } from '@story-cms/kit'; import env from '#start/env'; -@inject() export default class HealthController { - constructor(private readonly healthService: HealthService) {} - public async index({ request, response, logger }: HttpContext) { if (request.input('extend') !== 'db') { return response.ok({ status: 'ok' }); @@ -25,11 +21,12 @@ export default class HealthController { } try { - const { database, databaseCheckedAt } = await this.healthService.checkDatabase(); + await db.rawQuery('select 1'); + const databaseCheckedAt = DateTime.utc().toISO() as string; return response.ok({ status: 'ok', - database, + database: 'ok', databaseCheckedAt, }); } catch (error) { @@ -39,7 +36,6 @@ export default class HealthController { { err: error, component: 'database', - provider: 'neon', healthCheck: 'extended', databaseCheckedAt, }, diff --git a/src/backend/stubs/tests/functional/health.stub b/src/backend/stubs/tests/functional/health.stub index a3309f96..0b544e6c 100644 --- a/src/backend/stubs/tests/functional/health.stub +++ b/src/backend/stubs/tests/functional/health.stub @@ -2,29 +2,24 @@ exports({ to: app.makePath('tests/functional/health.spec.ts') }) }}} import { test } from '@japa/runner'; -import app from '@adonisjs/core/services/app'; +import db from '@adonisjs/lucid/services/db'; import env from '#start/env'; -import { HealthService, type DatabaseHealth } from '@story-cms/kit'; const HEALTH_CHECK_TOKEN = 'test-health-check-token'; -const DATABASE_CHECKED_AT = '2026-07-24T10:00:00.000Z'; -class FakeHealthService extends HealthService { - constructor(private readonly check: () => Promise) { - super(); - } +type RawQueryStub = (...args: Parameters) => unknown; - public override checkDatabase(): Promise { - return this.check(); - } -} - -function swapHealthService(check: () => Promise) { - app.container.swap(HealthService, () => new FakeHealthService(check)); +function swapRawQuery(replacement: RawQueryStub) { + const original = db.rawQuery.bind(db); + db.rawQuery = replacement as typeof db.rawQuery; + return () => { + db.rawQuery = original; + }; } test.group('GET /health', (group) => { let previousHealthCheckToken: string | undefined; + let restoreRawQuery: (() => void) | undefined; group.each.setup(() => { previousHealthCheckToken = env.get('HEALTH_CHECK_TOKEN'); @@ -32,7 +27,8 @@ test.group('GET /health', (group) => { }); group.each.teardown(() => { - app.container.restore(HealthService); + restoreRawQuery?.(); + restoreRawQuery = undefined; env.set('HEALTH_CHECK_TOKEN', previousHealthCheckToken ?? ''); }); @@ -41,7 +37,7 @@ test.group('GET /health', (group) => { client, }) => { let databaseChecks = 0; - swapHealthService(async () => { + restoreRawQuery = swapRawQuery(async () => { databaseChecks++; throw new Error('The basic health check must not query the database'); }); @@ -58,7 +54,7 @@ test.group('GET /health', (group) => { client, }) => { let databaseChecks = 0; - swapHealthService(async () => { + restoreRawQuery = swapRawQuery(async () => { databaseChecks++; throw new Error('An unauthenticated check must not query the database'); }); @@ -75,7 +71,7 @@ test.group('GET /health', (group) => { client, }) => { let databaseChecks = 0; - swapHealthService(async () => { + restoreRawQuery = swapRawQuery(async () => { databaseChecks++; throw new Error('A wrong token must not query the database'); }); @@ -96,7 +92,7 @@ test.group('GET /health', (group) => { env.set('HEALTH_CHECK_TOKEN', ''); let databaseChecks = 0; - swapHealthService(async () => { + restoreRawQuery = swapRawQuery(async () => { databaseChecks++; throw new Error('Must not query when token env is missing'); }); @@ -110,12 +106,9 @@ test.group('GET /health', (group) => { test('runs an authenticated database check', async ({ assert, client }) => { let databaseChecks = 0; - swapHealthService(async () => { + restoreRawQuery = swapRawQuery(async () => { databaseChecks++; - return { - database: 'ok', - databaseCheckedAt: DATABASE_CHECKED_AT, - }; + return []; }); const response = await client @@ -123,11 +116,9 @@ test.group('GET /health', (group) => { .header('x-health-check-token', HEALTH_CHECK_TOKEN); response.assertStatus(200); - assert.deepEqual(response.body(), { - status: 'ok', - database: 'ok', - databaseCheckedAt: DATABASE_CHECKED_AT, - }); + assert.equal(response.body().status, 'ok'); + assert.equal(response.body().database, 'ok'); + assert.isString(response.body().databaseCheckedAt); assert.equal(databaseChecks, 1); }); @@ -135,7 +126,7 @@ test.group('GET /health', (group) => { assert, client, }) => { - swapHealthService(async () => { + restoreRawQuery = swapRawQuery(async () => { throw new Error('connection refused'); }); @@ -151,12 +142,9 @@ test.group('GET /health', (group) => { test('does not cache authenticated database checks', async ({ assert, client }) => { let databaseChecks = 0; - swapHealthService(async () => { + restoreRawQuery = swapRawQuery(async () => { databaseChecks++; - return { - database: 'ok', - databaseCheckedAt: DATABASE_CHECKED_AT, - }; + return []; }); const first = await client diff --git a/src/backend/stubs/tests/unit/health_service.stub b/src/backend/stubs/tests/unit/health_service.stub deleted file mode 100644 index 38da007c..00000000 --- a/src/backend/stubs/tests/unit/health_service.stub +++ /dev/null @@ -1,52 +0,0 @@ -{{{ - exports({ to: app.makePath('tests/unit/health_service.spec.ts') }) -}}} -import { test } from '@japa/runner'; -import { HealthService } from '@story-cms/kit'; - -test.group('HealthService.checkDatabase', () => { - test('runs one lightweight query and returns its check time', async ({ assert }) => { - const service = new HealthService(); - let queryCalls = 0; - - const result = await service.checkDatabase(async () => { - queryCalls++; - }); - - assert.equal(queryCalls, 1); - assert.equal(result.database, 'ok'); - assert.isString(result.databaseCheckedAt); - }); - - test('preserves the database error for the controller to report', async ({ - assert, - }) => { - const service = new HealthService(); - const databaseError = new Error('connection refused'); - let caughtError: unknown; - - try { - await service.checkDatabase(async () => { - throw databaseError; - }); - } catch (error) { - caughtError = error; - } - - assert.equal(caughtError, databaseError); - }); - - test('does not cache database checks in process memory', async ({ assert }) => { - const service = new HealthService(); - let queryCalls = 0; - - await service.checkDatabase(async () => { - queryCalls++; - }); - await service.checkDatabase(async () => { - queryCalls++; - }); - - assert.equal(queryCalls, 2); - }); -});