mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
test(pwa): cover self-hosted provider target flow
This commit is contained in:
1 parent
5d31c1d49a
commit
401d434bce
3 files changed
+104
-8
No files matched your search
@@ -48,6 +48,27 @@ async function addXtreamPortal(page: Page): Promise<void> {
|
|||||||
await page.waitForURL(/xtreams.*vod/);
|
await page.waitForURL(/xtreams.*vod/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addM3uPlaylist(page: Page): Promise<void> {
|
||||||
|
await page.getByRole('button', { name: 'Add playlist' }).click();
|
||||||
|
const dialog = page.locator('mat-dialog-container');
|
||||||
|
await expect(dialog).toBeVisible();
|
||||||
|
|
||||||
|
await setInputValue(
|
||||||
|
dialog.getByRole('textbox', { name: /Playlist URL/ }),
|
||||||
|
`${XTREAM_MOCK_SERVER}/playlist.m3u`
|
||||||
|
);
|
||||||
|
await setInputValue(
|
||||||
|
dialog.getByRole('textbox', { name: 'Playlist title' }),
|
||||||
|
'Self-hosted M3U'
|
||||||
|
);
|
||||||
|
|
||||||
|
await dialog
|
||||||
|
.getByRole('button', { name: 'Add playlist', exact: true })
|
||||||
|
.click();
|
||||||
|
await expect(dialog).toBeHidden();
|
||||||
|
await page.waitForURL(/playlists.*all/);
|
||||||
|
}
|
||||||
|
|
||||||
async function addStalkerPortal(page: Page): Promise<void> {
|
async function addStalkerPortal(page: Page): Promise<void> {
|
||||||
await page.getByRole('button', { name: 'Add playlist' }).click();
|
await page.getByRole('button', { name: 'Add playlist' }).click();
|
||||||
const dialog = page.locator('mat-dialog-container');
|
const dialog = page.locator('mat-dialog-container');
|
||||||
@@ -65,6 +86,27 @@ async function addStalkerPortal(page: Page): Promise<void> {
|
|||||||
await page.waitForURL(/stalker.*vod/);
|
await page.waitForURL(/stalker.*vod/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collectBackendRequests(page: Page, path: string): string[] {
|
||||||
|
const requests: string[] = [];
|
||||||
|
page.on('request', (request) => {
|
||||||
|
const requestUrl = request.url();
|
||||||
|
if (requestUrl.startsWith(`${WEB_BACKEND_URL}${path}`)) {
|
||||||
|
requests.push(requestUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return requests;
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectRequestsUseTargetId(requests: string[], path: string): void {
|
||||||
|
expect(requests.length).toBeGreaterThan(0);
|
||||||
|
for (const requestUrl of requests) {
|
||||||
|
const url = new URL(requestUrl);
|
||||||
|
expect(url.pathname).toBe(path);
|
||||||
|
expect(url.searchParams.get('targetId')).toBeTruthy();
|
||||||
|
expect(url.searchParams.has('url')).toBeFalsy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test.beforeEach(async ({ page, request }) => {
|
test.beforeEach(async ({ page, request }) => {
|
||||||
await request.post(`${XTREAM_MOCK_SERVER}/reset`);
|
await request.post(`${XTREAM_MOCK_SERVER}/reset`);
|
||||||
await request.post(`${STALKER_MOCK_SERVER}/reset`);
|
await request.post(`${STALKER_MOCK_SERVER}/reset`);
|
||||||
@@ -90,17 +132,36 @@ test('@self-hosted runtime config points PWA calls at the monorepo backend', asy
|
|||||||
test('@self-hosted Xtream portal loads through web-backend proxy', async ({
|
test('@self-hosted Xtream portal loads through web-backend proxy', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
const xtreamRequests = collectBackendRequests(page, '/xtream');
|
||||||
|
|
||||||
await addXtreamPortal(page);
|
await addXtreamPortal(page);
|
||||||
|
|
||||||
const categoryItems = page.locator('.category-item');
|
const categoryItems = page.locator('.category-item');
|
||||||
await expect(categoryItems.first()).toBeVisible({ timeout: 15_000 });
|
await expect(categoryItems.first()).toBeVisible({ timeout: 15_000 });
|
||||||
|
expectRequestsUseTargetId(xtreamRequests, '/xtream');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('@self-hosted M3U URL loads through web-backend proxy', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const parseRequests = collectBackendRequests(page, '/parse');
|
||||||
|
|
||||||
|
await addM3uPlaylist(page);
|
||||||
|
|
||||||
|
await expect(page.getByText('4 channels')).toBeVisible({ timeout: 15_000 });
|
||||||
|
await expect(page.getByText('1. Channel 1')).toBeVisible();
|
||||||
|
await expect(page.getByText('4. HappyKids TV')).toBeVisible();
|
||||||
|
expectRequestsUseTargetId(parseRequests, '/parse');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('@self-hosted Stalker portal loads through web-backend proxy', async ({
|
test('@self-hosted Stalker portal loads through web-backend proxy', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
const stalkerRequests = collectBackendRequests(page, '/stalker');
|
||||||
|
|
||||||
await addStalkerPortal(page);
|
await addStalkerPortal(page);
|
||||||
|
|
||||||
const categoryItems = page.locator('.category-item');
|
const categoryItems = page.locator('.category-item');
|
||||||
await expect(categoryItems.first()).toBeVisible({ timeout: 15_000 });
|
await expect(categoryItems.first()).toBeVisible({ timeout: 15_000 });
|
||||||
|
expectRequestsUseTargetId(stalkerRequests, '/stalker');
|
||||||
});
|
});
|
||||||
@@ -9,6 +9,16 @@ import { renderMarketingAssetSvg } from './app/generators/marketing.generator.js
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = parseInt(process.env['PORT'] ?? '3211', 10);
|
const PORT = parseInt(process.env['PORT'] ?? '3211', 10);
|
||||||
|
const M3U_FIXTURE = `#EXTM3U
|
||||||
|
#EXTINF:0 tvg-id="1" tvg-logo="http://channel.icons.url/img/1.png" group-title="News", Channel 1
|
||||||
|
https://example.channels/path-to-file/1.m3u8
|
||||||
|
#EXTINF:0 tvg-id="2" tvg-logo="http://channel.icons.url/img/2.png" group-title="News", Positive News TV
|
||||||
|
https://example.channels/path-to-file/2.m3u8
|
||||||
|
#EXTINF:0 tvg-id="3" tvg-logo="http://channel.icons.url/img/3.png" group-title="Sport", Sport TVX
|
||||||
|
https://example.channels/path-to-file/3.m3u8
|
||||||
|
#EXTINF:0 tvg-id="4" tvg-logo="http://channel.icons.url/img/4.png" group-title="Kids", HappyKids TV
|
||||||
|
https://example.channels/path-to-file/4.m3u8
|
||||||
|
`;
|
||||||
const marketingRasterAssetRoot = join(
|
const marketingRasterAssetRoot = join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'apps/xtream-mock-server/public/marketing'
|
'apps/xtream-mock-server/public/marketing'
|
||||||
@@ -28,6 +38,13 @@ app.post('/reset', (_req, res) => {
|
|||||||
res.json({ status: 'reset' });
|
res.json({ status: 'reset' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── M3U fixture endpoint for self-hosted PWA URL import tests ───────────────
|
||||||
|
app.get('/playlist.m3u', (_req, res) => {
|
||||||
|
res.type('audio/x-mpegurl')
|
||||||
|
.set('Cache-Control', 'no-store')
|
||||||
|
.send(M3U_FIXTURE);
|
||||||
|
});
|
||||||
|
|
||||||
// ─── Local fictional artwork for release screenshots ──────────────────────────
|
// ─── Local fictional artwork for release screenshots ──────────────────────────
|
||||||
app.get('/assets/marketing/:kind/:slug', (req, res) => {
|
app.get('/assets/marketing/:kind/:slug', (req, res) => {
|
||||||
const kind = req.params['kind'] as
|
const kind = req.params['kind'] as
|
||||||
@@ -46,18 +63,20 @@ app.get('/assets/marketing/:kind/:slug', (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rasterSlug = slug.replace(/\.(svg|png)$/i, '');
|
const rasterSlug = slug.replace(/\.(svg|png)$/i, '');
|
||||||
const rasterPath = join(marketingRasterAssetRoot, kind, `${rasterSlug}.png`);
|
const rasterPath = join(
|
||||||
|
marketingRasterAssetRoot,
|
||||||
|
kind,
|
||||||
|
`${rasterSlug}.png`
|
||||||
|
);
|
||||||
|
|
||||||
if (existsSync(rasterPath)) {
|
if (existsSync(rasterPath)) {
|
||||||
res
|
res.type('image/png')
|
||||||
.type('image/png')
|
|
||||||
.set('Cache-Control', 'public, max-age=3600')
|
.set('Cache-Control', 'public, max-age=3600')
|
||||||
.send(readFileSync(rasterPath));
|
.send(readFileSync(rasterPath));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res
|
res.type('image/svg+xml')
|
||||||
.type('image/svg+xml')
|
|
||||||
.set('Cache-Control', 'public, max-age=3600')
|
.set('Cache-Control', 'public, max-age=3600')
|
||||||
.send(renderMarketingAssetSvg(kind, slug, size));
|
.send(renderMarketingAssetSvg(kind, slug, size));
|
||||||
});
|
});
|
||||||
@@ -149,8 +168,12 @@ server.on('error', (err: NodeJS.ErrnoException) => {
|
|||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log(`[xtream-mock] Listening on http://localhost:${PORT}`);
|
console.log(`[xtream-mock] Listening on http://localhost:${PORT}`);
|
||||||
console.log(`[xtream-mock] Direct API: http://localhost:${PORT}/player_api.php?username=user1&password=pass1&action=get_account_info`);
|
console.log(
|
||||||
console.log(`[xtream-mock] PWA proxy: http://localhost:${PORT}/xtream?url=http://localhost:${PORT}&username=user1&password=pass1&action=get_account_info`);
|
`[xtream-mock] Direct API: http://localhost:${PORT}/player_api.php?username=user1&password=pass1&action=get_account_info`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`[xtream-mock] PWA proxy: http://localhost:${PORT}/xtream?url=http://localhost:${PORT}&username=user1&password=pass1&action=get_account_info`
|
||||||
|
);
|
||||||
console.log(`[xtream-mock] Health: http://localhost:${PORT}/health`);
|
console.log(`[xtream-mock] Health: http://localhost:${PORT}/health`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -173,4 +196,6 @@ process.on('unhandledRejection', (reason) => {
|
|||||||
// When Nx (or any process manager) closes stdin, prevent auto-exit.
|
// When Nx (or any process manager) closes stdin, prevent auto-exit.
|
||||||
// The HTTP server handle is what keeps the process alive.
|
// The HTTP server handle is what keeps the process alive.
|
||||||
process.stdin.resume();
|
process.stdin.resume();
|
||||||
process.stdin.on('end', () => { /* ignore stdin close */ });
|
process.stdin.on('end', () => {
|
||||||
|
/* ignore stdin close */
|
||||||
|
});
|
||||||
@@ -95,6 +95,16 @@ Response: `{ payload: <data>, action: <action> }`
|
|||||||
This mirrors the backend proxy in `apps/electron-backend` so the same
|
This mirrors the backend proxy in `apps/electron-backend` so the same
|
||||||
Angular service code works in both environments.
|
Angular service code works in both environments.
|
||||||
|
|
||||||
|
### M3U fixture endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /playlist.m3u
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns a small deterministic four-channel playlist. The self-hosted PWA E2E
|
||||||
|
suite uses this endpoint to verify M3U URL imports through `apps/web-backend`
|
||||||
|
and the provider target registry.
|
||||||
|
|
||||||
### Stream stub endpoints
|
### Stream stub endpoints
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in new issue
Block a user