237 lines
7.4 KiB
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];
|
|
}
|