fix(cron): judge stale seasons by the latest season fetch

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Sonnet 5.5 committed 2026-10-05 14:15:05 -04:00
1 parent 6576f21235
commit 899be96316
2 files changed
+36 -3

No files matched your search

+29 -1
View File
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { cronRuns, titles } from "@sofa/db/schema";
import { cronRuns, seasons, titles } from "@sofa/db/schema";
import { clearAllTables, eq, insertTitle, testDb } from "@sofa/test/db";
import {
@@ -8,6 +8,7 @@ import {
failCronRun,
getLibraryTitlesDueForRefresh,
getStaleLibraryTitles,
getTitleIdsWithStaleSeasons,
libraryRefreshIntervalMs,
runIsolated,
startCronRun,
@@ -198,3 +199,30 @@ describe("getLibraryTitlesDueForRefresh", () => {
expect(getLibraryTitlesDueForRefresh(["t-shell"], now)).toEqual(["t-shell"]);
});
});
describe("getTitleIdsWithStaleSeasons", () => {
const DAY = 24 * 60 * 60 * 1000;
const now = Date.now();
const cutoff = new Date(now - 7 * DAY);
function addSeason(id: string, titleId: string, seasonNumber: number, daysAgo: number) {
testDb
.insert(seasons)
.values({ id, titleId, seasonNumber, lastFetchedAt: new Date(now - daysAgo * DAY) })
.run();
}
test("is not stale when the most recent season fetch is fresh", () => {
insertTitle({ id: "tv-partial", tmdbId: 1, type: "tv" });
addSeason("s-1", "tv-partial", 1, 30);
addSeason("s-2", "tv-partial", 2, 1);
expect(getTitleIdsWithStaleSeasons(["tv-partial"], cutoff).has("tv-partial")).toBe(false);
});
test("is stale when every season was fetched long ago", () => {
insertTitle({ id: "tv-old", tmdbId: 2, type: "tv" });
addSeason("s-3", "tv-old", 1, 30);
addSeason("s-4", "tv-old", 2, 30);
expect(getTitleIdsWithStaleSeasons(["tv-old"], cutoff).has("tv-old")).toBe(true);
});
});
+7 -2
View File
@@ -1,4 +1,4 @@
import { and, eq, gte, inArray, isNotNull, isNull, lt, or } from "drizzle-orm";
import { and, eq, gte, inArray, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
import { db } from "../client";
import {
@@ -111,14 +111,19 @@ export function getReturningTvShows() {
.all();
}
/**
* Titles whose most recent season fetch is older than `staleDate`. Uses the latest
* season fetch (not any season) because partial refreshes only touch the newest seasons.
*/
export function getTitleIdsWithStaleSeasons(titleIds: string[], staleDate: Date) {
if (titleIds.length === 0) return new Set<string>();
return new Set(
db
.select({ titleId: seasons.titleId })
.from(seasons)
.where(and(inArray(seasons.titleId, titleIds), lt(seasons.lastFetchedAt, staleDate)))
.where(inArray(seasons.titleId, titleIds))
.groupBy(seasons.titleId)
.having(sql`max(${seasons.lastFetchedAt}) < ${sql.param(staleDate, seasons.lastFetchedAt)}`)
.all()
.map((r) => r.titleId),
);