podman/test/podman_client_container_runtime_test.dart

237 lines
7.4 KiB
Dart

import 'dart:convert';
import 'dart:typed_data';
import 'package:podman/podman.dart';
import 'package:test/test.dart';
import 'support/fake_podman_transport.dart';
void main() {
group('PodmanClient container runtime API', () {
test('wait parses numeric status', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/containers/web/wait',
queryParameters: const <String, List<String>>{
'condition': <String>['stopped'],
},
responseBody: '0',
);
final client = PodmanClient(transport: transport);
final result = await client.wait('web');
expect(result.statusCode, 0);
transport.expectNoPending();
});
test('healthStatus returns parsed healthcheck state', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/containers/web/json',
responseBody: <String, Object?>{
'Id': 'abc',
'Name': '/web',
'ImageName': 'example:latest',
'State': <String, Object?>{
'Status': 'running',
'Healthcheck': <String, Object?>{
'Status': 'healthy',
'FailingStreak': 0,
},
},
},
);
final client = PodmanClient(transport: transport);
final status = await client.healthStatus('web');
expect(status.status, 'healthy');
expect(status.failingStreak, 0);
transport.expectNoPending();
});
test('supports exec create/start/inspect', () async {
final framed = _frame(streamType: 1, payload: utf8.encode('hello\n'));
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/containers/web/exec',
body: const <String, Object?>{
'Cmd': <String>['echo', 'hello'],
'Privileged': false,
'Tty': false,
'AttachStdin': false,
'AttachStdout': true,
'AttachStderr': true,
},
statusCode: 201,
responseBody: const <String, Object?>{'Id': 'exec-1'},
)
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/exec/exec-1/start',
body: const <String, Object?>{'Detach': false, 'Tty': false},
statusCode: 200,
responseBody: framed,
)
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/exec/exec-1/json',
responseBody: const <String, Object?>{
'Id': 'exec-1',
'ContainerID': 'container-1',
'Running': false,
'ExitCode': 0,
'OpenStdout': true,
'OpenStderr': true,
},
);
final client = PodmanClient(transport: transport);
final created = await client.createExec(
'web',
const ExecCreateOptions(command: <String>['echo', 'hello']),
);
final started = await client.startExec(created.id);
final inspected = await client.inspectExec(created.id);
expect(created.id, 'exec-1');
expect(started.stdout, 'hello\n');
expect(started.stderr, '');
expect(inspected.exitCode, 0);
transport.expectNoPending();
});
test('reads one-shot stats snapshot', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/containers/web/stats',
queryParameters: const <String, List<String>>{
'stream': <String>['false'],
},
responseBody: const <String, Object?>{
'Id': 'container-1',
'name': 'web',
'read': '2026-04-06T21:00:00Z',
'cpu_stats': <String, Object?>{
'cpu_usage': <String, Object?>{'total_usage': 42},
},
'memory_stats': <String, Object?>{'usage': 512, 'limit': 1024},
'pids_stats': <String, Object?>{'current': 3},
},
);
final client = PodmanClient(transport: transport);
final stats = await client.stats('web');
expect(stats.id, 'container-1');
expect(stats.cpuTotalUsage, 42);
expect(stats.memoryUsage, 512);
expect(stats.pidsCurrent, 3);
transport.expectNoPending();
});
test('rejects streaming stats for now', () async {
final client = PodmanClient(transport: FakePodmanTransport());
expect(() => client.stats('web', stream: true), throwsArgumentError);
});
test('supports container and exec tty resize', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/containers/web/resize',
queryParameters: const <String, List<String>>{
'w': <String>['120'],
'h': <String>['40'],
'running': <String>['true'],
},
statusCode: 200,
)
..enqueue(
method: HttpMethod.post,
path: '/v5.0.0/libpod/exec/exec-1/resize',
queryParameters: const <String, List<String>>{
'w': <String>['120'],
'h': <String>['40'],
},
statusCode: 201,
);
final client = PodmanClient(transport: transport);
await client.resizeContainerTty(
'web',
width: 120,
height: 40,
ignoreNotRunning: true,
);
await client.resizeExecTty('exec-1', width: 120, height: 40);
transport.expectNoPending();
});
test('watchStats and watchContainerTop emit polling snapshots', () async {
final transport = FakePodmanTransport()
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/containers/web/stats',
queryParameters: const <String, List<String>>{
'stream': <String>['false'],
},
responseBody: const <String, Object?>{
'Id': 'container-1',
'name': 'web',
'cpu_stats': <String, Object?>{
'cpu_usage': <String, Object?>{'total_usage': 99},
},
'memory_stats': <String, Object?>{'usage': 512, 'limit': 1024},
'pids_stats': <String, Object?>{'current': 2},
},
)
..enqueue(
method: HttpMethod.get,
path: '/v5.0.0/libpod/containers/web/top',
responseBody: const <String, Object?>{
'Titles': <String>['PID', 'CMD'],
'Processes': <Object?>[
<String>['1', 'dart run bin/orchestrator.dart'],
],
},
);
final client = PodmanClient(transport: transport);
final stats = await client
.watchStats(
'web',
pollInterval: const Duration(milliseconds: 1),
reconnect: false,
)
.first;
expect(stats.cpuTotalUsage, 99);
final top = await client
.watchContainerTop(
'web',
pollInterval: const Duration(milliseconds: 1),
reconnect: false,
)
.first;
expect(top.processes, hasLength(1));
transport.expectNoPending();
});
});
}
List<int> _frame({required int streamType, required List<int> payload}) {
final header = ByteData(8);
header.setUint8(0, streamType);
header.setUint32(4, payload.length, Endian.big);
return <int>[...header.buffer.asUint8List(), ...payload];
}