Files
ReadMeABook/tests/utils/release-date.test.ts
T
kikootwo 6f8ac86a43 Add skip-unreleased auto-search feature
Introduce an indexer-wide option to skip automatic searches for books with future release dates (config key: `indexer.skip_unreleased`, default ON). Adds a GET/PUT admin API for indexer options, a UI toggle on the Indexers settings tab (persisted on save), and persistence of a request-level releaseDate in the Prisma schema.

Adds a new request status `awaiting_release` and wires it through constants, UI components (StatusBadge, RequestCard, RecentRequestsTable, Audiobook card/modal, RequestActions), API request flows (bookdate swipe, request creation, manual search, request PATCHs, request listing groups), and services. Implements a pure release-date utility (isUnreleased / shouldSkipAutoSearch) and updates background processors: monitor-rss-feeds (skip matches but do not mutate status), retry-missing-torrents (drives bidirectional transitions between awaiting_search and awaiting_release and queues searches when appropriate), and request-creator/bookdate swipe (gate initial auto-search). Adds tests for the swipe gate and other related test updates. Logs transitions and gate decisions for observability.
2026-05-15 15:35:01 -04:00

135 lines
3.9 KiB
TypeScript

/**
* Component: Release Date Utilities Tests
* Documentation: documentation/backend/database.md
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { isUnreleased, shouldSkipAutoSearch } from '@/lib/utils/release-date';
describe('isUnreleased', () => {
it('returns false for null', () => {
expect(isUnreleased(null)).toBe(false);
});
it('returns false for undefined', () => {
expect(isUnreleased(undefined)).toBe(false);
});
it('returns false for empty string', () => {
expect(isUnreleased('')).toBe(false);
});
it('returns false for malformed string', () => {
expect(isUnreleased('not-a-date')).toBe(false);
});
it('returns false when release date is today (UTC date-only)', () => {
const now = new Date();
const today = new Date(Date.UTC(
now.getUTCFullYear(),
now.getUTCMonth(),
now.getUTCDate()
));
expect(isUnreleased(today)).toBe(false);
});
it('returns false when release date is yesterday', () => {
const now = new Date();
const yesterday = new Date(Date.UTC(
now.getUTCFullYear(),
now.getUTCMonth(),
now.getUTCDate() - 1
));
expect(isUnreleased(yesterday)).toBe(false);
});
it('returns true when release date is tomorrow', () => {
const now = new Date();
const tomorrow = new Date(Date.UTC(
now.getUTCFullYear(),
now.getUTCMonth(),
now.getUTCDate() + 1
));
expect(isUnreleased(tomorrow)).toBe(true);
});
it('returns true for far-future ISO date string', () => {
expect(isUnreleased('2099-01-01')).toBe(true);
});
it('returns true for far-future ISO datetime string', () => {
expect(isUnreleased('2099-01-01T00:00:00Z')).toBe(true);
});
it('returns false for far-past Date object', () => {
expect(isUnreleased(new Date('1990-01-01'))).toBe(false);
});
it('returns true for far-future Date object', () => {
expect(isUnreleased(new Date('2099-01-01'))).toBe(true);
});
describe('UTC boundary cases with fake timers', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('treats same UTC day as released regardless of clock time', () => {
// Pin "now" to mid-day UTC on 2026-06-15
vi.setSystemTime(new Date('2026-06-15T12:00:00Z'));
// A release date at the very start of the same UTC day → released
expect(isUnreleased('2026-06-15T00:00:00Z')).toBe(false);
// A release date at the very end of the same UTC day → released
expect(isUnreleased('2026-06-15T23:59:59Z')).toBe(false);
});
it('treats next UTC day as unreleased', () => {
vi.setSystemTime(new Date('2026-06-15T23:59:59Z'));
expect(isUnreleased('2026-06-16T00:00:00Z')).toBe(true);
});
it('treats previous UTC day as released', () => {
vi.setSystemTime(new Date('2026-06-15T00:00:00Z'));
expect(isUnreleased('2026-06-14T23:59:59Z')).toBe(false);
});
});
});
describe('shouldSkipAutoSearch', () => {
const tomorrow = new Date();
tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);
const yesterday = new Date();
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
it('does not skip when setting is OFF, even if unreleased', () => {
expect(shouldSkipAutoSearch({ releaseDate: tomorrow }, false)).toEqual({
skip: false,
});
});
it('skips with reason "unreleased" when setting ON and release is in the future', () => {
expect(shouldSkipAutoSearch({ releaseDate: tomorrow }, true)).toEqual({
skip: true,
reason: 'unreleased',
});
});
it('does not skip when setting ON and release is in the past', () => {
expect(shouldSkipAutoSearch({ releaseDate: yesterday }, true)).toEqual({
skip: false,
});
});
it('does not skip when setting ON and releaseDate is null', () => {
expect(shouldSkipAutoSearch({ releaseDate: null }, true)).toEqual({
skip: false,
});
});
});