mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-04-28 14:20:45 +00:00
5b12650906
- seeded 54 objects across 6 categories (instrument, animal, food, vehicle, sport, household)
- @cf/google/gemma-4-26b-a4b-it judges via function calling; returns {is_guess, answer, hint}
- pre-AI validator rejects open-ended questions; handler dedups exact repeats
- secret-redacting hint filter as defense-in-depth
- 86 new vitest tests (seeds, state, validator, ai-client, handlers, render)
6.6 KiB
6.6 KiB
Phase 04 — Tests, Docs, Help Integration
Context links
- Plan overview:
./plan.md - Test pattern reference:
tests/modules/doantu/,tests/modules/loldle/ - Fakes:
tests/fakes/fake-kv-namespace.js,tests/fakes/fake-bot.js - Render module integration:
src/modules/util/(help command auto-discovers)
Overview
- Priority: P2 (ship-gate — module isn't complete without tests + docs)
- Status: planned
- Description: Write vitest unit tests covering seeds, state, validator,
ai-client (with stubbed
env.AI), handlers (with fakeenv.AI), and render. Replace the README stub with a complete module guide. Verify/helpsurfaces all four commands.
Key insights
- Workers AI binding is a plain JS object — stub it as
{ run: vi.fn().mockResolvedValue({...}) }in tests. No workerd, no MSW. - Repo convention: tests use injected fakes, not
vi.mock. Pass fake modules through handler{ db, env }arg explicitly. /helpauto-includes any module with public/protected commands — no extra wiring. Just confirm by inspectingnpm run register:dryoutput.
Requirements
Functional (test coverage)
seeds.test.js— every seed has non-emptytarget,category,initialHint;getRandomSeed(rng)deterministic with seeded rng;initialHintdoes NOT containtargetsubstring (case-insensitive).state.test.js— round-trip save/load; clear works; stats start zeroed;recordResultupdates fields correctly (solve increments solved + best; loss only increments played + totalTurns).validate-input.test.js— acceptsis/are/does/do/can/has/will/shouldquestions; rejectswhat/how/why/which/who; rejects empty + too-long; normalizes whitespace + case.ai-client.test.js— happy path: stubbedenv.AI.runreturns valid function call → judge returns clean shape; bad shape → defensive fallback used; thrown error → wrapped inUpstreamError.handlers.test.js— start round (no game, no arg); board view (game, no arg); turn flow (yes path + no path); solve flow (is_guess && yesends round, records, clears game); giveup; stats; repeat-question dedup; validator rejection bypasses AI.render.test.js— HTML escape: target/hint with<script>neutralized; formatStats handles zeroed stats; formatBoard renders empty turns array.
Non-functional
- All tests pure-logic (no network, no
setTimeout, no real KV). - Existing 200+ tests must still pass.
- Coverage parity with
doantutest count (~25–35 tests). - Docs ≤200 lines.
Architecture
tests/modules/twentyq/
├── seeds.test.js
├── state.test.js
├── validate-input.test.js
├── ai-client.test.js
├── handlers.test.js
└── render.test.js
tests/fakes/fake-ai.js (new) — { run: vi.fn() } factory with
result-builder helpers (e.g., okJudgement({ is_guess, answer, hint })).
Related code files
Create
tests/modules/twentyq/seeds.test.jstests/modules/twentyq/state.test.jstests/modules/twentyq/validate-input.test.jstests/modules/twentyq/ai-client.test.jstests/modules/twentyq/handlers.test.jstests/modules/twentyq/render.test.jstests/fakes/fake-ai.js
Edit
src/modules/twentyq/README.md— replace phase-1 stub with full doc.docs/codebase-summary.md— add a one-line entry for the new module (only if existing modules are listed there).docs/development-roadmap.md— mark this plan as completed once shipped (per global feedback rule: roadmap tracks future work; completed work documented in git log + plan file).
Implementation steps
- Create
tests/fakes/fake-ai.js:createFakeAi()returning{ run: vi.fn() }.- Helper
mockJudgement(ai, { is_guess, answer, hint })configures the mock to return a function-call shape matching whatai-clientparses.
- Write each test file in order matching the production-file order, using the existing doantu tests as the structural template.
- Run
npx vitest run tests/modules/twentyq/iteratively until green. - Run full suite (
npm test) — must stay green. - Replace
src/modules/twentyq/README.mdwith:- One-paragraph game description + Telegram
/slot. - Commands table (visibility column).
- Example flow (copy from
plan.md). - "Data source" — Workers AI Gemma 4 26B A4B + fixed seed list.
- "Architecture" — file-by-file, ~1 line each.
- "Storage" — KV layout table (mirror doantu README format).
- "Config" — env vars table (none in v1; document
env.AIbinding). - "Credits" — game concept (20 questions / Akinator-reverse).
- One-paragraph game description + Telegram
npm run register:dry— confirmsetMyCommandspayload includestwentyq,twentyq_giveup,twentyq_stats(allpublic).npm run lint— clean.- Manual one more end-to-end sanity check via
wrangler dev+ tunnel.
Todo list
tests/fakes/fake-ai.js— AI binding stub + helpersseeds.test.js(3–5 tests)state.test.js(5–7 tests)validate-input.test.js(6–8 tests)ai-client.test.js(4–6 tests)handlers.test.js(8–12 tests — happy path, edge cases, dedup, errors)render.test.js(4–6 tests — escape, all formatters)- README replacement
register:dryshows public commands- Full
npm testgreen - Mark plan status
completedinplan.mdfrontmatter
Success criteria
npm testgreen (all 200+ existing + new).npm run lintgreen.register:dryshows the three public commands.- README opens cleanly, matches doantu/semantle structure.
- Manual play in
wrangler devconfirms full game loop.
Risk assessment
- AI response shape mismatch between fixture and real Gemma response → ai-client tests pass but production breaks. Mitigation: capture one real response in dev (logged + redacted); use it as the test fixture canonical.
- Test flake —
getRandomSeedcould non-determ if rng default leaks into test. Mitigation: always pass deterministic rng in tests. - README drift — multiple modules have similar README shapes; copy from doantu and edit, don't write from scratch (consistency).
Security considerations
- Test fixtures must NOT contain real bot tokens or webhook secrets (none needed — all logic-level).
- README must not document any internal endpoint or account id.
Next steps
After this phase:
- Update
plan.mdfrontmatterstatus: completed. - Commit + push (conventional commit:
feat(twentyq): add reverse-Akinator yes/no game module powered by Workers AI). - Run
npm run deploy(auto-applies migrations + registers webhook/commands). - Smoke test on production bot via
/twentyq.