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 >{ 'condition': ['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: { 'Id': 'abc', 'Name': '/web', 'ImageName': 'example:latest', 'State': { 'Status': 'running', 'Healthcheck': { '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 { 'Cmd': ['echo', 'hello'], 'Privileged': false, 'Tty': false, 'AttachStdin': false, 'AttachStdout': true, 'AttachStderr': true, }, statusCode: 201, responseBody: const {'Id': 'exec-1'}, ) ..enqueue( method: HttpMethod.post, path: '/v5.0.0/libpod/exec/exec-1/start', body: const {'Detach': false, 'Tty': false}, statusCode: 200, responseBody: framed, ) ..enqueue( method: HttpMethod.get, path: '/v5.0.0/libpod/exec/exec-1/json', responseBody: const { '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: ['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 >{ 'stream': ['false'], }, responseBody: const { 'Id': 'container-1', 'name': 'web', 'read': '2026-04-06T21:00:00Z', 'cpu_stats': { 'cpu_usage': {'total_usage': 42}, }, 'memory_stats': {'usage': 512, 'limit': 1024}, 'pids_stats': {'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 >{ 'w': ['120'], 'h': ['40'], 'running': ['true'], }, statusCode: 200, ) ..enqueue( method: HttpMethod.post, path: '/v5.0.0/libpod/exec/exec-1/resize', queryParameters: const >{ 'w': ['120'], 'h': ['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 >{ 'stream': ['false'], }, responseBody: const { 'Id': 'container-1', 'name': 'web', 'cpu_stats': { 'cpu_usage': {'total_usage': 99}, }, 'memory_stats': {'usage': 512, 'limit': 1024}, 'pids_stats': {'current': 2}, }, ) ..enqueue( method: HttpMethod.get, path: '/v5.0.0/libpod/containers/web/top', responseBody: const { 'Titles': ['PID', 'CMD'], 'Processes': [ ['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 _frame({required int streamType, required List payload}) { final header = ByteData(8); header.setUint8(0, streamType); header.setUint32(4, payload.length, Endian.big); return [...header.buffer.asUint8List(), ...payload]; }