import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jinzhi_flutter_app/common/network/api_exception.dart'; import 'package:jinzhi_flutter_app/common/network/kratos_error_interceptor.dart'; import 'package:jinzhi_flutter_app/models/pb/errors/errors.pb.dart' as errors; void main() { test('classifies Kratos invalid token and preserves the raw body', () async { final body = errors.Status( code: 401, reason: 'INVALID_TOKEN', message: 'expired', ).writeToBuffer(); final dio = Dio() ..httpClientAdapter = _ErrorAdapter(body, 401) ..interceptors.add(KratosErrorInterceptor()); try { await dio.get>( '/me', options: Options(responseType: ResponseType.bytes), ); fail('request should fail'); } on DioException catch (error) { final exception = ApiException.from(error); expect(exception.kind, ApiExceptionKind.backend); expect(exception.reason, 'INVALID_TOKEN'); expect(exception.isUnauthorized, isTrue); expect(error.response!.data, body); } }); test('classifies transport, retryable server errors, and cancellation', () { final request = RequestOptions(path: '/test'); expect( ApiException.fromDio(DioException(requestOptions: request)).kind, ApiExceptionKind.transport, ); expect( ApiException.fromDio( DioException( requestOptions: request, response: Response(requestOptions: request, statusCode: 503), ), ).isRetryable, isTrue, ); expect( ApiException.fromDio( DioException(requestOptions: request, type: DioExceptionType.cancel), ).kind, ApiExceptionKind.cancelled, ); }); } class _ErrorAdapter implements HttpClientAdapter { _ErrorAdapter(this.body, this.status); final List body; final int status; @override Future fetch( RequestOptions options, Stream? requestStream, Future? cancelFuture, ) async => ResponseBody.fromBytes(body, status); @override void close({bool force = false}) {} }