import 'dart:convert'; import 'package:ac_web/Api/Api.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; const ApiConfig testConfig = ApiConfig(host: 'api.local', port: 5000, basePath: 'api'); AcApi apiReturning(String body, {int statusCode = 200, void Function(Uri uri)? onRequest}) { final client = MockClient((request) async { onRequest?.call(request.url); return http.Response(body, statusCode, headers: {'content-type': 'application/json'}); }); return AcApi(config: testConfig, httpClient: client); } void main() { group('ApiConfig', () { test('builds http uris including base path and query', () { final uri = testConfig.buildUri('agents', {'onlyOnline': true, 'skip': null}); expect(uri.toString(), 'http://api.local:5000/api/agents?onlyOnline=true'); }); }); group('AgentsService', () { test('parses agents with enums as names and nested tasks', () async { Uri? requested; final api = apiReturning( jsonEncode([ { 'name': 'Ada', 'type': 'CODER', 'lastSeen': '2026-08-28T12:00:00Z', 'status': 'WORKING', 'tasks': [ { 'title': 'Implement api layer', 'startTime': '2026-08-28T11:00:00Z', 'statusUpdates': ['Started', 'Half way through'], }, ], }, ]), onRequest: (uri) => requested = uri, ); final agents = await api.agents.getAgents(); expect(requested.toString(), 'http://api.local:5000/api/agents'); expect(agents, hasLength(1)); expect(agents.single.name, 'Ada'); expect(agents.single.type, EAgentType.coder); expect(agents.single.status, EAgentStatus.working); expect(agents.single.lastSeen.isUtc, isTrue); expect(agents.single.tasks.single.title, 'Implement api layer'); expect(agents.single.tasks.single.startTime, DateTime.utc(2026, 8, 28, 11)); expect(agents.single.tasks.single.statusUpdates, ['Started', 'Half way through']); expect(agents.single.tasks.single.latestStatus, 'Half way through'); }); test('parses numeric enums and falls back to unknown', () async { final api = apiReturning( jsonEncode([ { 'Name': 'Numeric', 'Type': 1, 'LastSeen': '2026-08-28T12:00:00Z', 'Status': 2, 'Tasks': [], }, { 'Name': 'Future', 'Type': 'SomethingNew', 'LastSeen': '2026-08-28T12:00:00Z', 'Status': 'AlsoNew', 'Tasks': [], }, ]), ); final agents = await api.agents.getAgents(); expect(agents.first.type, EAgentType.logAnalyzer); expect(agents.first.status, EAgentStatus.offline); expect(agents.first.tasks, isEmpty); expect(agents.last.type, EAgentType.unknown); expect(agents.last.status, EAgentStatus.unknown); }); test('throws ApiParsingException on a malformed payload', () async { final api = apiReturning(jsonEncode([{'Type': 'CODER'}])); expect(() => api.agents.getAgents(), throwsA(isA())); }); }); group('ReportsService', () { test('returns reports sorted newest first', () async { final api = apiReturning( jsonEncode([ { 'Id': 'older', 'Author': 'Ada', 'Timestamp': '2026-08-20T10:00:00Z', 'Type': 'LOG', 'Text': 'first', }, { 'Id': 'newer', 'Author': 'Grace', 'Timestamp': '2026-08-27T10:00:00Z', 'Type': 'CODE_REVIEW', 'Text': 'second', }, ]), ); final reports = await api.reports.getReports(); expect(reports.map((report) => report.id), ['newer', 'older']); expect(reports.first.author, 'Grace'); expect(reports.first.type, EReportType.codeReview); }); test('maps error status codes onto ApiHttpException', () async { final api = apiReturning('{"error":"boom"}', statusCode: 500); await expectLater( api.reports.getReports(), throwsA(isA().having((error) => error.isServerError, 'isServerError', isTrue)), ); }); test('maps transport failures onto ApiNetworkException', () async { final api = AcApi( config: testConfig, httpClient: MockClient((request) async => throw http.ClientException('offline')), ); await expectLater(api.reports.getReports(), throwsA(isA())); }); test('treats an empty body as an empty list', () async { final api = apiReturning('', statusCode: 204); expect(await api.reports.getReports(), isEmpty); }); }); group('Dto round trip', () { test('agent survives toJson/fromJson', () { final original = AgentDto( name: 'Ada', type: EAgentType.securityAnalyst, lastSeen: DateTime.utc(2026, 8, 28, 12), tasks: [ AgentTaskDto( title: 'Review', startTime: DateTime.utc(2026, 8, 28, 11), statusUpdates: const ['Open'], ), ], status: EAgentStatus.available, ); final restored = AgentDto.fromJson(original.toJson()); expect(restored.name, original.name); expect(restored.type, original.type); expect(restored.status, original.status); expect(restored.lastSeen, original.lastSeen); expect(restored.tasks.single.title, 'Review'); expect(restored.tasks.single.startTime, DateTime.utc(2026, 8, 28, 11)); expect(restored.tasks.single.statusUpdates, ['Open']); }); }); }