Skip to content

Commit

Permalink
chore(at_auth): Added missing export and formatted code to 80 width
Browse files Browse the repository at this point in the history
  • Loading branch information
Doug Todd committed Feb 21, 2025
1 parent 81c0b80 commit 5c3cc93
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 42 deletions.
4 changes: 4 additions & 0 deletions packages/at_auth/lib/at_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,22 @@ export 'src/enroll/at_enrollment_base.dart';
export 'src/enroll/at_enrollment_response.dart';
// The abstract class contains fields related to enrollment request
export 'src/enroll/base_enrollment_request.dart';

/// The class contains fields to submit enrollment request for APKAM keys which generate keys for
/// an application with restricted access to the namespaces.
export 'src/enroll/enrollment_request.dart';

/// This class serves as the entity responsible for either approving or denying an enrollment request
export 'src/enroll/enrollment_request_decision.dart';

/// The class stores enrollment request details. It notifies the approving app upon receiving a
/// request from the requesting app, for approval or denial.
export 'src/enroll/enrollment_server_response.dart';
export 'src/exception/at_auth_exceptions.dart';
export 'src/keys/at_auth_keys.dart';
export 'src/onboard/at_onboarding_request.dart';
export 'src/onboard/at_onboarding_response.dart';
export 'src/registrar/registrar.dart';

/// Global constant to access [AtAuthInterface].
///
Expand Down
4 changes: 4 additions & 0 deletions packages/at_auth/lib/src/registrar/registrar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export './registrar_service_base.dart';
export './registrar_service_impl.dart';
export './registrar_exception.dart';
export './registrar_validate_person_response.dart';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'registar_validate_person_response.dart';
import 'registrar_validate_person_response.dart';

abstract interface class RegistrarServiceBase {
/// Gets a free atSign from the registrar.
Expand All @@ -11,7 +11,8 @@ abstract interface class RegistrarServiceBase {

/// This request is used to validate the person registering for an atSign by verifying the one-time password that was
/// sent to the email address provided. The one-time password is valid for 15 minutes.
Future<ValidatePersonResponse> validatePerson({required String atSign, required String email, required String otp});
Future<ValidatePersonResponse> validatePerson(
{required String atSign, required String email, required String otp});

/// This request is used to check whether the person attempting to activate an atSign is its rightful owner.
/// The request takes an atSign and sends a one-time password to the email address and/or phone number associated
Expand All @@ -20,7 +21,8 @@ abstract interface class RegistrarServiceBase {

/// This request is used to check whether the person attempting to activate an atSign is its rightful owner.
/// The request takes an atSign and a one-time password then provides the cramkey once verified.
Future<String> authenticateAtSignAndActivate({required String atSign, required String otp});
Future<String> authenticateAtSignAndActivate(
{required String atSign, required String otp});

/// Weblink to registrar service.
String get registrarUrlSite;
Expand Down
20 changes: 13 additions & 7 deletions packages/at_auth/lib/src/registrar/registrar_service_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:at_auth/src/registrar/registrar_service_base.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';

import 'registar_validate_person_response.dart';
import 'registrar_validate_person_response.dart';
import 'registrar_exception.dart';

class RegistrarServiceImpl implements RegistrarServiceBase {
Expand All @@ -15,7 +15,8 @@ class RegistrarServiceImpl implements RegistrarServiceBase {
bool bypassCertificate = true,
this.maxRetries = 3,
this.retryDelayMs = 2000,
}) : _httpClient = IOClient(_createHttpClient(bypassCertificate: bypassCertificate)),
}) : _httpClient =
IOClient(_createHttpClient(bypassCertificate: bypassCertificate)),
_apiPath = '/api/app/v$version',
rootDomain = 'my.atsign.wtf',
weblink = 'https://atsign.wtf',
Expand Down Expand Up @@ -45,7 +46,8 @@ class RegistrarServiceImpl implements RegistrarServiceBase {
this.maxRetries = 3,
this.retryDelayMs = 2000,
IOClient? httpClient,
}) : _httpClient = httpClient ?? IOClient(_createHttpClient(bypassCertificate: bypassCertificate)),
}) : _httpClient = httpClient ??
IOClient(_createHttpClient(bypassCertificate: bypassCertificate)),
_apiPath = '/api/app/v$version',
assert(version >= 1 && version <= 3, 'Version must be between 1 and 3'),
assert(maxRetries > 0, 'Max retries must be greater than 0'),
Expand All @@ -63,13 +65,15 @@ class RegistrarServiceImpl implements RegistrarServiceBase {
static HttpClient _createHttpClient({required bool bypassCertificate}) {
final client = HttpClient();
if (bypassCertificate) {
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
return client;
}

/// Sends a POST request to the registrar API with retry logic.
Future<http.Response> _postRequest(String path, Map<String, String?>? data) async {
Future<http.Response> _postRequest(
String path, Map<String, String?>? data) async {
final url = Uri.https(rootDomain, '$_apiPath/$path');
final body = data != null ? json.encode(data) : null;

Expand Down Expand Up @@ -155,7 +159,8 @@ class RegistrarServiceImpl implements RegistrarServiceBase {
}

@override
Future<void> registerPerson({required String atSign, required String email}) async {
Future<void> registerPerson(
{required String atSign, required String email}) async {
final response = await _postRequest('register-person/', {
'atsign': atSign,
'email': email,
Expand Down Expand Up @@ -229,7 +234,8 @@ class RegistrarServiceImpl implements RegistrarServiceBase {
}

@override
Future<String> authenticateAtSignAndActivate({required String atSign, required String otp}) async {
Future<String> authenticateAtSignAndActivate(
{required String atSign, required String otp}) async {
final response = await _postRequest('authenticate/atsign/activate', {
'atsign': atSign,
'otp': otp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ class ValidatePersonResponse {
);
}

if (json.containsKey('data') && json['data'] != null && (json['data'] as Map<String, dynamic>).isNotEmpty) {
if (json.containsKey('data') &&
json['data'] != null &&
(json['data'] as Map<String, dynamic>).isNotEmpty) {
final data = json['data'] as Map<String, dynamic>;
final atSigns = (data['atsigns'] as List<dynamic>?)?.map((e) => e as String).toList() ?? [];
final atSigns = (data['atsigns'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[];
final newAtSign = data['newAtsign'] as String?;

return ValidatePersonResponse(
Expand Down
94 changes: 64 additions & 30 deletions packages/at_auth/test/registrar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,78 +43,103 @@ void main() {
});

test('getFreeAtSign throws exception on invalid response', () async {
when(() => mockHttpClient.get(any(), headers: any(named: 'headers'))).thenAnswer(
when(() => mockHttpClient.get(any(), headers: any(named: 'headers')))
.thenAnswer(
(_) async => http.Response(
jsonEncode({
"message": "Oops, this option is not available at the moment. Please try again later.",
"message":
"Oops, this option is not available at the moment. Please try again later.",
"status": "error"
}),
200,
),
);

expect(() async => await registrarService.getFreeAtSign(), throwsA(isA<RegistrarException>()));
expect(() async => await registrarService.getFreeAtSign(),
throwsA(isA<RegistrarException>()));
});

test('registerPerson succeeds on valid response', () async {
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(jsonEncode({'message': 'Sent Successfully'}), 200));
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async =>
http.Response(jsonEncode({'message': 'Sent Successfully'}), 200));

await registrarService.registerPerson(atSign: '@testuser', email: '[email protected]');
await registrarService.registerPerson(
atSign: '@testuser', email: '[email protected]');
});

test('registerPerson throws exception on API failure', () async {
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(jsonEncode({'message': 'Oops, atSign is required.'}), 400));
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(
jsonEncode({'message': 'Oops, atSign is required.'}), 400));

expect(() async => await registrarService.registerPerson(atSign: '@testuser', email: '[email protected]'),
expect(
() async => await registrarService.registerPerson(
atSign: '@testuser', email: '[email protected]'),
throwsA(isA<RegistrarException>()));
});

test('authenticateAtSignAndActivate returns cramKey on success', () async {
final testKey =
'7ca5f65fga49c7c667251d6f0cb2b0416dbc580b9712d943203ae644ae1b158bcdc02c6bc6453c33b51859773f05c6h5dd9b8a3c017d92cb87cf2ba3371a9d1f';
final mockResponse = jsonEncode({'message': 'Verified', 'cramkey': '@ashish:$testKey'});
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
final mockResponse =
jsonEncode({'message': 'Verified', 'cramkey': '@ashish:$testKey'});
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(mockResponse, 200));

final cramKey = await registrarService.authenticateAtSignAndActivate(atSign: '@testuser', otp: '123456');
final cramKey = await registrarService.authenticateAtSignAndActivate(
atSign: '@testuser', otp: '123456');

expect(cramKey, testKey);
});

test('authenticateAtSignAndActivate throws exception on API failure', () async {
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body'))).thenAnswer(
test('authenticateAtSignAndActivate throws exception on API failure',
() async {
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body'))).thenAnswer(
(_) async => http.Response(
jsonEncode({'message': 'Please enter the 4-character verification code that was sent to your email address'}),
jsonEncode({
'message':
'Please enter the 4-character verification code that was sent to your email address'
}),
400,
),
);

expect(() async => await registrarService.authenticateAtSignAndActivate(atSign: '@testuser', otp: '123456'),
expect(
() async => await registrarService.authenticateAtSignAndActivate(
atSign: '@testuser', otp: '123456'),
throwsA(isA<RegistrarException>()));
});

test('_retryRequest retries failed requests up to maxRetries', () async {
int attemptCount = 0;

when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async {
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'),
body: any(named: 'body'))).thenAnswer((_) async {
attemptCount++;
return http.Response('Server error', 500);
});

await expectLater(
() async => await registrarService.registerPerson(atSign: '@testuser', email: '[email protected]'),
() async => await registrarService.registerPerson(
atSign: '@testuser', email: '[email protected]'),
throwsA(isA<RegistrarException>()));

expect(attemptCount, equals(registrarService.maxRetries));
});

test('returns ValidatePersonResponse with cramKey when successful', () async {
final mockResponse = jsonEncode({"success": true, "cramKey": "@newatsign:cramKey123"});
test('returns ValidatePersonResponse with cramKey when successful',
() async {
final mockResponse =
jsonEncode({"success": true, "cramKey": "@newatsign:cramKey123"});

when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(mockResponse, 200));

final response = await registrarService.validatePerson(
Expand All @@ -136,7 +161,8 @@ void main() {
}
});

when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(mockResponse, 200));

final response = await registrarService.validatePerson(
Expand All @@ -145,15 +171,19 @@ void main() {
otp: '123456',
);

expect(response.existingAtSigns, containsAll(["oldAtSign1", "oldAtSign2"]));
expect(
response.existingAtSigns, containsAll(["oldAtSign1", "oldAtSign2"]));
expect(response.newAtSign, equals("newAtSign"));
expect(response.success, isTrue);
});

test('returns ValidatePersonResponse with error message on failure', () async {
final mockResponse = jsonEncode({"status": "error", "message": "Invalid OTP", "data": {}});
test('returns ValidatePersonResponse with error message on failure',
() async {
final mockResponse =
jsonEncode({"status": "error", "message": "Invalid OTP", "data": {}});

when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(mockResponse, 200));

final response = await registrarService.validatePerson(
Expand All @@ -166,7 +196,9 @@ void main() {
expect(response.success, isFalse);
});

test('returns ValidatePersonResponse with error message on more than 10 atsigns registered', () async {
test(
'returns ValidatePersonResponse with error message on more than 10 atsigns registered',
() async {
final mockResponse = jsonEncode({
'data': {
'atsigns': [
Expand All @@ -186,7 +218,8 @@ void main() {
'Oops! You already have the maximum number of free atSigns. Please select one of your existing atSigns.',
});

when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(mockResponse, 200));

final response = await registrarService.validatePerson(
Expand All @@ -205,7 +238,8 @@ void main() {
test('throws RegistrarException for invalid response format', () async {
final mockResponse = jsonEncode({"unexpected": "data"});

when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
when(() => mockHttpClient.post(any(),
headers: any(named: 'headers'), body: any(named: 'body')))
.thenAnswer((_) async => http.Response(mockResponse, 200));

expect(
Expand Down

0 comments on commit 5c3cc93

Please sign in to comment.