文件: lib/services/api_service_wrapper.dart:34, 53-98;lib/services/device/device_auth_service.dart:36-48
问题:
-
ApiServiceWrapper 的接口设计有一个隐式契约 bug:构造函数 ApiServiceWrapper([Dio? dio]) 允许测试时注入自定义 Dio 实例(注释"通过依赖注入创建实例",test/unit/services/api_service_wrapper_test.dart:11-16 也基于此断言),但 init() 总会无条件地:
- 把
_dio.options.baseUrl = host 覆盖注入者的 baseUrl;
- 把
_dio.httpClientAdapter = IOHttpClientAdapter(...) 覆盖注入者设置的 adapter(86-98 行);
- 把
LogInterceptor 注入到 _dio 上(注入者的拦截器链因此被破坏)。
结果:测试里注入的 mock adapter / 自定义 interceptor 全部被 init() 清掉,唯一"安全"用法是不调用 init(),但 init() 是真实业务路径必调。Provider 注入的 dio(ref.watch(apiServiceWrapperProvider) 实际不会注入 dio)没暴露点。
-
DeviceAuthService 类似:构造 private + useWrapper(ApiServiceWrapper) 一次性注入,单例持有;同时 ApiServiceWrapper._apiOverride 字段(device_auth_service.dart:43-48)允许 override,但 register() 内部直接 _api.dio.post(...),override 只换 getHost/getToken/dispose 等高层方法,dio 配置完全没法换。两个单例的依赖管理是双轨并行,容易在 Provider 树重组后出现"主 ApiServiceWrapper 已 dispose,DeviceAuthService 还在用旧引用"的悬挂场景。
影响:
- 测试困难(已经反映在 api_service_wrapper_test.dart 只有构造测试,缺少 init/调用测试);
- 真实业务里"用户改 backend 配置"调用
setConfig → init(),但若 device_auth_service 注入过 useWrapper,DeviceAuthService 持有的旧 wrapper 永远不会感知 host 变化。
修复建议:
- ApiServiceWrapper 改 immutable builder 模式:
build({host, token}) 一次性生成新 wrapper,避免 init 副作用;
- 或 init() 接受
{bool replaceAdapter, bool replaceInterceptors} 开关,让测试可以保留自定义 dio;
- DeviceAuthService 改为 Ref-scoped(不持单例),依赖注入
ApiServiceWrapper,由 Riverpod 负责生命周期。
忽略指南:在 lib/services/api_service_wrapper.dart:34 添加注释 // cr-ignore <CR_IGNORE_IID_HASH>: <你的理由>,下次审查会自动关闭。
文件: lib/services/api_service_wrapper.dart:34, 53-98;lib/services/device/device_auth_service.dart:36-48
问题:
ApiServiceWrapper的接口设计有一个隐式契约 bug:构造函数ApiServiceWrapper([Dio? dio])允许测试时注入自定义 Dio 实例(注释"通过依赖注入创建实例",test/unit/services/api_service_wrapper_test.dart:11-16 也基于此断言),但init()总会无条件地:_dio.options.baseUrl = host覆盖注入者的 baseUrl;_dio.httpClientAdapter = IOHttpClientAdapter(...)覆盖注入者设置的 adapter(86-98 行);LogInterceptor注入到 _dio 上(注入者的拦截器链因此被破坏)。结果:测试里注入的 mock adapter / 自定义 interceptor 全部被 init() 清掉,唯一"安全"用法是不调用 init(),但 init() 是真实业务路径必调。Provider 注入的 dio(
ref.watch(apiServiceWrapperProvider)实际不会注入 dio)没暴露点。DeviceAuthService类似:构造 private +useWrapper(ApiServiceWrapper)一次性注入,单例持有;同时ApiServiceWrapper._apiOverride字段(device_auth_service.dart:43-48)允许 override,但register()内部直接_api.dio.post(...),override 只换getHost/getToken/dispose等高层方法,dio 配置完全没法换。两个单例的依赖管理是双轨并行,容易在 Provider 树重组后出现"主 ApiServiceWrapper 已 dispose,DeviceAuthService 还在用旧引用"的悬挂场景。影响:
setConfig → init(),但若 device_auth_service 注入过 useWrapper,DeviceAuthService 持有的旧 wrapper 永远不会感知 host 变化。修复建议:
build({host, token})一次性生成新 wrapper,避免 init 副作用;{bool replaceAdapter, bool replaceInterceptors}开关,让测试可以保留自定义 dio;ApiServiceWrapper,由 Riverpod 负责生命周期。忽略指南:在
lib/services/api_service_wrapper.dart:34添加注释// cr-ignore <CR_IGNORE_IID_HASH>: <你的理由>,下次审查会自动关闭。