# TestSprite and E2E runs

## 2026-10-04 — Channel auto-sync live smoke (local compose)

- **Stack:** Rebuilt `api` + `frontend` on compose (`8890` / `3091`). Mock Odoo JSON-RPC on host `:3999` (`host.docker.internal`).
- **API:** Enabled Odoo, PATCH email channel `integrations.auto_sync_provider=odoo`. First inbound created `ticket_integration_links` `external_id=4242`. Second reply on the same ticket did **not** add another link.
- **Browser:** Channel **Ticket sync** combobox shows Odoo (other providers disabled until Settings). Ticket rail **Open in Odoo**. Thread has first message + follow-up only.
- **Live `support.appnotify.co.za`:** Current bundle still lacks Ticket sync (not deployed).
- **TestSprite:** Not run; Odoo was a local mock, not a real instance.

## 2026-10-04 — Channel auto-sync (first engagement)

- **Change:** Channel **Ticket sync** dropdown; `maybeAutoSyncFirstEngagement` on `messages.sequence === 1`. Web chat inherits via `channelService.getById`.
- **Typecheck:** `@wa-ticketing/shared` build, API + frontend `tsc --noEmit` passed.

## 2026-10-03 — Ticket integrations

- **Migration:** `029_ticket_integrations.sql` applied via `npm run migrate:sql`.
- **API smoke:** `GET /api/v1/integrations` (admin) returns odoo/trello/atlassian/github rows; `GET /enabled` returns `[]` until a provider is enabled.
- **Stack:** Compose on `FRONTEND_PORT=8890`, `API_HOST_PORT=3091`, `POSTGRES_HOST_PORT=15432` (host 5432/3000 occupied).
- **TestSprite:** Skipped — no remote provider credentials in this session; configure under Settings → Ticket integrations for live sync.

## 2026-09-29 — Landing chat database permissions

- **Change:** Removed owner-only index DDL from `POST /api/v1/landing/chat`; migration `026_landing_sessions_api_privileges.sql` grants the `postgres` API role `SELECT`, `INSERT`, and `UPDATE`.
- **Migration:** Fresh isolated Supabase Postgres initialized successfully; `npm run migrate:sql` applied migration `026`. Verified `landing_sessions` remains owned by `supabase_admin` while `postgres` can insert and update.
- **Unit/API:** API unit suite **28/28 passed**; API TypeScript build passed; direct unauthenticated `POST /api/v1/landing/chat` returned a session and welcome messages.
- **TestSprite fallback:** TestSprite tooling was unavailable in this runtime. The exact affected API path was smoke-tested against the isolated database. The full monorepo build remains blocked by the pre-existing `services/whatsapp/src/worker.ts:153` Buffer type error.

## 2026-07-24 — Landing chat feature (Docker unavailable)

- **Change:** Public landing chat on `/login` (`POST /api/v1/landing/chat`, migration `022_landing_sessions.sql`).
- **Unit tests:** `landingChatService.test.ts` / `detectLandingIntent` — **4/4 pass** (`npx tsx --test` in `apps/api`).
- **Migrate:** `npm run migrate:sql` failed — Docker Desktop engine not running (`dockerDesktopLinuxEngine` pipe missing). Apply `022_landing_sessions.sql` when the stack is up.
- **TestSprite / browser smoke:** Skipped — app not reachable (no Docker). Re-run migrate + smoke (guest help → onboarding; chat sign-in → dashboard) after `docker compose up -d`.

## 2026-06-08 — Full system QA pass

- **Stack:** Docker on `http://localhost:8890` (UI) / `http://localhost:3091` (API) per root `.env` `FRONTEND_PORT` / `API_HOST_PORT`.
- **Playwright:** **5/5** when run sequentially; **4 passed + 1 skipped** under full parallel (channels link wait — fixed with extra visibility assert). `playwright.config.ts` now reads `.env` ports; channels smoke targets WhatsApp link (`+/digits`), not portal channel.
- **Manual/browser audit:** Admin routes load (Dashboard, Tickets, Contacts, Channels, Companies, KB, Settings, Users, AI agents, Audit). Ticket detail blocks composer when WA disconnected; failed sends show “Not delivered to WhatsApp”. Date-range filter (TC008) and audit actor filter (TC025) present and API-backed.
- **API edge cases verified:** 401 without auth, 400 empty message, 400 closed ticket reply, 409 duplicate user, 400 empty onboarding template (after fix), 409 duplicate company (after fix). Staff blocked from `/users` and `/audit-logs` (403).
- **Fixes shipped:** `validateChannelSettings` on PATCH channels; `companyService.create` returns 409; Companies UI shows API error; e2e + Playwright port discovery.
- **Trello (Silicon Support → QA & TestSprite):** [empty onboarding templates](https://trello.com/c/WBi10Th0), [duplicate company](https://trello.com/c/pOJw6KPJ), [e2e channels smoke](https://trello.com/c/9eCmngcN), [Playwright .env ports](https://trello.com/c/r01p22Ou) — moved to **DONE** after fix.
- **Prior TestSprite failures re-checked:** TC008/TC025 appear fixed in UI; TC022 has `invalidateQueries` on role change; TC011 channel detail loads and saves in manual pass; TC013 API gap closed.

## 2026-06-08 — Client portal walkthrough (company_user)

- **Test account:** `client-qa-202606081212@example.com` / `Client123!` (created via admin `POST /companies/:id/users`, company Silicon Support Updated).
- **Login:** Success → dashboard with **Dashboard + Tickets** nav only (no Channels, Contacts, Companies, Admin).
- **Dashboard:** Company-scoped copy (“Your company”), open/recent ticket cards after first ticket created.
- **Start ticket:** Composer on `/tickets` → message sent → redirected to ticket detail `a21d3e32-…`; initial message visible in thread.
- **Reply:** Follow-up message in ticket thread delivered and shown in UI.
- **Tickets list:** Shows **1 ticket** after data load (brief “0 tickets” flash before fetch completes — minor UX, not blocking).
- **Route guard (logged in):** `/channels` → redirect to `/` (dashboard).
- **Route guard (logged out):** `/companies` → `/login`.
- **Logout:** Clears session; wrong password shows “Invalid email or password”.
- **Sign up:** Dialog opens SignupChatPanel with welcome (“new” vs “join” company) — prospect flow loads.
- **API as client:** `GET /users`, `/channels`, `/companies`, `/audit-logs`, `/contacts` → **403**; `POST /portal/messages` with empty body → **400** `content is required`.
- **Result:** Core client journey **pass** — login, open ticket, message, list, logout, signup entry, staff isolation.

## 2026-04-02 (documentation)

- **`PRD_v2_WhatsApp_Ticketing_System.md`** → **v2.2**: contact detail, company linking (`PATCH /contacts/:id`), companies API/UI, portal notes, schema updates (`companies`, `contacts.company_id`, `users.display_name` / roles).
- **TestSprite**: [`testsprite_tests/standard_prd.json`](../testsprite_tests/standard_prd.json) and [`testsprite_tests/testsprite_frontend_test_plan.json`](../testsprite_tests/testsprite_frontend_test_plan.json) updated (new routes + **TC030–TC033**). Summary in [`testsprite_tests/testsprite-mcp-test-report.md`](../testsprite_tests/testsprite-mcp-test-report.md).
- For MCP runs, optionally refresh `testsprite_tests/tmp/code_summary.yaml` via TestSprite **generate code summary** after route changes.

## 2026-04-02

### TestSprite (cloud)

- **Command**: TestSprite MCP `testsprite_generate_code_and_execute`, then `node scripts/run-testsprite-execute.mjs` with Docker **frontend** on **:3000** and **api** on **:3001**.
- **Local CLI**: Logged `checkPortListening tcp timeout: 3000 localhost`, tunnel `503` on first probe, then many connection timeouts — execution still **completed** and refreshed `testsprite_tests/tmp/raw_report.md`.
- **Outcome (raw report)**: ~**79.31%** passed (**23**/29). **Failed**: TC008 (no ticket date-range UI), TC011 (timeout on channel AI/templates flow), TC013 (empty template save without validation), TC022 (role change not reflected in UI per agent), TC025 (no audit “actor” filter), TC029 (non-admin login used invalid placeholder credentials).
- **Committed summary**: [testsprite_tests/testsprite-mcp-test-report.md](../testsprite_tests/testsprite-mcp-test-report.md). Full per-case links remain under **gitignored** `testsprite_tests/tmp/`.

## 2026-03-31

### TestSprite (cloud)

- **Bootstrap / plan**: `testsprite_tests/tmp/code_summary.yaml`, `testsprite_tests/testsprite_frontend_test_plan.json`, and `testsprite_tests/standard_prd.json` were generated.
- **Execution**: The CLI reported `AUTH_FAILED` / “Please create a new API_KEY” when posting results to TestSprite’s backend (HTTP 401), even after tunnel connectivity succeeded. **Action**: In the TestSprite product UI, create or rotate an API key and configure the Cursor TestSprite MCP with that key. Optionally set `TESTSPRITE_API_KEY` in the environment and run `node scripts/run-testsprite-execute.mjs` (the script prefers `TESTSPRITE_API_KEY`, then `API_KEY`, then the value in `testsprite_tests/tmp/config.json` if present).
- **Local helper**: [scripts/run-testsprite-execute.mjs](../scripts/run-testsprite-execute.mjs) runs `npx @testsprite/testsprite-mcp generateCodeAndExecute` with credentials.
- **Git**: `testsprite_tests/tmp/` is listed in `.gitignore` so tunnel config and logs are not committed.

### Playwright (local smoke)

With Docker (or any stack) serving **http://localhost:3000** and API **http://localhost:3001**:

```bash
npm install
npm run build --workspace=@wa-ticketing/shared
npx playwright install
npm run test:e2e
```

Default admin login in tests: `admin@example.com` / `Admin123!` (override with `E2E_ADMIN_EMAIL` / `E2E_ADMIN_PASSWORD`).

**Result (2026-03-31)**: All 3 smoke tests passed (login success, login failure message, channels → channel detail).

### Fixes made while validating

1. **401 on login failure**: The global axios interceptor redirected to `/login` on every 401, including failed `POST /auth/login`, so the inline error never appeared. **Fix**: Skip redirect when the request URL includes `/auth/login` ([apps/frontend/src/lib/api.ts](../apps/frontend/src/lib/api.ts)).
2. **API container crash (`ERR_UNKNOWN_FILE_EXTENSION` for `.ts`)**: Runtime imports from `@wa-ticketing/shared` pointed at TypeScript sources. **Fix**: Compile `packages/shared` to `dist/` via `tsc` and set `main`/`exports` to `./dist/types.js`; Dockerfiles run `npm run build --workspace=@wa-ticketing/shared` before building API/frontend.
3. **Existing Postgres volume without new columns**: If `channels.settings` / `tickets.intake` are missing, the API returns 500. Apply migration SQL (as a superuser/owner if `postgres` is not table owner), e.g.:

```bash
docker compose exec -T db psql -U supabase_admin -d postgres -c "ALTER TABLE public.channels ADD COLUMN IF NOT EXISTS settings JSONB NOT NULL DEFAULT '{}'; ALTER TABLE public.tickets ADD COLUMN IF NOT EXISTS intake JSONB NOT NULL DEFAULT '{}';"
```

## 2026-09-29 — Africa's Talking WhatsApp connector (docs + plan, OVER-345 Task 6)

- **Change:** Provider choice on Add Channel (Baileys / Africa's Talking), webhook URL card, capability badges, disabled group/reconnect actions; migration `028_channel_whatsapp_provider.sql`; env passthrough `AFRICASTALKING_USERNAME` / `AFRICASTALKING_API_KEY` in both compose files.
- **Test plan:** [`testsprite_tests/testsprite_frontend_test_plan.json`](../testsprite_tests/testsprite_frontend_test_plan.json) extended with **TC048–TC052** (provider selection, webhook URL display, capability badges, no Reconnect, disabled group option).
- **TestSprite / browser execution:** deferred to OVER-345 Task 7 — see the Task 7 section below for what was actually run and the TestSprite blocker.
- **Not exercised:** a real Africa's Talking callback and live Africa's Talking credentials.
- **Config checks in this task:** compose config validated for `docker-compose.yml` and `docker-compose.dokploy.yml`; see the task report for details.

## 2026-09-29 — Africa's Talking WhatsApp connector (verification, OVER-345 Task 7)

**TestSprite: not run — no authenticated config in this environment.** Exact blocker from `node scripts/run-testsprite-execute.mjs`:

```
Missing /…/silicon_support/testsprite_tests/tmp/config.json — run TestSprite bootstrap from Cursor first.
```

`testsprite_tests/tmp/` (gitignored, holds the API key and tunnel config) does not exist, no `.testsprite/config.json` is present, and neither `TESTSPRITE_API_KEY` nor `API_KEY` is set. The runner exits at its precondition check, so nothing was sent to TestSprite. To run it: complete TestSprite bootstrap from Cursor (or set `TESTSPRITE_API_KEY`) with the stack reachable, then re-run the script. The plan cases **TC048–TC052** added in Task 6 remain unexecuted.

**What was verified locally instead**, against a real Docker stack (`db` + `kong` + `api` + `frontend`, project `wa345`, UI `:13000` / API `:13001`, temporary gitignored env file, deleted afterwards):

- **Frontend unit tests:** **14/14 pass** (`npm run test:unit --workspace=@wa-ticketing/frontend`) — a new suite, the first in `apps/frontend`. Covers the pure send-gating helpers: composer usable on a disconnected Africa's Talking channel, still blocked on a disconnected Baileys channel, both allowed when connected, `connecting` blocked on either, and the banner-notice selection.
- **API unit tests:** **189/189 pass** (`env -u DATABASE_URL npm run test:unit --workspace=@wa-ticketing/api`), up from 93. The suite now also globs `src/routes/*.test.ts`. Added coverage: the webhook media cap clamp; the bounded, deadlined fetch queue and its slot-ownership rules; `Retry-After` mapping for a shed callback; rejection of the 6to4, Teredo and IPv4-translated tunnel prefixes plus IPv4-mapped dotted-form literals; the outbound provider status lifecycle; form-encoded callback parsing through `fastify.inject` against the real route; delivery/status/read-receipt acknowledgement versus malformed-payload rejection; the per-channel-token rate-limit key plus a two-token isolation check through `@fastify/rate-limit` itself; the staff-send eligibility rule (Africa's Talking `disconnected` allowed, Baileys `disconnected` refused) and its composition with the status-recovery wrapper; and delivery/status aliases in `messageType` / `mediaType` versus real image/audio/voice/document payloads — including receipts that **echo the original message type** (`{eventType:'delivery', messageType:'text'}`, `{type:'text', status:'delivered'}`), which are acknowledged as events because they carry no content, while a type accompanied by actual text or media is still a message. Everything added after the live stack run below is unit/route-inject evidence only — the live run exercised the 6to4, NAT64, mapped, loopback and link-local rejections through the real route, and no live run has been repeated since.
- **Note for anyone re-running these:** the route suite imports the real route plugin, which transitively loads `bcrypt`. An `npm install --ignore-scripts` leaves its native binding unbuilt and the suite dies at import; `npm rebuild bcrypt` fixes it, and the Dockerfile's `npm ci` builds it normally.
- **Builds:** `@wa-ticketing/shared`, `@wa-ticketing/api` and `@wa-ticketing/frontend` all build. The `@wa-ticketing/whatsapp` worker `tsc` fails on a **pre-existing** `Buffer<ArrayBufferLike>` error at `services/whatsapp/src/worker.ts:153` — reproduced identically inside the worker's own `node:20-alpine` image, so it is not host-Node-specific. The worker runs via `tsx` (no `tsc` in its runtime path), its image builds, and the container starts and waits for the API as designed. Root `npm run build` therefore exits non-zero on this repo regardless of this change.
- **Playwright (real stack):** `e2e/smoke.spec.ts` **5/5 pass**, including the new `add WhatsApp offers provider choices and Africa's Talking avoids QR`. `e2e/signup-browser.spec.ts` fails — **pre-existing and unrelated**: the spec (unchanged since the initial commit) clicks a button named `Sign up`, but the login page's button is labelled **Request access** (`apps/frontend/src/pages/Login.tsx`). No login/signup source file is touched by this work.
- **Migration `026`:** applied by initdb and again via `npm run migrate:sql`. Legacy backfill proved by rebuilding a genuine pre-026 schema (constraint, column and enum dropped), seeding legacy WhatsApp/email/portal channels, then applying the migration file verbatim: `UPDATE 2` moved both legacy WhatsApp rows to `baileys` and left non-WhatsApp rows `NULL`. Re-applying is idempotent (`UPDATE 0`, duplicate type handled). A 10-case constraint suite passed: WhatsApp without a provider rejected, both providers accepted, non-WhatsApp with a provider rejected, clearing a provider by `UPDATE` rejected, unknown enum label rejected, provider switch allowed.
- **Live API smoke (dummy Africa's Talking credentials, no outbound call):** Africa's Talking channel created `connected` with an inbound token and capabilities `groups/qr_pairing/session_reconnect = false`, `templates = true`; a Baileys channel created `connecting` with no inbound token and `groups/qr_pairing/session_reconnect = true`. A text callback created ticket + message; replaying the same `messageId` returned `{ok:true,duplicate:true}` with exactly **one** stored message. `POST /channels/:id/reconnect` on the Africa's Talking channel returned **400 `unsupported_capability`**. The inbound token is visible to an admin and `null` for a `user`-role staff account. Rate limit confirmed: 40 requests admitted, then **429**. Media SSRF rejections confirmed through the real route for `169.254.169.254`, `127.0.0.1`, 6to4 `[2002:a9fe:a9fe::]`, NAT64 `[64:ff9b::a9fe:a9fe]`, mapped `[::ffff:127.0.0.1]` and plain HTTP. **Zero** references to `africastalking.com` in API logs — no request was made to Africa's Talking.
- **Still not exercised:** a real Africa's Talking callback, live Africa's Talking credentials, outbound send to the provider, Baileys QR pairing against real WhatsApp, and the documented rollback procedure.
