126 lines
5.3 KiB
TypeScript
126 lines
5.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { APPS, bootstrapApplicationUrl, configuredApp, settingsPayload, settingsValues } from "./setup-model";
|
|
|
|
describe("first administrator application URL confirmation", () => {
|
|
it.each([
|
|
["https://magent.example.com", "https://magent.example.com"],
|
|
[" https://MAGENT.example.com:443/ ", "https://magent.example.com"],
|
|
["http://192.0.2.10:3000", "http://192.0.2.10:3000"],
|
|
["http://[fd00::10]:3000/", "http://[fd00::10]:3000"],
|
|
])("confirms a canonical same-origin address %s", (value, browserOrigin) => {
|
|
expect(bootstrapApplicationUrl(value, browserOrigin)).toBe(browserOrigin);
|
|
});
|
|
|
|
it.each([
|
|
"",
|
|
"magent.example.com",
|
|
"//magent.example.com",
|
|
"https:/magent.example.com",
|
|
"ftp://magent.example.com",
|
|
"javascript:alert(1)",
|
|
"https://user:password@magent.example.com",
|
|
"https://magent.example.com/setup",
|
|
"https://magent.example.com/../",
|
|
"https://magent.example.com?query=1",
|
|
"https://magent.example.com?",
|
|
"https://magent.example.com#fragment",
|
|
"https://magent.example.com#",
|
|
"https://magent.example.com\\path",
|
|
"https://magent.\texample.com",
|
|
])("rejects a non-origin or unsafe URL %j", (value) => {
|
|
expect(() => bootstrapApplicationUrl(value, "https://magent.example.com")).toThrow("Public Magent URL must");
|
|
});
|
|
|
|
it.each(["https://other.example.com", "http://magent.example.com", "https://magent.example.com:8443"])(
|
|
"requires the intended browser origin before claiming %s",
|
|
(value) => {
|
|
expect(() => bootstrapApplicationUrl(value, "https://magent.example.com")).toThrow(
|
|
"Open Magent at your intended address",
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("installation settings", () => {
|
|
it("offers every supported media integration", () => {
|
|
expect(APPS.map((app) => app.id).sort()).toEqual([
|
|
"bazarr",
|
|
"jellyfin",
|
|
"jellystat",
|
|
"prowlarr",
|
|
"qbittorrent",
|
|
"radarr",
|
|
"seerr",
|
|
"sonarr",
|
|
]);
|
|
});
|
|
it("never copies saved secrets into the form or overwrites them with a blank", () => {
|
|
expect(settingsValues([{ key: "sonarr_api_key", value: "secret", sensitive: true, isSet: true }])).toEqual({
|
|
sonarr_api_key: "",
|
|
});
|
|
expect(settingsPayload({ sonarr_api_key: "", sonarr_base_url: "http://sonarr:8989" })).toEqual({
|
|
sonarr_base_url: "http://sonarr:8989",
|
|
});
|
|
});
|
|
it("sends only editable fields and validates numeric settings", () => {
|
|
expect(
|
|
settingsPayload({ jwt_secret: "no", requests_cleanup_days: "90", site_login_show_signup_link: false }),
|
|
).toEqual({ requests_cleanup_days: 90, site_login_show_signup_link: false });
|
|
expect(() => settingsPayload({ requests_cleanup_days: "-1" })).toThrow("whole number");
|
|
expect(() => settingsPayload({ sonarr_quality_profile_id: "1.5" })).toThrow("whole number");
|
|
});
|
|
it("can save just one app without accidentally saving another draft", () => {
|
|
expect(
|
|
settingsPayload(
|
|
{ sonarr_base_url: "http://sonarr:8989", radarr_api_key: "draft-secret" },
|
|
APPS.find((app) => app.id === "sonarr")?.fields,
|
|
),
|
|
).toEqual({ sonarr_base_url: "http://sonarr:8989" });
|
|
});
|
|
it("validates URL drafts even when app testing bypasses browser form validation", () => {
|
|
for (const value of [
|
|
"sonarr:8989",
|
|
"/sonarr",
|
|
"ftp://sonarr:8989",
|
|
"javascript:alert(1)",
|
|
"http://sonarr/my library",
|
|
]) {
|
|
expect(() => settingsPayload({ sonarr_base_url: value })).toThrow("HTTP or HTTPS URL");
|
|
}
|
|
expect(() => settingsPayload({ sonarr_base_url: "https://user:secret@sonarr.test" })).toThrow("credential fields");
|
|
expect(
|
|
settingsPayload({
|
|
sonarr_base_url: " http://sonarr:8989 ",
|
|
magent_application_url: "https://magent.example.test",
|
|
}),
|
|
).toEqual({ sonarr_base_url: "http://sonarr:8989", magent_application_url: "https://magent.example.test" });
|
|
expect(settingsPayload({ sonarr_base_url: "" })).toEqual({ sonarr_base_url: "" });
|
|
});
|
|
it("validates sender email and sync time before step navigation saves", () => {
|
|
for (const value of ["not-an-email", "two@@example.test", "name@example test", "Name <name@example.test>"]) {
|
|
expect(() => settingsPayload({ magent_notify_email_from_address: value })).toThrow("valid email address");
|
|
}
|
|
for (const value of ["24:00", "12:60", "2:30", "02:30:00"]) {
|
|
expect(() => settingsPayload({ requests_full_sync_time: value })).toThrow("HH:MM");
|
|
}
|
|
expect(
|
|
settingsPayload({
|
|
magent_notify_email_from_address: " alerts+admin@example.test ",
|
|
requests_full_sync_time: "23:59",
|
|
}),
|
|
).toEqual({ magent_notify_email_from_address: "alerts+admin@example.test", requests_full_sync_time: "23:59" });
|
|
expect(settingsPayload({ magent_notify_email_from_address: "", requests_full_sync_time: "" })).toEqual({
|
|
magent_notify_email_from_address: "",
|
|
requests_full_sync_time: "",
|
|
});
|
|
});
|
|
it("does not call a URL-only app configured", () => {
|
|
const app = APPS[0];
|
|
const url = { key: "jellyfin_base_url", value: "http://jellyfin:8096", sensitive: false, isSet: true };
|
|
expect(configuredApp(app, [url])).toBe(false);
|
|
expect(configuredApp(app, [url, { key: "jellyfin_api_key", value: null, sensitive: true, isSet: true }])).toBe(
|
|
true,
|
|
);
|
|
});
|
|
});
|