feat: migrate test runner from Bun to Vitest

- Replace bun:test with Vitest across all 22 test files
- Create @sofa/test package with shared setup and DB test helpers
  - setup.ts: vi.mock for @sofa/db/client, Bun.randomUUIDv7 polyfill
  - db.ts: in-memory SQLite via better-sqlite3, seed helpers
- Add per-project vitest configs (packages/core, apps/web)
- Add root vitest.config.ts with projects and v8 coverage
- Set up @vitest/browser + Playwright for web component tests
- Move validateBackupDatabase from core/backup.ts to @sofa/db/client
- Derive required backup tables from schema instead of hard-coded list
- Update CI workflow with Playwright install and Codecov upload
- Update CLAUDE.md documentation for Vitest

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 4.6 committed 2026-03-22 12:03:06 -04:00
1 parent 3f05e08fea
commit 5f3e14b415
45 files changed
+589 -364

No files matched your search

+7 -13
View File
@@ -1,19 +1,13 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { availabilityOffers } from "@sofa/db/schema";
import {
clearAllTables,
eq,
insertAvailabilityOffer,
insertTitle,
testDb,
} from "@sofa/db/test-utils";
import { clearAllTables, eq, insertAvailabilityOffer, insertTitle, testDb } from "@sofa/test/db";
let watchProvidersResponse: { results: Record<string, unknown> } = { results: {} };
const { getWatchProviders } = vi.hoisted(() => ({
getWatchProviders: vi.fn(async () => ({ results: {} as Record<string, unknown> })),
}));
const getWatchProviders = mock(async () => watchProvidersResponse);
mock.module("@sofa/tmdb/client", () => ({
vi.mock("@sofa/tmdb/client", () => ({
getWatchProviders,
}));
@@ -21,7 +15,7 @@ import { refreshAvailability } from "../src/availability";
beforeEach(() => {
clearAllTables();
watchProvidersResponse = { results: {} };
getWatchProviders.mockImplementation(async () => ({ results: {} }));
});
describe("refreshAvailability", () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from "vitest";
import { getBackupSource, isKnownBackup, isValidBackupFilename } from "../src/backup";
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from "vitest";
import { parseColorPalette } from "../src/colors";
+2 -2
View File
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, test } from "bun:test";
import { beforeEach, describe, expect, test } from "vitest";
import { persons, titleCast } from "@sofa/db/schema";
import { clearAllTables, insertTitle, testDb } from "@sofa/db/test-utils";
import { clearAllTables, insertTitle, testDb } from "@sofa/test/db";
import { getCastForTitle } from "../src/credits";
+6 -6
View File
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, test } from "bun:test";
import { beforeEach, describe, expect, test } from "vitest";
import {
clearAllTables,
@@ -11,7 +11,7 @@ import {
insertTitle,
insertTvShow,
insertUser,
} from "@sofa/db/test-utils";
} from "@sofa/test/db";
import {
getContinueWatchingFeed,
@@ -82,7 +82,7 @@ describe("getWatchHistory", () => {
insertMovieWatch("user-1", "m2");
const history = getWatchHistory("user-1", "movies", "this_week");
expect(history).toBeArrayOfSize(7);
expect(history).toHaveLength(7);
const totalCount = history.reduce((sum, b) => sum + b.count, 0);
expect(totalCount).toBe(2);
});
@@ -90,20 +90,20 @@ describe("getWatchHistory", () => {
test("returns all-zero buckets when no watches", () => {
insertUser();
const history = getWatchHistory("user-1", "movies", "this_month");
expect(history).toBeArrayOfSize(30);
expect(history).toHaveLength(30);
expect(history.every((b) => b.count === 0)).toBe(true);
});
test("returns correct bucket count for today period", () => {
insertUser();
const history = getWatchHistory("user-1", "episodes", "today");
expect(history).toBeArrayOfSize(24);
expect(history).toHaveLength(24);
});
test("returns correct bucket count for this_year period", () => {
insertUser();
const history = getWatchHistory("user-1", "movies", "this_year");
expect(history).toBeArrayOfSize(12);
expect(history).toHaveLength(12);
});
});
+2 -2
View File
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, test } from "bun:test";
import { beforeEach, describe, expect, test } from "vitest";
import {
clearAllTables,
@@ -9,7 +9,7 @@ import {
insertTitle,
insertTvShow,
insertUser,
} from "@sofa/db/test-utils";
} from "@sofa/test/db";
import { generateUserExport } from "../src/export";
+2 -2
View File
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { loadImageBuffer } from "../src/image-cache";
@@ -13,7 +13,7 @@ afterEach(() => {
describe("loadImageBuffer", () => {
test("uses category-specific TMDB sizes when cache is disabled", async () => {
const urls: string[] = [];
const fetchSpy = spyOn(globalThis, "fetch").mockImplementation((async (
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation((async (
input: string | URL | Request,
) => {
urls.push(String(input));
+1 -2
View File
@@ -1,6 +1,5 @@
import { describe, expect, test } from "bun:test";
import AdmZip from "adm-zip";
import { describe, expect, test } from "vitest";
import {
type ParseResult,
+3 -3
View File
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, spyOn, test } from "bun:test";
import { beforeEach, describe, expect, test, vi } from "vitest";
import {
importJobs,
@@ -15,7 +15,7 @@ import {
insertTvShow,
insertUser,
testDb,
} from "@sofa/db/test-utils";
} from "@sofa/test/db";
import * as tmdbClient from "@sofa/tmdb/client";
import type { NormalizedImport } from "../src/imports/parsers";
@@ -435,7 +435,7 @@ describe("processImportJob — failed resolution", () => {
};
// Mock TMDB search to return empty results (no network call)
const searchSpy = spyOn(tmdbClient, "searchMovies").mockResolvedValue({
const searchSpy = vi.spyOn(tmdbClient, "searchMovies").mockResolvedValue({
results: [],
} as never);
+27 -27
View File
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, spyOn, test } from "bun:test";
import { afterEach, describe, expect, test, vi } from "vitest";
import * as tmdbClient from "@sofa/tmdb/client";
@@ -9,9 +9,9 @@ import { resolveMovieTmdbId, resolveShowTmdbId } from "../src/imports/resolve";
// globalThis.fetch because openapi-fetch captures the fetch reference at
// module-init time, making globalThis.fetch mocking ineffective.
let findSpy: ReturnType<typeof spyOn>;
let searchMoviesSpy: ReturnType<typeof spyOn>;
let searchTvSpy: ReturnType<typeof spyOn>;
let findSpy: ReturnType<typeof vi.spyOn>;
let searchMoviesSpy: ReturnType<typeof vi.spyOn>;
let searchTvSpy: ReturnType<typeof vi.spyOn>;
afterEach(() => {
findSpy?.mockRestore();
@@ -23,8 +23,8 @@ afterEach(() => {
describe("resolveMovieTmdbId", () => {
test("returns tmdbId directly when provided", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId");
searchMoviesSpy = spyOn(tmdbClient, "searchMovies");
findSpy = vi.spyOn(tmdbClient, "findByExternalId");
searchMoviesSpy = vi.spyOn(tmdbClient, "searchMovies");
const result = await resolveMovieTmdbId({ tmdbId: 123 });
expect(result).toBe(123);
@@ -33,7 +33,7 @@ describe("resolveMovieTmdbId", () => {
});
test("resolves via IMDB ID lookup", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [{ id: 456 }],
tv_results: [],
tv_episode_results: [],
@@ -45,7 +45,7 @@ describe("resolveMovieTmdbId", () => {
});
test("falls back to TVDB lookup when no IMDB ID", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [{ id: 789 }],
tv_results: [],
tv_episode_results: [],
@@ -58,7 +58,7 @@ describe("resolveMovieTmdbId", () => {
test("IMDB returns no movie, falls back to TVDB", async () => {
let callIndex = 0;
findSpy = spyOn(tmdbClient, "findByExternalId").mockImplementation(async () => {
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockImplementation(async () => {
callIndex++;
if (callIndex === 1) {
// IMDB lookup — no results
@@ -85,7 +85,7 @@ describe("resolveMovieTmdbId", () => {
});
test("falls back to title search when no IDs available", async () => {
searchMoviesSpy = spyOn(tmdbClient, "searchMovies").mockResolvedValue({
searchMoviesSpy = vi.spyOn(tmdbClient, "searchMovies").mockResolvedValue({
results: [{ id: 321, title: "Inception", release_date: "2010-07-16" }],
} as never);
@@ -95,7 +95,7 @@ describe("resolveMovieTmdbId", () => {
});
test("title search with year prefers matching year", async () => {
searchMoviesSpy = spyOn(tmdbClient, "searchMovies").mockResolvedValue({
searchMoviesSpy = vi.spyOn(tmdbClient, "searchMovies").mockResolvedValue({
results: [
{ id: 100, title: "Dune", release_date: "1984-12-14" },
{ id: 200, title: "Dune", release_date: "2021-10-22" },
@@ -107,7 +107,7 @@ describe("resolveMovieTmdbId", () => {
});
test("title search without year match returns null", async () => {
searchMoviesSpy = spyOn(tmdbClient, "searchMovies").mockResolvedValue({
searchMoviesSpy = vi.spyOn(tmdbClient, "searchMovies").mockResolvedValue({
results: [
{ id: 100, title: "Dune", release_date: "1984-12-14" },
{ id: 200, title: "Dune", release_date: "2021-10-22" },
@@ -120,12 +120,12 @@ describe("resolveMovieTmdbId", () => {
});
test("returns null when all methods fail", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [],
tv_results: [],
tv_episode_results: [],
} as never);
searchMoviesSpy = spyOn(tmdbClient, "searchMovies").mockResolvedValue({
searchMoviesSpy = vi.spyOn(tmdbClient, "searchMovies").mockResolvedValue({
results: [],
} as never);
@@ -137,8 +137,8 @@ describe("resolveMovieTmdbId", () => {
});
test("returns null when no identifiers at all", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId");
searchMoviesSpy = spyOn(tmdbClient, "searchMovies");
findSpy = vi.spyOn(tmdbClient, "findByExternalId");
searchMoviesSpy = vi.spyOn(tmdbClient, "searchMovies");
const result = await resolveMovieTmdbId({});
expect(result).toBeNull();
@@ -147,7 +147,7 @@ describe("resolveMovieTmdbId", () => {
});
test("cache prevents duplicate lookups", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [{ id: 555 }],
tv_results: [],
tv_episode_results: [],
@@ -166,7 +166,7 @@ describe("resolveMovieTmdbId", () => {
});
test("cache stores null for unresolvable items", async () => {
searchMoviesSpy = spyOn(tmdbClient, "searchMovies").mockResolvedValue({
searchMoviesSpy = vi.spyOn(tmdbClient, "searchMovies").mockResolvedValue({
results: [],
} as never);
@@ -186,7 +186,7 @@ describe("resolveMovieTmdbId", () => {
describe("resolveShowTmdbId", () => {
test("returns tmdbId directly when provided", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId");
findSpy = vi.spyOn(tmdbClient, "findByExternalId");
const result = await resolveShowTmdbId({ tmdbId: 42 });
expect(result).toBe(42);
@@ -194,7 +194,7 @@ describe("resolveShowTmdbId", () => {
});
test("resolves via IMDB ID — show-level result", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [],
tv_results: [{ id: 600, name: "Breaking Bad" }],
tv_episode_results: [],
@@ -205,7 +205,7 @@ describe("resolveShowTmdbId", () => {
});
test("resolves via IMDB ID — episode-level result extracts show_id", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [],
tv_results: [],
tv_episode_results: [
@@ -224,7 +224,7 @@ describe("resolveShowTmdbId", () => {
});
test("falls back to TVDB lookup", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [],
tv_results: [{ id: 800 }],
tv_episode_results: [],
@@ -236,7 +236,7 @@ describe("resolveShowTmdbId", () => {
});
test("TVDB lookup extracts show_id from episode result", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [],
tv_results: [],
tv_episode_results: [
@@ -255,7 +255,7 @@ describe("resolveShowTmdbId", () => {
});
test("falls back to title search", async () => {
searchTvSpy = spyOn(tmdbClient, "searchTv").mockResolvedValue({
searchTvSpy = vi.spyOn(tmdbClient, "searchTv").mockResolvedValue({
results: [{ id: 900, name: "The Office" }],
} as never);
@@ -265,12 +265,12 @@ describe("resolveShowTmdbId", () => {
});
test("returns null when all methods fail", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [],
tv_results: [],
tv_episode_results: [],
} as never);
searchTvSpy = spyOn(tmdbClient, "searchTv").mockResolvedValue({
searchTvSpy = vi.spyOn(tmdbClient, "searchTv").mockResolvedValue({
results: [],
} as never);
@@ -282,7 +282,7 @@ describe("resolveShowTmdbId", () => {
});
test("cache prevents duplicate show lookups", async () => {
findSpy = spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
findSpy = vi.spyOn(tmdbClient, "findByExternalId").mockResolvedValue({
movie_results: [],
tv_results: [{ id: 950 }],
tv_episode_results: [],
+11 -9
View File
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { beforeEach, describe, expect, test, vi } from "vitest";
import {
clearAllTables,
@@ -6,19 +6,21 @@ import {
insertStatus,
insertTitle,
insertUser,
} from "@sofa/db/test-utils";
} from "@sofa/test/db";
import { getRadarrList, getSonarrList, parseStatusParam, resolveListToken } from "../src/lists";
const { mockGetTvExternalIds } = vi.hoisted(() => ({
mockGetTvExternalIds: vi.fn(
(): Promise<{ tvdb_id: number | null; imdb_id: string | null }> =>
Promise.resolve({ tvdb_id: 55555, imdb_id: "tt9999999" }),
),
}));
// Mock getTvExternalIds for lazy resolution tests
const mockGetTvExternalIds = mock(
(): Promise<{ tvdb_id: number | null; imdb_id: string | null }> =>
Promise.resolve({ tvdb_id: 55555, imdb_id: "tt9999999" }),
);
mock.module("@sofa/tmdb/client", () => ({
vi.mock("@sofa/tmdb/client", () => ({
getTvExternalIds: mockGetTvExternalIds,
}));
import { getRadarrList, getSonarrList, parseStatusParam, resolveListToken } from "../src/lists";
beforeEach(() => {
clearAllTables();
mockGetTvExternalIds.mockClear();
+16 -25
View File
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { episodes, seasons, titles } from "@sofa/db/schema";
import { clearAllTables, eq, insertTitle, testDb } from "@sofa/db/test-utils";
import { clearAllTables, eq, insertTitle, testDb } from "@sofa/test/db";
interface MockSeasonEpisode {
episode_number: number;
@@ -21,7 +21,7 @@ interface MockSeasonDetails {
episodes: MockSeasonEpisode[];
}
let seasonDetails: MockSeasonDetails = {
const defaultSeasonDetails: MockSeasonDetails = {
season_number: 1,
name: "Season 1",
overview: null,
@@ -39,7 +39,11 @@ let seasonDetails: MockSeasonDetails = {
],
};
mock.module("@sofa/tmdb/client", () => ({
const { mockGetTvSeasonDetails } = vi.hoisted(() => ({
mockGetTvSeasonDetails: vi.fn(),
}));
vi.mock("@sofa/tmdb/client", () => ({
getMovieDetails: async () => {
throw new Error("not used");
},
@@ -48,7 +52,7 @@ mock.module("@sofa/tmdb/client", () => ({
getTvDetails: async () => {
throw new Error("not used");
},
getTvSeasonDetails: async () => seasonDetails,
getTvSeasonDetails: mockGetTvSeasonDetails,
getVideos: async () => ({ results: [] }),
}));
@@ -56,23 +60,10 @@ import { refreshTvChildren, updateTitleWithArtInvalidation } from "../src/metada
beforeEach(() => {
clearAllTables();
seasonDetails = {
season_number: 1,
name: "Season 1",
overview: null,
poster_path: "/new-season.png",
air_date: null,
episodes: [
{
episode_number: 1,
name: "Episode 1",
overview: null,
still_path: "/new-still.png",
air_date: null,
runtime: null,
},
],
};
mockGetTvSeasonDetails.mockImplementation(async () => ({
...defaultSeasonDetails,
episodes: [...defaultSeasonDetails.episodes],
}));
});
describe("refreshTvChildren", () => {
@@ -132,8 +123,8 @@ describe("refreshTvChildren", () => {
})
.run();
seasonDetails = {
...seasonDetails,
mockGetTvSeasonDetails.mockImplementation(async () => ({
...defaultSeasonDetails,
poster_path: "/season.png",
episodes: [
{
@@ -145,7 +136,7 @@ describe("refreshTvChildren", () => {
runtime: null,
},
],
};
}));
await refreshTvChildren("tv-1", 10, 1);
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from "vitest";
import type { TmdbMovieDetails, TmdbTvDetails, TmdbVideo } from "@sofa/tmdb/client";
+24 -54
View File
@@ -1,15 +1,14 @@
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { personFilmography, persons } from "@sofa/db/schema";
import { clearAllTables, eq, testDb } from "@sofa/db/test-utils";
import { clearAllTables, eq, testDb } from "@sofa/test/db";
const TINY_PNG = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4//8/AwAI/AL+X2NDNwAAAABJRU5ErkJggg==",
"base64",
);
let nextBuffer: Buffer | null = TINY_PNG;
let nextPersonDetails = {
const defaultPersonDetails = {
id: 100,
name: "Updated Person",
biography: "Bio",
@@ -21,8 +20,8 @@ let nextPersonDetails = {
popularity: 10,
imdb_id: null,
};
let combinedCreditsCalls = 0;
let nextCombinedCredits = {
const defaultCombinedCredits = {
cast: [
{
id: 501,
@@ -43,62 +42,34 @@ let nextCombinedCredits = {
crew: [],
};
mock.module("@sofa/tmdb/client", () => ({
getPersonDetails: async () => nextPersonDetails,
getPersonCombinedCredits: async () => {
combinedCreditsCalls++;
return nextCombinedCredits;
},
const { mockGetPersonDetails, mockGetPersonCombinedCredits } = vi.hoisted(() => ({
mockGetPersonDetails: vi.fn(),
mockGetPersonCombinedCredits: vi.fn(),
}));
vi.mock("@sofa/tmdb/client", () => ({
getPersonDetails: mockGetPersonDetails,
getPersonCombinedCredits: mockGetPersonCombinedCredits,
}));
import { fetchFullFilmography, getOrFetchPerson } from "../src/person";
let combinedCreditsCalls = 0;
beforeEach(() => {
clearAllTables();
process.env.IMAGE_CACHE_ENABLED = "false";
nextBuffer = TINY_PNG;
nextPersonDetails = {
id: 100,
name: "Updated Person",
biography: "Bio",
birthday: null,
deathday: null,
place_of_birth: null,
profile_path: "/new-profile.png",
known_for_department: "Acting",
popularity: 10,
imdb_id: null,
};
combinedCreditsCalls = 0;
nextCombinedCredits = {
cast: [
{
id: 501,
media_type: "movie",
title: "Cached Movie",
name: undefined,
overview: "Overview",
release_date: "2024-01-01",
first_air_date: undefined,
poster_path: "/poster.png",
backdrop_path: "/backdrop.png",
popularity: 5,
vote_average: 7.5,
vote_count: 42,
character: "Lead",
},
],
crew: [],
};
spyOn(globalThis, "fetch").mockImplementation((async (
mockGetPersonDetails.mockImplementation(async () => ({ ...defaultPersonDetails }));
mockGetPersonCombinedCredits.mockImplementation(async () => {
combinedCreditsCalls++;
return { cast: [...defaultCombinedCredits.cast], crew: [] };
});
vi.spyOn(globalThis, "fetch").mockImplementation((async (
_input: string | URL | Request,
_init?: RequestInit,
) => {
if (!nextBuffer) {
return new Response(null, { status: 404 });
}
return new Response(nextBuffer, {
return new Response(TINY_PNG, {
status: 200,
headers: { "content-type": "image/png" },
});
@@ -107,7 +78,6 @@ beforeEach(() => {
afterEach(() => {
delete process.env.IMAGE_CACHE_ENABLED;
mock.restore();
});
describe("getOrFetchPerson", () => {
@@ -127,7 +97,7 @@ describe("getOrFetchPerson", () => {
const person = await getOrFetchPerson("p1");
const stored = testDb.select().from(persons).where(eq(persons.id, "p1")).get();
expect(person?.profileThumbHash).toBeString();
expect(person?.profileThumbHash).toBeTypeOf("string");
expect(stored?.profileThumbHash).toBe(person?.profileThumbHash);
});
@@ -148,7 +118,7 @@ describe("getOrFetchPerson", () => {
const stored = testDb.select().from(persons).where(eq(persons.id, "p1")).get();
expect(stored?.profilePath).toBe("/new-profile.png");
expect(person?.profileThumbHash).toBeString();
expect(person?.profileThumbHash).toBeTypeOf("string");
expect(person?.profileThumbHash).not.toBe("stale-hash");
expect(stored?.profileThumbHash).toBe(person?.profileThumbHash);
});
+2 -2
View File
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, test } from "bun:test";
import { beforeEach, describe, expect, test } from "vitest";
import { personFilmography, persons } from "@sofa/db/schema";
import { clearAllTables, insertTitle, testDb } from "@sofa/db/test-utils";
import { clearAllTables, insertTitle, testDb } from "@sofa/test/db";
import { getLocalFilmography } from "../src/person";
-20
View File
@@ -1,20 +0,0 @@
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,
optimizeDatabase: () => {},
vacuumDatabase: () => {},
closeDatabase: () => {},
isDatabaseAccessBlocked: () => false,
withDatabaseAccessBlocked: async (fn: () => Promise<unknown> | unknown) => await fn(),
}));
applyMigrations();
afterEach(() => {
mock.restore();
});
+2 -2
View File
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, test } from "bun:test";
import { beforeEach, describe, expect, test } from "vitest";
import { user } from "@sofa/db/schema";
import { clearAllTables, eq, insertUser, testDb } from "@sofa/db/test-utils";
import { clearAllTables, eq, insertUser, testDb } from "@sofa/test/db";
import {
claimInitialAdmin,
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from "vitest";
import { parseSofaExport } from "../src/imports/sofa-parser";
+4 -5
View File
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { episodes, seasons, titles } from "@sofa/db/schema";
import { clearAllTables, eq, testDb } from "@sofa/db/test-utils";
import { clearAllTables, eq, testDb } from "@sofa/test/db";
const TINY_PNG = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4//8/AwAI/AL+X2NDNwAAAABJRU5ErkJggg==",
@@ -16,7 +16,7 @@ beforeEach(() => {
clearAllTables();
process.env.IMAGE_CACHE_ENABLED = "false";
nextBuffer = TINY_PNG;
spyOn(globalThis, "fetch").mockImplementation((async (
vi.spyOn(globalThis, "fetch").mockImplementation((async (
_input: string | URL | Request,
_init?: RequestInit,
) => {
@@ -33,13 +33,12 @@ beforeEach(() => {
afterEach(() => {
delete process.env.IMAGE_CACHE_ENABLED;
mock.restore();
});
describe("thumbhash generation", () => {
test("generates a thumbhash from a loaded image buffer", async () => {
const hash = await generateThumbHash("/poster.png", "posters");
expect(hash).toBeString();
expect(hash).toBeTypeOf("string");
expect(hash).not.toBe("");
});
+2 -2
View File
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, test } from "bun:test";
import { beforeEach, describe, expect, test } from "vitest";
import {
userEpisodeWatches,
@@ -14,7 +14,7 @@ import {
insertTvShow,
insertUser,
testDb,
} from "@sofa/db/test-utils";
} from "@sofa/test/db";
import {
getUserTitleInfo,
+2 -2
View File
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, test } from "bun:test";
import { beforeEach, describe, expect, test } from "vitest";
import {
clearAllTables,
@@ -8,7 +8,7 @@ import {
insertTitle,
insertTvShow,
insertUser,
} from "@sofa/db/test-utils";
} from "@sofa/test/db";
import { getUpcomingFeed } from "../src/discovery";
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, test } from "vitest";
import { isNewerVersion } from "../src/update-check";