mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
feat: validate and normalize book published year to 1000-2200 range
This commit is contained in:
@@ -4,7 +4,7 @@ import { useSafeHtml } from './useSafeHtml'
|
||||
|
||||
vi.mock('dompurify', () => ({
|
||||
default: {
|
||||
sanitize: vi.fn((input: string, config: { ALLOWED_TAGS: string[] }) => {
|
||||
sanitize: vi.fn<(input: string, config: { ALLOWED_TAGS: string[] }) => string>((input: string, config: { ALLOWED_TAGS: string[] }) => {
|
||||
if (!config?.ALLOWED_TAGS) return ''
|
||||
// Simulate DOMPurify by stripping disallowed tags and dangerous attributes
|
||||
return input
|
||||
|
||||
@@ -90,6 +90,8 @@ describe('Book DTO validation', () => {
|
||||
|
||||
expect((await errorsFor(UpdateBookMetadataDto, { rating: 6 })).length).toBeGreaterThan(0);
|
||||
expect((await errorsFor(UpdateBookMetadataDto, { publishedYear: 0 })).length).toBeGreaterThan(0);
|
||||
expect((await errorsFor(UpdateBookMetadataDto, { publishedYear: 999 })).length).toBeGreaterThan(0);
|
||||
expect((await errorsFor(UpdateBookMetadataDto, { publishedYear: 2201 })).length).toBeGreaterThan(0);
|
||||
expect((await errorsFor(UpdateBookMetadataDto, { authors: ['ok', 1] })).length).toBeGreaterThan(0);
|
||||
expect((await errorsFor(UpdateBookMetadataDto, { language: 'english-too-long' })).length).toBeGreaterThan(0);
|
||||
expect((await errorsFor(UpdateBookMetadataDto, { isbn10: '12345678901' })).length).toBeGreaterThan(0);
|
||||
|
||||
@@ -33,7 +33,7 @@ export class UpdateBookMetadataDto {
|
||||
@IsOptional() @IsString() @MaxLength(1000) subtitle?: string | null;
|
||||
@IsOptional() @IsString() description?: string | null;
|
||||
@IsOptional() @IsString() @MaxLength(500) publisher?: string | null;
|
||||
@IsOptional() @IsInt() @Min(1) publishedYear?: number | null;
|
||||
@IsOptional() @IsInt() @Min(1000) @Max(2200) publishedYear?: number | null;
|
||||
@IsOptional() @IsString() @MaxLength(10) language?: string | null;
|
||||
@IsOptional() @IsInt() @Min(1) pageCount?: number | null;
|
||||
@IsOptional() @IsString() @MaxLength(500) seriesName?: string | null;
|
||||
|
||||
@@ -530,6 +530,28 @@ describe('MetadataService', () => {
|
||||
expect(updateSet).toHaveBeenCalledWith(expect.objectContaining({ publishedYear: null }));
|
||||
});
|
||||
|
||||
it('extractAndSave(mobi) normalizes out-of-range 4-digit years to null before db writes', async () => {
|
||||
const { db, updateSet } = makeDb();
|
||||
const service = makeService(db);
|
||||
vi.spyOn(service, 'replaceAuthors').mockResolvedValue(undefined);
|
||||
vi.spyOn(service, 'replaceGenres').mockResolvedValue(undefined);
|
||||
|
||||
mockParseMobiFile.mockResolvedValue({
|
||||
title: 'Ancient Book',
|
||||
description: null,
|
||||
isbn: 'isbn',
|
||||
publisher: null,
|
||||
publishedDate: '0101-01-01',
|
||||
language: 'en',
|
||||
authors: ['Author'],
|
||||
tags: ['Tag'],
|
||||
});
|
||||
|
||||
await service.extractAndSave(34, '/tmp/ancient-book.mobi', 'mobi');
|
||||
|
||||
expect(updateSet).toHaveBeenCalledWith(expect.objectContaining({ publishedYear: null }));
|
||||
});
|
||||
|
||||
it('replaceAuthors normalizes names and deduplicates case-insensitively before db writes', async () => {
|
||||
const { db, deleteWhere, transaction } = makeDb();
|
||||
const service = makeService(db);
|
||||
|
||||
@@ -44,6 +44,16 @@ interface RelationMutationOptions {
|
||||
const AUDIO_FORMATS = ['m4b', 'mp3', 'm4a', 'opus', 'ogg', 'flac'] as const;
|
||||
const MAX_RELATION_NAME_LENGTH = 200;
|
||||
const EXTRACTED_COVER_SOURCE = 'extracted';
|
||||
const MIN_PUBLISHED_YEAR = 1000;
|
||||
const MAX_PUBLISHED_YEAR = 2200;
|
||||
|
||||
function normalizePublishedYear(year: number | null | undefined): number | null | undefined {
|
||||
if (year === undefined) return undefined;
|
||||
if (year === null) return null;
|
||||
if (!Number.isInteger(year)) return null;
|
||||
if (year < MIN_PUBLISHED_YEAR || year > MAX_PUBLISHED_YEAR) return null;
|
||||
return year;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MetadataService {
|
||||
@@ -490,7 +500,7 @@ export class MetadataService {
|
||||
if (filtered.title !== undefined) scalarFields.title = filtered.title;
|
||||
if (filtered.description !== undefined) scalarFields.description = filtered.description;
|
||||
if (filtered.publisher !== undefined) scalarFields.publisher = filtered.publisher;
|
||||
if (filtered.publishedYear !== undefined) scalarFields.publishedYear = filtered.publishedYear;
|
||||
if (filtered.publishedYear !== undefined) scalarFields.publishedYear = normalizePublishedYear(filtered.publishedYear);
|
||||
if (filtered.language !== undefined) scalarFields.language = filtered.language;
|
||||
if (filtered.audioMetadata?.durationSeconds !== undefined) scalarFields.durationSeconds = filtered.audioMetadata.durationSeconds;
|
||||
if (filtered.audioMetadata?.chapters !== undefined) scalarFields.chapters = filtered.audioMetadata.chapters;
|
||||
@@ -545,7 +555,7 @@ export class MetadataService {
|
||||
if (filtered.isbn10 !== undefined) scalarFields.isbn10 = filtered.isbn10;
|
||||
if (filtered.isbn13 !== undefined) scalarFields.isbn13 = filtered.isbn13;
|
||||
if (filtered.publisher !== undefined) scalarFields.publisher = filtered.publisher;
|
||||
if (filtered.publishedYear !== undefined) scalarFields.publishedYear = filtered.publishedYear;
|
||||
if (filtered.publishedYear !== undefined) scalarFields.publishedYear = normalizePublishedYear(filtered.publishedYear);
|
||||
if (filtered.language !== undefined) scalarFields.language = filtered.language;
|
||||
if (filtered.seriesName !== undefined) scalarFields.seriesName = filtered.seriesName;
|
||||
if (filtered.seriesIndex !== undefined) scalarFields.seriesIndex = filtered.seriesIndex;
|
||||
|
||||
Reference in New Issue
Block a user