# Test Plan (Feature + Integration + Mocks)

Scope aligns with PRD §13 acceptance and §15 ops.

## Feature Tests (User Flows)

- Onboarding: `/start` → role, city, budget, frequency → main menu shown; `users` updated.
  - City normalization: entering an alias maps to correct `city_id` via `city_aliases`.
- Main Menu parity (Reply Keyboard):
  - Tap «🏠 منوی اصلی» or send text «منوی اصلی»/«منو»/`/menu`/`menu` → expect exact same behavior as `/start` for existing users (persistent ReplyKeyboard shown + main menu message). For new users, `/start` triggers onboarding; tapping the button after onboarding still shows the menu.
  - Emoji normalization: sending a text that includes the house emoji plus the label still matches due to emoji stripping.
- Opportunities Today push: admin schedules → users in segment receive; buttons work; CTR tracked.
- Affordable Ads: returns 7–10 items for user city; paging; direct links; ⭐ save recorded.
- Valuation: collects inputs; `/ai/respond` returns valid JSON schema; summary text renders without تماس/CTA بیرونی و فقط دکمه‌های داخلی («تحلیل قیمت»، «تمرین»، «بازدید») فعال است.
- Negotiation: scenario starters; guided Q&A across ≥2 turns; thread preserved.
- Expert Q&A + Inspection: bullet answers; checklist produces risk map + next step.
- Legal Q&A: includes disclaimer from admin; concise bullets.
- Contracts: sends two fixed files (buy/sell) from storage.
- Quick Visit 3-min: 8–10 questions then risk map + CTA.
- City Report: weekly cron generates & pushes segment content.
- Payments: create → redirect URL (+authority); verify callback idempotent by authority, activates subscription; reconcile closes stale pendings.

## Saved Ads (⭐) Tests

- Save opportunity: `POST /user/saved_ads {"source":"opportunity","opportunity_id":123}` returns saved; duplicate returns 409.
- Save external ad: `POST /user/saved_ads {"source":"external","external_source":"bama","external_code":"C123","link":"https://..."}` returns saved; duplicate per user returns 409.
- List saved: `GET /user/saved_ads` returns items with correct fields.
- Unsave: `DELETE /user/saved_ads/{id}` removes record if owned; 404 otherwise.

## Integration Tests

- AI Provider (live Responses API): `/api/ai/respond` returns valuation envelope `{json,response_id,token_in,token_out,latency_ms}`; invalid JSON retries once.
- Payments (shetabit/payment): mock gateway responses for create/verify; `POST /payments/create` takes `{plan}` and uses DB (plans.amount_irr, duration_days); idempotent verify; failure paths.
- majidapi Client: mock HTTP for `bama?action=latest/search/details` and `tools/scraper`; verify normalize/dedup/score; −15% → −10% → rules.
- Jobs: BamaPullLatestJob, DivarScrapeJob ingest fixtures; ScoreOpportunitiesJob normalizes→dedups→scores; reasons_json length ≤ 2.
- Telegram Webhook: feed Telegram update payloads (message/callback) and assert outgoing messages via Telegraph fakes.

## Entitlements & Quotas

- Seeded entitlements for plans (Free/Member/Pro/Elite) exist in `plan_entitlements`.
- QuotaService: `checkAndConsume(user, valuations_per_month, month)` decrements and blocks after limit; persists in `user_quotas` for the period.
- FeatureGuard middleware:
  - `/api/ai/respond` with `feature.guard:pricecheck` returns 403 after limit.
  - `/api/user/saved_ads` GET with `feature.guard:view_deals` returns 403 after entitlement exhausted.
  - Admin push `/api/admin/opportunities/push` with `feature.guard:instant_push` returns 403 after daily cap.

## Add-ons

- Addons CRUD allows creating `entitlements_json` and attaching via `user_addons`; EntitlementResolver aggregates plan + addons numerically.

## Experiments & Push

- ExperimentsSeeder seeds PUSH_HOUR {09,11,14} and UPSELL_MESSAGE {speed,accuracy}.
- ExperimentService assigns a variant on first call and returns stable result.
- PushSchedulerJob transitions scheduled campaigns to sent; `push_logs` entries can be manually created and updated.
- Simulate click: `POST /simulate/click {id}` sets `clicked_cta_at`.

## Trial & Wallet

- GrowthDailyJob runs without errors; updates to `trials` and `wallet_ledger` are stubbed for now.

## Referral & Cancellation

- ReferralLinks and ReferralEvents tables accept inserts; unique code constraint validated.
- Cancellations resource accepts record creation with reason/selected_offer.

## Support Tickets (v1)

- Create: `POST /support/tickets {subject, body}` creates ticket with status=open.
- List (user): `GET /support/tickets` returns only user’s tickets.
- Admin reply: `POST /admin/tickets/{id}/reply {body}` sets `admin_replied_at`, stores last reply text; optional close.

## Performance/Resilience

- AI latency sampling: measure 100 calls with fake sleep distribution; assert p80 ≤ 2.5s budget in production env with real provider behind 20s timeout & retry once.
- Rate-limit: send >5 req/min/user to AI endpoint; expect 429 and correct `Retry-After` header.
- Backoff: simulate majidapi 429/5xx; ensure exponential retries and final logging.

## Data Contracts Validation

- JSON schema for valuation strictly enforced; fallback behavior on invalid JSON (treat as error and ask for more details).
- Valuation text regression: assert outputs do not contain دعوت به تماس/خرید (مثلاً «برای خرید مطمئن...»).
- Unique constraints: `users.tg_id`, `opportunities.dedup_key`, `vector_stores.openai_vector_store_id`, `vector_files.openai_file_id`.
- Index usage: basic query plans covered for `opportunities (city_id, brand, model, year, price)`.
- Saved ads uniqueness: enforce UK(user_id, opportunity_id) for source=opportunity; UK(user_id, external_source, external_code) for source=external.

## Mocks & Fixtures

- OpenAI Responses samples:
  - Valuation JSON envelope (see API_SPEC.internal.md).
  - Generic text response.
- majidapi samples:
  - `bama?action=latest` list payload (subset fields used by normalizer).
  - `bama?action=details&code=...` detail payload.
  - `tools/scraper?url=...` HTML snippet for parser test.
- Zarinpal verify:
  - Success with `RefID` returned; failure with error code.
- Telegram update payloads:
  - Message with command; CallbackQuery for paging.

## Cron/Queue Tests

- Scheduler registers jobs per PRD (daily report, city report weekly, payments reconcile).
- Queue uses DB driver; failed jobs table captures errors; retry counts respected.

## OPEN QUESTIONS

- CTR is measured for week‑1 pilot; denominator is delivered messages, numerator is any CTA click on the opportunity card.
## Phase 2C Focused Tests (New)

- Admin RBAC
  - `POST /api/admin/*` requires `role:admin`; unauthenticated/unauthorized requests receive 403.

- Webhook security
  - `POST /api/telegram/webhook` without valid secret header returns 401; with correct secret returns 200.

- Saved Ads CRUD ownership
  - Ensure only owner can delete; duplicates return 409.

- AI persistence and events
  - `/api/ai/respond` inserts chats row and emits `PriceCheck` (if user present).

- Opportunities browse (admin)
  - `GET /api/opportunities?city_id=...` returns items and requires `role:admin`.
