1import {
2 parseCreatedAt,
3 parseRepoDigest,
4 repositoryFromImageRef,
5 withRegistryHost,
6} from "@/Docker/Registry";
7import { describe, expect, it } from "alchemy-test";
8
9describe(
10 "repositoryFromImageRef",
11 { tags: ["unit", "provider:docker", "provider:docker:registry", "local"] },
12 () => {
13 it("strips a simple tag", () => {
14 expect(repositoryFromImageRef("nginx:alpine")).toBe("nginx");
15 });
16
17 it("keeps the registry host and path", () => {
18 expect(repositoryFromImageRef("ghcr.io/acme/app:latest")).toBe(
19 "ghcr.io/acme/app",
20 );
21 });
22
23 it("does not confuse a registry port for a tag", () => {
24 expect(repositoryFromImageRef("localhost:5000/acme/app:latest")).toBe(
25 "localhost:5000/acme/app",
26 );
27 });
28
29 it("strips a digest", () => {
30 expect(
31 repositoryFromImageRef(
32 "localhost:5000/acme/app@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
33 ),
34 ).toBe("localhost:5000/acme/app");
35 });
36
37 it("returns a bare repository unchanged", () => {
38 expect(repositoryFromImageRef("nginx")).toBe("nginx");
39 });
40 },
41);
42
43describe(
44 "withRegistryHost",
45 { tags: ["unit", "provider:docker", "provider:docker:registry", "local"] },
46 () => {
47 it("prefixes a bare reference with the registry host", () => {
48 expect(withRegistryHost("app:latest", { server: "ghcr.io" })).toBe(
49 "ghcr.io/app:latest",
50 );
51 });
52
53 it("trims a trailing slash from the server", () => {
54 expect(withRegistryHost("app:latest", { server: "ghcr.io/" })).toBe(
55 "ghcr.io/app:latest",
56 );
57 });
58
59 it("leaves a reference that already has a dotted-host prefix", () => {
60 expect(
61 withRegistryHost("registry.example.com/app:latest", {
62 server: "ghcr.io",
63 }),
64 ).toBe("registry.example.com/app:latest");
65 });
66
67 it("leaves a localhost:port reference untouched", () => {
68 expect(
69 withRegistryHost("localhost:5000/app:latest", { server: "ghcr.io" }),
70 ).toBe("localhost:5000/app:latest");
71 });
72 },
73);
74
75describe(
76 "parseRepoDigest",
77 { tags: ["unit", "provider:docker", "provider:docker:registry", "local"] },
78 () => {
79 it("extracts the repo digest from push output", () => {
80 expect(
81 parseRepoDigest(
82 "localhost:5000/app:latest",
83 "latest: digest: sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa size: 123",
84 ),
85 ).toBe(
86 "localhost:5000/app@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
87 );
88 });
89
90 it("returns undefined when no digest is present", () => {
91 expect(parseRepoDigest("app:latest", "Pushed without a digest")).toBe(
92 undefined,
93 );
94 });
95 },
96);
97
98describe(
99 "parseCreatedAt",
100 { tags: ["unit", "provider:docker", "provider:docker:registry", "local"] },
101 () => {
102 it("parses an RFC 3339 timestamp", () => {
103 const created = "2026-06-22T20:53:00.395Z";
104 expect(parseCreatedAt(created)).toBe(Date.parse(created));
105 });
106
107 it("falls back to the wall clock when omitted", () => {
108 const before = Date.now();
109 const result = parseCreatedAt(undefined);
110 expect(result).toBeGreaterThanOrEqual(before);
111 });
112
113 it("falls back to the wall clock for an empty string", () => {
114 const before = Date.now();
115 expect(parseCreatedAt("")).toBeGreaterThanOrEqual(before);
116 });
117
118 it("falls back to the wall clock for the year-1 zero value", () => {
119 const before = Date.now();
120 expect(parseCreatedAt("0001-01-01T00:00:00Z")).toBeGreaterThanOrEqual(
121 before,
122 );
123 });
124 },
125);