From ee411ac74f6bf5ea282212ad4d72726b67b5ae5b Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 9 Dec 2025 23:54:54 -0600 Subject: [PATCH] test(upload): add unit tests for getRegistryTag function - Introduced a new test suite for the getRegistryTag function, covering various scenarios including handling of usernames, image prefixes, and custom registry URLs. - Ensured that the function correctly constructs image tags based on different input conditions, improving test coverage and reliability. --- apps/dokploy/__test__/cluster/upload.test.ts | 209 +++++++++++++++++++ packages/server/src/utils/cluster/upload.ts | 35 +++- 2 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 apps/dokploy/__test__/cluster/upload.test.ts diff --git a/apps/dokploy/__test__/cluster/upload.test.ts b/apps/dokploy/__test__/cluster/upload.test.ts new file mode 100644 index 000000000..ca95cf663 --- /dev/null +++ b/apps/dokploy/__test__/cluster/upload.test.ts @@ -0,0 +1,209 @@ +import type { Registry } from "@dokploy/server"; +import { getRegistryTag } from "@dokploy/server"; +import { describe, expect, it } from "vitest"; + +describe("getRegistryTag", () => { + // Helper to create a mock registry + const createMockRegistry = (overrides: Partial = {}): Registry => { + return { + registryId: "test-registry-id", + registryName: "Test Registry", + username: "myuser", + password: "test-password", + registryUrl: "docker.io", + registryType: "cloud", + imagePrefix: null, + createdAt: new Date().toISOString(), + organizationId: "test-org-id", + ...overrides, + }; + }; + + describe("with username (no imagePrefix)", () => { + it("should handle simple image name without tag", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "nginx"); + expect(result).toBe("docker.io/myuser/nginx"); + }); + + it("should handle image name with tag", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "nginx:latest"); + expect(result).toBe("docker.io/myuser/nginx:latest"); + }); + + it("should handle image name with username already present (no duplication)", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "myuser/myprivaterepo"); + // Should not duplicate username + expect(result).toBe("docker.io/myuser/myprivaterepo"); + }); + + it("should handle image name with username and tag already present", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "myuser/myprivaterepo:latest"); + // Should not duplicate username + expect(result).toBe("docker.io/myuser/myprivaterepo:latest"); + }); + + it("should handle complex image name with username", () => { + const registry = createMockRegistry({ username: "siumauricio" }); + const result = getRegistryTag( + registry, + "siumauricio/app-parse-multi-byte-port-e32uh7", + ); + // Should not duplicate username + expect(result).toBe( + "docker.io/siumauricio/app-parse-multi-byte-port-e32uh7", + ); + }); + + it("should handle image name with different username (should not duplicate)", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "otheruser/myprivaterepo"); + expect(result).toBe("docker.io/myuser/myprivaterepo"); + }); + + it("should handle image name with full registry URL (no username)", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "docker.io/nginx"); + // Should add username since imageName doesn't have one + expect(result).toBe("docker.io/myuser/nginx"); + }); + + it("should handle image name with custom registry URL and username", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "ghcr.io/myuser/repo"); + // Should not duplicate username even if registry URL is different + expect(result).toBe("docker.io/myuser/repo"); + }); + + it("should handle image name with custom registry URL (different username)", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "ghcr.io/otheruser/repo"); + // Should use registry username, not the one in imageName + expect(result).toBe("docker.io/myuser/repo"); + }); + }); + + describe("with imagePrefix", () => { + it("should use imagePrefix instead of username", () => { + const registry = createMockRegistry({ + username: "myuser", + imagePrefix: "myorg", + }); + const result = getRegistryTag(registry, "nginx"); + expect(result).toBe("docker.io/myorg/nginx"); + }); + + it("should use imagePrefix with image tag", () => { + const registry = createMockRegistry({ + username: "myuser", + imagePrefix: "myorg", + }); + const result = getRegistryTag(registry, "nginx:latest"); + expect(result).toBe("docker.io/myorg/nginx:latest"); + }); + + it("should handle imagePrefix with username already in image name", () => { + const registry = createMockRegistry({ + username: "myuser", + imagePrefix: "myorg", + }); + const result = getRegistryTag(registry, "myuser/myprivaterepo"); + expect(result).toBe("docker.io/myorg/myprivaterepo"); + }); + + it("should handle imagePrefix matching image name prefix", () => { + const registry = createMockRegistry({ + username: "myuser", + imagePrefix: "myorg", + }); + const result = getRegistryTag(registry, "myorg/myprivaterepo"); + // Should not duplicate prefix + expect(result).toBe("docker.io/myorg/myprivaterepo"); + }); + }); + + describe("without registryUrl", () => { + it("should work without registryUrl", () => { + const registry = createMockRegistry({ + username: "myuser", + registryUrl: "", + }); + const result = getRegistryTag(registry, "nginx"); + expect(result).toBe("myuser/nginx"); + }); + + it("should work without registryUrl with imagePrefix", () => { + const registry = createMockRegistry({ + username: "myuser", + imagePrefix: "myorg", + registryUrl: "", + }); + const result = getRegistryTag(registry, "nginx"); + expect(result).toBe("myorg/nginx"); + }); + + it("should handle username already present without registryUrl", () => { + const registry = createMockRegistry({ + username: "myuser", + registryUrl: "", + }); + const result = getRegistryTag(registry, "myuser/myprivaterepo"); + // Should not duplicate username + expect(result).toBe("myuser/myprivaterepo"); + }); + }); + + describe("with custom registryUrl", () => { + it("should handle custom registry URL", () => { + const registry = createMockRegistry({ + username: "myuser", + registryUrl: "ghcr.io", + }); + const result = getRegistryTag(registry, "nginx"); + expect(result).toBe("ghcr.io/myuser/nginx"); + }); + + it("should handle custom registry URL with imagePrefix", () => { + const registry = createMockRegistry({ + username: "myuser", + imagePrefix: "myorg", + registryUrl: "ghcr.io", + }); + const result = getRegistryTag(registry, "nginx"); + expect(result).toBe("ghcr.io/myorg/nginx"); + }); + + it("should handle custom registry URL with username already present", () => { + const registry = createMockRegistry({ + username: "myuser", + registryUrl: "ghcr.io", + }); + const result = getRegistryTag(registry, "myuser/myprivaterepo"); + // Should not duplicate username + expect(result).toBe("ghcr.io/myuser/myprivaterepo"); + }); + }); + + describe("edge cases", () => { + it("should handle empty image name", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, ""); + expect(result).toBe("docker.io/myuser/"); + }); + + it("should handle image name with multiple slashes", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "org/suborg/repo"); + expect(result).toBe("docker.io/myuser/repo"); + }); + + it("should handle image name with username at different position", () => { + const registry = createMockRegistry({ username: "myuser" }); + const result = getRegistryTag(registry, "org/myuser/repo"); + expect(result).toBe("docker.io/myuser/repo"); + }); + }); +}); diff --git a/packages/server/src/utils/cluster/upload.ts b/packages/server/src/utils/cluster/upload.ts index f1860d5a8..e2cf4a4a4 100644 --- a/packages/server/src/utils/cluster/upload.ts +++ b/packages/server/src/utils/cluster/upload.ts @@ -74,11 +74,40 @@ export const uploadImageRemoteCommand = async ( throw error; } }; +/** + * Extract the repository name from imageName by taking the last part after '/' + * Examples: + * - "nginx" -> "nginx" + * - "nginx:latest" -> "nginx:latest" + * - "myuser/myrepo" -> "myrepo" + * - "myuser/myrepo:tag" -> "myrepo:tag" + * - "docker.io/myuser/myrepo" -> "myrepo" + */ +const extractRepositoryName = (imageName: string): string => { + const lastSlashIndex = imageName.lastIndexOf("/"); + + // If no '/', return the imageName as is + if (lastSlashIndex === -1) { + return imageName; + } + + // Extract everything after the last '/' + return imageName.substring(lastSlashIndex + 1); +}; + export const getRegistryTag = (registry: Registry, imageName: string) => { const { registryUrl, imagePrefix, username } = registry; - return imagePrefix - ? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}` - : `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`; + + // Extract the repository name (last part after '/') + const repositoryName = extractRepositoryName(imageName); + + // Build the final tag using registry's username/prefix + const targetPrefix = imagePrefix || username; + const finalRegistry = registryUrl || ""; + + return finalRegistry + ? `${finalRegistry}/${targetPrefix}/${repositoryName}` + : `${targetPrefix}/${repositoryName}`; }; const getRegistryCommands = (