diff --git a/apps/web/src/app/services/electron.service.spec.ts b/apps/web/src/app/services/electron.service.spec.ts new file mode 100644 index 000000000..610e2cb57 --- /dev/null +++ b/apps/web/src/app/services/electron.service.spec.ts @@ -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', + } + ); + }); +}); diff --git a/apps/web/src/app/services/electron.service.ts b/apps/web/src/app/services/electron.service.ts index 0a7765fa5..dac080e89 100644 --- a/apps/web/src/app/services/electron.service.ts +++ b/apps/web/src/app/services/electron.service.ts @@ -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) { - if (!payload.url) { + private async fetchM3uPlaylistFromUrl(payload?: Partial) { + if (!payload?.url) { return; } diff --git a/apps/web/src/app/services/pwa.service.spec.ts b/apps/web/src/app/services/pwa.service.spec.ts new file mode 100644 index 000000000..305656995 --- /dev/null +++ b/apps/web/src/app/services/pwa.service.spec.ts @@ -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); + }); +}); diff --git a/apps/web/src/app/services/pwa.service.ts b/apps/web/src/app/services/pwa.service.ts index c7a853b51..f2477e116 100644 --- a/apps/web/src/app/services/pwa.service.ts +++ b/apps/web/src/app/services/pwa.service.ts @@ -144,8 +144,8 @@ export class PwaService extends DataService { return undefined as T; } - refreshPlaylist(payload: Partial) { - if (!payload.url || !payload.id) { + refreshPlaylist(payload?: Partial) { + 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): void { - if (!payload.url) { + fetchFromUrl(payload?: Partial): void { + if (!payload?.url) { return; }