Testing
Who is this for
Section titled “Who is this for”A developer who is about to change code in ai-sticker-pack or stickers-landing and needs to know which tests
exist, how they stay offline, and what must be green before a commit.
- Product:
python -m pytestfrom the repository root. 888 tests are collected at commitb715418(python -m pytest --collect-only -q). No keys, no network, no real Telegram. - Tests use mocks only. A live AI call is allowed only to look at a picture with your own eyes, with a budget stated for that task and a hard stop at that budget.
- An automatic fixture in
tests/conftest.pyneutralises your localbot/.env, so the suite gives the same result on every machine. - The kiosk idle timer has a JavaScript test run by
node --test; pytest runs it for you and skips it when Node.js is missing. - Landing:
npm test,npx tsc --noEmit,npm run lint,npm run build— all four before a commit. - A hanging check must fail within a minute. Landing tests run with
--test-timeout=60000; quality scripts use a watchdog.
Prerequisites
Section titled “Prerequisites”| Repository | Needs |
|---|---|
| Product | Python 3.14 locally (the pinned server requirements target Python 3.12 on the server); a virtualenv with `bot |
| equirements-dev.txt`; optional Node 22 for the kiosk JS tests. Full setup: Local setup: product | |
| Landing | Node 22 or newer; npm install. Full setup: Local setup: landing |
Product
Section titled “Product”Layout
Section titled “Layout”pytest.ini testpaths = tests, python_files = test_*.pytests/ conftest.py shared fixtures, isolation from the local .env, synthetic sheet factory tg_fake.py fake Telegram session for a real aiogram Bot and Dispatcher test_*.py 46 test modules quick_test.py stand-alone AI comparison script (NOT collected by pytest), has --selftest js/kiosk_idle.test.mjs kiosk idle timer, node:test with mock timers fixtures/ saved public pack pages (present / absent) and reference promptsProduction code lives in bot/ and is imported exactly as main.py imports it: conftest.py puts bot/ on
sys.path.
python -m pytest # everythingpython -m pytest tests\test_api.py # one modulepython -m pytest -k "leads and not housekeeping" # by keywordpython -m pytest -x -q # stop at the first failure
python tests\quick_test.py --selftest --out out\selftest # offline check of the sheet-cutting scriptnode --test tests\js\ # kiosk JS test on its own (optional)quick_test.py --selftest draws a synthetic sheet, cuts it, writes report.html and prints SELFTEST OK. It
makes no API call. Every other mode of that script is paid; see
Local setup: product.
What each test group covers
Section titled “What each test group covers”| Family (files) | What it protects |
|---|---|
Kiosk API — test_api.py, test_kiosk_idle.py, test_messages.py |
Session → QR → outfit → photo → queued job through FastAPI TestClient; status codes, upload limits, template list order and preview caching; idle reset on the outfit screen (server side plus the JS timer); wording and order of guest messages. |
Bot handlers — test_bot_handlers.py, test_admin_stats.py |
Consent callback; /stats through a real Dispatcher, including the access filter. |
Queue and limits — test_db_queue.py, test_pack_limit.py, test_pack_naming.py |
Session state transitions, queue, one-pack-per-account rule checked atomically, pack name and title rules. |
Worker end to end — test_worker_pipeline.py, test_shutdown.py, test_speedups.py |
The whole pipeline with a fake AI and a fake bot: what is really sent to Telegram; graceful stop and re-queue without losing an attempt; stage timing log lines and safe speed-ups. |
Delivery — test_delivery.py |
The link is sent only after getStickerSet reports 12 stickers and the public pack page confirms twice; the 5-minute notice and the 15-minute failure, both surviving a restart; database migration. |
Image processing — test_sticker_builder.py, test_chroma_regression.py, test_layout_regression.py, test_cut_cv.py, test_outline_mode.py, test_template_m1.py |
Cutting, chroma key, composing on a body template, export to 512 px; regressions from real bad sheets, reproduced synthetically; outline A/B switch; the ready-made body template and the raw-to-template tool. |
Figure mode — test_figure_mode.py, test_figure_cut.py, test_render_mode.py |
Figure sheet prompts, poses and emoji, cutting robust to gaps, attempts and the composite fallback; render mode read from .env (including a byte-order mark), empty value or typo is an error, second main.py refuses to start. |
Identity and likeness — test_appearance.py, test_consistency.py, test_likeness_classic.py, test_likeness_hybrid.py, test_skin_zone_flat_cut.py |
Appearance card schema, identity lock text, QA retries; anchor portrait, skin normalisation, pack QA and targeted regeneration; the classic and hybrid likeness modes; skin shift cap and the flat side-cut detector. |
Outfits — test_outfits.py |
Catalogue entries, preview card format and size (640 × 640 WebP ≤ 70 KB served, 1024 × 1024 source ≤ 250 KB), outfit text in prompts and QA, default outfit, re-choosing before the photo. |
Cost and billing — test_usage.py, test_costs.py, test_billing.py |
Usage recording per call, cost estimate from .env prices, per-job ceiling, daily image counter; the “out of credit” (402) path and maintenance mode. |
Admin panel — test_admin_panel.py |
HTTP Basic, lockout after five failures, no-store / noindex headers, summary, guest table without photos, audited actions, /admin_stats, safe migration of an existing database. |
Alerts and config — test_alerts.py, test_config_logging.py, test_event_guard.py, test_storage.py |
Alert throttling and triggers; monitoring settings and file logging; the paid-tool guard; TTL cleanup. |
Leads — test_leads_api.py, test_leads_bot.py, test_leads_commands.py, test_leads_pick.py, test_leads_repeats.py, test_leads_schedule.py, test_leads_housekeeping.py, test_leads_wiring.py |
Field validation identical to the landing schema, POST /leads, card and status buttons, publishing with retries, group commands and their access rules, picking a lead with buttons, repeat requests within 24 hours, reminder / digest / retention timing with frozen time, the exact dispatcher built by main.build_dispatcher. |
Deploy — test_deploy.py, test_deploy_caddy.py |
Shell syntax and LF line endings of deploy scripts, systemd units, reverse-proxy config, what the push script excludes, strict prod start-up, /health, 429 back-off. Nothing touches a server: stubs replace system commands. |
Tools — test_style_compare_pair.py |
The pair mode of tools/style_compare.py without live calls, including its generation and budget limits. |
| Fake | Where | What it replaces |
|---|---|---|
| Environment isolation | conftest.py, autouse fixture _isolate_from_local_env |
Overrides the loaded config for every test: empty bot token and AI keys, no alert chat, maintenance off, composite render mode, consistency off, layout 2x6, prices unset, leads without a secret, placeholders hidden, log directory in a temp folder. Your real bot/.env cannot leak into a test. |
| Public pack page | same fixture | telegram_stickers.check_tme always answers “present”. Negative scenarios replace it themselves. Polling pauses in delivery are reduced to about a millisecond. |
| Vision model | same fixture | ai_pipeline.vision_json raises “vision disabled in tests”. The pipeline must work without it; tests of the appearance card and QA install their own stub. |
| Image model | per test (monkeypatch) |
Tests replace a pipeline entry point (for example ai_pipeline.generate_head_sheet) or the provider runner with a function that returns a synthetic sheet from conftest.make_sheet() (green background, circle heads). This is the “fake AI”. |
| Telegram for the worker | FakeBot classes inside test modules, e.g. test_worker_pipeline.py |
Records createNewStickerSet and send_message; asserts that packs are never deleted and that 12 stickers go in one call. |
| Telegram for handlers | tests/tg_fake.py |
A real aiogram Bot whose session records Bot API requests instead of sending them. Updates are fed through a real Dispatcher (dp.feed_update), so router filters and middleware are exercised. Helpers: make_bot(), message_update(), callback_update(). |
| Time | parameters now= / clock= |
Lead scheduling and rate limiters take the clock as an argument; tests freeze it. |
Kiosk JavaScript test
Section titled “Kiosk JavaScript test”tests/js/kiosk_idle.test.mjs
loads bot/kiosk/idle.js and drives it with the mock timers of node:test. No browser is involved.
tests/test_kiosk_idle.py finds node on PATH and runs node --test on that file; if Node.js is absent the
test is skipped, not failed. Install Node.js before changing idle.js.
Visual and behavioural checks of the whole kiosk page are not part of pytest: run python tools\kiosk_check.py
(free, needs Edge or Chrome).
The rule about live calls
Section titled “The rule about live calls”- Tests and code checks use mocks only.
- A live call exists for one purpose: a human looks at the generated picture.
- Each live run has a budget in dollars stated for that task, and stops at it.
- One run per check: one face first; more only if the first result is acceptable.
- Paid tools use
TEST_GEMINI_API_KEYwhen it is set and refuse to start withEVENT_MODE=1without it.
The reasons are on Code rules.
Pre-commit checklist (product)
Section titled “Pre-commit checklist (product)”python -m pytest— green, and no unexpected skips (check that the kiosk JS test ran if you touchedbot/kiosk/).python tests\quick_test.py --selftest --out out\selftest— printsSELFTEST OK.- If you touched
bot/kiosk/:python tools\kiosk_check.pyexits with 0. - If you touched templates, outfits or cutting: the matching preview tool from Outfits, templates and art styles.
git statusshows nobot/.env,bot/data/,*.db,preview_*.pngorout/.- If behaviour, API, environment variables or deployment changed: the matching page of this documentation is updated in the same pull request.
- Commit only when asked to; the author comes from the repository’s local git configuration.
Landing
Section titled “Landing”Commands
Section titled “Commands”npm test # tsx --test --test-timeout=60000 on six test filesnpx tsc --noEmit # type check (TypeScript strict)npm run lint # eslintnpm run build # production buildRequires Node.js 22 or newer.
What each test file enforces
Section titled “What each test file enforces”| File | Enforces |
|---|---|
src/i18n/dictionaries.test.ts |
ru, uz and en have identical keys and array lengths; no empty string; nothing promises what the product cannot do or names what must not be named; the only numbers are 12, 72 and “3D” (plus the phone sample); Uzbek apostrophes use the correct Unicode letters; a dash never starts a line; each part of the page keeps one form of address; Accept-Language routing; the browser language never selects /uz; the Uzbek publish flag in both positions. |
src/content/packs.test.ts |
Every configured pack has its 12 stickers on disk in both sizes; every outfit has a preview card; the mock kiosk is exactly 3 + 3 with no outfit twice; six demo people with one outfit each; the owner’s pack appears in the hero only; the sticker ribbon uses demo people only, sixteen per row, no repeats. |
src/lib/requestSchema.test.ts |
Form validation and normalisation: required name, Telegram handle and phone; accepted and rejected handle and phone formats; optional fields; length limits; trimming; the layout of the message posted to the group. |
src/lib/deliverRequest.test.ts |
Delivery routing: success on the leads server with the secret header; a duplicate counts as success; timeout, network error, 5xx, 401, 404 → Telegram fallback with a warning line; 429 → throttled, no fallback; 422 → failure, no fallback; both routes failing → failure and the form stays; no leads variables → direct Telegram; no variables at all → console; the secret never reaches the client. |
src/lib/requestSent.test.ts |
The “request sent” flag lasts 24 hours; flags without a valid date are expired; the inline gate before first paint follows the same rule and survives blocked storage. |
src/components/hero/scene/steps.test.ts |
Every step of the hero scene lasts 3–4 s; the stepper has six steps and each scene step belongs to exactly one; one device is in focus per step. |
The server-side field rules in bot/services/leads.py mirror requestSchema.ts. The product test
tests/test_leads_api.py reuses the same examples, so a change to one side must be made on both. See
API reference.
Timeout and watchdog rule
Section titled “Timeout and watchdog rule”A hang is an error within a minute, not hours of waiting.
npm testpasses--test-timeout=60000. It catches both a slow test and an endless loop at module import.- Each quality script (
audit,shots,film,js-weight) runs in steps throughscripts/lib/watchdog.mjs: a minute without progress prints the name of the stalled step and exits with code 1. A step that is long on purpose declares its own limit and the reason. - A slow machine raises the limit with
STEP_TIMEOUT=<ms>; it never removes it. - A new check script starts with its own watchdog.
- When you run checks from a terminal, give each command its own time limit instead of one unlimited chain.
- On Windows, killing a timed-out command stops only the parent process.
next buildworkers may keep.next/lock, andnext startmay keep port 3000. Find the owner and stop the whole tree:
Get-NetTCPConnection -LocalPort 3000 | Select-Object OwningProcesstaskkill /PID <pid> /T /FVisual checks (npm run shots, npm run audit, npm run film, npm run lighthouse) are described on
Landing quality tooling.
Pre-commit checklist (landing)
Section titled “Pre-commit checklist (landing)”npx tsc --noEmitnpm run lintnpm testnpm run build- For any visible change:
npm run shotsandnpm run auditagainst a running server, and look at the screenshots. - No string of page text hard-coded in a component; all three dictionaries updated together.
- Commit and push only when asked to; never pass
-c user.*to git.
Where to go next
Section titled “Where to go next”- Code rules — the rules these tests enforce, with reasons.
- Landing quality tooling — audit, screenshots, Lighthouse.
- Local setup: product — paid tools and their caps.
- Outfits, templates and art styles — which tests to run after a catalogue change.
- Deploy & rollback — what runs after the tests pass.
Last verified: 2026-11-13 against ai-sticker-pack@b715418 and ai-stickers-landing@e350ff5.