// Author: fruit.com.cn import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/models/account_profile.dart'; import 'package:fruitshorts/core/data/remote/account_api.dart'; import 'package:fruitshorts/core/data/remote/api_client.dart'; import '../support/fake_http_adapter.dart'; void main() { String envelope(Object? data, {int code = 0, String message = 'ok'}) => jsonEncode({'code': code, 'message': message, 'data': data}); AccountApi apiWith(FakeHttpAdapter adapter) => AccountApi(ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter)); test('should_get_profile_and_parse_numeric_vip_level', () async { final adapter = FakeHttpAdapter( (_) async => FakeHttpResponse( envelope({ 'uid': 'U-K19', 'nickname': 'Nadia', 'avatarUrl': 'https://cdn/avatar.png', 'gender': 'female', 'countryCode': 'ID', 'regionCode': 'JK', 'language': 'id', 'timezone': 'Asia/Jakarta', 'currency': 'IDR', 'status': 'normal', 'maskedTarget': 'n***@example.com', 'createdAt': '2026-10-19T08:00:00', 'lastLoginAt': '2026-10-19T09:00:00', 'vipLevel': 2, 'vipExpireAt': null, }), ), ); final profile = await apiWith(adapter).profile(); expect(adapter.requests.single.method, 'GET'); expect(adapter.requests.single.uri.path, '/app/me'); expect(profile.uid, 'U-K19'); expect(profile.nickname, 'Nadia'); expect(profile.vipLevel, 2); expect(profile.createdAt, DateTime(2026, 10, 19, 8)); }); test('should_send_only_changed_profile_fields_when_updating', () async { final adapter = FakeHttpAdapter( (_) async => FakeHttpResponse( envelope({'uid': 'U-K19', 'nickname': 'Nadia Baru'}), ), ); final profile = await apiWith(adapter).updateProfile( const AccountProfileUpdate( nickname: 'Nadia Baru', gender: 'female', birthday: '1999-01-02', ), ); expect(adapter.requests.single.method, 'PUT'); expect(adapter.requests.single.uri.path, '/app/me'); expect(adapter.requests.single.data, { 'nickname': 'Nadia Baru', 'gender': 'female', 'birthday': '1999-01-02', }); expect(profile.nickname, 'Nadia Baru'); }); test('should_parse_device_ids_from_strings_and_numbers', () async { final adapter = FakeHttpAdapter( (_) async => FakeHttpResponse( envelope([ { 'id': '2111012753262120962', 'userId': '2111012753262120000', 'deviceId': 'd-device-1234', 'platform': 'IOS', 'deviceModel': 'iPhone 15', 'firstSeenAt': '2026-10-18T08:00:00', 'lastSeenAt': '2026-10-19T09:00:00', 'status': 'active', 'deleted': 0, }, {'id': 7, 'deviceId': 'd-android-5678', 'platform': 'ANDROID'}, ]), ), ); final devices = await apiWith(adapter).devices(); expect(adapter.requests.single.uri.path, '/app/me/devices'); expect(devices.first.id, '2111012753262120962'); expect(devices.first.lastSeenAt, DateTime(2026, 10, 19, 9)); expect(devices.last.id, '7'); expect(devices.last.deviceModel, ''); }); test('should_parse_export_counts_and_keep_complete_json', () async { final data = { 'profile': {'uid': 'U-K19', 'nickname': 'Nadia'}, 'credentials': [ {'type': 'EMAIL', 'masked': 'n***@example.com', 'verified': true}, ], 'consents': [ {'scene': 'privacy', 'version': '2026-10-01'}, ], 'devices': [ {'id': '9', 'deviceId': 'd-device-1234', 'status': 'active'}, ], }; final adapter = FakeHttpAdapter( (_) async => FakeHttpResponse(envelope(data)), ); final export = await apiWith(adapter).export(); expect(adapter.requests.single.uri.path, '/app/me/export'); expect(export.profile.nickname, 'Nadia'); expect(export.credentials.single.masked, 'n***@example.com'); expect(export.credentials.single.verified, isTrue); expect(export.consentCount, 1); expect(export.deviceCount, 1); expect(export.rawJson, contains('"credentials"')); }); test('should_request_deactivation_with_reason_and_parse_schedule', () async { final adapter = FakeHttpAdapter( (_) async => FakeHttpResponse(envelope('2026-11-03T09:30:00')), ); final scheduledAt = await apiWith(adapter).deactivate(reason: '不再使用'); expect(adapter.requests.single.method, 'POST'); expect(adapter.requests.single.uri.path, '/app/me/deactivate'); expect(adapter.requests.single.data, {'reason': '不再使用'}); expect(scheduledAt, DateTime(2026, 11, 3, 9, 30)); }); test('should_cancel_deactivation_with_post_and_no_body', () async { final adapter = FakeHttpAdapter( (_) async => FakeHttpResponse(envelope(null)), ); await apiWith(adapter).cancelDeactivation(); expect(adapter.requests.single.method, 'POST'); expect(adapter.requests.single.uri.path, '/app/me/deactivate/cancel'); expect(adapter.requests.single.data, isNull); }); test('should_keep_backend_message_when_cancel_fails', () async { final adapter = FakeHttpAdapter( (_) async => FakeHttpResponse(envelope(null, code: 100009, message: '没有待处理的注销申请')), ); await expectLater( apiWith(adapter).cancelDeactivation(), throwsA( isA() .having((error) => error.code, 'code', 100009) .having((error) => error.message, 'message', '没有待处理的注销申请'), ), ); }); }