import 'package:flutter_test/flutter_test.dart'; import 'package:alt_client/src/contracts/alt_api_result.dart'; import 'package:alt_client/src/generated/alt/v1/backtest.pb.dart'; import 'package:alt_client/src/generated/alt/v1/common.pb.dart'; import 'package:alt_client/src/generated/alt/v1/market.pb.dart'; void main() { group('AltApiError mapping', () { test('fromErrorInfo preserves stable code and message', () { final info = ErrorInfo(code: 'NOT_FOUND', message: 'instrument missing'); final error = AltApiError.fromErrorInfo(info); expect(error.code, equals('NOT_FOUND')); expect(error.message, equals('instrument missing')); }); test('keeps empty fields when ErrorInfo is empty', () { final error = AltApiError.fromErrorInfo(ErrorInfo()); expect(error.code, isEmpty); expect(error.message, isEmpty); }); test('value equality is based on code and message', () { final a = AltApiError.fromErrorInfo( ErrorInfo(code: 'E1', message: 'm'), ); final b = const AltApiError(code: 'E1', message: 'm'); expect(a, equals(b)); expect(a.hashCode, equals(b.hashCode)); }); }); group('AltApiResult variants', () { test('success carries the decoded value', () { const result = AltApiResult.success(42); expect(result, isA>()); expect((result as AltApiSuccess).value, equals(42)); }); test('apiError carries the mapped error', () { final error = AltApiError.fromErrorInfo( ErrorInfo(code: 'BAD', message: 'rejected'), ); final result = AltApiResult.apiError(error); expect(result, isA>()); expect((result as AltApiFailure).error, equals(error)); }); test('transportFailure stays a stable state without ErrorInfo', () { const result = AltApiResult.transportFailure( AltTransportKind.timeout, detail: 'no response within 5s', ); expect(result, isA>()); final failure = result as AltTransportFailure; expect(failure.kind, equals(AltTransportKind.timeout)); expect(failure.detail, equals('no response within 5s')); }); test('transportFailure supports a bare kind with no detail', () { const result = AltApiResult.transportFailure(AltTransportKind.disconnected); final failure = result as AltTransportFailure; expect(failure.kind, equals(AltTransportKind.disconnected)); expect(failure.detail, isNull); }); test('exhaustive switch covers every variant', () { String describe(AltApiResult result) { return switch (result) { AltApiSuccess() => 'success', AltApiFailure() => 'apiError', AltTransportFailure() => 'transport', }; } expect(describe(const AltApiResult.success(1)), equals('success')); expect( describe( const AltApiResult.apiError( AltApiError(code: 'E', message: 'm'), ), ), equals('apiError'), ); expect( describe( const AltApiResult.transportFailure(AltTransportKind.unknown), ), equals('transport'), ); }); }); group('altResponseError extraction', () { test('reads typed error from each supported response', () { final error = ErrorInfo(code: 'X', message: 'boom'); final responses = [ ListInstrumentsResponse(error: error), ListBarsResponse(error: error), StartBacktestResponse(error: error), GetBacktestRunResponse(error: error), GetBacktestResultResponse(error: error), ListBacktestRunsResponse(error: error), GetBacktestRunDetailResponse(error: error), CompareBacktestRunsResponse(error: error), ]; for (final response in responses) { final extracted = altResponseError(response); expect(extracted, isNotNull, reason: '${response.runtimeType}'); expect(extracted!.code, equals('X')); expect(extracted.message, equals('boom')); } }); test('returns null when a supported response has no error set', () { expect(altResponseError(ListInstrumentsResponse()), isNull); expect(altResponseError(ListBarsResponse()), isNull); expect(altResponseError(GetBacktestRunDetailResponse()), isNull); }); test('returns null for unsupported response types', () { expect(altResponseError(HelloResponse()), isNull); expect(altResponseError(ListInstrumentsRequest()), isNull); expect(altResponseError('not a message'), isNull); }); }); }