mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 20:16:15 -08:00
test(stalker): bound auth e2e worker slots
This commit is contained in:
1 parent
f962438a04
commit
1f3d684ee6
4 files changed
+51
-30
No files matched your search
@@ -86,7 +86,7 @@ actually get wrong:
|
||||
| `00:1A:79:00:00:06` | **legacy-pagination** | No `get_all_channels` support — tests the paginated `get_ordered_list` crawl fallback for the full ITV channel list |
|
||||
| `00:1A:79:00:00:07` | **marketing-demo** | 35 original poster movies with the newest 20 first — safe for screenshots and marketing |
|
||||
| `00:1A:79:00:00:08` | **login-required** | `get_profile` answers `status: 2` until the client completes `do_auth` with non-empty credentials. The app drives this end to end: enter a username and password in the Stalker import dialog and it runs `do_auth`, then retries `get_profile` with `auth_second_step=1`. Covered by `stalker.e2e.ts` (`@stalker full portal authentication`) |
|
||||
| `00:1A:79:AE:<worker>:02` | **login-required** | Worker-scoped alias range used by parallel auth E2E runs; it has the same login contract as `00:1A:79:00:00:08` but independent per-MAC state |
|
||||
| `00:1A:79:AE:<slot>:02` | **login-required** | Parallel-slot-scoped alias range used by concurrent auth E2E runs; it has the same login contract as `00:1A:79:00:00:08` but independent per-MAC state |
|
||||
| `00:1A:79:00:00:09` | **gated-stream** | `create_link` returns a local `/stream/gated/…` URL that answers 403 without the mac cookie and the MAC's current Bearer token — proves a player's media requests really carry the portal credentials |
|
||||
| `00:1A:79:00:00:0A` | **static-channel-cmd** | ITV rows carry a directly playable `cmd` with `use_http_tmp_link`/`use_load_balancing` both `'0'` — a client honouring the flags must play them without calling `create_link` |
|
||||
| `<any other MAC>` | **auto** | MAC bytes used as seed → deterministic unique dataset |
|
||||
@@ -180,9 +180,10 @@ everything. The sibling specs that talk to this server (`self-hosted.e2e.ts`,
|
||||
the `sources-pwa` helpers) own a disjoint `00:1A:79:5F:*` range for the same
|
||||
reason, and `stalker.e2e.ts` runs `mode: 'serial'` because its tests
|
||||
deliberately share scenario MACs. Its stateful authentication tests derive a
|
||||
separate `00:1A:79:AE:<worker>:*` range from Playwright's worker index and add
|
||||
only that range to the scoped reset, so browser projects and repeat workers do
|
||||
not clear one another's sessions or pinned device identity. Full contract:
|
||||
separate `00:1A:79:AE:<slot>:*` range from Playwright's bounded
|
||||
`parallelIndex` and add only that range to the scoped reset, so concurrent
|
||||
browser projects do not clear one another's sessions or pinned device
|
||||
identity; restarted workers retain their slot. Full contract:
|
||||
[`docs/architecture/stalker-mock-server.md`](../../docs/architecture/stalker-mock-server.md#test-isolation).
|
||||
|
||||
## EPG Behavior
|
||||
|
||||
@@ -126,7 +126,7 @@ describe('stalker mock authentication handlers', () => {
|
||||
).toBe(null);
|
||||
});
|
||||
|
||||
it('applies the login-required scenario to worker-scoped MACs', () => {
|
||||
it('applies the login-required scenario to parallel-slot MACs', () => {
|
||||
const token = invoke(handleHandshake, { action: 'handshake' }, {
|
||||
mac: WORKER_LOGIN_REQUIRED_MAC,
|
||||
})['token'] as string;
|
||||
|
||||
@@ -40,9 +40,10 @@ import {
|
||||
* test; serializing one file is the cheaper trade.
|
||||
*
|
||||
* - ACROSS BROWSER PROJECTS/WORKERS: state-sensitive auth tests use one MAC
|
||||
* range per Playwright worker. Their `beforeEach` adds only that worker's
|
||||
* MACs to the scoped reset, so concurrent projects or repeat workers cannot
|
||||
* clear one another's token, invalidated session or pinned device id.
|
||||
* range per Playwright parallel slot. Their `beforeEach` adds only that
|
||||
* slot's MACs to the scoped reset, so concurrent projects cannot clear one
|
||||
* another's token, invalidated session or pinned device id. A restarted
|
||||
* worker retains its bounded `parallelIndex`.
|
||||
*/
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
@@ -80,8 +81,8 @@ const STATIC_CMD_MAC = '00:1A:79:00:00:0A';
|
||||
/**
|
||||
* These tests assert state transitions within one portal session, so a reset
|
||||
* from a concurrent browser project or repeat worker would invalidate the
|
||||
* assertion itself. Giving every worker its own MAC range preserves browser
|
||||
* parallelism and also keeps `--repeat-each` runs isolated.
|
||||
* assertion itself. Giving every concurrent worker slot its own MAC range
|
||||
* preserves browser parallelism and also keeps `--repeat-each` runs isolated.
|
||||
*/
|
||||
interface StatefulAuthMacs {
|
||||
authenticatedFlow: string;
|
||||
@@ -91,16 +92,25 @@ interface StatefulAuthMacs {
|
||||
reauthentication: string;
|
||||
}
|
||||
|
||||
function getStatefulAuthMacs(workerIndex: number): StatefulAuthMacs {
|
||||
function getStatefulAuthMacs({
|
||||
parallelIndex,
|
||||
}: {
|
||||
parallelIndex: number;
|
||||
}): StatefulAuthMacs {
|
||||
if (
|
||||
!Number.isSafeInteger(workerIndex) ||
|
||||
workerIndex < 0 ||
|
||||
workerIndex > 255
|
||||
!Number.isSafeInteger(parallelIndex) ||
|
||||
parallelIndex < 0 ||
|
||||
parallelIndex > 255
|
||||
) {
|
||||
throw new Error(`Unsupported Playwright worker index: ${workerIndex}`);
|
||||
throw new Error(
|
||||
`Unsupported Playwright parallel index: ${parallelIndex}`
|
||||
);
|
||||
}
|
||||
|
||||
const workerOctet = workerIndex.toString(16).padStart(2, '0').toUpperCase();
|
||||
const workerOctet = parallelIndex
|
||||
.toString(16)
|
||||
.padStart(2, '0')
|
||||
.toUpperCase();
|
||||
const workerPrefix = `00:1A:79:AE:${workerOctet}`;
|
||||
|
||||
return {
|
||||
@@ -311,7 +321,7 @@ function recordPortalRequests(
|
||||
|
||||
test.beforeEach(async ({ page, request }, testInfo) => {
|
||||
// Reset mock server state (clears in-memory favorites and cache)
|
||||
await resetMockServer(request, getStatefulAuthMacs(testInfo.workerIndex));
|
||||
await resetMockServer(request, getStatefulAuthMacs(testInfo));
|
||||
|
||||
// Playwright creates a fresh browser context per test, so extra
|
||||
// IndexedDB cleanup here only risks racing with app-managed DB handles.
|
||||
@@ -332,6 +342,14 @@ test('@stalker health check — mock server is running', async ({ request }) =>
|
||||
expect(body.status).toBe('ok');
|
||||
});
|
||||
|
||||
test('@stalker auth MAC allocation survives worker process restarts', () => {
|
||||
const restartedWorker = { parallelIndex: 7, workerIndex: 256 };
|
||||
|
||||
expect(getStatefulAuthMacs(restartedWorker).loginRequired).toBe(
|
||||
'00:1A:79:AE:07:02'
|
||||
);
|
||||
});
|
||||
|
||||
test('@stalker add a Stalker portal and see it in the playlist list', async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -980,7 +998,7 @@ test.describe('@stalker full portal authentication', () => {
|
||||
}, testInfo) => {
|
||||
const requests = recordPortalRequests(page);
|
||||
const actionsInOrder = () => requests.map((entry) => entry.action);
|
||||
const mac = getStatefulAuthMacs(testInfo.workerIndex).authenticatedFlow;
|
||||
const mac = getStatefulAuthMacs(testInfo).authenticatedFlow;
|
||||
|
||||
await addFullStalkerPortal(page, { mac });
|
||||
|
||||
@@ -1038,7 +1056,7 @@ test.describe('@stalker full portal authentication', () => {
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const requests = recordPortalRequests(page);
|
||||
const mac = getStatefulAuthMacs(testInfo.workerIndex).loginRequired;
|
||||
const mac = getStatefulAuthMacs(testInfo).loginRequired;
|
||||
// The second auth step must only be claimed on the retry AFTER
|
||||
// do_auth — a client that always sends auth_second_step=1 works
|
||||
// against the mock but violates the documented protocol.
|
||||
@@ -1093,7 +1111,7 @@ test.describe('@stalker full portal authentication', () => {
|
||||
test('explains a login-required portal and recovers once credentials are entered', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const mac = getStatefulAuthMacs(testInfo.workerIndex).loginRequired;
|
||||
const mac = getStatefulAuthMacs(testInfo).loginRequired;
|
||||
// First attempt without credentials: the import must fail with the
|
||||
// portal's actual reason, not a generic "check URL and MAC" error.
|
||||
await page.getByRole('button', { name: 'Add playlist' }).click();
|
||||
@@ -1135,7 +1153,7 @@ test.describe('@stalker full portal authentication', () => {
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const requests = recordPortalRequests(page);
|
||||
const mac = getStatefulAuthMacs(testInfo.workerIndex).tokenReuse;
|
||||
const mac = getStatefulAuthMacs(testInfo).tokenReuse;
|
||||
|
||||
await addFullStalkerPortal(page, { mac });
|
||||
|
||||
@@ -1258,7 +1276,7 @@ test.describe('@stalker full portal authentication', () => {
|
||||
page,
|
||||
request,
|
||||
}, testInfo) => {
|
||||
const mac = getStatefulAuthMacs(testInfo.workerIndex).deviceConflict;
|
||||
const mac = getStatefulAuthMacs(testInfo).deviceConflict;
|
||||
// Pin a device id the way another client (StbEmu, a set-top box)
|
||||
// would have: the stock server binds the first non-empty `device_id`
|
||||
// it sees to the MAC and refuses every different one afterwards.
|
||||
@@ -1315,7 +1333,7 @@ test.describe('@stalker full portal authentication', () => {
|
||||
request,
|
||||
}, testInfo) => {
|
||||
const requests = recordPortalRequests(page);
|
||||
const mac = getStatefulAuthMacs(testInfo.workerIndex).reauthentication;
|
||||
const mac = getStatefulAuthMacs(testInfo).reauthentication;
|
||||
|
||||
await addFullStalkerPortal(page, { mac });
|
||||
|
||||
|
||||
@@ -319,9 +319,9 @@ supporting scenarios, `get_all_channels` (`get-all-channels.handler.ts`,
|
||||
(adult) genres.
|
||||
|
||||
The login-required scenario also recognizes
|
||||
`00:1A:79:AE:<worker>:02`. This test-only alias range preserves the same
|
||||
`status: 2`/`do_auth` contract while giving concurrent Playwright workers
|
||||
independent per-MAC auth state.
|
||||
`00:1A:79:AE:<slot>:02`. This test-only alias range preserves the same
|
||||
`status: 2`/`do_auth` contract while giving concurrent Playwright parallel
|
||||
slots independent per-MAC auth state.
|
||||
|
||||
The `marketing-demo` scenario (`00:1A:79:00:00:07`) replaces faker-generated
|
||||
VOD with the 35-movie provider-neutral showcase catalog from
|
||||
@@ -401,10 +401,12 @@ file running in parallel workers, so isolation is per-MAC rather than global:
|
||||
(their fixture shapes are what the assertions are written against), so the
|
||||
file uses `test.describe.configure({ mode: 'serial' })`.
|
||||
- Authentication tests whose assertions span multiple requests derive a
|
||||
disjoint `00:1A:79:AE:<worker>:*` range from Playwright's `workerIndex`.
|
||||
Their `beforeEach` adds only the current worker's range to the batched reset,
|
||||
so browser projects and `--repeat-each` workers cannot clear one another's
|
||||
token, login completion, invalidated session or pinned device identity.
|
||||
disjoint `00:1A:79:AE:<slot>:*` range from Playwright's bounded
|
||||
`parallelIndex`. Their `beforeEach` adds only the current parallel slot's
|
||||
range to the batched reset, so browser projects cannot clear one another's
|
||||
token, login completion, invalidated session or pinned device identity; a
|
||||
restarted worker retains the same slot instead of consuming a wider MAC
|
||||
value.
|
||||
|
||||
A reset drops the generated content cache, favorites (`data-store.ts`), the
|
||||
auth/session record (`auth-store.ts`, including any pinned device identity) and
|
||||
|
||||
Reference in new issue
Block a user