|
| 1 | +import { describe, expect, it } from "vitest"; |
| 2 | +import { |
| 3 | + ecrImageExists, |
| 4 | + interpretBatchGetImageResponse, |
| 5 | + parseEcrImageReference, |
| 6 | +} from "~/v3/services/verifyDeploymentImage.server"; |
| 7 | +import { type RegistryConfig } from "~/v3/registryConfig.server"; |
| 8 | + |
| 9 | +const ECR_HOST = "123456789012.dkr.ecr.us-east-1.amazonaws.com"; |
| 10 | +const ecrConfig: RegistryConfig = { host: ECR_HOST, namespace: "deployments-test" }; |
| 11 | + |
| 12 | +describe("parseEcrImageReference", () => { |
| 13 | + it("splits repository and tag for a ref under the configured host", () => { |
| 14 | + const ref = `${ECR_HOST}/deployments-test/proj_abc:20240101.1.prod.a1b2c3d4`; |
| 15 | + expect(parseEcrImageReference(ref, ECR_HOST)).toEqual({ |
| 16 | + repositoryName: "deployments-test/proj_abc", |
| 17 | + tag: "20240101.1.prod.a1b2c3d4", |
| 18 | + }); |
| 19 | + }); |
| 20 | + |
| 21 | + it("drops a trailing @sha256 digest", () => { |
| 22 | + const ref = `${ECR_HOST}/deployments-test/proj_abc:v1.prod.a1b2c3d4@sha256:${"a".repeat(64)}`; |
| 23 | + expect(parseEcrImageReference(ref, ECR_HOST)).toEqual({ |
| 24 | + repositoryName: "deployments-test/proj_abc", |
| 25 | + tag: "v1.prod.a1b2c3d4", |
| 26 | + }); |
| 27 | + }); |
| 28 | + |
| 29 | + it("returns null when the ref is not under the configured host (trust boundary)", () => { |
| 30 | + const ref = "evil.example.com/whatever/proj_abc:v1"; |
| 31 | + expect(parseEcrImageReference(ref, ECR_HOST)).toBeNull(); |
| 32 | + }); |
| 33 | + |
| 34 | + it("returns null when there is no tag", () => { |
| 35 | + expect(parseEcrImageReference(`${ECR_HOST}/deployments-test/proj_abc`, ECR_HOST)).toBeNull(); |
| 36 | + }); |
| 37 | + |
| 38 | + it("returns null when the tag segment contains a slash", () => { |
| 39 | + // a stray colon earlier in the path must not be treated as the tag separator |
| 40 | + expect(parseEcrImageReference(`${ECR_HOST}/ns:weird/proj_abc`, ECR_HOST)).toBeNull(); |
| 41 | + }); |
| 42 | +}); |
| 43 | + |
| 44 | +describe("interpretBatchGetImageResponse", () => { |
| 45 | + it("returns found when an image is present", () => { |
| 46 | + expect(interpretBatchGetImageResponse({ images: [{}] } as any)).toBe("found"); |
| 47 | + }); |
| 48 | + |
| 49 | + it("returns missing on an ImageNotFound failure", () => { |
| 50 | + expect( |
| 51 | + interpretBatchGetImageResponse({ failures: [{ failureCode: "ImageNotFound" }] } as any) |
| 52 | + ).toBe("missing"); |
| 53 | + }); |
| 54 | + |
| 55 | + it("returns unknown when there is neither an image nor a not-found failure", () => { |
| 56 | + expect(interpretBatchGetImageResponse({ failures: [{ failureCode: "Other" }] } as any)).toBe( |
| 57 | + "unknown" |
| 58 | + ); |
| 59 | + expect(interpretBatchGetImageResponse({} as any)).toBe("unknown"); |
| 60 | + }); |
| 61 | +}); |
| 62 | + |
| 63 | +describe("ecrImageExists", () => { |
| 64 | + it("returns unknown for a non-ECR registry without calling the registry", async () => { |
| 65 | + let called = false; |
| 66 | + const result = await ecrImageExists( |
| 67 | + { |
| 68 | + imageReference: "registry.digitalocean.com/trigger-deployments/proj_abc:v1", |
| 69 | + registryConfig: { host: "registry.digitalocean.com", namespace: "trigger-deployments" }, |
| 70 | + }, |
| 71 | + async () => { |
| 72 | + called = true; |
| 73 | + return {} as any; |
| 74 | + } |
| 75 | + ); |
| 76 | + expect(result).toBe("unknown"); |
| 77 | + expect(called).toBe(false); |
| 78 | + }); |
| 79 | + |
| 80 | + it("returns found when the image exists", async () => { |
| 81 | + const result = await ecrImageExists( |
| 82 | + { |
| 83 | + imageReference: `${ECR_HOST}/deployments-test/proj_abc:v1.prod.a1b2c3d4`, |
| 84 | + registryConfig: ecrConfig, |
| 85 | + }, |
| 86 | + async () => ({ images: [{}] }) as any |
| 87 | + ); |
| 88 | + expect(result).toBe("found"); |
| 89 | + }); |
| 90 | + |
| 91 | + it("returns missing when the registry reports ImageNotFound", async () => { |
| 92 | + const result = await ecrImageExists( |
| 93 | + { |
| 94 | + imageReference: `${ECR_HOST}/deployments-test/proj_abc:v1.prod.a1b2c3d4`, |
| 95 | + registryConfig: ecrConfig, |
| 96 | + }, |
| 97 | + async () => ({ failures: [{ failureCode: "ImageNotFound" }] }) as any |
| 98 | + ); |
| 99 | + expect(result).toBe("missing"); |
| 100 | + }); |
| 101 | + |
| 102 | + it("returns unknown (fails open) when the registry call throws", async () => { |
| 103 | + const result = await ecrImageExists( |
| 104 | + { |
| 105 | + imageReference: `${ECR_HOST}/deployments-test/proj_abc:v1.prod.a1b2c3d4`, |
| 106 | + registryConfig: ecrConfig, |
| 107 | + }, |
| 108 | + async () => { |
| 109 | + throw new Error("AccessDenied"); |
| 110 | + } |
| 111 | + ); |
| 112 | + expect(result).toBe("unknown"); |
| 113 | + }); |
| 114 | + |
| 115 | + it("queries by digest when a valid digest is supplied", async () => { |
| 116 | + const digest = `sha256:${"b".repeat(64)}`; |
| 117 | + let seen: any; |
| 118 | + await ecrImageExists( |
| 119 | + { |
| 120 | + imageReference: `${ECR_HOST}/deployments-test/proj_abc:v1.prod.a1b2c3d4`, |
| 121 | + imageDigest: digest, |
| 122 | + registryConfig: ecrConfig, |
| 123 | + }, |
| 124 | + async (input) => { |
| 125 | + seen = input; |
| 126 | + return { images: [{}] } as any; |
| 127 | + } |
| 128 | + ); |
| 129 | + expect(seen.imageIds).toEqual([{ imageDigest: digest }]); |
| 130 | + }); |
| 131 | + |
| 132 | + it("falls back to the tag when the supplied digest is malformed", async () => { |
| 133 | + let seen: any; |
| 134 | + await ecrImageExists( |
| 135 | + { |
| 136 | + imageReference: `${ECR_HOST}/deployments-test/proj_abc:v1.prod.a1b2c3d4`, |
| 137 | + imageDigest: "not-a-digest", |
| 138 | + registryConfig: ecrConfig, |
| 139 | + }, |
| 140 | + async (input) => { |
| 141 | + seen = input; |
| 142 | + return { images: [{}] } as any; |
| 143 | + } |
| 144 | + ); |
| 145 | + expect(seen.imageIds).toEqual([{ imageTag: "v1.prod.a1b2c3d4" }]); |
| 146 | + }); |
| 147 | +}); |
0 commit comments