mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 03:55:38 -04:00
- 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
311 lines
7.7 KiB
TypeScript
311 lines
7.7 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
parseEmbyPayload,
|
|
parseJellyfinPayload,
|
|
parsePlexPayload,
|
|
toOptionalInt,
|
|
} from "./webhooks";
|
|
|
|
describe("toOptionalInt", () => {
|
|
test("returns integer number as-is", () => {
|
|
expect(toOptionalInt(42)).toBe(42);
|
|
});
|
|
|
|
test("returns 0 for zero", () => {
|
|
expect(toOptionalInt(0)).toBe(0);
|
|
});
|
|
|
|
test("returns undefined for float", () => {
|
|
expect(toOptionalInt(3.14)).toBeUndefined();
|
|
});
|
|
|
|
test("parses valid integer string", () => {
|
|
expect(toOptionalInt("7")).toBe(7);
|
|
});
|
|
|
|
test("parses string with whitespace", () => {
|
|
expect(toOptionalInt(" 12 ")).toBe(12);
|
|
});
|
|
|
|
test("returns undefined for non-numeric string", () => {
|
|
expect(toOptionalInt("abc")).toBeUndefined();
|
|
});
|
|
|
|
test("returns undefined for empty string", () => {
|
|
expect(toOptionalInt("")).toBeUndefined();
|
|
});
|
|
|
|
test("returns undefined for null", () => {
|
|
expect(toOptionalInt(null)).toBeUndefined();
|
|
});
|
|
|
|
test("returns undefined for undefined", () => {
|
|
expect(toOptionalInt(undefined)).toBeUndefined();
|
|
});
|
|
|
|
test("returns undefined for boolean", () => {
|
|
expect(toOptionalInt(true)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("parsePlexPayload", () => {
|
|
function makeFormData(payload: unknown): FormData {
|
|
const fd = new FormData();
|
|
fd.set("payload", JSON.stringify(payload));
|
|
return fd;
|
|
}
|
|
|
|
test("returns null for missing payload field", () => {
|
|
expect(parsePlexPayload(new FormData())).toBeNull();
|
|
});
|
|
|
|
test("returns null for invalid JSON", () => {
|
|
const fd = new FormData();
|
|
fd.set("payload", "not json{");
|
|
expect(parsePlexPayload(fd)).toBeNull();
|
|
});
|
|
|
|
test("returns null for wrong event type", () => {
|
|
expect(parsePlexPayload(makeFormData({ event: "media.play" }))).toBeNull();
|
|
});
|
|
|
|
test("returns null when Metadata is missing", () => {
|
|
expect(
|
|
parsePlexPayload(makeFormData({ event: "media.scrobble" })),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("returns null for unsupported media type", () => {
|
|
expect(
|
|
parsePlexPayload(
|
|
makeFormData({
|
|
event: "media.scrobble",
|
|
Metadata: { type: "track" },
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("parses movie scrobble", () => {
|
|
const result = parsePlexPayload(
|
|
makeFormData({
|
|
event: "media.scrobble",
|
|
Metadata: {
|
|
type: "movie",
|
|
title: "Inception",
|
|
Guid: [{ id: "tmdb://27205" }, { id: "imdb://tt1375666" }],
|
|
},
|
|
}),
|
|
);
|
|
expect(result).toEqual({
|
|
provider: "plex",
|
|
mediaType: "movie",
|
|
title: "Inception",
|
|
tmdbId: 27205,
|
|
imdbId: "tt1375666",
|
|
tvdbId: undefined,
|
|
seasonNumber: undefined,
|
|
episodeNumber: undefined,
|
|
showTitle: undefined,
|
|
});
|
|
});
|
|
|
|
test("parses episode scrobble", () => {
|
|
const result = parsePlexPayload(
|
|
makeFormData({
|
|
event: "media.scrobble",
|
|
Metadata: {
|
|
type: "episode",
|
|
title: "Pilot",
|
|
grandparentTitle: "Breaking Bad",
|
|
parentIndex: 1,
|
|
index: 1,
|
|
Guid: [{ id: "tvdb://349232" }],
|
|
},
|
|
}),
|
|
);
|
|
expect(result).toEqual({
|
|
provider: "plex",
|
|
mediaType: "episode",
|
|
title: "Pilot",
|
|
tmdbId: undefined,
|
|
imdbId: undefined,
|
|
tvdbId: "349232",
|
|
seasonNumber: 1,
|
|
episodeNumber: 1,
|
|
showTitle: "Breaking Bad",
|
|
});
|
|
});
|
|
|
|
test("extracts GUIDs from lowercase guid field", () => {
|
|
const result = parsePlexPayload(
|
|
makeFormData({
|
|
event: "media.scrobble",
|
|
Metadata: {
|
|
type: "movie",
|
|
title: "Test",
|
|
guid: [{ id: "tmdb://12345" }],
|
|
},
|
|
}),
|
|
);
|
|
expect(result?.tmdbId).toBe(12345);
|
|
});
|
|
});
|
|
|
|
describe("parseJellyfinPayload", () => {
|
|
test("returns null for wrong NotificationType", () => {
|
|
expect(
|
|
parseJellyfinPayload({ NotificationType: "PlaybackStart" }),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("returns null when PlayedToCompletion is false", () => {
|
|
expect(
|
|
parseJellyfinPayload({
|
|
NotificationType: "PlaybackStop",
|
|
PlayedToCompletion: false,
|
|
ItemType: "Movie",
|
|
}),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("returns null for unsupported ItemType", () => {
|
|
expect(
|
|
parseJellyfinPayload({
|
|
NotificationType: "PlaybackStop",
|
|
PlayedToCompletion: true,
|
|
ItemType: "Audio",
|
|
}),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("parses movie payload", () => {
|
|
const result = parseJellyfinPayload({
|
|
NotificationType: "PlaybackStop",
|
|
PlayedToCompletion: true,
|
|
ItemType: "Movie",
|
|
Name: "The Matrix",
|
|
Provider_tmdb: "603",
|
|
Provider_imdb: "tt0133093",
|
|
});
|
|
expect(result).toEqual({
|
|
provider: "jellyfin",
|
|
mediaType: "movie",
|
|
title: "The Matrix",
|
|
tmdbId: 603,
|
|
imdbId: "tt0133093",
|
|
tvdbId: undefined,
|
|
seasonNumber: undefined,
|
|
episodeNumber: undefined,
|
|
showTitle: undefined,
|
|
});
|
|
});
|
|
|
|
test("parses episode payload", () => {
|
|
const result = parseJellyfinPayload({
|
|
NotificationType: "PlaybackStop",
|
|
PlayedToCompletion: true,
|
|
ItemType: "Episode",
|
|
Name: "Ozymandias",
|
|
Provider_tmdb: "62161",
|
|
SeasonNumber: 5,
|
|
EpisodeNumber: 14,
|
|
SeriesName: "Breaking Bad",
|
|
});
|
|
expect(result).toEqual({
|
|
provider: "jellyfin",
|
|
mediaType: "episode",
|
|
title: "Ozymandias",
|
|
tmdbId: 62161,
|
|
imdbId: undefined,
|
|
tvdbId: undefined,
|
|
seasonNumber: 5,
|
|
episodeNumber: 14,
|
|
showTitle: "Breaking Bad",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("parseEmbyPayload", () => {
|
|
test("returns null for wrong event type", () => {
|
|
expect(
|
|
parseEmbyPayload({ Event: "playback.start", PlayedToCompletion: true }),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("returns null when PlayedToCompletion is false", () => {
|
|
expect(
|
|
parseEmbyPayload({ Event: "playback.stop", PlayedToCompletion: false }),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("returns null when Item is missing", () => {
|
|
expect(
|
|
parseEmbyPayload({ Event: "playback.stop", PlayedToCompletion: true }),
|
|
).toBeNull();
|
|
});
|
|
|
|
test("accepts 'playback.stop' event string", () => {
|
|
const result = parseEmbyPayload({
|
|
Event: "playback.stop",
|
|
PlayedToCompletion: true,
|
|
Item: {
|
|
Type: "Movie",
|
|
Name: "Interstellar",
|
|
ProviderIds: { Tmdb: "157336" },
|
|
},
|
|
});
|
|
expect(result?.provider).toBe("emby");
|
|
expect(result?.tmdbId).toBe(157336);
|
|
});
|
|
|
|
test("accepts 'PlaybackStop' event string", () => {
|
|
const result = parseEmbyPayload({
|
|
Event: "PlaybackStop",
|
|
PlayedToCompletion: true,
|
|
Item: {
|
|
Type: "Movie",
|
|
Name: "Interstellar",
|
|
ProviderIds: { Tmdb: "157336" },
|
|
},
|
|
});
|
|
expect(result?.provider).toBe("emby");
|
|
});
|
|
|
|
test("parses episode with ProviderIds", () => {
|
|
const result = parseEmbyPayload({
|
|
Event: "playback.stop",
|
|
PlayedToCompletion: true,
|
|
Item: {
|
|
Type: "Episode",
|
|
Name: "Pilot",
|
|
ParentIndexNumber: 1,
|
|
IndexNumber: 1,
|
|
SeriesName: "Lost",
|
|
ProviderIds: { Tmdb: "12345", Imdb: "tt0000001", Tvdb: "67890" },
|
|
},
|
|
});
|
|
expect(result).toEqual({
|
|
provider: "emby",
|
|
mediaType: "episode",
|
|
title: "Pilot",
|
|
tmdbId: 12345,
|
|
imdbId: "tt0000001",
|
|
tvdbId: "67890",
|
|
seasonNumber: 1,
|
|
episodeNumber: 1,
|
|
showTitle: "Lost",
|
|
});
|
|
});
|
|
|
|
test("handles missing ProviderIds gracefully", () => {
|
|
const result = parseEmbyPayload({
|
|
Event: "playback.stop",
|
|
PlayedToCompletion: true,
|
|
Item: { Type: "Movie", Name: "Unknown Movie" },
|
|
});
|
|
expect(result?.tmdbId).toBeUndefined();
|
|
expect(result?.imdbId).toBeUndefined();
|
|
});
|
|
});
|