import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:tdc114plus/src/core/network/api_error.dart'; import 'package:tdc114plus/src/features/auth/data/auth_api_client.dart'; import 'package:tdc114plus/src/features/auth/domain/auth_models.dart'; void main() { test('posts phone login request to tdc114plus namespace', () async { final client = AuthApiClient( httpClient: MockClient((request) async { expect( request.url.toString(), 'https://sso.example.test/api/v1/tdc114plus/auth/phone-login', ); expect(request.method, 'POST'); expect(request.headers['content-type'], 'application/json'); expect(jsonDecode(request.body)['phoneNumber'], '01012345678'); return http.Response( jsonEncode({ 'status': 'ok', 'token': 'session-token', 'expiresAt': '2026-07-02T12:00:00Z', 'user': { 'id': 'user-uuid', 'name': 'User One', 'phoneNumber': '+821012345678', 'tenantId': 'tenant-uuid', 'tenantName': 'Hanmac', 'tenantSlug': 'hanmac', }, }), 200, headers: {'content-type': 'application/json'}, ); }), baseUri: Uri.parse('https://sso.example.test'), ); final response = await client.phoneLogin( const PhoneLoginRequest( phoneNumber: '01012345678', device: LoginDeviceInfo( platform: 'android', appVersion: '0.1.0', deviceName: 'Pixel 8', ), ), ); expect(response.token, 'session-token'); expect(response.user.tenantSlug, 'hanmac'); }); test('throws ApiException when login fails', () async { final client = AuthApiClient( httpClient: MockClient((request) async { return http.Response( jsonEncode({ 'error': 'login failed', 'code': 'login_failed', 'details': {}, }), 401, headers: {'content-type': 'application/json'}, ); }), baseUri: Uri.parse('https://sso.example.test'), ); expect( () => client.phoneLogin( const PhoneLoginRequest( phoneNumber: '01012345678', device: LoginDeviceInfo( platform: 'android', appVersion: '0.1.0', deviceName: 'Pixel 8', ), ), ), throwsA( isA() .having((error) => error.statusCode, 'statusCode', 401) .having((error) => error.code, 'code', 'login_failed'), ), ); }); test('requests phone link from auth server namespace', () async { final client = AuthApiClient( httpClient: MockClient((request) async { expect( request.url.toString(), 'https://sso.example.test/api/v1/auth/link/init', ); expect(request.method, 'POST'); expect(jsonDecode(request.body)['phoneNumber'], '01012345678'); return http.Response( jsonEncode({ 'status': 'pending', 'pendingRef': 'pending-ref', 'expiresIn': 180, 'pollInterval': 3, 'resendAfter': 30, 'provider': 'Ory (Kratos/Hydra)', }), 200, headers: {'content-type': 'application/json'}, ); }), baseUri: Uri.parse('https://sso.example.test'), ); final response = await client.requestPhoneLoginLink( const PhoneLoginLinkInitRequest( phoneNumber: '01012345678', device: LoginDeviceInfo( platform: 'android', appVersion: '0.1.0', deviceName: 'Pixel 8', ), ), ); expect(response.pendingRef, 'pending-ref'); expect(response.interval, 3); }); test('polls headless phone link and parses completed session', () async { final client = AuthApiClient( httpClient: MockClient((request) async { expect( request.url.toString(), 'https://sso.example.test/api/v1/auth/link/poll', ); expect(jsonDecode(request.body)['pendingRef'], 'pending-ref'); return http.Response( jsonEncode({ 'status': 'ok', 'session': { 'status': 'ok', 'accessToken': 'session-token', 'expiresAt': '2026-07-02T12:00:00Z', 'user': { 'id': 'user-uuid', 'name': 'User One', 'phoneNumber': '+821012345678', 'tenantId': 'tenant-uuid', 'tenantName': 'Hanmac', 'tenantSlug': 'hanmac', }, }, }), 200, headers: {'content-type': 'application/json'}, ); }), baseUri: Uri.parse('https://sso.example.test'), ); final response = await client.pollPhoneLoginLink( const PhoneLoginLinkPollRequest(pendingRef: 'pending-ref'), ); expect(response.session?.token, 'session-token'); expect(response.session?.user.tenantSlug, 'hanmac'); }); test('parses completed auth-server poll response with top-level accessToken', () async { final client = AuthApiClient( httpClient: MockClient((request) async { expect( request.url.toString(), 'https://auth.example.test/api/v1/auth/link/poll', ); return http.Response( jsonEncode({ 'status': 'ok', 'accessToken': 'app-session-jwt', 'expiresAt': '2026-07-13T16:32:56+09:00', 'user': { 'id': 'baron-user', 'name': 'Baron User', 'phoneNumber': '01091365338', }, }), 200, headers: {'content-type': 'application/json'}, ); }), baseUri: Uri.parse('https://auth.example.test'), ); final response = await client.pollPhoneLoginLink( const PhoneLoginLinkPollRequest(pendingRef: 'pending-ref'), ); expect(response.status, 'ok'); expect(response.session?.token, 'app-session-jwt'); expect(response.session?.user.phoneNumber, '01091365338'); }); }