OTP test infrastructure for Maestro flows

Dev-only /internal/_test/peek-otp + /internal/_test/reset-phone endpoints
gated by NODE_ENV !== 'production'. peek-otp reads the latest stub OTP
out of an in-memory map populated by otp.service.js fazpassSendStub;
reset-phone wipes otp_requests rows (and optionally the customers row)
so flows can re-run without tripping cooldowns.

JS + shell helpers under .maestro/scripts/ wrap the endpoints for use
inside Maestro runScript steps. 01_smoke.yaml expanded from a launch-only
sanity check to a full cold-start onboarding -> force-register -> OTP ->
home walk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 16:19:22 +08:00
parent d33d4419ea
commit 4680c36e34
8 changed files with 212 additions and 8 deletions

View File

@@ -0,0 +1,41 @@
// Dev/test-only routes. Registration in app.internal.js is gated on
// NODE_ENV !== 'production' so these endpoints never exist in prod builds.
//
// Used by Maestro flows + curl harnesses to read state that the OTP stub
// keeps in memory (the stub-generated code per phone), without baking
// test phone numbers or fixed codes into production code paths.
import { peekStubOtp } from '../../services/otp.service.js'
import { getDb } from '../../db/client.js'
const sql = getDb()
export const internalTestRoutes = async (fastify) => {
fastify.get('/peek-otp', async (request, reply) => {
const phone = request.query?.phone
if (!phone) {
return reply.code(400).send({ error: 'phone query param required' })
}
const entry = peekStubOtp(phone)
if (!entry) {
return reply.code(404).send({ error: 'no_otp_for_phone', phone })
}
return entry
})
// Wipe rate-limit + cooldown state for a phone so flows can re-run quickly.
// Deletes otp_requests rows for the phone and (optionally) the customer row
// so identity-upgrade flows start fresh.
fastify.post('/reset-phone', async (request, reply) => {
const phone = request.body?.phone
if (!phone) {
return reply.code(400).send({ error: 'phone required in body' })
}
const dropCustomer = request.body?.drop_customer === true
await sql`DELETE FROM otp_requests WHERE phone = ${phone}`
if (dropCustomer) {
await sql`DELETE FROM customers WHERE phone = ${phone}`
}
return { ok: true, phone, dropped_customer: dropCustomer }
})
}