fix(downloads): fail closed on invalid stored coordinates

This commit is contained in:
4gray committed 2026-08-02 11:13:52 +02:00
1 parent 87727f14b7
commit aaf4568d89
2 files changed
+47 -18

No files matched your search

@@ -1,6 +1,9 @@
import * as schema from '../../database/schema';
import type { DownloadsDatabase } from './download-task';
import { resolveExistingDownloadIdentity } from './download-request-identity';
import {
type ExistingDownloadIdentityResolution,
resolveExistingDownloadIdentity,
} from './download-request-identity';
type DownloadRow = typeof schema.downloads.$inferSelect;
@@ -66,9 +69,10 @@ describe('resolveExistingDownloadIdentity', () => {
const row = createDownloadRow();
const harness = createQueryHarness([row], []);
await expect(
resolveExistingDownloadIdentity(harness.db, episodeRequest)
).resolves.toEqual({
const resolution: ExistingDownloadIdentityResolution =
await resolveExistingDownloadIdentity(harness.db, episodeRequest);
expect(resolution).toEqual({
item: row,
kind: 'match',
migrateCanonicalId: false,
@@ -112,6 +116,24 @@ describe('resolveExistingDownloadIdentity', () => {
).resolves.toEqual({ kind: 'conflict' });
});
it.each([
['series id', { seriesXtreamId: Number.MAX_SAFE_INTEGER + 1 }],
['season number', { seasonNumber: 2.5 }],
['episode number', { episodeNumber: Number.NaN }],
])(
'fails closed when the canonical row has a present but unsafe %s',
async (_label, override) => {
const harness = createQueryHarness(
[createDownloadRow(override)],
[]
);
await expect(
resolveExistingDownloadIdentity(harness.db, episodeRequest)
).resolves.toEqual({ kind: 'conflict' });
}
);
it('fails closed when multiple rows share the legacy coordinates', async () => {
const harness = createQueryHarness([], [
createDownloadRow({ id: 42, xtreamId: 77 }),
@@ -11,20 +11,20 @@ export const DOWNLOAD_IDENTITY_KIND = {
} as const;
interface DownloadIdentityNone {
kind: typeof DOWNLOAD_IDENTITY_KIND.NONE;
readonly kind: typeof DOWNLOAD_IDENTITY_KIND.NONE;
}
interface DownloadIdentityConflict {
kind: typeof DOWNLOAD_IDENTITY_KIND.CONFLICT;
readonly kind: typeof DOWNLOAD_IDENTITY_KIND.CONFLICT;
}
interface DownloadIdentityMatch {
item: DownloadRow;
kind: typeof DOWNLOAD_IDENTITY_KIND.MATCH;
migrateCanonicalId: boolean;
readonly item: DownloadRow;
readonly kind: typeof DOWNLOAD_IDENTITY_KIND.MATCH;
readonly migrateCanonicalId: boolean;
}
export type DownloadIdentityResolution =
export type ExistingDownloadIdentityResolution =
| DownloadIdentityNone
| DownloadIdentityConflict
| DownloadIdentityMatch;
@@ -38,7 +38,7 @@ export interface DownloadIdentityRequest {
xtreamId: number;
}
function isSafeInteger(value: number | undefined): value is number {
function isSafeInteger(value: number | null | undefined): value is number {
return Number.isSafeInteger(value);
}
@@ -51,25 +51,32 @@ function rowHasConflictingCoordinates(
>
>
): boolean {
const { episodeNumber, seasonNumber, seriesXtreamId } = row;
if (
!isSafeInteger(row.seriesXtreamId ?? undefined) ||
!isSafeInteger(row.seasonNumber ?? undefined) ||
!isSafeInteger(row.episodeNumber ?? undefined)
seriesXtreamId === null ||
seriesXtreamId === undefined ||
seasonNumber === null ||
seasonNumber === undefined ||
episodeNumber === null ||
episodeNumber === undefined
) {
return false;
}
return (
row.seriesXtreamId !== request.seriesXtreamId ||
row.seasonNumber !== request.seasonNumber ||
row.episodeNumber !== request.episodeNumber
!isSafeInteger(seriesXtreamId) ||
!isSafeInteger(seasonNumber) ||
!isSafeInteger(episodeNumber) ||
seriesXtreamId !== request.seriesXtreamId ||
seasonNumber !== request.seasonNumber ||
episodeNumber !== request.episodeNumber
);
}
export async function resolveExistingDownloadIdentity(
db: DownloadsDatabase,
request: DownloadIdentityRequest
): Promise<DownloadIdentityResolution> {
): Promise<ExistingDownloadIdentityResolution> {
const canonicalRows = await db
.select()
.from(schema.downloads)