podman/test/podman_client_images_test.dart

299 lines
10 KiB
Dart

import 'package:podman/podman.dart';
import 'package:test/test.dart';
import 'support/fake_podman_transport.dart';
void main() {
group('PodmanClient image API', () {
test('lists images', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/images/json',
queryParameters: const <String, List<String>>{
'all': <String>['false'],
},
responseBody: <Object?>[
<String, Object?>{
'Id': 'sha256:abc',
'Repository': 'hello-world',
'Tag': 'latest',
'Digest': 'sha256:def',
'Size': '123 MB',
},
],
);
final client = PodmanClient(transport: transport);
final images = await client.listImages();
expect(images, hasLength(1));
expect(images.first.id, 'sha256:abc');
expect(images.first.reference, 'hello-world:latest');
transport.expectNoPending();
});
test('pull returns response body', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/images/pull',
queryParameters: const <String, List<String>>{
'reference': <String>['hello-world:latest'],
'quiet': <String>['true'],
},
responseBody: 'pull complete',
);
final client = PodmanClient(transport: transport);
final output = await client.pull('hello-world:latest', quiet: true);
expect(output, 'pull complete');
transport.expectNoPending();
});
test('image exists false on 404', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/images/missing%3Alatest/exists',
statusCode: 404,
);
final client = PodmanClient(transport: transport);
final exists = await client.imageExists('missing:latest');
expect(exists, isFalse);
transport.expectNoPending();
});
test('supports remove, tag, and prune', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.delete,
path: '/v5.0.0/libpod/images/hello-world%3Alatest',
queryParameters: const <String, List<String>>{
'force': <String>['true'],
},
statusCode: 200,
)
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/images/hello-world%3Alatest/tag',
queryParameters: const <String, List<String>>{
'repo': <String>['local/hello-world'],
'tag': <String>['test'],
},
statusCode: 201,
)
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/images/prune',
queryParameters: const <String, List<String>>{
'all': <String>['true'],
},
statusCode: 200,
responseBody: const <Object?>[],
);
final client = PodmanClient(transport: transport);
await client.removeImage('hello-world:latest', force: true);
await client.tagImage('hello-world:latest', 'local/hello-world:test');
await client.pruneImages(all: true);
transport.expectNoPending();
});
test(
'supports inspect, history, tree, push, load/import/export, and batch remove',
() async {
final archiveBytes = <int>[1, 2, 3, 4];
final exportBytes = <int>[9, 8, 7];
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/images/hello-world%3Alatest/json',
responseBody: const <String, Object?>{
'Id': 'sha256:abc',
'Digest': 'sha256:def',
'RepoTags': <String>['hello-world:latest'],
'RepoDigests': <String>['hello-world@sha256:def'],
'Created': '2026-04-01T00:00:00Z',
'Size': 1234,
},
)
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/images/hello-world%3Alatest/history',
responseBody: <Object?>[
<String, Object?>{
'Id': 'sha256:layer1',
'Created': 1712361600,
'CreatedBy': '/bin/sh -c echo hi',
'Tags': <String>['hello-world:latest'],
'Size': 42,
'Comment': 'base layer',
},
],
)
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/images/hello-world%3Alatest/tree',
queryParameters: const <String, List<String>>{
'whatrequires': <String>['true'],
},
responseBody: const <String, Object?>{
'Tree': 'hello-world:latest\n└── scratch',
},
)
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/images/hello-world%3Alatest/push',
queryParameters: const <String, List<String>>{
'destination': <String>['quay.io/groupware/hello-world:latest'],
'retry': <String>['3'],
'retryDelay': <String>['1s'],
'tlsVerify': <String>['false'],
'quiet': <String>['false'],
},
responseBody:
'{"stream":"copying"}\n'
'{"manifestdigest":"sha256:push"}\n',
)
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/images/load',
body: archiveBytes,
responseBody: const <String, Object?>{
'Names': <String>['hello-world:latest'],
},
)
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/images/load',
queryParameters: const <String, List<String>>{
'path': <String>['/tmp/image.tar'],
},
responseBody: const <String, Object?>{
'Names': <String>['hello-world:latest'],
},
)
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/images/import',
queryParameters: const <String, List<String>>{
'reference': <String>['quay.io/groupware/imported:latest'],
'message': <String>['import image'],
'changes': <String>['CMD ["/bin/sh"]'],
},
body: archiveBytes,
responseBody: const <String, Object?>{'Id': 'sha256:imported'},
)
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/images/hello-world%3Alatest/get',
queryParameters: const <String, List<String>>{
'compress': <String>['true'],
'format': <String>['docker-archive'],
},
responseBody: exportBytes,
)
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/images/export',
queryParameters: const <String, List<String>>{
'format': <String>['docker-archive'],
'references': <String>['hello-world:latest', 'busybox:latest'],
},
responseBody: exportBytes,
)
..enqueue(
method: HttpMethod.delete,
path: '/v5.0.0/libpod/images/remove',
queryParameters: const <String, List<String>>{
'images': <String>['hello-world:latest'],
'ignore': <String>['true'],
},
responseBody: const <String, Object?>{
'Deleted': <String>['sha256:def'],
'Untagged': <String>['hello-world:latest'],
'ExitCode': 0,
'Errors': <String>[],
},
);
final client = PodmanClient(transport: transport);
final details = await client.inspectImage('hello-world:latest');
expect(details.id, 'sha256:abc');
final history = await client.imageHistory('hello-world:latest');
expect(history, hasLength(1));
expect(history.first.id, 'sha256:layer1');
final tree = await client.imageTree(
'hello-world:latest',
whatRequires: true,
);
expect(tree.tree, contains('hello-world:latest'));
final pushEvents = await client.pushImage(
'hello-world:latest',
options: const ImagePushOptions(
destination: 'quay.io/groupware/hello-world:latest',
retry: 3,
retryDelay: '1s',
tlsVerify: false,
quiet: false,
),
);
expect(pushEvents, hasLength(2));
expect(pushEvents.last.manifestDigest, 'sha256:push');
final loaded = await client.loadImages(archiveBytes);
expect(loaded.names, contains('hello-world:latest'));
final loadedFromPath = await client.loadImagesFromPath(
'/tmp/image.tar',
);
expect(loadedFromPath.names, contains('hello-world:latest'));
final imported = await client.importImage(
options: const ImageImportOptions(
reference: 'quay.io/groupware/imported:latest',
message: 'import image',
changes: <String>['CMD ["/bin/sh"]'],
),
archiveBytes: archiveBytes,
);
expect(imported.id, 'sha256:imported');
final exported = await client.exportImage(
'hello-world:latest',
options: const ImageExportOptions(
compress: true,
format: 'docker-archive',
),
);
expect(exported, exportBytes);
final exportedMulti = await client.exportImages(const <String>[
'hello-world:latest',
'busybox:latest',
], options: const ImageExportOptions(format: 'docker-archive'));
expect(exportedMulti, exportBytes);
final removed = await client.removeImages(
const ImageRemoveOptions(
images: <String>['hello-world:latest'],
ignore: true,
),
);
expect(removed.deleted, contains('sha256:def'));
transport.expectNoPending();
},
);
});
}