feat(release): publish minimal self-contained Magent source
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user