305 lines
9.7 KiB
TypeScript
305 lines
9.7 KiB
TypeScript
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" },
|
|
]);
|
|
});
|
|
});
|