127 lines
4.7 KiB
TypeScript
127 lines
4.7 KiB
TypeScript
/**
|
|
* ECR control-plane compatibility tests.
|
|
*
|
|
* Test-first: this file is committed before the server-side ECR implementation
|
|
* lands. With ECR unimplemented, every test below should fail.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import {
|
|
ECRClient,
|
|
CreateRepositoryCommand,
|
|
DescribeRepositoriesCommand,
|
|
DeleteRepositoryCommand,
|
|
GetAuthorizationTokenCommand,
|
|
ListImagesCommand,
|
|
PutImageTagMutabilityCommand,
|
|
PutLifecyclePolicyCommand,
|
|
GetLifecyclePolicyCommand,
|
|
SetRepositoryPolicyCommand,
|
|
GetRepositoryPolicyCommand,
|
|
ImageTagMutability,
|
|
RepositoryAlreadyExistsException,
|
|
RepositoryNotFoundException,
|
|
} from '@aws-sdk/client-ecr';
|
|
import { makeClient } from './setup';
|
|
|
|
const REPO_NAME = 'floci-it/app-node';
|
|
|
|
describe('ECR Repository Lifecycle', () => {
|
|
let ecr: ECRClient;
|
|
|
|
beforeAll(() => {
|
|
ecr = makeClient(ECRClient);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
try {
|
|
await ecr.send(new DeleteRepositoryCommand({ repositoryName: REPO_NAME, force: true }));
|
|
} catch {
|
|
// ignore
|
|
}
|
|
});
|
|
|
|
it('CreateRepository returns a repositoryUri pointing at loopback', async () => {
|
|
const resp = await ecr.send(new CreateRepositoryCommand({ repositoryName: REPO_NAME }));
|
|
const repo = resp.repository!;
|
|
expect(repo.repositoryName).toBe(REPO_NAME);
|
|
expect(repo.repositoryArn).toMatch(new RegExp(`^arn:aws:ecr:.*:.*:repository/${REPO_NAME}$`));
|
|
expect(repo.repositoryUri).toContain(`/${REPO_NAME}`);
|
|
expect(repo.repositoryUri).toContain('localhost:');
|
|
});
|
|
|
|
it('CreateRepository on duplicate name throws RepositoryAlreadyExistsException', async () => {
|
|
await expect(
|
|
ecr.send(new CreateRepositoryCommand({ repositoryName: REPO_NAME })),
|
|
).rejects.toBeInstanceOf(RepositoryAlreadyExistsException);
|
|
});
|
|
|
|
it('DescribeRepositories returns the created repository', async () => {
|
|
const resp = await ecr.send(
|
|
new DescribeRepositoriesCommand({ repositoryNames: [REPO_NAME] }),
|
|
);
|
|
expect(resp.repositories?.[0]?.repositoryName).toBe(REPO_NAME);
|
|
});
|
|
|
|
it('GetAuthorizationToken returns a usable docker login token', async () => {
|
|
const resp = await ecr.send(new GetAuthorizationTokenCommand({}));
|
|
expect(resp.authorizationData).toBeTruthy();
|
|
const data = resp.authorizationData![0];
|
|
expect(data.authorizationToken).toBeTruthy();
|
|
expect(data.proxyEndpoint).toMatch(/^https?:\/\//);
|
|
expect(data.expiresAt).toBeDefined();
|
|
const decoded = Buffer.from(data.authorizationToken!, 'base64').toString('utf-8');
|
|
expect(decoded).toMatch(/^AWS:/);
|
|
});
|
|
|
|
it('ListImages on an empty repository returns no image identifiers', async () => {
|
|
const resp = await ecr.send(new ListImagesCommand({ repositoryName: REPO_NAME }));
|
|
expect(resp.imageIds ?? []).toEqual([]);
|
|
});
|
|
|
|
it('PutImageTagMutability round-trips IMMUTABLE', async () => {
|
|
const resp = await ecr.send(
|
|
new PutImageTagMutabilityCommand({
|
|
repositoryName: REPO_NAME,
|
|
imageTagMutability: ImageTagMutability.IMMUTABLE,
|
|
}),
|
|
);
|
|
expect(resp.imageTagMutability).toBe(ImageTagMutability.IMMUTABLE);
|
|
});
|
|
|
|
it('PutLifecyclePolicy round-trips the policy text', async () => {
|
|
const policy =
|
|
'{"rules":[{"rulePriority":1,"selection":{"tagStatus":"untagged","countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}}]}';
|
|
await ecr.send(
|
|
new PutLifecyclePolicyCommand({ repositoryName: REPO_NAME, lifecyclePolicyText: policy }),
|
|
);
|
|
const got = await ecr.send(new GetLifecyclePolicyCommand({ repositoryName: REPO_NAME }));
|
|
expect(got.lifecyclePolicyText).toBe(policy);
|
|
});
|
|
|
|
it('SetRepositoryPolicy round-trips the policy text', async () => {
|
|
const policy =
|
|
'{"Version":"2012-10-17","Statement":[{"Sid":"AllowAll","Effect":"Allow","Principal":"*","Action":"ecr:*"}]}';
|
|
await ecr.send(
|
|
new SetRepositoryPolicyCommand({ repositoryName: REPO_NAME, policyText: policy }),
|
|
);
|
|
const got = await ecr.send(new GetRepositoryPolicyCommand({ repositoryName: REPO_NAME }));
|
|
expect(got.policyText).toBe(policy);
|
|
});
|
|
|
|
it('DeleteRepository force=true removes the repository', async () => {
|
|
await ecr.send(new DeleteRepositoryCommand({ repositoryName: REPO_NAME, force: true }));
|
|
await expect(
|
|
ecr.send(new DescribeRepositoriesCommand({ repositoryNames: [REPO_NAME] })),
|
|
).rejects.toBeInstanceOf(RepositoryNotFoundException);
|
|
});
|
|
|
|
it('DescribeRepositories on missing name throws RepositoryNotFoundException', async () => {
|
|
await expect(
|
|
ecr.send(
|
|
new DescribeRepositoriesCommand({ repositoryNames: ['does-not-exist-node'] }),
|
|
),
|
|
).rejects.toBeInstanceOf(RepositoryNotFoundException);
|
|
});
|
|
});
|