refactor: 拆分主页与公共组件,扫码登录改走 WinHTTP
- 抽出 lib/core 的日期/JSON 工具,收敛各页重复解析 - 主页与音乐风险页拆成组件,风险功能共用一套运行框架 - 修复设置页作为主页页签保存时弹掉根路由导致的黑屏 - 登录对齐 Astral aimedb 协议:84 位二维码校验、签名与请求头修正, 补 GlobalSign 中间证书;Windows 改用 WinHTTP 绕过服务端对 BoringSSL 的 TLS 指纹拦截(证书校验保持开启) - 补开发预览数据与测试
This commit is contained in:
1 parent
0cb5320290
commit
e9357f5969
55 files changed
+4387
-3392
No files matched your search
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ichino/core/date_time_format.dart';
|
||||
|
||||
void main() {
|
||||
test('上海时区固定为 UTC+8', () {
|
||||
expect(shanghaiOffset, const Duration(hours: 8));
|
||||
expect(
|
||||
shanghaiFromUnixSeconds(0),
|
||||
DateTime.utc(1970, 1, 1, 8),
|
||||
);
|
||||
});
|
||||
|
||||
test('formatDate 补零', () {
|
||||
expect(formatDate(DateTime(2026, 1, 5, 7, 3, 9)), '2026-01-05');
|
||||
expect(formatDate(DateTime(2026, 12, 31, 23, 59, 59)), '2026-12-31');
|
||||
});
|
||||
|
||||
test('formatDateTime 与 formatPlayDateTime 的秒与小数位', () {
|
||||
final dt = DateTime(2026, 10, 2, 22, 19, 46);
|
||||
expect(formatDateTime(dt), '2026-10-02 22:19:46');
|
||||
expect(formatPlayDateTime(dt), '2026-10-02 22:19:46.0');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ichino/core/json_value.dart';
|
||||
|
||||
void main() {
|
||||
group('jsonInt', () {
|
||||
test('int / num / 数字字符串都能读出来', () {
|
||||
expect(jsonInt(12), 12);
|
||||
expect(jsonInt(12.9), 12);
|
||||
expect(jsonInt('42'), 42);
|
||||
expect(jsonInt('-3'), -3);
|
||||
});
|
||||
|
||||
test('null、非数字字符串、其他类型退回 fallback', () {
|
||||
expect(jsonInt(null), 0);
|
||||
expect(jsonInt('12abc'), 0);
|
||||
expect(jsonInt('12abc', fallback: -1), -1);
|
||||
expect(jsonInt({'v': 1}), 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('jsonDouble', () {
|
||||
test('int / double / 数字字符串都能读出来', () {
|
||||
expect(jsonDouble(3), 3.0);
|
||||
expect(jsonDouble(3.25), 3.25);
|
||||
expect(jsonDouble('3.25'), 3.25);
|
||||
});
|
||||
|
||||
test('解析失败退回 fallback', () {
|
||||
expect(jsonDouble(null), 0.0);
|
||||
expect(jsonDouble('x'), 0.0);
|
||||
expect(jsonDouble('x', fallback: 1.5), 1.5);
|
||||
});
|
||||
});
|
||||
|
||||
group('jsonBool', () {
|
||||
test('bool、数字、常见字符串形式', () {
|
||||
expect(jsonBool(true), isTrue);
|
||||
expect(jsonBool(false), isFalse);
|
||||
expect(jsonBool(1), isTrue);
|
||||
expect(jsonBool(0), isFalse);
|
||||
expect(jsonBool('1'), isTrue);
|
||||
expect(jsonBool(' TRUE '), isTrue);
|
||||
expect(jsonBool('true'), isTrue);
|
||||
expect(jsonBool('0'), isFalse);
|
||||
expect(jsonBool('false'), isFalse);
|
||||
});
|
||||
|
||||
test('无法判断时用 fallback', () {
|
||||
expect(jsonBool(null), isFalse);
|
||||
expect(jsonBool(null, fallback: true), isTrue);
|
||||
expect(jsonBool('yes'), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('jsonString', () {
|
||||
test('null 用 fallback,其他类型转字符串', () {
|
||||
expect(jsonString(null), '');
|
||||
expect(jsonString(null, fallback: '-'), '-');
|
||||
expect(jsonString(7), '7');
|
||||
expect(jsonString('abc'), 'abc');
|
||||
});
|
||||
});
|
||||
|
||||
group('jsonMap / jsonMapList', () {
|
||||
test('jsonMap 只接受 Map<String, dynamic>', () {
|
||||
final map = <String, dynamic>{'a': 1};
|
||||
expect(jsonMap(map), same(map));
|
||||
expect(jsonMap(null), isEmpty);
|
||||
expect(jsonMap([1, 2]), isEmpty);
|
||||
});
|
||||
|
||||
test('jsonMapList 丢掉非 Map 元素,其余原样保留', () {
|
||||
final rows = [
|
||||
<String, dynamic>{'id': 1},
|
||||
'oops',
|
||||
null,
|
||||
<String, dynamic>{'id': 2},
|
||||
];
|
||||
expect(jsonMapList(rows).map((e) => e['id']), [1, 2]);
|
||||
expect(jsonMapList(null), isEmpty);
|
||||
expect(jsonMapList(<String, dynamic>{'id': 1}), isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ichino/config/strings.dart';
|
||||
import 'package:ichino/config/title_server_config.dart';
|
||||
import 'package:ichino/dev/ui_preview_data.dart';
|
||||
import 'package:ichino/models/session_model.dart';
|
||||
import 'package:ichino/pages/about_page.dart';
|
||||
import 'package:ichino/pages/best50_page.dart';
|
||||
import 'package:ichino/pages/high_risk_feature_page.dart';
|
||||
import 'package:ichino/pages/home/home_tab_view.dart';
|
||||
import 'package:ichino/pages/home_page.dart';
|
||||
import 'package:ichino/pages/settings_page.dart';
|
||||
import 'package:ichino/pages/ticket_page.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
await TitleServerConfigHolder().clear();
|
||||
SessionModel.instance.reset();
|
||||
});
|
||||
|
||||
testWidgets('主页启动时只构建首页,其他页签首次访问才创建', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(home: HomePage(userId: 1, token: 't')),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(HomeTabView), findsOneWidget);
|
||||
expect(find.byType(TicketPage), findsNothing);
|
||||
expect(find.byType(Best50Page), findsNothing);
|
||||
expect(find.byType(HighRiskFeaturePage), findsNothing);
|
||||
expect(find.byType(SettingsPage), findsNothing);
|
||||
expect(find.byType(AboutPage), findsNothing);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.bar_chart_outlined));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(Best50Page), findsOneWidget);
|
||||
expect(find.widgetWithText(AppBar, AppStrings.tabBest50), findsOneWidget);
|
||||
// 其余页签仍未构建。
|
||||
expect(find.byType(TicketPage), findsNothing);
|
||||
expect(find.byType(SettingsPage), findsNothing);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.settings_outlined));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(SettingsPage), findsOneWidget);
|
||||
// 已访问过的页签保留在 IndexedStack 里,切换回来不重建。
|
||||
expect(find.byType(Best50Page, skipOffstage: false), findsOneWidget);
|
||||
expect(find.byType(TicketPage), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('有预览种子数据时,未配置 Title Server 也展示主页数据', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 1600);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.reset);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: HomePage(
|
||||
userId: uiPreviewUserId,
|
||||
token: uiPreviewToken,
|
||||
restored: SessionRestoreResult(
|
||||
reusedCookie: true,
|
||||
userData: uiPreviewUserData,
|
||||
preview: uiPreviewPreview,
|
||||
loggedIn: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text(AppStrings.titleServerNotConfigured), findsNothing);
|
||||
expect(find.text('Ichino'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -11,14 +11,17 @@ void main() {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pump();
|
||||
|
||||
// 72 位:截取的后 64 位里没有 SGWCMAID 前缀
|
||||
// 72 位:既不是 84 位二维码串,也不是二维码链接
|
||||
await tester.enterText(find.byType(TextField), 'ABCDEF' * 12);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text(AppStrings.login));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text(AppStrings.qrTokenUnrecognized), findsOneWidget);
|
||||
expect(
|
||||
find.textContaining(AppStrings.qrTokenUnrecognized),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('粘贴连接信息时按钮切成「恢复会话」', (tester) async {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ichino/config/title_server_config.dart';
|
||||
import 'package:ichino/services/api_service.dart';
|
||||
|
||||
/// 扫码登录的输入归一化与 Keychip 拼装:纯逻辑,不发请求。
|
||||
void main() {
|
||||
const payload = 'A63E01C304710072610022219460000000000000000000000000000000000000';
|
||||
const qrString = 'SGWCMAID261002221946$payload';
|
||||
const qrUrl =
|
||||
'https://wq.wahlap.net/qrcode/req/MAID261002221946$payload.html'
|
||||
'?l=123456&t=261002221946&d=1';
|
||||
|
||||
group('normalizeQr / extractQRCode', () {
|
||||
test('84 位二维码串取后 64 位载荷', () {
|
||||
expect(payload.length, 64);
|
||||
expect(qrString.length, 84);
|
||||
expect(ApiService.extractQRCode(qrString), payload);
|
||||
});
|
||||
|
||||
test('二维码链接还原成同一条串', () {
|
||||
expect(ApiService.normalizeQr(qrUrl), qrString);
|
||||
expect(ApiService.extractQRCode(qrUrl), payload);
|
||||
});
|
||||
|
||||
test('长度、前缀、GameID、载荷字符集不合预期时抛 FormatException', () {
|
||||
expect(
|
||||
() => ApiService.normalizeQr('ABCDEF' * 12),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => ApiService.normalizeQr('XGWC${qrString.substring(4)}'),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => ApiService.normalizeQr('SGWCXBID${qrString.substring(8)}'),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => ApiService.normalizeQr('SGWCMAID261002221946${'G' * 64}'),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => ApiService.normalizeQr('https://wq.wahlap.net/other/x.html'),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('formatTimestamp', () {
|
||||
test('本机本地墙上时间,yymmddHHMMss', () {
|
||||
expect(
|
||||
ApiService.formatTimestamp(DateTime(2026, 10, 2, 22, 19, 46)),
|
||||
'261002221946',
|
||||
);
|
||||
expect(
|
||||
ApiService.formatTimestamp(DateTime(2026, 1, 5, 7, 3, 9)),
|
||||
'260105070309',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('TitleServerConfig.fullKeychipId', () {
|
||||
TitleServerConfig config(String keychip, {int placeId = 1007}) =>
|
||||
TitleServerConfig(
|
||||
titleServerUrl: 'https://example.invalid',
|
||||
aesKey: 'k',
|
||||
aesIv: 'v',
|
||||
clientId: 'A63E01C3047',
|
||||
placeId: placeId,
|
||||
keychipId: keychip,
|
||||
);
|
||||
|
||||
test('完整形式(带连字符 / 连写 / 小写)归一到大写带连字符', () {
|
||||
expect(config('A63E-01C30471007').fullKeychipId, 'A63E-01C30471007');
|
||||
expect(config('A63E01C30471007').fullKeychipId, 'A63E-01C30471007');
|
||||
expect(config('a63e-01c30471007').fullKeychipId, 'A63E-01C30471007');
|
||||
});
|
||||
|
||||
test('只填 11 位短值时按 placeId 补全', () {
|
||||
expect(config('A63E01C3047').fullKeychipId, 'A63E-01C30471007');
|
||||
});
|
||||
|
||||
test('短于 11 位当没配,交给登录层报错', () {
|
||||
expect(config('').fullKeychipId, isEmpty);
|
||||
expect(config('A63E01').fullKeychipId, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ichino/config/strings.dart';
|
||||
import 'package:ichino/widgets/risk/risk_list_widgets.dart';
|
||||
|
||||
Widget _wrap(Widget child) =>
|
||||
MaterialApp(home: Scaffold(body: Column(children: [child])));
|
||||
|
||||
void main() {
|
||||
testWidgets('RiskPendingList 空列表显示提示,非空显示行', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
_wrap(
|
||||
const RiskPendingList(
|
||||
title: '待解锁',
|
||||
emptyHint: '还没有添加',
|
||||
count: 0,
|
||||
rows: [],
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.text('待解锁 (0)'), findsOneWidget);
|
||||
expect(find.text('还没有添加'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(
|
||||
RiskPendingList(
|
||||
title: '待解锁',
|
||||
emptyHint: '还没有添加',
|
||||
count: 1,
|
||||
rows: const [
|
||||
RiskPendingRow(leading: '#834', trailing: 'Master', onRemove: _noop),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.text('待解锁 (1)'), findsOneWidget);
|
||||
expect(find.text('还没有添加'), findsNothing);
|
||||
expect(find.textContaining('#834'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('RiskPendingRow 禁用时删除按钮不触发回调', (tester) async {
|
||||
var removed = 0;
|
||||
await tester.pumpWidget(
|
||||
_wrap(
|
||||
RiskPendingRow(
|
||||
leading: '#1',
|
||||
trailing: 'x',
|
||||
onRemove: () => removed++,
|
||||
enabled: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
final button = tester.widget<IconButton>(find.byType(IconButton));
|
||||
expect(button.onPressed, isNull);
|
||||
expect(find.byTooltip(AppStrings.listRemoveTooltip), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(
|
||||
RiskPendingRow(
|
||||
leading: '#1',
|
||||
trailing: 'x',
|
||||
onRemove: () => removed++,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.byTooltip(AppStrings.listRemoveTooltip));
|
||||
expect(removed, 1);
|
||||
});
|
||||
|
||||
testWidgets('RiskNumberField 与 RiskAddButton 共用统一样式', (tester) async {
|
||||
final controller = TextEditingController();
|
||||
addTearDown(controller.dispose);
|
||||
var added = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(
|
||||
Column(
|
||||
children: [
|
||||
RiskNumberField(controller: controller, label: 'A', hint: 'B'),
|
||||
RiskAddButton(onPressed: () => added++),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.text('A'), findsOneWidget);
|
||||
expect(find.text('B'), findsOneWidget);
|
||||
await tester.tap(find.text(AppStrings.listAddButton));
|
||||
expect(added, 1);
|
||||
});
|
||||
}
|
||||
|
||||
void _noop() {}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ichino/models/session_model.dart';
|
||||
|
||||
void main() {
|
||||
final session = SessionModel.instance;
|
||||
|
||||
setUp(session.reset);
|
||||
tearDown(session.reset);
|
||||
|
||||
test('updateCookies 只在 Cookie 变化时通知,空值忽略', () {
|
||||
var notified = 0;
|
||||
session.addListener(() => notified++);
|
||||
|
||||
session.updateCookies(null);
|
||||
session.updateCookies('');
|
||||
expect(notified, 0);
|
||||
expect(session.cookies, isNull);
|
||||
|
||||
session.updateCookies('JSESSIONID=A');
|
||||
expect(session.cookies, 'JSESSIONID=A');
|
||||
expect(notified, 1);
|
||||
|
||||
// 同一个值重复回写不制造额外重建。
|
||||
session.updateCookies('JSESSIONID=A');
|
||||
expect(notified, 1);
|
||||
|
||||
session.updateCookies('JSESSIONID=B');
|
||||
expect(session.cookies, 'JSESSIONID=B');
|
||||
expect(notified, 2);
|
||||
});
|
||||
|
||||
test('reset 清空 Cookie 与登录态并通知', () {
|
||||
session.updateCookies('JSESSIONID=A');
|
||||
var notified = 0;
|
||||
session.addListener(() => notified++);
|
||||
|
||||
session.reset();
|
||||
|
||||
expect(session.cookies, isNull);
|
||||
expect(session.gameLogin, isNull);
|
||||
expect(notified, 1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ichino/config/strings.dart';
|
||||
import 'package:ichino/config/title_server_config.dart';
|
||||
import 'package:ichino/models/session_model.dart';
|
||||
import 'package:ichino/pages/settings_page.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
await TitleServerConfigHolder().clear();
|
||||
SessionModel.instance.reset();
|
||||
});
|
||||
|
||||
testWidgets('设置页作为主页页签保存时不会弹掉根路由', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(body: SettingsPage(showAppBar: false)),
|
||||
),
|
||||
);
|
||||
|
||||
final fields = find.byType(TextFormField);
|
||||
expect(fields, findsNWidgets(11));
|
||||
for (var i = 0; i < 11; i++) {
|
||||
await tester.enterText(fields.at(i), 'value');
|
||||
}
|
||||
|
||||
final save = find.widgetWithText(FilledButton, AppStrings.save);
|
||||
await tester.ensureVisible(save);
|
||||
await tester.tap(save);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 根路由还在,页面没有变成黑屏;改为用 SnackBar 提示已保存。
|
||||
expect(find.byType(SettingsPage), findsOneWidget);
|
||||
expect(find.text(AppStrings.configSaved), findsOneWidget);
|
||||
expect(tester.state<NavigatorState>(find.byType(Navigator)).canPop(), isFalse);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:ichino/config/title_server_config.dart';
|
||||
import 'package:ichino/services/title_api_service.dart';
|
||||
|
||||
const _config = TitleServerConfig(
|
||||
titleServerUrl: 'http://127.0.0.1:9',
|
||||
aesKey: '0123456789abcdef',
|
||||
aesIv: 'fedcba9876543210',
|
||||
clientId: 'A63E01C2805',
|
||||
);
|
||||
|
||||
void main() {
|
||||
test('describeApiError 只取协议层异常的 message', () {
|
||||
expect(
|
||||
describeApiError(const TitleApiException('GetUserDataApi returned 500')),
|
||||
'GetUserDataApi returned 500',
|
||||
);
|
||||
expect(describeApiError(const FormatException('bad qr')), 'FormatException: bad qr');
|
||||
expect(describeApiError('oops'), 'oops');
|
||||
});
|
||||
|
||||
test('响应头里的 JSESSIONID 会写回实例并通知调用方', () async {
|
||||
final captured = <String>[];
|
||||
final sentCookies = <String?>[];
|
||||
final service = TitleApiService(
|
||||
_config,
|
||||
cookies: 'JSESSIONID=OLD',
|
||||
onCookiesChanged: captured.add,
|
||||
);
|
||||
|
||||
var requests = 0;
|
||||
final client = MockClient((request) async {
|
||||
sentCookies.add(request.headers['Cookie']);
|
||||
requests++;
|
||||
if (requests == 1) {
|
||||
return http.Response(
|
||||
'',
|
||||
500,
|
||||
headers: {'Set-Cookie': 'JSESSIONID=NEW; Path=/; HttpOnly'},
|
||||
);
|
||||
}
|
||||
return http.Response('', 500);
|
||||
});
|
||||
|
||||
await http.runWithClient(() async {
|
||||
// Cookie 捕获发生在状态码检查之前:即使这次请求失败,
|
||||
// 服务器轮换的 Cookie 也不能丢。
|
||||
await expectLater(
|
||||
service.getUserData(1),
|
||||
throwsA(isA<TitleApiException>()),
|
||||
);
|
||||
await expectLater(
|
||||
service.getUserData(1),
|
||||
throwsA(isA<TitleApiException>()),
|
||||
);
|
||||
}, () => client);
|
||||
|
||||
expect(captured, ['JSESSIONID=NEW']);
|
||||
expect(service.cookies, 'JSESSIONID=NEW');
|
||||
expect(sentCookies, ['JSESSIONID=OLD', 'JSESSIONID=NEW']);
|
||||
});
|
||||
|
||||
test('响应没有 JSESSIONID 时不回写', () async {
|
||||
final captured = <String>[];
|
||||
final service = TitleApiService(
|
||||
_config,
|
||||
cookies: 'JSESSIONID=OLD',
|
||||
onCookiesChanged: captured.add,
|
||||
);
|
||||
|
||||
await http.runWithClient(() async {
|
||||
await expectLater(
|
||||
service.getUserData(1),
|
||||
throwsA(isA<TitleApiException>()),
|
||||
);
|
||||
}, () => MockClient((_) async => http.Response('', 500)));
|
||||
|
||||
expect(captured, isEmpty);
|
||||
expect(service.cookies, 'JSESSIONID=OLD');
|
||||
});
|
||||
|
||||
test('lastRawResponse 是实例状态,不会跨实例串数据', () {
|
||||
final first = TitleApiService(_config);
|
||||
final second = TitleApiService(_config);
|
||||
|
||||
first.lastRawResponse = '{"userData":{}}';
|
||||
|
||||
expect(first.lastRawResponse, '{"userData":{}}');
|
||||
expect(second.lastRawResponse, isNull);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user