mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
fix(backup): export and restore hidden Xtream categories by real xtream IDs (#1224)
Category rows crossed the DB-worker IPC boundary with Drizzle's camelCase property names while the renderer contracts declare snake_case, so backup export dropped hidden-category IDs and restore degraded to a type-only match that hid every category. Project category ops to the declared wire shape, normalize restore state from untrusted sources (dropping entries without a numeric xtreamId), reject entries with missing user-state collections, and add full export→import round-trip coverage (unit manifest-equality + Electron e2e) plus regression tests. Closes #1017 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bd07e17857
commit
b4c0cce741
16 files changed
+1424
-151
No files matched your search
@@ -1,6 +1,23 @@
|
||||
import type { AppDatabase } from '../database.types';
|
||||
import * as schema from '@iptvnator/shared/database/schema';
|
||||
import { getCategories, saveCategories } from './category.operations';
|
||||
import {
|
||||
getAllCategories,
|
||||
getCategories,
|
||||
saveCategories,
|
||||
} from './category.operations';
|
||||
|
||||
// Renderer consumers (XCategoryFromDb/XtreamCategoryFromDb) expect category
|
||||
// rows in this snake_case wire shape. A bare select() would return Drizzle's
|
||||
// camelCase property names and silently break the playlist backup
|
||||
// export/restore (issue #1017).
|
||||
const categoryWireShape = {
|
||||
id: schema.categories.id,
|
||||
playlist_id: schema.categories.playlistId,
|
||||
name: schema.categories.name,
|
||||
type: schema.categories.type,
|
||||
xtream_id: schema.categories.xtreamId,
|
||||
hidden: schema.categories.hidden,
|
||||
};
|
||||
|
||||
function createDbMock(existingCount = 0) {
|
||||
const where = jest.fn().mockResolvedValue([{ count: existingCount }]);
|
||||
@@ -37,6 +54,21 @@ describe('category.operations', () => {
|
||||
await getCategories(db, 'playlist-1', 'live');
|
||||
|
||||
expect(orderBy).toHaveBeenCalledWith(schema.categories.id);
|
||||
expect(select).toHaveBeenCalledWith(categoryWireShape);
|
||||
});
|
||||
|
||||
it('projects all categories to the snake_case wire shape', async () => {
|
||||
const orderBy = jest.fn().mockResolvedValue([]);
|
||||
const where = jest.fn().mockReturnValue({ orderBy });
|
||||
const from = jest.fn().mockReturnValue({ where });
|
||||
const select = jest.fn().mockReturnValue({ from });
|
||||
const db = {
|
||||
select,
|
||||
} as unknown as AppDatabase;
|
||||
|
||||
await getAllCategories(db, 'playlist-1', 'movies');
|
||||
|
||||
expect(select).toHaveBeenCalledWith(categoryWireShape);
|
||||
});
|
||||
|
||||
it('restores hidden categories when Xtream API category IDs are strings', async () => {
|
||||
|
||||
@@ -7,6 +7,20 @@ type XtreamCategoryInput = {
|
||||
category_id: string | number;
|
||||
};
|
||||
|
||||
// Category rows cross the DB-worker IPC boundary in the snake_case wire
|
||||
// shape declared by XCategoryFromDb/XtreamCategoryFromDb. A bare select()
|
||||
// would leak Drizzle's camelCase property names (xtreamId, playlistId)
|
||||
// instead, silently breaking consumers such as the playlist backup
|
||||
// export/restore (issue #1017).
|
||||
const categoryWireShape = {
|
||||
id: schema.categories.id,
|
||||
playlist_id: schema.categories.playlistId,
|
||||
name: schema.categories.name,
|
||||
type: schema.categories.type,
|
||||
xtream_id: schema.categories.xtreamId,
|
||||
hidden: schema.categories.hidden,
|
||||
};
|
||||
|
||||
function normalizeXtreamCategoryId(
|
||||
rawCategoryId: string | number
|
||||
): number | null {
|
||||
@@ -43,7 +57,7 @@ export async function getCategories(
|
||||
// If partial category re-inserts are added later, persist a provider
|
||||
// sort index instead of relying on the insertion id.
|
||||
return db
|
||||
.select()
|
||||
.select(categoryWireShape)
|
||||
.from(schema.categories)
|
||||
.where(
|
||||
and(
|
||||
@@ -123,7 +137,7 @@ export async function getAllCategories(
|
||||
type: 'live' | 'movies' | 'series'
|
||||
) {
|
||||
return db
|
||||
.select()
|
||||
.select(categoryWireShape)
|
||||
.from(schema.categories)
|
||||
.where(
|
||||
and(
|
||||
|
||||
Reference in new issue
Block a user