import 'dart:convert'; import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:patbond_flutter/features/auth/auth_models.dart'; import 'package:patbond_flutter/features/auth/auth_repository.dart'; import 'package:patbond_flutter/features/auth/session_manager.dart'; /// 测试用内存 token 存储。 class InMemoryTokenStore implements TokenStore { final Map values = {}; @override Future read(String key) async => values[key]; @override Future write(String key, String value) async => values[key] = value; @override Future delete(String key) async => values.remove(key); } /// 测试用假认证仓库:各方法可注入行为,默认成功空实现。 class FakeAuthRepository implements AuthRepository { FakeAuthRepository({ this.loginHandler, this.registerHandler, this.restoreHandler, this.meHandler, this.updateMeHandler, }); final Future Function()? loginHandler; final Future Function()? registerHandler; final Future Function()? restoreHandler; final Future Function()? meHandler; final Future Function(UpdateMeRequest)? updateMeHandler; int loginCalls = 0; int registerCalls = 0; int logoutCalls = 0; int meCalls = 0; /// 收到的 PATCH 载荷(三态断言用:**未改字段不应出现在 map 里**)。 final List> updateMePayloads = []; @override Future login({required String username, required String password}) { loginCalls += 1; return loginHandler?.call() ?? Future.value(); } @override Future register({ required String username, required String phone, required String password, }) { registerCalls += 1; return registerHandler?.call() ?? Future.value(); } @override Future logout() async { logoutCalls += 1; } @override Future restoreSession() { return restoreHandler?.call() ?? Future.value(SessionRestoreResult.noSession); } @override Future me() async { meCalls += 1; // 缺省给一个「无昵称无头像」的样本(回退到 username 的形态): // 主壳级测试不必逐个注入。 return meHandler?.call() ?? Future.value(buildProfile()); } @override Future updateMe(UpdateMeRequest request) async { updateMePayloads.add(request.toJson()); if (updateMeHandler == null) throw UnimplementedError(); return updateMeHandler!(request); } } /// `/me` 响应样本(契约 v1.4.0 六字段全字段)。 Map sampleMeJson({ String userId = 'user-1', String username = 'llx', String? nickname, String? avatarUrl, String? phone = '+8613800138000', }) => { 'userId': userId, 'username': username, 'nickname': nickname, 'avatarUrl': avatarUrl, 'phone': phone, 'createdAt': '2026-09-01T10:00:00+08:00', }; UserProfile buildProfile({ String userId = 'user-1', String username = 'llx', String? nickname, String? avatarUrl, }) => UserProfile.fromJson( sampleMeJson( userId: userId, username: username, nickname: nickname, avatarUrl: avatarUrl, ), ); /// mock dio:用假 HttpClientAdapter 返回预设响应并记录请求。 class FakeHttpAdapter implements HttpClientAdapter { FakeHttpAdapter(this.handler); final Future Function(RequestOptions options) handler; final List requests = []; @override Future fetch( RequestOptions options, Stream? requestStream, Future? cancelFuture, ) { requests.add(options); return handler(options); } @override void close({bool force = false}) {} } ResponseBody jsonResponse(int statusCode, Map body) { return ResponseBody.fromString( jsonEncode(body), statusCode, headers: { Headers.contentTypeHeader: [Headers.jsonContentType], }, ); } Map okEnvelope(Map data) => { 'code': 0, 'message': 'ok', 'data': data, }; Map errorEnvelope(int code, [String message = 'error']) => { 'code': code, 'message': message, 'data': null, }; Map tokenDataJson({ String access = 'access-1', String refresh = 'refresh-1', }) => { 'userId': 'user-1', 'tokenType': 'Bearer', 'accessToken': access, 'accessTokenExpiresAt': '2026-09-04T13:00:00+08:00', 'refreshToken': refresh, 'refreshTokenExpiresAt': '2026-10-04T12:00:00+08:00', }; AuthTokens sampleTokens({ String access = 'access-0', String refresh = 'refresh-0', }) => AuthTokens.fromJson(tokenDataJson(access: access, refresh: refresh));