Files
pangolin/client/test/unit/api_client_test.dart
wangjia ba53a0d478
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
feat(client): P1 真实数据接入地基(#6 6A)
- api_config.dart:API 基址单源(消除各处重复 _kApiUrl)
- api_client.dart:统一鉴权 HTTP 客户端,401→AuthNotifier.refresh→重试一次,
  错误统一 AuthApiException;+ 单测覆盖 401 刷新重试
- 模型 me/device/plan/usage_point(对齐后端 snake_case 契约)
- account_api.dart:/v1/me、/v1/plans、/v1/me/devices(列表+删)、/v1/usage、
  /v1/redeem、/v1/ads/unlock 封装
- auth_provider:注入 AuthApi + refresh();account_providers 装配 ApiClient/AccountApi
- 顺带修复 onboarding 提交遗留的测试 stub(_NullTokenStore 缺 isOnboarded/markOnboarded)

flutter analyze 0 error;114 tests passed。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 23:32:57 +08:00

63 lines
2.0 KiB
Dart

// api_client_test.dart — ApiClient 鉴权 + 401 自动刷新重试。
import 'package:flutter_test/flutter_test.dart';
import 'package:http/testing.dart';
import 'package:http/http.dart' as http;
import 'package:pangolin_vpn/services/api_client.dart';
import 'package:pangolin_vpn/services/auth_api.dart';
void main() {
group('ApiClient', () {
test('GET 附带 Bearer 并解析 JSON', () async {
late String seenAuth;
final c = ApiClient(
baseUrl: 'http://x',
getToken: () => 'tok1',
refresh: () async => false,
client: MockClient((req) async {
seenAuth = req.headers['Authorization'] ?? '';
return http.Response('{"email":"a@b.c"}', 200);
}),
);
final body = await c.getJson('/v1/me');
expect(seenAuth, 'Bearer tok1');
expect(body['email'], 'a@b.c');
});
test('401 → refresh 成功 → 用新令牌重试一次', () async {
var token = 'old';
var calls = 0;
final c = ApiClient(
baseUrl: 'http://x',
getToken: () => token,
refresh: () async {
token = 'new';
return true;
},
client: MockClient((req) async {
calls++;
if (req.headers['Authorization'] == 'Bearer old') {
return http.Response('{"message_zh":"expired"}', 401);
}
return http.Response('{"ok":true}', 200);
}),
);
final body = await c.getJson('/v1/me');
expect(calls, 2); // 首次 401 + 刷新后重试
expect(body['ok'], true);
});
test('401 → refresh 失败 → 抛 AuthApiException(401)', () async {
final c = ApiClient(
baseUrl: 'http://x',
getToken: () => 'old',
refresh: () async => false,
client: MockClient((req) async => http.Response('{"message_zh":"expired"}', 401)),
);
expect(
() => c.getJson('/v1/me'),
throwsA(isA<AuthApiException>().having((e) => e.statusCode, 'status', 401)),
);
});
});
}