refactor(client): 网络层由官方 http 换成 dio(对外行为不变)

为什么:权威文档(AGENTS.md / CODING_STANDARDS.md)强制网络层用 dio,差异清单 A 组第 1 题
用户裁定选 A;原来用官方 http 是在无文档状态下按口头要求选的。

- ApiClient 改用 dio + BaseOptions(validateStatus 恒真、8s 超时、ResponseType.bytes 自行 UTF-8 解码)
- 新增 AuthHeaderInterceptor(Authorization / X-Device-Id)与 EnvelopeInterceptor
  (解包 {code,message,data} + 错误码映射 + 401 判定)
- 401 → 刷新 → 重发一次在 ApiClient 里编排,刻意不放在拦截器内重发:dio 的排队拦截器
  在回调里再发请求会自锁
- 接口层(auth_api/catalog_api)签名、全部 controller/logic/page 未改动
- 测试:新增假 dio adapter 替代 MockClient,断言口径一条未改;用例名改为 should_<期望>_when_<条件>;
  新增 api_client_test(解包/错误映射/请求头/查询参数/跳过重试)与 live_auth_test(真实后端 POST 链路)
- pubspec:加 dio、移除已无引用的 http
- 实测:dart analyze lib test 零告警;flutter test 155 全过;真实联调(8081)GET/POST 链路均通
This commit is contained in:
Vicky committed 2026-10-16 09:30:46 +08:00
1 parent ae49b10407
commit 1853e1845c
10 files changed
+508 -143

No files matched your search

+6 -6
View File
@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
@@ -13,6 +11,8 @@ import 'package:nontonki/core/data/services/auth_session_store.dart';
import 'package:nontonki/l10n/app_localizations.dart';
import 'package:nontonki/pages/auth/auth_view.dart';
import '../support/fake_http_adapter.dart';
/// 登录/注册页的可读证据:校验提示 + 登录/注册切换 + 未登录时不会去发请求。
void main() {
setUp(() {
@@ -26,7 +26,7 @@ void main() {
// 这个测试不联网:请求一律 500,正好用于验证"本地校验没过就不发请求"
ApiClient(
baseUrl: 'http://127.0.0.1:1',
client: MockClient((_) async => http.Response('{}', 500)),
adapter: FakeHttpAdapter((_) async => FakeHttpResponse('{}', 500)),
),
),
AuthSessionStore(),
@@ -45,7 +45,7 @@ void main() {
await tester.pumpAndSettle();
}
testWidgets('empty form shows the email error and stays on the page', (tester) async {
testWidgets('should show the email error and stay on the page when the form is empty', (tester) async {
await pumpAuthPage(tester);
expect(find.text('Welcome back'), findsOneWidget);
@@ -57,7 +57,7 @@ void main() {
expect(find.text('Welcome back'), findsOneWidget);
});
testWidgets('weak password is rejected locally', (tester) async {
testWidgets('should reject a weak password locally when only letters are typed', (tester) async {
await pumpAuthPage(tester);
await tester.enterText(find.byType(TextField).first, 'a@b.com');
@@ -68,7 +68,7 @@ void main() {
expect(find.text('Password needs both letters and numbers'), findsOneWidget);
});
testWidgets('switch link flips between sign in and sign up', (tester) async {
testWidgets('should flip between sign in and sign up when the switch link is tapped', (tester) async {
await pumpAuthPage(tester);
expect(find.text('Welcome back'), findsOneWidget);