Files
tdc114plus/app/test/auth/auth_api_client_test.dart
T

206 lines
6.2 KiB
Dart

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<ApiException>()
.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');
});
}