test(stalker): enforce portal auth in the mock and cover the full-portal flow

The mock server implemented neither get_profile nor get_events and
validated no auth at all, and the e2e suite imported the portal through
/portal.php — which the app classifies as a *simple* portal. The entire
authenticated branch (handshake, token, watchdog, re-auth) therefore had
zero coverage, right before a series of PRs that reworks exactly that.

Mock server:
- serve the canonical /stalker_portal/server/load.php endpoint, which
  enforces the Bearer token and the Infomir MAC format like the real
  middleware; /portal.php stays tolerant so the existing suite keeps
  covering the simple-portal branch
- auth-store.ts models the parts of Stalker 4.9.35 a client can get
  wrong: plain-text auth failures with HTTP 200, a handshake that is not
  yet a session, idempotent token re-presentation, and permanent
  device_id pinning (including the blank-after-pinned lockout)
- add get_profile (status 0/1/2, device conflict, block_msg) and the
  get_events watchdog; profile advertises watchdog_timeout/timeslot
- new login-required scenario MAC and POST /invalidate-session so tests
  can force a mid-session token loss
- the /stalker proxy route now forwards the token as a Bearer header and
  wraps auth failures in the { payload } envelope, matching web-backend

Also moves extractMac into request-mac.ts: importing it from the
categories handler dragged the whole data generator into any consumer,
which broke unit tests on the workspace alias.

E2E: new stalker-auth.e2e.ts asserts handshake precedes get_profile
precedes content, that content requests carry the token while the
handshake does not, that the plain-text failure body is never rendered,
and that the client re-authenticates after the portal drops the session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-08-01 12:30:26 +02:00
1 parent 9f4e11d6de
commit eeda703849
22 files changed
+905 -44

No files matched your search

+46 -3
View File
@@ -37,7 +37,50 @@ Stalker portals use MAC address as the primary credential. The mock server follo
### In-Memory Only
No files or databases are written. All state (generated content + favorites) lives in process memory and resets on server restart. This is intentional — tests should not share state across runs.
No files or databases are written. All state (generated content + favorites + portal sessions) lives in process memory and resets on server restart. This is intentional — tests should not share state across runs.
### Two Endpoints With Different Strictness
The app decides how to talk to a portal from the shape of its URL: a URL
containing `/stalker_portal` is imported as a **full portal** (handshake,
`Authorization: Bearer`, watchdog), anything else as a **simple portal** with no
authentication at all. The mock therefore serves the same action set at two
paths:
| Path | Router | Behaviour |
|---|---|---|
| `/portal.php` | `createPortalRouter(false)` | Tolerant: ignores the token and the MAC format, like most reseller panels |
| `/stalker_portal/server/load.php` | `createPortalRouter(true)` | Strict: enforces both, like the real middleware |
Keeping the tolerant path is what lets the pre-existing e2e suite (which imports
`portal.php`) stay meaningful — it covers the simple-portal branch — while the
strict path finally covers the authenticated branch that had no coverage at all.
The strict behaviours mirror the plaintext Stalker 4.9.35 middleware
(`server/lib/stb.class.php`), the last openly readable ancestor of the encoded
5.x core:
- **Plain-text auth failures.** `Authorization failed.` / `Unauthorized request.`
are returned with **HTTP 200** and a `text/html` body, because the real server
`exit`s before the JSON envelope is built. A client checking only status codes
sees "success" and renders nothing. The `/stalker` proxy route still wraps the
body in the `{ payload }` envelope, matching what `apps/web-backend` does.
- **A handshake is not a session.** The token only authorizes requests once
`get_profile` has adopted it for that MAC.
- **Idempotent handshake.** Presenting the MAC's current token returns that same
token, which is what allows real clients to persist tokens across restarts.
- **Device-id pinning.** `device_id`/`device_id2` are stored on first non-empty
value; any later change — including reverting to empty — is a permanent
`device conflict` carrying the "Your STB is damaged." block message. This is
the only identity check the stock server actually enforces.
- **`signature`, `metrics`, `prehash` are ignored**, exactly as upstream ignores
them; they exist for portals with a custom `access_filter.php`.
- **MAC format validation.** Non-Infomir MACs (`00:1A:79:XX:XX:XX`) get a bare
`{ status: 1 }` from `get_profile`.
Session state lives in `src/app/auth-store.ts` and is cleared by `/reset`.
`POST /invalidate-session?macAddress=<mac>` drops a single session so tests can
assert the client re-handshakes and retries instead of surfacing an error.
## Data Generation Pipeline
@@ -308,6 +351,6 @@ test('browse VOD categories', async ({ page }) => {
- **New content types**: Add a new generator function in `data-generator.ts` and a new handler in `handlers/`.
- **New scenarios**: Add to `SCENARIOS` in `scenarios.ts`.
- **Stateful session tokens**: `handshake.handler.ts` generates a token from the MAC — extend this to track token expiry for testing re-auth flows.
- **Error simulation**: Add a special MAC or query param to trigger error responses (e.g. 401, 500) for testing error handling in the Stalker store.
- **Session behaviour**: `auth-store.ts` owns tokens and device pinning. Add TTLs or a "token replaced by another device" mode there rather than in the handlers.
- **Error simulation**: Add a special MAC or query param to trigger error responses for testing error handling in the Stalker store. Note that portal-level auth errors are *not* HTTP errors — see [Two Endpoints With Different Strictness](#two-endpoints-with-different-strictness).
- **Slow responses**: Add a `MOCK_DELAY_MS` env var and apply it in middleware for testing loading states.