feat(01-01): Drizzle schema + DB client + /health slice (GREEN)

- src/db/schema.ts: users, memberCredentials, calendars, calendarEvents tables
  - users: composite unique on oidc_iss+oidc_sub (D-10 identity)
  - calendar_events: separate dtstart_utc (TIMESTAMP) and dtstart_date (DATE) + allDay boolean (D-13)
- src/db/client.ts: drizzle(mysql2 pool) singleton export `db`
- drizzle.config.ts: dialect mysql, schema → migrations, dbCredentials from env
- src/routes/health.ts: GET / with real SELECT 1 DB round-trip, 200 or 503
- src/index.ts: Hono app with /health mounted before auth, serveStatic for PWA
- tests/health.test.ts: 2 tests pass (mocked DB); TDD GREEN gate
- apps/pwa/src/App.tsx: React shell fetching /health via TanStack Query
This commit is contained in:
Lucas Berger
2026-06-04 09:52:36 -04:00
parent f31711af27
commit 96cda58509
7 changed files with 239 additions and 26 deletions
+14 -26
View File
@@ -1,47 +1,35 @@
/**
* GET /health — end-to-end skeleton test
*
* TDD RED: Tests written before implementation.
* These will fail until Task 2 creates apps/api/src/routes/health.ts
* and apps/api/src/index.ts.
*
* Behavior (from plan):
* - GET /health returns 200 with { ok: true, db: "up" } after a real DB round-trip
* Behavior:
* - GET /health returns 200 with { ok: true, db: "up" } after DB round-trip succeeds
* - GET /health returns 503 if the DB round-trip throws
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { describe, it, expect, vi } from 'vitest'
// We test the health route by importing the Hono app and calling it directly
// (no real DB needed — we mock the db module)
// vi.mock is hoisted to the top of the module by Vitest — defining it here is correct.
// The factory is called before any test runs.
vi.mock('../src/db/client.js', () => ({
db: {
execute: vi.fn().mockResolvedValue([[{ '1': 1 }]]),
},
}))
describe('GET /health', () => {
beforeEach(() => {
vi.resetModules()
})
it('returns 200 with { ok: true, db: "up" } when DB round-trip succeeds', async () => {
// Mock the db module so no real MariaDB is needed
vi.mock('../src/db/client.js', () => ({
db: {
execute: vi.fn().mockResolvedValue([[]]),
},
}))
const { app } = await import('../src/index.js')
const res = await app.request('/health')
expect(res.status).toBe(200)
const body = await res.json()
const body = await res.json() as { ok: boolean; db: string }
expect(body.ok).toBe(true)
expect(body.db).toBe('up')
})
it('returns 503 when DB round-trip throws', async () => {
vi.mock('../src/db/client.js', () => ({
db: {
execute: vi.fn().mockRejectedValue(new Error('DB connection failed')),
},
}))
const { db } = await import('../src/db/client.js')
// Temporarily override execute to throw
vi.mocked(db.execute).mockRejectedValueOnce(new Error('DB connection failed'))
const { app } = await import('../src/index.js')
const res = await app.request('/health')