diff --git a/app/backend/internal/api/deployments.go b/app/backend/internal/api/deployments.go index b95df89..37a4fac 100644 --- a/app/backend/internal/api/deployments.go +++ b/app/backend/internal/api/deployments.go @@ -803,9 +803,15 @@ func splitImageReference(image, fallbackTag string) (string, string) { return "", fallbackTag } + // Digest-pinned refs (name[:tag]@sha256:…) carry no usable tag — the + // colon lives inside the digest and must not be split. + if strings.Contains(image, "@") { + return image, fallbackTag + } + lastSlash := strings.LastIndex(image, "/") lastColon := strings.LastIndex(image, ":") - if lastColon > lastSlash && !strings.Contains(image[lastColon:], "@") { + if lastColon > lastSlash { return image[:lastColon], image[lastColon+1:] } diff --git a/app/backend/internal/api/deployments_test.go b/app/backend/internal/api/deployments_test.go new file mode 100644 index 0000000..a3f21f8 --- /dev/null +++ b/app/backend/internal/api/deployments_test.go @@ -0,0 +1,46 @@ +package api + +import "testing" + +func TestSplitImageReference(t *testing.T) { + cases := []struct { + name string + image string + fallback string + wantImage string + wantTag string + }{ + {"empty uses fallback tag", "", "latest", "", "latest"}, + {"bare image", "nginx", "latest", "nginx", "latest"}, + {"tagged image", "nginx:1.27", "latest", "nginx", "1.27"}, + {"registry with port untagged", "registry.local:5000/app", "latest", "registry.local:5000/app", "latest"}, + {"registry with port tagged", "registry.local:5000/app:v2", "latest", "registry.local:5000/app", "v2"}, + {"digest stays whole", "nginx@sha256:abc123", "latest", "nginx@sha256:abc123", "latest"}, + {"tag and digest", "nginx:1.27@sha256:abc", "latest", "nginx:1.27@sha256:abc", "latest"}, + {"namespaced tagged", "ghcr.io/owner/repo:dev", "latest", "ghcr.io/owner/repo", "dev"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + img, tag := splitImageReference(tc.image, tc.fallback) + if img != tc.wantImage || tag != tc.wantTag { + t.Fatalf("splitImageReference(%q, %q) = (%q, %q), want (%q, %q)", + tc.image, tc.fallback, img, tag, tc.wantImage, tc.wantTag) + } + }) + } +} + +func TestMapEngineStatusToDBStatus(t *testing.T) { + cases := map[string]string{ + "running": "deployed", + "building": "building", + "failed": "failed", + "cancelled": "cancelled", + "queued": "queued", + } + for in, want := range cases { + if got := mapEngineStatusToDBStatus(in); got != want { + t.Fatalf("mapEngineStatusToDBStatus(%q) = %q, want %q", in, got, want) + } + } +}