mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 12:04:02 +00:00
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:
3
bun.lock
3
bun.lock
@@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
10
lefthook.yml
10
lefthook.yml
@@ -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
|
||||||
|
|||||||
@@ -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 .",
|
||||||
|
|||||||
21
packages/config/src/env-e2e.ts
Normal file
21
packages/config/src/env-e2e.ts
Normal 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')
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { env } from './env'
|
export { env } from './env'
|
||||||
|
export { e2eEnv } from './env-e2e'
|
||||||
|
|||||||
@@ -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,19 +150,29 @@ async function run(): Promise<void> {
|
|||||||
return { ok: true, result: true } as any
|
return { ok: true, result: true } as any
|
||||||
})
|
})
|
||||||
|
|
||||||
registerPurchaseTopicIngestion(
|
|
||||||
bot,
|
|
||||||
{
|
|
||||||
householdId: ids.household,
|
|
||||||
householdChatId: chatId,
|
|
||||||
purchaseTopicId
|
|
||||||
},
|
|
||||||
ingestionClient.repository
|
|
||||||
)
|
|
||||||
|
|
||||||
financeService.register(bot)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
coreClient = createDbClient(databaseUrl, {
|
||||||
|
max: 2,
|
||||||
|
prepare: false
|
||||||
|
})
|
||||||
|
|
||||||
|
ingestionClient = createPurchaseMessageRepository(databaseUrl)
|
||||||
|
financeService = createFinanceCommandsService(databaseUrl, {
|
||||||
|
householdId: ids.household
|
||||||
|
})
|
||||||
|
|
||||||
|
registerPurchaseTopicIngestion(
|
||||||
|
bot,
|
||||||
|
{
|
||||||
|
householdId: ids.household,
|
||||||
|
householdChatId: chatId,
|
||||||
|
purchaseTopicId
|
||||||
|
},
|
||||||
|
ingestionClient.repository
|
||||||
|
)
|
||||||
|
|
||||||
|
financeService.register(bot)
|
||||||
|
|
||||||
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()
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user