mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor(web): harden runtime service payloads
This commit is contained in:
1 parent
76346a569b
commit
1806cb46aa
4 files changed
+205
-8
No files matched your search
@@ -0,0 +1,117 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PLAYLIST_PARSE_BY_URL } from '@iptvnator/shared/interfaces';
|
||||
import { ElectronService } from './electron.service';
|
||||
|
||||
describe('ElectronService', () => {
|
||||
const session = { id: 'session-1' };
|
||||
let electronBridge: {
|
||||
fetchPlaylistByUrl: jest.Mock;
|
||||
openInMpv: jest.Mock;
|
||||
openInVlc: jest.Mock;
|
||||
};
|
||||
let service: ElectronService;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
electronBridge = {
|
||||
fetchPlaylistByUrl: jest.fn(),
|
||||
openInMpv: jest.fn().mockResolvedValue(session),
|
||||
openInVlc: jest.fn().mockResolvedValue(session),
|
||||
};
|
||||
|
||||
Object.defineProperty(window, 'electron', {
|
||||
configurable: true,
|
||||
value: electronBridge,
|
||||
});
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
ElectronService,
|
||||
{
|
||||
provide: MatSnackBar,
|
||||
useValue: {
|
||||
open: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: Store,
|
||||
useValue: {
|
||||
dispatch: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: TranslateService,
|
||||
useValue: {
|
||||
instant: jest.fn((key: string) => key),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
service = TestBed.inject(ElectronService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(window, 'electron', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('ignores URL imports without a payload instead of calling the Electron bridge', async () => {
|
||||
await service.sendIpcEvent(PLAYLIST_PARSE_BY_URL);
|
||||
|
||||
expect(electronBridge.fetchPlaylistByUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('preserves an absent MPV user-agent so backend fallback headers can apply', async () => {
|
||||
await service.sendIpcEvent('OPEN_MPV_PLAYER', {
|
||||
url: 'https://example.test/live.m3u8',
|
||||
headers: {
|
||||
'User-Agent': 'FallbackAgent/1.0',
|
||||
},
|
||||
});
|
||||
|
||||
expect(electronBridge.openInMpv).toHaveBeenCalledWith(
|
||||
'https://example.test/live.m3u8',
|
||||
'',
|
||||
'',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
'User-Agent': 'FallbackAgent/1.0',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves an absent VLC user-agent so backend fallback headers can apply', async () => {
|
||||
await service.sendIpcEvent('OPEN_VLC_PLAYER', {
|
||||
url: 'https://example.test/live.m3u8',
|
||||
headers: {
|
||||
'User-Agent': 'FallbackAgent/1.0',
|
||||
},
|
||||
});
|
||||
|
||||
expect(electronBridge.openInVlc).toHaveBeenCalledWith(
|
||||
'https://example.test/live.m3u8',
|
||||
'',
|
||||
'',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
'User-Agent': 'FallbackAgent/1.0',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -156,7 +156,7 @@ export class ElectronService extends DataService {
|
||||
data.url,
|
||||
data.title ?? '',
|
||||
data.thumbnail ?? '',
|
||||
data['user-agent'] ?? undefined,
|
||||
data['user-agent'],
|
||||
data.referer ?? undefined,
|
||||
data.origin ?? undefined,
|
||||
data.contentInfo,
|
||||
@@ -185,7 +185,7 @@ export class ElectronService extends DataService {
|
||||
data.url,
|
||||
data.title ?? '',
|
||||
data.thumbnail ?? '',
|
||||
data['user-agent'] ?? undefined,
|
||||
data['user-agent'],
|
||||
data.referer ?? undefined,
|
||||
data.origin ?? undefined,
|
||||
data.contentInfo,
|
||||
@@ -265,8 +265,8 @@ export class ElectronService extends DataService {
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchM3uPlaylistFromUrl(payload: Partial<Playlist>) {
|
||||
if (!payload.url) {
|
||||
private async fetchM3uPlaylistFromUrl(payload?: Partial<Playlist>) {
|
||||
if (!payload?.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import {
|
||||
PLAYLIST_PARSE_BY_URL,
|
||||
PLAYLIST_UPDATE,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { PwaService } from './pwa.service';
|
||||
|
||||
describe('PwaService', () => {
|
||||
let http: HttpTestingController;
|
||||
let service: PwaService;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [
|
||||
PwaService,
|
||||
{
|
||||
provide: MatSnackBar,
|
||||
useValue: {
|
||||
open: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: Store,
|
||||
useValue: {
|
||||
dispatch: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: SwUpdate,
|
||||
useValue: {
|
||||
versionUpdates: EMPTY,
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: TranslateService,
|
||||
useValue: {
|
||||
instant: jest.fn((key: string) => key),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
http = TestBed.inject(HttpTestingController);
|
||||
service = TestBed.inject(PwaService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
http.verify();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('ignores URL imports without a payload or URL instead of calling the backend', () => {
|
||||
service.sendIpcEvent(PLAYLIST_PARSE_BY_URL);
|
||||
service.sendIpcEvent(PLAYLIST_PARSE_BY_URL, {});
|
||||
|
||||
expect(http.match(() => true)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('ignores playlist refreshes without a payload, URL, or id instead of calling the backend', () => {
|
||||
service.sendIpcEvent(PLAYLIST_UPDATE);
|
||||
service.sendIpcEvent(PLAYLIST_UPDATE, { id: 'playlist-1' });
|
||||
service.sendIpcEvent(PLAYLIST_UPDATE, {
|
||||
url: 'https://example.test/playlist.m3u',
|
||||
});
|
||||
|
||||
expect(http.match(() => true)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -144,8 +144,8 @@ export class PwaService extends DataService {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
refreshPlaylist(payload: Partial<Playlist & { id: string }>) {
|
||||
if (!payload.url || !payload.id) {
|
||||
refreshPlaylist(payload?: Partial<Playlist & { id: string }>) {
|
||||
if (!payload?.url || !payload?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -205,8 +205,8 @@ export class PwaService extends DataService {
|
||||
* Fetches playlist from the specified url
|
||||
* @param payload playlist payload
|
||||
*/
|
||||
fetchFromUrl(payload: Partial<Playlist>): void {
|
||||
if (!payload.url) {
|
||||
fetchFromUrl(payload?: Partial<Playlist>): void {
|
||||
if (!payload?.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user