diff --git a/.github/workflows/polygonid.yaml b/.github/workflows/polygonid.yaml deleted file mode 100644 index 9c04e9c6a..000000000 --- a/.github/workflows/polygonid.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: polygonid - -on: [ pull_request, push ] - -jobs: - build: - uses: TalaoDAO/AltMe/.github/workflows/flutter_package.yaml@main - with: - flutter_channel: stable - flutter_version: 3.19.6 - min_coverage: 30 - working_directory: packages/polygonid - dart_sdk: 3.3.4 - build_runner: false diff --git a/.github/workflows/key_generator.yaml b/.github/workflows/secure_storage.yaml similarity index 70% rename from .github/workflows/key_generator.yaml rename to .github/workflows/secure_storage.yaml index 7be1bb9dc..8f292dfb8 100644 --- a/.github/workflows/key_generator.yaml +++ b/.github/workflows/secure_storage.yaml @@ -1,6 +1,6 @@ -name: key_generator +name: secure_storage -on: [ pull_request, push ] +on: [pull_request, push] jobs: build: @@ -9,6 +9,6 @@ jobs: flutter_channel: stable flutter_version: 3.19.6 min_coverage: 30 - working_directory: packages/key_generator + working_directory: packages/secure_storage dart_sdk: 3.3.4 build_runner: false diff --git a/lib/app/shared/dio_client/dio_client.dart b/lib/app/shared/dio_client/dio_client.dart index 462b48704..083528646 100644 --- a/lib/app/shared/dio_client/dio_client.dart +++ b/lib/app/shared/dio_client/dio_client.dart @@ -55,35 +55,14 @@ class DioClient { bool isCachingEnabled = false, }) async { try { - final isInternetAvailable = await isConnected(); - if (!isInternetAvailable) { - throw NetworkException( - message: NetworkError.NETWORK_ERROR_NO_INTERNET_CONNECTION, - ); - } - final stopwatch = Stopwatch()..start(); await getSpecificHeader(uri, headers); log.i('uri - $uri'); - - final cachedData = await secureStorageProvider.get(uri); dynamic response; - if (!isCachingEnabled || cachedData == null) { - response = await dio.get( - uri, - queryParameters: queryParameters, - options: options, - cancelToken: cancelToken, - onReceiveProgress: onReceiveProgress, - ); - } else { - final cachedDataJson = jsonDecode(cachedData); - final expiry = int.parse(cachedDataJson['expiry'].toString()); - - final isExpired = DateTime.now().millisecondsSinceEpoch > expiry; - - if (isExpired) { + if (isCachingEnabled) { + final cachedData = await secureStorageProvider.get(uri); + if (cachedData == null) { response = await dio.get( uri, queryParameters: queryParameters, @@ -92,18 +71,34 @@ class DioClient { onReceiveProgress: onReceiveProgress, ); } else { - /// directly return cached data - /// returned here to avoid the caching override everytime - final response = await cachedDataJson['data']; - log.i('Time - ${stopwatch.elapsed}'); - return response; + final cachedDataJson = jsonDecode(cachedData); + final expiry = int.parse(cachedDataJson['expiry'].toString()); + + final isExpired = DateTime.now().millisecondsSinceEpoch > expiry; + if (isExpired) { + response = await dio.get( + uri, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + onReceiveProgress: onReceiveProgress, + ); + } else { + /// directly return cached data + /// returned here to avoid the caching override everytime + final response = await cachedDataJson['data']; + return response; + } } + } else { + response = await dio.get( + uri, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + onReceiveProgress: onReceiveProgress, + ); } - final expiry = - DateTime.now().add(const Duration(days: 2)).millisecondsSinceEpoch; - - final value = {'expiry': expiry, 'data': response.data}; - await secureStorageProvider.set(uri, jsonEncode(value)); log.i('Time - ${stopwatch.elapsed}'); return response.data; @@ -153,13 +148,6 @@ class DioClient { }, }) async { try { - final isInternetAvailable = await isConnected(); - if (!isInternetAvailable) { - throw NetworkException( - message: NetworkError.NETWORK_ERROR_NO_INTERNET_CONNECTION, - ); - } - final stopwatch = Stopwatch()..start(); await getSpecificHeader(uri, headers); final response = await dio.post( diff --git a/lib/app/shared/enum/credential_category.dart b/lib/app/shared/enum/credential_category.dart index af7c4faed..1fd9c5455 100644 --- a/lib/app/shared/enum/credential_category.dart +++ b/lib/app/shared/enum/credential_category.dart @@ -80,8 +80,7 @@ extension CredentialCategoryX on CredentialCategory { } } - CredentialCategoryConfig config(BuildContext context) { - final l10n = context.l10n; + CredentialCategoryConfig config(AppLocalizations l10n) { switch (this) { case CredentialCategory.advantagesCards: return CredentialCategoryConfig( diff --git a/lib/app/shared/extension/credential_status.dart b/lib/app/shared/enum/status/credential_status_extension.dart similarity index 100% rename from lib/app/shared/extension/credential_status.dart rename to lib/app/shared/enum/status/credential_status_extension.dart diff --git a/lib/app/shared/enum/status/status.dart b/lib/app/shared/enum/status/status.dart index 633bdfb16..f924629ab 100644 --- a/lib/app/shared/enum/status/status.dart +++ b/lib/app/shared/enum/status/status.dart @@ -2,6 +2,7 @@ export 'app_status.dart'; export 'beacon_status.dart'; export 'credential_detail_tab_status.dart'; export 'credential_status.dart'; +export 'credential_status_extension.dart'; export 'credentials_status.dart'; export 'home_status.dart'; export 'kyc_verification_status.dart'; diff --git a/lib/app/shared/extension/extension.dart b/lib/app/shared/extension/extension.dart index 1703838c0..c8a08d0ed 100644 --- a/lib/app/shared/extension/extension.dart +++ b/lib/app/shared/extension/extension.dart @@ -1,5 +1,4 @@ export 'bigint_extension.dart'; -export 'credential_status.dart'; export 'double_extension.dart'; export 'iterable_extension.dart'; export 'string_extension.dart'; diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 057047a56..1617c70f7 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -23,6 +23,7 @@ import 'package:oidc4vc/oidc4vc.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:secure_storage/secure_storage.dart'; import 'package:x509/x509.dart' as x509; +import 'package:x509/x509.dart'; String generateDefaultAccountName( int accountIndex, @@ -74,11 +75,15 @@ KeyStoreModel getKeysFromSecretKey({required String secretKey}) { ); } -String stringToHexPrefixedWith05({required String payload}) { +String stringToHexPrefixedWith05({ + required String payload, + DateTime? dateTime, +}) { + dateTime ??= DateTime.now(); final String formattedInput = [ 'Tezos Signed Message:', 'altme.io', - DateTime.now().toString(), + dateTime.toString(), payload, ].join(' '); @@ -199,7 +204,7 @@ Future isCredentialAvaialble({ credential .credentialPreview.credentialSubjectModel.credentialSubjectType; - final matchFormat = vcFormatType.value == credential.format; + final matchFormat = vcFormatType.vcValue == credential.format; if (matchSubjectType && matchFormat) { return true; } @@ -237,11 +242,13 @@ Future getStoragePermission() async { return false; } -String getDateTimeWithoutSpace() { - final dateTime = DateTime.fromMicrosecondsSinceEpoch( - DateTime.now().microsecondsSinceEpoch, +String getDateTimeWithoutSpace({DateTime? dateTime}) { + dateTime ??= DateTime.now(); + + final dateTimeString = DateTime.fromMicrosecondsSinceEpoch( + dateTime.microsecondsSinceEpoch, ).toString().replaceAll(' ', '-'); - return dateTime; + return dateTimeString; } int getIndexValue({ @@ -272,6 +279,16 @@ Future getPrivateKey({ required ProfileCubit profileCubit, required DidKeyType didKeyType, }) async { + final customOidc4vcProfile = profileCubit.state.model.profileSetting + .selfSovereignIdentityOptions.customOidc4vcProfile; + + if (customOidc4vcProfile.clientType == ClientType.p256JWKThumprint) { + final privateKey = + await getP256KeyToGetAndPresentVC(profileCubit.secureStorageProvider); + + return privateKey; + } + final mnemonic = await profileCubit.secureStorageProvider .get(SecureStorageKeys.ssiMnemonic); @@ -718,6 +735,7 @@ Future< final OpenIdConfiguration openIdConfiguration = await oidc4vc.getOpenIdConfig( baseUrl: issuer, isAuthorizationServer: false, + dio: client.dio, ); if (preAuthorizedCode == null) { @@ -742,6 +760,7 @@ Future< authorizationServerConfiguration = await oidc4vc.getOpenIdConfig( baseUrl: authorizationServer, isAuthorizationServer: true, + dio: client.dio, ); } @@ -971,7 +990,12 @@ Future?> getClientMetada({ return null; } } catch (e) { - return null; + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': 'Client metaData is invalid', + }, + ); } } @@ -992,6 +1016,7 @@ Future isEBSIV3ForVerifiers({ await oidc4vc.getOpenIdConfig( baseUrl: clientId, isAuthorizationServer: false, + dio: Dio(), ); final subjectTrustFrameworksSupported = @@ -1158,7 +1183,7 @@ MessageHandler getMessageHandler(dynamic e) { 'error_description': 'Failed to extract header from jwt.', }, ); - } else if (stringException.contains('INVALID_TOKEN')) { + } else if (stringException.contains('INVALID_PAYLOAD')) { return ResponseMessage( data: { 'error': 'invalid_format', @@ -1579,8 +1604,11 @@ Future<(String?, String?, String?, String?)> getClientDetails({ final didKeyType = customOidc4vcProfile.defaultDid; - final privateKey = - await getP256KeyToGetAndPresentVC(profileCubit.secureStorageProvider); + final String privateKey = await fetchPrivateKey( + profileCubit: profileCubit, + isEBSIV3: isEBSIV3, + didKeyType: didKeyType, + ); final (did, _) = await fetchDidAndKid( privateKey: privateKey, @@ -1594,7 +1622,7 @@ Future<(String?, String?, String?, String?)> getClientDetails({ did: '', // just added as it is required field mediaType: MediaType.basic, // just added as it is required field clientType: - ClientType.p256JWKThumprint, // just added as it is required field + customOidc4vcProfile.clientType, // just added as it is required field proofHeaderType: customOidc4vcProfile.proofHeader, clientId: '', // just added as it is required field ); @@ -1840,10 +1868,10 @@ List getStringCredentialsForToken({ } /// create list of supported formats - if (presentLdpVc) supportingFormats.add(VCFormatType.ldpVc.value); - if (presentJwtVc) supportingFormats.add(VCFormatType.jwtVc.value); - if (presentJwtVcJson) supportingFormats.add(VCFormatType.jwtVcJson.value); - if (presentVcSdJwt) supportingFormats.add(VCFormatType.jwtVcJson.value); + if (presentLdpVc) supportingFormats.add(VCFormatType.ldpVc.vcValue); + if (presentJwtVc) supportingFormats.add(VCFormatType.jwtVc.vcValue); + if (presentJwtVcJson) supportingFormats.add(VCFormatType.jwtVcJson.vcValue); + if (presentVcSdJwt) supportingFormats.add(VCFormatType.jwtVcJson.vcValue); /// make sure only one of all are true if (presentLdpVc && vcFormatType == VCFormatType.ldpVc) { @@ -1861,7 +1889,7 @@ List getStringCredentialsForToken({ presentJwtVc = false; presentJwtVcJson = true; presentVcSdJwt = false; - } else if (presentJwtVc && vcFormatType == VCFormatType.vcSdJWT) { + } else if (presentVcSdJwt && vcFormatType == VCFormatType.vcSdJWT) { presentLdpVc = false; presentJwtVc = false; presentJwtVcJson = false; @@ -2013,9 +2041,9 @@ Future?> checkX509({ final seq = asn1lib.ASN1Sequence.fromBytes(decoded); final cert = x509.X509Certificate.fromAsn1(seq); - final subject = cert.tbsCertificate.subject; + final extensions = cert.tbsCertificate.extensions; - if (subject == null) { + if (extensions == null) { throw ResponseMessage( data: { 'error': 'invalid_format', @@ -2024,9 +2052,11 @@ Future?> checkX509({ ); } - final names = subject.names; + final extension = extensions + .where((Extension element) => element.extnId.name == 'subjectAltName') + .firstOrNull; - if (names.isEmpty) { + if (extension == null) { throw ResponseMessage( data: { 'error': 'invalid_format', @@ -2035,9 +2065,9 @@ Future?> checkX509({ ); } - final value = names[0].entries.map((element) => element.value).toList(); + final extnValue = extension.extnValue.toString(); - if (!value.contains(clientId)) { + if (!extnValue.contains(clientId)) { throw ResponseMessage( data: { 'error': 'invalid_format', @@ -2056,6 +2086,18 @@ Future?> checkX509({ 'n': n.replaceAll('=', ''), }; return publicKeyJwk; + } else if (publicKey is x509.EcPublicKey) { + final BigInt xModulus = BigInt.parse(publicKey.xCoordinate.toString()); + final BigInt yModulus = BigInt.parse(publicKey.yCoordinate.toString()); + final x = base64Encode(xModulus.toBytes); + final y = base64Encode(yModulus.toBytes); + final publicKeyJwk = { + 'kty': 'EC', + 'crv': 'P-256', + 'x': x.replaceAll('=', ''), + 'y': y.replaceAll('=', ''), + }; + return publicKeyJwk; } } return null; @@ -2114,7 +2156,10 @@ String? getWalletAddress(CredentialSubjectModel credentialSubjectModel) { return null; } -Future fetchRpcUrl(BlockchainNetwork blockchainNetwork) async { +Future fetchRpcUrl({ + required BlockchainNetwork blockchainNetwork, + required DotEnv dotEnv, +}) async { String rpcUrl = ''; if (blockchainNetwork is BinanceNetwork || @@ -2122,8 +2167,8 @@ Future fetchRpcUrl(BlockchainNetwork blockchainNetwork) async { rpcUrl = blockchainNetwork.rpcNodeUrl as String; } else { if (blockchainNetwork.networkname == 'Mainnet') { - await dotenv.load(); - final String infuraApiKey = dotenv.get('INFURA_API_KEY'); + await dotEnv.load(); + final String infuraApiKey = dotEnv.get('INFURA_API_KEY'); late String prefixUrl; diff --git a/lib/app/shared/issuer/check_issuer.dart b/lib/app/shared/issuer/check_issuer.dart index 7b4851735..e5a2308c8 100644 --- a/lib/app/shared/issuer/check_issuer.dart +++ b/lib/app/shared/issuer/check_issuer.dart @@ -28,8 +28,6 @@ class CheckIssuer { try { log.i('fetching issuer data'); - final dynamic response = - await client.get('$checkIssuerServerUrl/$didToTest'); if (checkIssuerServerUrl == Urls.checkIssuerEbsiUrl) { return Issuer( preferredName: '', @@ -44,6 +42,8 @@ class CheckIssuer { ); } + final dynamic response = + await client.get('$checkIssuerServerUrl/$didToTest'); final issuer = Issuer.fromJson(response['issuer'] as Map); diff --git a/lib/app/shared/local_notification/local_notification.dart b/lib/app/shared/local_notification/local_notification.dart deleted file mode 100644 index 65de789f9..000000000 --- a/lib/app/shared/local_notification/local_notification.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:altme/app/app.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:flutter_native_timezone/flutter_native_timezone.dart'; -import 'package:timezone/data/latest.dart' as tz; -import 'package:timezone/timezone.dart' as tz; - -class LocalNotification { - factory LocalNotification() { - return _localNotification; - } - - LocalNotification._internal(); - - static final LocalNotification _localNotification = - LocalNotification._internal(); - - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - - Future init() async { - await _configureLocalTimeZone(); - const android = AndroidInitializationSettings('@mipmap/ic_launcher'); - const iOS = DarwinInitializationSettings(); - const initSettings = InitializationSettings(android: android, iOS: iOS); - - await flutterLocalNotificationsPlugin.initialize( - initSettings, - onDidReceiveNotificationResponse: - (NotificationResponse notificationResponse) { - switch (notificationResponse.notificationResponseType) { - case NotificationResponseType.selectedNotification: - case NotificationResponseType.selectedNotificationAction: - _onSelectNotification(notificationResponse.payload); - } - }, - ); - } - - Future _configureLocalTimeZone() async { - if (kIsWeb) { - return; - } - tz.initializeTimeZones(); - final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone(); - tz.setLocalLocation(tz.getLocation(timeZoneName)); - } - - Future showNotification({ - String? link, - String? title, - String? message, - }) async { - const android = AndroidNotificationDetails( - 'channel id', - 'channel name', - channelDescription: 'channel description', - priority: Priority.high, - importance: Importance.max, - ); - const iOS = DarwinNotificationDetails(); - const platform = NotificationDetails(android: android, iOS: iOS); - - await flutterLocalNotificationsPlugin.show( - 0, // notification id - title, - message, - platform, - payload: link, - ); - } - - Future _onSelectNotification(String? link) async { - if (link != null) { - await LaunchUrl.launch(link); - } - } - - Future cancelAllNotifications() async { - await flutterLocalNotificationsPlugin.cancelAll(); - } -} diff --git a/lib/app/shared/shared.dart b/lib/app/shared/shared.dart index 342c58ab6..c43fa398d 100644 --- a/lib/app/shared/shared.dart +++ b/lib/app/shared/shared.dart @@ -9,7 +9,6 @@ export 'issuer/issuer.dart'; export 'launch_url/launch_url.dart'; export 'loading/loading.dart'; export 'local_auth/local_auth_api.dart'; -export 'local_notification/local_notification.dart'; export 'm_web3_client/m_web3_client.dart'; export 'message_handler/message_handler.dart'; export 'models/model.dart'; diff --git a/lib/app/shared/translation/translation.dart b/lib/app/shared/translation/translation.dart index 81859b5f2..2a10e3731 100644 --- a/lib/app/shared/translation/translation.dart +++ b/lib/app/shared/translation/translation.dart @@ -9,6 +9,7 @@ class GetTranslation { String translation; final translated = translations.where((element) => element.language == l10n.localeName); + if (translated.isEmpty) { final List translationList = translations.where((element) => element.language == 'en').toList(); diff --git a/lib/app/shared/widget/base/box_decoration.dart b/lib/app/shared/widget/base/box_decoration.dart index 3e441fa34..8d9da92ac 100644 --- a/lib/app/shared/widget/base/box_decoration.dart +++ b/lib/app/shared/widget/base/box_decoration.dart @@ -116,7 +116,7 @@ class BaseBoxDecoration extends Decoration { color, shapeColor, borderRadius, - Object.hashAll(boxShadow!), + boxShadow != null ? Object.hashAll(boxShadow!) : null, gradient, ); } diff --git a/lib/app/shared/widget/default_dialog.dart b/lib/app/shared/widget/default_dialog.dart index f60e08e43..9e0c95281 100644 --- a/lib/app/shared/widget/default_dialog.dart +++ b/lib/app/shared/widget/default_dialog.dart @@ -19,8 +19,8 @@ class DefaultDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.popupBackground, - surfaceTintColor: Colors.transparent, + backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 81fca4fd4..30398b6ae 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'dart:developer'; -import 'package:altme/app/app.dart'; import 'package:bloc/bloc.dart'; import 'package:dartez/dartez.dart'; import 'package:flutter/widgets.dart'; @@ -38,7 +37,6 @@ Future bootstrap(FutureOr Function() builder) async { () async { WidgetsFlutterBinding.ensureInitialized(); - await LocalNotification().init(); await initSecureStorage; /// Disable Http google font diff --git a/lib/credentials/cubit/credentials_cubit.dart b/lib/credentials/cubit/credentials_cubit.dart index 9a94dd22f..1176414a9 100644 --- a/lib/credentials/cubit/credentials_cubit.dart +++ b/lib/credentials/cubit/credentials_cubit.dart @@ -901,44 +901,18 @@ class CredentialsCubit extends Cubit { .toList(); if (credentialsOfSameType.isNotEmpty && subjectType.supportSingleOnly) { - final availableWalletAddresses = []; - - if (isBlockchainAccount && supportAssociatedCredential) { - /// getting list of available wallet address of current - /// blockchain account - for (final credential in credentialsOfSameType) { - final String? walletAddress = getWalletAddress( - credential.credentialPreview.credentialSubjectModel, - ); - - if (walletAddress != null) { - availableWalletAddresses.add(walletAddress); - } - } - } - /// credential available case for (final credential in credentialsOfSameType) { if (isBlockchainAccount && supportAssociatedCredential) { /// there can be multiple blockchain profiles /// /// each profiles should be allowed to add the respective cards - /// - /// so we have to check the current profile wallet address and - /// compare with existing blockchain card to add in discover or - /// not - - final String? currentWalletAddress = - walletCubit.state.currentAccount?.walletAddress; - - /// if current blockchain card is not available in list of - /// credentails then add in the discover list - /// else do not add if it is blockchain - final isBlockChainCardAvailable = availableWalletAddresses - .contains(currentWalletAddress.toString()); + /// We always have the associated Adress credential + /// in the discover since "Do not remove the GET a crypto account + /// in the Discover #2649" - if (!isBlockChainCardAvailable && isCurrentBlockchainAccount) { + if (isCurrentBlockchainAccount) { /// if already added do not add if (!requiredDummySubjects.contains(subjectType)) { requiredDummySubjects.add(subjectType); @@ -947,7 +921,7 @@ class CredentialsCubit extends Cubit { //get current wallet address } else { - if (vcFormatType.value == credential.getFormat) { + if (vcFormatType.vcValue == credential.getFormat) { /// do not add if format matched /// there can be same credentials with different format } else { diff --git a/lib/credentials/cubit/credentials_helper_function.dart b/lib/credentials/cubit/credentials_helper_function.dart index 0a5594f65..8a8c936b4 100644 --- a/lib/credentials/cubit/credentials_helper_function.dart +++ b/lib/credentials/cubit/credentials_helper_function.dart @@ -268,7 +268,7 @@ Future _createCredential({ data: jsonLd, shareLink: '', jwt: jwt, - format: customOidc4vcProfile.vcFormatType.value, + format: customOidc4vcProfile.vcFormatType.vcValue, credentialPreview: Credential.fromJson(jsonLd), credentialManifest: credentialManifest, activities: [Activity(acquisitionAt: dateTime)], diff --git a/lib/dashboard/connection/operation/cubit/operation_cubit.dart b/lib/dashboard/connection/operation/cubit/operation_cubit.dart index 2f1d4ce5b..a99c7ecef 100644 --- a/lib/dashboard/connection/operation/cubit/operation_cubit.dart +++ b/lib/dashboard/connection/operation/cubit/operation_cubit.dart @@ -208,8 +208,10 @@ class OperationCubit extends Cubit { walletConnectCubit.state.transaction!.value!; amount = MWeb3Client.formatEthAmount(amount: ethAmount.getInWei); - final web3RpcURL = - await fetchRpcUrl(manageNetworkCubit.state.network); + final web3RpcURL = await fetchRpcUrl( + blockchainNetwork: manageNetworkCubit.state.network, + dotEnv: dotenv, + ); log.i('web3RpcURL - $web3RpcURL'); final (_, _, feeData) = await MWeb3Client.estimateEVMFee( @@ -356,7 +358,10 @@ class OperationCubit extends Cubit { final EtherAmount ethAmount = walletConnectCubit.state.transaction!.value!; - final rpcUrl = await fetchRpcUrl(manageNetworkCubit.state.network); + final rpcUrl = await fetchRpcUrl( + blockchainNetwork: manageNetworkCubit.state.network, + dotEnv: dotenv, + ); log.i('rpcUrl - $rpcUrl'); diff --git a/lib/dashboard/discover/widgets/discover_credential_category_item.dart b/lib/dashboard/discover/widgets/discover_credential_category_item.dart index 45265578a..47475725c 100644 --- a/lib/dashboard/discover/widgets/discover_credential_category_item.dart +++ b/lib/dashboard/discover/widgets/discover_credential_category_item.dart @@ -1,5 +1,6 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/l10n/l10n.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; @@ -17,7 +18,7 @@ class DiscoverCredentialCategoryItem extends StatelessWidget { @override Widget build(BuildContext context) { - final credentialCategoryConfig = credentialCategory.config(context); + final credentialCategoryConfig = credentialCategory.config(context.l10n); //sort credentials by order dummyCredentials.sort( (a, b) => diff --git a/lib/dashboard/drawer/ssi/oidc4vc_settngs/widget/vc_format_widget.dart b/lib/dashboard/drawer/ssi/oidc4vc_settngs/widget/vc_format_widget.dart index c974b7340..ee02be9be 100644 --- a/lib/dashboard/drawer/ssi/oidc4vc_settngs/widget/vc_format_widget.dart +++ b/lib/dashboard/drawer/ssi/oidc4vc_settngs/widget/vc_format_widget.dart @@ -47,7 +47,7 @@ class VCFormatWidget extends StatelessWidget { ), ), title: Text( - vcFormatType.formattedString, + vcFormatType.vcValue, style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), diff --git a/lib/dashboard/home/home/cubit/home_cubit.dart b/lib/dashboard/home/home/cubit/home_cubit.dart index 91ca98701..0f4f8a470 100644 --- a/lib/dashboard/home/home/cubit/home_cubit.dart +++ b/lib/dashboard/home/home/cubit/home_cubit.dart @@ -187,7 +187,7 @@ class HomeCubit extends Cubit { final Map newCredential = Map.from(credential); newCredential['credentialPreview'] = credential; - newCredential['format'] = vcFormatType.value; + newCredential['format'] = vcFormatType.vcValue; final CredentialManifest credentialManifest = await getCredentialManifestFromAltMe( oidc4vc: oidc4vc, diff --git a/lib/dashboard/home/tab_bar/credentials/helper_functions/get_credential_manifest_from_altme.dart b/lib/dashboard/home/tab_bar/credentials/helper_functions/get_credential_manifest_from_altme.dart index f909ffd4f..12157492c 100644 --- a/lib/dashboard/home/tab_bar/credentials/helper_functions/get_credential_manifest_from_altme.dart +++ b/lib/dashboard/home/tab_bar/credentials/helper_functions/get_credential_manifest_from_altme.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:credential_manifest/credential_manifest.dart'; +import 'package:dio/dio.dart'; import 'package:json_path/json_path.dart'; import 'package:oidc4vc/oidc4vc.dart'; @@ -11,6 +12,7 @@ Future getCredentialManifestFromAltMe({ final OpenIdConfiguration openIdConfiguration = await oidc4vc.getOpenIdConfig( baseUrl: 'https://issuer.talao.co', isAuthorizationServer: false, + dio: Dio(), ); final JsonPath credentialManifetPath = JsonPath(r'$..credential_manifest'); final credentialManifest = CredentialManifest.fromJson( diff --git a/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_item.dart b/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_item.dart index 9589bdbd1..ec0f44211 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_item.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_item.dart @@ -1,5 +1,6 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/l10n/l10n.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; @@ -29,7 +30,7 @@ class HomeCredentialCategoryItem extends StatelessWidget { ? 1 : 0, ); - final credentialCategoryConfig = credentialCategory.config(context); + final credentialCategoryConfig = credentialCategory.config(context.l10n); return Padding( padding: margin, child: Column( diff --git a/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_list.dart b/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_list.dart index 0b9a9dbff..b16f00312 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_list.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_list.dart @@ -76,7 +76,7 @@ class HomeCredentialCategoryList extends StatelessWidget { } /// do not load the credential if vc format is different - if (customOidc4vcProfile.vcFormatType.value != + if (customOidc4vcProfile.vcFormatType.vcValue != element.getFormat) { return false; } diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart index 0183f8046..16eb3abaa 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart @@ -31,22 +31,22 @@ List filterCredenialListByFormat({ (CredentialModel credentialModel) { /// remove ldpVc if (presentLdpVc) { - return credentialModel.format != VCFormatType.ldpVc.value; + return credentialModel.format != VCFormatType.ldpVc.vcValue; } /// remove jwtVc if (presentJwtVc) { - return credentialModel.format != VCFormatType.jwtVc.value; + return credentialModel.format != VCFormatType.jwtVc.vcValue; } /// remove JwtVcJson if (presentJwtVcJson) { - return credentialModel.format != VCFormatType.jwtVcJson.value; + return credentialModel.format != VCFormatType.jwtVcJson.vcValue; } /// remove vcSdJwt if (presentVcSdJwt) { - return credentialModel.format != VCFormatType.vcSdJWT.value; + return credentialModel.format != VCFormatType.vcSdJWT.vcValue; } return false; diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/get_credentials_from_filter_list.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/get_credentials_from_filter_list.dart index 6ac7ad1db..e06a2ca07 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/get_credentials_from_filter_list.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/get_credentials_from_filter_list.dart @@ -32,6 +32,8 @@ List getCredentialsFromFilterList({ pattern = field.filter!.pattern; } else if (field.filter?.contains?.containsConst != null) { pattern = field.filter?.contains?.containsConst; + } else if (field.filter?.containsConst != null) { + pattern = field.filter?.containsConst; } else { /// sd-jwt vc bool case if (searchParameter == 'true') return false; diff --git a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart index d0b3c1979..39d23e328 100644 --- a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart +++ b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart @@ -119,7 +119,10 @@ class ConfirmTokenTransactionCubit extends Cubit { final sender = credentials.address; final reciever = EthereumAddress.fromHex(state.withdrawalAddress); - final web3RpcURL = await fetchRpcUrl(manageNetworkCubit.state.network); + final web3RpcURL = await fetchRpcUrl( + blockchainNetwork: manageNetworkCubit.state.network, + dotEnv: dotenv, + ); final (maxGas, priceOfGas, feeData) = await MWeb3Client.estimateEVMFee( web3RpcURL: web3RpcURL, @@ -424,7 +427,10 @@ class ConfirmTokenTransactionCubit extends Cubit { } else { final selectedEthereumNetwork = manageNetworkCubit.state.network; - final chainRpcUrl = await fetchRpcUrl(manageNetworkCubit.state.network); + final chainRpcUrl = await fetchRpcUrl( + blockchainNetwork: manageNetworkCubit.state.network, + dotEnv: dotenv, + ); await _sendContractInvocationOperationEVM( tokenAmount: state.totalAmount, diff --git a/lib/dashboard/home/tab_bar/tokens/send_receive_home/view/send_receive_home_page.dart b/lib/dashboard/home/tab_bar/tokens/send_receive_home/view/send_receive_home_page.dart index 950f1c79c..34e3b3647 100644 --- a/lib/dashboard/home/tab_bar/tokens/send_receive_home/view/send_receive_home_page.dart +++ b/lib/dashboard/home/tab_bar/tokens/send_receive_home/view/send_receive_home_page.dart @@ -45,9 +45,10 @@ class _SendReceiveHomePageState extends State { @override void initState() { - Future.microtask( - sendReceiveHomeCubit.init, - ); + Future.delayed(Duration.zero, () { + sendReceiveHomeCubit.init( + baseUrl: context.read().state.network.apiUrl); + }); super.initState(); } diff --git a/lib/dashboard/missing_creentials/view/missing_credentials_page.dart b/lib/dashboard/missing_creentials/view/missing_credentials_page.dart index bbe47121a..6c1d919c9 100644 --- a/lib/dashboard/missing_creentials/view/missing_credentials_page.dart +++ b/lib/dashboard/missing_creentials/view/missing_credentials_page.dart @@ -118,25 +118,20 @@ class MissingCredentialsView extends StatelessWidget { MyGradientButton( onPressed: () async { for (final credentials in state.dummyCredentials) { - if (credentials.credentialSubjectType.isBlockchainAccount) { - await Navigator.of(context) - .push(ChooseAddAccountMethodPage.route()); - } else { - await Navigator.push( - context, - DiscoverDetailsPage.route( - dummyCredential: credentials, - buttonText: l10n.getThisCard, - onCallBack: () async { - await discoverCredential( - dummyCredential: credentials, - context: context, - ); - Navigator.pop(context); - }, - ), - ); - } + await Navigator.push( + context, + DiscoverDetailsPage.route( + dummyCredential: credentials, + buttonText: l10n.getThisCard, + onCallBack: () async { + await discoverCredential( + dummyCredential: credentials, + context: context, + ); + Navigator.pop(context); + }, + ), + ); } Navigator.pop(context); }, diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index c69bce946..1789ddba1 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -935,22 +935,16 @@ class QRCodeScanCubit extends Cubit { client: client, uri: state.uri!, ); - if (clientMetaData == null) { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': 'Client metaData is invalid', - }, - ); - } - if (!clientMetaData.containsKey('vp_formats')) { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': 'Format is missing.', - }, - ); + if (clientMetaData != null) { + if (!clientMetaData.containsKey('vp_formats')) { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': 'Format is missing.', + }, + ); + } } } @@ -998,8 +992,14 @@ class QRCodeScanCubit extends Cubit { shareLink: 'shareLink', data: const {}, jwt: null, - format: profileCubit.state.model.profileSetting - .selfSovereignIdentityOptions.customOidc4vcProfile.vcFormatType.value, + format: profileCubit + .state + .model + .profileSetting + .selfSovereignIdentityOptions + .customOidc4vcProfile + .vcFormatType + .vcValue, credentialManifest: credentialManifest, ); @@ -1158,6 +1158,7 @@ class QRCodeScanCubit extends Cubit { stateValue: stateValue, clientType: customOidc4vcProfile.clientType, proofHeaderType: customOidc4vcProfile.proofHeader, + dio: client.dio, ); String? url; @@ -1293,6 +1294,7 @@ class QRCodeScanCubit extends Cubit { final openIdConfiguration = await oidc4vc.getOpenIdConfig( baseUrl: issuer, isAuthorizationServer: false, + dio: client.dio, ); if (savedAccessToken == null) { @@ -1315,6 +1317,7 @@ class QRCodeScanCubit extends Cubit { redirectUri: Parameters.oidc4vcUniversalLink, openIdConfiguration: openIdConfiguration, clientAssertion: clientAssertion, + dio: client.dio, ); savedAccessToken = accessToken; diff --git a/lib/enterprise/cubit/enterprise_state.dart b/lib/enterprise/cubit/enterprise_state.dart index 994bc02f3..226dd6145 100644 --- a/lib/enterprise/cubit/enterprise_state.dart +++ b/lib/enterprise/cubit/enterprise_state.dart @@ -30,7 +30,6 @@ class EnterpriseState extends Equatable { EnterpriseState copyWith({ required StateMessage? message, AppStatus? status, - WalletProviderType? walletProviderType, }) { return EnterpriseState( status: status ?? this.status, diff --git a/lib/oidc4vc/add_oidc4vc_credential.dart b/lib/oidc4vc/add_oidc4vc_credential.dart index 9f771e97b..78ffad433 100644 --- a/lib/oidc4vc/add_oidc4vc_credential.dart +++ b/lib/oidc4vc/add_oidc4vc_credential.dart @@ -23,15 +23,15 @@ Future addOIDC4VCCredential({ }) async { late Map credentialFromOIDC4VC; - if (format == VCFormatType.jwtVc.value || - format == VCFormatType.jwtVcJson.value || - format == VCFormatType.vcSdJWT.value) { + if (format == VCFormatType.jwtVc.vcValue || + format == VCFormatType.jwtVcJson.vcValue || + format == VCFormatType.vcSdJWT.vcValue) { //jwt_vc final data = encodedCredentialFromOIDC4VC['credential'] as String; final jsonContent = jwtDecode.parseJwt(data); - if (format == VCFormatType.vcSdJWT.value) { + if (format == VCFormatType.vcSdJWT.vcValue) { final sdAlg = jsonContent['_sd_alg']; if (sdAlg == null || sdAlg != 'sha-256') { @@ -48,7 +48,7 @@ Future addOIDC4VCCredential({ credentialFromOIDC4VC = jsonContent['vc'] as Map; } - if (format == VCFormatType.vcSdJWT.value) { + if (format == VCFormatType.vcSdJWT.vcValue) { /// type if (!credentialFromOIDC4VC.containsKey('type')) { credentialFromOIDC4VC['type'] = [credentialType]; @@ -115,7 +115,7 @@ Future addOIDC4VCCredential({ // } credentialFromOIDC4VC['jwt'] = data; - } else if (format == VCFormatType.ldpVc.value) { + } else if (format == VCFormatType.ldpVc.vcValue) { //ldp_vc final data = encodedCredentialFromOIDC4VC['credential']; diff --git a/lib/oidc4vc/get_and_add_deffered_credential.dart b/lib/oidc4vc/get_and_add_deffered_credential.dart index a178361db..81915e146 100644 --- a/lib/oidc4vc/get_and_add_deffered_credential.dart +++ b/lib/oidc4vc/get_and_add_deffered_credential.dart @@ -3,6 +3,7 @@ import 'package:altme/credentials/credentials.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/oidc4vc/oidc4vc.dart'; +import 'package:dio/dio.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:oidc4vc/oidc4vc.dart'; @@ -53,6 +54,7 @@ Future getAndAddDefferedCredential({ deferredCredentialEndpoint: credentialModel.pendingInfo!.deferredCredentialEndpoint, body: body, + dio: Dio(), ); await addOIDC4VCCredential( diff --git a/lib/oidc4vc/get_authorization_uri_for_issuer.dart b/lib/oidc4vc/get_authorization_uri_for_issuer.dart index 146133df2..fb0d36cce 100644 --- a/lib/oidc4vc/get_authorization_uri_for_issuer.dart +++ b/lib/oidc4vc/get_authorization_uri_for_issuer.dart @@ -95,6 +95,7 @@ Future getAuthorizationUriForIssuer({ vcFormatType: vcFormatType, clientAssertion: clientAssertion, secureAuthorizedFlow: secureAuthorizedFlow, + dio: client.dio, ); if (secureAuthorizedFlow) { diff --git a/lib/oidc4vc/get_credential.dart b/lib/oidc4vc/get_credential.dart index d0b8bdd33..e1a3774e0 100644 --- a/lib/oidc4vc/get_credential.dart +++ b/lib/oidc4vc/get_credential.dart @@ -1,5 +1,6 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; +import 'package:dio/dio.dart'; import 'package:oidc4vc/oidc4vc.dart'; /// Retreive credential_type from url @@ -61,6 +62,7 @@ Future< cnonce: nonce, authorizationDetails: authorizationDetails, openIdConfiguration: openIdConfiguration, + dio: Dio(), ); return ( diff --git a/lib/oidc4vc/verify_encoded_data.dart b/lib/oidc4vc/verify_encoded_data.dart index 69cf5fae9..9edf38a38 100644 --- a/lib/oidc4vc/verify_encoded_data.dart +++ b/lib/oidc4vc/verify_encoded_data.dart @@ -1,4 +1,5 @@ import 'package:altme/app/app.dart'; +import 'package:dio/dio.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:oidc4vc/oidc4vc.dart'; @@ -34,6 +35,7 @@ Future verifyEncodedData({ publicJwk: publicKeyJwk, fromStatusList: fromStatusList, isCachingEnabled: isCachingEnabled, + dio: Dio(), ); return verificationType; } diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index 6ba772041..91014351a 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -175,7 +175,7 @@ class _OnBoardingGenPhraseViewState extends State { text: l10n.verifyNow, verticalSpacing: 18, onPressed: () { - Navigator.of(context).push( + Navigator.of(context).push( OnBoardingVerifyPhrasePage.route( mnemonic: mnemonic!, isFromOnboarding: true, diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index f326b6758..4a93e0f7e 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -183,6 +183,19 @@ class ScanCubit extends Cubit { for (final item in credentialsToBePresented) { final presentationId = 'urn:uuid:${const Uuid().v4()}'; + /// proof is done with a creation date 20 seconds in the past to avoid + /// proof check to fail because of time difference on server + + final options = jsonEncode({ + 'verificationMethod': kid, + 'proofPurpose': 'assertionMethod', + 'challenge': credentialModel.challenge, + 'domain': credentialModel.domain, + 'created': DateTime.now() + .subtract(const Duration(seconds: 20)) + .toUtc() + .toIso8601String(), + }); final presentation = await didKitProvider.issuePresentation( jsonEncode({ '@context': ['https://www.w3.org/2018/credentials/v1'], @@ -191,12 +204,7 @@ class ScanCubit extends Cubit { 'holder': did, 'verifiableCredential': item.data, }), - jsonEncode({ - 'verificationMethod': kid, - 'proofPurpose': 'assertionMethod', - 'challenge': credentialModel.challenge, - 'domain': credentialModel.domain, - }), + options, privateKey, ); presentations = List.of(presentations)..add(presentation); @@ -346,6 +354,20 @@ class ScanCubit extends Cubit { ); final presentationId = 'urn:uuid:${const Uuid().v4()}'; + + /// proof is done with a creation date 20 seconds in the past to avoid + /// proof check to fail because of time difference on server + final options = jsonEncode({ + 'verificationMethod': kid, + 'proofPurpose': 'assertionMethod', + 'challenge': challenge, + 'domain': domain, + 'created': DateTime.now() + .subtract(const Duration(seconds: 20)) + .toUtc() + .toIso8601String(), + }); + final presentation = await didKitProvider.issuePresentation( jsonEncode({ '@context': ['https://www.w3.org/2018/credentials/v1'], @@ -356,12 +378,7 @@ class ScanCubit extends Cubit { ? credentialsToBePresented.first.data : credentialsToBePresented.map((c) => c.data).toList(), }), - jsonEncode({ - 'verificationMethod': kid, - 'proofPurpose': 'assertionMethod', - 'challenge': challenge, - 'domain': domain, - }), + options, privateKey, ); @@ -744,49 +761,33 @@ class ScanCubit extends Cubit { } } else { if (clientMetaData == null) { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': 'Client metaData is invalid', - }, - ); - } - - final vpFormats = clientMetaData['vp_formats'] as Map; - - if (vpFormats.containsKey('ldp_vc')) { - vcFormat = 'ldp_vc'; - } else if (vpFormats.containsKey('jwt_vc')) { - vcFormat = 'jwt_vc'; - } else if (vpFormats.containsKey('jwt_vc_json')) { - vcFormat = 'jwt_vc_json'; - } + final vcFormatType = profileSetting + .selfSovereignIdentityOptions.customOidc4vcProfile.vcFormatType; + vcFormat = vcFormatType.vcValue; + vpFormat = vcFormatType.vpValue; + } else { + final vpFormats = clientMetaData['vp_formats'] as Map; + + if (vpFormats.containsKey('ldp_vc')) { + vcFormat = 'ldp_vc'; + } else if (vpFormats.containsKey('jwt_vc')) { + vcFormat = 'jwt_vc'; + } else if (vpFormats.containsKey('jwt_vc_json')) { + vcFormat = 'jwt_vc_json'; + } - if (vpFormats.containsKey('ldp_vp')) { - vpFormat = 'ldp_vp'; - } else if (vpFormats.containsKey('jwt_vp')) { - vpFormat = 'jwt_vp'; - } else if (vpFormats.containsKey('jwt_vp_json')) { - vpFormat = 'jwt_vp_json'; - } else if (vpFormats.containsKey('vc+sd-jwt')) { - vpFormat = 'vc+sd-jwt'; + if (vpFormats.containsKey('ldp_vp')) { + vpFormat = 'ldp_vp'; + } else if (vpFormats.containsKey('jwt_vp')) { + vpFormat = 'jwt_vp'; + } else if (vpFormats.containsKey('jwt_vp_json')) { + vpFormat = 'jwt_vp_json'; + } else if (vpFormats.containsKey('vc+sd-jwt')) { + vpFormat = 'vc+sd-jwt'; + } } } - final vcFormatType = profileSetting - .selfSovereignIdentityOptions.customOidc4vcProfile.vcFormatType; - - vcFormat ??= vcFormatType.value; - - if (vpFormat == null) { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': 'VC format is missing.', - }, - ); - } - for (int i = 0; i < credentialsToBePresented.length; i++) { for (final InputDescriptor inputDescriptor in presentationDefinition.inputDescriptors) { @@ -889,12 +890,19 @@ class ScanCubit extends Cubit { ); if (presentLdpVc) { + /// proof is done with a creation date 20 seconds in the past to avoid + /// proof check to fail because of time difference on server final options = jsonEncode({ 'verificationMethod': kid, 'proofPurpose': 'assertionMethod', 'challenge': nonce, 'domain': clientId, + 'created': DateTime.now() + .subtract(const Duration(seconds: 20)) + .toUtc() + .toIso8601String(), }); + final presentationId = 'urn:uuid:${const Uuid().v4()}'; final vpToken = await didKitProvider.issuePresentation( jsonEncode({ diff --git a/lib/selective_disclosure/selective_disclosure.dart b/lib/selective_disclosure/selective_disclosure.dart index 456c8152e..bd65c31eb 100644 --- a/lib/selective_disclosure/selective_disclosure.dart +++ b/lib/selective_disclosure/selective_disclosure.dart @@ -154,7 +154,7 @@ class SelectiveDisclosure { } String? get getPicture { - if (credentialModel.format.toString() != VCFormatType.vcSdJWT.value) { + if (credentialModel.format.toString() != VCFormatType.vcSdJWT.vcValue) { return null; } diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index 38076f3fa..c928bb0bb 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -276,6 +276,7 @@ final qrCodeBlocListener = BlocListener( openIdConfiguration: openIdConfiguration, issuer: issuer, oidc4vciDraftType: customOidc4vcProfile.oidc4vciDraft, + dio: Dio(), ); credentialEndpoint = diff --git a/lib/splash/cubit/splash_cubit.dart b/lib/splash/cubit/splash_cubit.dart index 66330314c..f9b2097d9 100644 --- a/lib/splash/cubit/splash_cubit.dart +++ b/lib/splash/cubit/splash_cubit.dart @@ -23,8 +23,9 @@ class SplashCubit extends Cubit { required this.altmeChatSupportCubit, required this.client, required this.profileCubit, + this.packageInfo, }) : super(const SplashState()) { - _getAppVersion(); + _getAppVersion(packageInfo); } final SecureStorageProvider secureStorageProvider; @@ -34,6 +35,7 @@ class SplashCubit extends Cubit { final AltmeChatSupportCubit altmeChatSupportCubit; final DioClient client; final ProfileCubit profileCubit; + final PackageInfo? packageInfo; Future initialiseApp() async { double counter = 0; @@ -71,8 +73,14 @@ class SplashCubit extends Cubit { }); } - Future _getAppVersion() async { - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + Future _getAppVersion(PackageInfo? packageInformation) async { + late PackageInfo packageInfo; + if (packageInformation == null) { + packageInfo = await PackageInfo.fromPlatform(); + } else { + packageInfo = packageInformation; + } + final String? savedVersion = await secureStorageProvider.get( SecureStorageKeys.version, ); diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 008904ff7..b57d76cd1 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -951,12 +951,6 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w600, ); - TextStyle get discoverOverlayDescription => GoogleFonts.roboto( - color: const Color(0xffFFFFFF), - fontSize: 11, - fontWeight: FontWeight.w600, - ); - TextStyle get faqQue => GoogleFonts.roboto( color: const Color(0xffFFFFFF), fontSize: 16, diff --git a/packages/credential_manifest/lib/src/models/filter.dart b/packages/credential_manifest/lib/src/models/filter.dart index 2ab8fbae9..37bbf7b01 100644 --- a/packages/credential_manifest/lib/src/models/filter.dart +++ b/packages/credential_manifest/lib/src/models/filter.dart @@ -9,12 +9,15 @@ class Filter { required this.type, this.pattern, this.contains, + this.containsConst, }); factory Filter.fromJson(Map json) => _$FilterFromJson(json); final String type; final String? pattern; final Contains? contains; + @JsonKey(name: 'const') + String? containsConst; Map toJson() => _$FilterToJson(this); } diff --git a/packages/cryptocurrency_keys/test/src/cryptocurrency_keys_test.dart b/packages/cryptocurrency_keys/test/src/cryptocurrency_keys_test.dart index a0228db15..8803b7690 100644 --- a/packages/cryptocurrency_keys/test/src/cryptocurrency_keys_test.dart +++ b/packages/cryptocurrency_keys/test/src/cryptocurrency_keys_test.dart @@ -6,8 +6,9 @@ void main() { 'notice photo opera keen climb agent soft parrot best joke field devote'; const message = '{"name": "My name is Bibash."}'; - const cipherText = '¨`ýp<ÐW3AR1¯#.í©¥¿e’,|VrtuXÅ'; - const authenticationTag = '䃆U~ḈÞ¦BÌuDÅ'; + const cipherText = '‚Ô\$æ²½-ôÜwĪ8aã~…\n' + 'Yyz¶«Þž[f*ýç'; + const authenticationTag = '¸^\x01áAê?^Ü6u¿¡¡S'; const encryptionModelJson = { 'cipherText': cipherText, 'authenticationTag': authenticationTag, @@ -42,12 +43,13 @@ void main() { 'generateKeyPair() should always derive the same keypair when using the' ' same mnemonic', () async { final generatedKey = await cryptocurrencyKeys.generateKeyPair(mnemonic); - expect(generatedKey.privateKey.hashCode, equals(1100798733)); - expect(generatedKey.publicKey.hashCode, equals(1100798733)); + expect(generatedKey.privateKey.hashCode, equals(264718007)); + expect(generatedKey.publicKey.hashCode, equals(264718007)); }); test('response is encrypted correctly', () async { final encryptedData = await cryptocurrencyKeys.encrypt(message, mnemonic); + expect(encryptedData.cipherText, equals(cipherText)); expect(encryptedData.authenticationTag, equals(authenticationTag)); }); @@ -64,32 +66,29 @@ void main() { expect(decryptedData, equals(message)); }); - test('invalid cipherText throws Auth message', () async { + test('invalid cipherText throws Exception', () async { const inCipherText = '123'; const encryption = Encryption( cipherText: inCipherText, authenticationTag: authenticationTag, ); - try { - await cryptocurrencyKeys.decrypt(mnemonic, encryption); - } catch (e) { - final error = e.toString().startsWith('Auth message'); - expect(error, true); - } + + expect( + () => cryptocurrencyKeys.decrypt(mnemonic, encryption), + throwsException, + ); }); - test('invalid authenticationTag throws Auth message', () async { + test('invalid authenticationTag throws Exception', () async { const inValidAuthenticationTag = '123'; const encryption = Encryption( cipherText: cipherText, authenticationTag: inValidAuthenticationTag, ); - try { - await cryptocurrencyKeys.decrypt(mnemonic, encryption); - } catch (e) { - final error = e.toString().startsWith('Auth message'); - expect(error, true); - } + expect( + () => cryptocurrencyKeys.decrypt(mnemonic, encryption), + throwsException, + ); }); }); } diff --git a/packages/jwt_decode/test/src/jwt_decode_test.dart b/packages/jwt_decode/test/src/jwt_decode_test.dart index 6d067404b..74c066e10 100644 --- a/packages/jwt_decode/test/src/jwt_decode_test.dart +++ b/packages/jwt_decode/test/src/jwt_decode_test.dart @@ -63,7 +63,7 @@ void main() { const validJwtTokenWithInvalidPayload = '''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.W3siSGVsbG8iOjEyfV0.vfPVZG-CndvivC2JaWA4leWl_1F3pm1lr-l3BISqVAc'''; const jsonStringOfValidTokenOne = - r'''{scope: openid, response_type: id_token, client_id: did:web:altme.co, redirect_uri: https://altme.co/gaiax/login_redirect/287f58e9-a50c-11ec-bea0-0a1628958560, response_mode: post, claims: {"id_token":{},"vp_token":{"presentation_definition":{"id":"pass_for_gaiax","input_descriptors":[{"id":"GaiaxPass issued by altme","purpose":"Test for Gaia-X hackathon","format":{"ldp_vc":{"proof_type":["JsonWebSignature2020"]}},"constraints":{"limit_disclosure":"required","fields":[{"path":["$.credentialSubject.type"],"purpose":"One can only accept GaiaxPass","filter":{"type":"string","pattern":"GaiaxPass"}},{"path":["$.issuer"],"purpose":"One can accept only GaiaxPass signed by altme","filter":{"type":"string","pattern":"did:web:altme.co"}}]}}]}}}, nonce: 6j0RATZeIj, registration: {"id_token_signing_alg_values_supported":["RS256","ES256","ES256K","EdDSA"],"subject_syntax_types_supported":["did:web","did:tz","did:key","did:ion","did:pkh","did:ethr"]}, request_uri: https://altme.co/gaiax/login_request_uri/287f58e9-a50c-11ec-bea0-0a1628958560}'''; + r'''{scope: openid, response_type: id_token, client_id: did:web:talao.co, redirect_uri: https://talao.co/gaiax/login_redirect/287f58e9-a50c-11ec-bea0-0a1628958560, response_mode: post, claims: {"id_token":{},"vp_token":{"presentation_definition":{"id":"pass_for_gaiax","input_descriptors":[{"id":"GaiaxPass issued by Talao","purpose":"Test for Gaia-X hackathon","format":{"ldp_vc":{"proof_type":["JsonWebSignature2020"]}},"constraints":{"limit_disclosure":"required","fields":[{"path":["$.credentialSubject.type"],"purpose":"One can only accept GaiaxPass","filter":{"type":"string","pattern":"GaiaxPass"}},{"path":["$.issuer"],"purpose":"One can accept only GaiaxPass signed by Talao","filter":{"type":"string","pattern":"did:web:talao.co"}}]}}]}}}, nonce: 6j0RATZeIj, registration: {"id_token_signing_alg_values_supported":["RS256","ES256","ES256K","EdDSA"],"subject_syntax_types_supported":["did:web","did:tz","did:key","did:ion","did:pkh","did:ethr"]}, request_uri: https://talao.co/gaiax/login_request_uri/287f58e9-a50c-11ec-bea0-0a1628958560}'''; const inValidJwtTokenWithLessThanThreePart = 'partOne.partTwo'; const inValidJwtTokenWithThreePart = 'partOne.partTwo.partThree'; @@ -96,7 +96,7 @@ void main() { isA().having( (p0) => p0.toString(), 'toString()', - 'Exception: Invalid Token', + 'Exception: INVALID_TOKEN', ), ), ); @@ -118,10 +118,36 @@ void main() { isA().having( (p0) => p0.toString(), 'toString()', - 'Exception: Invalid Payload', + 'Exception: INVALID_PAYLOAD', ), ), ); }); + + test('parseJwtHeader throws exception for invalid token', () { + expect( + () => jwtDecode.parseJwtHeader('invalid.token'), + throwsA( + isA().having( + (p0) => p0.toString(), + 'toString()', + 'Exception: INVALID_TOKEN', + ), + ), + ); + }); + + test('parseJwtHeader returns header for valid token', () { + final header = jwtDecode.parseJwtHeader(validJwtToken); + expect(header['alg'], 'RS256'); + expect(header['typ'], 'JWT'); + }); + + test('parsePolygonIdJwtHeader throws exception for invalid token', () { + expect( + () => jwtDecode.parsePolygonIdJwtHeader('invalid.token'), + throwsException, + ); + }); }); } diff --git a/packages/oidc4vc/lib/src/oidc4vc.dart b/packages/oidc4vc/lib/src/oidc4vc.dart index 33679a15f..668bd74c7 100644 --- a/packages/oidc4vc/lib/src/oidc4vc.dart +++ b/packages/oidc4vc/lib/src/oidc4vc.dart @@ -28,8 +28,6 @@ class OIDC4VC { /// {@macro ebsi} OIDC4VC(); - final Dio dio = Dio(); - /// create JWK from mnemonic String privateKeyFromMnemonic({ required String mnemonic, @@ -148,17 +146,20 @@ class OIDC4VC { required VCFormatType vcFormatType, required String? clientAssertion, required bool secureAuthorizedFlow, + required Dio dio, }) async { try { final openIdConfiguration = await getOpenIdConfig( baseUrl: issuer, isAuthorizationServer: false, + dio: dio, ); final authorizationEndpoint = await readAuthorizationEndPoint( openIdConfiguration: openIdConfiguration, issuer: issuer, oidc4vciDraftType: oidc4vciDraftType, + dio: dio, ); final authorizationRequestParemeters = getAuthorizationRequestParemeters( @@ -428,6 +429,7 @@ class OIDC4VC { required OpenIdConfiguration openIdConfiguration, required String accessToken, required String? cnonce, + required Dio dio, List? authorizationDetails, }) async { var nonce = cnonce; @@ -500,6 +502,7 @@ class OIDC4VC { privateKey: privateKey, accessToken: accessToken, nonce: nonce, + dio: dio, ); /// update nonce value @@ -532,6 +535,7 @@ class OIDC4VC { privateKey: privateKey, accessToken: accessToken, nonce: cnonce, + dio: dio, ); credentialResponseData.add(credentialResponseDataValue); @@ -553,6 +557,7 @@ class OIDC4VC { required OIDC4VCIDraftType oidc4vciDraftType, required String redirectUri, required OpenIdConfiguration openIdConfiguration, + required Dio dio, String? preAuthorizedCode, String? userPin, String? code, @@ -564,6 +569,7 @@ class OIDC4VC { openIdConfiguration: openIdConfiguration, issuer: issuer, oidc4vciDraftType: oidc4vciDraftType, + dio: dio, ); Map? tokenResponse; @@ -587,6 +593,7 @@ class OIDC4VC { tokenEndPoint: tokenEndPoint, tokenData: tokenData, authorization: authorization, + dio: dio, ); if (tokenResponse.containsKey('c_nonce')) { @@ -619,6 +626,7 @@ class OIDC4VC { required String privateKey, required String accessToken, required String? nonce, + required Dio dio, }) async { final credentialData = await buildCredentialData( nonce: nonce, @@ -665,6 +673,7 @@ class OIDC4VC { required Map credentialHeaders, required Map? body, required String deferredCredentialEndpoint, + required Dio dio, }) async { final dynamic credentialResponse = await dio.post( deferredCredentialEndpoint, @@ -730,6 +739,7 @@ class OIDC4VC { required String didKey, required bool fromStatusList, required bool isCachingEnabled, + required Dio dio, }) async { try { if (isURL(didKey)) { @@ -746,6 +756,7 @@ class OIDC4VC { baseUrl: didKey, isAuthorizationServer: isAuthorizationServer, isCachingEnabled: isCachingEnabled, + dio: dio, ); final authorizationServer = openIdConfiguration.authorizationServer; @@ -755,6 +766,7 @@ class OIDC4VC { baseUrl: authorizationServer, isAuthorizationServer: true, isCachingEnabled: isCachingEnabled, + dio: dio, ); } @@ -765,6 +777,7 @@ class OIDC4VC { final response = await dioGet( openIdConfiguration.jwksUri!, isCachingEnabled: isCachingEnabled, + dio: dio, ); return response as Map; @@ -789,6 +802,7 @@ class OIDC4VC { required OpenIdConfiguration openIdConfiguration, required String issuer, required OIDC4VCIDraftType oidc4vciDraftType, + required Dio dio, }) async { var tokenEndPoint = '$issuer/token'; @@ -801,6 +815,7 @@ class OIDC4VC { final authorizationServerConfiguration = await getOpenIdConfig( baseUrl: authorizationServer, isAuthorizationServer: true, + dio: dio, ); if (authorizationServerConfiguration.tokenEndpoint != null) { @@ -815,6 +830,7 @@ class OIDC4VC { required OpenIdConfiguration openIdConfiguration, required String issuer, required OIDC4VCIDraftType oidc4vciDraftType, + required Dio dio, }) async { var authorizationEndpoint = '$issuer/authorize'; @@ -827,6 +843,7 @@ class OIDC4VC { final authorizationServerConfiguration = await getOpenIdConfig( baseUrl: authorizationServer, isAuthorizationServer: true, + dio: dio, ); if (authorizationServerConfiguration.authorizationEndpoint != null) { @@ -1123,6 +1140,7 @@ class OIDC4VC { required Map? publicJwk, required bool fromStatusList, required bool isCachingEnabled, + required Dio dio, }) async { try { Map? publicKeyJwk; @@ -1134,6 +1152,7 @@ class OIDC4VC { didKey: issuer, fromStatusList: fromStatusList, isCachingEnabled: isCachingEnabled, + dio: dio, ); publicKeyJwk = readPublicKeyJwk( @@ -1150,7 +1169,6 @@ class OIDC4VC { } late final bool isVerified; - if (kty == 'OKP') { isVerified = verifyTokenEdDSA( publicKey: publicKeyJwk, @@ -1271,6 +1289,7 @@ class OIDC4VC { required String tokenEndPoint, required Map tokenData, required String? authorization, + required Dio dio, }) async { /// getting token final tokenHeaders = { @@ -1365,6 +1384,7 @@ class OIDC4VC { required String? stateValue, required ClientType clientType, required ProofHeaderType proofHeaderType, + required Dio dio, }) async { try { final private = jsonDecode(privateKey) as Map; @@ -1596,6 +1616,7 @@ class OIDC4VC { Future getOpenIdConfig({ required String baseUrl, required bool isAuthorizationServer, + required Dio dio, bool isCachingEnabled = false, }) async { ///for OIDC4VCI, the server is an issuer the metadata are all in th @@ -1611,6 +1632,7 @@ class OIDC4VC { final data = await getOpenIdConfigSecondMethod( baseUrl, isCachingEnabled: isCachingEnabled, + dio: dio, ); return data; } @@ -1619,6 +1641,7 @@ class OIDC4VC { final response = await dioGet( url, isCachingEnabled: isCachingEnabled, + dio: dio, ); final data = response is String ? jsonDecode(response) as Map @@ -1629,6 +1652,7 @@ class OIDC4VC { final data = await getOpenIdConfigSecondMethod( baseUrl, isCachingEnabled: isCachingEnabled, + dio: dio, ); return data; } @@ -1637,6 +1661,7 @@ class OIDC4VC { Future getOpenIdConfigSecondMethod( String baseUrl, { required bool isCachingEnabled, + required Dio dio, }) async { final url = '$baseUrl/.well-known/openid-credential-issuer'; @@ -1644,10 +1669,12 @@ class OIDC4VC { final response = await dioGet( url, isCachingEnabled: isCachingEnabled, + dio: dio, ); final data = response is String ? jsonDecode(response) as Map : response as Map; + return OpenIdConfiguration.fromJson(data); } catch (e) { throw Exception('OPENID-CONFIGURATION-ISSUE'); @@ -1712,40 +1739,45 @@ class OIDC4VC { Future dioGet( String uri, { + required Dio dio, Map headers = const { 'Content-Type': 'application/json; charset=UTF-8', }, bool isCachingEnabled = false, }) async { try { - final secureStorageProvider = getSecureStorage; - final cachedData = await secureStorageProvider.get(uri); dynamic response; dio.options.headers = headers; - if (!isCachingEnabled || cachedData == null) { - response = await dio.get(uri); - } else { - final cachedDataJson = jsonDecode(cachedData); - final expiry = int.parse(cachedDataJson['expiry'].toString()); - - final isExpired = DateTime.now().millisecondsSinceEpoch > expiry; - - if (isExpired) { + if (isCachingEnabled) { + final secureStorageProvider = getSecureStorage; + final cachedData = await secureStorageProvider.get(uri); + if (cachedData == null) { response = await dio.get(uri); } else { - /// directly return cached data - /// returned here to avoid the caching override everytime - final response = await cachedDataJson['data']; - return response; + final cachedDataJson = jsonDecode(cachedData); + final expiry = int.parse(cachedDataJson['expiry'].toString()); + + final isExpired = DateTime.now().millisecondsSinceEpoch > expiry; + + if (isExpired) { + response = await dio.get(uri); + } else { + /// directly return cached data + /// returned here to avoid the caching override everytime + final response = await cachedDataJson['data']; + return response; + } } - } - final expiry = - DateTime.now().add(const Duration(days: 2)).millisecondsSinceEpoch; + final expiry = + DateTime.now().add(const Duration(days: 2)).millisecondsSinceEpoch; - final value = {'expiry': expiry, 'data': response.data}; - await secureStorageProvider.set(uri, jsonEncode(value)); + final value = {'expiry': expiry, 'data': response.data}; + await secureStorageProvider.set(uri, jsonEncode(value)); + } else { + response = await dio.get(uri); + } return response.data; } on FormatException catch (_) { diff --git a/packages/oidc4vc/lib/src/vc_format_type.dart b/packages/oidc4vc/lib/src/vc_format_type.dart index 34c83d918..93540f3eb 100644 --- a/packages/oidc4vc/lib/src/vc_format_type.dart +++ b/packages/oidc4vc/lib/src/vc_format_type.dart @@ -14,7 +14,7 @@ enum VCFormatType { } extension VCFormatTypeX on VCFormatType { - String get formattedString { + String get vcValue { switch (this) { case VCFormatType.ldpVc: return 'ldp_vc'; @@ -29,7 +29,7 @@ extension VCFormatTypeX on VCFormatType { } } - String get value { + String get urlValue { switch (this) { case VCFormatType.ldpVc: return 'ldp_vc'; @@ -40,34 +40,34 @@ extension VCFormatTypeX on VCFormatType { case VCFormatType.jwtVcJsonLd: return 'jwt_vc_json-ld'; case VCFormatType.vcSdJWT: - return 'vc+sd-jwt'; + return 'vcsd-jwt'; } } - String get urlValue { + bool get supportCryptoCredential { switch (this) { case VCFormatType.ldpVc: - return 'ldp_vc'; - case VCFormatType.jwtVc: - return 'jwt_vc'; case VCFormatType.jwtVcJson: - return 'jwt_vc_json'; + return true; + case VCFormatType.jwtVc: case VCFormatType.jwtVcJsonLd: - return 'jwt_vc_json-ld'; case VCFormatType.vcSdJWT: - return 'vcsd-jwt'; + return false; } } - bool get supportCryptoCredential { + String get vpValue { switch (this) { case VCFormatType.ldpVc: - case VCFormatType.jwtVcJson: - return true; + return 'ldp_vp'; case VCFormatType.jwtVc: + return 'jwt_vp'; + case VCFormatType.jwtVcJson: + return 'jwt_vp_json'; case VCFormatType.jwtVcJsonLd: + return 'jwt_vp_json-ld'; case VCFormatType.vcSdJWT: - return false; + return 'vc+sd-jwt'; } } } diff --git a/packages/oidc4vc/test/src/client_authentication_test.dart b/packages/oidc4vc/test/src/client_authentication_test.dart new file mode 100644 index 000000000..7bc7993a8 --- /dev/null +++ b/packages/oidc4vc/test/src/client_authentication_test.dart @@ -0,0 +1,15 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('ClientAuthenticationX', () { + test('value', () { + expect(ClientAuthentication.none.value, 'none'); + expect( + ClientAuthentication.clientSecretBasic.value, 'client_secret_basic'); + expect(ClientAuthentication.clientSecretPost.value, 'client_secret_post'); + expect(ClientAuthentication.clientId.value, 'client_id'); + expect(ClientAuthentication.clientSecretJwt.value, 'client_secret_jwt'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/client_type_test.dart b/packages/oidc4vc/test/src/client_type_test.dart new file mode 100644 index 000000000..dd5de9522 --- /dev/null +++ b/packages/oidc4vc/test/src/client_type_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('ClientTypeX', () { + test('getTitle', () { + expect(ClientType.p256JWKThumprint.getTitle, 'P-256 JWK Thumbprint'); + expect(ClientType.did.getTitle, 'DID'); + expect(ClientType.confidential.getTitle, 'Confidential Client'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/const_values.dart b/packages/oidc4vc/test/src/const_values.dart index 3169cbc83..1eb369812 100644 --- a/packages/oidc4vc/test/src/const_values.dart +++ b/packages/oidc4vc/test/src/const_values.dart @@ -30,10 +30,12 @@ const keyWithAlg = { 'alg': 'HS256', }; -const didKey = 'did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; +const issuer = 'https://talao.co/issuer/zxhaokccsi'; -const kid = - '''did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj#Cgcg1y9xj9uWFw56PMc29XBd9EReixzvnftBz8JwQFiB'''; +const clientId = + '''did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrbpg5is8LfTLuQ1RsW5r7s7ZjbDDFbDgy1tLrdc7Bj3itBGQkuGUQyfzKhFqbUNW2PqJPMSSzWoF2DGSvDSijCtJtYCSRsjSVLrwu5oHNbnPFvSEC4iRZPpU6B6nExRBTa'''; + +const kid = ''; const ES256Alg = 'ES256'; @@ -41,40 +43,7 @@ const ES256KAlg = 'ES256K'; const HS256Alg = 'HS256'; -const thumbprint = [ - 173, - 150, - 139, - 1, - 202, - 186, - 32, - 132, - 51, - 6, - 216, - 230, - 103, - 154, - 26, - 196, - 52, - 23, - 248, - 132, - 91, - 7, - 58, - 174, - 149, - 38, - 148, - 157, - 199, - 122, - 118, - 36, -]; +const thumbprint = 'rZaLAcq6IIQzBtjmZ5oaxDQX+IRbBzqulSaUncd6diQ'; const rfc7638Jwk = { 'e': 'AQAB', @@ -83,37 +52,5 @@ const rfc7638Jwk = { // ignore: lines_longer_than_80_chars '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw', }; -const expectedThumbprintForrfc7638Jwk = [ - 55, - 54, - 203, - 177, - 120, - 124, - 184, - 48, - 156, - 119, - 238, - 140, - 55, - 5, - 197, - 225, - 111, - 251, - 158, - 133, - 151, - 21, - 144, - 31, - 30, - 76, - 89, - 177, - 17, - 130, - 245, - 123, -]; +const expectedThumbprintForrfc7638Jwk = + 'NzbLsXh8uDCcd+6MNwXF4W/7noWXFZAfHkxZsRGC9Xs'; diff --git a/packages/oidc4vc/test/src/helper_function_test.dart b/packages/oidc4vc/test/src/helper_function_test.dart new file mode 100644 index 000000000..f6fbdd488 --- /dev/null +++ b/packages/oidc4vc/test/src/helper_function_test.dart @@ -0,0 +1,21 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/src/helper_function.dart'; + +void main() { + group('listToString', () { + test('converts list of strings to a single string with spaces', () { + final inputList = ['This', 'is', 'Bibash']; + expect(listToString(inputList), 'This is Bibash'); + }); + + test('converts list of numbers to a single string with spaces', () { + final inputList = [1, 2, 3, 4]; + expect(listToString(inputList), '1 2 3 4'); + }); + + test('returns empty string for empty input list', () { + final inputList = []; + expect(listToString(inputList), ''); + }); + }); +} diff --git a/packages/oidc4vc/test/src/issuer_token_parameters/issuer_token_parameters_class.dart b/packages/oidc4vc/test/src/issuer_token_parameters/issuer_token_parameters_class.dart index 5710dff77..bc4f2cbb5 100644 --- a/packages/oidc4vc/test/src/issuer_token_parameters/issuer_token_parameters_class.dart +++ b/packages/oidc4vc/test/src/issuer_token_parameters/issuer_token_parameters_class.dart @@ -1,41 +1,66 @@ -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:oidc4vc/oidc4vc.dart'; - -// import '../const_values.dart'; -// import '../token_parameters/token_parameters_class.dart'; - -// class IssuerTokenParameterTest extends TokenParameterTest { -// final issuerTokenParameters = IssuerTokenParameters(privateKey, '', '', ''); - -// @override -// void publicKeyTest() { -// expect(issuerTokenParameters.publicJWK, publicJWK); -// } - -// @override -// void didTest() { -// expect(tokenParameters.did, didKey); -// } - -// @override -// void keyIdTest() { -// expect(tokenParameters.kid, kid); -// } - -// @override -// void algorithmIsES256KTest() { -// expect(tokenParameters.alg, ES256KAlg); -// } - -// @override -// void algorithmIsES256Test() { -// final tokenParameters = IssuerTokenParameters(privateKey2, '', '', ''); -// expect(tokenParameters.alg, ES256Alg); -// } - -// @override -// void algorithmIsNotNullTest() { -// final tokenParameters = IssuerTokenParameters(keyWithAlg, '', '', ''); -// expect(tokenParameters.alg, HS256Alg); -// } -// } +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +import '../const_values.dart'; +import '../token_parameters/token_parameters_class.dart'; + +class IssuerTokenParameterTest extends TokenParameterTest { + final issuerTokenParameters = IssuerTokenParameters( + privateKey: privateKey, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + issuer: issuer, + kid: '', + ); + + @override + void publicKeyTest() { + expect(issuerTokenParameters.publicJWK, publicJWK); + } + + @override + void didTest() { + expect(tokenParameters.did, clientId); + } + + @override + void keyIdTest() { + expect(tokenParameters.kid, kid); + } + + @override + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } + + @override + void algorithmIsES256Test() { + final tokenParameters = IssuerTokenParameters( + privateKey: privateKey2, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + issuer: issuer, + ); + expect(tokenParameters.alg, ES256Alg); + } + + @override + void algorithmIsNotNullTest() { + final tokenParameters = IssuerTokenParameters( + privateKey: keyWithAlg, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + issuer: issuer, + ); + expect(tokenParameters.alg, HS256Alg); + } +} diff --git a/packages/oidc4vc/test/src/issuer_token_parameters/issuer_token_parameters_test.dart b/packages/oidc4vc/test/src/issuer_token_parameters/issuer_token_parameters_test.dart index edbdf1e0c..5de3e5960 100644 --- a/packages/oidc4vc/test/src/issuer_token_parameters/issuer_token_parameters_test.dart +++ b/packages/oidc4vc/test/src/issuer_token_parameters/issuer_token_parameters_test.dart @@ -1,47 +1,47 @@ -// import 'package:flutter_test/flutter_test.dart'; - -// import 'issuer_token_parameters_class.dart'; - -// void main() { -// group('override test', () { -// final issuerTokenParameterTest = IssuerTokenParameterTest(); - -// // test( -// // 'public key is P-256K private key without d parameter', -// // issuerTokenParameterTest.publicKeyTest, -// // ); - -// // test('did EBSI', issuerTokenParameterTest.didTest); - -// // test('kID EBSI', issuerTokenParameterTest.keyIdTest); - -// group('algorithm test', () { -// test( -// "algorithm is ES256K when key's curve is not P-256", -// issuerTokenParameterTest.algorithmIsES256KTest, -// ); - -// test( -// "algorithm is ES256 when key's curve is P-256", -// issuerTokenParameterTest.algorithmIsES256Test, -// ); - -// test( -// 'if alg is not null then return as it is', -// issuerTokenParameterTest.algorithmIsNotNullTest, -// ); -// }); - -// // group('thumbprint test', () { -// // test( -// // 'thumbprint of the public Key', -// // issuerTokenParameterTest.thumprintOfKey, -// // ); - -// // test( -// // 'thumbrprint of the Key from exemple in rfc 7638', -// // issuerTokenParameterTest.thumprintOfKeyForrfc7638, -// // ); -// // }); -// }); -// } +import 'package:flutter_test/flutter_test.dart'; + +import 'issuer_token_parameters_class.dart'; + +void main() { + group('override test', () { + final issuerTokenParameterTest = IssuerTokenParameterTest(); + + test( + 'public key is P-256K private key without d parameter', + issuerTokenParameterTest.publicKeyTest, + ); + + test('did EBSI', issuerTokenParameterTest.didTest); + + test('kID EBSI', issuerTokenParameterTest.keyIdTest); + + group('algorithm test', () { + test( + "algorithm is ES256K when key's curve is not P-256", + issuerTokenParameterTest.algorithmIsES256KTest, + ); + + test( + "algorithm is ES256 when key's curve is P-256", + issuerTokenParameterTest.algorithmIsES256Test, + ); + + test( + 'if alg is not null then return as it is', + issuerTokenParameterTest.algorithmIsNotNullTest, + ); + }); + + group('thumbprint test', () { + test( + 'thumbprint of the public Key', + issuerTokenParameterTest.thumprintOfKey, + ); + + test( + 'thumbrprint of the Key from exemple in rfc 7638', + issuerTokenParameterTest.thumprintOfKeyForrfc7638, + ); + }); + }); +} diff --git a/packages/oidc4vc/test/src/media_type_test.dart b/packages/oidc4vc/test/src/media_type_test.dart new file mode 100644 index 000000000..5237155c2 --- /dev/null +++ b/packages/oidc4vc/test/src/media_type_test.dart @@ -0,0 +1,13 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('MediaTypeX', () { + test('typ', () { + expect(MediaType.proofOfOwnership.typ, 'openid4vci-proof+jwt'); + expect(MediaType.basic.typ, 'JWT'); + expect(MediaType.walletAttestation.typ, 'wiar+jwt'); + expect(MediaType.selectiveDisclosure.typ, 'kb+jwt'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/oidc4vc_test.dart b/packages/oidc4vc/test/src/oidc4vc_test.dart index 16d13c8dd..42ddd0db3 100644 --- a/packages/oidc4vc/test/src/oidc4vc_test.dart +++ b/packages/oidc4vc/test/src/oidc4vc_test.dart @@ -15,27 +15,24 @@ import 'package:http_mock_adapter/http_mock_adapter.dart'; import 'package:mocktail/mocktail.dart'; import 'package:oidc4vc/oidc4vc.dart'; -class MockDio extends Mock implements Dio {} - class MockPkcePair extends Mock implements PkcePair {} void main() { final client = Dio(); final dioAdapter = DioAdapter(dio: Dio(BaseOptions()), matcher: const UrlRequestMatcher()); + client.httpClientAdapter = dioAdapter; const mnemonic = 'position taste mention august skin taste best air sure acoustic poet ritual'; + final oidc4vc = OIDC4VC(); test('OIDC4VC class can be instantiated', () { - final oidc4vc = OIDC4VC(); expect(oidc4vc, isNotNull); }); - group('EBSI DID and JWK', () { - final oidc4vc = OIDC4VC(); - + group('OIDC4VC DID and JWK', () { const seedBytes = [ 179, 252, @@ -80,39 +77,14 @@ void main() { }; const index = 0; - test('JWK from mnemonic', () async { - final jwk = await oidc4vc.privateKeyFromMnemonic( + test('JWK from mnemonic', () { + final jwk = oidc4vc.privateKeyFromMnemonic( mnemonic: mnemonic, indexValue: index, ); expect(jsonDecode(jwk), expectedECJwk); }); - // group('getPrivateKey', () { - // test('privateKey from mnemonic', () async { - // final jwk = await oidc4vc.getPrivateKey( - // mnemonic: mnemonic, - // indexValue: index, - // ); - // expect(jwk, expectedECJwk); - // }); - - // test('privateKey from key', () async { - // const key = { - // 'crv': 'secp256k1', - // 'd': 's_wb6Ef1ardGsT5Il6WLRvQ9Zu0lp7I2OVwtzT5iQpo', - // 'kty': 'EC', - // 'x': 'qs4JLbsmA-7L-3o9V4BoNVrDtYoWE2OOZIvujoVJZ1U', - // 'y': '8PLGROkTALZP3YHY5pm0yrMVCjQoctHM3uaxug70mq8', - // }; - // final jwk = await oidc4vc.getPrivateKey( - // privateKey: jsonEncode(key), - // indexValue: index, - // ); - // expect(jwk, expectedECJwk); - // }); - // }); - test('JWK from seeds', () { final jwk = oidc4vc.jwkFromSeed(seedBytes: Uint8List.fromList(seedBytes)); expect(jwk, expectedECJwk); @@ -120,7 +92,6 @@ void main() { }); test('P256 JWK from mnemonics', () { - final oidc4vc = OIDC4VC(); final jwk = oidc4vc.p256PrivateKeyFromMnemonics( mnemonic: mnemonic, indexValue: 0, @@ -128,9 +99,9 @@ void main() { final expectedP256Jwk = { 'kty': 'EC', 'crv': 'P-256', - 'd': 'cFCdgT569Shto8jEVbyKqdtEck0EcUSwGz_vqrclTC8', - 'x': 'vhoSh07qpGqiCJNGQJmY8YaARnxuHUgv403c5TmrABQ', - 'y': 'E04Ate9RceryoHlz1x3BVIbN9LZR74TWRCeeMNY-oew', + 'd': 's_wb6Ef1ardGsT5Il6WLRvQ9Zu0lp7I2OVwtzT5iQpo', + 'x': 'MZZjpNhZGGxqBcPXq499FVC2iu5FcZWwti5u8hgMUaI', + 'y': 'KD4zffV54PZUsQzTzVgoVlWHwKqogRF3JpKQnIGwIRM', }; expect(jsonDecode(jwk), expectedP256Jwk); @@ -151,8 +122,6 @@ void main() { const kid = '3623b877bbb24b08ba390f3585418f53'; - final oidc4vc = OIDC4VC(); - test('sign and verify with edDSA', () async { final token = oidc4vc.generateTokenEdDSA( payload: payload, @@ -170,8 +139,6 @@ void main() { }); group('selective disclosure', () { - final oidc4vc = OIDC4VC(); - const content = '["Qg_O64zqAxe412a108iroA", "phone_number", "+81-80-1234-5678"]'; @@ -192,8 +159,6 @@ void main() { }); group('publicKeyBase58ToPublicJwk', () { - final oidc4vc = OIDC4VC(); - const publicKeyBase58 = '2S73k5pn5umfnaW31qx6dXFndEn6SmWw7LpgSjNNC5BF'; final expectedPublicJWK = { @@ -209,632 +174,586 @@ void main() { }); group('EBSI: getAuthorizationUriForIssuer', () { - const issuer = 'https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi'; - - const openIdConfiguration = - '{"authorization_server":"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi/authorize_server","credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi","credential_supported":[{"display":[{"locale":"en-GB","name":"Verifiable diploma"}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"]},{"display":[{"locale":"en-GB","name":"Email proof"}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","EmailPass"]},{"display":[{"locale":"en-GB","name":"Verifiable Id"}],"format":"jwt_vc","id":"VerifiableId","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableId"]}],"credentials_supported":[{"display":[{"locale":"en-GB","name":"Verifiable diploma"}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"]},{"display":[{"locale":"en-GB","name":"Email proof"}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","EmailPass"]},{"display":[{"locale":"en-GB","name":"Verifiable Id"}],"format":"jwt_vc","id":"VerifiableId","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableId"]}],"deferred_credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi/deferred","service_documentation":"New environment for V3 compliance test, use specific did:key"}'; - - const selectedCredentials = [ - { - 'format': 'jwt_vc', - 'types': [ - 'VerifiableCredential', - 'VerifiableAttestation', - 'VerifiableDiploma', - ], - } - ]; + const selectedCredentials = ['EmailPass']; const clientId = - 'did:key:zBhBLmYmyihtomRdJJNEKzbPj51o4a3GYFeZoRHSABKUwqdjiQPY2fjSoTWRD2nvVmaLnLHU8GXeGFmL1ed8ZAoPGoDeBMwcp4XFchNeTw917v2evpUjyst2gxZuRrVDSxDCb6G2Z1Tbz8kWHbjfUKgLVQd7CexS5GvPHfSQGdsLdjj4cNZvvZa'; - const webLink = 'https://app.altme.io/app/download/oidc4vc'; - const schema = - 'openid-credential-offer://?credential_offer_uri=https://talao.co/sandbox/ebsi/issuer/credential_offer_uri/a572f0a6-56a9-11ee-ac4f-0a1628958560'; - const issuerState = 'a53d709e-56a9-11ee-828d-0a1628958560'; - const nonce = '6044cc7d-2bd9-4804-82ae-c9950d8eedd8'; - const options = '[0]'; - const state = '4eee52c0-e524-4b62-a005-629e97f82dc5'; + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjZka1U2Wk1GSzc5V3dpY3dKNXJieEUxM3pTdWtCWTJPb0VpVlVFanFNRWMiLCJ5IjoiUm5Iem55VmxyUFNNVDdpckRzMTVEOXd4Z01vamlTREFRcGZGaHFUa0xSWSJ9'; + const redirectUri = 'https://app.altme.io/app/download/oidc4vc'; + + const issuer = 'https://talao.co/issuer/mfyttabosy'; + + const issuerState = 'test7'; + const nonce = '8b60e2fb-87f3-4401-8107-0f0128ea01ab'; + const pkcePair = PkcePair( - 'l-NEmG-JlH-VwUxNoNmv8NPD47K_2Pu0hEY6tAHg9pE', - 'E0BAjRGdb3bspwyNsGnRDcV1zHp4CyCB7_2EQUsB4Ls', + 'Pzy4U_sJ0J7VdIAR6JCwL5hbecv30egmJVP81VDFAnk', + '4KorCwmYyO-_t4i_hva7F3aHGpT_2WqqDh6erimepOA', + ); + + const state = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlVmVyaWZpZXIiOiI0S29yQ3dtWXlPLV90NGlfaHZhN0YzYUhHcFRfMldxcURoNmVyaW1lcE9BIiwiY3JlZGVudGlhbHMiOlsiRW1haWxQYXNzIl0sImlzc3VlciI6Imh0dHBzOi8vdGFsYW8uY28vaXNzdWVyL21meXR0YWJvc3kiLCJpc0VCU0lWMyI6ZmFsc2UsImNsaWVudF9pZCI6ImRpZDpqd2s6ZXlKamNuWWlPaUpRTFRJMU5pSXNJbXQwZVNJNklrVkRJaXdpZUNJNklqWmthMVUyV2sxR1N6YzVWM2RwWTNkS05YSmllRVV4TTNwVGRXdENXVEpQYjBWcFZsVkZhbkZOUldNaUxDSjVJam9pVW01SWVtNTVWbXh5VUZOTlZEZHBja1J6TVRWRU9YZDRaMDF2YW1sVFJFRlJjR1pHYUhGVWEweFNXU0o5IiwiaWF0IjoxNzE0NTQ5OTUxfQ.JJtv8H52NTvPzR3IPET1sXGALdt0yXaQBQbGvDLKNlM'; + + const authorizationEndPoint = 'https://app.altme.io/app/download/authorize'; + + const openIdConfiguration = + '{"authorization_server":null,"credential_endpoint":"https://talao.co/issuer/mfyttabosy/credential","credential_issuer":"https://talao.co/issuer/mfyttabosy","subject_syntax_types_supported":["urn:ietf:params:oauth:jwk-thumbprint","did:key","did:ebsi","did:tz","did:pkh","did:hedera","did:key","did:ethr","did:web","did:jwk"],"token_endpoint":"https://talao.co/issuer/mfyttabosy/token","batch_endpoint":null,"authorization_endpoint":"https://talao.co/issuer/mfyttabosy/authorize","subject_trust_frameworks_supported":["ebsi"],"credentials_supported":null,"credential_configurations_supported":{"DBCGuest":{"credential_definition":{"type":["VerifiableCredential","DBCGuest"]},"display":[{"background_color":"#3B6F6D","background_image":{"alt_text":"Connected open cubes in blue with one orange cube as a background of the card","url":"https://i.ibb.co/CHqjxrJ/dbc-card-hig-res.png"},"description":"The DBC Guest credential is a DIIP example.","logo":{"alt_text":"An orange block shape, with the text Dutch Blockchain Coalition next to it, portraying the logo of the Dutch Blockchain Coalition.","url":"https://dutchblockchaincoalition.org/assets/images/icons/Logo-DBC.png"},"name":"DBC Guest (DIIP)","text_color":"#FFFFFF"},{"background_color":"#3B6F6D","background_image":{"alt_text":"Connected open cubes in blue with one orange cube as a background of the card","url":"https://i.ibb.co/CHqjxrJ/dbc-card-hig-res.png"},"description":"The DBC guest credential is a DIIP example.","locale":"en-US","logo":{"alt_text":"An orange block shape, with the text Dutch Blockchain Coalition next to it, portraying the logo of the Dutch Blockchain Coalition.","url":"https://dutchblockchaincoalition.org/assets/images/icons/Logo-DBC.png"},"name":"DBC Guest (DIIP)","text_color":"#FFFFFF"},{"background_color":"#3B6F6D","background_image":{"alt_text":"Connected open cubes in blue with one orange cube as a background of the card","url":"https://i.ibb.co/CHqjxrJ/dbc-card-hig-res.png"},"description":"De DBC gast credential is een DIIP voorbeeld.","locale":"nl-NL","logo":{"alt_text":"Aaneengesloten open blokken in de kleur blauw, met een blok in de kleur oranje, die tesamen de achtergrond van de kaart vormen.","url":"https://dutchblockchaincoalition.org/assets/images/icons/Logo-DBC.png"},"name":"DBC gast (DIIP)","text_color":"#FFFFFF"}],"format":"jwt_vc_json","scope":"DBCGuest_scope"},"EmailPass":{"credential_definition":{"type":["VerifiableCredential","EmailPass"]},"credential_signing_alg_values_supported":["ES256K","ES256","ES384","RS256"],"cryptographic_binding_methods_supported":["DID","jwk"],"display":[{"locale":"en-GB","name":"Proof of Email"}],"format":"jwt_vc_json","scope":"EmailPass_scope"},"EmployeeCredential":{"credential_definition":{"type":["VerifiableCredential","EmployeeCredential"]},"credential_signing_alg_values_supported":["ES256K","ES256","ES384","RS256"],"cryptographic_binding_methods_supported":["DID","jwk"],"display":[{"background_color":"#12107c","locale":"en-US","logo":{"alt_text":"a square logo of a university","url":"https://exampleuniversity.com/public/logo.png"},"name":"Employee Credential","text_color":"#FFFFFF"}],"format":"jwt_vc_json","scope":"EmployeeCredential_scope"},"Over18":{"credential_definition":{"type":["VerifiableCredential","Over18"]},"credential_signing_alg_values_supported":["ES256K","ES256","ES384","RS256"],"cryptographic_binding_methods_supported":["DID","jwk"],"display":[{"locale":"en-GB","name":"Over 18yo proof"},{"locale":"fr-GB","name":"Preuve de majorité"}],"format":"jwt_vc_json","scope":"Over18_scope"},"PhoneProof":{"credential_definition":{"type":["VerifiableCredential","PhoneProof"]},"credential_signing_alg_values_supported":["ES256K","ES256","ES384","RS256"],"cryptographic_binding_methods_supported":["DID","jwk"],"display":[{"locale":"en-GB","name":"Proof of phone number"}],"format":"jwt_vc_json","scope":"PhoneProof_scope"},"VerifiableId":{"credential_definition":{"credentialSubject":{"dateIssued":{"display":[{"locale":"en-US","name":"Issuance date"},{"locale":"fr-FR","name":"Délivré le"}],"mandatory":true},"dateOfBirth":{"display":[{"locale":"en-US","name":"Date of birth"},{"locale":"fr-FR","name":"Né(e) le"}],"mandatory":true},"email":{"display":[{"locale":"en-US","name":"Email"},{"locale":"fr-FR","name":"Email"}],"mandatory":true},"familyName":{"display":[{"locale":"en-US","name":"Family name"},{"locale":"fr-FR","name":"Nom"}],"mandatory":true},"firstName":{"display":[{"locale":"en-US","name":"First name"},{"locale":"fr-FR","name":"Prénom(s)"}],"mandatory":true},"gender":{"display":[{"locale":"en-US","name":"Gender"},{"locale":"fr-FR","name":"Sexe"}],"mandatory":true},"issuing_country":{"display":[{"locale":"en-US","name":"Issuing country"},{"locale":"fr-FR","name":"Délivré par"}],"mandatory":true},"phone_number":{"display":[{"locale":"en-US","name":"Phone number"},{"locale":"fr-FR","name":"Téléphone"}],"mandatory":true}},"order":["firstName","familyName","dateOfBirth","gender","dateIssued","issuing_country","email","phone_number"],"type":["VerifiableCredential","VerifiableId"]},"credential_signing_alg_values_supported":["ES256K","ES256","ES384","RS256"],"cryptographic_binding_methods_supported":["DID","jwk"],"display":[{"background_color":"#12107c","locale":"en-US","name":"Verifiable Id","text_color":"#FFFFFF"}],"format":"jwt_vc_json","scope":"VerifiableId_scope"}},"deferred_credential_endpoint":"https://talao.co/issuer/mfyttabosy/deferred","service_documentation":null,"credential_manifest":null,"credential_manifests":null,"issuer":null,"jwks_uri":"https://talao.co/issuer/mfyttabosy/jwks","grant_types_supported":["authorization_code","urn:ietf:params:oauth:grant-type:pre-authorized_code"]}'; + + test( + 'given Url of openid request we return Uri for authentication endpoint', + () async { + const expectedAuthorizationEndpoint = + 'https://talao.co/issuer/mfyttabosy/authorize'; + + const expectedAuthorizationRequestParemeters = { + 'response_type': 'code', + 'redirect_uri': 'https://app.altme.io/app/download/oidc4vc', + 'state': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlVmVyaWZpZXIiOiI0S29yQ3dtWXlPLV90NGlfaHZhN0YzYUhHcFRfMldxcURoNmVyaW1lcE9BIiwiY3JlZGVudGlhbHMiOlsiRW1haWxQYXNzIl0sImlzc3VlciI6Imh0dHBzOi8vdGFsYW8uY28vaXNzdWVyL21meXR0YWJvc3kiLCJpc0VCU0lWMyI6ZmFsc2UsImNsaWVudF9pZCI6ImRpZDpqd2s6ZXlKamNuWWlPaUpRTFRJMU5pSXNJbXQwZVNJNklrVkRJaXdpZUNJNklqWmthMVUyV2sxR1N6YzVWM2RwWTNkS05YSmllRVV4TTNwVGRXdENXVEpQYjBWcFZsVkZhbkZOUldNaUxDSjVJam9pVW01SWVtNTVWbXh5VUZOTlZEZHBja1J6TVRWRU9YZDRaMDF2YW1sVFJFRlJjR1pHYUhGVWEweFNXU0o5IiwiaWF0IjoxNzE0NTQ5OTUxfQ.JJtv8H52NTvPzR3IPET1sXGALdt0yXaQBQbGvDLKNlM', + 'nonce': '8b60e2fb-87f3-4401-8107-0f0128ea01ab', + 'code_challenge': '4KorCwmYyO-_t4i_hva7F3aHGpT_2WqqDh6erimepOA', + 'code_challenge_method': 'S256', + 'issuer_state': 'test7', + 'client_metadata': + '{\"authorization_endpoint\":\"https://app.altme.io/app/download/authorize\",\"scopes_supported\":[\"openid\"],\"response_types_supported\":[\"vp_token\",\"id_token\"],\"client_id_schemes_supported\":[\"redirect_uri\",\"did\"],\"grant_types_supported\":[\"authorization_code\",\"pre-authorized_code\"],\"subject_types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"ES256\",\"ES256K\"],\"request_object_signing_alg_values_supported\":[\"ES256\",\"ES256K\"],\"request_parameter_supported\":true,\"request_uri_parameter_supported\":true,\"request_authentication_methods_supported\":{\"authorization_endpoint\":[\"request_object\"]},\"vp_formats_supported\":{\"jwt_vp\":{\"alg_values_supported\":[\"ES256\",\"ES256K\"]},\"jwt_vc\":{\"alg_values_supported\":[\"ES256\",\"ES256K\"]}},\"subject_syntax_types_supported\":[\"urn:ietf:params:oauth:jwk-thumbprint\",\"did:key\",\"did:pkh\",\"did:key\",\"did:polygonid\"],\"subject_syntax_types_discriminations\":[\"did:key:jwk_jcs-pub\",\"did:ebsi:v1\"],\"subject_trust_frameworks_supported\":[\"ebsi\"],\"id_token_types_supported\":[\"subject_signed_id_token\"],\"token_endpoint_auth_method\":\"client_id\"}', + 'client_id': + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjZka1U2Wk1GSzc5V3dpY3dKNXJieEUxM3pTdWtCWTJPb0VpVlVFanFNRWMiLCJ5IjoiUm5Iem55VmxyUFNNVDdpckRzMTVEOXd4Z01vamlTREFRcGZGaHFUa0xSWSJ9', + 'scope': 'openid', + 'authorization_details': + '[{\"type\":\"openid_credential\",\"credential_configuration_id\":\"EmailPass\"}]', + }; + + dioAdapter.onGet( + 'https://talao.co/issuer/mfyttabosy/.well-known/openid-credential-issuer', + (request) => request.reply(200, jsonDecode(openIdConfiguration)), + ); + + final (authorizationEndpoint, authorizationRequestParemeters) = + await oidc4vc.getAuthorizationData( + selectedCredentials: selectedCredentials, + clientId: clientId, + clientSecret: null, + redirectUri: redirectUri, + issuerState: issuerState, + nonce: nonce, + pkcePair: pkcePair, + state: state, + authorizationEndPoint: authorizationEndPoint, + scope: false, + clientAuthentication: ClientAuthentication.clientId, + oidc4vciDraftType: OIDC4VCIDraftType.draft13, + vcFormatType: VCFormatType.jwtVcJson, + clientAssertion: null, + secureAuthorizedFlow: false, + issuer: issuer, + dio: client, + ); + + expect(authorizationEndpoint, expectedAuthorizationEndpoint); + expect( + authorizationRequestParemeters, + expectedAuthorizationRequestParemeters, + ); + }, ); - // test( - // 'given Url of openid request we return Uri for authentication endpoint', - // () async { - // final expectedAuthorizationEndpointdUri = Uri.parse( - // 'https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi/authorize?response_type=code&client_id=did%3Akey%3AzBhBLmYmyihtomRdJJNEKzbPj51o4a3GYFeZoRHSABKUwqdjiQPY2fjSoTWRD2nvVmaLnLHU8GXeGFmL1ed8ZAoPGoDeBMwcp4XFchNeTw917v2evpUjyst2gxZuRrVDSxDCb6G2Z1Tbz8kWHbjfUKgLVQd7CexS5GvPHfSQGdsLdjj4cNZvvZa&redirect_uri=https%3A%2F%2Fapp.altme.io%2Fapp%2Fdownload%2Foidc4vc%3Furi%3Dopenid-credential-offer%3A%2F%2F%3Fcredential_offer_uri%3Dhttps%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fcredential_offer_uri%2Fa572f0a6-56a9-11ee-ac4f-0a1628958560%26code_verifier%3Dl-NEmG-JlH-VwUxNoNmv8NPD47K_2Pu0hEY6tAHg9pE%26options%3D%5B0%5D&scope=openid&issuer_state=a53d709e-56a9-11ee-828d-0a1628958560&state=4eee52c0-e524-4b62-a005-629e97f82dc5&nonce=6044cc7d-2bd9-4804-82ae-c9950d8eedd8&code_challenge=E0BAjRGdb3bspwyNsGnRDcV1zHp4CyCB7_2EQUsB4Ls&code_challenge_method=S256&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22locations%22%3A%5B%22https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fpcbrwbvrsi%22%5D%2C%22format%22%3A%22jwt_vc%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22VerifiableAttestation%22%2C%22VerifiableDiploma%22%5D%7D%5D&client_metadata=%7B%22authorization_endpoint%22%3A%22https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fpcbrwbvrsi%2Fauthorize%22%2C%22scopes_supported%22%3A%5B%22openid%22%5D%2C%22response_types_supported%22%3A%5B%22vp_token%22%2C%22id_token%22%5D%2C%22subject_types_supported%22%3A%5B%22public%22%5D%2C%22id_token_signing_alg_values_supported%22%3A%5B%22ES256%22%5D%2C%22request_object_signing_alg_values_supported%22%3A%5B%22ES256%22%5D%2C%22vp_formats_supported%22%3A%7B%22jwt_vp%22%3A%7B%22alg_values_supported%22%3A%5B%22ES256%22%5D%7D%2C%22jwt_vc%22%3A%7B%22alg_values_supported%22%3A%5B%22ES256%22%5D%7D%7D%2C%22subject_syntax_types_supported%22%3A%5B%22urn%3Aietf%3Aparams%3Aoauth%3Ajwk-thumbprint%22%2C%22did%F0%9F%94%91jwk_jcs-pub%22%5D%2C%22id_token_types_supported%22%3A%5B%22subject_signed_id_token%22%5D%7D'); - - // dioAdapter.onGet( - // issuer, - // (request) => request.reply(200, jsonDecode(openIdConfiguration)), - // ); - - // final oidc4vc = OIDC4VC(); - - // final authorizationEndpointdUri = - // await oidc4vc.getAuthorizationUriForIssuer( - // selectedCredentials: selectedCredentials, - // clientId: clientId, - // webLink: webLink, - // schema: schema, - // issuer: issuer, - // issuerState: issuerState, - // nonce: nonce, - // options: options, - // state: state, - // pkcePair: pkcePair, - // ); - - // expect( - // authorizationEndpointdUri.toString(), - // expectedAuthorizationEndpointdUri.toString(), - // ); - // }, - // ); test( 'throw Exception with when request is not valid', () async { - final oidc4vc = OIDC4VC(); - - // expect( - // () async => oidc4vc.getAuthorizationUriForIssuer( - // selectedCredentials: [], - // clientId: '', - // webLink: '', - // schema: '', - // issuer: '', - // issuerState: '', - // nonce: '', - // options: '', - // state: '', - // pkcePair: const PkcePair( - // '', - // '', - // ), - // ), - // throwsA( - // isA().having( - // (p0) => p0.toString(), - // 'toString()', - // 'Exception: Not a valid openid url to initiate issuance', - // ), - // ), - // ); + expect( + () async => oidc4vc.getAuthorizationData( + selectedCredentials: [], + clientId: '', + issuer: '', + issuerState: '', + nonce: '', + state: '', + pkcePair: const PkcePair( + '', + '', + ), + authorizationEndPoint: '', + clientAssertion: '', + clientAuthentication: ClientAuthentication.clientId, + clientSecret: '', + oidc4vciDraftType: OIDC4VCIDraftType.draft11, + redirectUri: '', + scope: false, + secureAuthorizedFlow: false, + vcFormatType: VCFormatType.jwtVc, + dio: client, + ), + throwsA( + isA().having( + (p0) => p0.toString(), + 'toString()', + 'Exception: NOT_A_VALID_OPENID_URL', + ), + ), + ); }, ); test( 'given correct authorization request parameter', () async { - const openIdConfigurationResponse = - '{"authorization_server":"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi/authorize_server","credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi","credentials_supported":[{"display":[{"locale":"en-GB","name":"Verifiable diploma"}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"]},{"display":[{"locale":"en-GB","name":"Email proof"}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","EmailPass"]},{"display":[{"locale":"en-GB","name":"Verifiable Id"}],"format":"jwt_vc","id":"VerifiableId","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableId"]}],"deferred_credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi/deferred","service_documentation":"New environment for V3 compliance test, use specific did:key"}'; const expectedAuthorizationRequestParemeters = - r'{"response_type":"code","client_id":"did:key:zBhBLmYmyihtomRdJJNEKzbPj51o4a3GYFeZoRHSABKUwqdjiQPY2fjSoTWRD2nvVmaLnLHU8GXeGFmL1ed8ZAoPGoDeBMwcp4XFchNeTw917v2evpUjyst2gxZuRrVDSxDCb6G2Z1Tbz8kWHbjfUKgLVQd7CexS5GvPHfSQGdsLdjj4cNZvvZa","redirect_uri":"https://app.altme.io/app/download/oidc4vc?uri=openid-credential-offer://?credential_offer_uri=https://talao.co/sandbox/ebsi/issuer/credential_offer_uri/a572f0a6-56a9-11ee-ac4f-0a1628958560&code_verifier=l-NEmG-JlH-VwUxNoNmv8NPD47K_2Pu0hEY6tAHg9pE&options=[0]","scope":"openid","issuer_state":"a53d709e-56a9-11ee-828d-0a1628958560","state":"4eee52c0-e524-4b62-a005-629e97f82dc5","nonce":"6044cc7d-2bd9-4804-82ae-c9950d8eedd8","code_challenge":"E0BAjRGdb3bspwyNsGnRDcV1zHp4CyCB7_2EQUsB4Ls","code_challenge_method":"S256","authorization_details":"[{\"type\":\"openid_credential\",\"locations\":[\"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi\"],\"format\":\"jwt_vc\",\"types\":[\"VerifiableCredential\",\"VerifiableAttestation\",\"VerifiableDiploma\"]}]","client_metadata":"{\"authorization_endpoint\":\"https://talao.co/sandbox/ebsi/issuer/pcbrwbvrsi/authorize\",\"scopes_supported\":[\"openid\"],\"response_types_supported\":[\"vp_token\",\"id_token\"],\"subject_types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"ES256\"],\"request_object_signing_alg_values_supported\":[\"ES256\"],\"vp_formats_supported\":{\"jwt_vp\":{\"alg_values_supported\":[\"ES256\"]},\"jwt_vc\":{\"alg_values_supported\":[\"ES256\"]}},\"subject_syntax_types_supported\":[\"urn:ietf:params:oauth:jwk-thumbprint\",\"did🔑jwk_jcs-pub\"],\"id_token_types_supported\":[\"subject_signed_id_token\"]}"}'; - final oidc4vc = OIDC4VC(); - - // final authorizationRequestParemeters = - // oidc4vc.getAuthorizationRequestParemeters( - // selectedCredentials: selectedCredentials, - // clientId: clientId, - // webLink: webLink, - // schema: schema, - // issuer: issuer, - // issuerState: issuerState, - // nonce: nonce, - // options: options, - // state: state, - // pkcePair: pkcePair, - // openidConfigurationResponse: - // jsonDecode(openIdConfigurationResponse) as Map, - // ); - - // expect( - // jsonEncode(authorizationRequestParemeters), - // expectedAuthorizationRequestParemeters, - // ); + r'{"response_type":"code","redirect_uri":"https://app.altme.io/app/download/oidc4vc","state":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlVmVyaWZpZXIiOiI0S29yQ3dtWXlPLV90NGlfaHZhN0YzYUhHcFRfMldxcURoNmVyaW1lcE9BIiwiY3JlZGVudGlhbHMiOlsiRW1haWxQYXNzIl0sImlzc3VlciI6Imh0dHBzOi8vdGFsYW8uY28vaXNzdWVyL21meXR0YWJvc3kiLCJpc0VCU0lWMyI6ZmFsc2UsImNsaWVudF9pZCI6ImRpZDpqd2s6ZXlKamNuWWlPaUpRTFRJMU5pSXNJbXQwZVNJNklrVkRJaXdpZUNJNklqWmthMVUyV2sxR1N6YzVWM2RwWTNkS05YSmllRVV4TTNwVGRXdENXVEpQYjBWcFZsVkZhbkZOUldNaUxDSjVJam9pVW01SWVtNTVWbXh5VUZOTlZEZHBja1J6TVRWRU9YZDRaMDF2YW1sVFJFRlJjR1pHYUhGVWEweFNXU0o5IiwiaWF0IjoxNzE0NTQ5OTUxfQ.JJtv8H52NTvPzR3IPET1sXGALdt0yXaQBQbGvDLKNlM","nonce":"8b60e2fb-87f3-4401-8107-0f0128ea01ab","code_challenge":"4KorCwmYyO-_t4i_hva7F3aHGpT_2WqqDh6erimepOA","code_challenge_method":"S256","issuer_state":"test7","client_metadata":"{\"authorization_endpoint\":\"https://app.altme.io/app/download/authorize\",\"scopes_supported\":[\"openid\"],\"response_types_supported\":[\"vp_token\",\"id_token\"],\"client_id_schemes_supported\":[\"redirect_uri\",\"did\"],\"grant_types_supported\":[\"authorization_code\",\"pre-authorized_code\"],\"subject_types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"ES256\",\"ES256K\"],\"request_object_signing_alg_values_supported\":[\"ES256\",\"ES256K\"],\"request_parameter_supported\":true,\"request_uri_parameter_supported\":true,\"request_authentication_methods_supported\":{\"authorization_endpoint\":[\"request_object\"]},\"vp_formats_supported\":{\"jwt_vp\":{\"alg_values_supported\":[\"ES256\",\"ES256K\"]},\"jwt_vc\":{\"alg_values_supported\":[\"ES256\",\"ES256K\"]}},\"subject_syntax_types_supported\":[\"urn:ietf:params:oauth:jwk-thumbprint\",\"did:key\",\"did:pkh\",\"did:key\",\"did:polygonid\"],\"subject_syntax_types_discriminations\":[\"did:key:jwk_jcs-pub\",\"did:ebsi:v1\"],\"subject_trust_frameworks_supported\":[\"ebsi\"],\"id_token_types_supported\":[\"subject_signed_id_token\"],\"token_endpoint_auth_method\":\"client_id\"}","client_id":"did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjZka1U2Wk1GSzc5V3dpY3dKNXJieEUxM3pTdWtCWTJPb0VpVlVFanFNRWMiLCJ5IjoiUm5Iem55VmxyUFNNVDdpckRzMTVEOXd4Z01vamlTREFRcGZGaHFUa0xSWSJ9","scope":"openid","authorization_details":"[{\"type\":\"openid_credential\",\"credential_configuration_id\":\"EmailPass\"}]"}'; + + final authorizationRequestParemeters = + oidc4vc.getAuthorizationRequestParemeters( + selectedCredentials: selectedCredentials, + clientId: clientId, + authorizationEndPoint: authorizationEndPoint, + clientAssertion: null, + scope: false, + clientAuthentication: ClientAuthentication.clientId, + oidc4vciDraftType: OIDC4VCIDraftType.draft13, + vcFormatType: VCFormatType.jwtVcJson, + secureAuthorizedFlow: false, + issuer: issuer, + issuerState: issuerState, + nonce: nonce, + state: state, + pkcePair: pkcePair, + clientSecret: null, + openIdConfiguration: OpenIdConfiguration.fromJson( + jsonDecode(openIdConfiguration) as Map, + ), + redirectUri: redirectUri, + ); + + expect( + jsonEncode(authorizationRequestParemeters), + expectedAuthorizationRequestParemeters, + ); }, ); }); - //group('OIC4VC request credential', () { - // final credentialRequest = Uri.parse( - // 'https://app.altme.io/app/download?credential_type=https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&issuer=https://talao.co/sandbox/ebsi/issuer/vgvghylozl?code=cb803d46-9c88-11ed-bdb3-0a1628958560&state=0d189873-9c87-11ed-8dbf-0a1628958560', - // ); - - // final credentialRequestWithPreAuthorizedCode = Uri.parse( - // 'openid://initiate_issuance?issuer=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fvgvghylozl&credential_type=https%3A%2F%2Fapi.preprod.oidc4vc.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&op_state=test&pre-authorized_code=ff8e73c5-ae07-11ed-b1f7-0a1628958560&user_pin_required=False', - // ); - - // const issuerResponse = - // r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; - - // const tokenResponse = - // '{"access_token":"7a07dd19-a879-11ed-ad95-0a1628958560","c_nonce":"7a07de0f-a879-11ed-822b-0a1628958560","token_type":"Bearer","expires_in":1000}'; // ignore: lines_longer_than_80_chars - - // const credentialRequestUrl = - // 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential'; - - // const credentialRequestResponse = - // '{"format":"jwt_vc","credential":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU5NDcwNTguODkyNzc4LCJpYXQiOjE2NzU5NDYwNTguODkyNzcxLCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTk0NjA1OC44OTI3NzYsIm5vbmNlIjoiMTFmNWY2MDAtYTg3Ni0xMWVkLTkwZjItMGExNjI4OTU4NTYwIiwic3ViIjoiZGlkOmVic2k6em94UkdWWlFuZFRmUWs1NEI3dEtkd3dOZGhhaTVnbTlGOE5hdjhlY2VOQUJhIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjEvc2NoZW1hcy8weGJmNzhmYzA4YTdhOWYyOGY1NDc5ZjU4ZGVhMjY5ZDM2NTdmNTRmMTNjYTM3ZDM4MGNkNGU5MjIzN2ZiNjkxZGQiLCJ0eXBlIjoiSnNvblNjaGVtYVZhbGlkYXRvcjIwMTgifSwiY3JlZGVudGlhbFN0YXR1cyI6eyJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3N0YXR1cy9lZHVjYXRpb24jaGlnaGVyRWR1Y2F0aW9uIzM5MmFjN2Y2LTM5OWEtNDM3Yi1hMjY4LTQ2OTFlYWQ4ZjE3NiIsInR5cGUiOiJDcmVkZW50aWFsU3RhdHVzTGlzdDIwMjAifSwiY3JlZGVudGlhbFN1YmplY3QiOnsiYXdhcmRpbmdPcHBvcnR1bml0eSI6eyJhd2FyZGluZ0JvZHkiOnsiZWlkYXNMZWdhbElkZW50aWZpZXIiOiJVbmtub3duIiwiaG9tZXBhZ2UiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS8iLCJpZCI6ImRpZDplYnNpOnpkUnZ2S2JYaFZWQnNYaGF0anVpQmhzIiwicHJlZmVycmVkTmFtZSI6IkxlYXN0b24gVW5pdmVyc2l0eSIsInJlZ2lzdHJhdGlvbiI6IjA1OTcwNjVKIn0sImVuZGVkQXRUaW1lIjoiMjAyMC0wNi0yNlQwMDowMDowMFoiLCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNBd2FyZGluZ09wcG9ydHVuaXR5IiwiaWRlbnRpZmllciI6Imh0dHBzOi8vY2VydGlmaWNhdGUtZGVtby5iY2RpcGxvbWEuY29tL2NoZWNrLzg3RUQyRjIyNzBFNkM0MTQ1NkU5NEI4NkI5RDkxMTVCNEUzNUJDQ0FEMjAwQTQ5Qjg0NjU5MkMxNEY3OUM4NkJWMUZuYmxsdGEwTlpUbkprUjNsRFdsUm1URGxTUlVKRVZGWklTbU5tWXpKaFVVNXNaVUo1WjJGSlNIcFdibVpaIiwibG9jYXRpb24iOiJGUkFOQ0UiLCJzdGFydGVkQXRUaW1lIjoiMjAxOS0wOS0wMlQwMDowMDowMFoifSwiZGF0ZU9mQmlydGgiOiIxOTkzLTA0LTA4IiwiZmFtaWx5TmFtZSI6IkRPRSIsImdpdmVuTmFtZXMiOiJKYW5lIiwiZ3JhZGluZ1NjaGVtZSI6eyJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNHcmFkaW5nU2NoZW1lIiwidGl0bGUiOiIyIHllYXIgZnVsbC10aW1lIHByb2dyYW1tZSAvIDQgc2VtZXN0ZXJzIn0sImlkIjoiZGlkOmVic2k6em94UkdWWlFuZFRmUWs1NEI3dEtkd3dOZGhhaTVnbTlGOE5hdjhlY2VOQUJhIiwiaWRlbnRpZmllciI6IjA5MDQwMDgwODRIIiwibGVhcm5pbmdBY2hpZXZlbWVudCI6eyJhZGRpdGlvbmFsTm90ZSI6WyJESVNUUklCVVRJT04gTUFOQUdFTUVOVCJdLCJkZXNjcmlwdGlvbiI6IlRoZSBNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIChNSUNTKSBhdCB0aGUgVW5pdmVyc2l0eSBvZiBMdXhlbWJvdXJnIGVuYWJsZXMgc3R1ZGVudHMgdG8gYWNxdWlyZSBkZWVwZXIga25vd2xlZGdlIGluIGNvbXB1dGVyIHNjaWVuY2UgYnkgdW5kZXJzdGFuZGluZyBpdHMgYWJzdHJhY3QgYW5kIGludGVyZGlzY2lwbGluYXJ5IGZvdW5kYXRpb25zLCBmb2N1c2luZyBvbiBwcm9ibGVtIHNvbHZpbmcgYW5kIGRldmVsb3BpbmcgbGlmZWxvbmcgbGVhcm5pbmcgc2tpbGxzLiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0xlYXJuaW5nQWNoaWV2bWVudCIsInRpdGxlIjoiTWFzdGVyIGluIEluZm9ybWF0aW9uIGFuZCBDb21wdXRlciBTY2llbmNlcyJ9LCJsZWFybmluZ1NwZWNpZmljYXRpb24iOnsiZWN0c0NyZWRpdFBvaW50cyI6MTIwLCJlcWZMZXZlbCI6NywiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdTcGVjaWZpY2F0aW9uIiwiaXNjZWRmQ29kZSI6WyI3Il0sIm5xZkxldmVsIjpbIjciXX19LCJldmlkZW5jZSI6eyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwiaWQiOiJodHRwczovL2Vzc2lmLmV1cm9wYS5ldS90c3ItdmEvZXZpZGVuY2UvZjJhZWVjOTctZmMwZC00MmJmLThjYTctMDU0ODE5MmQ1Njc4Iiwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6Mjk2MmZiNzg0ZGY2MWJhYTI2N2M4MTMyNDk3NTM5ZjhjNjc0YjM3YzEyNDRhN2EifSwiaWQiOiJ1cm46dXVpZDo2YjFkODQxMS05ZWQ1LTQ1NjYtOWM3Zi00YzI0MTY1ZmYyMzYiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTAyLTA5VDEyOjM0OjE4WiIsImlzc3VlZCI6IjIwMjMtMDItMDlUMTI6MzQ6MThaIiwiaXNzdWVyIjoiZGlkOmVic2k6emhTdzVyUFhrY0hqdnF1d25WY1R6ekIiLCJwcm9vZiI6eyJjcmVhdGVkIjoiMjAyMi0wNC0yN1QxMjoyNTowN1oiLCJjcmVhdG9yIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJkb21haW4iOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUiLCJqd3MiOiJleUppTmpRaU9tWmhiSE5sTENKamNtbDBJanBiSW1JMk5DSmRMQ0poYkdjaU9pSkZVekkxTmtzaWZRLi5tSUJuTThYRFFxU1lLUU5YX0x2YUpobXNieUNyNU9aNWNVMlprLVJlcUxwcjRkb0ZzZ21vb2JrTzUxMjh0WnktOEtpbVZqSmtHdzB3TDF1QlduTUxXUSIsIm5vbmNlIjoiM2VhNjhkYWUtZDA3YS00ZGFhLTkzMmItZmJiNThmNWMyMGM0IiwidHlwZSI6IkVjZHNhU2VjcDI1NmsxU2lnbmF0dXJlMjAxOSJ9LCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZURpcGxvbWEiXSwidmFsaWRGcm9tIjoiMjAyMy0wMi0wOVQxMjozNDoxOFoifX0.uQK9sK-VtqmKjLJIw_v5Ff5xAMDAosZCtl1LFYxZhUolReD6a7O-NI1f5lcswBCZPLfJ-HJyb5iShehHObzFDA","c_nonce":"128952c8-a876-11ed-bbc4-0a1628958560","c_nonce_expires_in":1000}'; // ignore: lines_longer_than_80_chars - - // dioAdapter - // ..onGet( - // issuer, - // (request) => request.reply(200, jsonDecode(issuerResponse)), - // ) - // ..onPost( - // 'tokenUrl', - // (request) => request.reply( - // 200, - // jsonDecode(tokenResponse), - // ), - // ) - // ..onPost( - // credentialRequestUrl, - // (request) => request.reply( - // 200, - // jsonDecode(credentialRequestResponse), - // ), - // ); - - // test('When getCredentialType receive url it returns json response', - // () async { - // final oidc4vc = OIDC4VC(client); - // final credential = await oidc4vc.getCredential( - // credentialRequest, - // mnemonic, - // null, - // ); - // expect(jsonEncode(credential), credentialRequestResponse); - // }); - - //}); - // test('throw Exception when token is not verified', () { - // const issuerDid = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB'; - - // const didDocumentUrl = - // 'https://api-pilot.oidc4vc.eu/did-registry/v3/identifiers/$issuerDid'; - - // const didDocumentResponse = - // '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"crv":"P-521","kty":"EC","x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk","y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2"},"type":"Ed25519VerificationKey2019"}]}'; - - // dioAdapter.onGet( - // didDocumentUrl, - // (request) => request.reply(200, jsonDecode(didDocumentResponse)), - // ); - - // final oidc4vc = OIDC4VC(client); - - // expect( - // () async { - // await oidc4vc.getCredential( - // credentialRequest, - // mnemonic, - // null, - // ); - // }, - // throwsA( - // isA().having( - // (p0) => p0.toString(), - // 'toString()', - // 'Exception: VERIFICATION_ISSUE', - // ), - // ), - // ); - // }); - - // group('build token data', () { - // final oidc4vc = OIDC4VC(client); - // test('get token data with credentialRequestUri', () async { - // const expectedTokenData = - // '{"code":"cb803d46-9c88-11ed-bdb3-0a1628958560","grant_type":"authorization_code"}'; // ignore: lines_longer_than_80_chars - // final tokenData = oidc4vc.buildTokenData( - // credentialRequest, - // ); - // expect(jsonEncode(tokenData), expectedTokenData); - // }); - - // test('get token data with credentialRequestUri', () { - // const expectedTokenData = - // '{"pre-authorized_code":"ff8e73c5-ae07-11ed-b1f7-0a1628958560","grant_type":"urn:ietf:params:oauth:grant-type:pre-authorized_code"}'; // ignore: lines_longer_than_80_chars - // final tokenData = - // oidc4vc.buildTokenData(credentialRequestWithPreAuthorizedCode); - // expect(jsonEncode(tokenData), expectedTokenData); - // }); - // }); - - // group('getIssuer', () { - // final oidc4vc = OIDC4VC(client); - // test('get issuer with credentialRequestUri', () { - // const expectedIssuer = - // 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; - // final issuer = oidc4vc.getIssuer(credentialRequest); - // expect(expectedIssuer, issuer); - // }); - - // test( - // 'get issuer with credentialRequestUri when PreAuthorizedCode is given', // ignore: lines_longer_than_80_chars - // () { - // const expectedIssuer = - // 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; - // final issuer = - // oidc4vc.getIssuer(credentialRequestWithPreAuthorizedCode); - // expect(expectedIssuer, issuer); - // }); - // }); - - // test('get readTokenEndPoint with openidConfigurationResponse', () async { - // const openidConfigurationResponse = - // r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; - - // final oidc4vc = OIDC4VC(client); - - // final issuer = oidc4vc.readTokenEndPoint( - // Response( - // requestOptions: RequestOptions(path: ''), - // data: jsonDecode(openidConfigurationResponse) as Map, - // ), - // ); - // expect(issuer, tokenUrl); - // }); - - // test('get issuer did with openidConfigurationResponse', () { - // const openidConfigurationResponse = - // r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"},{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; - // final oidc4vc = OIDC4VC(client); - - // final issuer = oidc4vc.readIssuerDid( - // Response( - // requestOptions: RequestOptions(path: ''), - // data: jsonDecode(openidConfigurationResponse) as Map, - // ), - // ); - // expect(issuer, 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB'); - // }); - - // test('get publicKey did with didDocumentResponse', () { - // const didDocumentResponse = - // '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"},"type":"Ed25519VerificationKey2019"}]}'; - // const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; - - // const expectedPublicKey = - // '{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"}'; // ignore: lines_longer_than_80_chars - - // final oidc4vc = OIDC4VC(client); - - // final publicKey = oidc4vc.readPublicKeyJwk( - // issuerDid, - // Response( - // requestOptions: RequestOptions(path: ''), - // data: jsonDecode(didDocumentResponse) as Map, - // ), - // ); - // expect(jsonEncode(publicKey), expectedPublicKey); - // }); - // }); - - // group('verify encoded data', () { - // const issuerDid1 = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; - // const issuerDid2 = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzC'; - // const issuerDid3 = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzC'; - - // const issuerKid = - // 'did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53'; - - // const didDocumentUrl = - // 'https://api-pilot.oidc4vc.eu/did-registry/v3/identifiers/$issuerDid1'; - - // const didDocumentResponse = - // '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"crv":"P-521","kty":"EC","x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk","y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2"},"type":"Ed25519VerificationKey2019"}]}'; - - // const didDocumentUrl2 = - // 'https://api-pilot.oidc4vc.eu/did-registry/v3/identifiers/$issuerDid2'; - - // const didDocumentResponse2 = - // '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"crv":"Ed25519","kty":"OKP","x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk","y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2"},"type":"Ed25519VerificationKey2019"}]}'; - - // const didDocumentUrl3 = - // 'https://api-pilot.oidc4vc.eu/did-registry/v3/identifiers/$issuerDid3'; - - // const didDocumentResponse3 = - // '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"alg":"EdDSA","crv":"Ed25519","kid":"-1909572257","kty":"OKP","x":"XWxGtApfcqmKI7p0OKnF5JSEWMVoLsytFXLEP7xZ_l8"},"type":"Ed25519VerificationKey2019"}]}'; - // dioAdapter - // ..onGet( - // didDocumentUrl, - // (request) => request.reply(200, jsonDecode(didDocumentResponse)), - // ) - // ..onGet( - // didDocumentUrl2, - // (request) => request.reply(200, jsonDecode(didDocumentResponse2)), - // ) - // ..onGet( - // didDocumentUrl3, - // (request) => request.reply(200, jsonDecode(didDocumentResponse3)), - // ); - - // final oidc4vc = OIDC4VC(client); - // test('returns VerificationType.verified', () async { - // const vcJwt = 'eyJhbGciOiJFUzUxMiJ9.' - // 'UGF5bG9hZA.' - // 'AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZq' - // 'wqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8Kp' - // 'EHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn'; - - // final isVerified = await oidc4vc.verifyEncodedData( - // issuerDid: issuerDid1, - // jwt: vcJwt, - // issuerKid: issuerKid, - // ); - - // expect(isVerified, VerificationType.verified); - // }); - - // test('returns VerificationType.notVerified', () async { - // const vcJwt = - // 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; // ignore: lines_longer_than_80_chars - - // final isVerified = await oidc4vc.verifyEncodedData( - // issuerDid: issuerDid1, - // jwt: vcJwt, - // issuerKid: issuerKid, - // ); - - // expect(isVerified, VerificationType.notVerified); - // }); - - // test('returns VerificationType.unKnown', () async { - // const vcJwt = 'random'; - - // final isVerified = await oidc4vc.verifyEncodedData( - // issuerDid: issuerDid2, - // jwt: vcJwt, - // issuerKid: issuerKid, - // ); - // expect(isVerified, VerificationType.unKnown); - // }); - - // test('returns VerificationType.notVerified for OKP', () async { - // const vcJwt = 'eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0.' - // 'eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9.' // ignore: lines_longer_than_80_chars - // 'rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg'; // ignore: lines_longer_than_80_chars - - // final isVerified = await oidc4vc.verifyEncodedData( - // issuerDid: issuerDid3, - // jwt: vcJwt, - // issuerKid: issuerKid, - // ); - // expect(isVerified, VerificationType.verified); - // }); - // }); - - // group('getCredentialRequest', () { - // final oidc4vc = OIDC4VC(client); - - // test('extract correct credential type url from openId', () async { - // final url = oidc4vc.getCredentialRequest(givenOpenIdRequest); - // expect( - // url, - // 'https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd', - // ); - // }); - - // test('credential type url is empty when url is not OK', () async { - // final url = oidc4vc.getCredentialRequest('www.example.com'); - // expect(url, ''); - // }); - // }); - - // test('extract correct credential type url from openId', () async { - // final oidc4vc = OIDC4VC(client); - // final url = oidc4vc.getCredentialRequest(givenOpenIdRequest); - // expect( - // url, - // 'https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd', - // ); - // }); - - // group('get token', () { - // test('get correct token ', () async { - // final oidc4vc = OIDC4VC(client); - - // final json = { - // 'code': 'cb803d46-9c88-11ed-bdb3-0a1628958560', - // 'grant_type': 'authorization_code' - // }; - - // const expectedValue = - // '{"access_token":"7a07dd19-a879-11ed-ad95-0a1628958560","c_nonce":"7a07de0f-a879-11ed-822b-0a1628958560","token_type":"Bearer","expires_in":1000}'; // ignore: lines_longer_than_80_chars - - // final token = await oidc4vc.getToken(tokenUrl, json); - // expect(jsonEncode(token), expectedValue); - // }); - - // test('throw expection when invalid value is sent', () async { - // expect( - // () async { - // dioAdapter.onPost( - // tokenUrl, - // (request) => request.throws( - // 401, - // DioException(requestOptions: RequestOptions(path: tokenUrl)), - // ), - // ); - // final oidc4vc = OIDC4VC(client); - - // final json = {}; - - // await oidc4vc.getToken(tokenUrl, json); - // }, - // throwsA(isA()), - // ); - // }); - // }); - - // group('get did', () { - // final oidc4vc = OIDC4VC(client); - - // const expectedDidP256K = - // 'did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; - // test('from mnemonic ', () async { - // final did = await oidc4vc.getDidFromMnemonic(mnemonic, null); - // expect(did, expectedDidP256K); - // }); - - // test('from P-256K privateKey ', () async { - // const key = { - // 'crv': 'P-256K', - // 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', - // 'kty': 'EC', - // 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - // 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' - // }; - // final did = await oidc4vc.getDidFromMnemonic(null, jsonEncode(key)); - // expect(did, expectedDidP256K); - // }); - // test('from secp256k1 privateKey ', () async { - // const expectedDidsecp256k1 = - // 'did:ebsi:zfuA5yVRZVaKna9pRCHv28yyCYWiXWHZXXvtp3AbTdB4p'; - // const key = { - // 'crv': 'secp256k1', - // 'd': 'jMudzhP9YFNywIeIYYJbHtyizeaXOa1PWdX-qBjPTg8', - // 'kty': 'EC', - // 'x': 'yEC4JxUbpYx2-tKExh2NrLpETx-nnudZfcg4AcyL1to', - // 'y': 'HNh3aF4zQsnf_sFXUVaSzrQF85veDoVxhPQ-163wUYM' - // }; - // final did = await oidc4vc.getDidFromMnemonic(null, jsonEncode(key)); - // expect(did, expectedDidsecp256k1); - // }); - - // group('send Presentation', () { - // const url = - // 'https://talao.co/sandbox/ebsi/login/endpoint/f1722b9e-ae19-11ed-ac9f-0a1628958560'; - - // final uri = Uri.parse( - // 'openid://?scope=openid&response_type=id_token&client_id=xjcqarovuv&redirect_uri=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Flogin%2Fendpoint%2Ff1722b9e-ae19-11ed-ac9f-0a1628958560&claims=%7B%27id_token%27%3A+%7B%27email%27%3A+None%7D%2C+%27vp_token%27%3A+%7B%27presentation_definition%27%3A+%7B%27id%27%3A+%27f17247aa-ae19-11ed-9241-0a1628958560%27%2C+%27input_descriptors%27%3A+%5B%7B%27constraints%27%3A+%7B%27fields%27%3A+%5B%7B%27path%27%3A+%5B%27%24.credentialSchema.id%27%5D%2C+%27filter%27%3A+%7B%27type%27%3A+%27string%27%2C+%27pattern%27%3A+%27https%3A%2F%2Fapi.preprod.oidc4vc.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd%27%7D%7D%5D%7D%2C+%27id%27%3A+%27f1724e6c-ae19-11ed-a3cf-0a1628958560%27%2C+%27name%27%3A+%27Input+descriptor+1%27%2C+%27purpose%27%3A+%27+%27%7D%5D%2C+%27format%27%3A+%7B%27jwt_vp%27%3A+%7B%27alg%27%3A+%5B%27ES256K%27%2C+%27ES256%27%2C+%27PS256%27%2C+%27RS256%27%5D%7D%7D%7D%7D%7D&nonce=f17239d6-ae19-11ed-8550-0a1628958560', - // ); - - // final credentialToBePresented = [ - // r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zhFWcvr8DFL3cAVdheCpjHg3sPn1WUh9Gynm6hevPFzpw","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]},"type":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-16T12:04:19Z","issued":"2023-02-16T12:04:19Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.oidc4vc.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-16T12:04:19Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-16T12:04:19Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zhFWcvr8DFL3cAVdheCpjHg3sPn1WUh9Gynm6hevPFzpw","type":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","issuedBy":{"name":""},"expires":"","awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-16T17:34:24.679713","presentation":null},{"acquisitionAt":null,"presentation":{"issuer":{"preferredName":"","did":[],"organizationInfo":{"id":"","legalName":"","currentAddress":"","website":"domain","issuerDomain":[]}},"presentedAt":"2023-02-16T17:34:47.206600"}}],"jwt":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzY1NTAwNTkuMjMzMzY3LCJpYXQiOjE2NzY1NDkwNTkuMjMzMzYsImlzcyI6ImRpZDplYnNpOnpoU3c1clBYa2NIanZxdXduVmNUenpCIiwianRpIjoidXJuOnV1aWQ6NmIxZDg0MTEtOWVkNS00NTY2LTljN2YtNGMyNDE2NWZmMjM2IiwibmJmIjoxNjc2NTQ5MDU5LjIzMzM2NCwibm9uY2UiOiIwYTc4MDg2MC1hZGYyLTExZWQtYmIzZS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6aEZXY3ZyOERGTDNjQVZkaGVDcGpIZzNzUG4xV1VoOUd5bm02aGV2UEZ6cHciLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6aEZXY3ZyOERGTDNjQVZkaGVDcGpIZzNzUG4xV1VoOUd5bm02aGV2UEZ6cHciLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMTZUMTI6MDQ6MTlaIiwiaXNzdWVkIjoiMjAyMy0wMi0xNlQxMjowNDoxOVoiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTE2VDEyOjA0OjE5WiJ9fQ.7Bmdrkp-hpM7BcNASc7ngjia3cjrZr-nqmP0Plsfnefn26G_yR5fBJV8BT_U2zsMlcqWsxuJ9pS2Vyiq_SrooQ"}' - // ]; - - // test('successfully send presentation', () async { - // const response = - // '{"access":"ok","created":1676566727.680238,"credential_status":"signature check bypassed","holder_did_status":"ok","id_token_status":"ok","qrcode_status":"ok","response_format":"ok","status_code":200,"vp_token_status":"ok"}'; // ignore: lines_longer_than_80_chars - // dioAdapter.onGet( - // url, - // (request) => request.reply(200, jsonDecode(response)), - // ); - // final oidc4vc = OIDC4VC(client); - - // expect( - // () async { - // await oidc4vc.sendPresentation( - // uri, - // credentialToBePresented, - // mnemonic, - // null, - // ); - // }, - // returnsNormally, - // ); - // }); - - // test('throw exception', () async { - // expect( - // () async { - // dioAdapter.onPost( - // url, - // (request) => request.throws( - // 401, - // DioException(requestOptions: RequestOptions(path: tokenUrl)), - // ), - // ); - // final oidc4vc = OIDC4VC(client); - - // await oidc4vc.sendPresentation( - // uri, - // credentialToBePresented, - // mnemonic, - // null, - // ); - // }, - // throwsA(isA()), - // ); - // }); - // }); - // }); - - // test('get corresponding did document', () async { - // const url = - // 'https://api-pilot.oidc4vc.eu/did-registry/v3/identifiers/$didKey'; - // const response = { - // '@context': 'https://w3id.org/did/v1', - // 'id': 'did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH', - // 'verificationMethod': [ - // { - // 'id': 'did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH#keys-1', - // 'type': 'Secp256k1VerificationKey2018', - // 'controller': 'did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH', - // 'publicKeyHex': - // '04f9139fbd3b9780863b17e8390934a1017fc8d0f18b84f02acb04f24c9cae261e45745c6507881509b9e6d837329153ea75fafb2c1a5d504702d04f1e22a80ecc' // ignore: lines_longer_than_80_chars - // } - // ], - // 'authentication': ['did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH'], - // 'assertionMethod': ['did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH#keys-1'] - // }; - - // dioAdapter.onGet( - // url, - // (request) => request.reply(200, response), - // ); - // final oidc4vc = OIDC4VC(client); - - // final value = await oidc4vc.getDidDocument(didKey); - - // expect(value.data, response); - // }); - - // test('sandbox', () { - // const cNonce = 'cNonce'; - // const did = 'did'; - // const issuer = 'issuer'; - // final payload = { - // 'iss': did, - // 'nonce': cNonce, - // 'iat': DateTime.now().microsecondsSinceEpoch, - // 'aud': issuer - // }; - - // final jwt = JWT( - // // Payload - // payload, - - // issuer: issuer, - // ); - - // // Sign it (default with HS256 algorithm) - // // ignore: unused_local_variable - // final token = jwt.sign(SecretKey('secret passphrase')); - // }); - - // // test('generateToken', () { - // // final jwk = { - // // 'kty': 'OKP', - // // 'crv': 'Ed25519', - // // 'd': '-_9OD-PMHKE2mFKEVH5R1vDhEMimtXPUX-2w1Xa0hQ0=', - // // 'x': '1tknP1Fx-YIQBzw3xXtCwLGgYoTb-nSsNn-k9uzWpuw=', - // // }; - - // // final vpTokenPayload = { - // // 'iss': 'did:ebsi:zrDzYQPDztwjQ8HdXto1B4FVB14fxoiNawZd8eyEuhR7K', - // // 'nonce': '8dd8d00a-ad17-11ed-9fe9-0a1628958560', - // // 'iat': '1676455223753366', - // // 'aud': 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl', - // // }; - - // // final oidc4vc = OIDC4VC(client); - - // // final tokenParameters = TokenParameters(jwk); - // // final token = oidc4vc.generateToken(vpTokenPayload, tokenParameters); - //}); - //} + + group('OIC4VC request credential', () { + const issuer = 'https://talao.co/issuer/zxhaokccsi'; + + const credential = { + 'format': 'jwt_vc', + 'types': [ + 'VerifiableCredential', + 'VerifiableAttestation', + 'VerifiableDiploma2', + ], + }; + + const did = + 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrbpg5is8LfTLuQ1RsW5r7s7ZjbDDFbDgy1tLrdc7Bj3itBGQkuGUQyfzKhFqbUNW2PqJPMSSzWoF2DGSvDSijCtJtYCSRsjSVLrwu5oHNbnPFvSEC4iRZPpU6B6nExRBTa'; + + const kid = + 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrbpg5is8LfTLuQ1RsW5r7s7ZjbDDFbDgy1tLrdc7Bj3itBGQkuGUQyfzKhFqbUNW2PqJPMSSzWoF2DGSvDSijCtJtYCSRsjSVLrwu5oHNbnPFvSEC4iRZPpU6B6nExRBTa#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrbpg5is8LfTLuQ1RsW5r7s7ZjbDDFbDgy1tLrdc7Bj3itBGQkuGUQyfzKhFqbUNW2PqJPMSSzWoF2DGSvDSijCtJtYCSRsjSVLrwu5oHNbnPFvSEC4iRZPpU6B6nExRBTa'; + + const privateKey = + '{"kty":"EC","crv":"P-256","d":"amrwK13ZiYoJ5g0fc6MvXc86RB9ID8VuK_dMowU68FE","x":"fJQ2c9P_YDep3jzidwykcSlyoC4omqBvd9RHP1nz0cw","y":"K7VxrW-S1ONuX5cxrWIltF36ac1K8kj9as_o5cyc2zk"}'; + + const openIdConfiguration = + '{"authorization_server":"https://talao.co/issuer/zxhaokccsi","credential_endpoint":"https://talao.co/issuer/zxhaokccsi/credential","credential_issuer":"https://talao.co/issuer/zxhaokccsi","subject_syntax_types_supported":null,"token_endpoint":null,"batch_endpoint":null,"authorization_endpoint":null,"subject_trust_frameworks_supported":null,"credentials_supported":[{"display":[{"locale":"en-US","name":"EU Diploma","description":"This the official EBSI VC Diploma","text_color":"#FFFFFF","background_color":"#3B6F6D","background_image":{"url":"https://i.ibb.co/CHqjxrJ/dbc-card-hig-res.png","alt_text":"Connected open cubes in blue with one orange cube as a background of the card"},"logo":{"url":"https://dutchblockchaincoalition.org/assets/images/icons/Logo-DBC.png","alt_text":"An orange block shape, with the text Dutch Blockchain Coalition next to it, portraying the logo of the Dutch Blockchain Coalition."}}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma2"],"id":null,"scope":null,"credentialSubject":{"dateOfBirth":{"display":[{"locale":"en-US","name":"Birth Date"},{"locale":"fr-FR","name":"Date de naissance"}]},"familyName":{"display":[{"locale":"en-US","name":"Family Name"},{"locale":"fr-FR","name":"Nom"}]},"givenNames":{"display":[{"locale":"en-US","name":"First Name"},{"locale":"fr-FR","name":"Prénom"}]}}},{"display":[{"locale":"en-US","name":"Individual attestation","description":"This is the EBSI Individual Verifiable Attestation","text_color":"#FFFFFF","background_color":"#3B6F6D","background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","IndividualVerifiableAttestation"],"id":null,"scope":null,"credentialSubject":{"dateOfBirth":{"display":[{"locale":"en-US","name":"Birth Date"},{"locale":"fr-FR","name":"Date de naissance"}]},"familyName":{"display":[{"locale":"en-US","name":"Family Name"},{"locale":"fr-FR","name":"Nom"}]},"firstName":{"display":[{"locale":"en-US","name":"First Name"},{"locale":"fr-FR","name":"Prénom"}]},"issuing_country":{"display":[{"locale":"en-US","name":"Issued by"},{"locale":"fr-FR","name":"Délivré par"}]},"placeOfBirth":{"display":[{"locale":"en-US","name":"Birth Place"},{"locale":"fr-FR","name":"Lieu de naissance"}]}}},{"display":[{"locale":"en-GB","name":"Email proof","description":"This is a verifiable credential","text_color":null,"background_color":null,"background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","EmailPass"],"id":null,"scope":null,"credentialSubject":null},{"display":[{"locale":"en-GB","name":"Verifiable Id","description":"This is a verifiable credential","text_color":null,"background_color":null,"background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableId"],"id":null,"scope":null,"credentialSubject":null}],"credential_configurations_supported":null,"deferred_credential_endpoint":"https://talao.co/issuer/zxhaokccsi/deferred","service_documentation":null,"credential_manifest":null,"credential_manifests":null,"issuer":null,"jwks_uri":null,"grant_types_supported":null}'; + + const accessToken = '0f0119c2-0867-11ef-8bfa-0a1628958560'; + + const nonce = '0f011beb-0867-11ef-817f-0a1628958560'; + + const credentialRequestUrl = + 'https://talao.co/issuer/zxhaokccsi/credential'; + + const expecedCredentialResponse = + '{"credential":"eyJhbGciOiJFUzI1NiIsImtpZCI6InFsM2g2Z3Jqem5iaGNSRzVPRWk3V1B6dHNkZ1FLaGhiLXBOU1laSWgtdk0iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjE3NDYxODE3NDYsImlhdCI6MTcxNDY0NTc0NiwiaXNzIjoiaHR0cHM6Ly90YWxhby5jby9pc3N1ZXIvenhoYW9rY2NzaSIsImp0aSI6InVybjp1dWlkOmNkZGE4MWYyLTA4NmUtMTFlZi05ODE3LTBhMTYyODk1ODU2MCIsIm5iZiI6MTcxNDY0NTc0Niwibm9uY2UiOiJjOWZkMzJiYS0wODZlLTExZWYtOTQ5Yi0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnJicGc1aXM4TGZUTHVRMVJzVzVyN3M3WmpiRERGYkRneTF0THJkYzdCajNpdEJHUWt1R1VReWZ6S2hGcWJVTlcyUHFKUE1TU3pXb0YyREdTdkRTaWpDdEp0WUNTUnNqU1ZMcnd1NW9ITmJuUEZ2U0VDNGlSWlBwVTZCNm5FeFJCVGEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjpbeyJpZCI6Imh0dHBzOi8vdGFsYW8uY28vc2FuZGJveC9pc3N1ZXIvYml0c3RyaW5nc3RhdHVzbGlzdC8xIzczMDE5Iiwic3RhdHVzTGlzdENyZWRlbnRpYWwiOiJodHRwczovL3RhbGFvLmNvL3NhbmRib3gvaXNzdWVyL2JpdHN0cmluZ3N0YXR1c2xpc3QvMSIsInN0YXR1c0xpc3RJbmRleCI6IjczMDE5Iiwic3RhdHVzUHVycG9zZSI6InJldm9jYXRpb24iLCJ0eXBlIjoiQml0c3RyaW5nU3RhdHVzTGlzdEVudHJ5In1dLCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWRlbnRpZmllciI6IjA5MDQwMDgwODRIIiwibGVhcm5pbmdBY2hpZXZlbWVudCI6eyJhZGRpdGlvbmFsTm90ZSI6WyJESVNUUklCVVRJT04gTUFOQUdFTUVOVCJdLCJkZXNjcmlwdGlvbiI6IlRoZSBNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIChNSUNTKSBhdCB0aGUgVW5pdmVyc2l0eSBvZiBMdXhlbWJvdXJnIGVuYWJsZXMgc3R1ZGVudHMgdG8gYWNxdWlyZSBkZWVwZXIga25vd2xlZGdlIGluIGNvbXB1dGVyIHNjaWVuY2UgYnkgdW5kZXJzdGFuZGluZyBpdHMgYWJzdHJhY3QgYW5kIGludGVyZGlzY2lwbGluYXJ5IGZvdW5kYXRpb25zLCBmb2N1c2luZyBvbiBwcm9ibGVtIHNvbHZpbmcgYW5kIGRldmVsb3BpbmcgbGlmZWxvbmcgbGVhcm5pbmcgc2tpbGxzLiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0xlYXJuaW5nQWNoaWV2bWVudCIsInRpdGxlIjoiTWFzdGVyIGluIEluZm9ybWF0aW9uIGFuZCBDb21wdXRlciBTY2llbmNlcyJ9LCJsZWFybmluZ1NwZWNpZmljYXRpb24iOnsiZWN0c0NyZWRpdFBvaW50cyI6MTIwLCJlcWZMZXZlbCI6NywiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdTcGVjaWZpY2F0aW9uIiwiaXNjZWRmQ29kZSI6WyI3Il0sIm5xZkxldmVsIjpbIjciXX0sInR5cGUiOiJWZXJpZmlhYmxlRGlwbG9tYTIifSwiZXZpZGVuY2UiOnsiZG9jdW1lbnRQcmVzZW5jZSI6WyJQaHlzaWNhbCJdLCJldmlkZW5jZURvY3VtZW50IjpbIlBhc3Nwb3J0Il0sImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvdHNyLXZhL2V2aWRlbmNlL2YyYWVlYzk3LWZjMGQtNDJiZi04Y2E3LTA1NDgxOTJkNTY3OCIsInN1YmplY3RQcmVzZW5jZSI6IlBoeXNpY2FsIiwidHlwZSI6WyJEb2N1bWVudFZlcmlmaWNhdGlvbiJdLCJ2ZXJpZmllciI6ImRpZDplYnNpOjI5NjJmYjc4NGRmNjFiYWEyNjdjODEzMjQ5NzUzOWY4YzY3NGIzN2MxMjQ0YTdhIn0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdfX0.eU1nRdOMklOK6kKvJk-0iCdng5gXZ7quZV1ob_kr2c3_7wSsOEhlgikZzTkkZAOuxmkkdSnWRsGMoA0M4YEi1Q","c_nonce":"cddafe06-086e-11ef-b80b-0a1628958560","c_nonce_expires_in":5000,"format":"jwt_vc"}'; + dioAdapter.onPost( + credentialRequestUrl, + (request) => request.reply( + 200, + expecedCredentialResponse, + ), + ); + + test('When getCredentialType receive url it returns json response', + () async { + final (credentialResponseData, deferredCredentialEndpoint, format) = + await oidc4vc.getCredential( + issuer: issuer, + credential: credential, + did: did, + clientId: did, + kid: kid, + privateKey: privateKey, + cryptoHolderBinding: true, + clientType: ClientType.did, + proofHeaderType: ProofHeaderType.kid, + oidc4vciDraftType: OIDC4VCIDraftType.draft11, + clientAuthentication: ClientAuthentication.clientId, + proofType: ProofType.jwt, + openIdConfiguration: OpenIdConfiguration.fromJson( + jsonDecode(openIdConfiguration) as Map, + ), + accessToken: accessToken, + cnonce: nonce, + dio: client, + ); + + expect(credentialResponseData, [expecedCredentialResponse]); + expect( + deferredCredentialEndpoint, + 'https://talao.co/issuer/zxhaokccsi/deferred', + ); + expect(format, 'jwt_vc'); + }); + + test('throw Exception when token is not verified', () { + expect( + () async { + await oidc4vc.getCredential( + issuer: '', + credential: null, + did: '', + clientId: null, + kid: '', + privateKey: '', + cryptoHolderBinding: true, + clientType: ClientType.did, + proofHeaderType: ProofHeaderType.kid, + oidc4vciDraftType: OIDC4VCIDraftType.draft11, + clientAuthentication: ClientAuthentication.clientId, + proofType: ProofType.jwt, + openIdConfiguration: OpenIdConfiguration.fromJson( + jsonDecode(openIdConfiguration) as Map, + ), + accessToken: '', + cnonce: null, + dio: client, + ); + }, + throwsA(isA()), + ); + }); + }); + + group('build token data', () { + const redirectUri = 'https://app.altme.io/app/download/callback'; + const preAuthorizedCode = + 'eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDplYnNpOjEyMzQja2V5LTEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3RhbGFvLmNvL3NhbmRib3gvZWJzaS9pc3N1ZXIvenhoYW9rY2NzaSIsImNsaWVudF9pZCI6Imh0dHBzOi8vc2VsZi1pc3N1ZWQubWUvdjIiLCJleHAiOjE3MTQ2MzU4NDIsImlhdCI6MTcxNDYzNDg0MiwiaXNzIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3p4aGFva2Njc2kiLCJub25jZSI6IjZhMGJkZWUxLTA4NTUtMTFlZi04MzJlLTBhMTYyODk1ODU2MCIsInN1YiI6Imh0dHBzOi8vc2VsZi1pc3N1ZWQubWUvdjIifQ.ViX87lulUM6WZ0lNj5XMEz-Ty5q8nIcI7b-bIYa7VRsqo1wcR_en-8hzN_Q_sp8hqi8lKX80n4jM-DqXqvJk5g'; + + test('get token data with credentialRequestUri for preAuthorizedCode', + () async { + const clientId = + 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbpog7BZb9wdCJCjHfWMTpjcviuoFJ2fd9AiwsWGMFvhNJ5gVMA2mzHSFqkrLMXdHNeePjiaTP15sw8uaWDfyAxehGHKj7YsxymgVnEhcEJgKsLRJHgJZXAiXJGyRxWPGEYC'; + + const expectedTokenData = + '{"pre-authorized_code":"eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDplYnNpOjEyMzQja2V5LTEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3RhbGFvLmNvL3NhbmRib3gvZWJzaS9pc3N1ZXIvenhoYW9rY2NzaSIsImNsaWVudF9pZCI6Imh0dHBzOi8vc2VsZi1pc3N1ZWQubWUvdjIiLCJleHAiOjE3MTQ2MzU4NDIsImlhdCI6MTcxNDYzNDg0MiwiaXNzIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3p4aGFva2Njc2kiLCJub25jZSI6IjZhMGJkZWUxLTA4NTUtMTFlZi04MzJlLTBhMTYyODk1ODU2MCIsInN1YiI6Imh0dHBzOi8vc2VsZi1pc3N1ZWQubWUvdjIifQ.ViX87lulUM6WZ0lNj5XMEz-Ty5q8nIcI7b-bIYa7VRsqo1wcR_en-8hzN_Q_sp8hqi8lKX80n4jM-DqXqvJk5g","grant_type":"urn:ietf:params:oauth:grant-type:pre-authorized_code","client_id":"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbpog7BZb9wdCJCjHfWMTpjcviuoFJ2fd9AiwsWGMFvhNJ5gVMA2mzHSFqkrLMXdHNeePjiaTP15sw8uaWDfyAxehGHKj7YsxymgVnEhcEJgKsLRJHgJZXAiXJGyRxWPGEYC"}'; // ignore: lines_longer_than_80_chars + final tokenData = oidc4vc.buildTokenData( + redirectUri: redirectUri, + preAuthorizedCode: preAuthorizedCode, + clientId: clientId, + ); + + expect(jsonEncode(tokenData), expectedTokenData); + }); + + test('get token data with credentialRequestUri - authorization flow', () { + const clientId = + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjZka1U2Wk1GSzc5V3dpY3dKNXJieEUxM3pTdWtCWTJPb0VpVlVFanFNRWMiLCJ5IjoiUm5Iem55VmxyUFNNVDdpckRzMTVEOXd4Z01vamlTREFRcGZGaHFUa0xSWSJ9'; + const expectedTokenData = + '{"code":"6486b7c9-0858-11ef-a82c-0a1628958560","grant_type":"authorization_code","code_verifier":"qZNF2gMjTQf7pJN2NMai1TS9Y81z8xzfPQtbmyVG-Gk","redirect_uri":"https://app.altme.io/app/download/callback","client_id":"did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjZka1U2Wk1GSzc5V3dpY3dKNXJieEUxM3pTdWtCWTJPb0VpVlVFanFNRWMiLCJ5IjoiUm5Iem55VmxyUFNNVDdpckRzMTVEOXd4Z01vamlTREFRcGZGaHFUa0xSWSJ9"}'; + final tokenData = oidc4vc.buildTokenData( + redirectUri: redirectUri, + clientId: clientId, + code: '6486b7c9-0858-11ef-a82c-0a1628958560', + codeVerifier: 'qZNF2gMjTQf7pJN2NMai1TS9Y81z8xzfPQtbmyVG-Gk', + ); + expect(jsonEncode(tokenData), expectedTokenData); + }); + }); + + group('getIssuer', () { + test('get issuer with credentialRequestUri', () async { + const clientId = + 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrbpg5is8LfTLuQ1RsW5r7s7ZjbDDFbDgy1tLrdc7Bj3itBGQkuGUQyfzKhFqbUNW2PqJPMSSzWoF2DGSvDSijCtJtYCSRsjSVLrwu5oHNbnPFvSEC4iRZPpU6B6nExRBTa'; + + const expectedIssuerJwt = + 'eyJhbGciOiJFUzI1NiIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0Iiwia2lkIjoidmNEV1FrRzBmbXhmRm90clhKZFRZMEhzQklobTQ2UDZYYzNqcXdVbjRVWSJ9.eyJpc3MiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnJicGc1aXM4TGZUTHVRMVJzVzVyN3M3WmpiRERGYkRneTF0THJkYzdCajNpdEJHUWt1R1VReWZ6S2hGcWJVTlcyUHFKUE1TU3pXb0YyREdTdkRTaWpDdEp0WUNTUnNqU1ZMcnd1NW9ITmJuUEZ2U0VDNGlSWlBwVTZCNm5FeFJCVGEiLCJpYXQiOjE3MTQ3MTU1NDQsImF1ZCI6Imh0dHBzOi8vdGFsYW8uY28vaXNzdWVyL3p4aGFva2Njc2kiLCJub25jZSI6IjJkYTJkNTA2LTA5MTAtMTFlZi05ZTQ5LTBhMTYyODk1ODU2MCJ9.kmRv9RfhwGmAArHiqsZl7HkxE32vO9hyiVuI-lcMmpBPsgJ_eqPSXvkhSrIoUoKCtWAS3gaTT1hRZ5O0_fk9fA'; + + final tokenParameters = IssuerTokenParameters( + privateKey: { + 'kty': 'EC', + 'crv': 'P-256', + 'd': 'amrwK13ZiYoJ5g0fc6MvXc86RB9ID8VuK_dMowU68FE', + 'x': 'fJQ2c9P_YDep3jzidwykcSlyoC4omqBvd9RHP1nz0cw', + 'y': 'K7VxrW-S1ONuX5cxrWIltF36ac1K8kj9as_o5cyc2zk', + }, + did: clientId, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + clientType: ClientType.did, + clientId: clientId, + issuer: 'https://talao.co/issuer/zxhaokccsi', + ); + final issuerJwt = await oidc4vc.getIssuerJwt( + tokenParameters: tokenParameters, + clientAuthentication: ClientAuthentication.clientId, + iss: clientId, + cnonce: '2da2d506-0910-11ef-9e49-0a1628958560', + ); + + expect(issuerJwt.startsWith('ey'), expectedIssuerJwt.startsWith('ey')); + }); + }); + + test('get readTokenEndPoint with openidConfigurationResponse', () async { + const issuer = 'https://talao.co/issuer/zxhaokccsi'; + const openidConfigurationResponse = + '{"authorization_server":"https://talao.co/issuer/zxhaokccsi","credential_endpoint":"https://talao.co/issuer/zxhaokccsi/credential","credential_issuer":"https://talao.co/issuer/zxhaokccsi","subject_syntax_types_supported":null,"token_endpoint":null,"batch_endpoint":null,"authorization_endpoint":null,"subject_trust_frameworks_supported":null,"credentials_supported":[{"display":[{"locale":"en-US","name":"EU Diploma","description":"This the official EBSI VC Diploma","text_color":"#FFFFFF","background_color":"#3B6F6D","background_image":{"url":"https://i.ibb.co/CHqjxrJ/dbc-card-hig-res.png","alt_text":"Connected open cubes in blue with one orange cube as a background of the card"},"logo":{"url":"https://dutchblockchaincoalition.org/assets/images/icons/Logo-DBC.png","alt_text":"An orange block shape, with the text Dutch Blockchain Coalition next to it, portraying the logo of the Dutch Blockchain Coalition."}}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma2"],"id":null,"scope":null,"credentialSubject":{"dateOfBirth":{"display":[{"locale":"en-US","name":"Birth Date"},{"locale":"fr-FR","name":"Date de naissance"}]},"familyName":{"display":[{"locale":"en-US","name":"Family Name"},{"locale":"fr-FR","name":"Nom"}]},"givenNames":{"display":[{"locale":"en-US","name":"First Name"},{"locale":"fr-FR","name":"Prénom"}]}}},{"display":[{"locale":"en-US","name":"Individual attestation","description":"This is the EBSI Individual Verifiable Attestation","text_color":"#FFFFFF","background_color":"#3B6F6D","background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","IndividualVerifiableAttestation"],"id":null,"scope":null,"credentialSubject":{"dateOfBirth":{"display":[{"locale":"en-US","name":"Birth Date"},{"locale":"fr-FR","name":"Date de naissance"}]},"familyName":{"display":[{"locale":"en-US","name":"Family Name"},{"locale":"fr-FR","name":"Nom"}]},"firstName":{"display":[{"locale":"en-US","name":"First Name"},{"locale":"fr-FR","name":"Prénom"}]},"issuing_country":{"display":[{"locale":"en-US","name":"Issued by"},{"locale":"fr-FR","name":"Délivré par"}]},"placeOfBirth":{"display":[{"locale":"en-US","name":"Birth Place"},{"locale":"fr-FR","name":"Lieu de naissance"}]}}},{"display":[{"locale":"en-GB","name":"Email proof","description":"This is a verifiable credential","text_color":null,"background_color":null,"background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","EmailPass"],"id":null,"scope":null,"credentialSubject":null},{"display":[{"locale":"en-GB","name":"Verifiable Id","description":"This is a verifiable credential","text_color":null,"background_color":null,"background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableId"],"id":null,"scope":null,"credentialSubject":null}],"credential_configurations_supported":null,"deferred_credential_endpoint":"https://talao.co/issuer/zxhaokccsi/deferred","service_documentation":null,"credential_manifest":null,"credential_manifests":null,"issuer":null,"jwks_uri":null,"grant_types_supported":null}'; + const openidConfigurationResponse2 = + '{"authorization_endpoint":"https://talao.co/issuer/zxhaokccsi/authorize","grant_types_supported":["authorization_code","urn:ietf:params:oauth:grant-type:pre-authorized_code"],"id_token_signing_alg_values_supported":["ES256","ES256K","EdDSA","RS256"],"id_token_types_supported":["subject_signed_id_token"],"jwks_uri":"https://talao.co/issuer/zxhaokccsi/jwks","pushed_authorization_request_endpoint":"https://talao.co/issuer/zxhaokccsi/authorize/par","request_authentication_methods_supported":{"authorization_endpoint":["request_object"]},"request_object_signing_alg_values_supported":["ES256","ES256K","EdDSA","RS256"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"response_modes_supported":["query"],"response_types_supported":["vp_token","id_token"],"scopes_supported":["openid"],"subject_syntax_types_discriminations":["did:key:jwk_jcs-pub","did:ebsi:v1"],"subject_syntax_types_supported":["urn:ietf:params:oauth:jwk-thumbprint","did:key","did:ebsi","did:tz","did:pkh","did:hedera","did:key","did:ethr","did:web","did:jwk"],"subject_trust_frameworks_supported":["ebsi"],"subject_types_supported":["public","pairwise"],"token_endpoint":"https://talao.co/issuer/zxhaokccsi/token","token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","none"]}'; + + const expectedTokenEndpoint = 'https://talao.co/issuer/zxhaokccsi/token'; + + dioAdapter.onPost( + '$issuer/.well-known/openid-configuration', + (request) => request.reply( + 200, + jsonDecode(openidConfigurationResponse2), + ), + ); + + final tokenEndpoint = await oidc4vc.readTokenEndPoint( + dio: client, + issuer: issuer, + oidc4vciDraftType: OIDC4VCIDraftType.draft11, + openIdConfiguration: OpenIdConfiguration.fromJson( + jsonDecode(openidConfigurationResponse) as Map, + ), + ); + + expect(tokenEndpoint, expectedTokenEndpoint); + }); + + test('get issuer did with openidConfigurationResponse', () { + const openidConfigurationResponse = + r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"},{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.oidc4vc.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; + + final issuer = oidc4vc.readIssuerDid( + Response( + requestOptions: RequestOptions(), + data: jsonDecode(openidConfigurationResponse) as Map, + ), + ); + expect(issuer, 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB'); + }); + + test('get publicKey did with didDocumentResponse', () { + const didDocument = + '{"keys":[{"crv":"Ed25519","kid":"AegXN9J71CIQWw7TjhM-eHYZW45TfP0uC5xJduiH_w0","kty":"OKP","x":"FUoLewH4w4-KdaPH2cjZbL--CKYxQRWR05Yd_bIbhQo"}]}'; + const issuerDid = 'https://talao.co/sandbox/issuer/statuslist'; + const holderKid = 'AegXN9J71CIQWw7TjhM-eHYZW45TfP0uC5xJduiH_w0'; + + const expectedPublicKey = + '{"crv":"Ed25519","kid":"AegXN9J71CIQWw7TjhM-eHYZW45TfP0uC5xJduiH_w0","kty":"OKP","x":"FUoLewH4w4-KdaPH2cjZbL--CKYxQRWR05Yd_bIbhQo"}'; + + final publicKey = oidc4vc.readPublicKeyJwk( + issuer: issuerDid, + holderKid: holderKid, + didDocument: jsonDecode(didDocument) as Map, + ); + expect(jsonEncode(publicKey), expectedPublicKey); + }); + + group('verify encoded data', () { + const issuer = 'did:web:talao.co'; + const issuerKid1 = 'did:web:talao.co#key-2'; + const issuerKid2 = 'did:web:talao.co#key-222'; + const issuerKid3 = 'did:web:talao.co#key-21'; + const issuerKid4 = 'did:web:talao.co#key-3'; + + const universal = 'https://unires:test@unires.talao.co/1.0/identifiers'; + + const jwt = + 'eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6dGFsYW8uY28ja2V5LTIiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjE3NDYyNTUzMDYsImlhdCI6MTcxNDcxOTMwNiwiaXNzIjoiZGlkOndlYjp0YWxhby5jbyIsImp0aSI6InVybjp1dWlkOjEyZjdmNmI1LTA5MWEtMTFlZi04MWU1LTBhMTYyODk1ODU2MCIsIm5iZiI6MTcxNDcxOTMwNiwibm9uY2UiOiIxMWVhZTg1YS0wOTFhLTExZWYtODJkNC0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6andrOmV5SmpjbllpT2lKUUxUSTFOaUlzSW10MGVTSTZJa1ZESWl3aWVDSTZJa1pIYTFScWFrUXhPR1l6VVRsc2FteG9WblEyVnpaMWJqSmtjbFJ1WjJoSU1XOVhSM1JRUmpOZk16Z2lMQ0o1SWpvaVkwWkNhWHBYZURoNE5rbE1XbWt5T1ZkRVFtNHpialU0WldWbGNra3ROVmMxWWxOeGVHaFFVR05RWXlKOSIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS1jb25mb3JtYW5jZS5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92Mi9zY2hlbWFzL3oyMlpBTWRRdE5Md2k1MVQydmRaWEdHWmFZeWpyc3VQMXl6V3lYWmlyQ0FIdiIsInR5cGUiOiJGdWxsSnNvblNjaGVtYVZhbGlkYXRvcjIwMjEifSwiY3JlZGVudGlhbFN0YXR1cyI6W3siaWQiOiJodHRwczovL3RhbGFvLmNvL3NhbmRib3gvaXNzdWVyL2JpdHN0cmluZ3N0YXR1c2xpc3QvMSM1ODg5NyIsInN0YXR1c0xpc3RDcmVkZW50aWFsIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2lzc3Vlci9iaXRzdHJpbmdzdGF0dXNsaXN0LzEiLCJzdGF0dXNMaXN0SW5kZXgiOiI1ODg5NyIsInN0YXR1c1B1cnBvc2UiOiJyZXZvY2F0aW9uIiwidHlwZSI6IkJpdHN0cmluZ1N0YXR1c0xpc3RFbnRyeSJ9XSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGF0ZUlzc3VlZCI6IjIwMjItMTItMjAiLCJkYXRlT2ZCaXJ0aCI6IjE5MzAtMTAtMDEiLCJmYW1pbHlOYW1lIjoiQ2FzdGFmaW9yaSIsImZpcnN0TmFtZSI6IkJpYW5jYSIsImdlbmRlciI6IkYiLCJ0eXBlIjoiVmVyaWZpYWJsZUlkIn0sImV4cGlyYXRpb25EYXRlIjoiMjAyNS0wNS0wM1QwNjo1NDo1M1oiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUlkIl0sInZhbGlkRnJvbSI6IjIwMjUtMDUtMDNUMDY6NTQ6NTNaIn19.5DJwR_gUbu-GDpSF7hwXcpmHg-wYmKU_AxOvR4Psimefk0H4JUbX803svm3QhxIK2i4GgMhRWmgqhvML_x7nTw'; + + const didDocument = + r'{"@context":"https://w3id.org/did-resolution/v1","didDocument":{"@context":["https://www.w3.org/ns/did/v1",{"@id":"https://w3id.org/security#publicKeyJwk","@type":"@json"}],"id":"did:web:talao.co","verificationMethod":[{"id":"did:web:talao.co#key-1","type":"JwsVerificationKey2020","controller":"did:web:talao.co","publicKeyJwk":{"e":"AQAB","kid":"did:web:talao.co#key-1","kty":"RSA","n":"pPocyKreTAn3YrmGyPYXHklYqUiSSQirGACwJSYYs-ksfw4brtA3SZCmA2sdAO8a2DXfqADwFgVSxJFtJ3GkHLV2ZvOIOnZCX6MF6NIWHB9c64ydrYNJbEy72oyG_-v-sE6rb0x-D-uJe9DFYIURzisyBlNA7imsiZPQniOjPLv0BUgED0vdO5HijFe7XbpVhoU-2oTkHHQ4CadmBZhelCczACkXpOU7mwcImGj9h1__PsyT5VBLi_92-93NimZjechPaaTYEU2u0rfnfVW5eGDYNAynO4Q2bhpFPRTXWZ5Lhnhnq7M76T6DGA3GeAu_MOzB0l4dxpFMJ6wHnekdkQ"}},{"id":"did:web:talao.co#key-2","type":"JwsVerificationKey2020","controller":"did:web:talao.co","publicKeyJwk":{"crv":"P-256","kty":"EC","x":"Bls7WaGu_jsharYBAzakvuSERIV_IFR2tS64e5p_Y_Q","y":"haeKjXQ9uzyK4Ind1W4SBUkR_9udjjx1OmKK4vl1jko"}},{"id":"did:web:talao.co#key-21","type":"JwsVerificationKey2020","controller":"did:web:talao.co","publicKeyJwk":{"crv":"P-256","kty":"EC","x":"J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI","y":"HNh3aF4zQsnf_sFXUVaSzrQF85veDoVxhPQ-163wUYM"}},{"id":"did:web:talao.co#key-3","type":"JwsVerificationKey2020","controller":"did:web:talao.co","publicKeyJwk":{"crv":"Ed25519","kty":"OKP","x":"FUoLewH4w4-KdaPH2cjZbL--CKYxQRWR05Yd_bIbhQo"}},{"id":"did:web:talao.co#key-4","type":"Ed25519VerificationKey2018","controller":"did:web:talao.co","publicKeyBase58":"2S73k5pn5umfnaW31qx6dXFndEn6SmWw7LpgSjNNC5BF"}],"authentication":["did:web:talao.co#key-1","did:web:talao.co#key-2","did:web:talao.co#key-3","did:web:talao.co#key-4"],"assertionMethod":["did:web:talao.co#key-1","did:web:talao.co#key-2","did:web:talao.co#key-3","did:web:talao.co#key-4"],"keyAgreement":["did:web:talao.co#key-3","did:web:talao.co#key-4"],"capabilityInvocation":["did:web:talao.co#key-1","did:web:talao.co#key-4"],"service":[{"id":"did:web:talao.co#domain-1","type":"LinkedDomains","serviceEndpoint":"https://talao.co"}]},"didResolutionMetadata":{"contentType":"application/did+ld+json","pattern":"^(did:web:.+)$","driverUrl":"http://uni-resolver-driver-did-uport:8081/1.0/identifiers/","duration":42,"did":{"didString":"did:web:talao.co","methodSpecificId":"talao.co","method":"web"}},"didDocumentMetadata":{}}'; + + dioAdapter.onGet( + '$universal/$issuer', + (request) => request.reply(200, jsonDecode(didDocument)), + ); + + test('returns VerificationType.verified', () async { + final isVerified = await oidc4vc.verifyEncodedData( + issuer: issuer, + jwt: jwt, + issuerKid: issuerKid1, + dio: client, + fromStatusList: false, + isCachingEnabled: false, + publicJwk: null, + ); + expect(isVerified, VerificationType.verified); + }); + + test('returns VerificationType.unKnown', () async { + final isVerified = await oidc4vc.verifyEncodedData( + issuer: issuer, + jwt: jwt, + issuerKid: issuerKid2, + dio: client, + fromStatusList: false, + isCachingEnabled: false, + publicJwk: null, + ); + expect(isVerified, VerificationType.unKnown); + }); + + test('returns VerificationType.notVerified', () async { + const vcJwt = + 'eyJhbGciOiJIUzI1NiJ9.eyJSb2xlIjoiQWRtaW4iLCJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkphdmFJblVzZSIsImV4cCI6MTcxNDcyMjY3NiwiaWF0IjoxNzE0NzIyNjc2fQ.mVQhu1VmyA6LlcA77NmdhUvTrOoawL_VxhrMzkkh7BE'; + final isVerified = await oidc4vc.verifyEncodedData( + issuer: issuer, + jwt: vcJwt, + issuerKid: issuerKid3, + dio: client, + fromStatusList: false, + isCachingEnabled: false, + publicJwk: null, + ); + expect(isVerified, VerificationType.notVerified); + }); + + test('returns VerificationType.notVerified for OKP', () async { + const vcJwt = 'eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0.' + 'eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9.' // ignore: lines_longer_than_80_chars + 'rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg'; // ignore: lines_longer_than_80_chars + + final isVerified = await oidc4vc.verifyEncodedData( + issuer: issuer, + jwt: vcJwt, + issuerKid: issuerKid4, + dio: client, + fromStatusList: false, + isCachingEnabled: false, + publicJwk: null, + ); + expect(isVerified, VerificationType.notVerified); + }); + }); + + group('get token', () { + const tokenEndPoint = 'https://talao.co/issuer/zarbjrqrzj/token'; + const tokenData = { + 'pre-authorized_code': '5cdf5508-0923-11ef-90b1-0a1628958560', + 'grant_type': 'urn:ietf:params:oauth:grant-type:pre-authorized_code', + 'client_id': 'did:key:zDnaeoAcB8wFcSWqLeiJbCg663C3qAKkEfuuTM9rGWx2NFWCt', + 'user_pin': '4444', + 'tx_code': '4444', + }; + + test('get correct token ', () async { + const response = + '{"access_token":"ac9e48a7-0923-11ef-95a3-0a1628958560","c_nonce":"ac9e4a9c-0923-11ef-a47c-0a1628958560","token_type":"Bearer","expires_in":10000,"c_nonce_expires_in":1704466725,"refresh_token":"ac9e49f2-0923-11ef-91ef-0a1628958560"}'; + + dioAdapter.onPost( + tokenEndPoint, + (request) => request.reply(200, jsonDecode(response)), + ); + + const expectedValue = + '{"access_token":"ac9e48a7-0923-11ef-95a3-0a1628958560","c_nonce":"ac9e4a9c-0923-11ef-a47c-0a1628958560","token_type":"Bearer","expires_in":10000,"c_nonce_expires_in":1704466725,"refresh_token":"ac9e49f2-0923-11ef-91ef-0a1628958560"}'; + final token = await oidc4vc.getToken( + dio: client, + tokenData: tokenData, + tokenEndPoint: tokenEndPoint, + authorization: null, + ); + + expect(jsonEncode(token), expectedValue); + }); + + test('throw expection when invalid value is sent', () async { + expect( + () async { + dioAdapter.onPost( + tokenEndPoint, + (request) => request.throws( + 401, + DioException(requestOptions: RequestOptions(path: tokenEndPoint)), + ), + ); + + await oidc4vc.getToken( + dio: client, + tokenData: tokenData, + tokenEndPoint: tokenEndPoint, + authorization: null, + ); + }, + throwsA(isA()), + ); + }); + }); + + group( + 'get did document', + () { + test('get corresponding did document', () async { + const issuer = 'did:web:talao.co'; + const universal = 'https://unires:test@unires.talao.co/1.0/identifiers'; + + const didDocument = + r'{"@context":"https://w3id.org/did-resolution/v1","didDocument":{"@context":["https://www.w3.org/ns/did/v1",{"@id":"https://w3id.org/security#publicKeyJwk","@type":"@json"}],"id":"did:web:talao.co","verificationMethod":[{"id":"did:web:talao.co#key-1","type":"JwsVerificationKey2020","controller":"did:web:talao.co","publicKeyJwk":{"e":"AQAB","kid":"did:web:talao.co#key-1","kty":"RSA","n":"pPocyKreTAn3YrmGyPYXHklYqUiSSQirGACwJSYYs-ksfw4brtA3SZCmA2sdAO8a2DXfqADwFgVSxJFtJ3GkHLV2ZvOIOnZCX6MF6NIWHB9c64ydrYNJbEy72oyG_-v-sE6rb0x-D-uJe9DFYIURzisyBlNA7imsiZPQniOjPLv0BUgED0vdO5HijFe7XbpVhoU-2oTkHHQ4CadmBZhelCczACkXpOU7mwcImGj9h1__PsyT5VBLi_92-93NimZjechPaaTYEU2u0rfnfVW5eGDYNAynO4Q2bhpFPRTXWZ5Lhnhnq7M76T6DGA3GeAu_MOzB0l4dxpFMJ6wHnekdkQ"}},{"id":"did:web:talao.co#key-2","type":"JwsVerificationKey2020","controller":"did:web:talao.co","publicKeyJwk":{"crv":"P-256","kty":"EC","x":"Bls7WaGu_jsharYBAzakvuSERIV_IFR2tS64e5p_Y_Q","y":"haeKjXQ9uzyK4Ind1W4SBUkR_9udjjx1OmKK4vl1jko"}},{"id":"did:web:talao.co#key-21","type":"JwsVerificationKey2020","controller":"did:web:talao.co","publicKeyJwk":{"crv":"P-256","kty":"EC","x":"J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI","y":"HNh3aF4zQsnf_sFXUVaSzrQF85veDoVxhPQ-163wUYM"}},{"id":"did:web:talao.co#key-3","type":"JwsVerificationKey2020","controller":"did:web:talao.co","publicKeyJwk":{"crv":"Ed25519","kty":"OKP","x":"FUoLewH4w4-KdaPH2cjZbL--CKYxQRWR05Yd_bIbhQo"}},{"id":"did:web:talao.co#key-4","type":"Ed25519VerificationKey2018","controller":"did:web:talao.co","publicKeyBase58":"2S73k5pn5umfnaW31qx6dXFndEn6SmWw7LpgSjNNC5BF"}],"authentication":["did:web:talao.co#key-1","did:web:talao.co#key-2","did:web:talao.co#key-3","did:web:talao.co#key-4"],"assertionMethod":["did:web:talao.co#key-1","did:web:talao.co#key-2","did:web:talao.co#key-3","did:web:talao.co#key-4"],"keyAgreement":["did:web:talao.co#key-3","did:web:talao.co#key-4"],"capabilityInvocation":["did:web:talao.co#key-1","did:web:talao.co#key-4"],"service":[{"id":"did:web:talao.co#domain-1","type":"LinkedDomains","serviceEndpoint":"https://talao.co"}]},"didResolutionMetadata":{"contentType":"application/did+ld+json","pattern":"^(did:web:.+)$","driverUrl":"http://uni-resolver-driver-did-uport:8081/1.0/identifiers/","duration":42,"did":{"didString":"did:web:talao.co","methodSpecificId":"talao.co","method":"web"}},"didDocumentMetadata":{}}'; + + dioAdapter.onGet( + '$universal/$issuer', + (request) => request.reply(200, jsonDecode(didDocument)), + ); + + final value = await oidc4vc.getDidDocument( + didKey: issuer, + fromStatusList: false, + isCachingEnabled: false, + dio: client, + ); + + expect(value, jsonDecode(didDocument)); + }); + + test('get corresponding did document', () async { + const issuer = 'https://talao.co/issuer/zxhaokccsi'; + const openidConfigurationResponse1 = + '{"authorization_server":"https://talao.co/issuer/zxhaokccsi","credential_endpoint":"https://talao.co/issuer/zxhaokccsi/credential","credential_issuer":"https://talao.co/issuer/zxhaokccsi","subject_syntax_types_supported":null,"token_endpoint":null,"batch_endpoint":null,"authorization_endpoint":null,"subject_trust_frameworks_supported":null,"credentials_supported":[{"display":[{"locale":"en-US","name":"EU Diploma","description":"This the official EBSI VC Diploma","text_color":"#FFFFFF","background_color":"#3B6F6D","background_image":{"url":"https://i.ibb.co/CHqjxrJ/dbc-card-hig-res.png","alt_text":"Connected open cubes in blue with one orange cube as a background of the card"},"logo":{"url":"https://dutchblockchaincoalition.org/assets/images/icons/Logo-DBC.png","alt_text":"An orange block shape, with the text Dutch Blockchain Coalition next to it, portraying the logo of the Dutch Blockchain Coalition."}}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma2"],"id":null,"scope":null,"credentialSubject":{"dateOfBirth":{"display":[{"locale":"en-US","name":"Birth Date"},{"locale":"fr-FR","name":"Date de naissance"}]},"familyName":{"display":[{"locale":"en-US","name":"Family Name"},{"locale":"fr-FR","name":"Nom"}]},"givenNames":{"display":[{"locale":"en-US","name":"First Name"},{"locale":"fr-FR","name":"Prénom"}]}}},{"display":[{"locale":"en-US","name":"Individual attestation","description":"This is the EBSI Individual Verifiable Attestation","text_color":"#FFFFFF","background_color":"#3B6F6D","background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","IndividualVerifiableAttestation"],"id":null,"scope":null,"credentialSubject":{"dateOfBirth":{"display":[{"locale":"en-US","name":"Birth Date"},{"locale":"fr-FR","name":"Date de naissance"}]},"familyName":{"display":[{"locale":"en-US","name":"Family Name"},{"locale":"fr-FR","name":"Nom"}]},"firstName":{"display":[{"locale":"en-US","name":"First Name"},{"locale":"fr-FR","name":"Prénom"}]},"issuing_country":{"display":[{"locale":"en-US","name":"Issued by"},{"locale":"fr-FR","name":"Délivré par"}]},"placeOfBirth":{"display":[{"locale":"en-US","name":"Birth Place"},{"locale":"fr-FR","name":"Lieu de naissance"}]}}},{"display":[{"locale":"en-GB","name":"Email proof","description":"This is a verifiable credential","text_color":null,"background_color":null,"background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","EmailPass"],"id":null,"scope":null,"credentialSubject":null},{"display":[{"locale":"en-GB","name":"Verifiable Id","description":"This is a verifiable credential","text_color":null,"background_color":null,"background_image":null,"logo":null}],"format":"jwt_vc","trust_framework":{"name":"ebsi","type":"Accreditation","uri":"TIR link towards accreditation"},"types":["VerifiableCredential","VerifiableAttestation","VerifiableId"],"id":null,"scope":null,"credentialSubject":null}],"credential_configurations_supported":null,"deferred_credential_endpoint":"https://talao.co/issuer/zxhaokccsi/deferred","service_documentation":null,"credential_manifest":null,"credential_manifests":null,"issuer":null,"jwks_uri":null,"grant_types_supported":null}'; + const openidConfigurationResponse2 = + '{"authorization_server":null,"credential_endpoint":null,"credential_issuer":null,"subject_syntax_types_supported":null,"token_endpoint":null,"batch_endpoint":null,"authorization_endpoint":null,"subject_trust_frameworks_supported":null,"credentials_supported":null,"credential_configurations_supported":null,"deferred_credential_endpoint":null,"service_documentation":null,"credential_manifest":null,"credential_manifests":null,"issuer":"https://talao.co/sandbox/issuer/statuslist","jwks_uri":"https://talao.co/sandbox/issuer/statuslist/jwks","grant_types_supported":null}'; + const jwkUriResponse = + '{"keys":[{"crv":"Ed25519","kid":"AegXN9J71CIQWw7TjhM-eHYZW45TfP0uC5xJduiH_w0","kty":"OKP","x":"FUoLewH4w4-KdaPH2cjZbL--CKYxQRWR05Yd_bIbhQo"}]}'; + + const expectedDidDocument = + '{"keys":[{"crv":"Ed25519","kid":"AegXN9J71CIQWw7TjhM-eHYZW45TfP0uC5xJduiH_w0","kty":"OKP","x":"FUoLewH4w4-KdaPH2cjZbL--CKYxQRWR05Yd_bIbhQo"}]}'; + + dioAdapter + ..onGet( + '$issuer/.well-known/openid-credential-issuer', + (request) => + request.reply(200, jsonDecode(openidConfigurationResponse1)), + ) + ..onGet( + '$issuer/.well-known/openid-configuration', + (request) => + request.reply(200, jsonDecode(openidConfigurationResponse2)), + ) + ..onGet( + 'https://talao.co/sandbox/issuer/statuslist/jwks', + (request) => request.reply(200, jsonDecode(jwkUriResponse)), + ); + + final value = await oidc4vc.getDidDocument( + didKey: issuer, + fromStatusList: false, + isCachingEnabled: false, + dio: client, + ); + expect(value, jsonDecode(expectedDidDocument)); + }); + }, + ); } diff --git a/packages/oidc4vc/test/src/oidc4vci_draft_type_test.dart b/packages/oidc4vc/test/src/oidc4vci_draft_type_test.dart new file mode 100644 index 000000000..1e5771ecf --- /dev/null +++ b/packages/oidc4vc/test/src/oidc4vci_draft_type_test.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('OIDC4VCIDraftTypeX', () { + test('formattedString', () { + expect(OIDC4VCIDraftType.draft11.formattedString, 'Draft 11'); + expect(OIDC4VCIDraftType.draft13.formattedString, 'Draft 13'); + }); + + test('numbering', () { + expect(OIDC4VCIDraftType.draft11.numbering, '11'); + expect(OIDC4VCIDraftType.draft13.numbering, '13'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/oidc4vp_draft_type_test.dart b/packages/oidc4vc/test/src/oidc4vp_draft_type_test.dart new file mode 100644 index 000000000..7b088cb12 --- /dev/null +++ b/packages/oidc4vc/test/src/oidc4vp_draft_type_test.dart @@ -0,0 +1,13 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('OIDC4VPDraftTypeX', () { + test('formattedString', () { + expect(OIDC4VPDraftType.draft10.formattedString, 'Draft 10'); + expect(OIDC4VPDraftType.draft13.formattedString, 'Draft 13'); + expect(OIDC4VPDraftType.draft18.formattedString, 'Draft 18'); + expect(OIDC4VPDraftType.draft20.formattedString, 'Draft 20'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/pkce_dart_test.dart b/packages/oidc4vc/test/src/pkce_dart_test.dart new file mode 100644 index 000000000..02574173a --- /dev/null +++ b/packages/oidc4vc/test/src/pkce_dart_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('PkcePair', () { + test('generate', () { + final pkcePair = PkcePair.generate(); + expect(pkcePair.codeVerifier.length, 43); + expect(pkcePair.codeChallenge.length, 43); + }); + + test('generate with invalid length', () { + expect( + () => PkcePair.generate(length: 31), // Less than 32 + throwsA(isA()), + ); + + expect( + () => PkcePair.generate(length: 97), // Greater than 96 + throwsA(isA()), + ); + }); + + test('generate produces unique values', () { + final pkcePair1 = PkcePair.generate(); + final pkcePair2 = PkcePair.generate(); + + expect(pkcePair1.codeVerifier, isNot(pkcePair2.codeVerifier)); + expect(pkcePair1.codeChallenge, isNot(pkcePair2.codeChallenge)); + }); + }); +} diff --git a/packages/oidc4vc/test/src/proof_header_type_test.dart b/packages/oidc4vc/test/src/proof_header_type_test.dart new file mode 100644 index 000000000..d9a73f101 --- /dev/null +++ b/packages/oidc4vc/test/src/proof_header_type_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('ProofHeaderTypeX', () { + test('formattedString', () { + expect(ProofHeaderType.kid.formattedString, 'kid'); + expect(ProofHeaderType.jwk.formattedString, 'jwk'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/proof_type_test.dart b/packages/oidc4vc/test/src/proof_type_test.dart new file mode 100644 index 000000000..e9056bfac --- /dev/null +++ b/packages/oidc4vc/test/src/proof_type_test.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('ProofTypeeX', () { + test('formattedString', () { + expect(ProofType.ldpVp.formattedString, 'ldp_vp'); + expect(ProofType.jwt.formattedString, 'jwt'); + }); + + test('value', () { + expect(ProofType.ldpVp.value, 'ldp_vp'); + expect(ProofType.jwt.value, 'jwt'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/soipv2_draft_type_test.dart b/packages/oidc4vc/test/src/soipv2_draft_type_test.dart new file mode 100644 index 000000000..97b46019b --- /dev/null +++ b/packages/oidc4vc/test/src/soipv2_draft_type_test.dart @@ -0,0 +1,10 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('SIOPV2DraftTypeX', () { + test('formattedString', () { + expect(SIOPV2DraftType.draft12.formattedString, 'Draft 12'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/token_parameters/token_parameters_class.dart b/packages/oidc4vc/test/src/token_parameters/token_parameters_class.dart index 53a0ea956..701363be4 100644 --- a/packages/oidc4vc/test/src/token_parameters/token_parameters_class.dart +++ b/packages/oidc4vc/test/src/token_parameters/token_parameters_class.dart @@ -1,43 +1,72 @@ -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:oidc4vc/src/token_parameters.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; -// import '../const_values.dart'; +import '../const_values.dart'; -// class TokenParameterTest { -// final tokenParameters = TokenParameters(privateKey, '', ''); +class TokenParameterTest { + final tokenParameters = TokenParameters( + privateKey: privateKey, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + kid: '', + ); -// void publicKeyTest() { -// expect(tokenParameters.publicJWK, publicJWK); -// } + void publicKeyTest() { + expect(tokenParameters.publicJWK, publicJWK); + } -// void didTest() { -// expect(tokenParameters.did, didKey); -// } + void didTest() { + expect(tokenParameters.did, clientId); + } -// void keyIdTest() { -// expect(tokenParameters.kid, kid); -// } + void keyIdTest() { + expect(tokenParameters.kid, kid); + } -// void algorithmIsES256KTest() { -// expect(tokenParameters.alg, ES256KAlg); -// } + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } -// void algorithmIsES256Test() { -// final tokenParameters = TokenParameters(privateKey2, '', ''); -// expect(tokenParameters.alg, ES256Alg); -// } + void algorithmIsES256Test() { + final tokenParameters = TokenParameters( + privateKey: privateKey2, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + ); + expect(tokenParameters.alg, ES256Alg); + } -// void algorithmIsNotNullTest() { -// final tokenParameters = TokenParameters(keyWithAlg, '', ''); -// expect(tokenParameters.alg, HS256Alg); -// } + void algorithmIsNotNullTest() { + final tokenParameters = TokenParameters( + privateKey: keyWithAlg, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + ); + expect(tokenParameters.alg, HS256Alg); + } -// // void thumprintOfKey() { -// // expect(tokenParameters.thumbprint, thumbprint); -// // } + void thumprintOfKey() { + expect(tokenParameters.thumbprint, thumbprint); + } -// // void thumprintOfKeyForrfc7638() { -// // final tokenParameters = TokenParameters(rfc7638Jwk, '', '', ''); -// // expect(tokenParameters.thumbprint, expectedThumbprintForrfc7638Jwk); -// // } -// } + void thumprintOfKeyForrfc7638() { + final tokenParameters = TokenParameters( + privateKey: rfc7638Jwk, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + ); + expect(tokenParameters.thumbprint, expectedThumbprintForrfc7638Jwk); + } +} diff --git a/packages/oidc4vc/test/src/token_parameters/token_parameters_test.dart b/packages/oidc4vc/test/src/token_parameters/token_parameters_test.dart index b32e6bb3d..ebb4006fc 100644 --- a/packages/oidc4vc/test/src/token_parameters/token_parameters_test.dart +++ b/packages/oidc4vc/test/src/token_parameters/token_parameters_test.dart @@ -1,130 +1,77 @@ -// // ignore: unnecessary_lambdas - -// // Copyright (c) 2022, Very Good Ventures -// // https://verygood.ventures -// // -// // Use of this source code is governed by an MIT-style -// // license that can be found in the LICENSE file or at -// // https://opensource.org/licenses/MIT. - -// // ignore_for_file: lines_longer_than_80_chars - -// import 'package:dio/dio.dart'; -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:mocktail/mocktail.dart'; - -// import 'token_parameters_class.dart'; - -// class MockDio extends Mock implements Dio {} - -// void main() { -// group('TokenParameters', () { -// final tokenParametersTest = TokenParameterTest(); - -// // test( -// // 'public key is P-256K private key without d parameter', -// // tokenParametersTest.publicKeyTest, -// // ); - -// // test('did EBSI', tokenParametersTest.didTest); - -// // test('kID EBSI', tokenParametersTest.keyIdTest); - -// group('algorithm test', () { -// test( -// "algorithm is ES256K when key's curve is not P-256", -// tokenParametersTest.algorithmIsES256KTest, -// ); - -// test( -// "algorithm is ES256 when key's curve is P-256", -// tokenParametersTest.algorithmIsES256Test, -// ); - -// test( -// 'if alg is not null then return as it is', -// tokenParametersTest.algorithmIsNotNullTest, -// ); -// }); - -// // group('thumbprint test', () { -// // test('thumbprint of the public Key', tokenParametersTest.thumprintOfKey); - -// // test( -// // 'thumbrprint of the Key from exemple in rfc 7638', -// // tokenParametersTest.thumprintOfKeyForrfc7638, -// // ); -// // }); - -// // group('more didKey test', () { -// // test('did EBSI from Thierry s key', () { -// // final thierryPrivate1 = { -// // 'crv': 'secp256k1', -// // 'kty': 'EC', -// // 'x': 'XMO-urq7MgxkcpQDAJQY84lIO7sTo1Ab-_cqvUyreno', -// // 'y': 'sRnukSTqSCXUShUyDitq6MvTLr5F1ETs6xnR455WW_g' -// // }; - -// // const expectedDid = -// // 'did:ebsi:zkEcb5YVNX5ZRq3nZVH3FVWePLE6vxbqDattYsGan6iLi'; -// // final tokenParameters = TokenParameters(thierryPrivate1, '', '', ''); -// // expect(tokenParameters.didKey, expectedDid); -// // }); - -// // test('did EBSI 1 from Thierry vectors', () { -// // final thierryPrivate = { -// // 'crv': 'secp256k1', -// // 'd': '5DCgRx7Snk-ltE3exxHy94L6LPf8gBSb5_-U8NgRH10', -// // 'kty': 'EC', -// // 'x': 'XMO-urq7MgxkcpQDAJQY84lIO7sTo1Ab-_cqvUyreno', -// // 'y': 'sRnukSTqSCXUShUyDitq6MvTLr5F1ETs6xnR455WW_g' -// // }; -// // const expectedDid = -// // 'did:ebsi:zkEcb5YVNX5ZRq3nZVH3FVWePLE6vxbqDattYsGan6iLi'; -// // final tokenParameters = TokenParameters(thierryPrivate, '', '', ''); -// // expect(tokenParameters.didKey, expectedDid); -// // }); -// // test('did EBSI 2 from Thierry vectors', () { -// // final thierryPrivate = { -// // 'crv': 'secp256k1', -// // 'd': '2CYcyeeIGExqssjp0W3jzHdoEzSWHGBr0ukO66r0h2g', -// // 'kty': 'EC', -// // 'x': 'sg-ra2GWe8qoBIsBL2ZF7HjV71PP02nWuJLnTL2bn7E', -// // 'y': 'h8F81NtkFSHgyY_KCEjXhDfQEU_Jv0AEHMvR9EW65xs' -// // }; -// // const expectedDid = -// // 'did:ebsi:zcpweJw1cLnMM4yaWHRGjSKpuyGNq7SSTuQrBLp5tCpEz'; -// // final tokenParameters = TokenParameters(thierryPrivate, '', '', ''); -// // expect(tokenParameters.didKey, expectedDid); -// // }); -// // test('did EBSI 3 from Thierry vectors', () { -// // final thierryPrivate = { -// // 'crv': 'secp256k1', -// // 'd': 'zlZrsHYH8aeaJWu4ptTjNnDhgBGyFc0UguJ8N4zbsdA', -// // 'kty': 'EC', -// // 'x': 'hdEzHBKnhigtNoU7nIaxQJWIVTqVcuEZdpOKUzkAfkA', -// // 'y': 'Gq1zCf9H0_Wyo5nNyjR8IA-XgTkX1PYaBYb2WKYF3PQ' -// // }; -// // const expectedDid = -// // 'did:ebsi:zgfaTZiwnaK7k4Zf9ce9ydcYQKh76QJcpf2MFDeKYSj1c'; -// // final tokenParameters = TokenParameters(thierryPrivate, '', '', ''); -// // expect(tokenParameters.didKey, expectedDid); -// // }); - -// // test('did EBSI from Alice s key', () { -// // final aliceKey = { -// // 'crv': 'P-256', -// // 'd': 'd_PpSCGQWWgUc1t4iLLH8bKYlYfc9Zy_M7TsfOAcbg8', -// // 'kty': 'EC', -// // 'x': 'ngy44T1vxAT6Di4nr-UaM9K3Tlnz9pkoksDokKFkmNc', -// // 'y': 'QCRfOKlSM31GTkb4JHx3nXB4G_jSPMsbdjzlkT_UpPc', -// // }; - -// // const expectedDid = -// // 'did:ebsi:znxntxQrN369GsNyjFjYb8fuvU7g3sJGyYGwMTcUGdzuy'; -// // final tokenParameters = TokenParameters(aliceKey, '', '', ''); -// // expect(tokenParameters.didKey, expectedDid); -// // }); -// // }); -// }); -// } +// ignore: unnecessary_lambdas + +// Copyright (c) 2022, Very Good Ventures +// https://verygood.ventures +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +// ignore_for_file: lines_longer_than_80_chars + +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +import 'token_parameters_class.dart'; + +void main() { + group('TokenParameters', () { + final tokenParametersTest = TokenParameterTest(); + + test( + 'public key is P-256K private key without d parameter', + tokenParametersTest.publicKeyTest, + ); + + test('did EBSI', tokenParametersTest.didTest); + + test('kID EBSI', tokenParametersTest.keyIdTest); + + group('algorithm test', () { + test( + "algorithm is ES256K when key's curve is not P-256", + tokenParametersTest.algorithmIsES256KTest, + ); + + test( + "algorithm is ES256 when key's curve is P-256", + tokenParametersTest.algorithmIsES256Test, + ); + + test( + 'if alg is not null then return as it is', + tokenParametersTest.algorithmIsNotNullTest, + ); + }); + + group('thumbprint test', () { + test('thumbprint of the public Key', tokenParametersTest.thumprintOfKey); + + test( + 'thumbrprint of the Key from exemple in rfc 7638', + tokenParametersTest.thumprintOfKeyForrfc7638, + ); + }); + + test('get alg', () { + const keyWithAlg = { + 'crv': 'P-256K', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE', + 'alg': 'HS256', + }; + + final tokenParameters = TokenParameters( + privateKey: keyWithAlg, + clientId: '', + did: '', + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + ); + expect(tokenParameters.alg, 'HS256'); + }); + }); +} diff --git a/packages/oidc4vc/test/src/vc_format_type_test.dart b/packages/oidc4vc/test/src/vc_format_type_test.dart new file mode 100644 index 000000000..deb3714d2 --- /dev/null +++ b/packages/oidc4vc/test/src/vc_format_type_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +void main() { + group('VCFormatTypeX', () { + test('formattedString', () { + expect(VCFormatType.ldpVc.vcValue, 'ldp_vc'); + expect(VCFormatType.jwtVc.vcValue, 'jwt_vc'); + expect(VCFormatType.jwtVcJson.vcValue, 'jwt_vc_json'); + expect(VCFormatType.jwtVcJsonLd.vcValue, 'jwt_vc_json-ld'); + expect(VCFormatType.vcSdJWT.vcValue, 'vc+sd-jwt'); + }); + + test('value', () { + expect(VCFormatType.ldpVc.vpValue, 'ldp_vp'); + expect(VCFormatType.jwtVc.vpValue, 'jwt_vp'); + expect(VCFormatType.jwtVcJson.vpValue, 'jwt_vp_json'); + expect(VCFormatType.jwtVcJsonLd.vpValue, 'jwt_vp_json-ld'); + expect(VCFormatType.vcSdJWT.vpValue, 'vc+sd-jwt'); + }); + + test('urlValue', () { + expect(VCFormatType.ldpVc.urlValue, 'ldp_vc'); + expect(VCFormatType.jwtVc.urlValue, 'jwt_vc'); + expect(VCFormatType.jwtVcJson.urlValue, 'jwt_vc_json'); + expect(VCFormatType.jwtVcJsonLd.urlValue, 'jwt_vc_json-ld'); + expect(VCFormatType.vcSdJWT.urlValue, 'vcsd-jwt'); + }); + + test('supportCryptoCredential', () { + expect(VCFormatType.ldpVc.supportCryptoCredential, true); + expect(VCFormatType.jwtVc.supportCryptoCredential, false); + expect(VCFormatType.jwtVcJson.supportCryptoCredential, true); + expect(VCFormatType.jwtVcJsonLd.supportCryptoCredential, false); + expect(VCFormatType.vcSdJWT.supportCryptoCredential, false); + }); + }); +} diff --git a/packages/oidc4vc/test/src/verifier_token_parameters/verifier_token_parameters_class.dart b/packages/oidc4vc/test/src/verifier_token_parameters/verifier_token_parameters_class.dart index a681b97f1..44acfca68 100644 --- a/packages/oidc4vc/test/src/verifier_token_parameters/verifier_token_parameters_class.dart +++ b/packages/oidc4vc/test/src/verifier_token_parameters/verifier_token_parameters_class.dart @@ -1,56 +1,96 @@ -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:oidc4vc/oidc4vc.dart'; - -// import '../const_values.dart'; -// import '../token_parameters/token_parameters_class.dart'; - -// class VerifierTokenParametersTest extends TokenParameterTest { -// final verifierTokenParameters = -// VerifierTokenParameters(privateKey, '', '', '', [], ''); - -// @override -// void publicKeyTest() { -// expect(verifierTokenParameters.publicJWK, publicJWK); -// } - -// @override -// void didTest() { -// expect(tokenParameters.did, didKey); -// } - -// @override -// void keyIdTest() { -// expect(tokenParameters.kid, kid); -// } - -// @override -// void algorithmIsES256KTest() { -// expect(tokenParameters.alg, ES256KAlg); -// } - -// @override -// void algorithmIsES256Test() { -// final tokenParameters = -// VerifierTokenParameters(privateKey2, '', '', '', [], ''); -// expect(tokenParameters.alg, ES256Alg); -// } - -// @override -// void algorithmIsNotNullTest() { -// final tokenParameters = -// VerifierTokenParameters(keyWithAlg, '', '', '', [], ''); -// expect(tokenParameters.alg, HS256Alg); -// } - -// // @override -// // void thumprintOfKey() { -// // expect(tokenParameters.thumbprint, thumbprint); -// // } - -// // @override -// // void thumprintOfKeyForrfc7638() { -// // final tokenParameters2 = -// // VerifierTokenParameters(rfc7638Jwk, '', '', '', Uri.parse(''), []); -// // expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); -// // } -// } +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +import '../const_values.dart'; +import '../token_parameters/token_parameters_class.dart'; + +class VerifierTokenParametersTest extends TokenParameterTest { + final verifierTokenParameters = VerifierTokenParameters( + privateKey: privateKey, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + audience: '', + credentials: [], + kid: '', + nonce: '', + ); + + @override + void publicKeyTest() { + expect(verifierTokenParameters.publicJWK, publicJWK); + } + + @override + void didTest() { + expect(tokenParameters.did, clientId); + } + + @override + void keyIdTest() { + expect(tokenParameters.kid, kid); + } + + @override + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } + + @override + void algorithmIsES256Test() { + final tokenParameters = VerifierTokenParameters( + privateKey: privateKey2, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + audience: '', + credentials: [], + kid: '', + nonce: '', + ); + expect(tokenParameters.alg, ES256Alg); + } + + @override + void algorithmIsNotNullTest() { + final tokenParameters = VerifierTokenParameters( + privateKey: keyWithAlg, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + audience: '', + credentials: [], + kid: '', + nonce: '', + ); + expect(tokenParameters.alg, HS256Alg); + } + + @override + void thumprintOfKey() { + expect(tokenParameters.thumbprint, thumbprint); + } + + @override + void thumprintOfKeyForrfc7638() { + final tokenParameters2 = VerifierTokenParameters( + privateKey: rfc7638Jwk, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + audience: '', + credentials: [], + kid: '', + nonce: '', + ); + expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); + } +} diff --git a/packages/oidc4vc/test/src/verifier_token_parameters/verifier_token_parameters_test.dart b/packages/oidc4vc/test/src/verifier_token_parameters/verifier_token_parameters_test.dart index f22b2edb4..89743d4f2 100644 --- a/packages/oidc4vc/test/src/verifier_token_parameters/verifier_token_parameters_test.dart +++ b/packages/oidc4vc/test/src/verifier_token_parameters/verifier_token_parameters_test.dart @@ -1,187 +1,194 @@ -// // Copyright (c) 2022, Very Good Ventures -// // https://verygood.ventures -// // -// // Use of this source code is governed by an MIT-style -// // license that can be found in the LICENSE file or at -// // https://opensource.org/licenses/MIT. - -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:oidc4vc/oidc4vc.dart'; - -// import 'verifier_token_parameters_class.dart'; - -// void main() { -// group('Verifier TokenParameters', () { -// const privateKey = { -// 'crv': 'P-256K', -// 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', -// 'kty': 'EC', -// 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', -// 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE', -// }; - -// const credentialsToBePresented = [ -// r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-08T15:10:56Z","issued":"2023-02-08T15:10:56Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-08T15:10:56Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-08T15:10:56Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","type":null,"issuedBy":{"name":""}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-08T20:41:02.024360","presentation":null}],"jwt":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU4NzAwNTYuMTcxMDgyLCJpYXQiOjE2NzU4NjkwNTYuMTcxMDc1LCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTg2OTA1Ni4xNzEwOCwibm9uY2UiOiJjOGM3Nzg0Yi1hN2MyLTExZWQtOGUwMS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMDhUMTU6MTA6NTZaIiwiaXNzdWVkIjoiMjAyMy0wMi0wOFQxNToxMDo1NloiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTA4VDE1OjEwOjU2WiJ9fQ.avhpj0UC7SJiOo8PWJV3zhJoxngdmrBnyTkQsnqe76JlOmatLkgAY5Mp_sUJQnN1uENiT-_KBf0KY3izx4X_SA"}', -// r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-08T15:10:56Z","issued":"2023-02-08T15:10:56Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-08T15:10:56Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-08T15:10:56Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","type":null,"issuedBy":{"name":""}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-08T20:41:02.024360","presentation":null}]}', -// ]; - -// final verifierTokenParameters = VerifierTokenParameters( -// privateKey, -// '', -// '', -// '', -// credentialsToBePresented, -// '', -// ); - -// const nonce = '69165b47-a851-11ed-bd52-0a1628958560'; - -// const audience = 'xjcqarovuv'; - -// const jwtsOfCredentials = [ -// // ignore: lines_longer_than_80_chars -// 'eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU4NzAwNTYuMTcxMDgyLCJpYXQiOjE2NzU4NjkwNTYuMTcxMDc1LCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTg2OTA1Ni4xNzEwOCwibm9uY2UiOiJjOGM3Nzg0Yi1hN2MyLTExZWQtOGUwMS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMDhUMTU6MTA6NTZaIiwiaXNzdWVkIjoiMjAyMy0wMi0wOFQxNToxMDo1NloiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTA4VDE1OjEwOjU2WiJ9fQ.avhpj0UC7SJiOo8PWJV3zhJoxngdmrBnyTkQsnqe76JlOmatLkgAY5Mp_sUJQnN1uENiT-_KBf0KY3izx4X_SA', - -// { -// '@context': ['https://www.w3.org/2018/credentials/v1'], -// 'credentialSchema': { -// 'id': -// 'https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd', -// 'type': 'JsonSchemaValidator2018', -// }, -// 'credentialStatus': { -// 'id': -// 'https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176', -// 'type': 'CredentialStatusList2020', -// }, -// 'credentialSubject': { -// 'awardingOpportunity': { -// 'awardingBody': { -// 'eidasLegalIdentifier': 'Unknown', -// 'homepage': 'https://leaston.bcdiploma.com/', -// 'id': 'did:ebsi:zdRvvKbXhVVBsXhatjuiBhs', -// 'preferredName': 'Leaston University', -// 'registration': '0597065J', -// }, -// 'endedAtTime': '2020-06-26T00:00:00Z', -// 'id': -// 'https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity', -// 'identifier': -// 'https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ', -// 'location': 'FRANCE', -// 'startedAtTime': '2019-09-02T00:00:00Z', -// }, -// 'dateOfBirth': '1993-04-08', -// 'familyName': 'DOE', -// 'givenNames': 'Jane', -// 'gradingScheme': { -// 'id': -// 'https://leaston.bcdiploma.com/law-economics-management#GradingScheme', -// 'title': '2 year full-time programme / 4 semesters', -// }, -// 'id': 'did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa', -// 'identifier': '0904008084H', -// 'learningAchievement': { -// 'additionalNote': ['DISTRIBUTION MANAGEMENT'], -// 'description': -// 'The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.', // ignore: lines_longer_than_80_chars -// 'id': -// 'https://leaston.bcdiploma.com/law-economics-management#LearningAchievment', -// 'title': 'Master in Information and Computer Sciences', -// }, -// 'learningSpecification': { -// 'ectsCreditPoints': 120, -// 'eqfLevel': 7, -// 'id': -// 'https://leaston.bcdiploma.com/law-economics-management#LearningSpecification', -// 'iscedfCode': ['7'], -// 'nqfLevel': ['7'], -// }, -// }, -// 'evidence': { -// 'documentPresence': ['Physical'], -// 'evidenceDocument': ['Passport'], -// 'id': -// 'https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678', -// 'subjectPresence': 'Physical', -// 'type': ['DocumentVerification'], -// 'verifier': -// 'did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a', -// }, -// 'id': 'urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236', -// 'issuanceDate': '2023-02-08T15:10:56Z', -// 'issued': '2023-02-08T15:10:56Z', -// 'issuer': 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB', -// 'proof': { -// 'created': '2022-04-27T12:25:07Z', -// 'creator': 'did:ebsi:zdRvvKbXhVVBsXhatjuiBhs', -// 'domain': 'https://api.preprod.ebsi.eu', -// 'jws': -// 'eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ', // ignore: lines_longer_than_80_chars -// 'nonce': '3ea68dae-d07a-4daa-932b-fbb58f5c20c4', -// 'type': 'EcdsaSecp256k1Signature2019', -// }, -// 'type': [ -// 'VerifiableCredential', -// 'VerifiableAttestation', -// 'VerifiableDiploma', -// ], -// 'validFrom': '2023-02-08T15:10:56Z', -// } -// ]; - -// test('nonce', () { -// expect(verifierTokenParameters.nonce, nonce); -// }); - -// test('jwtsOfCredentials', () { -// expect(verifierTokenParameters.jsonIdOrJwtList, jwtsOfCredentials); -// }); - -// test('audience', () { -// expect(verifierTokenParameters.audience, audience); -// }); - -// group('override test', () { -// final verifierTokenParametersTest = VerifierTokenParametersTest(); - -// // test( -// // 'public key is P-256K private key without d parameter', -// // verifierTokenParametersTest.publicKeyTest, -// // ); - -// // test('did EBSI', verifierTokenParametersTest.didTest); - -// // test('kID EBSI', verifierTokenParametersTest.keyIdTest); - -// group('algorithm test', () { -// test( -// "algorithm is ES256K when key's curve is not P-256", -// verifierTokenParametersTest.algorithmIsES256KTest, -// ); - -// test( -// "algorithm is ES256 when key's curve is P-256", -// verifierTokenParametersTest.algorithmIsES256Test, -// ); - -// test( -// 'if alg is not null then return as it is', -// verifierTokenParametersTest.algorithmIsNotNullTest, -// ); -// }); - -// group('thumbprint test', () { -// // test( -// // 'thumbprint of the public Key', -// // verifierTokenParametersTest.thumprintOfKey, -// // ); - -// // test( -// // 'thumbrprint of the Key from exemple in rfc 7638', -// // verifierTokenParametersTest.thumprintOfKeyForrfc7638, -// // ); -// }); -// }); -// }); -// } +// Copyright (c) 2022, Very Good Ventures +// https://verygood.ventures +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:oidc4vc/oidc4vc.dart'; + +import 'verifier_token_parameters_class.dart'; + +void main() { + group('Verifier TokenParameters', () { + const privateKey = { + 'crv': 'P-256K', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE', + }; + + const clientId = + '''did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrbpg5is8LfTLuQ1RsW5r7s7ZjbDDFbDgy1tLrdc7Bj3itBGQkuGUQyfzKhFqbUNW2PqJPMSSzWoF2DGSvDSijCtJtYCSRsjSVLrwu5oHNbnPFvSEC4iRZPpU6B6nExRBTa'''; + + const credentialsToBePresented = [ + r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-08T15:10:56Z","issued":"2023-02-08T15:10:56Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-08T15:10:56Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-08T15:10:56Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","type":null,"issuedBy":{"name":""}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-08T20:41:02.024360","presentation":null}],"jwt":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU4NzAwNTYuMTcxMDgyLCJpYXQiOjE2NzU4NjkwNTYuMTcxMDc1LCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTg2OTA1Ni4xNzEwOCwibm9uY2UiOiJjOGM3Nzg0Yi1hN2MyLTExZWQtOGUwMS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMDhUMTU6MTA6NTZaIiwiaXNzdWVkIjoiMjAyMy0wMi0wOFQxNToxMDo1NloiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTA4VDE1OjEwOjU2WiJ9fQ.avhpj0UC7SJiOo8PWJV3zhJoxngdmrBnyTkQsnqe76JlOmatLkgAY5Mp_sUJQnN1uENiT-_KBf0KY3izx4X_SA"}', + r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-08T15:10:56Z","issued":"2023-02-08T15:10:56Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-08T15:10:56Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-08T15:10:56Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","type":null,"issuedBy":{"name":""}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-08T20:41:02.024360","presentation":null}]}', + ]; + + const nonce = '69165b47-a851-11ed-bd52-0a1628958560'; + + const audience = 'xjcqarovuv'; + + final verifierTokenParameters = VerifierTokenParameters( + privateKey: privateKey, + clientId: clientId, + did: clientId, + clientType: ClientType.did, + mediaType: MediaType.proofOfOwnership, + proofHeaderType: ProofHeaderType.kid, + audience: audience, + credentials: credentialsToBePresented, + kid: '', + nonce: nonce, + ); + + const jwtsOfCredentials = [ + // ignore: lines_longer_than_80_chars + 'eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU4NzAwNTYuMTcxMDgyLCJpYXQiOjE2NzU4NjkwNTYuMTcxMDc1LCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTg2OTA1Ni4xNzEwOCwibm9uY2UiOiJjOGM3Nzg0Yi1hN2MyLTExZWQtOGUwMS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMDhUMTU6MTA6NTZaIiwiaXNzdWVkIjoiMjAyMy0wMi0wOFQxNToxMDo1NloiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTA4VDE1OjEwOjU2WiJ9fQ.avhpj0UC7SJiOo8PWJV3zhJoxngdmrBnyTkQsnqe76JlOmatLkgAY5Mp_sUJQnN1uENiT-_KBf0KY3izx4X_SA', + + { + '@context': ['https://www.w3.org/2018/credentials/v1'], + 'credentialSchema': { + 'id': + 'https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd', + 'type': 'JsonSchemaValidator2018', + }, + 'credentialStatus': { + 'id': + 'https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176', + 'type': 'CredentialStatusList2020', + }, + 'credentialSubject': { + 'awardingOpportunity': { + 'awardingBody': { + 'eidasLegalIdentifier': 'Unknown', + 'homepage': 'https://leaston.bcdiploma.com/', + 'id': 'did:ebsi:zdRvvKbXhVVBsXhatjuiBhs', + 'preferredName': 'Leaston University', + 'registration': '0597065J', + }, + 'endedAtTime': '2020-06-26T00:00:00Z', + 'id': + 'https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity', + 'identifier': + 'https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ', + 'location': 'FRANCE', + 'startedAtTime': '2019-09-02T00:00:00Z', + }, + 'dateOfBirth': '1993-04-08', + 'familyName': 'DOE', + 'givenNames': 'Jane', + 'gradingScheme': { + 'id': + 'https://leaston.bcdiploma.com/law-economics-management#GradingScheme', + 'title': '2 year full-time programme / 4 semesters', + }, + 'id': 'did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa', + 'identifier': '0904008084H', + 'learningAchievement': { + 'additionalNote': ['DISTRIBUTION MANAGEMENT'], + 'description': + 'The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.', // ignore: lines_longer_than_80_chars + 'id': + 'https://leaston.bcdiploma.com/law-economics-management#LearningAchievment', + 'title': 'Master in Information and Computer Sciences', + }, + 'learningSpecification': { + 'ectsCreditPoints': 120, + 'eqfLevel': 7, + 'id': + 'https://leaston.bcdiploma.com/law-economics-management#LearningSpecification', + 'iscedfCode': ['7'], + 'nqfLevel': ['7'], + }, + }, + 'evidence': { + 'documentPresence': ['Physical'], + 'evidenceDocument': ['Passport'], + 'id': + 'https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678', + 'subjectPresence': 'Physical', + 'type': ['DocumentVerification'], + 'verifier': + 'did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a', + }, + 'id': 'urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236', + 'issuanceDate': '2023-02-08T15:10:56Z', + 'issued': '2023-02-08T15:10:56Z', + 'issuer': 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB', + 'proof': { + 'created': '2022-04-27T12:25:07Z', + 'creator': 'did:ebsi:zdRvvKbXhVVBsXhatjuiBhs', + 'domain': 'https://api.preprod.ebsi.eu', + 'jws': + 'eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ', // ignore: lines_longer_than_80_chars + 'nonce': '3ea68dae-d07a-4daa-932b-fbb58f5c20c4', + 'type': 'EcdsaSecp256k1Signature2019', + }, + 'type': [ + 'VerifiableCredential', + 'VerifiableAttestation', + 'VerifiableDiploma', + ], + 'validFrom': '2023-02-08T15:10:56Z', + } + ]; + + test('nonce', () { + expect(verifierTokenParameters.nonce, nonce); + }); + + test('jwtsOfCredentials', () { + expect(verifierTokenParameters.jsonIdOrJwtList, jwtsOfCredentials); + }); + + test('audience', () { + expect(verifierTokenParameters.audience, audience); + }); + + group('override test', () { + final verifierTokenParametersTest = VerifierTokenParametersTest(); + + test( + 'public key is P-256K private key without d parameter', + verifierTokenParametersTest.publicKeyTest, + ); + + test('did EBSI', verifierTokenParametersTest.didTest); + + test('kID EBSI', verifierTokenParametersTest.keyIdTest); + + group('algorithm test', () { + test( + "algorithm is ES256K when key's curve is not P-256", + verifierTokenParametersTest.algorithmIsES256KTest, + ); + + test( + "algorithm is ES256 when key's curve is P-256", + verifierTokenParametersTest.algorithmIsES256Test, + ); + + test( + 'if alg is not null then return as it is', + verifierTokenParametersTest.algorithmIsNotNullTest, + ); + }); + + group('thumbprint test', () { + test( + 'thumbprint of the public Key', + verifierTokenParametersTest.thumprintOfKey, + ); + + test( + 'thumbrprint of the Key from exemple in rfc 7638', + verifierTokenParametersTest.thumprintOfKeyForrfc7638, + ); + }); + }); + }); +} diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/bug_report.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..50a4c7b8b --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug Report +about: Create a report to help us improve +title: "fix: " +labels: bug +--- + +**Description** + +A clear and concise description of what the bug is. + +**Steps To Reproduce** + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected Behavior** + +A clear and concise description of what you expected to happen. + +**Screenshots** + +If applicable, add screenshots to help explain your problem. + +**Additional Context** + +Add any other context about the problem here. diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/build.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/build.md new file mode 100644 index 000000000..0cf8e62cd --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/build.md @@ -0,0 +1,14 @@ +--- +name: Build System +about: Changes that affect the build system or external dependencies +title: "build: " +labels: build +--- + +**Description** + +Describe what changes need to be done to the build system and why. + +**Requirements** + +- [ ] The build system is passing diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/chore.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/chore.md new file mode 100644 index 000000000..498ebfd82 --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/chore.md @@ -0,0 +1,14 @@ +--- +name: Chore +about: Other changes that don't modify src or test files +title: "chore: " +labels: chore +--- + +**Description** + +Clearly describe what change is needed and why. If this changes code then please use another issue type. + +**Requirements** + +- [ ] No functional changes to the code diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/ci.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/ci.md new file mode 100644 index 000000000..fa2dd9e2d --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/ci.md @@ -0,0 +1,14 @@ +--- +name: Continuous Integration +about: Changes to the CI configuration files and scripts +title: "ci: " +labels: ci +--- + +**Description** + +Describe what changes need to be done to the ci/cd system and why. + +**Requirements** + +- [ ] The ci system is passing diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/config.yml b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..ec4bb386b --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/documentation.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 000000000..f494a4d98 --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,14 @@ +--- +name: Documentation +about: Improve the documentation so all collaborators have a common understanding +title: "docs: " +labels: documentation +--- + +**Description** + +Clearly describe what documentation you are looking to add or improve. + +**Requirements** + +- [ ] Requirements go here diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/feature_request.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..ddd2fcca9 --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature Request +about: A new feature to be added to the project +title: "feat: " +labels: feature +--- + +**Description** + +Clearly describe what you are looking to add. The more context the better. + +**Requirements** + +- [ ] Checklist of requirements to be fulfilled + +**Additional Context** + +Add any other context or screenshots about the feature request go here. diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/performance.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/performance.md new file mode 100644 index 000000000..699b8d45f --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/performance.md @@ -0,0 +1,14 @@ +--- +name: Performance Update +about: A code change that improves performance +title: "perf: " +labels: performance +--- + +**Description** + +Clearly describe what code needs to be changed and what the performance impact is going to be. Bonus point's if you can tie this directly to user experience. + +**Requirements** + +- [ ] There is no drop in test coverage. diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/refactor.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/refactor.md new file mode 100644 index 000000000..1626c5704 --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/refactor.md @@ -0,0 +1,14 @@ +--- +name: Refactor +about: A code change that neither fixes a bug nor adds a feature +title: "refactor: " +labels: refactor +--- + +**Description** + +Clearly describe what needs to be refactored and why. Please provide links to related issues (bugs or upcoming features) in order to help prioritize. + +**Requirements** + +- [ ] There is no drop in test coverage. diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/revert.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/revert.md new file mode 100644 index 000000000..9d121dc56 --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/revert.md @@ -0,0 +1,16 @@ +--- +name: Revert Commit +about: Reverts a previous commit +title: "revert: " +labels: revert +--- + +**Description** + +Provide a link to a PR/Commit that you are looking to revert and why. + +**Requirements** + +- [ ] Change has been reverted +- [ ] No change in test coverage has happened +- [ ] A new ticket is created for any follow on work that needs to happen diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/style.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/style.md new file mode 100644 index 000000000..02244a7bd --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/style.md @@ -0,0 +1,14 @@ +--- +name: Style Changes +about: Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc) +title: "style: " +labels: style +--- + +**Description** + +Clearly describe what you are looking to change and why. + +**Requirements** + +- [ ] There is no drop in test coverage. diff --git a/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/test.md b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/test.md new file mode 100644 index 000000000..431a7ea76 --- /dev/null +++ b/packages/secure_jwt_signer/.github/ISSUE_TEMPLATE/test.md @@ -0,0 +1,14 @@ +--- +name: Test +about: Adding missing tests or correcting existing tests +title: "test: " +labels: test +--- + +**Description** + +List out the tests that need to be added or changed. Please also include any information as to why this was not covered in the past. + +**Requirements** + +- [ ] There is no drop in test coverage. diff --git a/packages/secure_jwt_signer/.github/PULL_REQUEST_TEMPLATE.md b/packages/secure_jwt_signer/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..116993637 --- /dev/null +++ b/packages/secure_jwt_signer/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ + + +## Status + +**READY/IN DEVELOPMENT/HOLD** + +## Description + + + +## Type of Change + + + +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) +- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) +- [ ] 🧹 Code refactor +- [ ] ✅ Build configuration change +- [ ] 📝 Documentation +- [ ] 🗑️ Chore diff --git a/packages/secure_jwt_signer/.github/cspell.json b/packages/secure_jwt_signer/.github/cspell.json new file mode 100644 index 000000000..be80a6290 --- /dev/null +++ b/packages/secure_jwt_signer/.github/cspell.json @@ -0,0 +1,21 @@ +{ + "version": "0.2", + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "dictionaries": ["vgv_allowed", "vgv_forbidden"], + "dictionaryDefinitions": [ + { + "name": "vgv_allowed", + "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/allowed.txt", + "description": "Allowed VGV Spellings" + }, + { + "name": "vgv_forbidden", + "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/forbidden.txt", + "description": "Forbidden VGV Spellings" + } + ], + "useGitignore": true, + "words": [ + "secure_jwt_signer" + ] +} diff --git a/packages/secure_jwt_signer/.github/dependabot.yaml b/packages/secure_jwt_signer/.github/dependabot.yaml new file mode 100644 index 000000000..63b035cde --- /dev/null +++ b/packages/secure_jwt_signer/.github/dependabot.yaml @@ -0,0 +1,11 @@ +version: 2 +enable-beta-ecosystems: true +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "daily" diff --git a/packages/secure_jwt_signer/.github/workflows/main.yaml b/packages/secure_jwt_signer/.github/workflows/main.yaml new file mode 100644 index 000000000..c7146c3cb --- /dev/null +++ b/packages/secure_jwt_signer/.github/workflows/main.yaml @@ -0,0 +1,27 @@ +name: ci + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + semantic_pull_request: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1 + + spell-check: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1 + with: + includes: "**/*.md" + modified_files_only: false + + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 + diff --git a/packages/secure_jwt_signer/.gitignore b/packages/secure_jwt_signer/.gitignore new file mode 100644 index 000000000..526da1584 --- /dev/null +++ b/packages/secure_jwt_signer/.gitignore @@ -0,0 +1,7 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +pubspec.lock \ No newline at end of file diff --git a/packages/secure_jwt_signer/README.md b/packages/secure_jwt_signer/README.md new file mode 100644 index 000000000..148264463 --- /dev/null +++ b/packages/secure_jwt_signer/README.md @@ -0,0 +1,62 @@ +# Secure Jwt Signer + +[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] +[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason) +[![License: MIT][license_badge]][license_link] + +Sign jwt with keys from TEE/Strongbox or Secure Element + +## Installation 💻 + +**❗ In order to start using Secure Jwt Signer you must have the [Dart SDK][dart_install_link] installed on your machine.** + +Install via `dart pub add`: + +```sh +dart pub add secure_jwt_signer +``` + +--- + +## Continuous Integration 🤖 + +Secure Jwt Signer comes with a built-in [GitHub Actions workflow][github_actions_link] powered by [Very Good Workflows][very_good_workflows_link] but you can also add your preferred CI/CD solution. + +Out of the box, on each pull request and push, the CI `formats`, `lints`, and `tests` the code. This ensures the code remains consistent and behaves correctly as you add functionality or make changes. The project uses [Very Good Analysis][very_good_analysis_link] for a strict set of analysis options used by our team. Code coverage is enforced using the [Very Good Workflows][very_good_coverage_link]. + +--- + +## Running Tests 🧪 + +To run all unit tests: + +```sh +dart pub global activate coverage 1.2.0 +dart test --coverage=coverage +dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info +``` + +To view the generated coverage report you can use [lcov](https://github.com/linux-test-project/lcov). + +```sh +# Generate Coverage Report +genhtml coverage/lcov.info -o coverage/ + +# Open Coverage Report +open coverage/index.html +``` + +[dart_install_link]: https://dart.dev/get-dart +[github_actions_link]: https://docs.github.com/en/actions/learn-github-actions +[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg +[license_link]: https://opensource.org/licenses/MIT +[logo_black]: https://raw.githubusercontent.com/VGVentures/very_good_brand/main/styles/README/vgv_logo_black.png#gh-light-mode-only +[logo_white]: https://raw.githubusercontent.com/VGVentures/very_good_brand/main/styles/README/vgv_logo_white.png#gh-dark-mode-only +[mason_link]: https://github.com/felangel/mason +[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg +[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis +[very_good_coverage_link]: https://github.com/marketplace/actions/very-good-coverage +[very_good_ventures_link]: https://verygood.ventures +[very_good_ventures_link_light]: https://verygood.ventures#gh-light-mode-only +[very_good_ventures_link_dark]: https://verygood.ventures#gh-dark-mode-only +[very_good_workflows_link]: https://github.com/VeryGoodOpenSource/very_good_workflows diff --git a/packages/secure_jwt_signer/analysis_options.yaml b/packages/secure_jwt_signer/analysis_options.yaml new file mode 100644 index 000000000..a608bb333 --- /dev/null +++ b/packages/secure_jwt_signer/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:very_good_analysis/analysis_options.yaml +linter: + rules: + public_member_api_docs: false + constant_identifier_names: false diff --git a/packages/secure_jwt_signer/coverage_badge.svg b/packages/secure_jwt_signer/coverage_badge.svg new file mode 100644 index 000000000..499e98ce2 --- /dev/null +++ b/packages/secure_jwt_signer/coverage_badge.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + coverage + coverage + 100% + 100% + + diff --git a/packages/secure_jwt_signer/lib/secure_jwt_signer.dart b/packages/secure_jwt_signer/lib/secure_jwt_signer.dart new file mode 100644 index 000000000..3df2dd988 --- /dev/null +++ b/packages/secure_jwt_signer/lib/secure_jwt_signer.dart @@ -0,0 +1,4 @@ +/// Sign jwt with keys from TEE/Strongbox or Secure Element +library secure_jwt_signer; + +export 'src/secure_jwt_signer.dart'; diff --git a/packages/secure_jwt_signer/lib/src/secure_jwt_signer.dart b/packages/secure_jwt_signer/lib/src/secure_jwt_signer.dart new file mode 100644 index 000000000..8393b33a1 --- /dev/null +++ b/packages/secure_jwt_signer/lib/src/secure_jwt_signer.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:crypto/crypto.dart'; +import 'package:secp256r1/secp256r1.dart'; + +/// Sign jwt with keys from TEE/Strongbox or Secure Element +class SecureJwtSigner { + const SecureJwtSigner(); + + Future signJwt({ + required Map header, + required Map payload, + required String privateKey, + }) async { + // Base64 url safe encoding of header and payload without padding + final headerB64 = + base64Encode(utf8.encode(jsonEncode(header))).replaceAll('=', ''); + final payloadB64 = + base64Encode(utf8.encode(jsonEncode(payload))).replaceAll('=', ''); + + // Calculate the message digest with SHA-256 + final message = '$headerB64.$payloadB64'; + final bytes = utf8.encode(message); + final digest = sha256.convert(bytes); + + // Sign the message digest with ECDSA key (P-256) + final signature = await SecureP256.sign( + privateKey, + Uint8List.fromList(digest.bytes), + ); + + // Encode signature with base64 url safe and no padding + final signatureB64 = base64Url.encode(signature).replaceAll('=', ''); + + // Return JWT + return '$headerB64.$payloadB64.$signatureB64'; + } +} diff --git a/packages/secure_jwt_signer/pubspec.yaml b/packages/secure_jwt_signer/pubspec.yaml new file mode 100644 index 000000000..16a7dfa4c --- /dev/null +++ b/packages/secure_jwt_signer/pubspec.yaml @@ -0,0 +1,16 @@ +name: secure_jwt_signer +description: Sign jwt with keys from TEE/Strongbox or Secure Element +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ">=3.1.0 <4.0.0" + +dependencies: + crypto: ^3.0.3 + secp256r1: ^0.1.0-dev.7 + +dev_dependencies: + mocktail: ^1.0.3 + test: ^1.19.2 + very_good_analysis: ^5.0.0+1 diff --git a/packages/secure_jwt_signer/test/src/secure_jwt_signer_test.dart b/packages/secure_jwt_signer/test/src/secure_jwt_signer_test.dart new file mode 100644 index 000000000..1fc327786 --- /dev/null +++ b/packages/secure_jwt_signer/test/src/secure_jwt_signer_test.dart @@ -0,0 +1,13 @@ +// ignore_for_file: prefer_const_constructors +import 'dart:convert'; + +import 'package:secure_jwt_signer/secure_jwt_signer.dart'; +import 'package:test/test.dart'; + +void main() { + group('SecureJwtSigner', () { + test('can be instantiated', () { + expect(SecureJwtSigner(), isNotNull); + }); + }); +} diff --git a/packages/secure_storage/pubspec.yaml b/packages/secure_storage/pubspec.yaml index 2ed66ba43..776ba96ae 100644 --- a/packages/secure_storage/pubspec.yaml +++ b/packages/secure_storage/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_secure_storage: ^9.0.0 + flutter_secure_storage: ^9.2.1 dev_dependencies: flutter_test: diff --git a/packages/secure_storage/test/src/secure_storage_test.dart b/packages/secure_storage/test/src/secure_storage_test.dart index 247ba18d2..5f3f13e6c 100644 --- a/packages/secure_storage/test/src/secure_storage_test.dart +++ b/packages/secure_storage/test/src/secure_storage_test.dart @@ -16,12 +16,16 @@ void main() { setUpAll(() async { mockFlutterSecureStorage = MockFlutterSecureStorage(); - secureStorageProvider = SecureStorageProvider(); + secureStorageProvider = + SecureStorageProvider(storage: mockFlutterSecureStorage); }); group('SecureStorage', () { test('can be instantiated', () { - expect(getSecureStorage, isNotNull); + expect(secureStorageProvider, isNotNull); + }); + test('getSecureStorage returns SecureStorageProvider instance', () { + expect(getSecureStorage, isA()); }); test('get method works correctly', () async { @@ -45,10 +49,9 @@ void main() { final result = await secureStorageProvider.getAllValues(); expect(result, equals(allValue)); verify( - () => mockFlutterSecureStorage.readAll( - iOptions: any(named: 'iOptions'), - ), - ).called(1); + () => + mockFlutterSecureStorage.readAll(iOptions: any(named: 'iOptions')), + ); }); test('set method works correctly', () async { @@ -97,5 +100,47 @@ void main() { await secureStorageProvider.deleteAll(); verify(mockFlutterSecureStorage.deleteAll).called(1); }); + + group('deleteAllExceptsSomeKeys method', () { + test('delete all when list is null', () async { + when(mockFlutterSecureStorage.deleteAll) + .thenAnswer((invocation) async {}); + + await secureStorageProvider.deleteAllExceptsSomeKeys(null); + verify(mockFlutterSecureStorage.deleteAll).called(1); + }); + + test('delete all when list is empty', () async { + when(mockFlutterSecureStorage.deleteAll) + .thenAnswer((invocation) async {}); + + await secureStorageProvider.deleteAllExceptsSomeKeys([]); + verify(mockFlutterSecureStorage.deleteAll).called(1); + }); + + test('delete all except sent in the list', () async { + when( + () => mockFlutterSecureStorage.readAll( + iOptions: any(named: 'iOptions'), + ), + ).thenAnswer((invocation) async { + return { + 'key1': '1', + 'key2': '2', + }; + }); + + when( + () => mockFlutterSecureStorage.delete( + key: 'key1', + iOptions: any(named: 'iOptions'), + ), + ).thenAnswer((_) async {}); + + await secureStorageProvider.deleteAllExceptsSomeKeys(['key2']); + verify(() => mockFlutterSecureStorage.delete(key: 'key1')).called(1); + verifyNever(() => mockFlutterSecureStorage.delete(key: 'key2')); + }); + }); }); } diff --git a/pubspec.lock b/pubspec.lock index d9de9330b..03e90e61a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,17 @@ packages: dependency: transitive description: name: archive +<<<<<<< HEAD sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373" url: "https://pub.dev" source: hosted version: "3.5.0" +======= + sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 + url: "https://pub.dev" + source: hosted + version: "3.5.1" +>>>>>>> october args: dependency: transitive description: @@ -61,10 +68,10 @@ packages: dependency: "direct main" description: name: asn1lib - sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 + sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda" url: "https://pub.dev" source: hosted - version: "1.5.2" + version: "1.5.3" async: dependency: "direct main" description: @@ -294,10 +301,10 @@ packages: dependency: "direct main" description: name: camera - sha256: "9499cbc2e51d8eb0beadc158b288380037618ce4e30c9acbc4fae1ac3ecb5797" + sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8 url: "https://pub.dev" source: hosted - version: "0.10.5+9" + version: "0.10.6" camera_android: dependency: transitive description: @@ -422,10 +429,10 @@ packages: dependency: transitive description: name: coverage - sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" + sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" url: "https://pub.dev" source: hosted - version: "1.7.2" + version: "1.8.0" credential_manifest: dependency: "direct main" description: @@ -744,7 +751,7 @@ packages: source: hosted version: "2.1.2" fake_async: - dependency: transitive + dependency: "direct dev" description: name: fake_async sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" @@ -811,10 +818,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.4" file_selector_platform_interface: dependency: transitive description: @@ -1045,50 +1052,50 @@ packages: dependency: transitive description: name: flutter_secure_storage - sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 + sha256: "8496a89eea74e23f92581885f876455d9d460e71201405dffe5f55dfe1155864" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.2.1" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + sha256: b768a7dab26d6186b68e2831b3104f8968154f0f4fdbf66e7c2dd7bdf299daaf url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.1" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.2" flutter_sodium: dependency: transitive description: @@ -1445,10 +1452,10 @@ packages: dependency: transitive description: name: local_auth_darwin - sha256: "33381a15b0de2279523eca694089393bb146baebdce72a404555d03174ebc1e9" + sha256: "959145a4cf6f0de745b9ec9ac60101270eb4c5b8b7c2a0470907014adc1c618d" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" local_auth_platform_interface: dependency: transitive description: @@ -1469,10 +1476,10 @@ packages: dependency: transitive description: name: logger - sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04" + sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" logging: dependency: transitive description: @@ -1724,10 +1731,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -2123,10 +2130,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -2449,10 +2456,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.3.0" url_launcher_linux: dependency: transitive description: @@ -2465,10 +2472,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8c63d4ffe..b762bb224 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,7 +55,6 @@ dependencies: flutter_dotenv: ^5.0.2 flutter_html: ^3.0.0-beta.1 flutter_image_compress: ^2.0.3 - flutter_local_notifications: ^16.3.0 flutter_localizations: sdk: flutter flutter_markdown: ^0.6.9 #flutter_markdown ^0.6.14 requires markdown ^7.0.0 @@ -130,6 +129,7 @@ dependency_overrides: dev_dependencies: bloc_test: ^9.1.2 build_runner: ^2.4.4 + fake_async: ^1.3.1 flutter_launcher_icons: ^0.13.1 flutter_test: sdk: flutter diff --git a/test/app/shared/alert_message/alert_message_test.dart b/test/app/shared/alert_message/alert_message_test.dart new file mode 100644 index 000000000..78c176835 --- /dev/null +++ b/test/app/shared/alert_message/alert_message_test.dart @@ -0,0 +1,190 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + testWidgets('AlertMessage shows SnackBar', (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + AlertMessage.showStateMessage( + context: context, + stateMessage: const StateMessage( + messageHandler: null, + stringMessage: 'Test Message', + injectedMessage: null, + showDialog: false, + duration: Duration(seconds: 2), + type: MessageType.info, + ), + ); + }, + child: const Text('Show SnackBar'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Show SnackBar')); + await tester.pumpAndSettle(); + + expect(find.text('Test Message'), findsOneWidget); + expect(find.byType(SnackBar), findsOneWidget); + expect(find.byType(SnackBarContent), findsOneWidget); + }); + + testWidgets('AlertMessage shows Dialog', (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + AlertMessage.showStateMessage( + context: context, + stateMessage: const StateMessage( + messageHandler: null, + stringMessage: 'Test Message', + injectedMessage: null, + showDialog: true, + type: MessageType.info, + ), + ); + }, + child: const Text('Show Dialog'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + expect(find.text('Test Message'), findsOneWidget); + expect(find.byType(ErrorDialog), findsOneWidget); + }); + testWidgets('SnackBarContent is displayed correctly', + (WidgetTester tester) async { + await tester.pumpApp( + const Scaffold( + body: SnackBarContent( + message: 'Test Message', + iconPath: 'assets/icon/add.png', + ), + ), + ); + + expect(find.text('Test Message'), findsOneWidget); + expect(find.byType(ElevatedButton), findsOneWidget); + }); + + testWidgets('AlertMessage shows Network error properly', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage( + messageHandler: NetworkException( + data: 'Netwok Test', + ), + injectedMessage: null, + showDialog: false, + duration: const Duration(seconds: 2), + type: MessageType.info, + ), + ); + }, + child: const Text('Show SnackBar'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Show SnackBar')); + await tester.pumpAndSettle(); + + expect(find.text('Netwok Test'), findsOneWidget); + }); + + testWidgets('AlertMessage shows default message when nothing is provided', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + AlertMessage.showStateMessage( + context: context, + stateMessage: const StateMessage( + messageHandler: null, + injectedMessage: null, + showDialog: false, + duration: Duration(seconds: 2), + type: MessageType.info, + ), + ); + }, + child: const Text('Show SnackBar'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Show SnackBar')); + await tester.pumpAndSettle(); + + expect(find.text('This request is not supported'), findsOneWidget); + }); + testWidgets('AlertMessage shows ResponseMessage correctly', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage( + messageHandler: ResponseMessage( + message: ResponseString + .RESPONSE_STRING_AN_ERROR_OCCURRED_WHILE_CONNECTING_TO_THE_SERVER, + ), + injectedMessage: null, + showDialog: false, + duration: const Duration(seconds: 2), + type: MessageType.info, + ), + ); + }, + child: const Text('Show SnackBar'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Show SnackBar')); + await tester.pumpAndSettle(); + + expect( + find.text('An error occurred while connecting to the server.'), + findsOneWidget, + ); + }); +} diff --git a/test/app/shared/constants/urls_test.dart b/test/app/shared/constants/urls_test.dart new file mode 100644 index 000000000..2216211dc --- /dev/null +++ b/test/app/shared/constants/urls_test.dart @@ -0,0 +1,11 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('ethPrice returns correct URL', () { + const symbol = 'ETH'; + const expectedUrl = + 'https://min-api.cryptocompare.com/data/price?fsym=$symbol&tsyms=USD'; + expect(Urls.ethPrice(symbol), expectedUrl); + }); +} diff --git a/test/app/shared/date/date_test.dart b/test/app/shared/date/date_test.dart new file mode 100644 index 000000000..cb447be14 --- /dev/null +++ b/test/app/shared/date/date_test.dart @@ -0,0 +1,84 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAppLocalizations extends Mock implements AppLocalizations { + @override + final localeName = 'en_US'; +} + +void main() { + group('UiDate', () { + group('displayRegionalDate', () { + test('should return formatted date for valid input', () { + const dateString = '2024-05-20'; + final localizations = MockAppLocalizations(); + + final result = UiDate.displayRegionalDate(localizations, dateString); + expect(result, '5/20/2024'); + }); + + test('should return empty string for empty input', () { + final localizations = MockAppLocalizations(); + + final result = UiDate.displayRegionalDate(localizations, ''); + + expect(result, ''); + }); + + test('should return empty string for invalid input', () { + final localizations = MockAppLocalizations(); + + final result = UiDate.displayRegionalDate(localizations, 'invalid'); + + expect(result, ''); + }); + }); + + test('formatStringDate returns formatted date', () { + const dateTime = '2022-05-10T10:00:00Z'; + expect(UiDate.formatStringDate(dateTime), '2022-05-10'); + }); + + test('formatDate returns formatted date', () { + final dateTime = DateTime(2022, 5, 10); + expect(UiDate.formatDate(dateTime), '2022-05-10'); + }); + + test( + 'formatDateForCredentialCard returns formatted date for credential card', + () { + const timestamp = '1643738400'; + expect(UiDate.formatDateForCredentialCard(timestamp), '2022-02-01'); + }); + + group('isTimestampString', () { + test('returns true for valid timestamp string', () { + const timestamp = '1643738400'; + expect(UiDate.isTimestampString(timestamp), true); + }); + + test('returns false for invalid timestamp string', () { + const invalidTimestamp = 'abc'; + expect(UiDate.isTimestampString(invalidTimestamp), false); + }); + }); + + group('normalFormat', () { + test('returns formatted date and time', () { + const dateTime = '2022-05-10T10:00:00Z'; + expect(UiDate.normalFormat(dateTime), '10-05-2022 15:45'); + }); + test('returns null if format is empty', () { + const dateTime = ''; + expect(UiDate.normalFormat(dateTime), null); + }); + + test('returns null if format is incorrect', () { + const dateTime = 'asdf'; + expect(UiDate.normalFormat(dateTime), null); + }); + }); + }); +} diff --git a/test/app/shared/enum/credential_category_test.dart b/test/app/shared/enum/credential_category_test.dart new file mode 100644 index 000000000..32a64652d --- /dev/null +++ b/test/app/shared/enum/credential_category_test.dart @@ -0,0 +1,253 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +class MockAppLocalizations extends Mock implements AppLocalizations { + final my = 'My'; + final get = 'Get'; + + final advantagesCards = 'A1'; + final advantagesCredentialHomeSubtitle = 'A2'; + final advantagesDiscoverCards = 'A3'; + final advantagesCredentialDiscoverSubtitle = 'A4'; + + final identityCards = 'A1'; + final identityCredentialHomeSubtitle = 'A2'; + final identityDiscoverCards = 'A3'; + final identityCredentialDiscoverSubtitle = 'A4'; + + final myProfessionalCards = 'A1'; + final myProfessionalrCardsSubtitle = 'A2'; + final myProfessionalCredentialDiscoverSubtitle = 'A3'; + + final contactInfoCredentials = 'A1'; + final contactInfoCredentialHomeSubtitle = 'A2'; + final contactInfoDiscoverCredentials = 'A3'; + final contactInfoCredentialDiscoverSubtitle = 'A4'; + + final blockchainAccounts = 'A1'; + final blockchainAccountsCredentialHomeSubtitle = 'A2'; + final blockchainCardsDiscoverTitle = 'A3'; + final blockchainCardsDiscoverSubtitle = 'A4'; + + final educationCredentials = 'A1'; + final educationCredentialHomeSubtitle = 'A2'; + final educationDiscoverCredentials = 'A3'; + final educationCredentialsDiscoverSubtitle = 'A4'; + + final otherCards = 'A1'; + final otherCredentialHomeSubtitle = 'A2'; + final otherCredentialDiscoverSubtitle = 'A4'; + + final financeCredentialsHomeTitle = 'A1'; + final financeCredentialsHomeSubtitle = 'A2'; + final financeCredentialsDiscoverTitle = 'A3'; + final financeCredentialsDiscoverSubtitle = 'A4'; + + final hummanityProofCredentialsHomeTitle = 'A1'; + final hummanityProofCredentialsHomeSubtitle = 'A2'; + final hummanityProofCredentialsDiscoverTitle = 'A3'; + final hummanityProofCredentialsDiscoverSubtitle = 'A4'; + + final socialMediaCredentialsHomeTitle = 'A1'; + final socialMediaCredentialsHomeSubtitle = 'A2'; + final socialMediaCredentialsDiscoverTitle = 'A3'; + final socialMediaCredentialsDiscoverSubtitle = 'A4'; + + final walletIntegrityCredentialsHomeTitle = 'A1'; + final walletIntegrityCredentialsHomeSubtitle = 'A2'; + final walletIntegrityCredentialsDiscoverTitle = 'A3'; + final walletIntegrityCredentialsDiscoverSubtitle = 'A4'; + + final polygonCredentialsHomeTitle = 'A1'; + final polygonCredentialsHomeSubtitle = 'A2'; + final polygonCredentialsDiscoverTitle = 'A3'; + final polygonCredentialsDiscoverSubtitle = 'A4'; + + final pendingCredentialsHomeTitle = 'A1'; + final pendingCredentialsHomeSubtitle = 'A2'; +} + +void main() { + test('getCredentialCategorySorted should return categories in sorted order', + () { + final sortedCategories = [ + CredentialCategory.identityCards, + CredentialCategory.professionalCards, + CredentialCategory.contactInfoCredentials, + CredentialCategory.educationCards, + CredentialCategory.financeCards, + CredentialCategory.humanityProofCards, + CredentialCategory.socialMediaCards, + CredentialCategory.walletIntegrity, + CredentialCategory.polygonidCards, + CredentialCategory.blockchainAccountsCards, + CredentialCategory.othersCards, + CredentialCategory.pendingCards, + CredentialCategory.advantagesCards, + ]; + + expect(getCredentialCategorySorted, sortedCategories); + }); + + group('CredentialCategoryExtension', () { + test('order should return correct order for each category', () { + expect(CredentialCategory.advantagesCards.order, 8); + expect(CredentialCategory.identityCards.order, 140); + expect(CredentialCategory.professionalCards.order, 135); + expect(CredentialCategory.contactInfoCredentials.order, 130); + expect(CredentialCategory.blockchainAccountsCards.order, 70); + expect(CredentialCategory.educationCards.order, 120); + expect(CredentialCategory.othersCards.order, 10); + expect(CredentialCategory.financeCards.order, 110); + expect(CredentialCategory.humanityProofCards.order, 100); + expect(CredentialCategory.socialMediaCards.order, 90); + expect(CredentialCategory.walletIntegrity.order, 80); + expect(CredentialCategory.polygonidCards.order, 75); + expect(CredentialCategory.pendingCards.order, 9); + }); + + test('showInHomeIfListEmpty should return correct value for each category', + () { + expect(CredentialCategory.identityCards.showInHomeIfListEmpty, true); + expect(CredentialCategory.professionalCards.showInHomeIfListEmpty, false); + expect(CredentialCategory.advantagesCards.showInHomeIfListEmpty, false); + expect(CredentialCategory.contactInfoCredentials.showInHomeIfListEmpty, + false); + expect(CredentialCategory.blockchainAccountsCards.showInHomeIfListEmpty, + false); + expect(CredentialCategory.educationCards.showInHomeIfListEmpty, false); + expect(CredentialCategory.othersCards.showInHomeIfListEmpty, false); + expect(CredentialCategory.financeCards.showInHomeIfListEmpty, false); + expect( + CredentialCategory.humanityProofCards.showInHomeIfListEmpty, false); + expect(CredentialCategory.socialMediaCards.showInHomeIfListEmpty, false); + expect(CredentialCategory.walletIntegrity.showInHomeIfListEmpty, false); + expect(CredentialCategory.polygonidCards.showInHomeIfListEmpty, false); + expect(CredentialCategory.pendingCards.showInHomeIfListEmpty, false); + }); + + test('config should return correct configuration for each category', () { + final localizations = MockAppLocalizations(); + + expect( + CredentialCategory.advantagesCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'My a1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.identityCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'My a1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.professionalCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'My a1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A3', + ), + ); + expect( + CredentialCategory.contactInfoCredentials.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'My a1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.blockchainAccountsCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'My a1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.educationCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'My a1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.othersCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'My a1', + homeSubTitle: 'A2', + discoverTitle: 'Get a1', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.financeCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'A1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.humanityProofCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'A1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.socialMediaCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'A1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.walletIntegrity.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'A1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.polygonidCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'A1', + homeSubTitle: 'A2', + discoverTitle: 'A3', + discoverSubTitle: 'A4', + ), + ); + expect( + CredentialCategory.pendingCards.config(localizations), + const CredentialCategoryConfig( + homeTitle: 'A1', + homeSubTitle: 'A2', + discoverTitle: '', + discoverSubTitle: '', + ), + ); + }); + }); +} diff --git a/test/app/shared/enum/credential_subject_type/credential_subject_type_extension_test.dart b/test/app/shared/enum/credential_subject_type/credential_subject_type_extension_test.dart new file mode 100644 index 000000000..df2f5615d --- /dev/null +++ b/test/app/shared/enum/credential_subject_type/credential_subject_type_extension_test.dart @@ -0,0 +1,952 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:flutter/material.dart'; +import 'package:oidc4vc/oidc4vc.dart'; +import 'package:test/test.dart'; + +void main() { + group('CredentialSubjectType Extension Tests', () { + test('CredentialSubjectType backgroundColor returns correct value', () { + final credentialModel = CredentialModel( + id: '', + credentialPreview: Credential.dummy(), + data: const {}, + image: '', + shareLink: '', + display: const Display(backgroundColor: '#FFFFFF'), + ); + final credentialModel2 = CredentialModel( + id: '', + credentialPreview: Credential.dummy(), + data: const {}, + image: '', + shareLink: '', + ); + + for (final value in CredentialSubjectType.values) { + expect(value.backgroundColor(credentialModel), + equals(const Color(0xFFFFFFFF))); + expect(value.backgroundColor(credentialModel2), + equals(value.defaultBackgroundColor)); + } + }); + + test('CredentialSubjectType defaultBackgroundColor returns correct value', + () { + expect( + CredentialSubjectType.defiCompliance.defaultBackgroundColor, + equals(const Color.fromARGB(255, 62, 15, 163)), + ); + + expect( + CredentialSubjectType.identityPass.defaultBackgroundColor, + equals(const Color(0xffCAFFBF)), + ); + + expect( + CredentialSubjectType + .professionalExperienceAssessment.defaultBackgroundColor, + equals(const Color(0xFFFFADAD)), + ); + + expect( + CredentialSubjectType + .professionalSkillAssessment.defaultBackgroundColor, + equals(const Color(0xffCAFFBF)), + ); + + expect( + CredentialSubjectType.residentCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.selfIssued.defaultBackgroundColor, + equals(const Color(0xffEFF0F6)), + ); + + expect( + CredentialSubjectType.defaultCredential.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.professionalStudentCard.defaultBackgroundColor, + equals(const Color(0xffCAFFBF)), + ); + + expect( + CredentialSubjectType.kycAgeCredential.defaultBackgroundColor, + equals(const Color(0xff8247E5)), + ); + + expect( + CredentialSubjectType.kycCountryOfResidence.defaultBackgroundColor, + equals(const Color(0xff8247E5)), + ); + + expect( + CredentialSubjectType.walletCredential.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.livenessCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.nationality.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.tezotopiaMembership.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.chainbornMembership.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.twitterCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.gender.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.tezosAssociatedWallet.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.verifiableIdCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.linkedInCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.over13.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.over15.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.over18.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.over21.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.over50.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.over65.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.passportFootprint.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.certificateOfEmployment.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.emailPass.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.ageRange.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.phonePass.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.learningAchievement.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.studentCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.voucher.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.tezVoucher.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.diplomaCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.aragoPass.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.aragoIdentityCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.aragoLearningAchievement.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.aragoEmailPass.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.aragoOver18.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.ethereumAssociatedWallet.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.fantomAssociatedWallet.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.polygonAssociatedWallet.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.binanceAssociatedWallet.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.ethereumPooAddress.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.fantomPooAddress.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.polygonPooAddress.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.binancePooAddress.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.tezosPooAddress.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.pcdsAgentCertificate.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.euDiplomaCard.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.euVerifiableId.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.proofOfTwitterStats.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.civicPassCredential.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.employeeCredential.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.legalPersonalCredential.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.identityCredential.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.eudiPid.defaultBackgroundColor, + equals(Colors.white), + ); + + expect( + CredentialSubjectType.pid.defaultBackgroundColor, + equals(Colors.white), + ); + }); + + test('CredentialSubjectType name returns correct value', () { + expect( + CredentialSubjectType.defiCompliance.name, equals('DefiCompliance')); + expect(CredentialSubjectType.livenessCard.name, equals('Liveness')); + expect(CredentialSubjectType.tezotopiaMembership.name, + equals('MembershipCard_1')); + expect(CredentialSubjectType.chainbornMembership.name, + equals('Chainborn_MembershipCard')); + expect(CredentialSubjectType.twitterCard.name, + equals('TwitterAccountProof')); + expect(CredentialSubjectType.ageRange.name, equals('AgeRange')); + expect(CredentialSubjectType.nationality.name, equals('Nationality')); + expect(CredentialSubjectType.gender.name, equals('Gender')); + expect(CredentialSubjectType.walletCredential.name, + equals('WalletCredential')); + expect(CredentialSubjectType.tezosAssociatedWallet.name, + equals('TezosAssociatedAddress')); + expect(CredentialSubjectType.ethereumAssociatedWallet.name, + equals('EthereumAssociatedAddress')); + expect(CredentialSubjectType.fantomAssociatedWallet.name, + equals('FantomAssociatedAddress')); + expect(CredentialSubjectType.polygonAssociatedWallet.name, + equals('PolygonAssociatedAddress')); + expect(CredentialSubjectType.binanceAssociatedWallet.name, + equals('BinanceAssociatedAddress')); + expect(CredentialSubjectType.ethereumPooAddress.name, + equals('EthereumPooAddress')); + expect(CredentialSubjectType.fantomPooAddress.name, + equals('FantomPooAddress')); + expect(CredentialSubjectType.polygonPooAddress.name, + equals('PolygonPooAddress')); + expect(CredentialSubjectType.binancePooAddress.name, + equals('BinancePooAddress')); + expect(CredentialSubjectType.tezosPooAddress.name, + equals('TezosPooAddress')); + expect(CredentialSubjectType.certificateOfEmployment.name, + equals('CertificateOfEmployment')); + expect(CredentialSubjectType.emailPass.name, equals('EmailPass')); + expect(CredentialSubjectType.identityPass.name, equals('IdentityPass')); + expect( + CredentialSubjectType.verifiableIdCard.name, equals('VerifiableId')); + expect(CredentialSubjectType.linkedInCard.name, equals('LinkedinCard')); + expect(CredentialSubjectType.learningAchievement.name, + equals('LearningAchievement')); + expect(CredentialSubjectType.over13.name, equals('Over13')); + expect(CredentialSubjectType.over15.name, equals('Over15')); + expect(CredentialSubjectType.over18.name, equals('Over18')); + expect(CredentialSubjectType.over21.name, equals('Over21')); + expect(CredentialSubjectType.over50.name, equals('Over50')); + expect(CredentialSubjectType.over65.name, equals('Over65')); + expect(CredentialSubjectType.passportFootprint.name, + equals('PassportNumber')); + expect(CredentialSubjectType.phonePass.name, equals('PhoneProof')); + expect(CredentialSubjectType.professionalExperienceAssessment.name, + equals('ProfessionalExperienceAssessment')); + expect(CredentialSubjectType.professionalSkillAssessment.name, + equals('ProfessionalSkillAssessment')); + expect(CredentialSubjectType.professionalStudentCard.name, + equals('ProfessionalStudentCard')); + expect(CredentialSubjectType.residentCard.name, equals('ResidentCard')); + expect(CredentialSubjectType.employeeCredential.name, + equals('EmployeeCredential')); + expect(CredentialSubjectType.legalPersonalCredential.name, + equals('LegalPersonCredential')); + expect(CredentialSubjectType.selfIssued.name, equals('SelfIssued')); + expect(CredentialSubjectType.studentCard.name, equals('StudentCard')); + expect(CredentialSubjectType.voucher.name, equals('Voucher')); + expect(CredentialSubjectType.tezVoucher.name, equals('TezVoucher_1')); + expect( + CredentialSubjectType.diplomaCard.name, equals('VerifiableDiploma')); + expect(CredentialSubjectType.aragoPass.name, equals('AragoPass')); + expect( + CredentialSubjectType.aragoEmailPass.name, equals('AragoEmailPass')); + expect( + CredentialSubjectType.aragoIdentityCard.name, equals('AragoIdCard')); + expect(CredentialSubjectType.aragoLearningAchievement.name, + equals('AragoLearningAchievement')); + expect(CredentialSubjectType.aragoOver18.name, equals('AragoOver18')); + expect(CredentialSubjectType.pcdsAgentCertificate.name, + equals('PCDSAgentCertificate')); + expect( + CredentialSubjectType.euDiplomaCard.name, + equals( + 'https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd')); + expect( + CredentialSubjectType.euVerifiableId.name, + equals( + 'https://api-conformance.ebsi.eu/trusted-schemas-registry/v2/schemas/z22ZAMdQtNLwi51T2vdZXGGZaYyjrsuP1yzWyXZirCAHv')); + expect(CredentialSubjectType.kycAgeCredential.name, + equals('KYCAgeCredential')); + expect(CredentialSubjectType.kycCountryOfResidence.name, + equals('KYCCountryOfResidenceCredential')); + expect(CredentialSubjectType.proofOfTwitterStats.name, + equals('ProofOfTwitterStats')); + expect(CredentialSubjectType.civicPassCredential.name, + equals('CivicPassCredential')); + expect(CredentialSubjectType.identityCredential.name, + equals('IdentityCredential')); + expect(CredentialSubjectType.eudiPid.name, equals('EudiPid')); + expect(CredentialSubjectType.pid.name, equals('Pid')); + expect(CredentialSubjectType.defaultCredential.name, equals('')); + }); + + test('CredentialSubjectType checkForAIKYC returns correct value', () { + for (final value in CredentialSubjectType.values) { + if (value == CredentialSubjectType.over18 || + value == CredentialSubjectType.over13 || + value == CredentialSubjectType.over15 || + value == CredentialSubjectType.over21 || + value == CredentialSubjectType.over50 || + value == CredentialSubjectType.over65 || + value == CredentialSubjectType.ageRange || + value == CredentialSubjectType.defiCompliance || + value == CredentialSubjectType.livenessCard) { + expect(value.checkForAIKYC, isTrue); + } else { + expect(value.checkForAIKYC, isFalse); + } + } + }); + + test('CredentialSubjectType getKycVcType returns correct value', () { + for (final value in CredentialSubjectType.values) { + if (value == CredentialSubjectType.over18) { + expect(value.getKycVcType, equals(KycVcType.over18)); + } else if (value == CredentialSubjectType.over13) { + expect(value.getKycVcType, equals(KycVcType.over13)); + } else if (value == CredentialSubjectType.over15) { + expect(value.getKycVcType, equals(KycVcType.over15)); + } else if (value == CredentialSubjectType.over21) { + expect(value.getKycVcType, equals(KycVcType.over21)); + } else if (value == CredentialSubjectType.over50) { + expect(value.getKycVcType, equals(KycVcType.over50)); + } else if (value == CredentialSubjectType.over65) { + expect(value.getKycVcType, equals(KycVcType.over65)); + } else if (value == CredentialSubjectType.ageRange) { + expect(value.getKycVcType, equals(KycVcType.ageRange)); + } else if (value == CredentialSubjectType.defiCompliance) { + expect(value.getKycVcType, equals(KycVcType.defiCompliance)); + } else { + expect(value.getKycVcType, equals(KycVcType.verifiableId)); + } + } + }); + + test('CredentialSubjectType aiValidationUrl returns correct value', () { + for (final value in CredentialSubjectType.values) { + if (value == CredentialSubjectType.over13) { + expect(value.aiValidationUrl, equals(Urls.over13AIValidationUrl)); + } else if (value == CredentialSubjectType.over15) { + expect(value.aiValidationUrl, equals(Urls.over15AIValidationUrl)); + } else if (value == CredentialSubjectType.over18) { + expect(value.aiValidationUrl, equals(Urls.over18AIValidationUrl)); + } else if (value == CredentialSubjectType.over21) { + expect(value.aiValidationUrl, equals(Urls.over21AIValidationUrl)); + } else if (value == CredentialSubjectType.over50) { + expect(value.aiValidationUrl, equals(Urls.over50AIValidationUrl)); + } else if (value == CredentialSubjectType.over65) { + expect(value.aiValidationUrl, equals(Urls.over65AIValidationUrl)); + } else if (value == CredentialSubjectType.ageRange) { + expect(value.aiValidationUrl, equals(Urls.ageRangeAIValidationUrl)); + } else { + expect( + () => CredentialSubjectType.defaultCredential.aiValidationUrl, + throwsA( + predicate( + (e) => + e is ResponseMessage && + e.data['error'] == 'invalid_request', + ), + ), + ); + } + } + }); + + test('CredentialSubjectType byPassDeepLink returns correct value', () { + for (final value in CredentialSubjectType.values) { + if (value == CredentialSubjectType.tezotopiaMembership || + value == CredentialSubjectType.chainbornMembership || + value == CredentialSubjectType.twitterCard || + value == CredentialSubjectType.over13 || + value == CredentialSubjectType.over15 || + value == CredentialSubjectType.over18 || + value == CredentialSubjectType.over21 || + value == CredentialSubjectType.over50 || + value == CredentialSubjectType.over65 || + value == CredentialSubjectType.verifiableIdCard || + value == CredentialSubjectType.ageRange || + value == CredentialSubjectType.nationality || + value == CredentialSubjectType.gender || + value == CredentialSubjectType.passportFootprint || + value == CredentialSubjectType.linkedInCard) { + expect(value.byPassDeepLink, isTrue); + } else { + expect(value.byPassDeepLink, isFalse); + } + } + }); + + test('CredentialSubjectType isEbsiCard returns correct value', () { + for (final value in CredentialSubjectType.values) { + if (value == CredentialSubjectType.euDiplomaCard || + value == CredentialSubjectType.euVerifiableId) { + expect(value.isEbsiCard, isTrue); + } else { + expect(value.isEbsiCard, isFalse); + } + } + }); + + test('CredentialSubjectType isBlockchainAccount returns correct value', () { + for (final value in CredentialSubjectType.values) { + if (value == CredentialSubjectType.tezosAssociatedWallet || + value == CredentialSubjectType.ethereumAssociatedWallet || + value == CredentialSubjectType.binanceAssociatedWallet || + value == CredentialSubjectType.fantomAssociatedWallet || + value == CredentialSubjectType.polygonAssociatedWallet) { + expect(value.isBlockchainAccount, isTrue); + } else { + expect(value.isBlockchainAccount, isFalse); + } + } + }); + + test('CredentialSubjectType blockchainWidget returns correct value', () { + for (final value in CredentialSubjectType.values) { + if (value == CredentialSubjectType.tezosAssociatedWallet) { + expect(value.blockchainWidget, isA()); + } else if (value == CredentialSubjectType.ethereumAssociatedWallet) { + expect( + value.blockchainWidget, isA()); + } else if (value == CredentialSubjectType.polygonAssociatedWallet) { + expect(value.blockchainWidget, isA()); + } else if (value == CredentialSubjectType.binanceAssociatedWallet) { + expect(value.blockchainWidget, isA()); + } else if (value == CredentialSubjectType.fantomAssociatedWallet) { + expect(value.blockchainWidget, isA()); + } else { + expect(value.blockchainWidget, isNull); + } + } + }); + + test('CredentialSubjectType title returns correct value', () { + expect(CredentialSubjectType.defiCompliance.title, 'Defi Compliance'); + expect(CredentialSubjectType.livenessCard.title, 'Liveness'); + expect( + CredentialSubjectType.tezotopiaMembership.title, 'Membership Card'); + expect(CredentialSubjectType.chainbornMembership.title, 'Chainborn'); + expect(CredentialSubjectType.twitterCard.title, 'Twitter Account Proof'); + expect(CredentialSubjectType.ageRange.title, 'Age Range'); + expect(CredentialSubjectType.nationality.title, 'Nationality'); + expect(CredentialSubjectType.gender.title, 'Gender'); + expect(CredentialSubjectType.walletCredential.title, 'Wallet Credential'); + expect(CredentialSubjectType.tezosAssociatedWallet.title, + 'Tezos Associated Address'); + expect(CredentialSubjectType.ethereumAssociatedWallet.title, + 'Ethereum Associated Address'); + expect(CredentialSubjectType.fantomAssociatedWallet.title, + 'Fantom Associated Address'); + expect(CredentialSubjectType.polygonAssociatedWallet.title, + 'Polygon Associated Address'); + expect(CredentialSubjectType.binanceAssociatedWallet.title, + 'BNB Chain Associated Address'); + expect(CredentialSubjectType.ethereumPooAddress.title, + 'Ethereum Poo Address'); + expect( + CredentialSubjectType.fantomPooAddress.title, 'Fantom Poo Address'); + expect( + CredentialSubjectType.polygonPooAddress.title, 'Polygon Poo Address'); + expect(CredentialSubjectType.binancePooAddress.title, + 'BNB Chain Poo Address'); + expect(CredentialSubjectType.tezosPooAddress.title, 'Tezos Poo Address'); + expect(CredentialSubjectType.certificateOfEmployment.title, + 'Certificate of Employment'); + expect(CredentialSubjectType.emailPass.title, 'Email Pass'); + expect(CredentialSubjectType.identityPass.title, 'Identity Pass'); + expect(CredentialSubjectType.verifiableIdCard.title, 'VerifiableId'); + expect(CredentialSubjectType.linkedInCard.title, 'Linkedin Card'); + expect(CredentialSubjectType.learningAchievement.title, + 'Learning Achievement'); + expect(CredentialSubjectType.over13.title, 'Over13'); + expect(CredentialSubjectType.over15.title, 'Over15'); + expect(CredentialSubjectType.over18.title, 'Over18'); + expect(CredentialSubjectType.over21.title, 'Over18'); + expect(CredentialSubjectType.over50.title, 'Over18'); + expect(CredentialSubjectType.over65.title, 'Over18'); + expect(CredentialSubjectType.passportFootprint.title, 'Passport Number'); + expect(CredentialSubjectType.phonePass.title, 'Phone Proof'); + expect(CredentialSubjectType.professionalExperienceAssessment.title, + 'Professional Experience Assessment'); + expect(CredentialSubjectType.professionalSkillAssessment.title, + 'Professional Skill Assessment'); + expect(CredentialSubjectType.professionalStudentCard.title, + 'Professional Student Card'); + expect(CredentialSubjectType.residentCard.title, 'Resident Card'); + expect(CredentialSubjectType.selfIssued.title, 'Self Issued'); + expect(CredentialSubjectType.studentCard.title, 'Student Card'); + expect(CredentialSubjectType.voucher.title, 'Voucher'); + expect(CredentialSubjectType.tezVoucher.title, 'TezVoucher'); + expect(CredentialSubjectType.diplomaCard.title, 'Verifiable Diploma'); + expect(CredentialSubjectType.aragoPass.title, 'Arago Pass'); + expect(CredentialSubjectType.aragoEmailPass.title, 'Arago Email Pass'); + expect(CredentialSubjectType.aragoIdentityCard.title, 'Arago Id Card'); + expect(CredentialSubjectType.aragoLearningAchievement.title, + 'Arago Learning Achievement'); + expect(CredentialSubjectType.aragoOver18.title, 'Arago Over18'); + expect(CredentialSubjectType.pcdsAgentCertificate.title, + 'PCDS Agent Certificate'); + expect(CredentialSubjectType.euDiplomaCard.title, 'EU Diploma'); + expect(CredentialSubjectType.euVerifiableId.title, 'EU VerifiableID'); + expect( + CredentialSubjectType.kycAgeCredential.title, 'KYC Age Credential'); + expect(CredentialSubjectType.kycCountryOfResidence.title, + 'KYC Country of Residence'); + expect(CredentialSubjectType.proofOfTwitterStats.title, + 'Proof Of Twitter Stats'); + expect(CredentialSubjectType.civicPassCredential.title, + 'Civic Pass Credential'); + expect(CredentialSubjectType.employeeCredential.title, + 'Employee Credential'); + expect(CredentialSubjectType.legalPersonalCredential.title, + 'Legal Person Credential'); + expect(CredentialSubjectType.identityCredential.title, + 'Identity Credential'); + expect(CredentialSubjectType.eudiPid.title, 'EudiPid'); + expect(CredentialSubjectType.pid.title, 'Pid'); + expect(CredentialSubjectType.defaultCredential.title, ''); + }); + + test('CredentialSubjectType supportSingleOnly returns correct value', () { + expect(CredentialSubjectType.defiCompliance.supportSingleOnly, true); + expect(CredentialSubjectType.livenessCard.supportSingleOnly, true); + expect(CredentialSubjectType.tezotopiaMembership.supportSingleOnly, true); + expect(CredentialSubjectType.chainbornMembership.supportSingleOnly, true); + expect(CredentialSubjectType.ageRange.supportSingleOnly, true); + expect(CredentialSubjectType.nationality.supportSingleOnly, true); + expect(CredentialSubjectType.gender.supportSingleOnly, true); + expect(CredentialSubjectType.identityPass.supportSingleOnly, true); + expect(CredentialSubjectType.verifiableIdCard.supportSingleOnly, true); + expect(CredentialSubjectType.over13.supportSingleOnly, true); + expect(CredentialSubjectType.over15.supportSingleOnly, true); + expect(CredentialSubjectType.over18.supportSingleOnly, true); + expect(CredentialSubjectType.over21.supportSingleOnly, true); + expect(CredentialSubjectType.over50.supportSingleOnly, true); + expect(CredentialSubjectType.over65.supportSingleOnly, true); + expect(CredentialSubjectType.passportFootprint.supportSingleOnly, true); + expect(CredentialSubjectType.residentCard.supportSingleOnly, true); + expect(CredentialSubjectType.voucher.supportSingleOnly, true); + expect(CredentialSubjectType.tezVoucher.supportSingleOnly, true); + expect(CredentialSubjectType.diplomaCard.supportSingleOnly, true); + expect(CredentialSubjectType.twitterCard.supportSingleOnly, true); + expect( + CredentialSubjectType.tezosAssociatedWallet.supportSingleOnly, true); + expect(CredentialSubjectType.ethereumAssociatedWallet.supportSingleOnly, + true); + expect( + CredentialSubjectType.fantomAssociatedWallet.supportSingleOnly, true); + expect(CredentialSubjectType.polygonAssociatedWallet.supportSingleOnly, + true); + expect(CredentialSubjectType.binanceAssociatedWallet.supportSingleOnly, + true); + + expect(CredentialSubjectType.walletCredential.supportSingleOnly, false); + expect(CredentialSubjectType.tezosPooAddress.supportSingleOnly, false); + expect(CredentialSubjectType.ethereumPooAddress.supportSingleOnly, false); + expect(CredentialSubjectType.fantomPooAddress.supportSingleOnly, false); + expect(CredentialSubjectType.polygonPooAddress.supportSingleOnly, false); + expect(CredentialSubjectType.binancePooAddress.supportSingleOnly, false); + expect(CredentialSubjectType.certificateOfEmployment.supportSingleOnly, + false); + expect(CredentialSubjectType.defaultCredential.supportSingleOnly, false); + expect(CredentialSubjectType.emailPass.supportSingleOnly, false); + expect(CredentialSubjectType.linkedInCard.supportSingleOnly, false); + expect( + CredentialSubjectType.learningAchievement.supportSingleOnly, false); + expect(CredentialSubjectType.phonePass.supportSingleOnly, false); + expect( + CredentialSubjectType + .professionalExperienceAssessment.supportSingleOnly, + false); + expect( + CredentialSubjectType.professionalSkillAssessment.supportSingleOnly, + false); + expect(CredentialSubjectType.professionalStudentCard.supportSingleOnly, + false); + expect(CredentialSubjectType.selfIssued.supportSingleOnly, false); + expect(CredentialSubjectType.studentCard.supportSingleOnly, false); + expect(CredentialSubjectType.aragoPass.supportSingleOnly, false); + expect(CredentialSubjectType.aragoEmailPass.supportSingleOnly, false); + expect(CredentialSubjectType.aragoIdentityCard.supportSingleOnly, false); + expect(CredentialSubjectType.aragoLearningAchievement.supportSingleOnly, + false); + expect(CredentialSubjectType.aragoOver18.supportSingleOnly, false); + expect( + CredentialSubjectType.pcdsAgentCertificate.supportSingleOnly, false); + expect(CredentialSubjectType.euDiplomaCard.supportSingleOnly, false); + expect(CredentialSubjectType.euVerifiableId.supportSingleOnly, false); + expect(CredentialSubjectType.kycAgeCredential.supportSingleOnly, false); + expect( + CredentialSubjectType.kycCountryOfResidence.supportSingleOnly, false); + expect( + CredentialSubjectType.proofOfTwitterStats.supportSingleOnly, false); + expect( + CredentialSubjectType.civicPassCredential.supportSingleOnly, false); + expect(CredentialSubjectType.employeeCredential.supportSingleOnly, false); + expect(CredentialSubjectType.legalPersonalCredential.supportSingleOnly, + false); + expect(CredentialSubjectType.identityCredential.supportSingleOnly, false); + expect(CredentialSubjectType.eudiPid.supportSingleOnly, false); + expect(CredentialSubjectType.pid.supportSingleOnly, false); + }); + + test('CredentialSubjectType getVCFormatType returns correct value', () { + expect(CredentialSubjectType.ethereumAssociatedWallet.getVCFormatType, + VCFormatType.values); + expect(CredentialSubjectType.fantomAssociatedWallet.getVCFormatType, + VCFormatType.values); + expect(CredentialSubjectType.polygonAssociatedWallet.getVCFormatType, + VCFormatType.values); + expect(CredentialSubjectType.binanceAssociatedWallet.getVCFormatType, + VCFormatType.values); + expect(CredentialSubjectType.tezosAssociatedWallet.getVCFormatType, + VCFormatType.values); + + expect( + CredentialSubjectType.over13.getVCFormatType, [VCFormatType.ldpVc]); + expect( + CredentialSubjectType.over15.getVCFormatType, [VCFormatType.ldpVc]); + expect( + CredentialSubjectType.over21.getVCFormatType, [VCFormatType.ldpVc]); + expect( + CredentialSubjectType.over50.getVCFormatType, [VCFormatType.ldpVc]); + expect( + CredentialSubjectType.over65.getVCFormatType, [VCFormatType.ldpVc]); + expect( + CredentialSubjectType.gender.getVCFormatType, [VCFormatType.ldpVc]); + expect( + CredentialSubjectType.ageRange.getVCFormatType, [VCFormatType.ldpVc]); + expect(CredentialSubjectType.defiCompliance.getVCFormatType, + [VCFormatType.ldpVc]); + expect(CredentialSubjectType.tezotopiaMembership.getVCFormatType, + [VCFormatType.ldpVc]); + expect(CredentialSubjectType.chainbornMembership.getVCFormatType, + [VCFormatType.ldpVc]); + + expect(CredentialSubjectType.verifiableIdCard.getVCFormatType, [ + VCFormatType.ldpVc, + VCFormatType.jwtVcJson, + VCFormatType.vcSdJWT, + VCFormatType.jwtVc, + ]); + + expect(CredentialSubjectType.identityCredential.getVCFormatType, + [VCFormatType.vcSdJWT]); + expect(CredentialSubjectType.eudiPid.getVCFormatType, + [VCFormatType.vcSdJWT]); + expect(CredentialSubjectType.pid.getVCFormatType, [VCFormatType.vcSdJWT]); + + expect(CredentialSubjectType.over18.getVCFormatType, [ + VCFormatType.ldpVc, + VCFormatType.jwtVcJson, + ]); + + expect(CredentialSubjectType.phonePass.getVCFormatType, + [VCFormatType.ldpVc, VCFormatType.jwtVcJson]); + expect(CredentialSubjectType.livenessCard.getVCFormatType, + [VCFormatType.ldpVc, VCFormatType.jwtVcJson]); + expect(CredentialSubjectType.emailPass.getVCFormatType, + [VCFormatType.ldpVc, VCFormatType.jwtVcJson]); + + expect(CredentialSubjectType.nationality.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.identityPass.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.passportFootprint.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.residentCard.getVCFormatType, + [VCFormatType.jwtVc]); + expect( + CredentialSubjectType.voucher.getVCFormatType, [VCFormatType.jwtVc]); + expect(CredentialSubjectType.tezVoucher.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.diplomaCard.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.twitterCard.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.walletCredential.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.tezosPooAddress.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.ethereumPooAddress.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.fantomPooAddress.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.polygonPooAddress.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.binancePooAddress.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.certificateOfEmployment.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.defaultCredential.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.linkedInCard.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.learningAchievement.getVCFormatType, + [VCFormatType.jwtVc]); + expect( + CredentialSubjectType + .professionalExperienceAssessment.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.professionalSkillAssessment.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.professionalStudentCard.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.selfIssued.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.studentCard.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.aragoPass.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.aragoEmailPass.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.aragoIdentityCard.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.aragoLearningAchievement.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.aragoOver18.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.pcdsAgentCertificate.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.euDiplomaCard.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.euVerifiableId.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.kycAgeCredential.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.kycCountryOfResidence.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.proofOfTwitterStats.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.civicPassCredential.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.employeeCredential.getVCFormatType, + [VCFormatType.jwtVc]); + expect(CredentialSubjectType.legalPersonalCredential.getVCFormatType, + [VCFormatType.jwtVc]); + }); + + test('CredentialSubjectType order returns correct value', () { + expect(CredentialSubjectType.defiCompliance.order, 0); + expect(CredentialSubjectType.livenessCard.order, 75); + expect(CredentialSubjectType.tezotopiaMembership.order, 79); + expect(CredentialSubjectType.chainbornMembership.order, 72); + expect(CredentialSubjectType.ageRange.order, 94); + expect(CredentialSubjectType.nationality.order, 97.3); + expect(CredentialSubjectType.gender.order, 93); + expect(CredentialSubjectType.walletCredential.order, 0); + expect(CredentialSubjectType.tezosAssociatedWallet.order, 68); + expect(CredentialSubjectType.ethereumAssociatedWallet.order, 69); + expect(CredentialSubjectType.fantomAssociatedWallet.order, 67); + expect(CredentialSubjectType.polygonAssociatedWallet.order, 71); + expect(CredentialSubjectType.binanceAssociatedWallet.order, 70); + expect(CredentialSubjectType.tezosPooAddress.order, 0); + expect(CredentialSubjectType.ethereumPooAddress.order, 0); + expect(CredentialSubjectType.fantomPooAddress.order, 0); + expect(CredentialSubjectType.polygonPooAddress.order, 0); + expect(CredentialSubjectType.binancePooAddress.order, 0); + expect(CredentialSubjectType.certificateOfEmployment.order, 85); + expect(CredentialSubjectType.defaultCredential.order, 100); + expect(CredentialSubjectType.emailPass.order, 99); + expect(CredentialSubjectType.identityPass.order, 90); + expect(CredentialSubjectType.verifiableIdCard.order, 97.5); + expect(CredentialSubjectType.linkedInCard.order, 86); + expect(CredentialSubjectType.learningAchievement.order, 0); + expect(CredentialSubjectType.over13.order, 97.3); + expect(CredentialSubjectType.over15.order, 97.2); + expect(CredentialSubjectType.over18.order, 97.1); + expect(CredentialSubjectType.over21.order, 97); + expect(CredentialSubjectType.over50.order, 96); + expect(CredentialSubjectType.over65.order, 95); + expect(CredentialSubjectType.passportFootprint.order, 91); + expect(CredentialSubjectType.phonePass.order, 98); + expect(CredentialSubjectType.professionalExperienceAssessment.order, 0); + expect(CredentialSubjectType.professionalSkillAssessment.order, 0); + expect(CredentialSubjectType.professionalStudentCard.order, 87); + expect(CredentialSubjectType.residentCard.order, 0); + expect(CredentialSubjectType.selfIssued.order, 0); + expect(CredentialSubjectType.studentCard.order, 88); + expect(CredentialSubjectType.voucher.order, 81); + expect(CredentialSubjectType.tezVoucher.order, 80); + expect(CredentialSubjectType.diplomaCard.order, 89); + expect(CredentialSubjectType.aragoPass.order, 81); + expect(CredentialSubjectType.aragoEmailPass.order, 0); + expect(CredentialSubjectType.aragoIdentityCard.order, 0); + expect(CredentialSubjectType.aragoLearningAchievement.order, 0); + expect(CredentialSubjectType.aragoOver18.order, 0); + expect(CredentialSubjectType.pcdsAgentCertificate.order, 82); + expect(CredentialSubjectType.twitterCard.order, 83); + expect(CredentialSubjectType.euDiplomaCard.order, 67); + expect(CredentialSubjectType.euVerifiableId.order, 92); + expect(CredentialSubjectType.kycAgeCredential.order, 0); + expect(CredentialSubjectType.kycCountryOfResidence.order, 0); + expect(CredentialSubjectType.proofOfTwitterStats.order, 0); + expect(CredentialSubjectType.civicPassCredential.order, 0); + expect(CredentialSubjectType.employeeCredential.order, 0); + expect(CredentialSubjectType.legalPersonalCredential.order, 0); + expect(CredentialSubjectType.identityCredential.order, 0); + expect(CredentialSubjectType.eudiPid.order, 0); + expect(CredentialSubjectType.pid.order, 0); + }); + }); +} diff --git a/test/app/shared/enum/polygon_id_network_test.dart b/test/app/shared/enum/polygon_id_network_test.dart new file mode 100644 index 000000000..54f309b20 --- /dev/null +++ b/test/app/shared/enum/polygon_id_network_test.dart @@ -0,0 +1,22 @@ +import 'package:altme/app/app.dart'; +import 'package:test/test.dart'; + +void main() { + group('PolygonIdNetwork Extension Tests', () { + test('name should return correct network name', () { + expect(PolygonIdNetwork.PolygonMainnet.name, 'Polygon Main'); + expect(PolygonIdNetwork.PolygonMumbai.name, 'Polygon Mumbai'); + }); + + test('tester should return correct tester string', () { + expect(PolygonIdNetwork.PolygonMainnet.tester, 'polygon:main'); + expect(PolygonIdNetwork.PolygonMumbai.tester, 'polygon:mumbai'); + }); + + test('oppositeNetwork should return correct opposite network', () { + expect( + PolygonIdNetwork.PolygonMainnet.oppositeNetwork, 'mumbai(testnet)'); + expect(PolygonIdNetwork.PolygonMumbai.oppositeNetwork, 'mainnet'); + }); + }); +} diff --git a/test/app/shared/enum/status/credential_status_extension_test.dart b/test/app/shared/enum/status/credential_status_extension_test.dart new file mode 100644 index 000000000..77fc84dd8 --- /dev/null +++ b/test/app/shared/enum/status/credential_status_extension_test.dart @@ -0,0 +1,19 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:test/test.dart'; + +void main() { + group('CredentialStatusExtension', () { + test('icon returns correct icon for each status', () { + expect(CredentialStatus.active.icon, Icons.check_circle); + expect(CredentialStatus.invalidStatus.icon, Icons.circle_outlined); + expect(CredentialStatus.expired.icon, Icons.circle_outlined); + expect(CredentialStatus.pending.icon, Icons.circle_outlined); + expect(CredentialStatus.unknown.icon, Icons.circle_outlined); + expect(CredentialStatus.invalidSignature.icon, Icons.circle_outlined); + expect(CredentialStatus.statusListInvalidSignature.icon, + Icons.circle_outlined); + expect(CredentialStatus.noStatus.icon, Icons.circle_outlined); + }); + }); +} diff --git a/test/app/shared/enum/status/mnemonic_status.dart b/test/app/shared/enum/status/mnemonic_status.dart new file mode 100644 index 000000000..47d3ae4a6 --- /dev/null +++ b/test/app/shared/enum/status/mnemonic_status.dart @@ -0,0 +1,30 @@ +import 'dart:ui'; + +enum MnemonicStatus { + unselected, + selected, + wrongSelection, +} + +extension MnemonicStatusX on MnemonicStatus { + bool get showOrder { + switch (this) { + case MnemonicStatus.unselected: + case MnemonicStatus.wrongSelection: + return false; + case MnemonicStatus.selected: + return true; + } + } + + Color get color { + switch (this) { + case MnemonicStatus.unselected: + return const Color(0xff86809D); + case MnemonicStatus.wrongSelection: + return const Color(0xffFF0045); + case MnemonicStatus.selected: + return const Color(0xff6600FF); + } + } +} diff --git a/test/app/shared/enum/type/blockchain_type_test.dart b/test/app/shared/enum/type/blockchain_type_test.dart new file mode 100644 index 000000000..8afe660a7 --- /dev/null +++ b/test/app/shared/enum/type/blockchain_type_test.dart @@ -0,0 +1,153 @@ +import 'dart:convert'; + +import 'package:altme/app/app.dart'; +import 'package:credential_manifest/credential_manifest.dart'; +import 'package:key_generator/key_generator.dart'; +import 'package:test/test.dart'; + +void main() { + group('BlockchainType Extension Tests', () { + test('BlockchainType icon returns correct value', () { + expect(BlockchainType.tezos.icon, equals(IconStrings.tezos)); + expect(BlockchainType.ethereum.icon, equals(IconStrings.ethereum)); + expect(BlockchainType.fantom.icon, equals(IconStrings.fantom)); + expect(BlockchainType.polygon.icon, equals(IconStrings.polygon)); + expect(BlockchainType.binance.icon, equals(IconStrings.binance)); + }); + + test('BlockchainType accountType returns correct value', () { + expect(BlockchainType.tezos.accountType, equals(AccountType.tezos)); + expect(BlockchainType.ethereum.accountType, equals(AccountType.ethereum)); + expect(BlockchainType.fantom.accountType, equals(AccountType.fantom)); + expect(BlockchainType.polygon.accountType, equals(AccountType.polygon)); + expect(BlockchainType.binance.accountType, equals(AccountType.binance)); + }); + + test('BlockchainType symbol returns correct value', () { + expect(BlockchainType.tezos.symbol, equals('XTZ')); + expect(BlockchainType.ethereum.symbol, equals('ETH')); + expect(BlockchainType.fantom.symbol, equals('FTM')); + expect(BlockchainType.polygon.symbol, equals('MATIC')); + expect(BlockchainType.binance.symbol, equals('BNB')); + }); + + test('BlockchainType chain returns correct value', () { + expect(() => BlockchainType.tezos.chain, throwsA(isA())); + expect( + BlockchainType.ethereum.chain, equals('${Parameters.NAMESPACE}:1')); + expect( + BlockchainType.fantom.chain, equals('${Parameters.NAMESPACE}:250')); + expect( + BlockchainType.polygon.chain, equals('${Parameters.NAMESPACE}:137')); + expect( + BlockchainType.binance.chain, equals('${Parameters.NAMESPACE}:56')); + }); + + test('BlockchainType chainId returns correct value', () { + expect( + () => BlockchainType.tezos.chainId, throwsA(isA())); + expect(BlockchainType.ethereum.chainId, equals(1)); + expect(BlockchainType.fantom.chainId, equals(250)); + expect(BlockchainType.polygon.chainId, equals(137)); + expect(BlockchainType.binance.chainId, equals(56)); + }); + + test('BlockchainType derivePathIndexKey returns correct value', () { + expect(BlockchainType.tezos.derivePathIndexKey, + equals(SecureStorageKeys.tezosDerivePathIndex)); + expect(BlockchainType.ethereum.derivePathIndexKey, + equals(SecureStorageKeys.ethereumDerivePathIndex)); + expect(BlockchainType.fantom.derivePathIndexKey, + equals(SecureStorageKeys.fantomDerivePathIndex)); + expect(BlockchainType.polygon.derivePathIndexKey, + equals(SecureStorageKeys.polygonDerivePathIndex)); + expect(BlockchainType.binance.derivePathIndexKey, + equals(SecureStorageKeys.binanceDerivePathIndex)); + }); + + test('BlockchainType credentialManifest returns correct value', () { + expect( + jsonEncode(BlockchainType.tezos.credentialManifest), + equals(jsonEncode(CredentialManifest.fromJson( + ConstantsJson.tezosAssociatedAddressCredentialManifestJson)))); + expect( + jsonEncode(BlockchainType.ethereum.credentialManifest), + equals(jsonEncode(CredentialManifest.fromJson( + ConstantsJson.ethereumAssociatedAddressCredentialManifestJson)))); + expect( + jsonEncode(BlockchainType.fantom.credentialManifest), + equals(jsonEncode(CredentialManifest.fromJson( + ConstantsJson.fantomAssociatedAddressCredentialManifestJson)))); + expect( + jsonEncode(BlockchainType.polygon.credentialManifest), + equals(jsonEncode(CredentialManifest.fromJson( + ConstantsJson.polygonAssociatedAddressCredentialManifestJson)))); + expect( + jsonEncode(BlockchainType.binance.credentialManifest), + equals(jsonEncode(CredentialManifest.fromJson( + ConstantsJson.binanceAssociatedAddressCredentialManifestJson)))); + }); + + test('BlockchainType filter returns correct value', () { + expect( + jsonEncode(BlockchainType.tezos.filter.toJson()), + equals(jsonEncode( + Filter(type: 'String', pattern: 'TezosAssociatedAddress') + .toJson()))); + expect( + jsonEncode(BlockchainType.ethereum.filter.toJson()), + equals(jsonEncode( + Filter(type: 'String', pattern: 'EthereumAssociatedAddress') + .toJson()))); + expect( + jsonEncode(BlockchainType.fantom.filter.toJson()), + equals(jsonEncode( + Filter(type: 'String', pattern: 'FantomAssociatedAddress') + .toJson()))); + expect( + jsonEncode(BlockchainType.polygon.filter.toJson()), + equals(jsonEncode( + Filter(type: 'String', pattern: 'PolygonAssociatedAddress') + .toJson()))); + expect( + jsonEncode(BlockchainType.binance.filter.toJson()), + equals(jsonEncode( + Filter(type: 'String', pattern: 'BinanceAssociatedAddress') + .toJson()))); + }); + + test('BlockchainType connectionBridge returns correct value', () { + expect(BlockchainType.tezos.connectionBridge, + equals(ConnectionBridgeType.beacon)); + expect(BlockchainType.ethereum.connectionBridge, + equals(ConnectionBridgeType.walletconnect)); + expect(BlockchainType.fantom.connectionBridge, + equals(ConnectionBridgeType.walletconnect)); + expect(BlockchainType.polygon.connectionBridge, + equals(ConnectionBridgeType.walletconnect)); + expect(BlockchainType.binance.connectionBridge, + equals(ConnectionBridgeType.walletconnect)); + }); + + test('BlockchainType networks returns correct value', () { + expect(BlockchainType.tezos.networks, + equals([TezosNetwork.mainNet(), TezosNetwork.ghostnet()])); + expect(BlockchainType.ethereum.networks, + equals([EthereumNetwork.mainNet(), EthereumNetwork.testNet()])); + expect(BlockchainType.fantom.networks, + equals([FantomNetwork.mainNet(), FantomNetwork.testNet()])); + expect(BlockchainType.polygon.networks, + equals([PolygonNetwork.mainNet(), PolygonNetwork.testNet()])); + expect(BlockchainType.binance.networks, + equals([BinanceNetwork.mainNet(), BinanceNetwork.testNet()])); + }); + + test('BlockchainType isDisabled returns correct value', () { + expect(BlockchainType.tezos.isDisabled, isFalse); + expect(BlockchainType.ethereum.isDisabled, isFalse); + expect(BlockchainType.fantom.isDisabled, isFalse); + expect(BlockchainType.polygon.isDisabled, isFalse); + expect(BlockchainType.binance.isDisabled, isFalse); + }); + }); +} diff --git a/test/app/shared/enum/type/kyc_vc_type_test.dart b/test/app/shared/enum/type/kyc_vc_type_test.dart new file mode 100644 index 000000000..53083c5bb --- /dev/null +++ b/test/app/shared/enum/type/kyc_vc_type_test.dart @@ -0,0 +1,18 @@ +import 'package:altme/app/app.dart'; +import 'package:test/test.dart'; + +void main() { + group('KycVcType Extension Tests', () { + test('KycVcType value returns correct value', () { + expect(KycVcType.verifiableId.value, equals('VerifiableId')); + expect(KycVcType.over13.value, equals('Over13')); + expect(KycVcType.over15.value, equals('Over15')); + expect(KycVcType.over18.value, equals('Over18')); + expect(KycVcType.over21.value, equals('Over21')); + expect(KycVcType.over50.value, equals('Over50')); + expect(KycVcType.over65.value, equals('Over65')); + expect(KycVcType.ageRange.value, equals('AgeRange')); + expect(KycVcType.defiCompliance.value, equals('DefiCompliance')); + }); + }); +} diff --git a/test/app/shared/enum/type/language_type_test.dart b/test/app/shared/enum/type/language_type_test.dart new file mode 100644 index 000000000..eaaa11216 --- /dev/null +++ b/test/app/shared/enum/type/language_type_test.dart @@ -0,0 +1,35 @@ +import 'package:altme/app/shared/enum/type/language_type.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class MockAppLocalizations extends Mock implements AppLocalizations { + @override + String get phoneLanguage => 'Phone'; + + @override + String get catalan => 'Catalan'; + + @override + String get english => 'English'; + + @override + String get spanish => 'Spanish'; + + @override + String get french => 'French'; +} + +void main() { + group('LanguageType Extension Tests', () { + test('LanguageType getTitle returns correct value', () { + final l10n = MockAppLocalizations(); + expect( + LanguageType.phone.getTitle(l10n: l10n, name: ''), equals('Phone')); + expect(LanguageType.ca.getTitle(l10n: l10n, name: ''), equals('Catalan')); + expect(LanguageType.en.getTitle(l10n: l10n, name: ''), equals('English')); + expect(LanguageType.es.getTitle(l10n: l10n, name: ''), equals('Spanish')); + expect(LanguageType.fr.getTitle(l10n: l10n, name: ''), equals('French')); + }); + }); +} diff --git a/test/app/shared/enum/type/oidc4vc_type_test.dart b/test/app/shared/enum/type/oidc4vc_type_test.dart new file mode 100644 index 000000000..0cde1154c --- /dev/null +++ b/test/app/shared/enum/type/oidc4vc_type_test.dart @@ -0,0 +1,15 @@ +import 'package:altme/app/app.dart'; +import 'package:test/test.dart'; + +void main() { + group('OIDC4VCType Extension Tests', () { + test('OIDC4VCType isEnabled returns correct value', () { + expect(OIDC4VCType.DEFAULT.isEnabled, equals(true)); + expect(OIDC4VCType.GAIAX.isEnabled, equals(true)); + expect(OIDC4VCType.GREENCYPHER.isEnabled, equals(true)); + expect(OIDC4VCType.EBSIV3.isEnabled, equals(true)); + expect(OIDC4VCType.JWTVC.isEnabled, equals(false)); + expect(OIDC4VCType.HAIP.isEnabled, equals(true)); + }); + }); +} diff --git a/test/app/shared/enum/type/profile/did_key_type_test.dart b/test/app/shared/enum/type/profile/did_key_type_test.dart new file mode 100644 index 000000000..314a25fd1 --- /dev/null +++ b/test/app/shared/enum/type/profile/did_key_type_test.dart @@ -0,0 +1,24 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DidKeyType Extension', () { + test('Formatted String', () { + expect(DidKeyType.edDSA.formattedString, 'did:key edDSA'); + expect(DidKeyType.secp256k1.formattedString, 'did:key secp256k1'); + expect(DidKeyType.p256.formattedString, 'did:key P-256'); + expect(DidKeyType.ebsiv3.formattedString, 'did:key EBSI-V3'); + expect(DidKeyType.jwkP256.formattedString, 'did:jwk P-256'); + expect(DidKeyType.jwtClientAttestation.formattedString, ''); + }); + + test('Support Crypto Credential', () { + expect(DidKeyType.edDSA.supportCryptoCredential, true); + expect(DidKeyType.secp256k1.supportCryptoCredential, true); + expect(DidKeyType.p256.supportCryptoCredential, true); + expect(DidKeyType.ebsiv3.supportCryptoCredential, false); + expect(DidKeyType.jwkP256.supportCryptoCredential, true); + expect(DidKeyType.jwtClientAttestation.supportCryptoCredential, true); + }); + }); +} diff --git a/test/app/shared/enum/type/profile/profile_type_test.dart b/test/app/shared/enum/type/profile/profile_type_test.dart new file mode 100644 index 000000000..08ba5c82d --- /dev/null +++ b/test/app/shared/enum/type/profile/profile_type_test.dart @@ -0,0 +1,15 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('ProfileType Extension', () { + test('Show Sponsered By', () { + expect(ProfileType.custom.showSponseredBy, false); + expect(ProfileType.ebsiV3.showSponseredBy, true); + expect(ProfileType.dutch.showSponseredBy, false); + expect(ProfileType.enterprise.showSponseredBy, true); + expect(ProfileType.owfBaselineProfile.showSponseredBy, true); + expect(ProfileType.defaultOne.showSponseredBy, false); + }); + }); +} diff --git a/test/app/shared/enum/type/wallet_provider_type_test.dart b/test/app/shared/enum/type/wallet_provider_type_test.dart new file mode 100644 index 000000000..a0ab74b83 --- /dev/null +++ b/test/app/shared/enum/type/wallet_provider_type_test.dart @@ -0,0 +1,16 @@ +import 'package:altme/app/app.dart'; +import 'package:test/test.dart'; + +void main() { + group('WalletProviderType Extension Tests', () { + test('WalletProviderType url returns correct value', () { + expect(WalletProviderType.Talao.url, equals(Urls.walletProvider)); + expect(WalletProviderType.Test.url, equals(Urls.walletTestProvider)); + }); + + test('WalletProviderType formattedString returns correct value', () { + expect(WalletProviderType.Talao.formattedString, equals('Talao')); + expect(WalletProviderType.Test.formattedString, equals('Test')); + }); + }); +} diff --git a/test/app/shared/extension/bigint_extension_test.dart b/test/app/shared/extension/bigint_extension_test.dart new file mode 100644 index 000000000..ed997ff8f --- /dev/null +++ b/test/app/shared/extension/bigint_extension_test.dart @@ -0,0 +1,27 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('BigIntExtension', () { + test('toBytes converts BigInt to byte list', () { + final bigInt = BigInt.from(16909060); // Example value: 0x01020304 + final bytes = bigInt.toBytes; + expect(bytes, [1, 2, 3, 4]); + }); + + test('toBytes returns empty list for zero', () { + final bigInt = BigInt.zero; + final bytes = bigInt.toBytes; + expect(bytes, []); + }); + + test('toBytes handles large BigInt values', () { + final bigInt = BigInt.parse('123456789012345678901234567890'); + final bytes = bigInt.toBytes; + expect( + bytes, + [1, 142, 233, 15, 246, 195, 115, 224, 238, 78, 63, 10, 210], + ); + }); + }); +} diff --git a/test/app/shared/extension/double_extension_test.dart b/test/app/shared/extension/double_extension_test.dart new file mode 100644 index 000000000..41aee422c --- /dev/null +++ b/test/app/shared/extension/double_extension_test.dart @@ -0,0 +1,41 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DoubleExtension', () { + test('decimalNumber returns string with specified decimal places', () { + const value = 3.14159; + expect(value.decimalNumber(2), '3.14'); + }); + + test('decimalNumber handles values with fewer decimal places', () { + const value = 123.4512; + expect(value.decimalNumber(4), '123.4512'); + }); + + test('decimalNumber handles values with fewer decimal places with 0s', () { + const value = 123.4500; + expect(value.decimalNumber(4), '123.45'); + }); + + test('decimalNumber handles values with more decimal places', () { + const value = 0.123456789; + expect(value.decimalNumber(5), '0.12345'); + }); + + test('decimalNumber returns 0.00 for zero', () { + const value = 0.0; + expect(value.decimalNumber(2), '0.0'); + }); + + test('decimalNumber handles negative values', () { + const value = -12.3456; + expect(value.decimalNumber(3), '-12.346'); + }); + + test('decimalNumber handles large values', () { + const value = 987654321.987656321; + expect(value.decimalNumber(5), '987654321.98765'); + }); + }); +} diff --git a/test/app/shared/extension/iterable_extension_test.dart b/test/app/shared/extension/iterable_extension_test.dart new file mode 100644 index 000000000..fd160f163 --- /dev/null +++ b/test/app/shared/extension/iterable_extension_test.dart @@ -0,0 +1,37 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('IterableExtension', () { + test('firstWhereOrNull returns first element that satisfies the condition', + () { + final list = [1, 2, 3, 4, 5]; + expect(list.firstWhereOrNull((element) => element.isOdd), 1); + expect(list.firstWhereOrNull((element) => element.isEven), 2); + }); + + test('firstWhereOrNull returns null if no element satisfies the condition', + () { + final list = [1, 3, 5, 7, 9]; + expect(list.firstWhereOrNull((element) => element.isEven), null); + }); + + test('lastWhereOrNull returns last element that satisfies the condition', + () { + final list = [1, 2, 3, 4, 5]; + expect(list.lastWhereOrNull((element) => element.isOdd), 5); + expect(list.lastWhereOrNull((element) => element.isEven), 4); + }); + + test('lastWhereOrNull returns null if no element satisfies the condition', + () { + final list = [1, 3, 5, 7, 9]; + expect(list.lastWhereOrNull((element) => element.isEven), null); + }); + + test('lastWhereOrNull returns null for empty iterable', () { + final list = []; + expect(list.lastWhereOrNull((element) => true), null); + }); + }); +} diff --git a/test/app/shared/extension/string_extension_test.dart b/test/app/shared/extension/string_extension_test.dart new file mode 100644 index 000000000..9ce281833 --- /dev/null +++ b/test/app/shared/extension/string_extension_test.dart @@ -0,0 +1,55 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('StringExtension', () { + test('formatNumber formats numbers correctly', () { + expect('1000'.formatNumber, '1,000'); + expect('123456789'.formatNumber, '123,456,789'); + expect('12345.6789'.formatNumber, '12,345.6789'); + expect('0.12345'.formatNumber, '0.12345'); + expect('123'.formatNumber, '123'); + expect('abc'.formatNumber, 'abc'); + }); + + test('isValidEmail validates email addresses', () { + expect('email@example.com'.isValidEmail(), true); + expect('invalid.email@'.isValidEmail(), false); + expect('another.invalid.email@domain'.isValidEmail(), false); + }); + + test('char2Bytes converts string to hex bytes', () { + expect('hello'.char2Bytes, '68656c6c6f'); + expect(''.char2Bytes, ''); + }); + + test('isEVM identifies EVM-based currencies', () { + expect('ETH'.isEVM, true); + expect('MATIC'.isEVM, true); + expect('FTM'.isEVM, true); + expect('BNB'.isEVM, true); + expect('BTC'.isEVM, false); + }); + + test('decimalNumber formats decimal numbers correctly', () { + expect('123.456'.decimalNumber(2), '123.45'); + expect('123.456'.decimalNumber(4), '123.456'); + expect('123'.decimalNumber(3), '123'); + }); + + test('invalid decimalNumber throws FormatException', () { + expect(() => 'abc'.decimalNumber(2), throwsFormatException); + }); + + test('Characters getter returns correct characters', () { + const input = 'Hello, world!'; + final characters = input.characters; + + expect(characters.length, input.length); + + for (int i = 0; i < input.length; i++) { + expect(characters.elementAt(i), input[i]); + } + }); + }); +} diff --git a/test/app/shared/extension/unit8List_extension_test.dart b/test/app/shared/extension/unit8List_extension_test.dart new file mode 100644 index 000000000..9effdc86f --- /dev/null +++ b/test/app/shared/extension/unit8List_extension_test.dart @@ -0,0 +1,40 @@ +import 'dart:typed_data'; +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Uint8ListExtension', () { + test( + 'filterPayload returns the original list if length is less than or equal to 6', + () { + final list = Uint8List.fromList([1, 2, 3]); + expect(list.filterPayload, list); + + final shortList = Uint8List.fromList([1, 2, 3, 4, 5, 6]); + expect(shortList.filterPayload, shortList); + }); + + test( + 'filterPayload returns the original list if the first two elements do not match the test list', + () { + final list = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9]); + expect(list.filterPayload, list); + + final differentFirstElement = + Uint8List.fromList([6, 1, 3, 4, 5, 6, 7, 8, 9]); + expect(differentFirstElement.filterPayload, differentFirstElement); + + final differentSecondElement = + Uint8List.fromList([5, 5, 3, 4, 5, 6, 7, 8, 9]); + expect(differentSecondElement.filterPayload, differentSecondElement); + }); + + test( + 'filterPayload returns a sublist starting from index 6 if the first two elements match the test list', + () { + final list = Uint8List.fromList([5, 1, 3, 4, 5, 6, 7, 8, 9]); + final expected = Uint8List.fromList([7, 8, 9]); + expect(list.filterPayload, expected); + }); + }); +} diff --git a/test/app/shared/helper_function.dart b/test/app/shared/helper_function_test.dart similarity index 100% rename from test/app/shared/helper_function.dart rename to test/app/shared/helper_function_test.dart diff --git a/test/app/shared/helper_functions/helper_functions_test.dart b/test/app/shared/helper_functions/helper_functions_test.dart new file mode 100644 index 000000000..7b7bcc304 --- /dev/null +++ b/test/app/shared/helper_functions/helper_functions_test.dart @@ -0,0 +1,1557 @@ +import 'dart:convert'; + +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/home/home.dart'; +import 'package:credential_manifest/credential_manifest.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http_mock_adapter/http_mock_adapter.dart'; +import 'package:jwt_decode/jwt_decode.dart'; +import 'package:key_generator/key_generator.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:oidc4vc/oidc4vc.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class MockSecureStorage extends Mock implements SecureStorageProvider {} + +class MockDotenv extends Mock implements DotEnv {} + +void main() { + late SecureStorageProvider mockSecureStorage; + + final client = Dio(); + late DioAdapter dioAdapter; + late DioClient mockClient; + + setUp(() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockSecureStorage = MockSecureStorage(); + + dioAdapter = + DioAdapter(dio: Dio(BaseOptions()), matcher: const UrlRequestMatcher()); + client.httpClientAdapter = dioAdapter; + mockClient = DioClient( + baseUrl: 'https://example.com/', + secureStorageProvider: mockSecureStorage, + dio: client, + ); + }); + + group('HelperFunctions', () { + group('generateDefaultAccountName', () { + test('Should generate default account name', () { + expect(generateDefaultAccountName(0, []), 'My account 1'); + expect(generateDefaultAccountName(1, []), 'My account 2'); + expect(generateDefaultAccountName(2, []), 'My account 3'); + }); + + test('Should generate unique default account name', () { + final accountNameList = ['My account 1', 'My account 2']; + expect(generateDefaultAccountName(0, accountNameList), 'My account 3'); + }); + + test('Should handle existing default account name', () { + final accountNameList = [ + 'My account 1', + 'My account 2', + 'My account 3' + ]; + expect(generateDefaultAccountName(0, accountNameList), 'My account 4'); + }); + }); + + group('Platform checks', () { + test('isAndroid should return true on Android platform', () { + expect(isAndroid, false); + }); + + test('isIOS should return true on iOS platform', () { + expect(isIOS, false); + }); + }); + + group('getIssuerDid', () { + test('Should return empty string if no issuer in URI', () { + final uri = Uri.parse('https://example.com'); + expect(getIssuerDid(uriToCheck: uri), ''); + }); + + test('Should return issuer DID if present in URI', () { + final uri = Uri.parse('https://example.com?issuer=did:example'); + expect(getIssuerDid(uriToCheck: uri), 'did:example'); + }); + + test('Should return last issuer DID if multiple issuers in URI', () { + final uri = Uri.parse( + 'https://example.com?issuer=did:example1&issuer=did:example2'); + expect(getIssuerDid(uriToCheck: uri), 'did:example2'); + }); + }); + + group('isValidPrivateKey', () { + test('Should return true for valid Ethereum private key', () { + const validKey = + '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; + expect(isValidPrivateKey(validKey), true); + }); + + test('Should return true for valid Tezos private key (edsk)', () { + const validKey = + 'edsk3nLLjZzUtwUmBzJbry8MZx8YmPfEzZaCFpSSKmCWUz1k5ehSJ8'; + expect(isValidPrivateKey(validKey), true); + }); + + test('Should return false for invalid private key', () { + const invalidKey = 'invalid_private_key'; + expect(isValidPrivateKey(invalidKey), false); + }); + }); + + test('stringToHexPrefixedWith05 returns correct value', () { + const payload = 'Bibash'; + expect( + stringToHexPrefixedWith05( + payload: payload, + dateTime: DateTime(2022, 1, 1, 0, 0, 0, 0), + ), + '05010031323254657a6f73205369676e6564204d6573736167653a20616c746d652e696f20323032322d30312d30312030303a30303a30302e30303020426962617368'); + }); + + test('getCredentialName returns correct credential name', () { + const constraints = { + 'fields': [ + { + 'path': r'[$.credentialSubject.type]', + 'filter': {'pattern': 'Bibash'} + }, + ], + }; + + final result = getCredentialName(jsonEncode(constraints)); + expect(result, equals('Bibash')); + }); + + test('getIssuersName returns correct issuer name', () { + const constraints = { + 'fields': [ + { + 'path': r'[$.issuer]', + 'filter': {'pattern': 'Bibash'} + }, + ], + }; + + final result = getIssuersName(jsonEncode(constraints)); + expect(result, equals('Bibash')); + }); + + test('getBlockchainType returns correct issuer name', () { + expect( + getBlockchainType(AccountType.tezos), equals(BlockchainType.tezos)); + expect(getBlockchainType(AccountType.ethereum), + equals(BlockchainType.ethereum)); + expect( + getBlockchainType(AccountType.fantom), equals(BlockchainType.fantom)); + expect(getBlockchainType(AccountType.polygon), + equals(BlockchainType.polygon)); + expect(getBlockchainType(AccountType.binance), + equals(BlockchainType.binance)); + expect(() => getBlockchainType(AccountType.ssi), + throwsA(isA())); + }); + + test('getCredTypeFromName returns correct type', () { + expect( + getCredTypeFromName('DefiCompliance'), + equals(CredentialSubjectType.defiCompliance), + ); + expect(getCredTypeFromName('afsd'), equals(isNull)); + }); + + test('timeFormatter formats time correctly', () { + expect(timeFormatter(timeInSecond: 65), equals('01 : 05')); + expect(timeFormatter(timeInSecond: 3600), equals('60 : 00')); + expect(timeFormatter(timeInSecond: 3665), equals('61 : 05')); + expect(timeFormatter(timeInSecond: 0), equals('00 : 00')); + }); + + test('getssiMnemonicsInList returns list of words', () async { + const mockMnemonic = + 'word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12'; + + when(() => mockSecureStorage.get(SecureStorageKeys.ssiMnemonic)) + .thenAnswer((_) => Future.value(mockMnemonic)); + + final result = await getssiMnemonicsInList(mockSecureStorage); + + expect( + result, + containsAll([ + 'word1', + 'word2', + 'word3', + 'word4', + 'word5', + 'word6', + 'word7', + 'word8', + 'word9', + 'word10', + 'word11', + 'word12' + ])); + }); + + test('getDateTimeWithoutSpace replaces spaces with dashes', () { + final formattedDateTime = + getDateTimeWithoutSpace(dateTime: DateTime(2022, 1, 1, 1, 1, 1, 1)); + expect(formattedDateTime, '2022-01-01-01:01:01.001'); + }); + + test('getIndexValue returns correct index for each DidKeyType', () { + expect( + getIndexValue(isEBSIV3: true, didKeyType: DidKeyType.secp256k1), 3); + expect( + getIndexValue(isEBSIV3: false, didKeyType: DidKeyType.secp256k1), 1); + + expect(getIndexValue(isEBSIV3: false, didKeyType: DidKeyType.p256), 4); + expect(getIndexValue(isEBSIV3: false, didKeyType: DidKeyType.ebsiv3), 5); + expect(getIndexValue(isEBSIV3: false, didKeyType: DidKeyType.jwkP256), 6); + expect(getIndexValue(isEBSIV3: false, didKeyType: DidKeyType.edDSA), 0); + expect( + getIndexValue( + isEBSIV3: false, didKeyType: DidKeyType.jwtClientAttestation), + 0); + }); + + group('getWalletAttestationP256Key', () { + test('returns existing key', () async { + const existingKey = 'existing_key'; + when(() => mockSecureStorage + .get(SecureStorageKeys.p256PrivateKeyForWallet)) + .thenAnswer((_) => Future.value(existingKey)); + + final result = await getWalletAttestationP256Key(mockSecureStorage); + + expect(result, existingKey.replaceAll('=', '')); + }); + + test('generates and returns new key', () async { + const newKey = 'new_key'; + when(() => mockSecureStorage + .get(SecureStorageKeys.p256PrivateKeyForWallet)) + .thenAnswer((_) => Future.value(null)); + + when(() => mockSecureStorage.set(any(), any())) + .thenAnswer((_) async {}); + + final result = await getWalletAttestationP256Key(mockSecureStorage); + final data = jsonDecode(result) as Map; + + expect(data['alg'], 'ES256'); + expect(data['crv'], 'P-256'); + expect(data['d'], isA()); + expect(data['kty'], 'EC'); + expect(data['use'], 'sig'); + expect(data['x'], isA()); + expect(data['y'], isA()); + }); + }); + + group('getP256KeyToGetAndPresentVC', () { + test('returns existing key', () async { + const existingKey = 'existing_key'; + when(() => mockSecureStorage + .get(SecureStorageKeys.p256PrivateKeyToGetAndPresentVC)) + .thenAnswer((_) => Future.value(existingKey)); + + final result = await getP256KeyToGetAndPresentVC(mockSecureStorage); + + expect(result, existingKey.replaceAll('=', '')); + }); + + test('generates and returns new key', () async { + const newKey = 'new_key'; + when(() => mockSecureStorage + .get(SecureStorageKeys.p256PrivateKeyToGetAndPresentVC)) + .thenAnswer((_) => Future.value(null)); + + when(() => mockSecureStorage.set(any(), any())) + .thenAnswer((_) async {}); + + final result = await getP256KeyToGetAndPresentVC(mockSecureStorage); + final data = jsonDecode(result) as Map; + + expect(data['alg'], 'ES256'); + expect(data['crv'], 'P-256'); + expect(data['d'], isA()); + expect(data['kty'], 'EC'); + expect(data['use'], 'sig'); + expect(data['x'], isA()); + expect(data['y'], isA()); + }); + }); + + test('generateRandomP256Key returns a valid P-256 key', () { + final key = generateRandomP256Key(); + + final data = jsonDecode(key) as Map; + + expect(data['alg'], 'ES256'); + expect(data['crv'], 'P-256'); + expect(data['d'], isA()); + expect(data['kty'], 'EC'); + expect(data['use'], 'sig'); + expect(data['x'], isA()); + expect(data['y'], isA()); + }); + + test('getDidKeyFromString returns correct enum value', () { + expect(getDidKeyFromString('DidKeyType.edDSA'), DidKeyType.edDSA); + expect(getDidKeyFromString('DidKeyType.secp256k1'), DidKeyType.secp256k1); + expect(getDidKeyFromString('DidKeyType.p256'), DidKeyType.p256); + expect(getDidKeyFromString('DidKeyType.ebsiv3'), DidKeyType.ebsiv3); + expect(getDidKeyFromString('DidKeyType.jwkP256'), DidKeyType.jwkP256); + expect(getDidKeyFromString('DidKeyType.jwtClientAttestation'), + DidKeyType.jwtClientAttestation); + expect(getDidKeyFromString('InvalidKeyType'), null); + expect(getDidKeyFromString(null), null); + }); + + group('JWT Decode Payload and Header', () { + final jwtDecode = JWTDecode(); + + const jwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFt' + 'ZSI6IkJpYmFzaCIsImlhdCI6MTUxNjIzOTAyMn0.ILWacv8Ed_PmWsLBEIK1mM-wOrt4w' + 'AU7_OUNLXQqtwI'; + + test('decodePayload correctly decodes a JWT token', () { + final decodedData = decodePayload(jwtDecode: jwtDecode, token: jwt); + final expectedData = { + 'sub': '1234567890', + 'name': 'Bibash', + 'iat': 1516239022, + }; + expect(decodedData, expectedData); + }); + + test('decodeHeader correctly decodes a JWT token', () { + final decodedData = decodeHeader(jwtDecode: jwtDecode, token: jwt); + final expectedData = { + 'alg': 'HS256', + 'typ': 'JWT', + }; + expect(decodedData, expectedData); + }); + + test('birthDateFormater formats the birth date correctly', () { + const birthData = 19900101; + final formattedBirthdate = birthDateFormater(birthData); + + expect(formattedBirthdate, '1990-01-01'); + }); + + test('getSignatureType returns correct signature type for circuitId', () { + expect(getSignatureType('credentialAtomicQuerySigV2'), 'BJJ Signature'); + expect(getSignatureType('credentialAtomicQuerySigV2OnChain'), + 'BJJ Signature'); + expect(getSignatureType('credentialAtomicQueryMTPV2'), 'SMT Signature'); + expect(getSignatureType('credentialAtomicQueryMTPV2OnChain'), + 'SMT Signature'); + expect(getSignatureType('unknownCircuitId'), ''); + }); + + test('splitUppercase splits PascalCase string correctly', () { + const input = 'BibashManShrestha'; + final result = splitUppercase(input); + expect(result, 'Bibash Man Shrestha'); + }); + + test('generateUriList returns list of URIs from "uri_list" parameter', + () { + const url = + 'https://example.com?uri_list=https%3A%2F%2Fexample.com%2Fpath1&uri_list=https%3A%2F%2Fexample.com%2Fpath2'; + final result = generateUriList(url); + expect( + result, ['https://example.com/path1', 'https://example.com/path2']); + }); + + test('sortedPublcJwk returns sorted public JWK without private key', () { + final privateKey = { + 'kty': 'EC', + 'use': 'sig', + 'crv': 'P-256K', + 'kid': '1234567890', + 'x': 'SjTww7i4eF-JKBYlShJqJ3lWQIVJF5y1g5uHY3gfAro', + 'y': '1bNb6uA0gKClEFhodSfgcW8FvfSHTgxE8WyFvSZ8bxc' + }; + final result = sortedPublcJwk(jsonEncode(privateKey)); + const expected = + '{"crv":"secp256k1","kid":"1234567890","kty":"EC","x":"SjTww7i4eF-JKBYlShJqJ3lWQIVJF5y1g5uHY3gfAro","y":"1bNb6uA0gKClEFhodSfgcW8FvfSHTgxE8WyFvSZ8bxc"}'; + expect(result, expected); + }); + + test('isPolygonIdUrl returns correct value for valid Polygon ID URLs', + () { + expect(isPolygonIdUrl('{"id":'), true); + expect(isPolygonIdUrl('{"body":{"'), true); + expect(isPolygonIdUrl('{"from": "did:polygonid:'), true); + expect(isPolygonIdUrl('{"to": "did:polygonid:'), true); + expect(isPolygonIdUrl('{"thid":'), true); + expect(isPolygonIdUrl('{"typ":'), true); + expect(isPolygonIdUrl('{"type":'), true); + }); + + test('isOIDC4VCIUrl returns true for valid OIDC4VCI URLs', () { + expect(isOIDC4VCIUrl(Uri.parse('openid://some/path')), true); + expect(isOIDC4VCIUrl(Uri.parse('haip://another/path')), true); + }); + + group('handleErrorForOID4VCI throws correct errors', () { + test('Test tokenEndpoint is null', () { + expect( + () async => handleErrorForOID4VCI( + url: 'example', + openIdConfiguration: const OpenIdConfiguration( + authorizationServer: 'example', + tokenEndpoint: null, + ), + authorizationServerConfiguration: const OpenIdConfiguration( + tokenEndpoint: null, + ), + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_issuer_metadata', + 'error_description': 'The issuer configuration is invalid. ' + 'The token_endpoint is missing.', + }), + ), + ); + }); + + test('Test credentialEndpoint is null', () { + expect( + () async => handleErrorForOID4VCI( + url: 'example', + openIdConfiguration: const OpenIdConfiguration( + authorizationServer: 'example', + tokenEndpoint: null, + credentialEndpoint: null, + ), + authorizationServerConfiguration: const OpenIdConfiguration( + tokenEndpoint: 'https://example.com/token', + ), + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_issuer_metadata', + 'error_description': 'The issuer configuration is invalid. ' + 'The credential_endpoint is missing.', + }), + ), + ); + }); + + test('Test credentialIssuer is null', () { + expect( + () async => handleErrorForOID4VCI( + url: 'example', + openIdConfiguration: const OpenIdConfiguration( + authorizationServer: 'example', + tokenEndpoint: null, + credentialEndpoint: 'https://example.com/cred', + credentialIssuer: null, + ), + authorizationServerConfiguration: const OpenIdConfiguration( + tokenEndpoint: 'https://example.com/token', + ), + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_issuer_metadata', + 'error_description': 'The issuer configuration is invalid. ' + 'The credential_issuer is missing.', + }), + ), + ); + }); + + test( + 'Test credentialsSupported and credentialConfigurationsSupported are null', + () { + expect( + () async => handleErrorForOID4VCI( + url: 'example', + openIdConfiguration: const OpenIdConfiguration( + authorizationServer: 'example', + tokenEndpoint: null, + credentialEndpoint: 'https://example.com/cred', + credentialIssuer: 'issuer', + credentialsSupported: null, + credentialConfigurationsSupported: null, + ), + authorizationServerConfiguration: const OpenIdConfiguration( + tokenEndpoint: 'https://example.com/token', + ), + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_issuer_metadata', + 'error_description': 'The issuer configuration is invalid. ' + 'The credentials_supported is missing.', + }), + ), + ); + }); + + test( + 'Test credentialsSupported and credentialConfigurationsSupported are null', + () { + expect( + () async => handleErrorForOID4VCI( + url: 'example', + openIdConfiguration: const OpenIdConfiguration( + authorizationServer: 'example', + tokenEndpoint: null, + credentialEndpoint: 'https://example.com/cred', + credentialIssuer: 'issuer', + credentialsSupported: null, + credentialConfigurationsSupported: 'asdf', + subjectSyntaxTypesSupported: ['asd'], + ), + authorizationServerConfiguration: const OpenIdConfiguration( + tokenEndpoint: 'https://example.com/token', + ), + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'subject_syntax_type_not_supported', + 'error_description': + 'The subject syntax type is not supported.', + }), + ), + ); + }); + }); + + group('getPresentationDefinition', () { + test('returns presentation definition from URI', () async { + final uri = Uri.parse( + "https://example.com?presentation_definition={'title':'Test'}"); + final presentationDefinition = + await getPresentationDefinition(uri: uri, client: mockClient); + + expect(presentationDefinition, {'title': 'Test'}); + }); + + test('returns null for invalid URI', () async { + final uri = Uri.parse('https://example.com'); + final presentationDefinition = + await getPresentationDefinition(uri: uri, client: mockClient); + + expect(presentationDefinition, isNull); + }); + + test( + 'returns presentation definition from URI with ' + 'presentation_definition_uri', () async { + final uri = Uri.parse( + 'https://example.com?presentation_definition_uri=https://example.com/presentation.com'); + + dioAdapter.onGet( + 'https://example.com/presentation.com', + (request) => request.reply(200, {'title': 'Test'}), + ); + + final presentationDefinition = + await getPresentationDefinition(uri: uri, client: mockClient); + + expect(presentationDefinition, {'title': 'Test'}); + }); + + test('returns null for invalid presentation_definition_uri', () async { + final uri = Uri.parse( + 'https://example.com?presentation_definition_uri=https://example.com/presentation.com'); + + dioAdapter.onGet( + 'https://example.com/presentation.com', + (request) => request.reply(200, 'asfd'), + ); + + final presentationDefinition = + await getPresentationDefinition(uri: uri, client: mockClient); + + expect(presentationDefinition, isNull); + }); + }); + + group('getClientMetada', () { + test('returns client metadata from URI', () async { + final uri = + Uri.parse("https://example.com?client_metadata={'title':'Test'}"); + final clientMetadata = + await getClientMetada(uri: uri, client: mockClient); + + expect(clientMetadata, {'title': 'Test'}); + }); + + test('returns null for invalid URI', () async { + final uri = Uri.parse('https://example.com'); + final clientMetadata = + await getClientMetada(uri: uri, client: mockClient); + + expect(clientMetadata, isNull); + }); + + test( + 'returns client metadata from URI with ' + 'client_metadata_uri', () async { + final uri = Uri.parse( + 'https://example.com?client_metadata_uri=https://example.com.com'); + + dioAdapter.onGet( + 'https://example.com.com', + (request) => request.reply(200, {'title': 'Test'}), + ); + + final clientMetadata = + await getClientMetada(uri: uri, client: mockClient); + + expect(clientMetadata, {'title': 'Test'}); + }); + + test('returns null for invalid client_metadata_uri', () async { + final uri = Uri.parse( + 'https://example.com?client_metadata_uri=https://example.com.com'); + + dioAdapter.onGet( + 'https://example.com.com', + (request) => request.reply(200, 'asfd'), + ); + + expect( + () async => getClientMetada(uri: uri, client: mockClient), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_request', + 'error_description': 'Client metaData is invalid', + }), + ), + ); + }); + }); + + group('getCredentialData', () { + test('getCredentialData returns credential if it is a String', () { + const credential = 'credentialData'; + final result = getCredentialData(credential); + expect(result, equals(credential)); + }); + + test('getCredentialData returns last credential type if it is a Map', + () { + final credential = { + 'types': ['type1', 'type2', 'type3'], + }; + final result = getCredentialData(credential); + expect(result, equals('type3')); + }); + + test('getCredentialData throws exception for invalid credential format', + () { + const credential = 123; + expect(() => getCredentialData(credential), throwsException); + }); + + test('getMessageHandler returns correct MessageHandler', () { + expect(getMessageHandler(MessageHandler), isA()); + expect( + getMessageHandler( + DioException( + requestOptions: RequestOptions(path: 'test/path'), + error: 'Test error', + response: Response( + data: 'Test data', + statusCode: 400, + requestOptions: RequestOptions(), + ), + ), + ), + isA(), + ); + expect( + getMessageHandler( + const FormatException('Test format exception', 'source'), + ), + isA().having((e) => e.data, '', { + 'error': 'unsupported_format', + 'error_description': 'Test format exception\n' + '\n' + 'source', + }), + ); + expect( + getMessageHandler(TypeError()), + isA().having((e) => e.data, '', { + 'error': 'unsupported_format', + 'error_description': + 'Some issue in the response from the server.', + }), + ); + expect( + getMessageHandler('Exception: CREDENTIAL_SUPPORT_DATA_ERROR'), + isA().having((e) => e.data, '', { + 'error': 'unsupported_credential_format', + 'error_description': + 'The credential support format has some issues.', + }), + ); + expect( + getMessageHandler('Exception: AUTHORIZATION_DETAIL_ERROR'), + isA().having((e) => e.data, '', { + 'error': 'unsupported_format', + 'error_description': 'Invalid token response format.', + }), + ); + expect( + getMessageHandler('Exception: INVALID_TOKEN'), + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'Failed to extract header from jwt.', + }), + ); + expect( + getMessageHandler('Exception: INVALID_PAYLOAD'), + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'Failed to extract payload from jwt.', + }), + ); + expect( + getMessageHandler('Exception: SSI_ISSUE'), + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'SSI does not support this process.', + }), + ); + expect( + getMessageHandler('Exception: OPENID-CONFIGURATION-ISSUE'), + isA().having((e) => e.data, '', { + 'error': 'unsupported_format', + 'error_description': 'Openid configuration response issue.', + }), + ); + expect( + getMessageHandler('Exception: NOT_A_VALID_OPENID_URL'), + isA().having((e) => e.data, '', { + 'error': 'unsupported_format', + 'error_description': + 'Not a valid openid url to initiate issuance.', + }), + ); + expect( + getMessageHandler('Exception: JWKS_URI_IS_NULL'), + isA().having((e) => e.data, '', { + 'error': 'unsupported_format', + 'error_description': 'The jwks_uri is null.', + }), + ); + expect( + getMessageHandler('Exception: Issue while getting'), + isA().having((e) => e.data, '', { + 'error': 'invalid_request', + 'error_description': 'Issue while getting', + }), + ); + expect( + getMessageHandler('Exception: SECURE_STORAGE_ISSUE'), + isA().having((e) => e.data, '', { + 'error': 'invalid_request', + 'error_description': 'Secure Storage issue with this device', + }), + ); + expect( + getMessageHandler('Exception: ISSUE_WHILE_ADDING_IDENTITY'), + isA().having((e) => e.data, '', { + 'error': 'invalid_request', + 'error_description': 'Issue while adding identity.', + }), + ); + expect( + getMessageHandler('Exception: ISSUE_WHILE_GETTING_CLAIMS'), + isA().having((e) => e.data, '', { + 'error': 'invalid_request', + 'error_description': 'Issue while getting claims.', + }), + ); + expect( + getMessageHandler('Exception: ISSUE_WHILE_RESTORING_CLAIMS'), + isA().having((e) => e.data, '', { + 'error': 'invalid_request', + 'error_description': 'Issue while restoring claims.', + }), + ); + expect( + getMessageHandler('Exception: PUBLICKEYJWK_EXTRACTION_ERROR'), + isA().having((e) => e.data, '', { + 'error': 'invalid_request', + 'error_description': 'Issue while restoring claims.', + }), + ); + expect( + getMessageHandler('Exception: random'), + isA(), + ); + }); + + test('getErrorResponseString returns correct ResponseString', () { + expect(getErrorResponseString('invalid_request'), + ResponseString.RESPONSE_STRING_invalidRequest); + expect(getErrorResponseString('invalid_request_uri'), + ResponseString.RESPONSE_STRING_invalidRequest); + expect(getErrorResponseString('invalid_request_object'), + ResponseString.RESPONSE_STRING_invalidRequest); + + expect(getErrorResponseString('unauthorized_client'), + ResponseString.RESPONSE_STRING_accessDenied); + expect(getErrorResponseString('access_denied'), + ResponseString.RESPONSE_STRING_accessDenied); + expect(getErrorResponseString('invalid_or_missing_proof'), + ResponseString.RESPONSE_STRING_accessDenied); + expect(getErrorResponseString('interaction_required'), + ResponseString.RESPONSE_STRING_accessDenied); + + expect(getErrorResponseString('unsupported_response_type'), + ResponseString.RESPONSE_STRING_thisRequestIsNotSupported); + expect(getErrorResponseString('invalid_scope'), + ResponseString.RESPONSE_STRING_thisRequestIsNotSupported); + expect(getErrorResponseString('request_not_supported'), + ResponseString.RESPONSE_STRING_thisRequestIsNotSupported); + expect(getErrorResponseString('request_uri_not_supported'), + ResponseString.RESPONSE_STRING_thisRequestIsNotSupported); + + expect(getErrorResponseString('unsupported_credential_type'), + ResponseString.RESPONSE_STRING_unsupportedCredential); + expect(getErrorResponseString('login_required'), + ResponseString.RESPONSE_STRING_aloginIsRequired); + expect(getErrorResponseString('account_selection_required'), + ResponseString.RESPONSE_STRING_aloginIsRequired); + + expect(getErrorResponseString('consent_required'), + ResponseString.RESPONSE_STRING_userConsentIsRequired); + + expect(getErrorResponseString('registration_not_supported'), + ResponseString.RESPONSE_STRING_theWalletIsNotRegistered); + + expect(getErrorResponseString('invalid_grant'), + ResponseString.RESPONSE_STRING_credentialIssuanceDenied); + expect(getErrorResponseString('invalid_client'), + ResponseString.RESPONSE_STRING_credentialIssuanceDenied); + expect(getErrorResponseString('invalid_token'), + ResponseString.RESPONSE_STRING_credentialIssuanceDenied); + + expect( + getErrorResponseString('unsupported_credential_format'), + ResponseString + .RESPONSE_STRING_thisCredentialFormatIsNotSupported); + + expect(getErrorResponseString('unsupported_format'), + ResponseString.RESPONSE_STRING_thisFormatIsNotSupported); + + expect(getErrorResponseString('invalid_issuer_metadata'), + ResponseString.RESPONSE_STRING_theCredentialOfferIsInvalid); + + expect(getErrorResponseString('server_error'), + ResponseString.RESPONSE_STRING_theServiceIsNotAvailable); + + expect( + getErrorResponseString('issuance_pending'), + ResponseString + .RESPONSE_STRING_theIssuanceOfThisCredentialIsPending); + + expect(getErrorResponseString('random'), + ResponseString.RESPONSE_STRING_thisRequestIsNotSupported); + }); + + test('isIDTokenOnly', () { + expect(isIDTokenOnly('id_token'), true); + expect(isIDTokenOnly('id_token vp_token'), false); + expect(isIDTokenOnly('vp_token'), false); + expect(isIDTokenOnly(''), false); + }); + + test('isVPTokenOnly', () { + expect(isVPTokenOnly('id_token'), false); + expect(isVPTokenOnly('vp_token'), true); + expect(isVPTokenOnly('id_token vp_token'), false); + expect(isVPTokenOnly(''), false); + }); + + test('isIDTokenAndVPToken', () { + expect(isIDTokenAndVPToken('id_token'), false); + expect(isIDTokenAndVPToken('vp_token'), false); + expect(isIDTokenAndVPToken('id_token vp_token'), true); + expect(isIDTokenAndVPToken(''), false); + }); + + test('hasIDToken', () { + expect(hasIDToken('id_token'), true); + expect(hasIDToken('vp_token'), false); + expect(hasIDToken('id_token vp_token'), true); + expect(hasIDToken(''), false); + }); + + test('hasVPToken', () { + expect(hasVPToken('id_token'), false); + expect(hasVPToken('vp_token'), true); + expect(hasVPToken('id_token vp_token'), true); + expect(hasVPToken(''), false); + }); + + test('hasIDTokenOrVPToken returns correct url', () { + expect(hasIDTokenOrVPToken('id_token'), true); + expect(hasIDTokenOrVPToken('vp_token'), true); + expect(hasIDTokenOrVPToken('id_token vp_token'), true); + expect(hasIDTokenOrVPToken(''), false); + }); + + test('getUpdatedUrlForSIOPV2OIC4VP', () { + final uri = Uri.parse('https://example.com'); + final response = { + 'response_type': 'code', + 'redirect_uri': 'https://example.com/callback', + 'scope': 'openid profile', + 'response_uri': 'https://example.com/response', + 'response_mode': 'form_post', + 'nonce': '12345', + 'client_id': 'client123', + 'claims': { + 'id_token': { + 'email': {'essential': true} + } + }, + 'state': 'state123', + 'presentation_definition': { + 'id': 'cred-123', + 'format': 'vp', + 'input_descriptors': [] + }, + 'presentation_definition_uri': 'https://example.com/presentation', + 'registration': { + 'token_endpoint_auth_method': 'client_secret_basic' + }, + 'client_metadata': {'example_key': 'example_value'}, + 'client_metadata_uri': 'https://example.com/client_metadata' + }; + + final updatedUrl = + getUpdatedUrlForSIOPV2OIC4VP(uri: uri, response: response); + + expect( + updatedUrl.split('&'), + containsAll([ + 'scope=openid+profile', + 'client_id=client123', + 'redirect_uri=https%3A%2F%2Fexample.com%2Fcallback', + 'response_uri=https%3A%2F%2Fexample.com%2Fresponse', + 'response_mode=form_post', + 'nonce=12345', + 'state=state123', + 'response_type=code', + 'claims=%7B%27id_token%27%3A%7B%27email%27%3A%7B%27essential%27%3Atrue%7D%7D%7D', + 'presentation_definition=%7B%27id%27%3A%27cred-123%27%2C%27format%27%3A%27vp%27%2C%27input_descriptors%27%3A%5B%5D%7D', + 'presentation_definition_uri=https%3A%2F%2Fexample.com%2Fpresentation', + 'registration=%7B%22token_endpoint_auth_method%22%3A%22client_secret_basic%22%7D', + 'client_metadata=%7B%22example_key%22%3A%22example_value%22%7D', + 'client_metadata_uri=https%3A%2F%2Fexample.com%2Fclient_metadata' + ]), + ); + }); + + group('getPresentVCDetails', () { + test( + 'returns correct value when presentationDefinition ldp_vc formats', + () { + expect( + getPresentVCDetails( + vcFormatType: VCFormatType.ldpVc, + presentationDefinition: PresentationDefinition( + inputDescriptors: [], + format: Format.fromJson( + { + 'ldp_vc': { + 'proof_type': [], + }, + }, + ), + ), + clientMetaData: null, + ), + (true, false, false, false), + ); + }); + + test( + 'throws ResponseMessage when presentationDefinition has no formats', + () { + final presentationDefinition = PresentationDefinition( + inputDescriptors: [], + format: Format.fromJson({}), + ); + + expect( + () => getPresentVCDetails( + vcFormatType: VCFormatType.ldpVc, + presentationDefinition: presentationDefinition, + clientMetaData: null, + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_request', + 'error_description': 'VC format is missing', + }), + ), + ); + }); + + test( + 'returns correct value(VCFormatType.jwtVc) when' + ' presentationDefinition.format' + ' and clientMetaData are null', () { + expect( + getPresentVCDetails( + vcFormatType: VCFormatType.jwtVc, + presentationDefinition: PresentationDefinition( + inputDescriptors: [], + format: null, + ), + clientMetaData: null, + ), + (false, true, false, false), + ); + }); + + test( + 'returns correct value(VCFormatType.jwtVcJson) when' + ' presentationDefinition.format' + ' and clientMetaData are null', () { + expect( + getPresentVCDetails( + vcFormatType: VCFormatType.jwtVcJson, + presentationDefinition: PresentationDefinition( + inputDescriptors: [], + format: null, + ), + clientMetaData: null, + ), + (false, false, true, false), + ); + }); + + test( + 'returns correct value(VCFormatType.vcSdJWT) when' + ' presentationDefinition.format' + ' and clientMetaData are null', () { + expect( + getPresentVCDetails( + vcFormatType: VCFormatType.vcSdJWT, + presentationDefinition: PresentationDefinition( + inputDescriptors: [], + format: null, + ), + clientMetaData: null, + ), + (false, false, false, true), + ); + }); + + test( + 'returns correct value(VCFormatType.jwtVcJson) when' + ' presentationDefinition.format is null' + ' and clientMetaData is provided', () { + expect( + getPresentVCDetails( + vcFormatType: VCFormatType.jwtVcJson, + presentationDefinition: PresentationDefinition( + inputDescriptors: [], + format: null, + ), + clientMetaData: { + 'vp_formats': { + 'jwt_vc_json': 'here', + }, + }, + ), + (false, false, true, false), + ); + }); + + test( + 'returns correct value(VCFormatType.vc+sd-jwt) when' + ' presentationDefinition.format is null' + ' and clientMetaData is provided', () { + expect( + getPresentVCDetails( + vcFormatType: VCFormatType.vcSdJWT, + presentationDefinition: PresentationDefinition( + inputDescriptors: [], + format: null, + ), + clientMetaData: { + 'vp_formats': { + 'vc+sd-jwt': 'here', + }, + }, + ), + (false, false, false, true), + ); + }); + }); + + group('collectSdValues', () { + test('returns an empty list when no _sd key is present', () { + final data = { + 'a': 1, + 'b': { + 'c': 2, + }, + }; + final result = collectSdValues(data); + expect(result, isEmpty); + }); + + test('collects values from _sd keys', () { + final data = { + '_sd': [1, 2, 3], + 'a': { + '_sd': [4, 5], + }, + 'b': { + 'c': { + '_sd': [6, 7], + }, + }, + }; + final result = collectSdValues(data); + expect(result, [1, 2, 3, 4, 5, 6, 7]); + }); + + test('collects values from ... keys within lists', () { + final data = { + 'a': [ + { + '...': 1, + }, + { + '...': 2, + }, + ], + 'c': [ + { + '...': 3, + }, + ], + }; + final result = collectSdValues(data); + expect(result, [1, 2, 3]); + }); + + test('handles complex nested structures', () { + final data = { + '_sd': [1], + 'a': { + '_sd': [2], + 'b': [ + { + '...': 3, + }, + ], + }, + 'd': [ + { + '...': 5, + }, + ], + }; + final result = collectSdValues(data); + expect(result, [1, 2, 3, 5]); + }); + + test('does not collect non-_sd or non-... values', () { + final data = { + 'a': 1, + 'b': { + 'c': 2, + 'd': [ + { + 'e': 3, + }, + ], + }, + }; + final result = collectSdValues(data); + expect(result, isEmpty); + }); + }); + + group('checkX509', () { + test('throws error for x5c is null', () async { + final result = await checkX509( + clientId: '', + header: {}, + encodedData: '', + ); + expect(result, isNull); + }); + + test('throws error for invalid x5c format', () async { + expect( + () => checkX509( + clientId: '', + header: { + 'x5c': 'i am not list', + }, + encodedData: '', + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'x509_san_dns scheme error', + }), + ), + ); + }); + + test('throws error for invalid x5c is empty', () async { + expect( + () => checkX509( + clientId: '', + header: { + 'x5c': [], + }, + encodedData: '', + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'x509_san_dns scheme error', + }), + ), + ); + }); + }); + + group('checkVerifierAttestation', () { + final jwtDecode = JWTDecode(); + test('throws error if JWT is missing', () async { + expect( + () => checkVerifierAttestation( + clientId: '', + header: {}, + jwtDecode: jwtDecode, + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'verifier_attestation scheme error', + }), + ), + ); + }); + + test('throws error when sub is null', () async { + expect( + () => checkVerifierAttestation( + clientId: '', + header: { + 'jwt': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.hqWGSaFpvbrXkOWc6lrnffhNWR19W_S1YKFBx2arWBk', + }, + jwtDecode: jwtDecode, + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'verifier_attestation scheme error', + }), + ), + ); + }); + + test('throws error when sub is not equal to clientId', () async { + expect( + () => checkVerifierAttestation( + clientId: 'random', + header: { + 'jwt': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjQiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.EyF5bWaxNMFda_CRAOHu3aagShvHlGpnSWK7uFsj23Q', + }, + jwtDecode: jwtDecode, + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'verifier_attestation scheme error', + }), + ), + ); + }); + + test('throws error when sub cnf is null', () async { + expect( + () => checkVerifierAttestation( + clientId: '124', + header: { + 'jwt': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjQiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.EyF5bWaxNMFda_CRAOHu3aagShvHlGpnSWK7uFsj23Q', + }, + jwtDecode: jwtDecode, + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'verifier_attestation scheme error', + }), + ), + ); + }); + + test('throws error when cnf is not Map', () async { + expect( + () => checkVerifierAttestation( + clientId: '124', + header: { + 'jwt': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjQiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImNuZiI6IjEyMyJ9.xZYbK4Oe2jGyQwyOiK5Zv0sHDtfjgY-sdS7WKs-aZYQ', + }, + jwtDecode: jwtDecode, + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'verifier_attestation scheme error', + }), + ), + ); + }); + + test('throws error when cnf does not contain jwk', () async { + expect( + () => checkVerifierAttestation( + clientId: '124', + header: { + 'jwt': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjQiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImNuZiI6eyJ0ZXN0IjoidGVzdCJ9fQ.tnYvd00vYuDxjTtC7goSB5EQUNGtSFR1JBpY1CTRzD0', + }, + jwtDecode: jwtDecode, + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'verifier_attestation scheme error', + }), + ), + ); + }); + + test('throws error when jwk is not Map', () async { + expect( + () => checkVerifierAttestation( + clientId: '124', + header: { + 'jwt': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjQiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImNuZiI6eyJqd2siOiJ0ZXN0In19.qjqntbtQTnRcR60UvveWyv8t3Y1dFMI0DLSyB_pwqBk', + }, + jwtDecode: jwtDecode, + ), + throwsA( + isA().having((e) => e.data, '', { + 'error': 'invalid_format', + 'error_description': 'verifier_attestation scheme error', + }), + ), + ); + }); + + test('return correct jwk', () async { + final result = await checkVerifierAttestation( + clientId: '124', + header: { + 'jwt': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjQiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImNuZiI6eyJqd2siOnsibmFtZSI6IkJpYmFzaCJ9fX0.daqYrO5JtoW9o0ZEDlG2a6ctAgQxaNxTT0iYyVBIIkY', + }, + jwtDecode: jwtDecode, + ); + + expect(result, {'name': 'Bibash'}); + }); + }); + + test('returns correct wallet Address', () { + expect( + getWalletAddress( + TezosAssociatedAddressModel( + id: 'id', + type: 'type', + issuedBy: const Author('name'), + associatedAddress: 'tezosAddress', + ), + ), + 'tezosAddress', + ); + expect( + getWalletAddress( + EthereumAssociatedAddressModel( + id: 'id', + type: 'type', + issuedBy: const Author('name'), + associatedAddress: 'ethereumAddress', + ), + ), + 'ethereumAddress', + ); + expect( + getWalletAddress( + PolygonAssociatedAddressModel( + id: 'id', + type: 'type', + issuedBy: const Author('name'), + associatedAddress: 'polygonAddress', + ), + ), + 'polygonAddress', + ); + expect( + getWalletAddress( + BinanceAssociatedAddressModel( + id: 'id', + type: 'type', + issuedBy: const Author('name'), + associatedAddress: 'address', + ), + ), + 'address', + ); + expect( + getWalletAddress( + FantomAssociatedAddressModel( + id: 'id', + type: 'type', + issuedBy: const Author('name'), + associatedAddress: 'fantomAddress', + ), + ), + 'fantomAddress', + ); + expect( + getWalletAddress( + AgeRangeModel( + id: 'id', + type: 'type', + issuedBy: const Author('name'), + ), + ), + isNull, + ); + }); + + group('fetchRpcUrl', () { + late DotEnv mockDotenv; + + setUpAll(() { + mockDotenv = MockDotenv(); + }); + + test('returns rpcNodeUrl for BinanceNetwork.mainNet()', () async { + final result = await fetchRpcUrl( + blockchainNetwork: BinanceNetwork.mainNet(), + dotEnv: mockDotenv, + ); + expect(result, 'https://bsc-dataseed.binance.org/'); + }); + + test('returns rpcNodeUrl for BinanceNetwork.testNet()', () async { + final result = await fetchRpcUrl( + blockchainNetwork: BinanceNetwork.testNet(), + dotEnv: mockDotenv, + ); + expect(result, 'https://bsc-testnet.public.blastapi.io'); + }); + + test('returns rpcNodeUrl for FantomNetwork.mainNet()', () async { + final result = await fetchRpcUrl( + blockchainNetwork: FantomNetwork.mainNet(), + dotEnv: mockDotenv, + ); + expect(result, 'https://rpcapi.fantom.network/'); + }); + + test('returns rpcNodeUrl for FantomNetwork.testNet()', () async { + final result = await fetchRpcUrl( + blockchainNetwork: FantomNetwork.testNet(), + dotEnv: mockDotenv, + ); + expect(result, 'https://rpc.testnet.fantom.network'); + }); + + test('returns infura URL for PolygonNetwork.mainNet()', () async { + when(() => mockDotenv.load()).thenAnswer((_) async => {}); + when(() => mockDotenv.get('INFURA_API_KEY')).thenReturn('123'); + final result = await fetchRpcUrl( + blockchainNetwork: PolygonNetwork.mainNet(), + dotEnv: mockDotenv, + ); + expect(result, '${Parameters.POLYGON_INFURA_URL}123'); + }); + + test('returns infura URL for PolygonNetwork on Testnet', () async { + final result = await fetchRpcUrl( + blockchainNetwork: PolygonNetwork.testNet(), + dotEnv: mockDotenv, + ); + expect(result, 'https://rpc-mumbai.maticvigil.com'); + }); + + test('returns infura URL for EthereumNetwork.mainNet()', () async { + when(() => mockDotenv.load()).thenAnswer((_) async => {}); + when(() => mockDotenv.get('INFURA_API_KEY')).thenReturn('123'); + final result = await fetchRpcUrl( + blockchainNetwork: EthereumNetwork.mainNet(), + dotEnv: mockDotenv, + ); + expect(result, '${Parameters.web3RpcMainnetUrl}123'); + }); + + test('returns infura URL for EthereumNetwork.testNet()', () async { + final result = await fetchRpcUrl( + blockchainNetwork: EthereumNetwork.testNet(), + dotEnv: mockDotenv, + ); + expect(result, 'https://rpc.sepolia.dev'); + }); + + test('returns infura URL for TezosNetwork.mainNet()', () async { + when(() => mockDotenv.load()).thenAnswer((_) async => {}); + when(() => mockDotenv.get('INFURA_API_KEY')).thenReturn('123'); + final result = await fetchRpcUrl( + blockchainNetwork: TezosNetwork.mainNet(), + dotEnv: mockDotenv, + ); + expect(result, '${Parameters.web3RpcMainnetUrl}123'); + }); + + test('returns infura URL for TezosNetwork.ghostnet()', () async { + final result = await fetchRpcUrl( + blockchainNetwork: TezosNetwork.ghostnet(), + dotEnv: mockDotenv, + ); + expect(result, 'https://rpc.tzkt.io/ghostnet'); + }); + }); + }); + }); + }); +} diff --git a/test/app/shared/issuer/check_issuer_test.dart b/test/app/shared/issuer/check_issuer_test.dart new file mode 100644 index 000000000..8f1c68b42 --- /dev/null +++ b/test/app/shared/issuer/check_issuer_test.dart @@ -0,0 +1,170 @@ +import 'dart:convert'; + +import 'package:altme/app/app.dart'; +import 'package:altme/app/shared/issuer/models/organization_info.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http_mock_adapter/http_mock_adapter.dart'; +import 'package:mockito/mockito.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class MockSecureStorage extends Mock implements SecureStorageProvider {} + +void main() { + group('CheckIssuer', () { + late SecureStorageProvider mockSecureStorage; + + final client = Dio(); + late DioAdapter dioAdapter; + late DioClient mockClient; + + late CheckIssuer checkIssuer; + + const String checkIssuerServerUrl = 'https://example.com'; + final Uri uriToCheck = Uri.parse('https://example.com'); + + setUp(() { + TestWidgetsFlutterBinding.ensureInitialized(); + mockSecureStorage = MockSecureStorage(); + + dioAdapter = DioAdapter( + dio: Dio(BaseOptions()), matcher: const UrlRequestMatcher()); + client.httpClientAdapter = dioAdapter; + mockClient = DioClient( + baseUrl: 'https://example.com/', + secureStorageProvider: mockSecureStorage, + dio: client, + ); + checkIssuer = CheckIssuer( + mockClient, + checkIssuerServerUrl, + uriToCheck, + ); + }); + + test('should return empty issuer if checkIssuerServerUrl is empty', + () async { + final checkIssuer = CheckIssuer( + mockClient, + '', + uriToCheck, + ); + + final result = await checkIssuer.isIssuerInApprovedList(); + + expect( + jsonEncode(result), + equals(jsonEncode(Issuer.emptyIssuer(uriToCheck.host))), + ); + }); + + test('should return empty issuer if did does not start with did:ebsi', + () async { + final checkIssuer = CheckIssuer( + mockClient, + Urls.checkIssuerEbsiUrl, + Uri.parse('https://example.com'), + ); + + final result = await checkIssuer.isIssuerInApprovedList(); + + expect( + jsonEncode(result), + equals(jsonEncode(Issuer.emptyIssuer('example.com'))), + ); + }); + + test( + 'should return specific issuer for' + ' https://api.conformance.intebsi.xyz/trusted-issuers-registry/v2/issuers', + () async { + final checkIssuer = CheckIssuer( + mockClient, + 'https://api.conformance.intebsi.xyz/trusted-issuers-registry/v2/issuers', + Uri.parse('https://example.com?issuer=did:ebsi'), + ); + + final result = await checkIssuer.isIssuerInApprovedList(); + + final expected = Issuer( + preferredName: '', + did: [], + organizationInfo: OrganizationInfo( + legalName: 'sdf', + currentAddress: '', + id: '', + issuerDomain: [], + website: uriToCheck.host, + ), + ); + + expect( + jsonEncode(result), + equals(jsonEncode(expected)), + ); + }); + + test('should return valid issuer when issuer is in approved list', + () async { + final response = { + 'issuer': { + 'preferredName': 'Example Issuer', + 'did': ['did:example:123'], + 'organizationInfo': { + 'legalName': 'Example Org', + 'currentAddress': '123 Street', + 'id': 'org-123', + 'issuerDomain': ['example.com'], + 'website': 'example.com', + }, + }, + }; + + dioAdapter.onGet( + 'https://example.com/', + (request) => request.reply(200, response), + ); + + final result = await checkIssuer.isIssuerInApprovedList(); + + expect(result.preferredName, equals('Example Issuer')); + expect(result.organizationInfo.legalName, equals('Example Org')); + expect(result.organizationInfo.issuerDomain, contains('example.com')); + }); + + test( + 'should return empty issuer if organizationInfo.issuerDomain does not contain uriToCheck.host', + () async { + final response = { + 'issuer': { + 'preferredName': 'Example Issuer', + 'did': ['did:example:123'], + 'organizationInfo': { + 'legalName': 'Example Org', + 'currentAddress': '123 Street', + 'id': 'org-123', + 'issuerDomain': ['another.com'], + 'website': 'example.com', + }, + }, + }; + + dioAdapter.onGet( + 'https://example.com/', + (request) => request.reply(200, response), + ); + + final result = await checkIssuer.isIssuerInApprovedList(); + + expect(jsonEncode(result), + equals(jsonEncode(Issuer.emptyIssuer(uriToCheck.host)))); + }); + + test('should throw exception when an error occurs', () async { + expect( + () async => checkIssuer.isIssuerInApprovedList(), + throwsA(isA()), + ); + }); + }); +} diff --git a/test/app/shared/issuer/models/issuer_test.dart b/test/app/shared/issuer/models/issuer_test.dart new file mode 100644 index 000000000..86b8ca1ab --- /dev/null +++ b/test/app/shared/issuer/models/issuer_test.dart @@ -0,0 +1,65 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/app/shared/issuer/models/organization_info.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Issuer', () { + test('Issuer fromJson should return a valid Issuer object', () { + final json = { + 'preferredName': 'Example Issuer', + 'did': ['did:example:123'], + 'organizationInfo': { + 'id': 'org-123', + 'legalName': 'Example Org', + 'currentAddress': '123 Street', + 'website': 'example.com', + 'issuerDomain': ['example.com'], + }, + }; + + final issuer = Issuer.fromJson(json); + + expect(issuer.preferredName, equals('Example Issuer')); + expect(issuer.did, equals(['did:example:123'])); + expect(issuer.organizationInfo.id, equals('org-123')); + expect(issuer.organizationInfo.legalName, equals('Example Org')); + expect(issuer.organizationInfo.currentAddress, equals('123 Street')); + expect(issuer.organizationInfo.website, equals('example.com')); + expect(issuer.organizationInfo.issuerDomain, equals(['example.com'])); + }); + + test('Issuer toJson should return a valid JSON map', () { + final organizationInfo = OrganizationInfo( + id: 'org-123', + legalName: 'Example Org', + currentAddress: '123 Street', + website: 'example.com', + issuerDomain: ['example.com'], + ); + + final issuer = Issuer( + preferredName: 'Example Issuer', + did: ['did:example:123'], + organizationInfo: organizationInfo, + ); + + final json = issuer.toJson(); + + expect(json['preferredName'], equals('Example Issuer')); + expect(json['did'], equals(['did:example:123'])); + expect(json['organizationInfo'], isA()); + }); + + test('Issuer.emptyIssuer should return an Issuer with default values', () { + final issuer = Issuer.emptyIssuer('example.com'); + + expect(issuer.preferredName, equals('')); + expect(issuer.did, isEmpty); + expect(issuer.organizationInfo.id, equals('')); + expect(issuer.organizationInfo.legalName, equals('')); + expect(issuer.organizationInfo.currentAddress, equals('')); + expect(issuer.organizationInfo.website, equals('example.com')); + expect(issuer.organizationInfo.issuerDomain, isEmpty); + }); + }); +} diff --git a/test/app/shared/issuer/models/organization_info_test.dart b/test/app/shared/issuer/models/organization_info_test.dart new file mode 100644 index 000000000..eb7d82165 --- /dev/null +++ b/test/app/shared/issuer/models/organization_info_test.dart @@ -0,0 +1,57 @@ +import 'package:altme/app/shared/issuer/models/organization_info.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('OrganizationInfo', () { + test( + 'OrganizationInfo fromJson should return a valid OrganizationInfo object', + () { + final json = { + 'id': 'org-123', + 'legalName': 'Example Org', + 'currentAddress': '123 Street', + 'website': 'example.com', + 'issuerDomain': ['example.com'], + }; + + final organizationInfo = OrganizationInfo.fromJson(json); + + expect(organizationInfo.id, equals('org-123')); + expect(organizationInfo.legalName, equals('Example Org')); + expect(organizationInfo.currentAddress, equals('123 Street')); + expect(organizationInfo.website, equals('example.com')); + expect(organizationInfo.issuerDomain, equals(['example.com'])); + }); + + test('OrganizationInfo toJson should return a valid JSON map', () { + final organizationInfo = OrganizationInfo( + id: 'org-123', + legalName: 'Example Org', + currentAddress: '123 Street', + website: 'example.com', + issuerDomain: ['example.com'], + ); + + final json = organizationInfo.toJson(); + + expect(json['id'], equals('org-123')); + expect(json['legalName'], equals('Example Org')); + expect(json['currentAddress'], equals('123 Street')); + expect(json['website'], equals('example.com')); + expect(json['issuerDomain'], equals(['example.com'])); + }); + + test( + 'OrganizationInfo.emptyOrganizationInfo should return an OrganizationInfo with default values', + () { + final organizationInfo = + OrganizationInfo.emptyOrganizationInfo('example.com'); + + expect(organizationInfo.id, equals('')); + expect(organizationInfo.legalName, equals('')); + expect(organizationInfo.currentAddress, equals('')); + expect(organizationInfo.website, equals('example.com')); + expect(organizationInfo.issuerDomain, isEmpty); + }); + }); +} diff --git a/test/app/shared/loading/loading_view_test.dart b/test/app/shared/loading/loading_view_test.dart new file mode 100644 index 000000000..e7bad840b --- /dev/null +++ b/test/app/shared/loading/loading_view_test.dart @@ -0,0 +1,57 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +class MockProfileCubit extends Mock implements ProfileCubit { + @override + final state = ProfileState(model: ProfileModel.empty()); +} + +void main() { + group('LoadingView', () { + late ProfileCubit profileCubit; + + setUp(() { + profileCubit = MockProfileCubit(); + }); + + testWidgets('shows loading view correctly', (tester) async { + await tester.pumpWidget( + BlocProvider( + create: (context) => profileCubit, + child: MaterialApp( + home: Builder( + builder: (context) { + return Column( + children: [ + ElevatedButton( + onPressed: () { + LoadingView() + .show(context: context, text: 'Loading...'); + }, + child: const Text('Show Loading View'), + ), + ElevatedButton( + onPressed: () { + LoadingView().hide(); + }, + child: const Text('Hide Loading View'), + ), + ], + ); + }, + ), + ), + ), + ); + + await tester.pump(); + + expect(find.byType(LoadingView), findsNothing); + expect(find.text('Show Loading View'), findsOneWidget); + }); + }); +} diff --git a/test/app/shared/message_handler/message_handler_test.dart b/test/app/shared/message_handler/message_handler_test.dart index 079fd3b23..c99c19826 100644 --- a/test/app/shared/message_handler/message_handler_test.dart +++ b/test/app/shared/message_handler/message_handler_test.dart @@ -1,17 +1,19 @@ -//import 'package:altme/app/shared/message_handler/message_handler.dart'; -//import 'package:flutter/material.dart'; +import 'package:altme/app/shared/message_handler/message_handler.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -//import '../../../helpers/helpers.dart'; +import '../../../helpers/helpers.dart'; + +class TestMessageHandler with MessageHandler {} void main() { group('MessageHandler Test', () { - // testWidgets('returns Unknown message', (tester) async { - // final MessageHandler messageHandler = MessageHandler(); - // await tester.pumpApp(Container()); - // final BuildContext context = tester.element(find.byType(Container)); - // final String text = messageHandler.getMessage(context, messageHandler); - // expect(text, 'Unknown message'); - // }); + testWidgets('returns Unknown message', (tester) async { + final TestMessageHandler messageHandler = TestMessageHandler(); + await tester.pumpApp(Container()); + final BuildContext context = tester.element(find.byType(Container)); + final String text = messageHandler.getMessage(context, messageHandler); + expect(text, 'Unknown message'); + }); }); } diff --git a/test/app/shared/models/blockchain_network/blockchain_network_test.dart b/test/app/shared/models/blockchain_network/blockchain_network_test.dart new file mode 100644 index 000000000..b484522cd --- /dev/null +++ b/test/app/shared/models/blockchain_network/blockchain_network_test.dart @@ -0,0 +1,50 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('BlockchainNetwork', () { + test('fromJson should create a BlockchainNetwork instance from JSON', () { + final json = { + 'networkname': 'TestNet', + 'apiUrl': 'https://example.com/api', + 'rpcNodeUrl': 'https://rpc.example.com', + 'title': 'Test Network', + 'subTitle': 'Subtitle', + 'type': 'ethereum', + 'apiKey': 'apikey', + }; + + final blockchainNetwork = BlockchainNetwork.fromJson(json); + + expect(blockchainNetwork.networkname, 'TestNet'); + expect(blockchainNetwork.apiUrl, 'https://example.com/api'); + expect(blockchainNetwork.rpcNodeUrl, 'https://rpc.example.com'); + expect(blockchainNetwork.title, 'Test Network'); + expect(blockchainNetwork.subTitle, 'Subtitle'); + expect(blockchainNetwork.type, BlockchainType.ethereum); + expect(blockchainNetwork.apiKey, 'apikey'); + }); + + test('toJson should convert a BlockchainNetwork instance to JSON', () { + const blockchainNetwork = BlockchainNetwork( + networkname: 'TestNet', + apiUrl: 'https://example.com/api', + rpcNodeUrl: 'https://rpc.example.com', + title: 'Test Network', + subTitle: 'Subtitle', + type: BlockchainType.ethereum, + apiKey: 'apikey', + ); + + final json = blockchainNetwork.toJson(); + + expect(json['networkname'], 'TestNet'); + expect(json['apiUrl'], 'https://example.com/api'); + expect(json['rpcNodeUrl'], 'https://rpc.example.com'); + expect(json['title'], 'Test Network'); + expect(json['subTitle'], 'Subtitle'); + expect(json['type'], 'ethereum'); + expect(json['apiKey'], 'apikey'); + }); + }); +} diff --git a/test/app/shared/models/blockchain_network/tezos_network_test.dart b/test/app/shared/models/blockchain_network/tezos_network_test.dart new file mode 100644 index 000000000..dab97499d --- /dev/null +++ b/test/app/shared/models/blockchain_network/tezos_network_test.dart @@ -0,0 +1,50 @@ +import 'package:altme/app/app.dart'; +import 'package:test/test.dart'; + +void main() { + group('TezosNetwork', () { + test('fromJson should create a TezosNetwork instance from JSON', () { + final json = { + 'networkname': 'TestNet', + 'apiUrl': 'https://example.com/api', + 'rpcNodeUrl': 'https://rpc.example.com', + 'title': 'Test Network', + 'subTitle': 'Subtitle', + 'type': 'tezos', + 'apiKey': 'apikey', + }; + + final tezosNetwork = TezosNetwork.fromJson(json); + + expect(tezosNetwork.networkname, 'TestNet'); + expect(tezosNetwork.apiUrl, 'https://example.com/api'); + expect(tezosNetwork.rpcNodeUrl, 'https://rpc.example.com'); + expect(tezosNetwork.title, 'Test Network'); + expect(tezosNetwork.subTitle, 'Subtitle'); + expect(tezosNetwork.type, BlockchainType.tezos); + expect(tezosNetwork.apiKey, 'apikey'); + }); + + test('toJson should convert a TezosNetwork instance to JSON', () { + const tezosNetwork = TezosNetwork( + networkname: 'TestNet', + apiUrl: 'https://example.com/api', + rpcNodeUrl: 'https://rpc.example.com', + title: 'Test Network', + subTitle: 'Subtitle', + type: BlockchainType.tezos, + apiKey: 'apikey', + ); + + final json = tezosNetwork.toJson(); + + expect(json['networkname'], 'TestNet'); + expect(json['apiUrl'], 'https://example.com/api'); + expect(json['rpcNodeUrl'], 'https://rpc.example.com'); + expect(json['title'], 'Test Network'); + expect(json['subTitle'], 'Subtitle'); + expect(json['type'], 'tezos'); + expect(json['apiKey'], 'apikey'); + }); + }); +} diff --git a/test/app/shared/models/state_message/state_message_test.dart b/test/app/shared/models/state_message/state_message_test.dart new file mode 100644 index 000000000..2467e27ba --- /dev/null +++ b/test/app/shared/models/state_message/state_message_test.dart @@ -0,0 +1,63 @@ +import 'package:test/test.dart'; +import 'package:altme/app/app.dart'; + +void main() { + group('StateMessage', () { + test('fromJson should create a StateMessage instance from JSON', () { + final json = { + 'type': 'error', + 'stringMessage': 'Error message', + 'showDialog': true, + 'duration': 1600, + }; + + final stateMessage = StateMessage.fromJson(json); + + expect(stateMessage.type, MessageType.error); + expect(stateMessage.stringMessage, 'Error message'); + expect(stateMessage.showDialog, isTrue); + expect(stateMessage.duration, const Duration(microseconds: 1600)); + }); + + test('toJson should convert a StateMessage instance to JSON', () { + const stateMessage = StateMessage( + type: MessageType.warning, + stringMessage: 'Warning message', + showDialog: false, + duration: Duration(microseconds: 2000), + ); + + final json = stateMessage.toJson(); + + expect(json['type'], 'warning'); + expect(json['stringMessage'], 'Warning message'); + expect(json['showDialog'], isFalse); + expect(json['duration'], 2000); + }); + + test('equality should compare StateMessage instances correctly', () { + const stateMessage1 = StateMessage( + type: MessageType.info, + stringMessage: 'Info message', + ); + const stateMessage2 = StateMessage( + type: MessageType.info, + stringMessage: 'Info message', + ); + const stateMessage3 = StateMessage( + type: MessageType.warning, + stringMessage: 'Warning message', + ); + + expect(stateMessage1, equals(stateMessage2)); + expect(stateMessage1, isNot(equals(stateMessage3))); + }); + + test('message types should have correct values', () { + expect(const StateMessage.error().type, MessageType.error); + expect(const StateMessage.warning().type, MessageType.warning); + expect(const StateMessage.info().type, MessageType.info); + expect(const StateMessage.success().type, MessageType.success); + }); + }); +} diff --git a/test/app/shared/models/xtz_data/xtz_data_test.dart b/test/app/shared/models/xtz_data/xtz_data_test.dart new file mode 100644 index 000000000..75cbfa95b --- /dev/null +++ b/test/app/shared/models/xtz_data/xtz_data_test.dart @@ -0,0 +1,50 @@ +import 'package:altme/app/app.dart'; +import 'package:test/test.dart'; + +void main() { + group('XtzData', () { + test('fromJson should create a XtzData instance from JSON', () { + final json = { + 'price': 3.5, + 'price24H': 3.7, + 'marketCap': 1000000000.0, + 'market24H': 900000000.0, + 'volume': 5000000.0, + 'volume24H': 6000000.0, + 'updated': '2023-05-17T15:25:00Z', + }; + + final xtzData = XtzData.fromJson(json); + + expect(xtzData.price, 3.5); + expect(xtzData.price24H, 3.7); + expect(xtzData.marketCap, 1000000000.0); + expect(xtzData.market24H, 900000000.0); + expect(xtzData.volume, 5000000.0); + expect(xtzData.volume24H, 6000000.0); + expect(xtzData.updated, DateTime.utc(2023, 5, 17, 15, 25, 0)); + }); + + test('toJson should convert a XtzData instance to JSON', () { + final xtzData = XtzData( + price: 3.5, + price24H: 3.7, + marketCap: 1000000000, + market24H: 900000000, + volume: 5000000, + volume24H: 6000000, + updated: DateTime.utc(2023, 5, 17, 15, 25, 0), + ); + + final json = xtzData.toJson(); + + expect(json['price'], 3.5); + expect(json['price24H'], 3.7); + expect(json['marketCap'], 1000000000.0); + expect(json['market24H'], 900000000.0); + expect(json['volume'], 5000000.0); + expect(json['volume24H'], 6000000.0); + expect(json['updated'], equals('2023-05-17T15:25:00.000Z')); + }); + }); +} diff --git a/test/app/shared/network/network_exception_test.dart b/test/app/shared/network/network_exception_test.dart index 61e2f9f11..53a02734d 100644 --- a/test/app/shared/network/network_exception_test.dart +++ b/test/app/shared/network/network_exception_test.dart @@ -102,7 +102,8 @@ void main() { ), ); final message = NetworkException.getDioException(error: error); - expect(message.message, NetworkError.NETWORK_ERROR_NOT_FOUND); + expect( + message.message, NetworkError.NETWORK_ERROR_NO_INTERNET_CONNECTION); }); test('return internalServerError response when statusCode is 500', () { @@ -119,7 +120,7 @@ void main() { final message = NetworkException.getDioException(error: error); expect( message.message, - NetworkError.NETWORK_ERROR_INTERNAL_SERVER_ERROR, + NetworkError.NETWORK_ERROR_UNEXPECTED_ERROR, ); }); @@ -249,7 +250,7 @@ void main() { test('return defaultError response when status code is not from our list', () { final message = NetworkException.handleResponse(410, null); - expect(message.message, NetworkError.NETWORK_ERROR_UNEXPECTED_ERROR); + expect(message.message, NetworkError.NETWORK_ERROR_NOT_READY); }); }); @@ -569,6 +570,37 @@ void main() { NetworkError.NETWORK_ERROR_NOT_FOUND.localise(context), ); }); + + testWidgets( + 'returns NetworkError.NETWORK_ERROR_PRECONDITION_FAILED for ' + 'NetworkException(message: NetworkError.NETWORK_ERROR_PRECONDITION_FAILED)', + (tester) async { + final MessageHandler messageHandler = NetworkException( + message: NetworkError.NETWORK_ERROR_PRECONDITION_FAILED, + ); + await tester.pumpApp(Container()); + final BuildContext context = tester.element(find.byType(Container)); + final String text = messageHandler.getMessage(context, messageHandler); + expect( + text, + NetworkError.NETWORK_ERROR_PRECONDITION_FAILED.localise(context), + ); + }); + + testWidgets( + 'returns NetworkError.NETWORK_ERROR_NOT_READY for ' + 'NetworkException(message: NetworkError.NETWORK_ERROR_NOT_READY)', + (tester) async { + final MessageHandler messageHandler = + NetworkException(message: NetworkError.NETWORK_ERROR_NOT_READY); + await tester.pumpApp(Container()); + final BuildContext context = tester.element(find.byType(Container)); + final String text = messageHandler.getMessage(context, messageHandler); + expect( + text, + NetworkError.NETWORK_ERROR_NOT_READY.localise(context), + ); + }); }); }); } diff --git a/test/app/shared/network/network_test.dart b/test/app/shared/network/network_test.dart index ae1ec7265..616ec5c15 100644 --- a/test/app/shared/network/network_test.dart +++ b/test/app/shared/network/network_test.dart @@ -1,105 +1,110 @@ -// import 'dart:convert'; - -// import 'package:altme/app/app.dart'; -// import 'package:dio/dio.dart'; -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:http_mock_adapter/http_mock_adapter.dart'; - -// import 'test_constants.dart'; - -// void main() { -// group('DioClient Class', () { -// // test('can be instantiated', () { -// // expect(getDioClient(baseUrl: baseUrl), isNotNull); -// // }); - -// group('interceptors', () { -// final dio = Dio(BaseOptions(baseUrl: baseUrl)); -// final dioAdapter = DioAdapter(dio: Dio(BaseOptions(baseUrl: baseUrl))); -// dio.httpClientAdapter = dioAdapter; -// // final interceptor = DioInterceptor(dio: dio); -// //final service = DioClient(baseUrl, dio, interceptors: [interceptor]); - -// // test('set interceptors', () { -// // expect(service.interceptors?.length, greaterThan(0)); -// // }); -// }); - -// group('exceptions', () { -// final dio = Dio(BaseOptions(baseUrl: 'http://no.domain.com')); -// final service = DioClient('http://no.domain.com', dio); -// test('socket exception in get method', () async { -// try { -// await service.get('/path'); -// } catch (e) { -// if (e is NetworkException) { -// expect( -// e.message, -// NetworkError.NETWORK_ERROR_NO_INTERNET_CONNECTION, -// ); -// } -// } -// }); - -// test('socket exception in post method', () async { -// try { -// await service.post('/path'); -// } catch (e) { -// if (e is NetworkException) { -// expect( -// e.message, -// NetworkError.NETWORK_ERROR_NO_INTERNET_CONNECTION, -// ); -// } -// } -// }); -// }); - -// group('Get Method', () { -// final dio = Dio(BaseOptions(baseUrl: baseUrl)); -// final dioAdapter = DioAdapter(dio: Dio(BaseOptions(baseUrl: baseUrl))); -// dio.httpClientAdapter = dioAdapter; -// final service = DioClient(baseUrl, dio); - -// test('Get Method Success test', () async { -// dioAdapter.onGet( -// baseUrl + testPath, -// (request) { -// return request.reply(200, successMessage); -// }, -// ); - -// final dynamic response = await service.get(baseUrl + testPath); - -// expect(response, successMessage); -// }); -// }); - -// group('Post Method', () { -// final dio = Dio(BaseOptions(baseUrl: baseUrl)); -// final dioAdapter = DioAdapter(dio: Dio(BaseOptions(baseUrl: baseUrl))); -// dio.httpClientAdapter = dioAdapter; -// final service = DioClient(baseUrl, dio); - -// test('Post Method Success test', () async { -// dioAdapter.onPost( -// baseUrl + testPath, -// (request) { -// return request.reply(201, successMessage); -// }, -// data: json.encode(testData), -// queryParameters: {}, -// headers: header, -// ); - -// final dynamic response = await service.post( -// baseUrl + testPath, -// data: json.encode(testData), -// options: Options(headers: header), -// ); - -// expect(response, successMessage); -// }); -// }); -// }); -// } +import 'dart:convert'; + +import 'package:altme/app/app.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http_mock_adapter/http_mock_adapter.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:secure_storage/secure_storage.dart'; + +import 'test_constants.dart'; + +class MockSecureStorageProvider extends Mock implements SecureStorageProvider {} + +class MockDio extends Mock implements Dio {} + +void main() { + late MockSecureStorageProvider mockSecureStorageProvider; + + final client = Dio(); + + late DioAdapter dioAdapter; + late DioClient service; + + setUp(() { + dioAdapter = + DioAdapter(dio: Dio(BaseOptions()), matcher: const UrlRequestMatcher()); + client.httpClientAdapter = dioAdapter; + mockSecureStorageProvider = MockSecureStorageProvider(); + service = DioClient( + baseUrl: baseUrl, + secureStorageProvider: mockSecureStorageProvider, + dio: client, + ); + }); + + group('DioClient Class', () { + test('can be instantiated', () { + expect(service, isNotNull); + }); + + test('set interceptors', () { + expect(service.dio.interceptors.length, greaterThan(0)); + }); + + group('exceptions', () { + test('socket exception in get method', () async { + try { + await service.get('/path'); + } catch (e) { + if (e is NetworkException) { + expect( + e.message, + NetworkError.NETWORK_ERROR_NO_INTERNET_CONNECTION, + ); + } + } + }); + + test('socket exception in post method', () async { + try { + await service.post('/path'); + } catch (e) { + if (e is NetworkException) { + expect( + e.message, + NetworkError.NETWORK_ERROR_NO_INTERNET_CONNECTION, + ); + } + } + }); + }); + + group('Get Method', () { + test('Get Method Success test', () async { + dioAdapter.onGet( + baseUrl + testPath, + (request) { + return request.reply(200, successMessage); + }, + ); + + final dynamic response = await service.get(baseUrl + testPath); + + expect(response, successMessage); + }); + }); + + group('Post Method', () { + test('Post Method Success test', () async { + dioAdapter.onPost( + baseUrl + testPath, + (request) { + return request.reply(201, successMessage); + }, + data: json.encode(testData), + queryParameters: {}, + headers: header, + ); + + final dynamic response = await service.post( + baseUrl + testPath, + data: json.encode(testData), + options: Options(headers: header), + ); + + expect(response, successMessage); + }); + }); + }); +} diff --git a/test/app/shared/network/test_constants.dart b/test/app/shared/network/test_constants.dart index 64ec8405b..5a0265582 100644 --- a/test/app/shared/network/test_constants.dart +++ b/test/app/shared/network/test_constants.dart @@ -2,7 +2,7 @@ const baseUrl = 'https://example.com/'; const successMessage = {'response': 'Success'}; const errorMessage = {'response': 'message'}; -const testPath = 'test'; +const testPath = '/test'; const testData = {'data': 'sample data'}; const header = { 'Content-Type': 'application/json', diff --git a/test/app/shared/translation/translation_test.dart b/test/app/shared/translation/translation_test.dart new file mode 100644 index 000000000..410ddf6ec --- /dev/null +++ b/test/app/shared/translation/translation_test.dart @@ -0,0 +1,51 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAppLocalizations extends Mock implements AppLocalizations {} + +void main() { + group('GetTranslation', () { + late AppLocalizations l10n; + setUp(() { + l10n = MockAppLocalizations(); + }); + + test('returns translation for the locale if available', () { + final translations = [ + Translation('en', 'Hello'), + Translation('es', 'Hola'), + ]; + when(() => l10n.localeName).thenReturn('es'); + final result = GetTranslation.getTranslation(translations, l10n); + expect(result, 'Hola'); + }); + + test('falls back to English if locale translation is not available', () { + final translations = [ + Translation('en', 'Hello'), + Translation('es', 'Hola'), + ]; + when(() => l10n.localeName).thenReturn('fr'); + final result = GetTranslation.getTranslation(translations, l10n); + expect(result, 'Hello'); + }); + + test('returns empty string if no translations are available', () { + final translations = [ + Translation('fr', 'Bonjour'), + ]; + when(() => l10n.localeName).thenReturn('es'); + final result = GetTranslation.getTranslation(translations, l10n); + expect(result, ''); + }); + + test('returns empty string if translations list is empty', () { + final translations = []; + when(() => l10n.localeName).thenReturn('en'); + final result = GetTranslation.getTranslation(translations, l10n); + expect(result, ''); + }); + }); +} diff --git a/test/app/shared/validators/wallet_address_validator_test.dart b/test/app/shared/validators/wallet_address_validator_test.dart new file mode 100644 index 000000000..0fca3d1ae --- /dev/null +++ b/test/app/shared/validators/wallet_address_validator_test.dart @@ -0,0 +1,48 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class WalletAddressValidatorImpl with WalletAddressValidator {} + +void main() { + group('WalletAddressValidator', () { + late WalletAddressValidatorImpl validator; + + setUp(() { + validator = WalletAddressValidatorImpl(); + }); + + test('returns false for null address', () { + expect(validator.validateWalletAddress(null), isFalse); + }); + + test('returns false for empty address', () { + expect(validator.validateWalletAddress(''), isFalse); + }); + + test('returns true for valid Ethereum address', () { + expect( + validator.validateWalletAddress( + '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'), + isTrue); + }); + + test('returns false for invalid Ethereum address', () { + expect(validator.validateWalletAddress('0x123'), isFalse); + }); + + test('returns true for valid Tezos address', () { + expect( + validator + .validateWalletAddress('tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb'), + isTrue); + }); + + test('returns false for invalid Tezos address', () { + expect(validator.validateWalletAddress('tz1short'), isFalse); + }); + + test('returns false for unsupported blockchain address', () { + expect(validator.validateWalletAddress('abc123'), isFalse); + }); + }); +} diff --git a/test/app/shared/widget/add_account/add_account_button_test.dart b/test/app/shared/widget/add_account/add_account_button_test.dart new file mode 100644 index 000000000..c54d24142 --- /dev/null +++ b/test/app/shared/widget/add_account/add_account_button_test.dart @@ -0,0 +1,52 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('AddAccountButton', () { + testWidgets('renders correctly', (tester) async { + await tester.pumpApp( + Scaffold( + body: AddAccountButton(onPressed: () {}), + ), + ); + + expect(find.byType(InkWell), findsOneWidget); + expect(find.byType(Container), findsOneWidget); + expect(find.byType(Column), findsOneWidget); + expect(find.byType(Row), findsOneWidget); + expect(find.byType(Image), findsOneWidget); + expect(find.byType(Text), findsNWidgets(2)); + }); + + testWidgets('displays correct text', (tester) async { + await tester.pumpApp( + Scaffold( + body: AddAccountButton(onPressed: () {}), + ), + ); + + expect(find.text('Add Account'), findsOneWidget); + expect(find.text('Create or import a new account.'), findsOneWidget); + }); + + testWidgets('triggers onPressed callback when tapped', (tester) async { + var click = false; + + await tester.pumpApp( + Scaffold( + body: AddAccountButton( + onPressed: () { + click = true; + }, + ), + ), + ); + + await tester.tap(find.byType(InkWell)); + expect(click, true); + }); + }); +} diff --git a/test/app/shared/widget/back_leading_button_test.dart b/test/app/shared/widget/back_leading_button_test.dart index 9d4440f4b..d007827aa 100644 --- a/test/app/shared/widget/back_leading_button_test.dart +++ b/test/app/shared/widget/back_leading_button_test.dart @@ -1,24 +1,25 @@ import 'package:altme/app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockingjay/mockingjay.dart'; import '../../../helpers/helpers.dart'; void main() { group('BackLeadingButton', () { testWidgets('pops when IconButton is tapped', (tester) async { - final MockNavigator navigator = MockNavigator(); + bool popTriggered = false; + await tester.pumpApp( - MockNavigatorProvider( - navigator: navigator, - child: const Material( - child: BackLeadingButton(), - ), + BackLeadingButton( + onPressed: () { + popTriggered = true; + }, ), ); await tester.tap(find.byType(IconButton)); - verify(navigator.pop).called(1); + await tester.pump(); + + expect(popTriggered, isTrue); }); }); } diff --git a/test/app/shared/widget/base/box_decoration_test.dart b/test/app/shared/widget/base/box_decoration_test.dart index cb24e7fa6..e02fd1775 100644 --- a/test/app/shared/widget/base/box_decoration_test.dart +++ b/test/app/shared/widget/base/box_decoration_test.dart @@ -175,8 +175,7 @@ void main() { }); testWidgets('hashCode function', (WidgetTester tester) async { - const hashCodeWithDefaultConstructor = 81498275; - expect(decoration.hashCode, hashCodeWithDefaultConstructor); + expect(decoration.hashCode, isA()); }); testWidgets('operator function', (WidgetTester tester) async { diff --git a/test/app/shared/widget/base/illustration_page_test.dart b/test/app/shared/widget/base/illustration_page_test.dart index 7aa16371a..ea5380ff6 100644 --- a/test/app/shared/widget/base/illustration_page_test.dart +++ b/test/app/shared/widget/base/illustration_page_test.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:altme/app/app.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -24,6 +26,11 @@ class FakeAssetBundle extends Fake implements AssetBundle { '''; + @override + Future load(String key) async { + return ByteData.sublistView(Uint8List.fromList(svgStr.codeUnits)); + } + @override Future loadString(String key, {bool cache = true}) async { return svgStr; @@ -41,27 +48,27 @@ void main() { description: 'description', backgroundColor: Colors.blueGrey, onPressed: () {}, + key: GlobalKey(), ), ), ); } group('IllustrationPage widget', () { - testWidgets('all sub widgets founded', (WidgetTester tester) async { - await tester.pumpWidget( - makeTestableWidget(), - ); + testWidgets('all sub widgets found', (WidgetTester tester) async { + await tester.pumpWidget(makeTestableWidget()); + await tester.pumpAndSettle(); + expect(find.byType(BaseIllustrationPage), findsOneWidget); expect(find.byType(BasePage), findsOneWidget); - expect(find.byType(MyElevatedButton), findsOneWidget); + expect(find.byType(MyOutlinedButton), findsOneWidget); expect(find.byType(SvgPicture), findsOneWidget); }); testWidgets('verify property of widget set correctly', (WidgetTester tester) async { - await tester.pumpWidget( - makeTestableWidget(), - ); + await tester.pumpWidget(makeTestableWidget()); + await tester.pumpAndSettle(); expect(find.byType(BaseIllustrationPage), findsOneWidget); final baseIllustrationPage = tester .widget(find.byType(BaseIllustrationPage)); diff --git a/test/app/shared/widget/base/markdown_page_test.dart b/test/app/shared/widget/base/markdown_page_test.dart index 067f70fa1..1c034b907 100644 --- a/test/app/shared/widget/base/markdown_page_test.dart +++ b/test/app/shared/widget/base/markdown_page_test.dart @@ -1,10 +1,5 @@ -import 'dart:convert'; -import 'dart:typed_data'; - import 'package:altme/app/app.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart' as services; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -13,95 +8,39 @@ void main() { return MaterialApp( home: MarkdownPage( title: 'title', - file: 'assets/notices.md', + file: 'assets/notices/notices_en.md', key: GlobalKey(), ), ); } - bool isFirstCase = true; - - setUpAll(() { - ///do mock the assets - TestWidgetsFlutterBinding.ensureInitialized(); - services.ServicesBinding.instance.defaultBinaryMessenger - .setMockMessageHandler('flutter/assets', (message) { - if (isFirstCase) { - final Uint8List encoded = - utf8.encoder.convert('[Link `with nested code` Text](href)'); - return Future.value(encoded.buffer.asByteData()); - } else { - return null; - } - }); - }); - - group('MarkdownPage widget', () { - late Widget widget; - - setUp(() { - widget = makeTestableWidget(); - }); - - testWidgets('verify property set correctly', (WidgetTester tester) async { - await tester.pumpWidget(widget); - expect(find.byType(MarkdownPage), findsOneWidget); - final markdownWidget = - tester.widget(find.byType(MarkdownPage)); - expect(markdownWidget.file, 'assets/notices.md'); - expect(markdownWidget.title, 'title'); - expect(markdownWidget.key, isA()); - }); - - testWidgets('sub widget founded', (WidgetTester tester) async { - await tester.pumpWidget(widget); - expect(find.byType(MarkdownPage), findsOneWidget); - expect(find.byType(BasePage), findsOneWidget); - expect(find.byType(BackLeadingButton), findsOneWidget); - expect(find.byType(Spinner), findsOneWidget); - await tester.pumpAndSettle(const Duration(seconds: 1)); - expect(find.byType(Markdown), findsOneWidget); - expect(find.byType(Spinner), findsNothing); - }); + testWidgets('MarkdownPage should load and display markdown content', + (WidgetTester tester) async { + await tester.pumpWidget(makeTestableWidget()); - testWidgets('tap on markdown widget', (WidgetTester tester) async { - await tester.pumpWidget(widget); - await tester.pumpAndSettle(); - expect(find.byType(Markdown), findsOneWidget); - final richText = tester.widget(find.byType(RichText).first); - final span = richText.text as TextSpan; + expect(find.byType(Spinner), findsOneWidget); - final gestureRecognizerTypes = []; - span.visitChildren((InlineSpan inlineSpan) { - if (inlineSpan is TextSpan) { - final recognizer = inlineSpan.recognizer; - gestureRecognizerTypes.add(recognizer?.runtimeType ?? Null); - if (recognizer != null && recognizer is TapGestureRecognizer) { - recognizer.onTap!(); - } - } - return true; - }); - - expect(span.children!.length, 3); - expect(gestureRecognizerTypes.length, 3); - expect(gestureRecognizerTypes, everyElement(TapGestureRecognizer)); - await tester.pumpAndSettle(); - }); + await tester.pumpAndSettle(); + expect(find.byType(Markdown), findsOneWidget); + expect( + find.text( + 'The Altme wallet is an open source project under Apache 2.0 licence', + ), + findsOneWidget, + ); }); - group('MarkdownPage bad state work properly', () { - final widget = MaterialApp( - home: MarkdownPage( - title: 'title', - file: 'assets1/notices.md', + testWidgets('MarkdownPage should handle loading errors', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: MarkdownPage(title: 'Test', file: 'invalid_file.md'), ), ); - testWidgets('trigger message ', (WidgetTester tester) async { - isFirstCase = false; - await tester.pumpWidget(widget); - await tester.pumpAndSettle(); - }); + expect(find.byType(Spinner), findsOneWidget); + + await tester.pumpAndSettle(); + expect(find.byType(Markdown), findsNothing); }); } diff --git a/test/app/shared/widget/base/otp_textfield_test.dart b/test/app/shared/widget/base/otp_textfield_test.dart new file mode 100644 index 000000000..390068e64 --- /dev/null +++ b/test/app/shared/widget/base/otp_textfield_test.dart @@ -0,0 +1,98 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('OtpTextField', () { + late List controllers; + + setUp(() { + controllers = List.generate( + 6, + (index) => TextEditingController( + text: '', + ), + ); + }); + + testWidgets('renders correctly', (tester) async { + await tester.pumpApp( + Scaffold( + body: OtpTextField( + controllers: controllers, + index: 0, + autoFocus: false, + ), + ), + ); + + expect(find.byType(TextField), findsOneWidget); + }); + + testWidgets('autofocus works correctly', (tester) async { + await tester.pumpApp( + Scaffold( + body: OtpTextField( + controllers: controllers, + index: 0, + autoFocus: true, + ), + ), + ); + + await tester.pump(); + + final finder = find.byType(TextField); + expect(finder, findsOneWidget); + final textField = tester.widget(finder); + expect(textField.autofocus, true); + }); + + testWidgets('moves to next field when a digit is entered', (tester) async { + await tester.pumpApp( + Scaffold( + body: Row( + children: List.generate(6, (index) { + return OtpTextField( + controllers: controllers, + index: index, + autoFocus: index == 0, + ); + }), + ), + ), + ); + + await tester.enterText(find.byType(TextField).at(1), '1'); + await tester.pump(); + + expect(controllers[1].text, '1'); + expect( + FocusScope.of(tester.element(find.byType(TextField).at(1))).hasFocus, + true); + }); + + testWidgets('onTap moves cursor to end of text', (tester) async { + await tester.pumpApp( + Scaffold( + body: OtpTextField( + controllers: controllers, + index: 0, + autoFocus: true, + ), + ), + ); + await tester.tap(find.byType(TextField)); + await tester.pump(); + + expect( + controllers[0].selection, + TextSelection.fromPosition( + TextPosition(offset: controllers[0].text.length)), + ); + }); + }); +} diff --git a/test/app/shared/widget/base/page_test.dart b/test/app/shared/widget/base/page_test.dart index 45491dc0c..c8800f802 100644 --- a/test/app/shared/widget/base/page_test.dart +++ b/test/app/shared/widget/base/page_test.dart @@ -7,18 +7,48 @@ import '../../../../helpers/helpers.dart'; void main() { group('BasePage', () { group('CustomAppBar', () { - testWidgets('does not renders CustomAppBar when title is null', - (tester) async { - await tester.pumpApp(BasePage(body: Container(), title: '')); + testWidgets( + 'does not renders CustomAppBar when title, titleTrailing and' + ' titleLeading are null', (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + title: null, + titleTrailing: null, + titleLeading: null, + ), + ); expect(find.byType(CustomAppBar), findsNothing); }); - testWidgets('does not renders CustomAppBar when title is empty', - (tester) async { - await tester.pumpApp(BasePage(body: Container(), title: '')); + testWidgets( + 'does not renders CustomAppBar when title, titleTrailing and' + ' titleLeading are null and secure screen is true', (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + title: null, + titleTrailing: null, + titleLeading: null, + secureScreen: true, + ), + ); expect(find.byType(CustomAppBar), findsNothing); }); + testWidgets( + 'renders CustomAppBar when title is provided and secure screen is true', + (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + title: 'I am title', + secureScreen: true, + ), + ); + expect(find.byType(CustomAppBar), findsOneWidget); + }); + testWidgets('renders CustomAppBar when title is provided', (tester) async { await tester.pumpApp(BasePage(body: Container(), title: 'I am title')); @@ -46,15 +76,151 @@ void main() { }); group('useSafeArea', () { - testWidgets('renders SafeArea when useSafeArea is true', (tester) async { - await tester.pumpApp(BasePage(body: Container(), useSafeArea: true)); - expect(find.byType(SafeArea), findsOneWidget); + group('renders SafeArea correctly when useSafeArea is true ', () { + testWidgets('navigation is provided', (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: true, + navigation: Container(), + ), + ); + expect(find.byType(SafeArea), findsNWidgets(2)); + }); + + testWidgets('scrollView is true', (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: true, + scrollView: true, + ), + ); + expect(find.byType(SafeArea), findsOneWidget); + }); + + testWidgets('scrollView is false', (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: true, + scrollView: false, + ), + ); + expect(find.byType(SafeArea), findsOneWidget); + }); + testWidgets('navigation is provided and secure screen is true', + (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: true, + secureScreen: true, + navigation: Container(), + ), + ); + expect(find.byType(SafeArea), findsNWidgets(2)); + }); + + testWidgets('scrollView is true and secure screen is true', + (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: true, + secureScreen: true, + scrollView: true, + ), + ); + expect(find.byType(SafeArea), findsOneWidget); + }); + + testWidgets('scrollView is false and secure screen is true', + (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: true, + scrollView: false, + secureScreen: true, + ), + ); + expect(find.byType(SafeArea), findsOneWidget); + }); }); - testWidgets('does not renders SafeArea when useSafeArea is false', - (tester) async { - await tester.pumpApp(BasePage(body: Container(), useSafeArea: false)); - expect(find.byType(SafeArea), findsNothing); + group('renders SafeArea correctly when useSafeArea is false ', () { + testWidgets(' navigation is provided', (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: false, + navigation: Container(), + ), + ); + expect(find.byType(SafeArea), findsNothing); + }); + + testWidgets('scrollView is true', (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: false, + scrollView: true, + ), + ); + expect(find.byType(SafeArea), findsNothing); + }); + + testWidgets('scrollView is false', (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: false, + scrollView: false, + ), + ); + expect(find.byType(SafeArea), findsNothing); + }); + + testWidgets('navigation is provided and secure screen is true', + (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: false, + secureScreen: true, + navigation: Container(), + ), + ); + expect(find.byType(SafeArea), findsNothing); + }); + + testWidgets('scrollView is true and secure screen is true', + (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: false, + secureScreen: true, + scrollView: true, + ), + ); + expect(find.byType(SafeArea), findsNothing); + }); + + testWidgets('scrollView is false and secure screen is true', + (tester) async { + await tester.pumpApp( + BasePage( + body: Container(), + useSafeArea: false, + scrollView: false, + secureScreen: true, + ), + ); + expect(find.byType(SafeArea), findsNothing); + }); }); }); }); diff --git a/test/app/shared/widget/base/text_field_test.dart b/test/app/shared/widget/base/text_field_test.dart new file mode 100644 index 000000000..7cb47c1c9 --- /dev/null +++ b/test/app/shared/widget/base/text_field_test.dart @@ -0,0 +1,126 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('BaseTextField Widget Tests', () { + late TextEditingController controller; + + setUp(() { + controller = TextEditingController(); + }); + + testWidgets('renders BaseTextField with default properties', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: BaseTextField( + controller: controller, + ), + ), + ); + + expect(find.byType(TextFormField), findsOneWidget); + expect(find.byType(SizedBox), findsNWidgets(2)); + }); + + testWidgets('renders BaseTextField with label', + (WidgetTester tester) async { + const label = 'Test Label'; + + await tester.pumpApp( + Scaffold( + body: BaseTextField( + controller: controller, + label: label, + ), + ), + ); + + expect(find.text(label), findsOneWidget); + }); + + testWidgets('renders BaseTextField with hint', (WidgetTester tester) async { + const hint = 'Test Hint'; + + await tester.pumpApp( + Scaffold( + body: BaseTextField( + controller: controller, + hint: hint, + ), + ), + ); + + expect(find.text(hint), findsOneWidget); + }); + + testWidgets('renders BaseTextField with error text', + (WidgetTester tester) async { + const error = 'Test Error'; + + await tester.pumpApp( + Scaffold( + body: BaseTextField( + controller: controller, + error: error, + ), + ), + ); + + expect(find.text(error), findsOneWidget); + }); + + testWidgets('renders BaseTextField with prefix and suffix icons', + (WidgetTester tester) async { + const prefixIcon = Icon(Icons.email); + const suffixIcon = Icon(Icons.visibility); + + await tester.pumpApp( + Scaffold( + body: BaseTextField( + controller: controller, + prefixIcon: prefixIcon, + suffixIcon: suffixIcon, + ), + ), + ); + + expect(find.byIcon(Icons.email), findsOneWidget); + expect(find.byIcon(Icons.visibility), findsOneWidget); + }); + + testWidgets('input text updates the controller', + (WidgetTester tester) async { + const inputText = 'Hello, Flutter!'; + + await tester.pumpApp( + Scaffold( + body: BaseTextField( + controller: controller, + ), + ), + ); + + await tester.enterText(find.byType(TextFormField), inputText); + expect(controller.text, inputText); + }); + + testWidgets('obscureText property works correctly', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: BaseTextField( + controller: controller, + obscureText: true, + ), + ), + ); + + final TextField textField = tester.widget(find.byType(TextField)); + expect(textField.obscureText, isTrue); + }); + }); +} diff --git a/test/app/shared/widget/copy_button_test.dart b/test/app/shared/widget/copy_button_test.dart new file mode 100644 index 000000000..ee810631c --- /dev/null +++ b/test/app/shared/widget/copy_button_test.dart @@ -0,0 +1,32 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group('CopyButton', () { + testWidgets('CopyButton displays correctly', (tester) async { + await tester.pumpApp(const Scaffold(body: CopyButton())); + await tester.pumpAndSettle(); + expect(find.byType(Text), findsOneWidget); + expect(find.text('Copy'), findsOneWidget); + }); + + testWidgets('CopyButton responds to tap correctly', (tester) async { + bool triggerred = false; + await tester.pumpApp( + Scaffold( + body: CopyButton( + onTap: () { + triggerred = true; + }, + ), + ), + ); + await tester.tap(find.byType(InkWell)); + await tester.pumpAndSettle(); + expect(triggerred, isTrue); + }); + }); +} diff --git a/test/app/shared/widget/custom_listtile_card_test.dart b/test/app/shared/widget/custom_listtile_card_test.dart new file mode 100644 index 000000000..11a91f2df --- /dev/null +++ b/test/app/shared/widget/custom_listtile_card_test.dart @@ -0,0 +1,55 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('CustomListTileCard displays correctly and responds to tap', + (WidgetTester tester) async { + bool triggerred = false; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CustomListTileCard( + title: 'Test Title', + subTitle: 'Test Subtitle', + imageAssetPath: 'assets/launcher_icon.png', + recommended: true, + onTap: () { + triggerred = true; + }, + ), + ), + ), + ); + + expect(find.text('Test Title'), findsOneWidget); + expect(find.text('Test Subtitle'), findsOneWidget); + expect(find.byType(Image), findsOneWidget); + expect(find.byType(Icon), findsOneWidget); + + expect(find.byIcon(Icons.thumb_up), findsOneWidget); + + await tester.tap(find.byType(ListTile)); + expect(triggerred, isTrue); + }); + + testWidgets( + 'CustomListTileCard does not display recommended icon when' + ' recommended is false', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CustomListTileCard( + title: 'Test Title', + subTitle: 'Test Subtitle', + imageAssetPath: 'assets/launcher_icon.png', + recommended: false, + onTap: () {}, + ), + ), + ), + ); + + expect(find.byIcon(Icons.thumb_up), findsNothing); + }); +} diff --git a/test/app/shared/widget/default_dialog_test.dart b/test/app/shared/widget/default_dialog_test.dart new file mode 100644 index 000000000..b2081c46d --- /dev/null +++ b/test/app/shared/widget/default_dialog_test.dart @@ -0,0 +1,53 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + testWidgets('DefaultDialog displays title and description', + (WidgetTester tester) async { + const title = 'Test Title'; + const description = 'Test Description'; + + await tester.pumpApp( + const Scaffold( + body: DefaultDialog( + title: title, + description: description, + ), + ), + ); + + expect(find.text(title), findsOneWidget); + expect(find.text(description), findsOneWidget); + }); + + testWidgets( + 'DefaultDialog displays button with label and handles button click', + (WidgetTester tester) async { + bool triggerred = false; + const title = 'Test Title'; + const description = 'Test Description'; + const buttonLabel = 'Button'; + + await tester.pumpApp( + Scaffold( + body: DefaultDialog( + title: title, + description: description, + buttonLabel: buttonLabel, + onButtonClick: () { + triggerred = true; + }, + ), + ), + ); + + expect(find.byType(MyElevatedButton), findsOneWidget); + + await tester.tap(find.byType(MyElevatedButton)); + await tester.pumpAndSettle(); + expect(triggerred, isTrue); + }); +} diff --git a/test/app/shared/widget/dialog/becareful_dialog_test.dart b/test/app/shared/widget/dialog/becareful_dialog_test.dart new file mode 100644 index 000000000..1afd7dc66 --- /dev/null +++ b/test/app/shared/widget/dialog/becareful_dialog_test.dart @@ -0,0 +1,140 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('BeCarefulDialog Widget Tests', () { + testWidgets('renders BeCarefulDialog with title', + (WidgetTester tester) async { + const title = 'Test Title'; + + await tester.pumpApp( + const Scaffold( + body: BeCarefulDialog( + title: title, + ), + ), + ); + + expect(find.text(title), findsOneWidget); + }); + + testWidgets('renders BeCarefulDialog with subtitle', + (WidgetTester tester) async { + const title = 'Test Title'; + const subtitle = 'Test Subtitle'; + + await tester.pumpApp( + const Scaffold( + body: BeCarefulDialog( + title: title, + subtitle: subtitle, + ), + ), + ); + + expect(find.text(title), findsOneWidget); + expect(find.text(subtitle), findsOneWidget); + }); + + testWidgets('renders BeCarefulDialog with yes and no buttons', + (WidgetTester tester) async { + const title = 'Test Title'; + const yesText = 'Yes'; + const noText = 'No'; + + await tester.pumpApp( + const Scaffold( + body: BeCarefulDialog( + title: title, + yes: yesText, + no: noText, + ), + ), + ); + + expect(find.text(yesText.toUpperCase()), findsOneWidget); + expect(find.text(noText.toUpperCase()), findsOneWidget); + }); + + testWidgets('clicking no button closes the dialog', + (WidgetTester tester) async { + const title = 'Test Title'; + const noText = 'No'; + + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return ElevatedButton( + onPressed: () { + BeCarefulDialog.show( + context: context, + title: title, + no: noText, + ); + }, + child: const Text('Show Dialog'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + expect(find.text(title), findsOneWidget); + expect(find.text(noText.toUpperCase()), findsOneWidget); + + await tester.tap(find.text(noText.toUpperCase())); + await tester.pumpAndSettle(); + + expect(find.text(title), findsNothing); + }); + + testWidgets( + 'clicking yes button triggers onContinueClick and closes the dialog', + (WidgetTester tester) async { + const title = 'Test Title'; + const yesText = 'Yes'; + bool onContinueClicked = false; + + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return ElevatedButton( + onPressed: () { + BeCarefulDialog.show( + context: context, + title: title, + yes: yesText, + onContinueClick: () { + onContinueClicked = true; + }, + ); + }, + child: const Text('Show Dialog'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + expect(find.text(title), findsOneWidget); + expect(find.text(yesText.toUpperCase()), findsOneWidget); + + await tester.tap(find.text(yesText.toUpperCase())); + await tester.pumpAndSettle(); + + expect(find.text(title), findsNothing); + expect(onContinueClicked, isTrue); + }); + }); +} diff --git a/test/app/shared/widget/dialog/confirm_dialog_test.dart b/test/app/shared/widget/dialog/confirm_dialog_test.dart new file mode 100644 index 000000000..fbe07bd1f --- /dev/null +++ b/test/app/shared/widget/dialog/confirm_dialog_test.dart @@ -0,0 +1,150 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('ConfirmDialog Widget Tests', () { + testWidgets('renders ConfirmDialog with title', + (WidgetTester tester) async { + const title = 'Test Title'; + + await tester.pumpApp( + const ConfirmDialog( + title: title, + ), + ); + + expect(find.text(title), findsOneWidget); + }); + + testWidgets('renders ConfirmDialog with subtitle', + (WidgetTester tester) async { + const title = 'Test Title'; + const subtitle = 'Test Subtitle'; + + await tester.pumpApp( + const ConfirmDialog( + title: title, + subtitle: subtitle, + ), + ); + + expect(find.text(title), findsOneWidget); + expect(find.text(subtitle), findsOneWidget); + }); + + testWidgets('renders ConfirmDialog with yes and no buttons', + (WidgetTester tester) async { + const title = 'Test Title'; + const yesText = 'Yes'; + const noText = 'No'; + + await tester.pumpApp( + const ConfirmDialog( + title: title, + yes: yesText, + no: noText, + ), + ); + + expect(find.text(yesText.toUpperCase()), findsOneWidget); + expect(find.text(noText.toUpperCase()), findsOneWidget); + }); + + testWidgets('clicking no button closes the dialog with false', + (WidgetTester tester) async { + const title = 'Test Title'; + const noText = 'No'; + + await tester.pumpApp( + Builder( + builder: (context) { + return ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (_) => const ConfirmDialog( + title: title, + no: noText, + ), + ); + }, + child: const Text('Show Dialog'), + ); + }, + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + expect(find.text(title), findsOneWidget); + expect(find.text(noText.toUpperCase()), findsOneWidget); + + await tester + .tap(find.widgetWithText(MyOutlinedButton, noText.toUpperCase())); + await tester.pumpAndSettle(); + + expect(find.text(title), findsNothing); + }); + + testWidgets('clicking yes button closes the dialog with true', + (WidgetTester tester) async { + const title = 'Test Title'; + const yesText = 'Yes'; + bool? result; + + await tester.pumpApp( + Builder( + builder: (context) { + return ElevatedButton( + onPressed: () async { + result = await showDialog( + context: context, + builder: (_) => const ConfirmDialog( + title: title, + yes: yesText, + ), + ); + }, + child: const Text('Show Dialog'), + ); + }, + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + expect(find.text(title), findsOneWidget); + expect(find.text(yesText.toUpperCase()), findsOneWidget); + + await tester + .tap(find.widgetWithText(MyElevatedButton, yesText.toUpperCase())); + await tester.pumpAndSettle(); + + expect(find.text(title), findsNothing); + expect(result, isTrue); + }); + + testWidgets( + 'ConfirmDialog shows only yes button when showNoButton is false', + (WidgetTester tester) async { + const title = 'Test Title'; + const yesText = 'Yes'; + const noText = 'No'; + + await tester.pumpApp(const ConfirmDialog( + title: title, + yes: yesText, + no: noText, + showNoButton: false, + )); + + expect(find.text(yesText.toUpperCase()), findsOneWidget); + expect(find.text(noText.toUpperCase()), findsNothing); + }); + }); +} diff --git a/test/app/shared/widget/dialog/error_details_dialog_test.dart b/test/app/shared/widget/dialog/error_details_dialog_test.dart new file mode 100644 index 000000000..9518cc48f --- /dev/null +++ b/test/app/shared/widget/dialog/error_details_dialog_test.dart @@ -0,0 +1,71 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('ErrorDetailsDialog Widget Tests', () { + testWidgets('renders ErrorDetailsDialog with error description', + (WidgetTester tester) async { + const errorDescription = 'An error occurred'; + + await tester.pumpApp( + const ErrorDetailsDialog( + erroDescription: errorDescription, + ), + ); + + expect(find.text(errorDescription), findsOneWidget); + }); + + testWidgets('renders ErrorDetailsDialog with more details link', + (WidgetTester tester) async { + const errorDescription = 'An error occurred'; + const errorUrl = 'https://example.com'; + + await tester.pumpApp( + const ErrorDetailsDialog( + erroDescription: errorDescription, + erroUrl: errorUrl, + ), + ); + + expect(find.text(errorDescription), findsOneWidget); + expect(find.text('More Details'), findsOneWidget); + }); + + testWidgets('clicking OK button closes the dialog', + (WidgetTester tester) async { + const errorDescription = 'An error occurred'; + + await tester.pumpApp( + Builder( + builder: (context) { + return ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (_) => const ErrorDetailsDialog( + erroDescription: errorDescription, + ), + ); + }, + child: const Text('Show Dialog'), + ); + }, + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + expect(find.text(errorDescription), findsOneWidget); + + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + expect(find.text(errorDescription), findsNothing); + }); + }); +} diff --git a/test/app/shared/widget/dialog/error_dialog_test.dart b/test/app/shared/widget/dialog/error_dialog_test.dart new file mode 100644 index 000000000..1e383c976 --- /dev/null +++ b/test/app/shared/widget/dialog/error_dialog_test.dart @@ -0,0 +1,56 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('ErrorDialog Widget Tests', () { + testWidgets( + 'displays the ErrorDialog with the correct title and description', + (WidgetTester tester) async { + const title = 'Error Title'; + const description = 'An error occurred'; + + await tester.pumpApp( + const Scaffold( + body: ErrorDialog( + title: title, + erroDescription: description, + ), + ), + ); + + expect(find.text(title), findsOneWidget); + expect(find.text('More Details'.toUpperCase()), findsOneWidget); + + await tester.tap(find.text('More Details'.toUpperCase())); + await tester.pumpAndSettle(); + + expect(find.byType(ErrorDetailsDialog), findsOneWidget); + expect(find.text(description), findsOneWidget); + }); + + testWidgets('Popup works correctly', (WidgetTester tester) async { + const title = 'Error Title'; + const description = 'An error occurred'; + + await tester.pumpApp( + const Scaffold( + body: ErrorDialog( + title: title, + erroDescription: description, + ), + ), + ); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text('OK'), findsOneWidget); + + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + }); + }); +} diff --git a/test/app/shared/widget/dialog/text_field_dialog_test.dart b/test/app/shared/widget/dialog/text_field_dialog_test.dart new file mode 100644 index 000000000..267382bea --- /dev/null +++ b/test/app/shared/widget/dialog/text_field_dialog_test.dart @@ -0,0 +1,54 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + group('Text Field Dialog', () { + testWidgets('TextFieldDialog widget test', (WidgetTester tester) async { + await tester.pumpApp( + const TextFieldDialog( + title: 'Title', + label: 'Label', + subtitle: 'Subtitle', + initialValue: 'Initial Value', + yes: 'Yes', + no: 'No', + ), + ); + // Verify that the title, label, subtitle, and initial value are displayed. + expect(find.text('Title'), findsOneWidget); + expect(find.text('Label'), findsOneWidget); + expect(find.text('Subtitle'), findsOneWidget); + expect(find.text('Initial Value'), findsOneWidget); + + // Verify that the Yes and No buttons are displayed. + expect(find.text('Yes'.toUpperCase()), findsOneWidget); + expect(find.text('No'.toUpperCase()), findsOneWidget); + + // Tap on the Yes button and verify that the text is popped. + await tester.tap(find.text('Yes'.toUpperCase())); + await tester.pumpAndSettle(); + expect(find.text('Initial Value'), findsNothing); + }); + + testWidgets('Popup works correctly', (WidgetTester tester) async { + await tester.pumpApp( + const TextFieldDialog( + title: 'Title', + label: 'Label', + subtitle: 'Subtitle', + initialValue: 'Initial Value', + yes: 'Yes', + no: 'No', + ), + ); + + expect(find.text('No'.toUpperCase()), findsOneWidget); + + await tester.tap(find.text('No'.toUpperCase())); + await tester.pumpAndSettle(); + expect(find.text('No'.toUpperCase()), findsNothing); + }); + }); +} diff --git a/test/app/shared/widget/dialog_close_button_test.dart b/test/app/shared/widget/dialog_close_button_test.dart new file mode 100644 index 000000000..6d1dc47d9 --- /dev/null +++ b/test/app/shared/widget/dialog_close_button_test.dart @@ -0,0 +1,62 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + testWidgets('DialogCloseButton displays text and icon', + (WidgetTester tester) async { + await tester.pumpApp( + const Scaffold(body: DialogCloseButton()), + ); + + expect(find.text('Close'), findsOneWidget); + expect(find.byIcon(Icons.close), findsOneWidget); + }); + + testWidgets('DialogCloseButton does not display text when showText is false', + (WidgetTester tester) async { + await tester.pumpApp( + const Scaffold(body: DialogCloseButton(showText: false)), + ); + + expect(find.text('Close'), findsNothing); + expect(find.byIcon(Icons.close), findsOneWidget); + }); + + testWidgets('DialogCloseButton closes the dialog when tapped', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) => Column( + children: [ + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => const AlertDialog( + content: DialogCloseButton(), + ), + ); + }, + child: const Text('Open Dialog'), + ), + ], + ), + ), + ), + ); + + await tester.tap(find.text('Open Dialog')); + await tester.pumpAndSettle(); + + expect(find.byType(DialogCloseButton), findsOneWidget); + + await tester.tap(find.text('Close')); + await tester.pumpAndSettle(); + + expect(find.byType(DialogCloseButton), findsNothing); + }); +} diff --git a/test/app/shared/widget/display_terms_test.dart b/test/app/shared/widget/display_terms_test.dart index 599668ba8..11e263fb2 100644 --- a/test/app/shared/widget/display_terms_test.dart +++ b/test/app/shared/widget/display_terms_test.dart @@ -16,21 +16,12 @@ void main() { expect(appState.log, isNotNull); }); - /// we disabled display of privacy asset in phone language for now - testWidgets('path of privacy policy matches with device language', + testWidgets('returns privacy policies and terms for empty string', (tester) async { await tester.pumpApp(const DisplayTermsofUse()); final dynamic appState = tester.state(find.byType(DisplayTermsofUse)); - appState.setPath('it'); - expect(appState.path, 'assets/privacy/privacy_en.md'); - }); - - testWidgets('returns english privacy policy for empty string', - (tester) async { - await tester.pumpApp(const DisplayTermsofUse()); - final dynamic appState = tester.state(find.byType(DisplayTermsofUse)); - appState.setPath(''); - expect(appState.path, 'assets/privacy/privacy_en.md'); + final list = await appState.getBodyData(''); + expect(list, isA>()); }); }); } diff --git a/test/app/shared/widget/error_view_test.dart b/test/app/shared/widget/error_view_test.dart new file mode 100644 index 000000000..49788ac30 --- /dev/null +++ b/test/app/shared/widget/error_view_test.dart @@ -0,0 +1,30 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + testWidgets('ErrorView displays message and try again button', + (WidgetTester tester) async { + const testMessage = 'An error occurred'; + bool wasButtonTapped = false; + + await tester.pumpApp( + ErrorView( + message: testMessage, + onTap: () { + wasButtonTapped = true; + }, + ), + ); + + expect(find.text(testMessage), findsOneWidget); + + expect(find.byType(MyOutlinedButton), findsOneWidget); + + await tester.tap(find.byType(MyOutlinedButton)); + await tester.pumpAndSettle(); + + expect(wasButtonTapped, isTrue); + }); +} diff --git a/test/app/shared/widget/grouped_section_test.dart b/test/app/shared/widget/grouped_section_test.dart new file mode 100644 index 000000000..00c38100e --- /dev/null +++ b/test/app/shared/widget/grouped_section_test.dart @@ -0,0 +1,29 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + testWidgets('GroupedSection renders children correctly', + (WidgetTester tester) async { + final testChildren = [ + const Text('Child 1'), + const Text('Child 2'), + const Text('Child 3'), + ]; + + await tester.pumpApp( + Scaffold( + body: GroupedSection( + children: testChildren, + ), + ), + ); + + // Verify the children are displayed + for (final child in testChildren) { + expect(find.byWidget(child), findsOneWidget); + } + }); +} diff --git a/test/app/shared/widget/image_from_network_test.dart b/test/app/shared/widget/image_from_network_test.dart index 738b1a902..174719839 100644 --- a/test/app/shared/widget/image_from_network_test.dart +++ b/test/app/shared/widget/image_from_network_test.dart @@ -34,12 +34,12 @@ void main() { final imageFromNetwork = tester .widget(find.byType(CachedImageFromNetwork)); expect(imageFromNetwork.url, networkImageUrl); - expect(imageFromNetwork.fit, null); + expect(imageFromNetwork.fit, BoxFit.cover); expect(imageFromNetwork.key, widgetKey); }); }); - testWidgets('get SizedBox on errorBuilder', (tester) async { + testWidgets('get ColoredBox on errorBuilder', (tester) async { await mockNetworkImagesFor(() async { await tester.pumpWidget(makeTestableWidget()); final image = tester.widget(find.byType(Image)); @@ -49,7 +49,7 @@ void main() { Object(), StackTrace.fromString('stackTraceString'), ); - expect(result, isA()); + expect(result, isA()); }); }); } diff --git a/test/app/shared/widget/imported_tag_test.dart b/test/app/shared/widget/imported_tag_test.dart new file mode 100644 index 000000000..139023b41 --- /dev/null +++ b/test/app/shared/widget/imported_tag_test.dart @@ -0,0 +1,19 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group('ImportedTag', () { + testWidgets('renders importedTag', (tester) async { + await tester.pumpApp(const ImportedTag()); + expect(find.byType(Text), findsOneWidget); + }); + + testWidgets('renders value correctly', (tester) async { + await tester.pumpApp(const ImportedTag()); + expect(find.text('IMPORTED'), findsOneWidget); + }); + }); +} diff --git a/test/app/shared/widget/m_webview/cubit/m_webview_cubit_test.dart b/test/app/shared/widget/m_webview/cubit/m_webview_cubit_test.dart new file mode 100644 index 000000000..c8c9953db --- /dev/null +++ b/test/app/shared/widget/m_webview/cubit/m_webview_cubit_test.dart @@ -0,0 +1,28 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('MWebViewCubit', () { + late MWebViewCubit cubit; + + setUp(() { + cubit = MWebViewCubit(); + }); + + tearDown(() { + cubit.close(); + }); + + test('initial state is true', () { + expect(cubit.state, true); + }); + + test('emits new state when setLoading is called', () { + expect(cubit.state, true); + cubit.setLoading(isLoading: false); + expect(cubit.state, false); + cubit.setLoading(isLoading: true); + expect(cubit.state, true); + }); + }); +} diff --git a/test/app/shared/widget/mnemonic_test.dart b/test/app/shared/widget/mnemonic_test.dart new file mode 100644 index 000000000..52f17dbed --- /dev/null +++ b/test/app/shared/widget/mnemonic_test.dart @@ -0,0 +1,37 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + testWidgets('MnemonicDisplay shows the correct words', + (WidgetTester tester) async { + final mnemonic = [ + 'apple', + 'banana', + 'cherry', + 'date', + 'elderberry', + 'fig', + 'grape', + 'honeydew', + 'kiwi', + 'lemon', + 'mango', + 'nectarine', + ]; + + await tester.pumpApp( + Scaffold( + body: MnemonicDisplay(mnemonic: mnemonic), + ), + ); + + await tester.pumpAndSettle(); + + for (int i = 0; i < mnemonic.length; i++) { + expect(find.text('${i + 1}. ${mnemonic[i]}'), findsOneWidget); + } + }); +} diff --git a/test/app/shared/widget/my_rich_text_test.dart b/test/app/shared/widget/my_rich_text_test.dart new file mode 100644 index 000000000..bdd263d26 --- /dev/null +++ b/test/app/shared/widget/my_rich_text_test.dart @@ -0,0 +1,51 @@ +import 'package:altme/app/app.dart'; +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + testWidgets('MyRichText displays the correct rich text', + (WidgetTester tester) async { + const textSpan = TextSpan( + text: 'Hello ', + children: [ + TextSpan( + text: 'World', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ); + + await tester + .pumpApp(const Scaffold(body: MyRichText(text: textSpan, maxLines: 1))); + + await tester.pumpAndSettle(); + + expect(find.text('Hello World'), findsOneWidget); + }); + + testWidgets('MyRichText respects maxLines', (WidgetTester tester) async { + const longTextSpan = TextSpan( + text: 'This is a long text that should be truncated', + ); + + // Build the widget + await tester.pumpApp( + const Scaffold( + body: SizedBox( + width: 100, + child: MyRichText( + text: longTextSpan, + maxLines: 1, + ), + ), + ), + ); + + final richTextWidget = + tester.widget(find.byType(AutoSizeText)); + expect(richTextWidget.maxLines, 1); + }); +} diff --git a/test/app/shared/widget/numeric_keyboard_test.dart b/test/app/shared/widget/numeric_keyboard_test.dart new file mode 100644 index 000000000..4c44b452c --- /dev/null +++ b/test/app/shared/widget/numeric_keyboard_test.dart @@ -0,0 +1,162 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import '../../../helpers/helpers.dart'; + +class MockKeyboardTapCallback extends Mock { + void call(String text); +} + +void main() { + group('NumericKeyboard', () { + testWidgets('renders keyboard buttons correctly', (tester) async { + final mockCallback = MockKeyboardTapCallback(); + + await tester.pumpApp( + Scaffold( + body: NumericKeyboard( + keyboardUIConfig: const KeyboardUIConfig(), + onKeyboardTap: mockCallback.call, + allowAction: true, + ), + ), + ); + + expect(find.byType(KeyboardButton), findsNWidgets(10)); + }); + + testWidgets('tapping a keyboard button triggers the callback', + (tester) async { + final mockCallback = MockKeyboardTapCallback(); + + await tester.pumpApp( + Scaffold( + body: NumericKeyboard( + keyboardUIConfig: const KeyboardUIConfig(), + onKeyboardTap: mockCallback.call, + allowAction: true, + ), + ), + ); + + await tester.tap(find.text('1')); + verify(() => mockCallback.call('1')).called(1); + }); + }); + + group('KeyboardButton', () { + testWidgets('renders label correctly', (tester) async { + await tester.pumpApp( + Scaffold( + body: Scaffold( + body: KeyboardButton( + semanticsLabel: '1', + label: '1', + onTap: (_) {}, + allowAction: true, + ), + ), + ), + ); + + expect(find.text('1'), findsOneWidget); + }); + + testWidgets('renders icon correctly', (tester) async { + await tester.pumpApp( + Scaffold( + body: Scaffold( + body: KeyboardButton( + semanticsLabel: 'delete', + icon: const Icon(Icons.delete), + onTap: (_) {}, + allowAction: true, + ), + ), + ), + ); + + expect(find.byIcon(Icons.delete), findsOneWidget); + }); + + testWidgets('triggers onTap callback when tapped', (tester) async { + final mockCallback = MockKeyboardTapCallback(); + await tester.pumpApp( + Scaffold( + body: Scaffold( + body: KeyboardButton( + semanticsLabel: '1', + label: '1', + onTap: mockCallback.call, + allowAction: true, + ), + ), + ), + ); + + await tester.tap(find.text('1')); + verify(() => mockCallback.call('1')).called(1); + }); + + testWidgets('does not trigger onTap callback when disabled', + (tester) async { + final mockCallback = MockKeyboardTapCallback(); + await tester.pumpApp( + Scaffold( + body: Scaffold( + body: KeyboardButton( + semanticsLabel: '1', + label: '1', + onTap: mockCallback.call, + allowAction: false, + ), + ), + ), + ); + + await tester.tap(find.text('1')); + verifyNever(() => mockCallback.call(any())); + }); + + testWidgets('triggers onLongPress callback when long pressed', + (tester) async { + final mockCallback = MockKeyboardTapCallback(); + await tester.pumpApp( + Scaffold( + body: Scaffold( + body: KeyboardButton( + semanticsLabel: '1', + label: '1', + onLongPress: mockCallback.call, + allowAction: true, + ), + ), + ), + ); + + await tester.longPress(find.text('1')); + verify(() => mockCallback.call('1')).called(1); + }); + + testWidgets('does not trigger onLongPress callback when disabled', + (tester) async { + final mockCallback = MockKeyboardTapCallback(); + await tester.pumpApp( + Scaffold( + body: Scaffold( + body: KeyboardButton( + semanticsLabel: '1', + label: '1', + onLongPress: mockCallback.call, + allowAction: false, + ), + ), + ), + ); + + await tester.longPress(find.text('1')); + verifyNever(() => mockCallback.call(any())); + }); + }); +} diff --git a/test/app/shared/widget/powered_by_text_test.dart b/test/app/shared/widget/powered_by_text_test.dart new file mode 100644 index 000000000..a745ec7cb --- /dev/null +++ b/test/app/shared/widget/powered_by_text_test.dart @@ -0,0 +1,19 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group('PoweredByText', () { + testWidgets('renders PoweredByText', (tester) async { + await tester.pumpApp(const PoweredByText()); + expect(find.byType(Text), findsOneWidget); + }); + + testWidgets('renders value correctly', (tester) async { + await tester.pumpApp(const PoweredByText()); + expect(find.text('Powered By ${Parameters.appName}'), findsOneWidget); + }); + }); +} diff --git a/test/app/shared/widget/share_button_test.dart b/test/app/shared/widget/share_button_test.dart new file mode 100644 index 000000000..af9532a79 --- /dev/null +++ b/test/app/shared/widget/share_button_test.dart @@ -0,0 +1,36 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group('ShareButton', () { + testWidgets('ShareButton displays the correct icon and text', + (WidgetTester tester) async { + await tester.pumpApp(const Scaffold(body: ShareButton())); + + expect(find.byType(Image), findsOneWidget); + expect(find.text('Share'), findsOneWidget); + }); + + testWidgets('ShareButton responds to tap', (WidgetTester tester) async { + bool tapped = false; + + await tester.pumpApp( + Scaffold( + body: ShareButton( + onTap: () { + tapped = true; + }, + ), + ), + ); + + await tester.tap(find.byType(InkWell)); + await tester.pumpAndSettle(); + + expect(tapped, true); + }); + }); +} diff --git a/test/app/shared/widget/shimmer_widget_test.dart b/test/app/shared/widget/shimmer_widget_test.dart new file mode 100644 index 000000000..7781dd4e0 --- /dev/null +++ b/test/app/shared/widget/shimmer_widget_test.dart @@ -0,0 +1,57 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shimmer/shimmer.dart'; +import '../../../helpers/helpers.dart'; + +void main() { + group('ShimmerWidget', () { + testWidgets('ShimmerWidget.rectangular displays correctly', + (WidgetTester tester) async { + await tester.pumpApp( + const Scaffold( + body: ShimmerWidget.rectangular(height: 100), + ), + ); + + expect(find.byType(Shimmer), findsOneWidget); + + final constraints = + tester.firstWidget(find.byType(Container)).constraints; + expect(constraints!.minWidth, double.infinity); + expect(constraints.maxWidth, double.infinity); + expect(constraints.minHeight, 100.0); + expect(constraints.maxHeight, 100.0); + + final container = tester.widget(find.byType(Container)); + + final shapeDecoration = container.decoration! as ShapeDecoration; + expect(shapeDecoration.shape, isA()); + final roundedRectBorder = shapeDecoration.shape as RoundedRectangleBorder; + expect( + roundedRectBorder.borderRadius, + const BorderRadius.all(Radius.circular(10)), + ); + }); + + testWidgets('ShimmerWidget.circular displays correctly', + (WidgetTester tester) async { + await tester.pumpApp( + const Scaffold(body: ShimmerWidget.circular(height: 100)), + ); + + expect(find.byType(Shimmer), findsOneWidget); + + final constraints = + tester.firstWidget(find.byType(Container)).constraints; + expect(constraints!.minWidth, double.infinity); + expect(constraints.maxWidth, double.infinity); + expect(constraints.minHeight, 100.0); + expect(constraints.maxHeight, 100.0); + + final container = tester.widget(find.byType(Container)); + final shapeDecoration = container.decoration! as ShapeDecoration; + expect(shapeDecoration.shape, isA()); + }); + }); +} diff --git a/test/app/shared/widget/wallet_logo_test.dart b/test/app/shared/widget/wallet_logo_test.dart new file mode 100644 index 000000000..5565c7fe9 --- /dev/null +++ b/test/app/shared/widget/wallet_logo_test.dart @@ -0,0 +1,222 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/flavor/flavor.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +class MockFlavorCubit extends MockCubit implements FlavorCubit { + @override + final state = FlavorMode.development; +} + +void main() { + late FlavorCubit mockFlavorCubit; + + setUpAll(() { + mockFlavorCubit = MockFlavorCubit(); + }); + + group('WalletLogo widget', () { + testWidgets( + 'displays correct image for ProfileType.defaultOne in development', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return BlocProvider( + create: (context) => mockFlavorCubit, + child: WalletLogo( + height: 100, + width: 100, + profileModel: ProfileModel.defaultOne( + polygonIdNetwork: PolygonIdNetwork.PolygonMainnet, + walletType: WalletType.personal, + walletProtectionType: WalletProtectionType.FA2, + isDeveloperMode: true, + clientId: 'clientId', + clientSecret: 'clientSecret', + ), + ), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(Image), findsOneWidget); + }); + + testWidgets('displays correct image for ProfileType.custom in development', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return BlocProvider( + create: (context) => mockFlavorCubit, + child: WalletLogo( + height: 100, + width: 100, + profileModel: ProfileModel.empty(), + ), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(Image), findsOneWidget); + }); + + testWidgets('displays correct image for ProfileType.dutch in development', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return BlocProvider( + create: (context) => mockFlavorCubit, + child: WalletLogo( + height: 100, + width: 100, + profileModel: ProfileModel.dutch( + polygonIdNetwork: PolygonIdNetwork.PolygonMainnet, + walletType: WalletType.personal, + walletProtectionType: WalletProtectionType.FA2, + isDeveloperMode: true, + clientId: 'clientId', + clientSecret: 'clientSecret', + ), + ), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(Image), findsOneWidget); + }); + + testWidgets('displays correct image for ProfileType.ebsiV3 in development', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return BlocProvider( + create: (context) => mockFlavorCubit, + child: WalletLogo( + height: 100, + width: 100, + profileModel: ProfileModel.ebsiV3( + polygonIdNetwork: PolygonIdNetwork.PolygonMainnet, + walletType: WalletType.personal, + walletProtectionType: WalletProtectionType.FA2, + isDeveloperMode: true, + clientId: 'clientId', + clientSecret: 'clientSecret', + ), + ), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(Image), findsOneWidget); + }); + + testWidgets( + 'displays correct image for ProfileType.owfBaselineProfile in development', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return BlocProvider( + create: (context) => mockFlavorCubit, + child: WalletLogo( + height: 100, + width: 100, + profileModel: ProfileModel.owfBaselineProfile( + polygonIdNetwork: PolygonIdNetwork.PolygonMainnet, + walletType: WalletType.personal, + walletProtectionType: WalletProtectionType.FA2, + isDeveloperMode: true, + clientId: 'clientId', + clientSecret: 'clientSecret', + ), + ), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(Image), findsOneWidget); + }); + + testWidgets( + 'displays correct image for ProfileType.enterprise in development', + (WidgetTester tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return BlocProvider( + create: (context) => mockFlavorCubit, + child: WalletLogo( + height: 100, + width: 100, + profileModel: ProfileModel( + polygonIdNetwork: PolygonIdNetwork.PolygonMainnet, + walletType: WalletType.personal, + walletProtectionType: WalletProtectionType.pinCode, + isDeveloperMode: false, + profileType: ProfileType.enterprise, + profileSetting: ProfileSetting( + blockchainOptions: BlockchainOptions.initial(), + discoverCardsOptions: DiscoverCardsOptions.initial(), + generalOptions: GeneralOptions( + walletType: WalletAppType.altme, + companyName: '', + companyWebsite: '', + companyLogo: 'https://www.demo.com', + tagLine: '', + splashScreenTitle: '', + profileName: '', + profileVersion: '', + published: DateTime.now(), + profileId: '', + customerPlan: '', + ), + helpCenterOptions: HelpCenterOptions.initial(), + selfSovereignIdentityOptions: + SelfSovereignIdentityOptions.initial(), + settingsMenu: SettingsMenu.initial(), + version: '', + walletSecurityOptions: WalletSecurityOptions.initial(), + ), + ), + ), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(CachedImageFromNetwork), findsOneWidget); + }); + }); +} diff --git a/test/app/view/app_test.dart b/test/app/view/app_test.dart index 5e73f602f..846497f80 100644 --- a/test/app/view/app_test.dart +++ b/test/app/view/app_test.dart @@ -6,7 +6,6 @@ // https://opensource.org/licenses/MIT. import 'package:altme/app/app.dart'; -import 'package:altme/splash/view/splash_page.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -15,10 +14,10 @@ void main() { expect(const App().flavorMode, FlavorMode.production); }); - testWidgets('renders SplashPage', (tester) async { - await tester.pumpWidget(const App()); - await tester.pumpAndSettle(); - expect(find.byType(SplashPage), findsOneWidget); - }); + // testWidgets('renders SplashPage', (tester) async { + // await tester.pumpWidget(const App()); + // await tester.pumpAndSettle(); + // expect(find.byType(SplashPage), findsOneWidget); + // }); }); } diff --git a/test/credentials/models/author_test.dart b/test/credentials/models/author_test.dart index 8b959d0fd..c2fb62b5e 100644 --- a/test/credentials/models/author_test.dart +++ b/test/credentials/models/author_test.dart @@ -4,26 +4,25 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('Author model work properly', () { test('initialize Author with default constructor', () { - const author = Author('Taleb'); - expect(author.name, 'Taleb'); + const author = Author('Test'); + expect(author.name, 'Test'); }); test('initialize Author fromJson constructor', () { const authorJson = { - 'name': 'Taleb', + 'name': 'Test1', 'logo': 'logo', }; final author = Author.fromJson(authorJson); - expect(author.name, 'Taleb'); + expect(author.name, 'Test1'); }); test('Author toJson worked correct', () { - const authorJson = { - 'name': 'Taleb', - 'logo': 'logo', - }; - final author = Author.fromJson(authorJson); - expect(author.toJson(), authorJson); + const author = Author('Test'); + + final json = author.toJson(); + + expect(json['name'], 'Test'); }); }); } diff --git a/test/credentials/models/credential_status_field_test.dart b/test/credentials/models/credential_status_field_test.dart index 92c7a0686..5d1fec607 100644 --- a/test/credentials/models/credential_status_field_test.dart +++ b/test/credentials/models/credential_status_field_test.dart @@ -25,31 +25,50 @@ void main() { test('fromJson constructor work correctly', () { final credentialStatusField = CredentialStatusField.fromJson( { - 'id': 'id', + 'id': '123', 'type': 'type', 'revocationListIndex': 'revocationListIndex', 'revocationListCredential': 'revocationListCredential', + 'statusListCredential': 'statusListCredential', + 'statusListIndex': 'statusListIndex', + 'statusPurpose': 'statusPurpose', }, ); - expect(credentialStatusField.id, 'id'); + expect(credentialStatusField.id, '123'); expect(credentialStatusField.type, 'type'); expect(credentialStatusField.revocationListIndex, 'revocationListIndex'); expect( credentialStatusField.revocationListCredential, 'revocationListCredential', ); + expect( + credentialStatusField.statusListCredential, + 'statusListCredential', + ); + expect(credentialStatusField.statusListIndex, 'statusListIndex'); + expect(credentialStatusField.statusPurpose, 'statusPurpose'); }); test('toJson constructor work correctly', () { - final json = { - 'id': 'id', - 'type': 'type', - 'revocationListIndex': 'revocationListIndex', - 'revocationListCredential': 'revocationListCredential', - }; - final credentialStatusField = CredentialStatusField.fromJson(json); + final credentialStatusField = CredentialStatusField( + 'id', + 'type', + 'revocationListIndex', + 'revocationListCredential', + 'statusListCredential', + 'statusListIndex', + 'statusPurpose', + ); + + final json = credentialStatusField.toJson(); - expect(credentialStatusField.toJson(), json); + expect(json['id'], 'id'); + expect(json['type'], 'type'); + expect(json['revocationListIndex'], 'revocationListIndex'); + expect(json['revocationListCredential'], 'revocationListCredential'); + expect(json['statusListCredential'], 'statusListCredential'); + expect(json['statusListIndex'], 'statusListIndex'); + expect(json['statusPurpose'], 'statusPurpose'); }); test('empty constructor work properly', () { diff --git a/test/credentials/models/proof_test.dart b/test/credentials/models/proof_test.dart index 982eb9e0f..1ca044383 100644 --- a/test/credentials/models/proof_test.dart +++ b/test/credentials/models/proof_test.dart @@ -31,16 +31,21 @@ void main() { }); test('toJson constructor work correctly', () { - final json = { - 'type': 'type', - 'proofPurpose': 'proofPurpose', - 'verificationMethod': 'verificationMethod', - 'created': 'created', - 'jws': 'jws', - }; - final proof = Proof.fromJson(json); + final proof = Proof( + 'type', + 'proofPurpose', + 'verificationMethod', + 'created', + 'jws', + ); + + final json = proof.toJson(); - expect(proof.toJson(), json); + expect(json['type'], 'type'); + expect(json['proofPurpose'], 'proofPurpose'); + expect(json['verificationMethod'], 'verificationMethod'); + expect(json['created'], 'created'); + expect(json['jws'], 'jws'); }); test('dummy constructor work properly', () { diff --git a/test/credentials/widgets/display_issuer_test.dart b/test/credentials/widgets/display_issuer_test.dart index b25aaaa75..aded9f424 100644 --- a/test/credentials/widgets/display_issuer_test.dart +++ b/test/credentials/widgets/display_issuer_test.dart @@ -5,19 +5,14 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('DisplayIssuer Widget work correctly', () { - late Author issuerWithLogo; - late Author issuerWithoutLogo; const authorName = 'Taleb'; - setUp(() { - issuerWithLogo = const Author(authorName); - issuerWithoutLogo = const Author(authorName); - }); + const author = Author(authorName); testWidgets('find issuer name', (WidgetTester tester) async { await tester.pumpWidget( - MaterialApp( - home: DisplayIssuer(issuer: issuerWithLogo), + const MaterialApp( + home: DisplayIssuer(issuer: author), ), ); expect(find.text(authorName), findsOneWidget); @@ -25,46 +20,24 @@ void main() { testWidgets('find issuer model', (WidgetTester tester) async { await tester.pumpWidget( - MaterialApp( - home: DisplayIssuer(issuer: issuerWithLogo), + const MaterialApp( + home: DisplayIssuer(issuer: author), ), ); final displayIssuer = tester.widget(find.byType(DisplayIssuer)) as DisplayIssuer; - expect(displayIssuer.issuer, issuerWithLogo); - }); - - testWidgets('find CachedImageFromNetwork', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: DisplayIssuer(issuer: issuerWithLogo), - ), - ); - expect(find.byType(CachedImageFromNetwork), findsOneWidget); - }); - - testWidgets('CachedImageFromNetwork gone when logo is empty', - (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: DisplayIssuer(issuer: issuerWithoutLogo), - ), - ); - expect(find.byType(CachedImageFromNetwork), findsNothing); + expect(displayIssuer.issuer, author); }); testWidgets('find all widget', (WidgetTester tester) async { await tester.pumpWidget( - MaterialApp( - home: DisplayIssuer(issuer: issuerWithLogo), + const MaterialApp( + home: DisplayIssuer(issuer: author), ), ); - expect(find.byType(Padding), findsOneWidget); - expect(find.byType(CachedImageFromNetwork), findsOneWidget); expect(find.byType(Row), findsOneWidget); - expect(find.byType(Text), findsOneWidget); - expect(find.byType(Spacer), findsOneWidget); - expect(find.byType(SizedBox), findsNWidgets(2)); + expect(find.byType(Expanded), findsOneWidget); + expect(find.byType(MyText), findsOneWidget); }); }); } diff --git a/test/onboarding/activate_biometircs/cubit/active_biometrics_cubit_test.dart b/test/onboarding/activate_biometircs/cubit/active_biometrics_cubit_test.dart new file mode 100644 index 000000000..15a5c1ee7 --- /dev/null +++ b/test/onboarding/activate_biometircs/cubit/active_biometrics_cubit_test.dart @@ -0,0 +1,77 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/onboarding/onboarding.dart'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockProfileCubit extends MockCubit + implements ProfileCubit {} + +void main() { + group('ActiveBiometricsCubit', () { + late ActiveBiometricsCubit activeBiometricsCubit; + late MockProfileCubit mockProfileCubit; + + setUp(() { + mockProfileCubit = MockProfileCubit(); + + activeBiometricsCubit = + ActiveBiometricsCubit(profileCubit: mockProfileCubit); + }); + + test('initial state is false', () { + expect(activeBiometricsCubit.state, false); + }); + + blocTest( + 'load emits true if walletProtectionType is FA2 or biometrics', + build: () { + when(() => mockProfileCubit.state).thenReturn( + ProfileState( + model: ProfileModel( + polygonIdNetwork: PolygonIdNetwork.PolygonMainnet, + walletType: WalletType.personal, + walletProtectionType: WalletProtectionType.biometrics, + isDeveloperMode: false, + profileType: ProfileType.custom, + profileSetting: ProfileSetting.initial(), + ), + ), + ); + return activeBiometricsCubit; + }, + act: (cubit) => cubit.load(), + expect: () => [true], + ); + + blocTest( + 'load emits false if walletProtectionType is not FA2 or biometrics', + build: () { + when(() => mockProfileCubit.state).thenReturn( + ProfileState( + model: ProfileModel( + polygonIdNetwork: PolygonIdNetwork.PolygonMainnet, + walletType: WalletType.personal, + walletProtectionType: WalletProtectionType.pinCode, + isDeveloperMode: false, + profileType: ProfileType.custom, + profileSetting: ProfileSetting.initial(), + ), + ), + ); + return activeBiometricsCubit; + }, + act: (cubit) => cubit.load(), + expect: () => [false], + ); + + blocTest( + 'updateSwitch emits the provided value', + build: () => activeBiometricsCubit, + act: (cubit) => cubit.updateSwitch(value: true), + expect: () => [true], + ); + }); +} diff --git a/test/onboarding/activate_biometircs/widgets/biometrics_switch_test.dart b/test/onboarding/activate_biometircs/widgets/biometrics_switch_test.dart new file mode 100644 index 000000000..99b51d0e8 --- /dev/null +++ b/test/onboarding/activate_biometircs/widgets/biometrics_switch_test.dart @@ -0,0 +1,26 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/onboarding/onboarding.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group('BiometricsSwitch', () { + testWidgets('renders value correctly', (tester) async { + await tester.pumpApp(const BiometricsSwitch(value: true)); + + expect( + find.byWidgetPredicate( + (widget) => + widget is Image && + widget.image is AssetImage && + (widget.image as AssetImage).assetName == + IconStrings.fingerprint2, + ), + findsOneWidget, + ); + expect(find.byType(CupertinoSwitch), findsOneWidget); + }); + }); +} diff --git a/test/onboarding/cubit/onboarding_cubit_test.dart b/test/onboarding/cubit/onboarding_cubit_test.dart new file mode 100644 index 000000000..374f128f9 --- /dev/null +++ b/test/onboarding/cubit/onboarding_cubit_test.dart @@ -0,0 +1,26 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/onboarding/cubit/onboarding_cubit.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('OnboardingCubit', () { + test('initial state is correct', () { + expect(OnboardingCubit().state, const OnboardingState()); + }); + + blocTest( + 'emits [loading] when emitOnboardingProcessing is called', + build: OnboardingCubit.new, + act: (cubit) => cubit.emitOnboardingProcessing(), + expect: () => [const OnboardingState(status: AppStatus.loading)], + ); + + blocTest( + 'emits [success] when emitOnboardingDone is called', + build: OnboardingCubit.new, + act: (cubit) => cubit.emitOnboardingDone(), + expect: () => [const OnboardingState(status: AppStatus.success)], + ); + }); +} diff --git a/test/onboarding/cubit/onboarding_state_test.dart b/test/onboarding/cubit/onboarding_state_test.dart new file mode 100644 index 000000000..7c49892f9 --- /dev/null +++ b/test/onboarding/cubit/onboarding_state_test.dart @@ -0,0 +1,22 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/onboarding/cubit/onboarding_cubit.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('OnboardingState', () { + test('can be instantiated from JSON', () { + final json = {'status': 'init'}; + + final onboardingState = OnboardingState.fromJson(json); + + expect(onboardingState.status, AppStatus.init); + }); + + test('can be converted to JSON', () { + const onboardingState = OnboardingState(status: AppStatus.init); + final json = onboardingState.toJson(); + + expect(json, {'status': 'init'}); + }); + }); +} diff --git a/test/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit_test.dart b/test/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit_test.dart deleted file mode 100644 index 6b2f2b246..000000000 --- a/test/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit_test.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:key_generator/key_generator.dart'; - -Future main() async { - final keyGenerator = KeyGenerator(); - group('from mnemonic for ssi', () { - const String mnemonic = - '''brief hello carry loop squeeze unknown click abstract lounge figure logic oblige child ripple about vacant scheme magnet open enroll stuff valve hobby what'''; - - test('jwk', () async { - final jwk = await keyGenerator.jwkFromMnemonic( - mnemonic: mnemonic, - accountType: AccountType.ssi, - ); - const expectedJwk = - '''{"kty":"OKP","crv":"Ed25519","d":"cMGD8eAmjDn6MqvJoscsaPoyAMrjG41xbLDfE-uQkYw=","x":"-PeGBkVyMz2-yketwH2lbQqiflneee3jmaTafMCsURE="}'''; - expect(jwk, equals(expectedJwk)); - }); - - test('address', () async { - final address = await keyGenerator.walletAddressFromMnemonic( - mnemonic: mnemonic, - accountType: AccountType.ssi, - ); - const expectedAddress = 'tz1NvqicaUW7v6sEbM4UYi3Wes7GHDft4kqY'; - expect(address, equals(expectedAddress)); - }); - - test('secret key', () async { - final secretKey = await keyGenerator.secretKeyFromMnemonic( - mnemonic: mnemonic, - accountType: AccountType.ssi, - ); - const expectedSecretKey = - '''edskRrmNgPfAAvbZyzTptfvTju9X7ooLR5VVN9u8sXA42hXdMBd8CgrhykP7sZQf8hWLCYuqfEoWUFzL6Us3aKtMD9NsELGkuP'''; - expect(secretKey, equals(expectedSecretKey)); - }); - }); - group('from secretKey', () { - const String secretKey = - '''edskRrmNgPfAAvbZyzTptfvTju9X7ooLR5VVN9u8sXA42hXdMBd8CgrhykP7sZQf8hWLCYuqfEoWUFzL6Us3aKtMD9NsELGkuP'''; - - test('jwk', () async { - final jwk = await keyGenerator.jwkFromSecretKey( - secretKey: secretKey, - accountType: AccountType.tezos, - ); - const expectedJwk = - '''{"kty":"OKP","crv":"Ed25519","d":"cMGD8eAmjDn6MqvJoscsaPoyAMrjG41xbLDfE-uQkYw=","x":"-PeGBkVyMz2-yketwH2lbQqiflneee3jmaTafMCsURE="}'''; - expect(jwk, equals(expectedJwk)); - }); - - test('address', () async { - final address = await keyGenerator.walletAddressFromSecretKey( - secretKey: secretKey, - accountType: AccountType.ssi, - ); - const expectedAddress = 'tz1NvqicaUW7v6sEbM4UYi3Wes7GHDft4kqY'; - expect(address, equals(expectedAddress)); - }); - }); -} diff --git a/test/onboarding/gen_phrase/view/onboarding_gen_phrase_test.dart b/test/onboarding/gen_phrase/view/onboarding_gen_phrase_test.dart new file mode 100644 index 000000000..169d425eb --- /dev/null +++ b/test/onboarding/gen_phrase/view/onboarding_gen_phrase_test.dart @@ -0,0 +1,190 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/chat_room/chat_room.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/onboarding/onboarding.dart'; +import 'package:altme/splash/splash.dart'; +import 'package:altme/wallet/wallet.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:did_kit/did_kit.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:key_generator/key_generator.dart'; +import 'package:mockingjay/mockingjay.dart'; +import 'package:secure_storage/secure_storage.dart'; + +import '../../../helpers/helpers.dart'; + +class MockSecureStorage extends Mock implements SecureStorageProvider {} + +class MockDIDKitProvider extends Mock implements DIDKitProvider {} + +class MockKeyGenerator extends Mock implements KeyGenerator {} + +class MockHomeCubit extends MockCubit implements HomeCubit { + @override + Future emitHasWallet() async {} +} + +class MockWalletCubit extends MockCubit implements WalletCubit { + @override + final state = WalletState( + cryptoAccount: CryptoAccount( + data: [ + CryptoAccountData( + name: '', + secretKey: '', + blockchainType: BlockchainType.tezos, + walletAddress: '', + ), + ], + ), + ); + + @override + Future createCryptoWallet({ + String? accountName, + required String mnemonicOrKey, + required bool isImported, + required bool isFromOnboarding, + BlockchainType? blockchainType, + bool showStatus = true, + void Function({ + required CryptoAccount cryptoAccount, + required MessageHandler messageHandler, + })? onComplete, + }) async {} +} + +class MockSplashCubit extends MockCubit implements SplashCubit {} + +class MockAltmeChatSupportCubit extends MockCubit + implements AltmeChatSupportCubit {} + +class MockProfileCubit extends MockCubit implements ProfileCubit { + @override + final state = ProfileState(model: ProfileModel.empty()); +} + +void main() { + late SecureStorageProvider mockSecureStorage; + late DIDKitProvider didKitProvider; + late KeyGenerator keyGenerator; + late HomeCubit homeCubit; + late WalletCubit walletCubit; + late SplashCubit splashCubit; + late AltmeChatSupportCubit altmeChatSupportCubit; + late ProfileCubit profileCubit; + + setUpAll(() { + WidgetsFlutterBinding.ensureInitialized(); + mockSecureStorage = MockSecureStorage(); + didKitProvider = MockDIDKitProvider(); + keyGenerator = MockKeyGenerator(); + homeCubit = MockHomeCubit(); + splashCubit = MockSplashCubit(); + walletCubit = MockWalletCubit(); + altmeChatSupportCubit = MockAltmeChatSupportCubit(); + profileCubit = MockProfileCubit(); + + when(() => mockSecureStorage.set(any(), any())).thenAnswer((_) async {}); + when(() => mockSecureStorage.get(any())).thenAnswer((_) async {}); + }); + + group('OnBoarding GenPhrase Page', () { + late OnBoardingGenPhraseCubit onBoardingGenPhraseCubit; + + late MockNavigator navigator; + + setUpAll(() { + onBoardingGenPhraseCubit = OnBoardingGenPhraseCubit( + secureStorageProvider: mockSecureStorage, + didKitProvider: didKitProvider, + keyGenerator: keyGenerator, + homeCubit: homeCubit, + walletCubit: walletCubit, + splashCubit: splashCubit, + altmeChatSupportCubit: altmeChatSupportCubit, + profileCubit: profileCubit, + ); + navigator = MockNavigator(); + when(navigator.canPop).thenReturn(true); + + when(() => navigator.push(any())).thenAnswer((_) async {}); + }); + + testWidgets('is routable', (tester) async { + await tester.pumpApp( + MockNavigatorProvider( + navigator: navigator, + child: Builder( + builder: (context) => Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () { + Navigator.of(context) + .push(OnBoardingVerifyPhrasePage.route( + isFromOnboarding: true, + mnemonic: [], + )); + }, + ), + ), + ), + ), + ); + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); + + verify( + () => navigator.push( + any( + that: isRoute( + whereName: equals('/OnBoardingVerifyPhrasePage'), + ), + ), + ), + ).called(1); + }); + + testWidgets('renders UI correctly', (tester) async { + await tester.pumpApp( + BlocProvider.value( + value: onBoardingGenPhraseCubit, + child: const OnBoardingGenPhraseView(), + ), + ); + + expect(find.byType(BasePage), findsOneWidget); + expect(find.byType(MStepper), findsOneWidget); + expect(find.byType(MnemonicDisplay), findsOneWidget); + expect(find.byType(MyElevatedButton), findsOneWidget); + expect(find.byType(MyGradientButton), findsOneWidget); + }); + + testWidgets( + 'navigates to OnBoardingVerifyPhrasePage when Verify Now button' + ' is tapped', (tester) async { + await tester.pumpApp( + MockNavigatorProvider( + navigator: navigator, + child: BlocProvider.value( + value: onBoardingGenPhraseCubit, + child: const OnBoardingGenPhraseView(), + ), + ), + ); + + await tester.tap(find.text('Verify Now'.toUpperCase())); + + verify( + () => navigator.push( + any( + that: isRoute( + whereName: equals('/OnBoardingVerifyPhrasePage'), + ), + ), + ), + ).called(1); + }); + }); +} diff --git a/test/onboarding/tos/view/onboarding_tos_page_test.dart b/test/onboarding/tos/view/onboarding_tos_page_test.dart index 898f76fca..9e0050464 100644 --- a/test/onboarding/tos/view/onboarding_tos_page_test.dart +++ b/test/onboarding/tos/view/onboarding_tos_page_test.dart @@ -1,19 +1,33 @@ import 'package:altme/app/app.dart'; import 'package:altme/onboarding/onboarding.dart'; +import 'package:bloc_test/bloc_test.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; import '../../../helpers/helpers.dart'; +class MockOnBoardingTosCubit extends MockCubit + implements OnBoardingTosCubit {} + void main() { group('OnBoarding Terms Page', () { + late MockOnBoardingTosCubit onBoardingTosCubit; + + setUpAll(() { + onBoardingTosCubit = MockOnBoardingTosCubit(); + when(() => onBoardingTosCubit.state) + .thenReturn(const OnBoardingTosState()); + }); + testWidgets('is routable', (tester) async { await tester.pumpApp( Builder( builder: (context) => Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { - //Navigator.of(context).push(OnBoardingTosPage.route()); + Navigator.of(context).push(OnBoardingTosPage.route()); }, ), ), @@ -25,33 +39,83 @@ void main() { }); testWidgets('renders OnBoardingTosPage', (tester) async { - await tester - .pumpApp(const OnBoardingTosPage()); + await tester.pumpApp( + BlocProvider.value( + value: onBoardingTosCubit, + child: const OnBoardingTosPage(), + ), + ); await tester.pumpAndSettle(); - expect(find.byType(OnBoardingTosPage), findsOneWidget); + expect(find.byType(OnBoardingTosView), findsOneWidget); }); testWidgets('nothing happens when button is pressed', (tester) async { - await tester - .pumpApp(const OnBoardingTosPage()); - await tester.tap(find.byType(MyElevatedButton)); + await tester.pumpApp(const OnBoardingTosPage()); + await tester.tap(find.byType(MyGradientButton)); await tester.pumpAndSettle(); expect(find.byType(OnBoardingTosPage), findsOneWidget); }); + // testWidgets('checkboxes toggle state', (WidgetTester tester) async { + // when(() => onBoardingTosCubit.setAgreeTerms( + // agreeTerms: any(named: 'agreeTerms'))).thenAnswer((_) async {}); + // when(() => onBoardingTosCubit.setReadTerms( + // readTerms: any(named: 'readTerms'))).thenAnswer((_) async {}); + + // await tester.pumpApp( + // BlocProvider( + // create: (_) => onBoardingTosCubit, + // child: const OnBoardingTosPage(), + // ), + // ); + + // final agreeCheckbox = find.text('I agree to the terms and conditions.'); + // final readCheckbox = find.text('I have read the terms of use.'); + + // expect(agreeCheckbox, findsOneWidget); + // expect(readCheckbox, findsOneWidget); + + // await tester.tap(agreeCheckbox); + // await tester.pumpAndSettle(); + + // verify(() => onBoardingTosCubit.setAgreeTerms(agreeTerms: true)) + // .called(1); + // }); + testWidgets('blocks going back from OnBoardingTosPage start page', (tester) async { - await tester - .pumpApp(const OnBoardingTosPage()); + await tester.pumpApp(const OnBoardingTosPage()); final dynamic appState = tester.state(find.byType(WidgetsApp)); expect(await appState.didPopRoute(), true); await tester.pumpAndSettle(); expect(find.byType(OnBoardingTosPage), findsOneWidget); }); + testWidgets( + 'MyGradientButton press calls onAcceptancePressed', + (WidgetTester tester) async { + await tester.pumpApp( + BlocProvider.value( + value: onBoardingTosCubit, + child: const OnBoardingTosPage(), + ), + ); + + when(() => onBoardingTosCubit.state).thenReturn( + const OnBoardingTosState( + agreeTerms: true, + readTerms: true, + ), + ); + await tester.pump(); + + final button = find.text('Start'.toUpperCase()); + expect(button, findsOneWidget); + }, + ); + testWidgets('renders DisplayTerms', (tester) async { - await tester - .pumpApp(const OnBoardingTosPage()); + await tester.pumpApp(const OnBoardingTosPage()); await tester.pumpAndSettle(); expect(find.byType(DisplayTermsofUse), findsOneWidget); }); diff --git a/test/splash/cubit/splash_cubit_test.dart b/test/splash/cubit/splash_cubit_test.dart index 66d7d4ae1..c77da64e6 100644 --- a/test/splash/cubit/splash_cubit_test.dart +++ b/test/splash/cubit/splash_cubit_test.dart @@ -1,29 +1,74 @@ +import 'dart:async'; + import 'package:altme/app/app.dart'; import 'package:altme/chat_room/chat_room.dart'; import 'package:altme/credentials/credentials.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/splash/cubit/splash_cubit.dart'; -import 'package:altme/wallet/cubit/wallet_cubit.dart'; +import 'package:altme/wallet/wallet.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:dio/dio.dart'; +import 'package:fake_async/fake_async.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:secure_storage/secure_storage.dart'; class MockSecureStorage extends Mock implements SecureStorageProvider {} -class MockHomeCubit extends MockCubit implements HomeCubit {} +class MockHomeCubit extends MockCubit implements HomeCubit { + @override + Future emitHasWallet() async {} +} class MockAltmeChatSupportCubit extends MockCubit implements AltmeChatSupportCubit {} class MockCredentialsCubit extends MockCubit - implements CredentialsCubit {} + implements CredentialsCubit { + @override + Future loadAllCredentials({ + required BlockchainType blockchainType, + }) async {} +} -class MockWalletCubit extends MockCubit implements WalletCubit {} +class MockWalletCubit extends MockCubit implements WalletCubit { + @override + final state = WalletState( + cryptoAccount: CryptoAccount( + data: [ + CryptoAccountData( + name: '', + secretKey: '', + blockchainType: BlockchainType.tezos, + walletAddress: '', + ), + ], + ), + ); + + @override + Future createCryptoWallet({ + String? accountName, + required String mnemonicOrKey, + required bool isImported, + required bool isFromOnboarding, + BlockchainType? blockchainType, + bool showStatus = true, + void Function({ + required CryptoAccount cryptoAccount, + required MessageHandler messageHandler, + })? onComplete, + }) async {} +} + +class MockProfileCubit extends MockCubit implements ProfileCubit { + @override + final state = ProfileState(model: ProfileModel.empty()); +} -class MockProfileCubit extends MockCubit - implements ProfileCubit {} +class MockTimer extends Mock implements Timer {} void main() { late SecureStorageProvider mockSecureStorage; @@ -33,13 +78,30 @@ void main() { late AltmeChatSupportCubit altmeChatSupportCubit; late ProfileCubit profileCubit; + final packageInfo = PackageInfo( + appName: 'testApp', + packageName: 'com.example.test', + version: '1.0.0', + buildNumber: '1', + ); + setUp(() { + WidgetsFlutterBinding.ensureInitialized(); mockSecureStorage = MockSecureStorage(); homeCubit = MockHomeCubit(); credentialsCubit = MockCredentialsCubit(); walletCubit = MockWalletCubit(); altmeChatSupportCubit = MockAltmeChatSupportCubit(); profileCubit = MockProfileCubit(); + + when(() => mockSecureStorage.get(SecureStorageKeys.version)) + .thenAnswer((_) async => '1.0.0'); + when(() => mockSecureStorage.get(SecureStorageKeys.buildNumber)) + .thenAnswer((_) async => '1'); + when(() => mockSecureStorage.set(SecureStorageKeys.version, '1.0.0')) + .thenAnswer((_) async => {}); + when(() => mockSecureStorage.set(SecureStorageKeys.buildNumber, '1')) + .thenAnswer((_) async => {}); }); group('Splash Cubit', () { @@ -57,120 +119,67 @@ void main() { ), altmeChatSupportCubit: altmeChatSupportCubit, profileCubit: profileCubit, + packageInfo: packageInfo, ).state, - SplashStatus.init, + const SplashState( + status: SplashStatus.init, + versionNumber: '', + buildNumber: '', + isNewVersion: false, + loadedValue: 0, + ), ); }); group('initialiseApp', () { - group('SecureStorageKeys.ssiKey', () { - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.ssiKey is null''', - () async { - when(() => mockSecureStorage.get(SecureStorageKeys.ssiKey)) - .thenAnswer((_) => Future.value(null)); - - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); - - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.ssiKey is empty''', - () async { - when(() => mockSecureStorage.get(SecureStorageKeys.ssiKey)) - .thenAnswer((_) => Future.value('')); - - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, + test('counter increases every 500 milliseconds and stops at 5', () { + final SplashCubit splashCubit = SplashCubit( + credentialsCubit: credentialsCubit, + secureStorageProvider: mockSecureStorage, + homeCubit: homeCubit, + walletCubit: walletCubit, + client: DioClient( + baseUrl: Urls.checkIssuerTalaoUrl, secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); + dio: Dio(), + ), + altmeChatSupportCubit: altmeChatSupportCubit, + profileCubit: profileCubit, + packageInfo: packageInfo, + ); + fakeAsync((async) { + splashCubit.initialiseApp(); + expect(splashCubit.state.loadedValue, equals(0.0)); - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); - }); + async.elapse(const Duration(milliseconds: 500)); + expect(splashCubit.state.loadedValue, equals(0.1)); - group('SecureStorageKeys.did', () { - setUp(() { - when(() => mockSecureStorage.get(SecureStorageKeys.ssiKey)) - .thenAnswer((_) => Future.value('key')); - }); + async.elapse(const Duration(milliseconds: 500)); + expect(splashCubit.state.loadedValue, equals(0.2)); - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.did is null''', - () async { - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); + // Continue elapsing time until counter reaches 5 + while (splashCubit.state.loadedValue < 1.0) { + async.elapse(const Duration(milliseconds: 500)); + } - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.did is empty''', - () async { - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); + // Check final state + expect(splashCubit.state.loadedValue, equals(1.0)); + expect(splashCubit.state.status, equals(SplashStatus.init)); - expect(splashCubit.state, SplashStatus.routeToPassCode); + async.flushMicrotasks(); }); }); - group('SecureStorageKeys.didMethod', () { - setUp(() { + group('Route to routeToPassCode', () { + test('when ssiKey and ssiMnemonics is not null', () async { + when( + () => mockSecureStorage.get(any()), + ).thenAnswer((_) => Future.value(null)); + when(() => mockSecureStorage.get(SecureStorageKeys.ssiMnemonic)) + .thenAnswer((_) => Future.value('xyz')); when(() => mockSecureStorage.get(SecureStorageKeys.ssiKey)) - .thenAnswer((_) => Future.value('key')); - }); + .thenAnswer((_) => Future.value('xyz')); - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.didMethod is null''', - () async { final SplashCubit splashCubit = SplashCubit( credentialsCubit: credentialsCubit, secureStorageProvider: mockSecureStorage, @@ -183,64 +192,23 @@ void main() { ), altmeChatSupportCubit: altmeChatSupportCubit, profileCubit: profileCubit, + packageInfo: packageInfo, ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); - - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.didMethod is empty''', - () async { - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); + fakeAsync((async) { + splashCubit.initialiseApp(); + // complete timer + while (splashCubit.state.loadedValue <= 1.0) { + async.elapse(const Duration(milliseconds: 500)); + } + }); + expect(splashCubit.state.status, SplashStatus.routeToPassCode); }); }); - group('SecureStorageKeys.didMethodName', () { - setUp(() { - when(() => mockSecureStorage.get(SecureStorageKeys.ssiKey)) - .thenAnswer((_) => Future.value('key')); - }); - - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.didMethodName is null''', - () async { - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); - - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.didMethodName is empty''', - () async { + group('Route to onboarding', () { + test('when ssiMnemonic is null', () async { + when(() => mockSecureStorage.get(SecureStorageKeys.ssiMnemonic)) + .thenAnswer((_) => Future.value(null)); final SplashCubit splashCubit = SplashCubit( credentialsCubit: credentialsCubit, secureStorageProvider: mockSecureStorage, @@ -253,25 +221,23 @@ void main() { ), altmeChatSupportCubit: altmeChatSupportCubit, profileCubit: profileCubit, + packageInfo: packageInfo, ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); + fakeAsync((async) { + splashCubit.initialiseApp(); + // complete timer + while (splashCubit.state.loadedValue <= 1.0) { + async.elapse(const Duration(milliseconds: 500)); + } + async.flushMicrotasks(); + }); + expect(splashCubit.state.status, SplashStatus.routeToOnboarding); }); - }); - - group('SecureStorageKeys.isEnterpriseUser', () { - setUp(() { + test('when ssiKey is null', () async { + when(() => mockSecureStorage.get(SecureStorageKeys.ssiMnemonic)) + .thenAnswer((_) => Future.value('xyz')); when(() => mockSecureStorage.get(SecureStorageKeys.ssiKey)) - .thenAnswer((_) => Future.value('key')); - }); - - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.isEnterpriseUser is null''', - () async { - when(() => mockSecureStorage.get(SecureStorageKeys.walletType)) .thenAnswer((_) => Future.value(null)); - final SplashCubit splashCubit = SplashCubit( credentialsCubit: credentialsCubit, secureStorageProvider: mockSecureStorage, @@ -284,114 +250,17 @@ void main() { ), altmeChatSupportCubit: altmeChatSupportCubit, profileCubit: profileCubit, + packageInfo: packageInfo, ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); - - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.isEnterpriseUser is empty''', - () async { - when(() => mockSecureStorage.get(SecureStorageKeys.walletType)) - .thenAnswer((_) => Future.value('')); - - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); - - group('when user is enterprise user', () { - setUp(() { - when( - () => mockSecureStorage.get(SecureStorageKeys.walletType), - ).thenAnswer((_) => Future.value('true')); - }); - - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.rsaKeyJson is null''', - () async { - when(() => mockSecureStorage.get(SecureStorageKeys.rsaKeyJson)) - .thenAnswer((_) => Future.value(null)); - - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); - - test( - '''emits SplashStatus.routeToPassCode when SecureStorageKeys.rsaKeyJson is empty''', - () async { - when(() => mockSecureStorage.get(SecureStorageKeys.rsaKeyJson)) - .thenAnswer((_) => Future.value('')); - - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); - }); - - test( - '''emits SplashStatus.bypassOnBoarding when we have required values''', - () async { - when(() => mockSecureStorage.get(SecureStorageKeys.rsaKeyJson)) - .thenAnswer((_) => Future.value('{"key" : "value"}')); - - final SplashCubit splashCubit = SplashCubit( - credentialsCubit: credentialsCubit, - secureStorageProvider: mockSecureStorage, - homeCubit: homeCubit, - walletCubit: walletCubit, - client: DioClient( - baseUrl: Urls.checkIssuerTalaoUrl, - secureStorageProvider: mockSecureStorage, - dio: Dio(), - ), - altmeChatSupportCubit: altmeChatSupportCubit, - profileCubit: profileCubit, - ); - await splashCubit.initialiseApp(); - - expect(splashCubit.state, SplashStatus.routeToPassCode); + fakeAsync((async) { + splashCubit.initialiseApp(); + // complete timer + while (splashCubit.state.loadedValue <= 1.0) { + async.elapse(const Duration(milliseconds: 500)); + } + async.flushMicrotasks(); }); + expect(splashCubit.state.status, SplashStatus.routeToOnboarding); }); }); }); diff --git a/test/splash/view/splash_test.dart b/test/splash/view/splash_test.dart index e1e949674..3e6950baf 100644 --- a/test/splash/view/splash_test.dart +++ b/test/splash/view/splash_test.dart @@ -1,9 +1,19 @@ import 'package:altme/app/app.dart'; +import 'package:altme/connection_bridge/connection_bridge.dart'; +import 'package:altme/credentials/credentials.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/enterprise/cubit/enterprise_cubit.dart'; import 'package:altme/flavor/cubit/flavor_cubit.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:altme/onboarding/starter/starter.dart'; +import 'package:altme/polygon_id/cubit/polygon_id_cubit.dart'; +import 'package:altme/scan/scan.dart'; import 'package:altme/splash/splash.dart'; +import 'package:altme/wallet/wallet.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockingjay/mockingjay.dart'; import 'package:secure_storage/secure_storage.dart'; @@ -14,33 +24,168 @@ class MockSecureStorageProvider extends Mock implements SecureStorageProvider {} class MockSplashCubit extends MockCubit implements SplashCubit {} +class MockWalletCubit extends MockCubit implements WalletCubit { + @override + final state = WalletState( + cryptoAccount: CryptoAccount( + data: [ + CryptoAccountData( + name: '', + secretKey: '', + blockchainType: BlockchainType.tezos, + walletAddress: '', + ), + ], + ), + ); +} + class MockFlavorCubit extends MockCubit implements FlavorCubit {} +class MockProfileCubit extends MockCubit implements ProfileCubit { + @override + final state = ProfileState( + model: ProfileModel.empty(), + status: AppStatus.success, + ); +} + +class MockCredentialsCubit extends MockCubit + implements CredentialsCubit { + @override + final state = const CredentialsState(); + + @override + Future loadAllCredentials({ + required BlockchainType blockchainType, + }) async {} +} + +class MockQRCodeScanCubit extends MockCubit + implements QRCodeScanCubit { + @override + final state = const QRCodeScanState(); +} + +class MockScanCubit extends MockCubit implements ScanCubit { + @override + final state = const ScanState(); +} + +class MockBeaconCubit extends MockCubit implements BeaconCubit { + @override + final state = const BeaconState(); +} + +class MockWalletConnectCubit extends MockCubit + implements WalletConnectCubit { + @override + final state = const WalletConnectState(); +} + +class MockPolygonIdCubit extends MockCubit + implements PolygonIdCubit { + @override + final state = const PolygonIdState(); +} + +class MockEnterpriseCubit extends MockCubit + implements EnterpriseCubit { + @override + final state = const EnterpriseState(); +} + +class MockAdvanceSettingsCubit extends MockCubit + implements AdvanceSettingsCubit { + @override + final state = const AdvanceSettingsState( + isGamingEnabled: true, + isIdentityEnabled: true, + isProfessionalEnabled: true, + isBlockchainAccountsEnabled: true, + isEducationEnabled: true, + isPassEnabled: true, + isSocialMediaEnabled: true, + isCommunityEnabled: true, + isOtherEnabled: true, + isFinanceEnabled: true, + isHumanityProofEnabled: true, + isWalletIntegrityEnabled: true, + ); + + @override + Future initialise() async {} + + @override + Future setState(AdvanceSettingsState newState) async {} +} + void main() { late FlavorCubit flavorCubit; late SplashCubit splashCubit; + late WalletCubit walletCubit; + late ProfileCubit profileCubit; + late AdvanceSettingsCubit advanceSettingsCubit; + late CredentialsCubit credentialsCubit; + late ScanCubit scanCubit; + late QRCodeScanCubit qRCodeScanCubit; + late BeaconCubit beaconCubit; + late WalletConnectCubit walletConnectCubit; + late PolygonIdCubit polygonIdCubit; + late EnterpriseCubit enterpriseCubit; setUpAll(() async { flavorCubit = MockFlavorCubit(); splashCubit = MockSplashCubit(); + walletCubit = MockWalletCubit(); + profileCubit = MockProfileCubit(); + advanceSettingsCubit = MockAdvanceSettingsCubit(); + credentialsCubit = MockCredentialsCubit(); + scanCubit = MockScanCubit(); + qRCodeScanCubit = MockQRCodeScanCubit(); + beaconCubit = MockBeaconCubit(); + walletConnectCubit = MockWalletConnectCubit(); + polygonIdCubit = MockPolygonIdCubit(); + enterpriseCubit = MockEnterpriseCubit(); + when(() => splashCubit.state) + .thenReturn(const SplashState(status: SplashStatus.init)); + when(() => splashCubit.initialiseApp()).thenAnswer((_) async {}); + when(() => flavorCubit.state).thenReturn(FlavorMode.development); }); Widget makeTestableWidget() { return MultiBlocProvider( providers: [ - BlocProvider.value(value: flavorCubit), - BlocProvider.value(value: splashCubit), + BlocProvider(create: (context) => flavorCubit), + BlocProvider(create: (context) => beaconCubit), + BlocProvider( + create: (context) => walletConnectCubit, + ), + BlocProvider(create: (context) => profileCubit), + BlocProvider( + create: (context) => advanceSettingsCubit, + ), + BlocProvider(create: (context) => walletCubit), + BlocProvider(create: (context) => credentialsCubit), + BlocProvider(create: (context) => polygonIdCubit), + BlocProvider(create: (context) => enterpriseCubit), + BlocProvider(create: (context) => scanCubit), + BlocProvider(create: (context) => qRCodeScanCubit), + BlocProvider(create: (context) => splashCubit), ], - child: const SplashView(), + child: const MaterialApp( + localizationsDelegates: [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + home: SplashView(), + ), ); } group('SplashPage', () { testWidgets('renders SplashView', (tester) async { - when(() => flavorCubit.state).thenReturn(FlavorMode.development); - when(() => splashCubit.state) - .thenReturn(const SplashState(status: SplashStatus.init)); - when(() => splashCubit.initialiseApp()).thenAnswer((_) async {}); await tester.pumpApp(makeTestableWidget()); await tester.pumpAndSettle(); expect(find.byType(SplashView), findsOneWidget); @@ -49,14 +194,7 @@ void main() { group('SplashView', () { group('SplashStatus.init', () { - setUp(() { - when(() => splashCubit.state) - .thenReturn(const SplashState(status: SplashStatus.init)); - when(() => splashCubit.initialiseApp()).thenAnswer((_) async {}); - }); - testWidgets('only one BasePage widget is rendered', (tester) async { - when(() => flavorCubit.state).thenReturn(FlavorMode.development); await tester.pumpApp(makeTestableWidget()); await tester.pumpAndSettle(); expect(find.byType(BasePage), findsOneWidget); @@ -70,7 +208,7 @@ void main() { final Image image = find.byType(Image).evaluate().single.widget as Image; final AssetImage assetImage = image.image as AssetImage; - expect(assetImage.assetName, equals(ImageStrings.appLogo)); + expect(assetImage.assetName, equals(ImageStrings.appLogoDev)); }); testWidgets('correct image is rendered for staging flavor', @@ -95,52 +233,62 @@ void main() { expect(assetImage.assetName, equals(ImageStrings.appLogo)); }); - // this test fails which does not make sense - // testWidgets('there is only one ScaleTransition widget', (tester) async - // { - // await tester.pumpApp(const SplashView()); - // expect(find.byType(ScaleTransition), findsOneWidget); - // }); - - testWidgets('scaleAnimation Tween is animated correctly', (tester) async { - when(() => flavorCubit.state).thenReturn(FlavorMode.development); + testWidgets('loading progress is animated correctly', (tester) async { + whenListen( + splashCubit, + Stream.fromIterable( + [ + const SplashState( + status: SplashStatus.init, + loadedValue: 0, + ), + const SplashState( + status: SplashStatus.init, + loadedValue: 1, + ), + ], + ), + initialState: const SplashState( + status: SplashStatus.init, + loadedValue: 1, + ), + ); await tester.pumpApp(makeTestableWidget()); - await tester.pump(); + await tester.pump(const Duration(milliseconds: 250)); + + expect(find.byType(LoadingProgress), findsOneWidget); - final initialScale = tester - .widget(find.byKey(const Key('scaleTransition'))); - expect(initialScale.scale.value, 0.2); + final midScale = + tester.widget(find.byType(LoadingProgress)); + expect(midScale.value, 0.5); - final frames = await tester.pumpAndSettle(); - final finalScale = tester - .widget(find.byKey(const Key('scaleTransition'))); - expect(finalScale.scale.value, 1.0); + await tester.pump(const Duration(milliseconds: 250)); - const int animationSecond = 5; - const double expectedFrames = ((animationSecond * 1000) / 100) + 1; - expect(frames, expectedFrames); + final finalScale = + tester.widget(find.byType(LoadingProgress)); + expect(finalScale.value, 1.0); }); }); - group('SplashStatus.onboarding', () { + group('Routing', () { testWidgets( - '''navigates to OnBoardingFirstPage when state is SplashStatus.onboarding''', + '''navigates to StarterPage when state is SplashStatus.routeToOnboarding''', (tester) async { - when(() => flavorCubit.state).thenReturn(FlavorMode.development); - when(() => splashCubit.initialiseApp()).thenAnswer((_) async {}); - whenListen( splashCubit, Stream.fromIterable( - [SplashStatus.init, SplashStatus.routeToPassCode], + [ + const SplashState(status: SplashStatus.init), + const SplashState(status: SplashStatus.routeToOnboarding), + ], ), - initialState: SplashStatus.init, + initialState: const SplashState(status: SplashStatus.init), ); await tester.pumpApp(makeTestableWidget()); await tester.pumpAndSettle(); - //expect(find.byType(OnBoardingStartPage), findsOneWidget); + expect(find.byType(StarterPage), findsOneWidget); }); }); }); diff --git a/test/theme/app_theme_test.dart b/test/theme/app_theme_test.dart index b7ee2d1e4..5f4b0a291 100644 --- a/test/theme/app_theme_test.dart +++ b/test/theme/app_theme_test.dart @@ -1,450 +1,617 @@ import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import '../helpers/helpers.dart'; void main() { + setUpAll(TestWidgetsFlutterBinding.ensureInitialized); test('can access AppTheme', () { expect(AppTheme, isNotNull); }); - group('Dark CustomColorScheme', () { - testWidgets('dark color is rendered correctly', (tester) async { - await tester.pumpApp( - MaterialApp( - theme: ThemeData(brightness: Brightness.dark), - home: Builder( - builder: (context) => Column( - children: [ - Container( - key: const Key('transparent'), - color: Theme.of(context).colorScheme.transparent, - ), - Container( - key: const Key('leadingButton'), - color: Theme.of(context).colorScheme.leadingButton, - ), - Container( - key: const Key('selectedBottomBar'), - color: Theme.of(context).colorScheme.selectedBottomBar, - ), - Container( - key: const Key('borderColor'), - color: Theme.of(context).colorScheme.borderColor, - ), - Container( - key: const Key('markDownH1'), - color: Theme.of(context).colorScheme.markDownH1, - ), - Container( - key: const Key('markDownH2'), - color: Theme.of(context).colorScheme.markDownH2, - ), - Container( - key: const Key('markDownP'), - color: Theme.of(context).colorScheme.markDownP, - ), - Container( - key: const Key('markDownA'), - color: Theme.of(context).colorScheme.markDownA, - ), - Container( - key: const Key('subtitle1'), - color: Theme.of(context).colorScheme.subtitle1, - ), - Container( - key: const Key('subtitle2'), - color: Theme.of(context).colorScheme.subtitle2, - ), - Container( - key: const Key('profileDummy'), - color: Theme.of(context).colorScheme.profileDummy, - ), - Container( - key: const Key('documentShadow'), - color: Theme.of(context).colorScheme.documentShadow, - ), - Container( - key: const Key('documentShape'), - color: Theme.of(context).colorScheme.documentShape, - ), - Container( - key: const Key('star'), - color: Theme.of(context).colorScheme.star, - ), - Container( - key: const Key('genderIcon'), - color: Theme.of(context).colorScheme.genderIcon, - ), - Container( - key: const Key('activeCredential'), - color: Theme.of(context).colorScheme.activeCredential, - ), - Container( - key: const Key('expiredCredential'), - color: Theme.of(context).colorScheme.expiredCredential, - ), - Container( - key: const Key('revokedCredential'), - color: Theme.of(context).colorScheme.revokedCredential, - ), - Container( - key: const Key('alertErrorMessage'), - color: Theme.of(context).colorScheme.alertErrorMessage, - ), - Container( - key: const Key('alertWarningMessage'), - color: Theme.of(context).colorScheme.alertWarningMessage, - ), - Container( - key: const Key('alertInfoMessage'), - color: Theme.of(context).colorScheme.alertInfoMessage, - ), - Container( - key: const Key('alertSuccessMessage'), - color: Theme.of(context).colorScheme.alertSuccessMessage, - ), - Container( - key: const Key('buttonDisabled'), - color: Theme.of(context).colorScheme.buttonDisabled, - ), - ], - ), - ), - ), - ); - final transparent = - tester.widget(find.byKey(const Key('transparent'))); - expect(transparent.color, Colors.transparent); - - final appBar = tester.widget(find.byKey(const Key('appBar'))); - expect(appBar.color, const Color(0xFF1D1D1D)); - - final backButton = - tester.widget(find.byKey(const Key('backButton'))); - expect(backButton.color, const Color(0xFFADACAC)); - - final selectedBottomBar = - tester.widget(find.byKey(const Key('selectedBottomBar'))); - expect(selectedBottomBar.color, AppTheme.darkOnSurface); - - final borderColor = - tester.widget(find.byKey(const Key('borderColor'))); - expect(borderColor.color, const Color(0xFF3B3A3A)); - - final markDownH1 = - tester.widget(find.byKey(const Key('markDownH1'))); - expect(markDownH1.color, const Color(0xFFDBD8D8)); - - final markDownH2 = - tester.widget(find.byKey(const Key('markDownH2'))); - expect(markDownH2.color, const Color(0xFFDBD8D8)); - - final markDownP = - tester.widget(find.byKey(const Key('markDownP'))); - expect(markDownP.color, const Color(0xFFADACAC)); - - final markDownA = - tester.widget(find.byKey(const Key('markDownA'))); - expect(markDownA.color, const Color(0xff517bff)); - - final subtitle1 = - tester.widget(find.byKey(const Key('subtitle1'))); - expect(subtitle1.color, const Color(0xFFFFFFFF)); - - final subtitle2 = - tester.widget(find.byKey(const Key('subtitle2'))); - expect(subtitle2.color, const Color(0xFF8B8C92)); - - final button = tester.widget(find.byKey(const Key('button'))); - expect(button.color, const Color(0xFFEEEAEA)); - - final profileDummy = - tester.widget(find.byKey(const Key('profileDummy'))); - expect(profileDummy.color, const Color(0xFF212121)); - - final documentShadow = - tester.widget(find.byKey(const Key('documentShadow'))); - expect(documentShadow.color, const Color(0xFF424242)); - - final documentShape = - tester.widget(find.byKey(const Key('documentShape'))); - expect( - documentShape.color, - const Color(0xff3700b3).withOpacity(0.5), - ); - - final star = tester.widget(find.byKey(const Key('star'))); - expect(star.color, const Color(0xFFFFB83D)); - - final genderIcon = - tester.widget(find.byKey(const Key('genderIcon'))); - expect(genderIcon.color, const Color(0xFF212121)); - - final activeCredential = - tester.widget(find.byKey(const Key('activeCredential'))); - expect(activeCredential.color, Colors.green); - - final expiredCredential = - tester.widget(find.byKey(const Key('expiredCredential'))); - expect(expiredCredential.color, Colors.orange); - - final revokedCredential = - tester.widget(find.byKey(const Key('revokedCredential'))); - expect(revokedCredential.color, Colors.red); - - final alertErrorMessage = - tester.widget(find.byKey(const Key('alertErrorMessage'))); - expect(alertErrorMessage.color, Colors.red); - - final alertWarningMessage = tester - .widget(find.byKey(const Key('alertWarningMessage'))); - expect(alertWarningMessage.color, Colors.yellow); - - final alertInfoMessage = - tester.widget(find.byKey(const Key('alertInfoMessage'))); - expect(alertInfoMessage.color, Colors.cyan); - - final alertSuccessMessage = tester - .widget(find.byKey(const Key('alertSuccessMessage'))); - expect(alertSuccessMessage.color, Colors.green); - - final buttonDisabled = - tester.widget(find.byKey(const Key('buttonDisabled'))); - expect(buttonDisabled.color, const Color(0xFF424242)); - }); + test('CustomColorScheme Test', () { + const colorScheme = ColorScheme.dark(); + + expect(colorScheme.redColor, const Color(0xFFFF0045)); + expect(colorScheme.transactionApplied, const Color(0xFF00B267)); + expect(colorScheme.transactionFailed, const Color(0xFFFF0045)); + expect(colorScheme.transactionSkipped, const Color(0xFFFF5F0A)); + expect(colorScheme.activeColorOfNetwork, const Color(0xFF2C7DF7)); + expect(colorScheme.greyText, const Color(0xFFD1CCE3)); + expect(colorScheme.kycKeyIconColor, const Color(0xFF86809D)); + expect(colorScheme.lightPurple, const Color(0xFF5F556F)); + expect(colorScheme.popupBackground, const Color(0xff271C38)); + expect(colorScheme.cardHighlighted, const Color(0xFF251F38)); + expect(colorScheme.defaultDialogDark, const Color(0xFF322643)); + expect(colorScheme.closeIconColor, const Color(0xFFA79ABA)); + expect(colorScheme.kycVerifyButton, const Color(0xFF0045FF)); + expect(colorScheme.checkMarkColor, const Color(0xFF00B267)); + expect(colorScheme.accountBottomSheetBorder, equals(Colors.grey[200])); + expect(colorScheme.digitPrimaryColor, equals(Colors.white)); + expect(colorScheme.digitFillColor, equals(Colors.transparent)); + expect(colorScheme.disabledBgColor, const Color(0xFF6A5F7B)); + expect(colorScheme.disabledTextColor, const Color(0xFF000000)); + expect(colorScheme.darkGradientStartColor, const Color(0xff0A0F19)); + expect(colorScheme.darkGradientEndColor, const Color(0xff25095B)); + expect(colorScheme.transparent, equals(Colors.transparent)); + expect(colorScheme.onOutlineButton, const Color(0xFF6600FF)); + expect(colorScheme.onElevatedButton, equals(Colors.white)); + expect(colorScheme.appBarUpperLayer, const Color(0xff25095B)); + expect(colorScheme.appBarLowerLayer, equals(colorScheme.background)); + expect(colorScheme.surfaceContainer, + equals(const Color(0xff707070).withOpacity(0.07))); + expect(colorScheme.drawerSurface, const Color(0xff232630)); + expect(colorScheme.label, equals(Colors.white)); + expect(colorScheme.unSelectedLabel, const Color(0xff86809D)); + expect(colorScheme.leadingButton, const Color(0xffF1EFF8)); + expect(colorScheme.selectedBottomBar, equals(colorScheme.surface)); + expect(colorScheme.drawerBackground, const Color(0xff0B0514)); + expect(colorScheme.borderColor, const Color(0xFFDDCEF4)); + expect(colorScheme.defualtDialogCancelButtonBorderColor, + const Color(0xFFFFFFFF).withOpacity(0.2)); + expect(colorScheme.markDownH1, equals(Colors.white)); + expect(colorScheme.markDownH2, equals(Colors.white)); + expect(colorScheme.markDownP, const Color(0xFFD1CCE3)); + expect(colorScheme.markDownA, const Color(0xff517bff)); + expect(colorScheme.subtitle1, equals(Colors.white)); + expect(colorScheme.subtitle2, const Color(0xFF8B8C92)); + expect(colorScheme.profileDummy, const Color(0xFF212121)); + expect(colorScheme.documentShadow, const Color(0xFF424242)); + expect(colorScheme.documentShape, + equals(const Color(0xff3700b3).withOpacity(0.05))); + expect(colorScheme.star, const Color(0xFFFFB83D)); + expect(colorScheme.genderIcon, const Color(0xFF212121)); + expect(colorScheme.activeCredential, equals(Colors.green)); + expect(colorScheme.expiredCredential, equals(Colors.orange)); + expect(colorScheme.revokedCredential, equals(Colors.red)); + expect(colorScheme.buttonDisabled, const Color(0xFF424242)); + expect(colorScheme.alertErrorMessage, equals(Colors.red)); + expect(colorScheme.alertWarningMessage, equals(Colors.yellow)); + expect(colorScheme.alertInfoMessage, equals(Colors.cyan)); + expect(colorScheme.alertSuccessMessage, equals(Colors.green)); + expect(colorScheme.qrScanBackground, const Color(0xff2B1C48)); + expect(colorScheme.qrScanInnerShadow, + const Color(0xff000000).withOpacity(0.16)); + expect(colorScheme.qrScanOuterShadow, const Color(0xff430F91)); + expect(colorScheme.dialogText, const Color(0xffF5F5F5)); + expect(colorScheme.tabBarNotSelected, const Color(0xFF280164)); + expect(colorScheme.credentialBackground, const Color(0xFF211F33)); + expect(colorScheme.cryptoAccountNotSelected, + equals(Colors.grey.withOpacity(0.15))); + expect(colorScheme.startButtonColorA, const Color(0xff18ACFF)); + expect(colorScheme.startButtonColorB, const Color(0xff6600FF)); + expect(colorScheme.associatedWalletBorder, const Color(0xff524B67)); + expect(colorScheme.deleteColor, const Color(0xff322643)); + expect(colorScheme.blueColor, const Color(0xff322643)); + expect(colorScheme.titleColor, const Color(0xffD1CCE3)); + expect(colorScheme.valueColor, const Color(0xffFFFFFF)); + expect(colorScheme.lightGrey, const Color(0xFF616161)); + expect(colorScheme.darkGrey, const Color(0xFF212121)); + expect(colorScheme.activeColor, const Color(0xFF08B530)); + expect(colorScheme.inactiveColor, const Color(0xFFFF0045)); + expect(colorScheme.beaconBorder, const Color(0xff86809D)); + expect(colorScheme.cardBackground, const Color(0xFF211F33)); }); - group('CustomTextTheme', () { - testWidgets('custom text theme rendered correctly', (tester) async { - await tester.pumpApp( - MaterialApp( - theme: ThemeData(brightness: Brightness.dark), - home: Builder( - builder: (context) => Column( - children: [ - Text( - 'credentialTitle', - key: const Key('credentialTitle'), - style: Theme.of(context).textTheme.credentialTitle, - ), - Text( - 'credentialDescription', - key: const Key('credentialDescription'), - style: Theme.of(context).textTheme.credentialDescription, - ), - Text( - 'credentialFieldTitle', - key: const Key('credentialFieldTitle'), - style: Theme.of(context).textTheme.credentialFieldTitle, - ), - Text( - 'credentialFieldDescription', - key: const Key('credentialFieldDescription'), - style: Theme.of(context).textTheme.credentialFieldDescription, - ), - Text( - 'learningAchievementTitle', - key: const Key('learningAchievementTitle'), - style: Theme.of(context).textTheme.learningAchievementTitle, - ), - Text( - 'learningAchievementDescription', - key: const Key('learningAchievementDescription'), - style: Theme.of(context) - .textTheme - .learningAchievementDescription, - ), - Text( - 'credentialIssuer', - key: const Key('credentialIssuer'), - style: Theme.of(context).textTheme.credentialIssuer, - ), - Text( - 'imageCard', - key: const Key('imageCard'), - style: Theme.of(context).textTheme.imageCard, - ), - Text( - 'loyaltyCard', - key: const Key('loyaltyCard'), - style: Theme.of(context).textTheme.loyaltyCard, - ), - Text( - 'professionalExperienceAssessmentRating', - key: const Key('professionalExperienceAssessmentRating'), - style: Theme.of(context) - .textTheme - .professionalExperienceAssessmentRating, - ), - Text( - 'voucherOverlay', - key: const Key('voucherOverlay'), - style: Theme.of(context).textTheme.voucherOverlay, - ), - ], - ), - ), - ), - ); - - final brand = tester.widget(find.byKey(const Key('brand'))); - expect( - brand.style, - GoogleFonts.roboto( - color: const Color(0xFFFFFFFF), - fontSize: 28, - fontWeight: FontWeight.w400, - ), - ); - - final credentialTitle = - tester.widget(find.byKey(const Key('credentialTitle'))); - expect( - credentialTitle.style, - GoogleFonts.roboto( - color: const Color(0xFF424242), - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ); - - final credentialDescription = - tester.widget(find.byKey(const Key('credentialDescription'))); - expect( - credentialDescription.style, - GoogleFonts.roboto( - color: const Color(0xFF757575), - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ); - - final credentialFieldTitle = - tester.widget(find.byKey(const Key('credentialFieldTitle'))); - expect( - credentialFieldTitle.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 12, - fontWeight: FontWeight.w400, - ), - ); - - final credentialFieldDescription = tester - .widget(find.byKey(const Key('credentialFieldDescription'))); - expect( - credentialFieldDescription.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ); - - final learningAchievementTitle = tester - .widget(find.byKey(const Key('learningAchievementTitle'))); - expect( - learningAchievementTitle.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 12, - fontWeight: FontWeight.w600, - ), - ); - - final learningAchievementDescription = tester.widget( - find.byKey(const Key('learningAchievementDescription')), - ); - expect( - learningAchievementDescription.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 12, - fontWeight: FontWeight.w400, - ), - ); - - final credentialIssuer = - tester.widget(find.byKey(const Key('credentialIssuer'))); - expect( - credentialIssuer.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 13, - fontWeight: FontWeight.w500, - ), - ); - - final imageCard = tester.widget(find.byKey(const Key('imageCard'))); - expect( - imageCard.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 12, - fontWeight: FontWeight.w500, - ), - ); - - final loyaltyCard = - tester.widget(find.byKey(const Key('loyaltyCard'))); - expect( - loyaltyCard.style, - GoogleFonts.roboto( - color: const Color(0xffffffff), - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ); - - final professionalExperienceAssessmentRating = tester.widget( - find.byKey(const Key('professionalExperienceAssessmentRating')), - ); - expect( - professionalExperienceAssessmentRating.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 13, - fontWeight: FontWeight.w500, - ), - ); - - final voucherOverlay = - tester.widget(find.byKey(const Key('voucherOverlay'))); - expect( - voucherOverlay.style, - GoogleFonts.roboto( - color: const Color(0xffFFFFFF), - fontSize: 13, - fontWeight: FontWeight.w500, - ), - ); - - final ecole42LearningAchievementStudentIdentity = tester.widget( - find.byKey(const Key('ecole42LearningAchievementStudentIdentity')), - ); - expect( - ecole42LearningAchievementStudentIdentity.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 6, - fontWeight: FontWeight.w700, - ), - ); - - final ecole42LearningAchievementLevel = tester.widget( - find.byKey(const Key('ecole42LearningAchievementLevel')), - ); - expect( - ecole42LearningAchievementLevel.style, - GoogleFonts.roboto( - color: const Color(0xff212121), - fontSize: 5, - fontWeight: FontWeight.w700, - ), - ); - }); + test('CustomTextTheme extension test', () { + const textTheme = TextTheme(); + + expect(textTheme.hintTextFieldStyle.fontSize, 14); + expect(textTheme.hintTextFieldStyle.fontSize, 14); + expect(textTheme.hintTextFieldStyle.height, 1.5); + expect(textTheme.hintTextFieldStyle.letterSpacing, 1.02); + expect(textTheme.hintTextFieldStyle.fontWeight, FontWeight.normal); + expect(textTheme.hintTextFieldStyle.color, const Color(0xffD1CCE3)); + + expect(textTheme.textFieldTitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.textFieldTitle.fontSize, 16); + expect(textTheme.textFieldTitle.fontWeight, FontWeight.w400); + + expect(textTheme.keyboardDigitTextStyle.fontSize, 30); + expect(textTheme.keyboardDigitTextStyle.color, Colors.white); + + expect(textTheme.calculatorKeyboardDigitTextStyle.fontSize, 30); + expect(textTheme.calculatorKeyboardDigitTextStyle.color, Colors.white); + expect( + textTheme.calculatorKeyboardDigitTextStyle.fontWeight, + FontWeight.bold, + ); + + expect(textTheme.keyboardDeleteButtonTextStyle.fontSize, 16); + expect(textTheme.keyboardDeleteButtonTextStyle.color, Colors.white); + + expect(textTheme.loadingText.color, const Color(0xFFFFFFFF)); + expect(textTheme.loadingText.fontSize, 16); + expect(textTheme.loadingText.fontWeight, FontWeight.w600); + + expect(textTheme.starterTitleStyle.color, const Color(0xFFFFFFFF)); + expect(textTheme.starterTitleStyle.fontSize, 32); + expect(textTheme.starterTitleStyle.fontWeight, FontWeight.w700); + + expect(textTheme.subtitle3.color, const Color(0xFF86809D)); + expect(textTheme.subtitle3.fontSize, 16); + expect(textTheme.subtitle3.fontWeight, FontWeight.w400); + expect(textTheme.subtitle3.height, 1.4); + + expect(textTheme.customListTileTitleStyle.color, const Color(0xFFFFFFFF)); + expect(textTheme.customListTileTitleStyle.fontSize, 18); + expect(textTheme.customListTileTitleStyle.fontWeight, FontWeight.w800); + + expect( + textTheme.customListTileSubTitleStyle.color, const Color(0xFF86809D)); + expect(textTheme.customListTileSubTitleStyle.fontSize, 16); + expect(textTheme.customListTileSubTitleStyle.fontWeight, FontWeight.w400); + + expect(textTheme.starterSubTitleStyle.color, const Color(0xFFEEEEEE)); + expect(textTheme.starterSubTitleStyle.fontSize, 22); + expect(textTheme.starterSubTitleStyle.fontWeight, FontWeight.w600); + + expect(textTheme.badgeStyle.color, const Color(0xFFEEEEEE)); + expect(textTheme.badgeStyle.fontSize, 8); + expect(textTheme.badgeStyle.fontWeight, FontWeight.w500); + + expect(textTheme.onBoardingTitleStyle.color, const Color(0xFFFFFFFF)); + expect(textTheme.onBoardingTitleStyle.fontSize, 22); + expect(textTheme.onBoardingTitleStyle.fontWeight, FontWeight.w600); + + expect(textTheme.onBoardingSubTitleStyle.color, const Color(0xFFD1CCE3)); + expect(textTheme.onBoardingSubTitleStyle.fontSize, 16); + expect(textTheme.onBoardingSubTitleStyle.fontWeight, FontWeight.w400); + + expect(textTheme.learnMoreTextStyle.color, const Color(0xFFD1CCE3)); + expect(textTheme.learnMoreTextStyle.fontSize, 16); + expect(textTheme.learnMoreTextStyle.fontWeight, FontWeight.w400); + expect(textTheme.learnMoreTextStyle.decoration, TextDecoration.underline); + + expect(textTheme.infoTitle.color, const Color(0xFFEDEAF5)); + expect(textTheme.infoTitle.fontSize, 20); + expect(textTheme.infoTitle.fontWeight, FontWeight.w600); + + expect(textTheme.infoSubtitle.color, const Color(0xFFD1CCE3)); + expect(textTheme.infoSubtitle.fontSize, 16); + expect(textTheme.infoSubtitle.fontWeight, FontWeight.w400); + + expect(textTheme.normal.color, const Color(0xFFD1CCE3)); + expect(textTheme.normal.fontSize, 16); + expect(textTheme.normal.fontWeight, FontWeight.w400); + + expect(textTheme.appBar.color, const Color(0xFFFFFFFF)); + expect(textTheme.appBar.fontSize, 24); + expect(textTheme.appBar.fontWeight, FontWeight.w800); + + expect(textTheme.bottomBar.color, const Color(0xFFFFFFFF)); + expect(textTheme.bottomBar.fontSize, 10); + expect(textTheme.bottomBar.fontWeight, FontWeight.w600); + + expect(textTheme.title.color, const Color(0xFFFFFFFF)); + expect(textTheme.title.fontSize, 18); + expect(textTheme.title.fontWeight, FontWeight.w800); + + expect(textTheme.listTitle.color, const Color(0xFFEDEAF5)); + expect(textTheme.listTitle.fontSize, 20); + expect(textTheme.listTitle.fontWeight, FontWeight.w600); + + expect(textTheme.listSubtitle.color, const Color(0xFFEDEAF5)); + expect(textTheme.listSubtitle.fontSize, 13); + expect(textTheme.listSubtitle.fontWeight, FontWeight.w500); + + expect(textTheme.bodySmall2.color, const Color(0xFF8682A8)); + expect(textTheme.bodySmall2.fontSize, 12); + expect(textTheme.bodySmall2.fontWeight, FontWeight.w400); + + expect(textTheme.bodySmall3.color, const Color(0xFF86809D)); + expect(textTheme.bodySmall3.fontSize, 16); + expect(textTheme.bodySmall3.fontWeight, FontWeight.w400); + + expect(textTheme.listTileTitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.listTileTitle.fontSize, 14); + expect(textTheme.listTileTitle.fontWeight, FontWeight.w600); + + expect(textTheme.listTileSubtitle.color, const Color(0xFF8682A8)); + expect(textTheme.listTileSubtitle.fontSize, 12); + expect(textTheme.listTileSubtitle.fontWeight, FontWeight.w400); + + expect(textTheme.close.color, const Color(0xFFD6C3F2)); + expect(textTheme.close.fontSize, 13); + expect(textTheme.close.fontWeight, FontWeight.w400); + + expect(textTheme.dialogClose.color, const Color(0xFFA79ABA)); + expect(textTheme.dialogClose.fontSize, 12); + expect(textTheme.dialogClose.fontWeight, FontWeight.w400); + + expect(textTheme.drawerMenu.color, const Color(0xFFD1CCE3)); + expect(textTheme.drawerMenu.fontSize, 15); + expect(textTheme.drawerMenu.fontWeight, FontWeight.w400); + + expect(textTheme.drawerItemTitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.drawerItemTitle.fontSize, 16); + expect(textTheme.drawerItemTitle.fontWeight, FontWeight.w800); + + expect(textTheme.drawerItemSubtitle.color, const Color(0xFFD1CCE3)); + expect(textTheme.drawerItemSubtitle.fontSize, 14); + expect(textTheme.drawerItemSubtitle.fontWeight, FontWeight.w500); + + expect(textTheme.drawerCategoryTitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.drawerCategoryTitle.fontSize, 18); + expect(textTheme.drawerCategoryTitle.fontWeight, FontWeight.w800); + + expect(textTheme.resetWalletTitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.resetWalletTitle.fontSize, 16); + expect(textTheme.resetWalletTitle.fontWeight, FontWeight.w700); + + expect(textTheme.resetWalletSubtitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.resetWalletSubtitle.fontSize, 16); + expect(textTheme.resetWalletSubtitle.fontWeight, FontWeight.w500); + + expect(textTheme.subtitle4.color, const Color(0xFF00A1FF)); + expect(textTheme.subtitle4.fontSize, 16); + expect(textTheme.subtitle4.fontWeight, FontWeight.w500); + + expect(textTheme.drawerCategorySubTitle.color, const Color(0xFFD1CCE3)); + expect(textTheme.drawerCategorySubTitle.fontSize, 16); + expect(textTheme.drawerCategorySubTitle.fontWeight, FontWeight.w400); + + expect(textTheme.biometricMessage.color, const Color(0xFFB1ADC3)); + expect(textTheme.biometricMessage.fontSize, 12); + expect(textTheme.biometricMessage.fontWeight, FontWeight.w400); + + expect(textTheme.pinCodeTitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.pinCodeTitle.fontSize, 20); + expect(textTheme.pinCodeTitle.fontWeight, FontWeight.w700); + + expect(textTheme.pinCodeMessage.color, const Color(0xFFFFFFFF)); + expect(textTheme.pinCodeMessage.fontSize, 14); + expect(textTheme.pinCodeMessage.fontWeight, FontWeight.w400); + + expect(textTheme.getCardsButton.color, const Color(0xFFFFFFFF)); + expect(textTheme.getCardsButton.fontSize, 12); + expect(textTheme.getCardsButton.fontWeight, FontWeight.w600); + + expect(textTheme.miniButton.color, const Color(0xFFFFFFFF)); + expect(textTheme.miniButton.fontSize, 11); + expect(textTheme.miniButton.fontWeight, FontWeight.w500); + + expect(textTheme.credentialTitle.color, const Color(0xFF424242)); + expect(textTheme.credentialTitle.fontSize, 15); + expect(textTheme.credentialTitle.fontWeight, FontWeight.bold); + + expect(textTheme.credentialDescription.color, const Color(0xFF757575)); + expect(textTheme.credentialDescription.fontSize, 13); + expect(textTheme.credentialDescription.fontWeight, FontWeight.w400); + + expect(textTheme.polygonCardDetail.color, const Color(0xffFFFFFF)); + expect(textTheme.polygonCardDetail.fontSize, 13); + expect(textTheme.polygonCardDetail.fontWeight, FontWeight.w400); + + expect(textTheme.credentialFieldTitle.color, const Color(0xff212121)); + expect(textTheme.credentialFieldTitle.fontSize, 14); + expect(textTheme.credentialFieldTitle.fontWeight, FontWeight.w400); + + expect(textTheme.credentialFieldDescription.color, const Color(0xff212121)); + expect(textTheme.credentialFieldDescription.fontSize, 14); + expect(textTheme.credentialFieldDescription.fontWeight, FontWeight.w400); + expect(textTheme.credentialFieldDescription.height, 1.5); + + expect(textTheme.discoverFieldTitle.color, const Color(0xffD1CCE3)); + expect(textTheme.discoverFieldTitle.fontSize, 14); + expect(textTheme.discoverFieldTitle.fontWeight, FontWeight.w800); + + expect(textTheme.discoverFieldDescription.color, const Color(0xffFFFFFF)); + expect(textTheme.discoverFieldDescription.fontSize, 14); + expect(textTheme.discoverFieldDescription.fontWeight, FontWeight.w400); + expect(textTheme.discoverFieldDescription.height, 1.5); + + expect(textTheme.learningAchievementTitle.color, const Color(0xff212121)); + expect(textTheme.learningAchievementTitle.fontSize, 12); + expect(textTheme.learningAchievementTitle.fontWeight, FontWeight.w600); + + expect( + textTheme.learningAchievementDescription.color, + const Color(0xff212121), + ); + expect(textTheme.learningAchievementDescription.fontSize, 12); + expect( + textTheme.learningAchievementDescription.fontWeight, FontWeight.w400); + + expect(textTheme.credentialIssuer.color, const Color(0xff212121)); + expect(textTheme.credentialIssuer.fontSize, 13); + expect(textTheme.credentialIssuer.fontWeight, FontWeight.w500); + + expect(textTheme.imageCard.color, const Color(0xff212121)); + expect(textTheme.imageCard.fontSize, 12); + expect(textTheme.imageCard.fontWeight, FontWeight.w500); + + expect(textTheme.loyaltyCard.color, const Color(0xffffffff)); + expect(textTheme.loyaltyCard.fontSize, 13); + expect(textTheme.loyaltyCard.fontWeight, FontWeight.w600); + + expect( + textTheme.professionalExperienceAssessmentRating.color, + const Color(0xff212121), + ); + expect(textTheme.professionalExperienceAssessmentRating.fontSize, 13); + expect( + textTheme.professionalExperienceAssessmentRating.fontWeight, + FontWeight.w500, + ); + + expect(textTheme.voucherOverlay.color, const Color(0xffFFFFFF)); + expect(textTheme.voucherOverlay.fontSize, 13); + expect(textTheme.voucherOverlay.fontWeight, FontWeight.w500); + + expect( + textTheme.certificateOfEmploymentTitleCard.color, + const Color(0xFF0650C6), + ); + expect(textTheme.certificateOfEmploymentTitleCard.fontSize, 20); + expect( + textTheme.certificateOfEmploymentTitleCard.fontWeight, + FontWeight.bold, + ); + + expect( + textTheme.certificateOfEmploymentDescription.color, + const Color(0xFF757575), + ); + expect(textTheme.certificateOfEmploymentDescription.fontSize, 13); + expect( + textTheme.certificateOfEmploymentDescription.fontWeight, + FontWeight.normal, + ); + + expect( + textTheme.certificateOfEmploymentData.color, + const Color(0xFF434e62), + ); + expect(textTheme.certificateOfEmploymentData.fontSize, 12); + expect(textTheme.certificateOfEmploymentData.fontWeight, FontWeight.normal); + + expect(textTheme.identityCardData.color, const Color(0xFFFFFFFF)); + expect(textTheme.identityCardData.fontSize, 12); + expect(textTheme.identityCardData.fontWeight, FontWeight.normal); + + expect(textTheme.tezosAssociatedAddressData.color, const Color(0xff605A71)); + expect(textTheme.tezosAssociatedAddressData.fontSize, 17); + expect(textTheme.tezosAssociatedAddressData.fontWeight, FontWeight.normal); + + expect(textTheme.tezosAssociatedAddressTitleCard.color, + const Color(0xffFAFDFF)); + expect(textTheme.tezosAssociatedAddressTitleCard.fontSize, 20); + expect( + textTheme.tezosAssociatedAddressTitleCard.fontWeight, FontWeight.w700); + + expect( + textTheme.credentialStudentCardTextCard.color, const Color(0xffffffff)); + expect(textTheme.credentialStudentCardTextCard.fontSize, 14); + expect( + textTheme.credentialStudentCardTextCard.fontWeight, FontWeight.normal); + + expect(textTheme.over18.color, const Color(0xffffffff)); + expect(textTheme.over18.fontSize, 20); + expect(textTheme.over18.fontWeight, FontWeight.normal); + + expect(textTheme.studentCardSchool.color, const Color(0xff9dc5ff)); + expect(textTheme.studentCardSchool.fontSize, 15); + expect(textTheme.studentCardSchool.fontWeight, FontWeight.bold); + + expect(textTheme.studentCardData.color, const Color(0xffffffff)); + expect(textTheme.studentCardData.fontSize, 12); + expect(textTheme.studentCardData.fontWeight, FontWeight.normal); + + expect(textTheme.credentialTitleCard.color, const Color(0xFFFFFFFF)); + expect(textTheme.credentialTitleCard.fontSize, 20); + expect(textTheme.credentialTitleCard.fontWeight, FontWeight.bold); + + expect(textTheme.voucherValueCard.color, const Color(0xFFFEEA00)); + expect(textTheme.voucherValueCard.fontSize, 50); + expect(textTheme.voucherValueCard.fontWeight, FontWeight.bold); + + expect(textTheme.credentialTextCard.color, const Color(0xff212121)); + expect(textTheme.credentialTextCard.fontSize, 14); + expect(textTheme.credentialTextCard.fontWeight, FontWeight.normal); + + expect(textTheme.illustrationPageDescription.color, Colors.white); + expect(textTheme.illustrationPageDescription.fontSize, 16); + expect(textTheme.illustrationPageDescription.fontWeight, FontWeight.w600); + + expect(textTheme.defaultDialogTitle.color, const Color(0xffF5F5F5)); + expect(textTheme.defaultDialogTitle.fontSize, 24); + expect(textTheme.defaultDialogTitle.fontWeight, FontWeight.bold); + + expect(textTheme.defaultDialogBody.color, const Color(0xFF86809D)); + expect(textTheme.defaultDialogBody.fontSize, 14); + expect(textTheme.defaultDialogBody.fontWeight, FontWeight.w400); + + expect(textTheme.defaultDialogSubtitle.color, const Color(0xff86809D)); + expect(textTheme.defaultDialogSubtitle.fontSize, 16); + + expect(textTheme.newVersionTitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.newVersionTitle.fontSize, 18); + expect(textTheme.newVersionTitle.fontWeight, FontWeight.w800); + + expect(textTheme.kycDialogTitle.color, const Color(0xffF5F5F5)); + expect(textTheme.kycDialogTitle.fontSize, 25); + expect(textTheme.kycDialogTitle.fontWeight, FontWeight.bold); + + expect(textTheme.kycDialogBodySmall.color, const Color(0xFF0045FF)); + expect(textTheme.kycDialogBodySmall.fontSize, 20); + expect(textTheme.kycDialogBodySmall.fontWeight, FontWeight.bold); + + expect(textTheme.kycDialogBody.color, const Color(0xff86809D)); + expect(textTheme.kycDialogBody.fontSize, 14); + expect(textTheme.kycDialogBody.fontWeight, FontWeight.w700); + + expect(textTheme.kycDialogFooter.color, const Color(0xff86809D)); + expect(textTheme.kycDialogFooter.fontSize, 10); + expect(textTheme.kycDialogFooter.fontWeight, FontWeight.w500); + + expect(textTheme.credentialCategoryTitle.color, const Color(0xffEDEAF5)); + expect(textTheme.credentialCategoryTitle.fontSize, 18); + expect(textTheme.credentialCategoryTitle.fontWeight, FontWeight.w600); + + expect(textTheme.credentialCategorySubTitle.color, const Color(0xFF86809D)); + expect(textTheme.credentialCategorySubTitle.fontSize, 14); + expect(textTheme.credentialCategorySubTitle.fontWeight, FontWeight.normal); + + expect(textTheme.credentialSurfaceText.color, const Color(0xff00B267)); + expect(textTheme.credentialSurfaceText.fontSize, 10); + expect(textTheme.credentialSurfaceText.fontWeight, FontWeight.w400); + + expect(textTheme.errorMessage.color, const Color(0xffFFFFFF)); + expect(textTheme.errorMessage.fontSize, 15); + expect(textTheme.errorMessage.fontWeight, FontWeight.w400); + + expect(textTheme.accountsText.color, const Color(0xffFFFFFF)); + expect(textTheme.accountsText.fontSize, 22); + expect(textTheme.accountsText.fontWeight, FontWeight.w600); + + expect(textTheme.accountsName.color, const Color(0xffFFFFFF)); + expect(textTheme.accountsName.fontSize, 18); + expect(textTheme.accountsName.fontWeight, FontWeight.w500); + + expect(textTheme.accountsListItemTitle.color, const Color(0xffFFFFFF)); + expect(textTheme.accountsListItemTitle.fontSize, 16); + expect(textTheme.accountsListItemTitle.fontWeight, FontWeight.w500); + + expect(textTheme.walletAddress.color, const Color(0xFF757575)); + expect(textTheme.walletAddress.fontSize, 12); + expect(textTheme.walletAddress.fontWeight, FontWeight.w400); + + expect(textTheme.textButton.color, const Color(0xff6600FF)); + expect(textTheme.textButton.fontSize, 16); + expect(textTheme.textButton.fontWeight, FontWeight.w700); + + expect(textTheme.scrollText.color, const Color(0xffFFFFFF)); + expect(textTheme.scrollText.fontSize, 9); + expect(textTheme.scrollText.fontWeight, FontWeight.w500); + + expect(textTheme.passPhraseText.color, const Color(0xffD1CCE3)); + expect(textTheme.passPhraseText.fontSize, 14); + expect(textTheme.passPhraseText.fontWeight, FontWeight.w600); + + expect(textTheme.message.color, const Color(0xffEDEAF5)); + expect(textTheme.message.fontSize, 18); + expect(textTheme.message.fontWeight, FontWeight.w600); + + expect(textTheme.subMessage.color, const Color(0xff71CBFF)); + expect(textTheme.subMessage.fontSize, 15); + expect(textTheme.subMessage.fontWeight, FontWeight.w400); + + expect(textTheme.genPhraseSubmessage.color, const Color(0xff71CBFF)); + expect(textTheme.genPhraseSubmessage.fontSize, 18); + expect(textTheme.genPhraseSubmessage.fontWeight, FontWeight.w400); + + expect(textTheme.pheaseVerifySubmessage.color, const Color(0xff86809D)); + expect(textTheme.pheaseVerifySubmessage.fontSize, 18); + expect(textTheme.pheaseVerifySubmessage.fontWeight, FontWeight.w400); + + expect(textTheme.credentialBaseLightText.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialBaseLightText.fontSize, 16); + expect(textTheme.credentialBaseLightText.fontWeight, FontWeight.w300); + + expect(textTheme.credentialBaseBoldText.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialBaseBoldText.fontSize, 16); + expect(textTheme.credentialBaseBoldText.fontWeight, FontWeight.w600); + + expect(textTheme.credentialBaseTitleText.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialBaseTitleText.fontSize, 36); + expect(textTheme.credentialBaseTitleText.fontWeight, FontWeight.w600); + + expect(textTheme.copyToClipBoard.color, const Color(0xffDED6EA)); + expect(textTheme.copyToClipBoard.fontSize, 18); + expect(textTheme.copyToClipBoard.fontWeight, FontWeight.w400); + expect(textTheme.copyToClipBoard.decoration, TextDecoration.underline); + + expect(textTheme.onBoardingCheckMessage.color, const Color(0xFFFFFFFF)); + expect(textTheme.onBoardingCheckMessage.fontSize, 18); + expect(textTheme.onBoardingCheckMessage.fontWeight, FontWeight.w700); + + expect(textTheme.messageTitle.color, const Color(0xffEDEAF5)); + expect(textTheme.messageTitle.fontSize, 20); + expect(textTheme.messageTitle.fontWeight, FontWeight.w600); + + expect(textTheme.messageSubtitle.color, const Color(0xffEDEAF5)); + expect(textTheme.messageSubtitle.fontSize, 16); + expect(textTheme.messageSubtitle.fontWeight, FontWeight.w400); + + expect(textTheme.radioTitle.color, const Color(0xFFFFFFFF)); + expect(textTheme.radioTitle.fontSize, 20); + expect(textTheme.radioTitle.fontWeight, FontWeight.w600); + + expect(textTheme.radioOption.color, const Color(0xFFFFFFFF)); + expect(textTheme.radioOption.fontSize, 16); + expect(textTheme.radioOption.fontWeight, FontWeight.w400); + + expect(textTheme.credentialManifestTitle1.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialManifestTitle1.fontSize, 18); + expect(textTheme.credentialManifestTitle1.fontWeight, FontWeight.w700); + + expect( + textTheme.credentialManifestDescription.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialManifestDescription.fontSize, 16); + expect(textTheme.credentialManifestDescription.fontWeight, FontWeight.w400); + + expect(textTheme.credentialManifestTitle2.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialManifestTitle2.fontSize, 16); + expect(textTheme.credentialManifestTitle2.fontWeight, FontWeight.w700); + + expect(textTheme.credentialSubtitle.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialSubtitle.fontSize, 14); + expect(textTheme.credentialSubtitle.fontWeight, FontWeight.w400); + + expect(textTheme.credentialStatus.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialStatus.fontSize, 18); + expect(textTheme.credentialStatus.fontWeight, FontWeight.w800); + + expect(textTheme.beaconRequestPermission.color, const Color(0xff86809D)); + expect(textTheme.beaconRequestPermission.fontSize, 16); + expect(textTheme.beaconRequestPermission.fontWeight, FontWeight.w400); + + expect(textTheme.beaconSelectAccont.color, const Color(0xffFFFFFF)); + expect(textTheme.beaconSelectAccont.fontSize, 18); + expect(textTheme.beaconSelectAccont.fontWeight, FontWeight.w800); + + expect(textTheme.uploadFileTitle.color, const Color(0xffFFFFFF)); + expect(textTheme.uploadFileTitle.fontSize, 18); + expect(textTheme.uploadFileTitle.fontWeight, FontWeight.w800); + + expect(textTheme.beaconPermissionTitle.color, const Color(0xffFFFFFF)); + expect(textTheme.beaconPermissionTitle.fontSize, 18); + expect(textTheme.beaconPermissionTitle.fontWeight, FontWeight.w800); + + expect(textTheme.beaconPermissions.color, const Color(0xffFFFFFF)); + expect(textTheme.beaconPermissions.fontSize, 16); + expect(textTheme.beaconPermissions.fontWeight, FontWeight.w400); + + expect(textTheme.beaconPayload.color, const Color(0xffFFFFFF)); + expect(textTheme.beaconPayload.fontSize, 16); + expect(textTheme.beaconPayload.fontWeight, FontWeight.w400); + + expect(textTheme.beaconWalletAddress.color, const Color(0xffFFFFFF)); + expect(textTheme.beaconWalletAddress.fontSize, 16); + expect(textTheme.beaconWalletAddress.fontWeight, FontWeight.w400); + + expect(textTheme.dappName.color, const Color(0xffFFFFFF)); + expect(textTheme.dappName.fontSize, 16); + expect(textTheme.dappName.fontWeight, FontWeight.w400); + + expect(textTheme.cacheErrorMessage.color, const Color(0xffFFFFFF)); + expect(textTheme.cacheErrorMessage.fontSize, 16); + expect(textTheme.cacheErrorMessage.fontWeight, FontWeight.w600); + + expect(textTheme.credentialSteps.color, const Color(0xffFFFFFF)); + expect(textTheme.credentialSteps.fontSize, 18); + expect(textTheme.credentialSteps.fontWeight, FontWeight.w600); + + expect(textTheme.faqQue.color, const Color(0xffFFFFFF)); + expect(textTheme.faqQue.fontSize, 16); + expect(textTheme.faqQue.fontWeight, FontWeight.normal); + + expect(textTheme.faqAns.color, const Color(0xFF757575)); + expect(textTheme.faqAns.fontSize, 14); + expect(textTheme.faqAns.fontWeight, FontWeight.w400); + + expect(textTheme.proofCardDetail.color, const Color(0xffFFFFFF)); + expect(textTheme.proofCardDetail.fontSize, 12); + expect(textTheme.proofCardDetail.fontWeight, FontWeight.w300); }); }