diff --git a/app/server/utils/restic.test.ts b/app/server/utils/restic.test.ts new file mode 100644 index 00000000..afb1364c --- /dev/null +++ b/app/server/utils/restic.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, test } from "bun:test"; +import { buildRepoUrl } from "./restic"; + +describe("buildRepoUrl", () => { + describe("S3 backend", () => { + test("should build URL without trailing slash", () => { + const config = { + backend: "s3" as const, + endpoint: "https://s3.amazonaws.com", + bucket: "my-bucket", + accessKeyId: "test", + secretAccessKey: "test", + }; + expect(buildRepoUrl(config)).toBe("s3:https://s3.amazonaws.com/my-bucket"); + }); + + test("should trim trailing slash from endpoint", () => { + const config = { + backend: "s3" as const, + endpoint: "https://s3.xxxxxxxxx.net/", + bucket: "backup", + accessKeyId: "test", + secretAccessKey: "test", + }; + expect(buildRepoUrl(config)).toBe("s3:https://s3.xxxxxxxxx.net/backup"); + }); + + test("should trim trailing whitespace from endpoint", () => { + const config = { + backend: "s3" as const, + endpoint: "https://s3.amazonaws.com/ ", + bucket: "my-bucket", + accessKeyId: "test", + secretAccessKey: "test", + }; + expect(buildRepoUrl(config)).toBe("s3:https://s3.amazonaws.com/my-bucket"); + }); + + test("should trim leading and trailing whitespace from endpoint", () => { + const config = { + backend: "s3" as const, + endpoint: " https://s3.amazonaws.com/ ", + bucket: "my-bucket", + accessKeyId: "test", + secretAccessKey: "test", + }; + expect(buildRepoUrl(config)).toBe("s3:https://s3.amazonaws.com/my-bucket"); + }); + }); + + describe("R2 backend", () => { + test("should build URL without trailing slash", () => { + const config = { + backend: "r2" as const, + endpoint: "https://myaccount.r2.cloudflarestorage.com", + bucket: "my-bucket", + accessKeyId: "test", + secretAccessKey: "test", + }; + expect(buildRepoUrl(config)).toBe("s3:myaccount.r2.cloudflarestorage.com/my-bucket"); + }); + + test("should trim trailing slash from endpoint", () => { + const config = { + backend: "r2" as const, + endpoint: "https://myaccount.r2.cloudflarestorage.com/", + bucket: "backup", + accessKeyId: "test", + secretAccessKey: "test", + }; + expect(buildRepoUrl(config)).toBe("s3:myaccount.r2.cloudflarestorage.com/backup"); + }); + + test("should strip protocol and trailing slash", () => { + const config = { + backend: "r2" as const, + endpoint: "https://myaccount.r2.cloudflarestorage.com/", + bucket: "my-bucket", + accessKeyId: "test", + secretAccessKey: "test", + }; + expect(buildRepoUrl(config)).toBe("s3:myaccount.r2.cloudflarestorage.com/my-bucket"); + }); + + test("should trim whitespace and strip protocol", () => { + const config = { + backend: "r2" as const, + endpoint: " https://myaccount.r2.cloudflarestorage.com/ ", + bucket: "my-bucket", + accessKeyId: "test", + secretAccessKey: "test", + }; + expect(buildRepoUrl(config)).toBe("s3:myaccount.r2.cloudflarestorage.com/my-bucket"); + }); + }); + + describe("other backends", () => { + test("should build local repository URL", () => { + const config = { + backend: "local" as const, + path: "/path/to/repo", + }; + expect(buildRepoUrl(config)).toBe("/path/to/repo"); + }); + }); +}); diff --git a/app/server/utils/restic.ts b/app/server/utils/restic.ts index 5299b1a1..16a0de0f 100644 --- a/app/server/utils/restic.ts +++ b/app/server/utils/restic.ts @@ -43,10 +43,12 @@ export const buildRepoUrl = (config: RepositoryConfig): string => { switch (config.backend) { case "local": return config.path; - case "s3": - return `s3:${config.endpoint}/${config.bucket}`; + case "s3": { + const endpoint = config.endpoint.trim().replace(/\/$/, ""); + return `s3:${endpoint}/${config.bucket}`; + } case "r2": { - const endpoint = config.endpoint.replace(/^https?:\/\//, ""); + const endpoint = config.endpoint.trim().replace(/^https?:\/\//, "").replace(/\/$/, ""); return `s3:${endpoint}/${config.bucket}`; } case "gcs":