mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 06:15:39 -04:00
Move test utilities from lib/ to test/ directory
- Rename `lib/test-preload.ts` → `test/preload.ts` and `lib/test-utils.ts` → `test/sqlite.ts` - Update `bunfig.toml` preload path to `./test/preload.ts` - Update all service test imports from `@/lib/test-utils` to `@/test/sqlite`
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
[test]
|
[test]
|
||||||
preload = ["./lib/test-preload.ts"]
|
preload = ["./test/preload.ts"]
|
||||||
|
|||||||
@@ -44,7 +44,23 @@ import {
|
|||||||
MAX_RECENT,
|
MAX_RECENT,
|
||||||
recentSearchesAtom,
|
recentSearchesAtom,
|
||||||
} from "@/lib/atoms/command-palette";
|
} from "@/lib/atoms/command-palette";
|
||||||
import { SHORTCUT_DESCRIPTIONS } from "@/lib/constants/shortcuts";
|
|
||||||
|
// Static shortcut descriptions for the help dialog.
|
||||||
|
// TanStack's HotkeyManager/SequenceManager handle all actual key listening.
|
||||||
|
const SHORTCUT_DESCRIPTIONS = [
|
||||||
|
{ scope: "Global", description: "Search", keys: ["/"] },
|
||||||
|
{ scope: "Global", description: "Keyboard shortcuts", keys: ["?"] },
|
||||||
|
{ scope: "Navigation", description: "Go to dashboard", keys: ["g", "h"] },
|
||||||
|
{ scope: "Navigation", description: "Go to explore", keys: ["g", "e"] },
|
||||||
|
{ scope: "Title", description: "Cycle status", keys: ["w"] },
|
||||||
|
{ scope: "Title", description: "Mark watched", keys: ["m"] },
|
||||||
|
{ scope: "Title", description: "Go back", keys: ["Escape"] },
|
||||||
|
{ scope: "Title", description: "Rate 1 star", keys: ["1"] },
|
||||||
|
{ scope: "Title", description: "Rate 2 stars", keys: ["2"] },
|
||||||
|
{ scope: "Title", description: "Rate 3 stars", keys: ["3"] },
|
||||||
|
{ scope: "Title", description: "Rate 4 stars", keys: ["4"] },
|
||||||
|
{ scope: "Title", description: "Rate 5 stars", keys: ["5"] },
|
||||||
|
] as const;
|
||||||
|
|
||||||
const groupedShortcuts: Record<
|
const groupedShortcuts: Record<
|
||||||
string,
|
string,
|
||||||
|
|||||||
@@ -1,178 +0,0 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test";
|
|
||||||
import {
|
|
||||||
getOidcProviderName,
|
|
||||||
isOidcAutoRegisterEnabled,
|
|
||||||
isOidcConfigured,
|
|
||||||
isPasswordLoginDisabled,
|
|
||||||
isTmdbConfigured,
|
|
||||||
} from "./config";
|
|
||||||
|
|
||||||
describe("isTmdbConfigured", () => {
|
|
||||||
const orig = process.env.TMDB_API_READ_ACCESS_TOKEN;
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (orig !== undefined) {
|
|
||||||
process.env.TMDB_API_READ_ACCESS_TOKEN = orig;
|
|
||||||
} else {
|
|
||||||
delete process.env.TMDB_API_READ_ACCESS_TOKEN;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns true when token is set", () => {
|
|
||||||
process.env.TMDB_API_READ_ACCESS_TOKEN = "some-token";
|
|
||||||
expect(isTmdbConfigured()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns false when token is unset", () => {
|
|
||||||
delete process.env.TMDB_API_READ_ACCESS_TOKEN;
|
|
||||||
expect(isTmdbConfigured()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns false when token is empty string", () => {
|
|
||||||
process.env.TMDB_API_READ_ACCESS_TOKEN = "";
|
|
||||||
expect(isTmdbConfigured()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("isOidcConfigured", () => {
|
|
||||||
const origId = process.env.OIDC_CLIENT_ID;
|
|
||||||
const origSecret = process.env.OIDC_CLIENT_SECRET;
|
|
||||||
const origIssuer = process.env.OIDC_ISSUER_URL;
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
for (const [key, val] of [
|
|
||||||
["OIDC_CLIENT_ID", origId],
|
|
||||||
["OIDC_CLIENT_SECRET", origSecret],
|
|
||||||
["OIDC_ISSUER_URL", origIssuer],
|
|
||||||
] as const) {
|
|
||||||
if (val !== undefined) {
|
|
||||||
process.env[key] = val;
|
|
||||||
} else {
|
|
||||||
delete process.env[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns true when all three vars are set", () => {
|
|
||||||
process.env.OIDC_CLIENT_ID = "id";
|
|
||||||
process.env.OIDC_CLIENT_SECRET = "secret";
|
|
||||||
process.env.OIDC_ISSUER_URL = "https://issuer.example.com";
|
|
||||||
expect(isOidcConfigured()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns false when client ID is missing", () => {
|
|
||||||
delete process.env.OIDC_CLIENT_ID;
|
|
||||||
process.env.OIDC_CLIENT_SECRET = "secret";
|
|
||||||
process.env.OIDC_ISSUER_URL = "https://issuer.example.com";
|
|
||||||
expect(isOidcConfigured()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns false when client secret is missing", () => {
|
|
||||||
process.env.OIDC_CLIENT_ID = "id";
|
|
||||||
delete process.env.OIDC_CLIENT_SECRET;
|
|
||||||
process.env.OIDC_ISSUER_URL = "https://issuer.example.com";
|
|
||||||
expect(isOidcConfigured()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns false when issuer URL is missing", () => {
|
|
||||||
process.env.OIDC_CLIENT_ID = "id";
|
|
||||||
process.env.OIDC_CLIENT_SECRET = "secret";
|
|
||||||
delete process.env.OIDC_ISSUER_URL;
|
|
||||||
expect(isOidcConfigured()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getOidcProviderName", () => {
|
|
||||||
const orig = process.env.OIDC_PROVIDER_NAME;
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (orig !== undefined) {
|
|
||||||
process.env.OIDC_PROVIDER_NAME = orig;
|
|
||||||
} else {
|
|
||||||
delete process.env.OIDC_PROVIDER_NAME;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns custom name when set", () => {
|
|
||||||
process.env.OIDC_PROVIDER_NAME = "Okta";
|
|
||||||
expect(getOidcProviderName()).toBe("Okta");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns 'SSO' as default", () => {
|
|
||||||
delete process.env.OIDC_PROVIDER_NAME;
|
|
||||||
expect(getOidcProviderName()).toBe("SSO");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("isOidcAutoRegisterEnabled", () => {
|
|
||||||
const orig = process.env.OIDC_AUTO_REGISTER;
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (orig !== undefined) {
|
|
||||||
process.env.OIDC_AUTO_REGISTER = orig;
|
|
||||||
} else {
|
|
||||||
delete process.env.OIDC_AUTO_REGISTER;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns true by default", () => {
|
|
||||||
delete process.env.OIDC_AUTO_REGISTER;
|
|
||||||
expect(isOidcAutoRegisterEnabled()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns false when explicitly set to 'false'", () => {
|
|
||||||
process.env.OIDC_AUTO_REGISTER = "false";
|
|
||||||
expect(isOidcAutoRegisterEnabled()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns true for any other value", () => {
|
|
||||||
process.env.OIDC_AUTO_REGISTER = "true";
|
|
||||||
expect(isOidcAutoRegisterEnabled()).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("isPasswordLoginDisabled", () => {
|
|
||||||
const origDisable = process.env.DISABLE_PASSWORD_LOGIN;
|
|
||||||
const origId = process.env.OIDC_CLIENT_ID;
|
|
||||||
const origSecret = process.env.OIDC_CLIENT_SECRET;
|
|
||||||
const origIssuer = process.env.OIDC_ISSUER_URL;
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
for (const [key, val] of [
|
|
||||||
["DISABLE_PASSWORD_LOGIN", origDisable],
|
|
||||||
["OIDC_CLIENT_ID", origId],
|
|
||||||
["OIDC_CLIENT_SECRET", origSecret],
|
|
||||||
["OIDC_ISSUER_URL", origIssuer],
|
|
||||||
] as const) {
|
|
||||||
if (val !== undefined) {
|
|
||||||
process.env[key] = val;
|
|
||||||
} else {
|
|
||||||
delete process.env[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns true when flag is 'true' and OIDC is configured", () => {
|
|
||||||
process.env.DISABLE_PASSWORD_LOGIN = "true";
|
|
||||||
process.env.OIDC_CLIENT_ID = "id";
|
|
||||||
process.env.OIDC_CLIENT_SECRET = "secret";
|
|
||||||
process.env.OIDC_ISSUER_URL = "https://issuer.example.com";
|
|
||||||
expect(isPasswordLoginDisabled()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns false when flag is 'true' but OIDC is not configured", () => {
|
|
||||||
process.env.DISABLE_PASSWORD_LOGIN = "true";
|
|
||||||
delete process.env.OIDC_CLIENT_ID;
|
|
||||||
delete process.env.OIDC_CLIENT_SECRET;
|
|
||||||
delete process.env.OIDC_ISSUER_URL;
|
|
||||||
expect(isPasswordLoginDisabled()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns false when flag is not set", () => {
|
|
||||||
delete process.env.DISABLE_PASSWORD_LOGIN;
|
|
||||||
process.env.OIDC_CLIENT_ID = "id";
|
|
||||||
process.env.OIDC_CLIENT_SECRET = "secret";
|
|
||||||
process.env.OIDC_ISSUER_URL = "https://issuer.example.com";
|
|
||||||
expect(isPasswordLoginDisabled()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// Static shortcut descriptions for the help dialog.
|
|
||||||
// TanStack's HotkeyManager/SequenceManager handle all actual key listening.
|
|
||||||
export const SHORTCUT_DESCRIPTIONS = [
|
|
||||||
{ scope: "Global", description: "Search", keys: ["/"] },
|
|
||||||
{ scope: "Global", description: "Keyboard shortcuts", keys: ["?"] },
|
|
||||||
{ scope: "Navigation", description: "Go to dashboard", keys: ["g", "h"] },
|
|
||||||
{ scope: "Navigation", description: "Go to explore", keys: ["g", "e"] },
|
|
||||||
{ scope: "Title", description: "Cycle status", keys: ["w"] },
|
|
||||||
{ scope: "Title", description: "Mark watched", keys: ["m"] },
|
|
||||||
{ scope: "Title", description: "Go back", keys: ["Escape"] },
|
|
||||||
{ scope: "Title", description: "Rate 1 star", keys: ["1"] },
|
|
||||||
{ scope: "Title", description: "Rate 2 stars", keys: ["2"] },
|
|
||||||
{ scope: "Title", description: "Rate 3 stars", keys: ["3"] },
|
|
||||||
{ scope: "Title", description: "Rate 4 stars", keys: ["4"] },
|
|
||||||
{ scope: "Title", description: "Rate 5 stars", keys: ["5"] },
|
|
||||||
] as const;
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { describe, expect, mock, test } from "bun:test";
|
|
||||||
|
|
||||||
mock.module("@/lib/services/availability", () => ({
|
|
||||||
refreshAvailability: async () => {},
|
|
||||||
}));
|
|
||||||
mock.module("@/lib/services/backup", () => ({
|
|
||||||
createBackup: async () => ({}),
|
|
||||||
ensureBackupDir: async () => {},
|
|
||||||
pruneBackups: async () => {},
|
|
||||||
}));
|
|
||||||
mock.module("@/lib/services/credits", () => ({
|
|
||||||
refreshCredits: async () => {},
|
|
||||||
}));
|
|
||||||
mock.module("@/lib/services/image-cache", () => ({
|
|
||||||
cacheEpisodeStills: async () => {},
|
|
||||||
cacheImagesForTitle: async () => {},
|
|
||||||
cacheProfilePhotos: async () => {},
|
|
||||||
cacheProviderLogos: async () => {},
|
|
||||||
imageCacheEnabled: () => false,
|
|
||||||
}));
|
|
||||||
mock.module("@/lib/services/metadata", () => ({
|
|
||||||
refreshRecommendations: async () => {},
|
|
||||||
refreshTitle: async () => {},
|
|
||||||
refreshTvChildren: async () => {},
|
|
||||||
}));
|
|
||||||
mock.module("@/lib/services/update-check", () => ({
|
|
||||||
performUpdateCheck: async () => ({}),
|
|
||||||
}));
|
|
||||||
mock.module("@/lib/tmdb/client", () => ({
|
|
||||||
getTvDetails: async () => ({}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
import { buildBackupCron } from "./cron";
|
|
||||||
|
|
||||||
describe("buildBackupCron", () => {
|
|
||||||
test("6h frequency", () => {
|
|
||||||
expect(buildBackupCron("6h", "02:00")).toBe("0 */6 * * *");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("12h frequency", () => {
|
|
||||||
expect(buildBackupCron("12h", "02:00")).toBe("0 2,14 * * *");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("1d frequency (default)", () => {
|
|
||||||
expect(buildBackupCron("1d", "03:30")).toBe("30 3 * * *");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("7d frequency with day of week", () => {
|
|
||||||
expect(buildBackupCron("7d", "04:15", 3)).toBe("15 4 * * 3");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("7d frequency defaults to Sunday (0)", () => {
|
|
||||||
expect(buildBackupCron("7d", "02:00")).toBe("0 2 * * 0");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("defaults to 1d when called with no args", () => {
|
|
||||||
expect(buildBackupCron()).toBe("0 2 * * *");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("handles invalid time gracefully", () => {
|
|
||||||
const result = buildBackupCron("1d", "invalid");
|
|
||||||
expect(result).toBe("0 2 * * *");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("6h ignores hour from time, uses minute only", () => {
|
|
||||||
expect(buildBackupCron("6h", "14:45")).toBe("45 */6 * * *");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("12h wraps hour correctly", () => {
|
|
||||||
expect(buildBackupCron("12h", "18:00")).toBe("0 18,6 * * *");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
|
||||||
import { generateProviderUrl } from "./providers";
|
|
||||||
|
|
||||||
describe("generateProviderUrl", () => {
|
|
||||||
test("generates Netflix URL", () => {
|
|
||||||
expect(generateProviderUrl(8, "Inception")).toBe(
|
|
||||||
"https://www.netflix.com/search?q=Inception",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("generates Amazon Prime Video URL", () => {
|
|
||||||
expect(generateProviderUrl(9, "The Matrix")).toBe(
|
|
||||||
"https://www.amazon.com/s?i=instant-video&k=The%20Matrix",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("generates Disney+ URL", () => {
|
|
||||||
expect(generateProviderUrl(337, "Frozen")).toBe(
|
|
||||||
"https://www.disneyplus.com/search/Frozen",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("URL-encodes spaces", () => {
|
|
||||||
expect(generateProviderUrl(8, "The Dark Knight")).toBe(
|
|
||||||
"https://www.netflix.com/search?q=The%20Dark%20Knight",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("URL-encodes special characters", () => {
|
|
||||||
expect(generateProviderUrl(8, "Tom & Jerry")).toBe(
|
|
||||||
"https://www.netflix.com/search?q=Tom%20%26%20Jerry",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("URL-encodes unicode", () => {
|
|
||||||
const url = generateProviderUrl(8, "Amelie");
|
|
||||||
expect(url).toContain("Amelie");
|
|
||||||
expect(url).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns null for unknown provider ID", () => {
|
|
||||||
expect(generateProviderUrl(99999, "Test")).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("generates Hulu URL", () => {
|
|
||||||
expect(generateProviderUrl(15, "Test")).toBe(
|
|
||||||
"https://www.hulu.com/search?q=Test",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
||||||
import { persons, titleCast } from "@/lib/db/schema";
|
import { persons, titleCast } from "@/lib/db/schema";
|
||||||
import { clearAllTables, insertTitle, testDb } from "@/lib/test-utils";
|
import { clearAllTables, insertTitle, testDb } from "@/test/sqlite";
|
||||||
|
|
||||||
mock.module("./image-cache", () => ({
|
mock.module("./image-cache", () => ({
|
||||||
imageCacheEnabled: () => false,
|
imageCacheEnabled: () => false,
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import {
|
|||||||
insertTitle,
|
insertTitle,
|
||||||
insertTvShow,
|
insertTvShow,
|
||||||
insertUser,
|
insertUser,
|
||||||
} from "@/lib/test-utils";
|
} from "@/test/sqlite";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getContinueWatchingFeed,
|
getContinueWatchingFeed,
|
||||||
getNewAvailableFeed,
|
getNewAvailableFeed,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
insertStatus,
|
insertStatus,
|
||||||
insertTitle,
|
insertTitle,
|
||||||
insertUser,
|
insertUser,
|
||||||
} from "@/lib/test-utils";
|
} from "@/test/sqlite";
|
||||||
import {
|
import {
|
||||||
getRadarrList,
|
getRadarrList,
|
||||||
getSonarrList,
|
getSonarrList,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, test } from "bun:test";
|
import { beforeEach, describe, expect, test } from "bun:test";
|
||||||
import { persons, titleCast } from "@/lib/db/schema";
|
import { persons, titleCast } from "@/lib/db/schema";
|
||||||
import { clearAllTables, insertTitle, testDb } from "@/lib/test-utils";
|
import { clearAllTables, insertTitle, testDb } from "@/test/sqlite";
|
||||||
|
|
||||||
import { getLocalFilmography } from "./person";
|
import { getLocalFilmography } from "./person";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { beforeEach, describe, expect, test } from "bun:test";
|
import { beforeEach, describe, expect, test } from "bun:test";
|
||||||
import { clearAllTables, insertUser } from "@/lib/test-utils";
|
import { clearAllTables, insertUser } from "@/test/sqlite";
|
||||||
import {
|
import {
|
||||||
getSetting,
|
getSetting,
|
||||||
getUserCount,
|
getUserCount,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
insertTvShow,
|
insertTvShow,
|
||||||
insertUser,
|
insertUser,
|
||||||
testDb,
|
testDb,
|
||||||
} from "@/lib/test-utils";
|
} from "@/test/sqlite";
|
||||||
import {
|
import {
|
||||||
getUserTitleInfo,
|
getUserTitleInfo,
|
||||||
logEpisodeWatch,
|
logEpisodeWatch,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { mock } from "bun:test";
|
import { mock } from "bun:test";
|
||||||
import { applyMigrations, testDb } from "@/lib/test-utils";
|
import { applyMigrations, testDb } from "./sqlite";
|
||||||
|
|
||||||
mock.module("@/lib/db/client", () => ({
|
mock.module("@/lib/db/client", () => ({
|
||||||
db: testDb,
|
db: testDb,
|
||||||
Reference in New Issue
Block a user