test(core): replace image-cache module mocks with globalThis.fetch spies

Switch `mock.module("../src/image-cache", ...)` stubs in thumbhash, person-detail, colors, and credits tests to `spyOn(globalThis, "fetch")` instead. This avoids the cross-file persistence problem with `mock.module` and tests the real code paths more faithfully.

Update `image-cache.ts` to call `globalThis.fetch` so the spy intercepts it, add `mock.restore()` to the global `afterEach` in `preload.ts`, and set `IMAGE_CACHE_ENABLED=false` in affected tests to avoid disk writes during test runs.
This commit is contained in:
2026-03-15 11:38:35 -04:00
parent d060177c67
commit c201006ca2
6 changed files with 66 additions and 26 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ async function fetchRemoteImage(
const url =
tmdbCdnImageUrl(tmdbPath, category) ?? `${TMDB_IMAGE_BASE_URL}${tmdbPath}`;
const res = await fetch(url);
const res = await globalThis.fetch(url);
if (!res.ok) {
log.warn(`Fetch failed: ${url} -> ${res.status}`);
return null;
+1 -8
View File
@@ -1,11 +1,4 @@
import { describe, expect, mock, test } from "bun:test";
mock.module("../src/image-cache", () => ({
downloadAndCacheImage: async () => {},
getLocalImagePath: () => "",
imageCacheEnabled: () => false,
isImageCached: async () => false,
}));
import { describe, expect, test } from "bun:test";
import { parseColorPalette } from "../src/colors";
+1 -6
View File
@@ -1,12 +1,7 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { beforeEach, describe, expect, test } from "bun:test";
import { persons, titleCast } from "@sofa/db/schema";
import { clearAllTables, insertTitle, testDb } from "@sofa/db/test-utils";
mock.module("../src/image-cache", () => ({
imageCacheEnabled: () => false,
cacheProfilePhotos: async () => {},
}));
import { getCastForTitle } from "../src/credits";
beforeEach(() => {
+28 -5
View File
@@ -1,4 +1,12 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import {
afterEach,
beforeEach,
describe,
expect,
mock,
spyOn,
test,
} from "bun:test";
import { eq } from "@sofa/db/helpers";
import { persons } from "@sofa/db/schema";
import { clearAllTables, testDb } from "@sofa/db/test-utils";
@@ -22,10 +30,6 @@ let nextPersonDetails = {
imdb_id: null,
};
mock.module("../src/image-cache", () => ({
loadImageBuffer: async () => nextBuffer,
}));
mock.module("@sofa/tmdb/client", () => ({
getPersonDetails: async () => nextPersonDetails,
getPersonCombinedCredits: async () => ({ cast: [] }),
@@ -35,6 +39,7 @@ import { getOrFetchPerson } from "../src/person";
beforeEach(() => {
clearAllTables();
process.env.IMAGE_CACHE_ENABLED = "false";
nextBuffer = TINY_PNG;
nextPersonDetails = {
id: 100,
@@ -48,6 +53,24 @@ beforeEach(() => {
popularity: 10,
imdb_id: null,
};
spyOn(globalThis, "fetch").mockImplementation((async (
_input: string | URL | Request,
_init?: RequestInit,
) => {
if (!nextBuffer) {
return new Response(null, { status: 404 });
}
return new Response(nextBuffer, {
status: 200,
headers: { "content-type": "image/png" },
});
}) as typeof fetch);
});
afterEach(() => {
delete process.env.IMAGE_CACHE_ENABLED;
mock.restore();
});
describe("getOrFetchPerson", () => {
+7 -1
View File
@@ -1,9 +1,15 @@
import { mock } from "bun:test";
import { afterEach, mock } from "bun:test";
import { applyMigrations, testDb } from "@sofa/db/test-utils";
process.env.LOG_LEVEL ??= "error";
mock.module("@sofa/db/client", () => ({
db: testDb,
closeDatabase: () => {},
}));
applyMigrations();
afterEach(() => {
mock.restore();
});
+28 -5
View File
@@ -1,4 +1,12 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import {
afterEach,
beforeEach,
describe,
expect,
mock,
spyOn,
test,
} from "bun:test";
import { eq } from "@sofa/db/helpers";
import { episodes, seasons, titles } from "@sofa/db/schema";
import { clearAllTables, testDb } from "@sofa/db/test-utils";
@@ -10,15 +18,30 @@ const TINY_PNG = Buffer.from(
let nextBuffer: Buffer | null = TINY_PNG;
mock.module("../src/image-cache", () => ({
loadImageBuffer: async () => nextBuffer,
}));
import { generateEpisodeThumbHash, generateThumbHash } from "../src/thumbhash";
beforeEach(() => {
clearAllTables();
process.env.IMAGE_CACHE_ENABLED = "false";
nextBuffer = TINY_PNG;
spyOn(globalThis, "fetch").mockImplementation((async (
_input: string | URL | Request,
_init?: RequestInit,
) => {
if (!nextBuffer) {
return new Response(null, { status: 404 });
}
return new Response(nextBuffer, {
status: 200,
headers: { "content-type": "image/png" },
});
}) as typeof fetch);
});
afterEach(() => {
delete process.env.IMAGE_CACHE_ENABLED;
mock.restore();
});
describe("thumbhash generation", () => {