fix:参考金值,研听,研值工程通用通用工程公共能力,重点对比网络请求、登录认证、状态管理、本地存储、WebView、推送、版本更新、一键登录、主题/UI 等模块,确定后续统一整改方案

This commit is contained in:
jingyun committed 2026-09-10 09:52:36 +08:00
1 parent 6997a65c96
commit d364c6d908
46 files changed
+2060 -395

No files matched your search

@@ -0,0 +1,74 @@
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<List<int>>(
'/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<void>(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<int> body;
final int status;
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
) async => ResponseBody.fromBytes(body, status);
@override
void close({bool force = false}) {}
}
@@ -0,0 +1,74 @@
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jinzhi_flutter_app/common/network/auth_interceptor.dart';
import 'package:jinzhi_flutter_app/common/storage/preferences_storage.dart';
import 'package:jinzhi_flutter_app/common/storage/secure_identity_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('external absolute URLs lose all backend identity headers', () async {
SharedPreferences.setMockInitialValues({});
final preferences = await SharedPreferences.getInstance();
final session = AuthSessionStorage(
SecureIdentityStorage(
PreferencesStorage(preferences),
secureStorage: _MemorySecureStorage(),
),
);
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'))
..interceptors.add(
AuthInterceptor(
session,
backendOrigin: Uri.parse('https://api.example.com'),
),
)
..httpClientAdapter = _CaptureAdapter();
final response = await dio.get<void>(
'https://cdn.example.net/ad.png',
options: Options(
headers: {
'Authorization': 'Bearer leaked',
'X-Device-Id': 'device',
'User-iOS-IDFA': 'idfa',
'User-DeviceToken': 'push',
},
),
);
final headers = response.requestOptions.headers.keys
.map((key) => key.toLowerCase())
.toSet();
expect(headers, isNot(contains('authorization')));
expect(headers, isNot(contains('x-device-id')));
expect(headers, isNot(contains('user-ios-idfa')));
expect(headers, isNot(contains('user-devicetoken')));
});
}
class _MemorySecureStorage implements SecureKeyValueStorage {
final Map<String, String> values = {};
@override
Future<void> delete(String key) async => values.remove(key);
@override
Future<String?> read(String key) async => values[key];
@override
Future<void> write(String key, String value) async => values[key] = value;
}
class _CaptureAdapter implements HttpClientAdapter {
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
) async => ResponseBody.fromString('', 204);
@override
void close({bool force = false}) {}
}