Files
sxchnge/frontend/packages/sxchnge_auth/test/backend_test.dart
T

149 lines
5.0 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:sxchnge_auth/sxchnge_auth.dart';
void main() {
http.Response response(Map<String, Object?> data, [int status = 202]) =>
http.Response(jsonEncode({'data': data}), status);
test('signup receipt avoids duplicate delivery and subsequent resend reaches API', () async {
final paths = <String>[];
final service = BackendAuthServices(
baseUri: Uri.parse('https://api.example/api/v1'),
clientId: 'web',
client: MockClient((request) async {
paths.add(request.url.path);
if (request.url.path.endsWith('/signup')) {
expect(jsonDecode(request.body)['password'], ' password preserved ');
return response({'userId': 'user'});
}
return response({'accepted': true});
}),
);
addTearDown(service.close);
final result = await service.signup(
const SignupRequest(
name: 'User',
email: 'USER@example.com',
password: ' password preserved ',
locale: 'en',
),
);
expect(result.isSimulated, isFalse);
await service.send(email: 'user@example.com', locale: 'en');
expect(paths, ['/api/v1/accounts/signup']);
await service.send(email: 'user@example.com', locale: 'en');
expect(paths.last, '/api/v1/accounts/verification/send');
});
test('login confirms backend session only after factor and consumes local grant once', () async {
final now = DateTime.now();
var verified = false;
final service = BackendAuthServices(
baseUri: Uri.parse('https://api.example/api/v1'),
clientId: 'web',
client: MockClient((request) async {
final body = jsonDecode(request.body);
if (request.url.path.endsWith('/login')) {
expect(body['login'], {
'login': 'user@example.com',
'password': ' password ',
});
expect(body['clientId'], 'web');
return response({
'userId': 'user',
'pendingToken': {'token': 'signed-pending'},
'target': 'u***@example.com',
'expiresAt': now.add(const Duration(minutes: 10)).toIso8601String(),
'resendAt': now.add(const Duration(minutes: 1)).toIso8601String(),
});
}
expect(request.url.path, '/api/v1/accounts/login/verify');
expect(body['pendingToken'], 'signed-pending');
if (body['code'] != '123456')
return response({'error': 'invalid'}, 400);
verified = true;
return response({
'account': {'id': 'user'},
'accessToken': {'token': 'access'},
'refreshToken': {'token': 'refresh'},
}, 200);
}),
);
addTearDown(service.close);
final result = await service.login(
const LoginRequest(
email: 'user@example.com',
password: ' password ',
locale: 'en',
),
);
expect(result.challenge, isNotNull);
expect(service.session, isNull);
final challenge = result.challenge!;
expect(challenge.id, isNot('signed-pending'));
await expectLater(
service.verify(challenge.id, '000000'),
throwsA(isA<FactorException>()),
);
expect(verified, isFalse);
expect(service.session, isNull);
final grant = await service.verify(challenge.id, '123456');
final accepted = await service.completeLogin(challenge, grant);
expect(accepted.userId, 'user');
expect(accepted.isSimulated, isFalse);
expect(service.session!.accessToken, 'access');
await expectLater(
service.completeLogin(challenge, grant),
throwsA(isA<AuthException>()),
);
await expectLater(
service.authorize(FactorPurpose.payout, 'payment', grant.token),
throwsA(isA<FactorException>()),
);
});
test(
'failed delivery preserves resend idempotency and never claims success',
() async {
final keys = <String>[];
final service = BackendAuthServices(
baseUri: Uri.parse('https://api.example/api/v1'),
clientId: 'web',
client: MockClient((request) async {
keys.add(jsonDecode(request.body)['idempotencyKey'] as String);
return response({'error': 'unavailable'}, 503);
}),
);
addTearDown(service.close);
for (var i = 0; i < 2; i++) {
await expectLater(
service.send(email: 'user@example.com', locale: 'en'),
throwsA(isA<VerificationException>()),
);
}
expect(keys[0], keys[1]);
},
);
test('expired activation maps to recovery state', () async {
final service = BackendAuthServices(
baseUri: Uri.parse('https://api.example/api/v1'),
clientId: 'web',
client: MockClient((_) async => response({'error': 'expired'}, 400)),
);
addTearDown(service.close);
await expectLater(
service.confirm(token: 'expired'),
throwsA(
isA<VerificationException>().having(
(e) => e.code,
'code',
VerificationError.expired,
),
),
);
});
}