Files
jiu/client/test/api_forbidden_flow_test.dart
wangjia 32bd64d676 feat(client): 实时授权状态与会话失效处理
- 心跳读取 /auth/ping 回带的授权概况,直接刷新横幅/状态栏/只读门禁,省去单独轮询 /license/info
- 账号被停用/删除(401 USER_DISABLED)即强制重新登录
- 令牌持久化经串行队列 + 会话代号守卫,杜绝续期写入与登出交叉把失效 token 写回
- 写操作门禁(write_guard)+ 授权文案(license_copy)按 grace/readonly/locked 分阶段降级

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 07:34:07 +08:00

84 lines
2.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:jiu_client/core/api/api_client.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('ApiClient 403 拦截 → onForbidden 回调', () {
test('写请求收到 403 时,onForbidden 收到响应体(code=READONLY_USER', () async {
dynamic captured;
final client = ApiClient(
token: 'test-token',
onForbidden: (body) => captured = body,
);
final adapter = DioAdapter(
dio: client.dioForTest,
matcher: const FullHttpRequestMatcher(),
);
adapter.onPost(
'/products',
(server) => server.reply(403, {'error': 'readonly user', 'code': 'READONLY_USER'}),
data: {'name': '测试'},
);
await expectLater(
client.post('/products', data: {'name': '测试'}),
throwsA(isA<DioException>()),
);
expect(captured, isA<Map>());
expect(captured['code'], 'READONLY_USER');
});
test('授权过期 403phase=readonly)时,onForbidden 收到 phase 字段', () async {
dynamic captured;
final client = ApiClient(
token: 'test-token',
onForbidden: (body) => captured = body,
);
final adapter = DioAdapter(
dio: client.dioForTest,
matcher: const FullHttpRequestMatcher(),
);
adapter.onPost(
'/stock-in',
(server) => server.reply(403, {'error': 'license expired', 'phase': 'readonly'}),
data: {'x': 1},
);
await expectLater(
client.post('/stock-in', data: {'x': 1}),
throwsA(isA<DioException>()),
);
expect(captured, isA<Map>());
expect(captured['phase'], 'readonly');
});
test('非 403 错误不触发 onForbidden', () async {
var called = false;
final client = ApiClient(
token: 'test-token',
onForbidden: (_) => called = true,
);
final adapter = DioAdapter(
dio: client.dioForTest,
matcher: const FullHttpRequestMatcher(),
);
adapter.onGet(
'/products',
(server) => server.reply(404, {'error': 'not found'}),
);
await expectLater(
client.get('/products'),
throwsA(isA<DioException>()),
);
expect(called, isFalse);
});
});
}