mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(playback): recommend recovery actions (#1374)
* docs(playback): design recovery recommendations * docs(playback): plan recovery recommendations * refactor(playback): extract diagnostic utilities * feat(playback): define recovery recommendation contracts * feat(playback): rank recovery recommendations * feat(playback): track session recovery attempts * feat(playback): identify content recovery sessions * feat(ui): add ranked playback diagnostic panel * feat(playback): switch temporarily to recommended players * test(playback): cover temporary player recommendation * test(playback): verify recommendation capability guards * docs(playback): document recovery recommendations * fix(playback): keep recovery keys credential-free * fix(playback): remove derived tracking ownership * fix(playback): preserve distinct recovery fallbacks * fix(playback): reset resume for new sources * fix(playback): preserve desktop recovery guidance * docs(playback): clarify recovery policy exceptions * fix(playback): reject stale progress updates * fix(playback): keep protected recovery guidance neutral * test(playback): cover stale progress output * fix(playback): neutralize protected diagnostic copy * fix(playback): harden runtime guidance ownership * fix(playback): stabilize recovery application ownership * fix(ci): classify playback util coverage * fix(e2e): preserve playback fixture bytes
This commit is contained in:
1 parent
5e4f2ca3dd
commit
fd96b85c19
176 files changed
+15228
-1908
No files matched your search
+62
-3
@@ -4,13 +4,17 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { DatabaseService, PlaylistsService } from '@iptvnator/services';
|
||||
import { UnifiedFavoriteChannel } from '@iptvnator/portal/shared/util';
|
||||
import { GlobalFavoritesService } from './global-favorites.service';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('GlobalFavoritesService', () => {
|
||||
let service: GlobalFavoritesService;
|
||||
let electronApi: {
|
||||
dbGetAppState: jest.Mock;
|
||||
dbReorderGlobalFavorites: jest.Mock;
|
||||
dbSetAppState: jest.Mock;
|
||||
};
|
||||
const storeSelect = jest.fn();
|
||||
const getPlaylistById = jest.fn();
|
||||
|
||||
const makeChannel = (
|
||||
overrides: Partial<UnifiedFavoriteChannel> &
|
||||
@@ -25,7 +29,10 @@ describe('GlobalFavoritesService', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
storeSelect.mockReset().mockReturnValue(of([]));
|
||||
getPlaylistById.mockReset();
|
||||
electronApi = {
|
||||
dbGetAppState: jest.fn().mockResolvedValue(null),
|
||||
dbReorderGlobalFavorites: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ success: true }),
|
||||
@@ -39,9 +46,12 @@ describe('GlobalFavoritesService', () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
GlobalFavoritesService,
|
||||
{ provide: Store, useValue: { select: jest.fn() } },
|
||||
{ provide: Store, useValue: { select: storeSelect } },
|
||||
{ provide: DatabaseService, useValue: {} },
|
||||
{ provide: PlaylistsService, useValue: {} },
|
||||
{
|
||||
provide: PlaylistsService,
|
||||
useValue: { getPlaylistById },
|
||||
},
|
||||
{
|
||||
provide: TranslateService,
|
||||
useValue: { instant: (key: string) => key },
|
||||
@@ -58,6 +68,52 @@ describe('GlobalFavoritesService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps legacy URL UIDs aligned with the persisted M3U order', async () => {
|
||||
const firstUrl = 'https://streams.example/one.ts';
|
||||
const secondUrl = 'https://streams.example/two.ts';
|
||||
storeSelect.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
_id: 'playlist-one',
|
||||
title: 'Playlist',
|
||||
favorites: [firstUrl, secondUrl],
|
||||
},
|
||||
])
|
||||
);
|
||||
getPlaylistById.mockReturnValue(
|
||||
of({
|
||||
playlist: {
|
||||
items: [
|
||||
{
|
||||
id: 'channel-one',
|
||||
name: 'Channel One',
|
||||
url: firstUrl,
|
||||
},
|
||||
{
|
||||
id: 'channel-two',
|
||||
name: 'Channel Two',
|
||||
url: secondUrl,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
electronApi.dbGetAppState.mockResolvedValue(
|
||||
JSON.stringify([
|
||||
`m3u::playlist-one::${secondUrl}`,
|
||||
`m3u::playlist-one::${firstUrl}`,
|
||||
])
|
||||
);
|
||||
|
||||
const channels = await service.getUnifiedLiveFavorites();
|
||||
|
||||
expect(channels.map((channel) => channel.uid)).toEqual([
|
||||
`m3u::playlist-one::${secondUrl}`,
|
||||
`m3u::playlist-one::${firstUrl}`,
|
||||
]);
|
||||
expect(channels.map((channel) => channel.position)).toEqual([0, 1]);
|
||||
});
|
||||
|
||||
describe('reorder', () => {
|
||||
it('sends playlist-scoped position updates for Xtream favorites', async () => {
|
||||
// The backend UPDATE filters by (contentId, playlistId); a payload
|
||||
@@ -109,7 +165,10 @@ describe('GlobalFavoritesService', () => {
|
||||
expect(electronApi.dbReorderGlobalFavorites).not.toHaveBeenCalled();
|
||||
expect(electronApi.dbSetAppState).toHaveBeenCalledWith(
|
||||
'global-favorites-channel-order-v1',
|
||||
JSON.stringify(['m3u::playlist-m::url', 'stalker::playlist-s::5'])
|
||||
JSON.stringify([
|
||||
'm3u::playlist-m::url',
|
||||
'stalker::playlist-s::5',
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user