import { describe, expect, it } from "vitest"; import { createEcrClient, getDeploymentImageRef, getEcrAuthToken, parseEcrRegistryDomain, parseRegistryTags, } from "../app/v3/getDeploymentImageRef.server"; import { DeleteRepositoryCommand } from "@aws-sdk/client-ecr"; describe("getDeploymentImageRef", () => { const testHost = process.env.DEPLOY_REGISTRY_HOST || "123456789012.dkr.ecr.us-east-1.amazonaws.com"; const testNamespace = process.env.DEPLOY_REGISTRY_NAMESPACE || "test-namespace"; const testProjectRef = "proj_test_" + Math.random().toString(36).substring(7); const testProjectRef2 = testProjectRef + "_2"; const registryTags = process.env.DEPLOY_REGISTRY_ECR_TAGS || "test=test,test2=test2"; const roleArn = process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN; const externalId = process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID; const assumeRole = { roleArn, externalId, }; // Clean up test repository after tests afterAll(async () => { if (process.env.KEEP_TEST_REPO === "1" || process.env.RUN_ECR_TESTS !== "1") { return; } try { const { region, accountId } = parseEcrRegistryDomain(testHost); const ecr = await createEcrClient({ region, assumeRole }); await Promise.all([ ecr.send( new DeleteRepositoryCommand({ repositoryName: `${testNamespace}/${testProjectRef}`, registryId: accountId, force: true, }) ), ecr.send( new DeleteRepositoryCommand({ repositoryName: `${testNamespace}/${testProjectRef2}`, registryId: accountId, force: true, }) ), ]); } catch (error) { console.warn("Failed to delete test repository:", error); } }); it("should return the correct image ref for non-ECR registry", async () => { const imageRef = await getDeploymentImageRef({ registry: { host: "registry.example.com", namespace: testNamespace, username: "test-user", password: "test-pass", ecrTags: registryTags, ecrAssumeRoleArn: roleArn, ecrAssumeRoleExternalId: externalId, }, projectRef: testProjectRef, nextVersion: "20250630.1", environmentType: "DEVELOPMENT", deploymentShortCode: "test1234", }); // Check the image ref structure and that it contains expected parts expect(imageRef.imageRef).toBe( `registry.example.com/${testNamespace}/${testProjectRef}:20250630.1.development.test1234` ); expect(imageRef.isEcr).toBe(false); }); it.skipIf(process.env.RUN_ECR_TESTS !== "1")( "should create ECR repository and return correct image ref", async () => { const imageRef1 = await getDeploymentImageRef({ registry: { host: testHost, namespace: testNamespace, username: "test-user", password: "test-pass", ecrTags: registryTags, ecrAssumeRoleArn: roleArn, ecrAssumeRoleExternalId: externalId, }, projectRef: testProjectRef2, nextVersion: "20250630.1", environmentType: "DEVELOPMENT", deploymentShortCode: "test1234", }); expect(imageRef1.imageRef).toBe( `${testHost}/${testNamespace}/${testProjectRef2}:20250630.1.development.test1234` ); expect(imageRef1.isEcr).toBe(true); expect(imageRef1.repoCreated).toBe(true); const imageRef2 = await getDeploymentImageRef({ registry: { host: testHost, namespace: testNamespace, username: "test-user", password: "test-pass", ecrTags: registryTags, ecrAssumeRoleArn: roleArn, ecrAssumeRoleExternalId: externalId, }, projectRef: testProjectRef2, nextVersion: "20250630.2", environmentType: "DEVELOPMENT", deploymentShortCode: "test1234", }); expect(imageRef2.imageRef).toBe( `${testHost}/${testNamespace}/${testProjectRef2}:20250630.2.development.test1234` ); expect(imageRef2.isEcr).toBe(true); expect(imageRef2.repoCreated).toBe(false); } ); it.skipIf(process.env.RUN_ECR_TESTS !== "1")("should reuse existing ECR repository", async () => { // This should use the repository created in the previous test const imageRef = await getDeploymentImageRef({ registry: { host: testHost, namespace: testNamespace, username: "test-user", password: "test-pass", ecrTags: registryTags, ecrAssumeRoleArn: roleArn, ecrAssumeRoleExternalId: externalId, }, projectRef: testProjectRef, nextVersion: "20250630.2", environmentType: "PRODUCTION", deploymentShortCode: "test1234", }); expect(imageRef.imageRef).toBe( `${testHost}/${testNamespace}/${testProjectRef}:20250630.2.production.test1234` ); expect(imageRef.isEcr).toBe(true); }); it("should generate unique image tags for different deployments with same environment type", async () => { // Simulates the scenario where multiple deployments happen to the same environment type const sameEnvironmentType = "PREVIEW"; const sameVersion = "20250630.1"; const firstImageRef = await getDeploymentImageRef({ registry: { host: "registry.example.com", namespace: testNamespace, username: "test-user", password: "test-pass", ecrTags: registryTags, ecrAssumeRoleArn: roleArn, ecrAssumeRoleExternalId: externalId, }, projectRef: testProjectRef, nextVersion: sameVersion, environmentType: sameEnvironmentType, deploymentShortCode: "test1234", }); const secondImageRef = await getDeploymentImageRef({ registry: { host: "registry.example.com", namespace: testNamespace, username: "test-user", password: "test-pass", ecrTags: registryTags, ecrAssumeRoleArn: roleArn, ecrAssumeRoleExternalId: externalId, }, projectRef: testProjectRef, nextVersion: sameVersion, environmentType: sameEnvironmentType, deploymentShortCode: "test4321", }); // Even with the same environment type and version, the image refs should be different due to deployment short codes expect(firstImageRef.imageRef).toBe( `registry.example.com/${testNamespace}/${testProjectRef}:${sameVersion}.preview.test1234` ); expect(secondImageRef.imageRef).toBe( `registry.example.com/${testNamespace}/${testProjectRef}:${sameVersion}.preview.test4321` ); expect(firstImageRef.imageRef).not.toBe(secondImageRef.imageRef); }); }); describe.skipIf(process.env.RUN_ECR_TESTS !== "1")("getEcrAuthToken", () => { const testHost = process.env.DEPLOY_REGISTRY_HOST || "123456789012.dkr.ecr.us-east-1.amazonaws.com"; const roleArn = process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN; const externalId = process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID; const assumeRole = { roleArn, externalId, }; it("should return valid ECR credentials", async () => { const auth = await getEcrAuthToken({ registryHost: testHost, assumeRole, }); // Check the structure and basic validation of the returned credentials expect(auth).toHaveProperty("username"); expect(auth).toHaveProperty("password"); expect(auth.username).toBe("AWS"); expect(typeof auth.password).toBe("string"); expect(auth.password.length).toBeGreaterThan(0); // Verify the token format (should be a base64-encoded string) expect(auth.password).toMatch(/^[A-Za-z0-9+/=]+$/); }); it("should throw error for invalid region", async () => { await expect( getEcrAuthToken({ registryHost: "invalid.ecr.amazonaws.com", assumeRole, }) ).rejects.toThrow(); }); }); describe("parseEcrRegistry", () => { it("should correctly parse a valid ECR registry host", () => { const result = parseEcrRegistryDomain("123456789012.dkr.ecr.us-east-1.amazonaws.com"); expect(result).toEqual({ accountId: "123456789012", region: "us-east-1", }); }); it("should handle invalid ECR registry hosts", () => { const invalidHosts = [ "invalid.ecr.amazonaws.com", "registry.hub.docker.com", "123456789012.dkr.ecr.us-east-1.not-amazon.com", "123456789012.wrong.ecr.us-east-1.amazonaws.com", ]; for (const host of invalidHosts) { expect(() => parseEcrRegistryDomain(host)).toThrow("Invalid ECR registry host"); } }); }); describe("parseRegistryTags", () => { it("should handle empty or null input", () => { expect(parseRegistryTags("")).toEqual([]); expect(parseRegistryTags(",,,")).toEqual([]); }); it("should parse key-only tags", () => { expect(parseRegistryTags("key1,key2")).toEqual([ { Key: "key1", Value: "" }, { Key: "key2", Value: "" }, ]); }); it("should parse key-value tags", () => { expect(parseRegistryTags("key1=value1,key2=value2")).toEqual([ { Key: "key1", Value: "value1" }, { Key: "key2", Value: "value2" }, ]); }); it("should handle mixed key-only and key-value tags", () => { expect(parseRegistryTags("key1,key2=value2,key3")).toEqual([ { Key: "key1", Value: "" }, { Key: "key2", Value: "value2" }, { Key: "key3", Value: "" }, ]); }); it("should handle whitespace", () => { expect(parseRegistryTags(" key1 , key2 = value2 ")).toEqual([ { Key: "key1", Value: "" }, { Key: "key2", Value: "value2" }, ]); }); it("should skip invalid tags", () => { expect(parseRegistryTags("=value,key1,=,key2=value2")).toEqual([ { Key: "key1", Value: "" }, { Key: "key2", Value: "value2" }, ]); }); });