fix(review): address CodeRabbit review feedback

- Guard prepare script for Docker builds without .git
- Add pre-push hook for heavier quality gates (typecheck/test/build)
- Pin drizzle-orm version in scripts/package.json
- Add E2E_SMOKE_ALLOW_WRITE opt-in guard via e2eEnv abstraction
- Create @household/config env-e2e.ts using same t3-env + zod pattern
- Make e2e teardown robust with optional chaining + allSettled
- Fix markdown code block language identifier (MD040)
- Fix CI integration docs to reflect actual workflow
This commit is contained in:
2026-03-05 21:13:38 +04:00
parent efc221f95e
commit 3675079a4c
8 changed files with 81 additions and 38 deletions

View File

@@ -72,8 +72,9 @@
"scripts": { "scripts": {
"name": "@household/scripts", "name": "@household/scripts",
"devDependencies": { "devDependencies": {
"@household/config": "workspace:*",
"@household/db": "workspace:*", "@household/db": "workspace:*",
"drizzle-orm": "*", "drizzle-orm": "^0.44.5",
}, },
}, },
}, },

View File

@@ -14,6 +14,7 @@ smoke test for the billing pipeline. It exercises:
- Bun 1.3+ installed - Bun 1.3+ installed
- A running Supabase/Postgres database with the schema applied - A running Supabase/Postgres database with the schema applied
- `DATABASE_URL` set (via `.env` or environment) - `DATABASE_URL` set (via `.env` or environment)
- `E2E_SMOKE_ALLOW_WRITE=true` set explicitly (safety guard)
## Running locally ## Running locally
@@ -26,7 +27,7 @@ cp .env.example .env
bun run db:migrate bun run db:migrate
# 3. Run the e2e smoke test # 3. Run the e2e smoke test
bun run test:e2e E2E_SMOKE_ALLOW_WRITE=true bun run test:e2e
``` ```
The test seeds its own data (household + 3 roommates), runs the full The test seeds its own data (household + 3 roommates), runs the full
@@ -37,7 +38,7 @@ cleans up after itself.
On success: On success:
``` ```text
E2E smoke passed: purchase ingestion, utility updates, and statements are deterministic E2E smoke passed: purchase ingestion, utility updates, and statements are deterministic
``` ```
@@ -45,9 +46,10 @@ On failure the script exits with code 1 and prints the assertion error.
## CI integration ## CI integration
The e2e smoke test runs in CI as part of the quality matrix when the Run the e2e smoke test with `bun run test:e2e` locally or in a dedicated
`DATABASE_URL` secret is configured. Without the secret, the job is CI job. If you wire it into CI, gate it on `DATABASE_URL` and
skipped automatically. `E2E_SMOKE_ALLOW_WRITE` to avoid false failures. The test is **not**
part of the standard CI quality matrix by default.
## Test data ## Test data

View File

@@ -9,3 +9,13 @@ pre-commit:
lint: lint:
glob: '*.{ts,tsx,js,jsx}' glob: '*.{ts,tsx,js,jsx}'
run: bun run lint run: bun run lint
pre-push:
parallel: true
commands:
typecheck:
run: bun run typecheck
test:
run: bun run test
build:
run: bun run build

View File

@@ -11,7 +11,7 @@
"build": "bun run --filter '*' build", "build": "bun run --filter '*' build",
"typecheck": "bun run --filter '*' typecheck", "typecheck": "bun run --filter '*' typecheck",
"test": "bun run --filter '*' test", "test": "bun run --filter '*' test",
"prepare": "lefthook install", "prepare": "[ -d .git ] && lefthook install || true",
"lint": "oxlint .", "lint": "oxlint .",
"lint:fix": "oxlint --fix .", "lint:fix": "oxlint --fix .",
"format": "bunx oxfmt .", "format": "bunx oxfmt .",

View File

@@ -0,0 +1,21 @@
import { createEnv } from '@t3-oss/env-core'
import { z } from 'zod'
const server = {
DATABASE_URL: z.string().url(),
E2E_SMOKE_ALLOW_WRITE: z
.enum(['true', 'false'])
.default('false')
.transform((v) => v === 'true')
}
export const e2eEnv = createEnv({
server,
runtimeEnv: process.env,
emptyStringAsUndefined: true,
onValidationError: (issues) => {
console.error('Invalid e2e environment variables:')
console.error(JSON.stringify(issues, null, 2))
throw new Error('E2E environment validation failed')
}
})

View File

@@ -1 +1,2 @@
export { env } from './env' export { env } from './env'
export { e2eEnv } from './env-e2e'

View File

@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
import { e2eEnv } from '@household/config'
import { createDbClient, schema } from '@household/db' import { createDbClient, schema } from '@household/db'
import { createTelegramBot } from '../../apps/bot/src/bot' import { createTelegramBot } from '../../apps/bot/src/bot'
@@ -12,11 +13,12 @@ import {
registerPurchaseTopicIngestion registerPurchaseTopicIngestion
} from '../../apps/bot/src/purchase-topic-ingestion' } from '../../apps/bot/src/purchase-topic-ingestion'
const databaseUrl = process.env.DATABASE_URL if (!e2eEnv.E2E_SMOKE_ALLOW_WRITE) {
if (!databaseUrl) { throw new Error('Set E2E_SMOKE_ALLOW_WRITE=true to run e2e smoke test')
throw new Error('DATABASE_URL is required for e2e smoke test')
} }
const databaseUrl: string = e2eEnv.DATABASE_URL
const chatId = '-100123456' const chatId = '-100123456'
const purchaseTopicId = 77 const purchaseTopicId = 77
const commandChatIdNumber = -100123456 const commandChatIdNumber = -100123456
@@ -118,15 +120,9 @@ async function run(): Promise<void> {
carol: '900003' carol: '900003'
} }
const coreClient = createDbClient(databaseUrl as string, { let coreClient: ReturnType<typeof createDbClient> | undefined
max: 2, let ingestionClient: ReturnType<typeof createPurchaseMessageRepository> | undefined
prepare: false let financeService: ReturnType<typeof createFinanceCommandsService> | undefined
})
const ingestionClient = createPurchaseMessageRepository(databaseUrl as string)
const financeService = createFinanceCommandsService(databaseUrl as string, {
householdId: ids.household
})
const bot = createTelegramBot('000000:test-token') const bot = createTelegramBot('000000:test-token')
const replies: string[] = [] const replies: string[] = []
@@ -154,6 +150,17 @@ async function run(): Promise<void> {
return { ok: true, result: true } as any return { ok: true, result: true } as any
}) })
try {
coreClient = createDbClient(databaseUrl, {
max: 2,
prepare: false
})
ingestionClient = createPurchaseMessageRepository(databaseUrl)
financeService = createFinanceCommandsService(databaseUrl, {
householdId: ids.household
})
registerPurchaseTopicIngestion( registerPurchaseTopicIngestion(
bot, bot,
{ {
@@ -166,7 +173,6 @@ async function run(): Promise<void> {
financeService.register(bot) financeService.register(bot)
try {
await coreClient.db.insert(schema.households).values({ await coreClient.db.insert(schema.households).values({
id: ids.household, id: ids.household,
name: 'E2E Smoke Household' name: 'E2E Smoke Household'
@@ -303,12 +309,13 @@ async function run(): Promise<void> {
'E2E smoke passed: purchase ingestion, utility updates, and statements are deterministic' 'E2E smoke passed: purchase ingestion, utility updates, and statements are deterministic'
) )
} finally { } finally {
await coreClient.db.delete(schema.households).where(eq(schema.households.id, ids.household)) await Promise.allSettled([
coreClient
await Promise.all([ ? coreClient.db.delete(schema.households).where(eq(schema.households.id, ids.household))
coreClient.queryClient.end({ timeout: 5 }), : undefined,
ingestionClient.close(), coreClient?.queryClient.end({ timeout: 5 }),
financeService.close() ingestionClient?.close(),
financeService?.close()
]) ])
} }
} }

View File

@@ -6,7 +6,8 @@
"typecheck": "tsgo --project tsconfig.json --noEmit" "typecheck": "tsgo --project tsconfig.json --noEmit"
}, },
"devDependencies": { "devDependencies": {
"drizzle-orm": "*", "drizzle-orm": "^0.44.5",
"@household/config": "workspace:*",
"@household/db": "workspace:*" "@household/db": "workspace:*"
} }
} }