Files
sofa/lib/test-utils.ts
T
jake 4cf326fa00 Add comprehensive test suite with CI workflow
- 211 tests across 15 files covering services (tracking, discovery,
  metadata, backup, credits, webhooks, settings, update-check, colors,
  person), utilities (config, cron, providers, title-theme), and TMDB
  image URL helpers
- `lib/test-preload.ts` + `bunfig.toml` wire up a global in-memory
  SQLite DB with migrations for all DB-backed tests
- `lib/test-utils.ts` provides `clearAllTables()` and seed helpers
  (insertUser, insertTitle, insertTvShow, insertMovieWatch, etc.)
- Export `getBackupSource`, `isKnownBackup`, `isValidBackupFilename`,
  `buildBackupCron`, `performUpdateCheck` internals for direct testing
- GitHub Actions workflow runs `bun test --coverage` on push/PR to main
2026-03-06 13:42:58 -05:00

201 lines
4.2 KiB
TypeScript

import { Database } from "bun:sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import * as schema from "@/lib/db/schema";
const {
user,
titles,
seasons,
episodes,
userMovieWatches,
userEpisodeWatches,
userTitleStatus,
userRatings,
availabilityOffers,
titleRecommendations,
} = schema;
export const testClient = new Database(":memory:");
testClient.run("PRAGMA foreign_keys = ON");
export const testDb = drizzle({ client: testClient, schema });
export function applyMigrations() {
migrate(testDb, { migrationsFolder: "./drizzle" });
}
export function clearAllTables() {
const tables = testClient
.query(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '__drizzle%'",
)
.all() as { name: string }[];
testClient.run("PRAGMA foreign_keys = OFF");
for (const { name } of tables) {
testClient.run(`DELETE FROM "${name}"`);
}
testClient.run("PRAGMA foreign_keys = ON");
}
const now = new Date();
export function insertUser(id = "user-1") {
testDb
.insert(user)
.values({
id,
name: "Test User",
email: `${id}@test.com`,
emailVerified: true,
createdAt: now,
updatedAt: now,
})
.run();
return id;
}
export function insertTitle(
overrides: {
id?: string;
tmdbId?: number;
type?: "movie" | "tv";
title?: string;
} = {},
) {
const id = overrides.id ?? "title-1";
testDb
.insert(titles)
.values({
id,
tmdbId: overrides.tmdbId ?? 12345,
type: overrides.type ?? "movie",
title: overrides.title ?? "Test Movie",
})
.run();
return id;
}
export function insertTvShow(
titleId = "tv-1",
tmdbId = 99999,
seasonCount = 1,
epsPerSeason = 3,
) {
insertTitle({ id: titleId, tmdbId, type: "tv", title: "Test Show" });
const episodeIds: string[] = [];
for (let s = 1; s <= seasonCount; s++) {
const seasonId = `${titleId}-s${s}`;
testDb
.insert(seasons)
.values({ id: seasonId, titleId, seasonNumber: s })
.run();
for (let e = 1; e <= epsPerSeason; e++) {
const epId = `${titleId}-s${s}e${e}`;
testDb
.insert(episodes)
.values({
id: epId,
seasonId,
episodeNumber: e,
name: `S${s}E${e}`,
})
.run();
episodeIds.push(epId);
}
}
return { titleId, episodeIds };
}
export function insertMovieWatch(
userId: string,
titleId: string,
watchedAt?: Date,
) {
testDb
.insert(userMovieWatches)
.values({
userId,
titleId,
watchedAt: watchedAt ?? new Date(),
source: "manual",
})
.run();
}
export function insertEpisodeWatch(
userId: string,
episodeId: string,
watchedAt?: Date,
) {
testDb
.insert(userEpisodeWatches)
.values({
userId,
episodeId,
watchedAt: watchedAt ?? new Date(),
source: "manual",
})
.run();
}
export function insertStatus(
userId: string,
titleId: string,
status: "watchlist" | "in_progress" | "completed",
) {
const now = new Date();
testDb
.insert(userTitleStatus)
.values({ userId, titleId, status, addedAt: now, updatedAt: now })
.run();
}
export function insertRating(
userId: string,
titleId: string,
ratingStars: number,
) {
testDb
.insert(userRatings)
.values({ userId, titleId, ratingStars, ratedAt: new Date() })
.run();
}
export function insertAvailabilityOffer(
titleId: string,
overrides: {
providerId?: number;
providerName?: string;
offerType?: "flatrate" | "rent" | "buy" | "free" | "ads";
} = {},
) {
testDb
.insert(availabilityOffers)
.values({
titleId,
providerId: overrides.providerId ?? 8,
providerName: overrides.providerName ?? "Netflix",
offerType: overrides.offerType ?? "flatrate",
})
.run();
}
export function insertRecommendation(
titleId: string,
recommendedTitleId: string,
overrides: {
source?: "tmdb_recommendations" | "tmdb_similar";
rank?: number;
} = {},
) {
testDb
.insert(titleRecommendations)
.values({
titleId,
recommendedTitleId,
source: overrides.source ?? "tmdb_recommendations",
rank: overrides.rank ?? 1,
})
.run();
}