diff --git a/lib/core/data/datasources/account_remote_data_source.dart b/lib/core/data/datasources/account_remote_data_source.dart index e91d8fbcd..615142f69 100644 --- a/lib/core/data/datasources/account_remote_data_source.dart +++ b/lib/core/data/datasources/account_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/user/data/models/user_model.dart'; import 'package:coffeecard/features/user/domain/entities/user.dart'; @@ -24,44 +23,35 @@ class AccountRemoteDataSource { Future> login( String email, String encodedPasscode, - ) async { - return executor( - () => apiV1.apiV1AccountLoginPost( - body: LoginDto( - email: email, - password: encodedPasscode, - version: ApiUriConstants.minAppVersion, - ), - ), - ).bindFuture( - (result) => AuthenticatedUser( - email: email, - token: result.token!, - ), - ); + ) { + return executor + .execute( + () => apiV1.apiV1AccountLoginPost( + body: LoginDto( + email: email, + password: encodedPasscode, + version: ApiUriConstants.minAppVersion, + ), + ), + ) + .map((result) => AuthenticatedUser(email: email, token: result.token!)); } - Future> getUser() async { - return executor( - apiV2.apiV2AccountGet, - ).bindFuture(UserModel.fromResponse); + Future> getUser() { + return executor.execute(apiV2.apiV2AccountGet).map(UserModel.fromResponse); } - Future> requestPasscodeReset( - String email, - ) async { - final result = await executor( - () => apiV1.apiV1AccountForgotpasswordPost(body: EmailDto(email: email)), + Future> requestPasscodeReset(String email) { + final body = EmailDto(email: email); + return executor.executeAndDiscard( + () => apiV1.apiV1AccountForgotpasswordPost(body: body), ); - - return result.pure(null); } - Future> emailExists(String email) async { - return executor( - () => apiV2.apiV2AccountEmailExistsPost( - body: EmailExistsRequest(email: email), - ), - ).bindFuture((result) => result.emailExists); + Future> emailExists(String email) { + final body = EmailExistsRequest(email: email); + return executor + .execute(() => apiV2.apiV2AccountEmailExistsPost(body: body)) + .map((result) => result.emailExists); } } diff --git a/lib/core/network/network_request_executor.dart b/lib/core/network/network_request_executor.dart index 0170f2629..56f5499f1 100644 --- a/lib/core/network/network_request_executor.dart +++ b/lib/core/network/network_request_executor.dart @@ -4,6 +4,11 @@ import 'package:coffeecard/utils/firebase_analytics_event_logging.dart'; import 'package:fpdart/fpdart.dart'; import 'package:logger/logger.dart'; +part 'network_request_executor_mapping.dart'; + +typedef _NetworkRequest = Future> Function(); +typedef _ExecutorResult = Future>; + class NetworkRequestExecutor { final Logger logger; final FirebaseAnalyticsEventLogging firebaseLogger; @@ -13,19 +18,24 @@ class NetworkRequestExecutor { required this.firebaseLogger, }); - Future> call( - Future> Function() request, - ) async { + /// Executes a network request and returns an [Either]. + /// + /// If the request fails, a [NetworkFailure] is returned in a [Left]. + /// If the request succeeds, the response body of type + /// [Body] is returned in a [Right]. + /// + /// If the response body type is empty or dynamic, use [executeAndDiscard] + /// instead, which always returns [Unit] in a [Right] if the request succeeds. + _ExecutorResult execute(_NetworkRequest request) async { try { final response = await request(); // request is successful if response code is >= 200 && <300 if (!response.isSuccessful) { - logResponse(response); + _logResponse(response); return Left(ServerFailure.fromResponse(response)); } - - return Right(response.body as Result); + return Right(response.body as Body); } on Exception catch (e) { // could not connect to backend for whatever reason logger.e(e.toString()); @@ -33,15 +43,31 @@ class NetworkRequestExecutor { } } - void logResponse(Response response) { + /// Executes the network [request] and returns the result as an [Either]. + /// + /// If the request fails, a [NetworkFailure] is returned in a [Left]. + /// If the request succeeds, [Unit] is returned in a [Right] and + /// the orignial response body is discarded. + /// + /// This method is useful as it allows to discard the response body when its + /// type is dynamic, e.g. when it is expected to be empty. This avoids having + /// to provide dummy values for dynamic response bodies in Mockito tests. + _ExecutorResult executeAndDiscard(_NetworkRequest request) async { + final result = await execute(request); + return result.map((_) => unit); + } + + /// Logs the response to the console and to Firebase. + /// + /// Does not log 401 responses to Firebase since these are expected when + /// the user is not logged in. + void _logResponse(Response response) { logger.e(response.toString()); - final ignore = [401]; + final ignoreCodes = [401]; - if (ignore.contains(response.statusCode)) { - return; + if (!ignoreCodes.contains(response.statusCode)) { + firebaseLogger.errorEvent(response.toString()); } - - firebaseLogger.errorEvent(response.toString()); } } diff --git a/lib/core/network/network_request_executor_mapping.dart b/lib/core/network/network_request_executor_mapping.dart new file mode 100644 index 000000000..8a0f11dac --- /dev/null +++ b/lib/core/network/network_request_executor_mapping.dart @@ -0,0 +1,19 @@ +part of 'network_request_executor.dart'; + +extension ExecutorMapX on _ExecutorResult { + /// If the result of the [Future] is a [Right], maps the value to a [C]. + _ExecutorResult map(C Function(R) mapper) async { + final result = await this; + return result.map(mapper); + } +} + +extension ExecutorMapAllX on _ExecutorResult> { + /// If the result of the [Future] is a [Right], + /// maps all values to a [List] of [C]. + // TODO(marfavi): return Iterable instead of List? + _ExecutorResult> mapAll(C Function(R) mapper) async { + final result = await this; + return result.map((items) => items.map(mapper).toList()); + } +} diff --git a/lib/features/environment/data/datasources/environment_remote_data_source.dart b/lib/features/environment/data/datasources/environment_remote_data_source.dart index ca74fa2a8..ebd2b3f61 100644 --- a/lib/features/environment/data/datasources/environment_remote_data_source.dart +++ b/lib/features/environment/data/datasources/environment_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/environment/domain/entities/environment.dart'; import 'package:coffeecard/generated/api/coffeecard_api_v2.swagger.dart'; @@ -14,9 +13,9 @@ class EnvironmentRemoteDataSource { final CoffeecardApiV2 apiV2; final NetworkRequestExecutor executor; - Future> getEnvironmentType() async { - return executor( - apiV2.apiV2AppconfigGet, - ).bindFuture(Environment.fromAppConfig); + Future> getEnvironmentType() { + return executor + .execute(apiV2.apiV2AppconfigGet) + .map(Environment.fromAppConfig); } } diff --git a/lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart b/lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart index 71a1f42b8..d077c8d17 100644 --- a/lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart +++ b/lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/leaderboard/data/models/leaderboard_user_model.dart'; import 'package:coffeecard/features/leaderboard/domain/entities/leaderboard_user.dart'; @@ -27,19 +26,19 @@ class LeaderboardRemoteDataSource { Future>> getLeaderboard( LeaderboardFilter category, int top, - ) async { - return executor( - () => apiV2.apiV2LeaderboardTopGet(preset: category.label, top: top), - ).bindFuture( - (result) => result.map(LeaderboardUserModel.fromDTO).toList(), - ); + ) { + return executor + .execute( + () => apiV2.apiV2LeaderboardTopGet(preset: category.label, top: top), + ) + .mapAll(LeaderboardUserModel.fromDTO); } Future> getLeaderboardUser( LeaderboardFilter category, - ) async { - return executor( - () => apiV2.apiV2LeaderboardGet(preset: category.label), - ).bindFuture(LeaderboardUserModel.fromDTO); + ) { + return executor + .execute(() => apiV2.apiV2LeaderboardGet(preset: category.label)) + .map(LeaderboardUserModel.fromDTO); } } diff --git a/lib/features/occupation/data/datasources/occupation_remote_data_source.dart b/lib/features/occupation/data/datasources/occupation_remote_data_source.dart index bb6970f49..e78388248 100644 --- a/lib/features/occupation/data/datasources/occupation_remote_data_source.dart +++ b/lib/features/occupation/data/datasources/occupation_remote_data_source.dart @@ -13,11 +13,9 @@ class OccupationRemoteDataSource { required this.executor, }); - Future>> getOccupations() async { - final result = await executor( - () => api.apiV1ProgrammesGet(), - ); - return result - .map((result) => result.map(OccupationModel.fromDTOV1).toList()); + Future>> getOccupations() { + return executor + .execute(api.apiV1ProgrammesGet) + .mapAll(OccupationModel.fromDTOV1); } } diff --git a/lib/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart b/lib/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart index 11d567d16..3a530624e 100644 --- a/lib/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart +++ b/lib/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart @@ -1,5 +1,7 @@ import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; +import 'package:coffeecard/features/opening_hours/data/models/opening_hours_model.dart'; +import 'package:coffeecard/features/opening_hours/domain/entities/opening_hours.dart'; import 'package:coffeecard/generated/api/shiftplanning_api.swagger.dart'; import 'package:fpdart/fpdart.dart'; @@ -16,18 +18,17 @@ class OpeningHoursRemoteDataSource { final shortkey = 'analog'; /// Check if the cafe is open. - Future> isOpen() async { - final result = await executor( - () async => api.apiOpenShortKeyGet(shortKey: shortkey), - ); - - return result.map((result) => result.open); + Future> isOpen() { + return executor + .execute(() => api.apiOpenShortKeyGet(shortKey: shortkey)) + .map((result) => result.open); } - /// Get the opening hours of the cafe. - Future>> getOpeningHours() async { - return executor( - () => api.apiShiftsShortKeyGet(shortKey: shortkey), - ); + /// Get the opening hours of the cafe, including today's opening hours and + /// the opening hours for the next 7 days. + Future> getOpeningHours() { + return executor + .execute(() => api.apiShiftsShortKeyGet(shortKey: shortkey)) + .map(OpeningHoursModel.fromDTO); } } diff --git a/lib/features/opening_hours/data/repositories/opening_hours_repository_impl.dart b/lib/features/opening_hours/data/models/opening_hours_model.dart similarity index 50% rename from lib/features/opening_hours/data/repositories/opening_hours_repository_impl.dart rename to lib/features/opening_hours/data/models/opening_hours_model.dart index 1edaa5d6e..2e4706952 100644 --- a/lib/features/opening_hours/data/repositories/opening_hours_repository_impl.dart +++ b/lib/features/opening_hours/data/models/opening_hours_model.dart @@ -1,37 +1,31 @@ import 'package:coffeecard/base/strings.dart'; -import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/extensions/string_extensions.dart'; import 'package:coffeecard/features/opening_hours/domain/entities/opening_hours.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; -import 'package:coffeecard/generated/api/shiftplanning_api.swagger.dart'; +import 'package:coffeecard/generated/api/shiftplanning_api.models.swagger.dart'; import 'package:coffeecard/models/opening_hours_day.dart'; -import 'package:fpdart/fpdart.dart'; -class OpeningHoursRepositoryImpl implements OpeningHoursRepository { - final OpeningHoursRemoteDataSource dataSource; +class OpeningHoursModel extends OpeningHours { + const OpeningHoursModel({ + required super.allOpeningHours, + required super.todaysOpeningHours, + }); - OpeningHoursRepositoryImpl({required this.dataSource}); - - @override - Future> getOpeningHours(int weekday) async { - final openingHours = await dataSource.getOpeningHours(); - - return openingHours.map((openingHours) { - final openingHoursMap = transformOpeningHours(openingHours); + factory OpeningHoursModel.fromDTO(List allShifts) { + final openingHours = _transformOpeningHours(allShifts); + final todaysOpeningHours = _calculateTodaysOpeningHours( + DateTime.now().weekday, + openingHours, + ); - return OpeningHours( - allOpeningHours: openingHoursMap, - todaysOpeningHours: calculateTodaysOpeningHours( - weekday, - openingHoursMap, - ), - ); - }); + return OpeningHoursModel( + allOpeningHours: openingHours, + todaysOpeningHours: todaysOpeningHours, + ); } - // An [OpeningHoursDTO] actually represents a barista shift, so "dto.start" - // means the start of the shift and "dto.end" means the end of the shift. - Map transformOpeningHours(List allShifts) { + static Map _transformOpeningHours( + List allShifts, + ) { final shiftsByWeekday = >{ DateTime.monday: [], DateTime.tuesday: [], @@ -47,6 +41,11 @@ class OpeningHoursRepositoryImpl implements OpeningHoursRepository { shiftsByWeekday[weekday]!.add(shift); } + // For each weekday, map the shifts to a string representation of the + // opening hours e.g '8 - 16' or 'Closed' + // + // This assumes that the shifts are sorted by start time and that there are + // no overlapping shifts. return shiftsByWeekday.map( (day, shifts) => MapEntry( day, @@ -58,8 +57,8 @@ class OpeningHoursRepositoryImpl implements OpeningHoursRepository { } /// Return the current weekday and the corresponding opening hours e.g - /// 'Monday: 8 - 16' - String calculateTodaysOpeningHours( + /// 'Mondays: 8 - 16' + static String _calculateTodaysOpeningHours( int weekday, Map openingHours, ) { diff --git a/lib/features/opening_hours/domain/repositories/opening_hours_repository.dart b/lib/features/opening_hours/domain/repositories/opening_hours_repository.dart deleted file mode 100644 index acb4b2aca..000000000 --- a/lib/features/opening_hours/domain/repositories/opening_hours_repository.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/features/opening_hours/domain/entities/opening_hours.dart'; -import 'package:fpdart/fpdart.dart'; - -abstract interface class OpeningHoursRepository { - Future> getOpeningHours(int weekday); -} diff --git a/lib/features/opening_hours/domain/usecases/check_open_status.dart b/lib/features/opening_hours/domain/usecases/check_open_status.dart index ad70b2825..0ea8771a4 100644 --- a/lib/features/opening_hours/domain/usecases/check_open_status.dart +++ b/lib/features/opening_hours/domain/usecases/check_open_status.dart @@ -1,6 +1,6 @@ import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/usecases/usecase.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart'; import 'package:fpdart/fpdart.dart'; class CheckOpenStatus implements UseCase { diff --git a/lib/features/opening_hours/domain/usecases/get_opening_hours.dart b/lib/features/opening_hours/domain/usecases/get_opening_hours.dart index 58c752dfe..51340f7ec 100644 --- a/lib/features/opening_hours/domain/usecases/get_opening_hours.dart +++ b/lib/features/opening_hours/domain/usecases/get_opening_hours.dart @@ -1,16 +1,16 @@ import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/usecases/usecase.dart'; +import 'package:coffeecard/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart'; import 'package:coffeecard/features/opening_hours/domain/entities/opening_hours.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; import 'package:fpdart/fpdart.dart'; class GetOpeningHours implements UseCase { - final OpeningHoursRepository repository; + final OpeningHoursRemoteDataSource dataSource; - GetOpeningHours({required this.repository}); + GetOpeningHours({required this.dataSource}); @override - Future> call(NoParams params) async { - return repository.getOpeningHours(DateTime.now().weekday); + Future> call(NoParams params) { + return dataSource.getOpeningHours(); } } diff --git a/lib/features/opening_hours/opening_hours.dart b/lib/features/opening_hours/opening_hours.dart deleted file mode 100644 index e6cccc5b2..000000000 --- a/lib/features/opening_hours/opening_hours.dart +++ /dev/null @@ -1,7 +0,0 @@ -export 'data/datasources/opening_hours_remote_data_source.dart'; -export 'data/repositories/opening_hours_repository_impl.dart'; -export 'domain/repositories/opening_hours_repository.dart'; -export 'domain/usecases/check_open_status.dart'; -export 'domain/usecases/get_opening_hours.dart'; -export 'presentation/cubit/opening_hours_cubit.dart'; -export 'presentation/widgets/opening_hours_indicator.dart'; diff --git a/lib/features/opening_hours/presentation/cubit/opening_hours_cubit.dart b/lib/features/opening_hours/presentation/cubit/opening_hours_cubit.dart index 0bcf38d7f..3114061c5 100644 --- a/lib/features/opening_hours/presentation/cubit/opening_hours_cubit.dart +++ b/lib/features/opening_hours/presentation/cubit/opening_hours_cubit.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:coffeecard/core/usecases/usecase.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/domain/usecases/check_open_status.dart'; +import 'package:coffeecard/features/opening_hours/domain/usecases/get_opening_hours.dart'; import 'package:equatable/equatable.dart'; part 'opening_hours_state.dart'; diff --git a/lib/features/opening_hours/presentation/pages/opening_hours_page.dart b/lib/features/opening_hours/presentation/pages/opening_hours_page.dart index ff10632c8..7bbc6c571 100644 --- a/lib/features/opening_hours/presentation/pages/opening_hours_page.dart +++ b/lib/features/opening_hours/presentation/pages/opening_hours_page.dart @@ -1,7 +1,7 @@ import 'package:coffeecard/base/strings.dart'; import 'package:coffeecard/base/style/colors.dart'; import 'package:coffeecard/base/style/text_styles.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/presentation/cubit/opening_hours_cubit.dart'; import 'package:coffeecard/widgets/components/scaffold.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; diff --git a/lib/features/opening_hours/presentation/widgets/opening_hours_indicator.dart b/lib/features/opening_hours/presentation/widgets/opening_hours_indicator.dart index 865899be4..27e5fafb6 100644 --- a/lib/features/opening_hours/presentation/widgets/opening_hours_indicator.dart +++ b/lib/features/opening_hours/presentation/widgets/opening_hours_indicator.dart @@ -1,7 +1,7 @@ import 'package:coffeecard/base/strings.dart'; import 'package:coffeecard/base/style/colors.dart'; import 'package:coffeecard/base/style/text_styles.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/presentation/cubit/opening_hours_cubit.dart'; import 'package:coffeecard/utils/analog_icons.dart'; import 'package:coffeecard/widgets/components/helpers/shimmer_builder.dart'; import 'package:flutter/widgets.dart'; diff --git a/lib/features/product/data/datasources/product_remote_data_source.dart b/lib/features/product/data/datasources/product_remote_data_source.dart index 7038899d1..212eb6729 100644 --- a/lib/features/product/data/datasources/product_remote_data_source.dart +++ b/lib/features/product/data/datasources/product_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/product/data/models/product_model.dart'; import 'package:coffeecard/features/product/domain/entities/product.dart'; @@ -15,9 +14,9 @@ class ProductRemoteDataSource { required this.executor, }); - Future>> getProducts() async { - return executor( - apiV1.apiV1ProductsGet, - ).bindFuture((result) => result.map(ProductModel.fromDTO).toList()); + Future>> getProducts() { + return executor + .execute(apiV1.apiV1ProductsGet) + .mapAll(ProductModel.fromDTO); } } diff --git a/lib/features/purchase/data/datasources/purchase_remote_data_source.dart b/lib/features/purchase/data/datasources/purchase_remote_data_source.dart index 07e01269d..a641561c3 100644 --- a/lib/features/purchase/data/datasources/purchase_remote_data_source.dart +++ b/lib/features/purchase/data/datasources/purchase_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/purchase/data/models/initiate_purchase_model.dart'; import 'package:coffeecard/features/purchase/data/models/single_purchase_model.dart'; @@ -22,23 +21,23 @@ class PurchaseRemoteDataSource { Future> initiatePurchase( int productId, PaymentType paymentType, - ) async { - return executor( - () => apiV2.apiV2PurchasesPost( - body: InitiatePurchaseRequest( - productId: productId, - paymentType: paymentTypeToJson(paymentType), - ), - ), - ).bindFuture(InitiatePurchaseModel.fromDto); + ) { + return executor + .execute( + () => apiV2.apiV2PurchasesPost( + body: InitiatePurchaseRequest( + productId: productId, + paymentType: paymentTypeToJson(paymentType), + ), + ), + ) + .map(InitiatePurchaseModel.fromDto); } /// Get a purchase by its purchase id - Future> getPurchase( - int purchaseId, - ) async { - return executor( - () => apiV2.apiV2PurchasesIdGet(id: purchaseId), - ).bindFuture(SinglePurchaseModel.fromDto); + Future> getPurchase(int purchaseId) { + return executor + .execute(() => apiV2.apiV2PurchasesIdGet(id: purchaseId)) + .map(SinglePurchaseModel.fromDto); } } diff --git a/lib/features/receipt/data/datasources/receipt_remote_data_source.dart b/lib/features/receipt/data/datasources/receipt_remote_data_source.dart index 0eb8f08c1..1879bea61 100644 --- a/lib/features/receipt/data/datasources/receipt_remote_data_source.dart +++ b/lib/features/receipt/data/datasources/receipt_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/receipt/data/models/purchase_receipt_model.dart'; import 'package:coffeecard/features/receipt/data/models/swipe_receipt_model.dart'; @@ -17,19 +16,16 @@ class ReceiptRemoteDataSource { }); /// Retrieves all of the users used receipts - Future>> getUsersUsedTicketsReceipts() async { - return executor( - () => apiV2.apiV2TicketsGet(includeUsed: true), - ).bindFuture( - (result) => result.map(SwipeReceiptModel.fromTicketResponse).toList(), - ); + Future>> getUsersUsedTicketsReceipts() { + return executor + .execute(() => apiV2.apiV2TicketsGet(includeUsed: true)) + .mapAll(SwipeReceiptModel.fromTicketResponse); } /// Retrieves all of the users purchase receipts - Future>> getUserPurchasesReceipts() async { - return executor(apiV2.apiV2PurchasesGet).bindFuture( - (result) => - result.map(PurchaseReceiptModel.fromSimplePurchaseResponse).toList(), - ); + Future>> getUserPurchasesReceipts() { + return executor + .execute(apiV2.apiV2PurchasesGet) + .mapAll(PurchaseReceiptModel.fromSimplePurchaseResponse); } } diff --git a/lib/features/register/data/datasources/register_remote_data_source.dart b/lib/features/register/data/datasources/register_remote_data_source.dart index 0c67264e9..2a6719be5 100644 --- a/lib/features/register/data/datasources/register_remote_data_source.dart +++ b/lib/features/register/data/datasources/register_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/generated/api/coffeecard_api_v2.swagger.dart'; import 'package:fpdart/fpdart.dart'; @@ -13,13 +12,13 @@ class RegisterRemoteDataSource { final CoffeecardApiV2 apiV2; final NetworkRequestExecutor executor; - Future> register( + Future> register( String name, String email, String encodedPasscode, int occupationId, - ) async { - return executor( + ) { + return executor.executeAndDiscard( () => apiV2.apiV2AccountPost( body: RegisterAccountRequest( name: name, @@ -28,6 +27,6 @@ class RegisterRemoteDataSource { programmeId: occupationId, ), ), - ).bindFuture((_) => const Right(null)); + ); } } diff --git a/lib/features/register/domain/usecases/register_user.dart b/lib/features/register/domain/usecases/register_user.dart index 49d00e425..7bf6e871b 100644 --- a/lib/features/register/domain/usecases/register_user.dart +++ b/lib/features/register/domain/usecases/register_user.dart @@ -4,13 +4,13 @@ import 'package:coffeecard/features/register/data/datasources/register_remote_da import 'package:equatable/equatable.dart'; import 'package:fpdart/fpdart.dart'; -class RegisterUser implements UseCase { +class RegisterUser implements UseCase { final RegisterRemoteDataSource remoteDataSource; RegisterUser({required this.remoteDataSource}); @override - Future> call(Params params) { + Future> call(Params params) { return remoteDataSource.register( params.name, params.email, diff --git a/lib/features/ticket/data/datasources/ticket_remote_data_source.dart b/lib/features/ticket/data/datasources/ticket_remote_data_source.dart index 20ea7bd14..68f824921 100644 --- a/lib/features/ticket/data/datasources/ticket_remote_data_source.dart +++ b/lib/features/ticket/data/datasources/ticket_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/receipt/data/models/swipe_receipt_model.dart'; import 'package:coffeecard/features/receipt/domain/entities/receipt.dart'; @@ -20,37 +19,39 @@ class TicketRemoteDataSource { final CoffeecardApiV2 apiV2; final NetworkRequestExecutor executor; - Future>> - getUserTickets() async { - return executor( - () => apiV2.apiV2TicketsGet(includeUsed: false), - ).bindFuture( - (result) => result - .groupListsBy((t) => t.productId) - .entries - .map( - (entry) { - final MapEntry(key: id, value: tickets) = entry; - // Join ticket names if they share the same product id - final ticketName = - tickets.map((t) => t.productName).toSet().join('/'); - return TicketCountModel( - count: tickets.length, - productName: ticketName, - productId: id, - ); - }, - ) - .sortedBy((t) => t.productId) - .toList(), - ); + Future>> getUserTickets() { + // Mapper function for mapping a list of tickets (all with the same product + // id) to a TicketCountModel. + // + // This also takes into account that there might be + // some tickets with the same product id, but different names. + TicketCountModel mapper(MapEntry> entry) { + final MapEntry(key: id, value: tickets) = entry; + // If there are multiple ticket names present, join them with a slash. + final ticketName = tickets.map((t) => t.productName).toSet().join('/'); + + return TicketCountModel( + count: tickets.length, + productName: ticketName, + productId: id, + ); + } + + return executor + .execute(() => apiV2.apiV2TicketsGet(includeUsed: false)) + .map( + (result) => result + .groupListsBy((t) => t.productId) + .entries + .map(mapper) + .sortedBy((t) => t.productId), + ); } - Future> useTicket(int productId) async { - return executor( - () => apiV1.apiV1TicketsUsePost( - body: UseTicketDTO(productId: productId), - ), - ).bindFuture(SwipeReceiptModel.fromTicketDto); + Future> useTicket(int productId) { + final body = UseTicketDTO(productId: productId); + return executor + .execute(() => apiV1.apiV1TicketsUsePost(body: body)) + .map(SwipeReceiptModel.fromTicketDto); } } diff --git a/lib/features/user/data/datasources/user_remote_data_source.dart b/lib/features/user/data/datasources/user_remote_data_source.dart index 506c7df1b..5db46373a 100644 --- a/lib/features/user/data/datasources/user_remote_data_source.dart +++ b/lib/features/user/data/datasources/user_remote_data_source.dart @@ -1,6 +1,7 @@ import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/user/data/models/user_model.dart'; +import 'package:coffeecard/features/user/domain/entities/user.dart'; import 'package:coffeecard/generated/api/coffeecard_api_v2.swagger.dart'; import 'package:coffeecard/models/account/update_user.dart'; import 'package:fpdart/fpdart.dart'; @@ -15,36 +16,30 @@ class UserRemoteDataSource { }); /// Get the currently logged in user. - Future> getUser() async { - final result = await executor(() => apiV2.apiV2AccountGet()); - - return result.map(UserModel.fromResponse); + Future> getUser() { + return executor.execute(apiV2.apiV2AccountGet).map(UserModel.fromResponse); } /// Updates the details of the currently logged in user based on /// the non-null details in [user] - Future> updateUserDetails( - UpdateUser user, - ) async { - final result = await executor( - () => apiV2.apiV2AccountPut( - body: UpdateUserRequest( - name: user.name, - programmeId: user.occupationId, - email: user.email, - privacyActivated: user.privacyActivated, - password: user.encodedPasscode, - ), - ), - ); - - return result.map(UserModel.fromResponse); + Future> updateUserDetails(UpdateUser user) { + return executor + .execute( + () => apiV2.apiV2AccountPut( + body: UpdateUserRequest( + name: user.name, + programmeId: user.occupationId, + email: user.email, + privacyActivated: user.privacyActivated, + password: user.encodedPasscode, + ), + ), + ) + .map(UserModel.fromResponse); } /// Request account deletion for the currently logged in user. - Future> requestAccountDeletion() async { - final result = await executor(() => apiV2.apiV2AccountDelete()); - - return result.bind((_) => const Right(null)); + Future> requestAccountDeletion() { + return executor.executeAndDiscard(apiV2.apiV2AccountDelete); } } diff --git a/lib/features/user/domain/usecases/request_account_deletion.dart b/lib/features/user/domain/usecases/request_account_deletion.dart index e161c6a0f..fb9828c23 100644 --- a/lib/features/user/domain/usecases/request_account_deletion.dart +++ b/lib/features/user/domain/usecases/request_account_deletion.dart @@ -9,7 +9,7 @@ class RequestAccountDeletion implements UseCase { RequestAccountDeletion({required this.dataSource}); @override - Future> call(NoParams params) async { + Future> call(NoParams params) async { return dataSource.requestAccountDeletion(); } } diff --git a/lib/features/voucher/data/datasources/voucher_remote_data_source.dart b/lib/features/voucher/data/datasources/voucher_remote_data_source.dart index 10f36b099..5c7dcf8f8 100644 --- a/lib/features/voucher/data/datasources/voucher_remote_data_source.dart +++ b/lib/features/voucher/data/datasources/voucher_remote_data_source.dart @@ -1,5 +1,4 @@ import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/core/extensions/either_extensions.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/voucher/data/models/redeemed_voucher_model.dart'; import 'package:coffeecard/features/voucher/domain/entities/redeemed_voucher.dart'; @@ -17,9 +16,11 @@ class VoucherRemoteDataSource { Future> redeemVoucher( String voucher, - ) async { - return executor( - () => apiV1.apiV1PurchasesRedeemvoucherPost(voucherCode: voucher), - ).bindFuture(RedeemedVoucherModel.fromDTO); + ) { + return executor + .execute( + () => apiV1.apiV1PurchasesRedeemvoucherPost(voucherCode: voucher), + ) + .map(RedeemedVoucherModel.fromDTO); } } diff --git a/lib/service_locator.dart b/lib/service_locator.dart index 27b0f3a92..ccd6e896b 100644 --- a/lib/service_locator.dart +++ b/lib/service_locator.dart @@ -19,7 +19,10 @@ import 'package:coffeecard/features/login/domain/usecases/login_user.dart'; import 'package:coffeecard/features/occupation/data/datasources/occupation_remote_data_source.dart'; import 'package:coffeecard/features/occupation/domain/usecases/get_occupations.dart'; import 'package:coffeecard/features/occupation/presentation/cubit/occupation_cubit.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart'; +import 'package:coffeecard/features/opening_hours/domain/usecases/check_open_status.dart'; +import 'package:coffeecard/features/opening_hours/domain/usecases/get_opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/presentation/cubit/opening_hours_cubit.dart'; import 'package:coffeecard/features/product/data/datasources/product_remote_data_source.dart'; import 'package:coffeecard/features/product/domain/usecases/get_all_products.dart'; import 'package:coffeecard/features/product/presentation/cubit/product_cubit.dart'; @@ -142,14 +145,9 @@ void initOpeningHours() { ); // use case - sl.registerFactory(() => GetOpeningHours(repository: sl())); + sl.registerFactory(() => GetOpeningHours(dataSource: sl())); sl.registerFactory(() => CheckOpenStatus(dataSource: sl())); - // repository - sl.registerLazySingleton( - () => OpeningHoursRepositoryImpl(dataSource: sl()), - ); - // data source sl.registerLazySingleton( () => OpeningHoursRemoteDataSource(api: sl(), executor: sl()), diff --git a/lib/widgets/pages/home_page.dart b/lib/widgets/pages/home_page.dart index 517ab4289..c8fbd7f56 100644 --- a/lib/widgets/pages/home_page.dart +++ b/lib/widgets/pages/home_page.dart @@ -5,7 +5,7 @@ import 'package:coffeecard/base/style/colors.dart'; import 'package:coffeecard/base/style/text_styles.dart'; import 'package:coffeecard/features/leaderboard/presentation/cubit/leaderboard_cubit.dart'; import 'package:coffeecard/features/leaderboard/presentation/pages/leaderboard_page.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/presentation/cubit/opening_hours_cubit.dart'; import 'package:coffeecard/features/receipt/presentation/cubit/receipt_cubit.dart'; import 'package:coffeecard/features/receipt/presentation/pages/receipts_page.dart'; import 'package:coffeecard/features/settings/presentation/pages/settings_page.dart'; diff --git a/test/core/data/datasources/account_remote_data_source_test.dart b/test/core/data/datasources/account_remote_data_source_test.dart index 34fcf4ff5..0748451b9 100644 --- a/test/core/data/datasources/account_remote_data_source_test.dart +++ b/test/core/data/datasources/account_remote_data_source_test.dart @@ -42,7 +42,7 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); provideDummy>( @@ -57,7 +57,7 @@ void main() { 'should return [Right] when executor returns token', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right(v1.TokenDto(token: 'token')), ); @@ -76,7 +76,7 @@ void main() { group('getUser', () { test('should return [Left] if executor fails', () async { // arrange - when(executor.call(any)) + when(executor.execute(any)) .thenAnswer((_) async => const Left(ServerFailure(testError))); // act @@ -90,7 +90,7 @@ void main() { 'should return [Right] when executor returns user response', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right( v2.UserResponse( email: 'email', @@ -141,22 +141,22 @@ void main() { group('requestPasscodeReset', () { test('should return [Right] when executor succeeds', () async { // arrange - when(executor.call(any)).thenAnswer( - (_) async => Right(v1.MessageResponseDto()), + when(executor.executeAndDiscard(any)).thenAnswer( + (_) async => const Right(unit), ); // act final actual = await dataSource.requestPasscodeReset('name'); // assert - expect(actual, const Right(null)); + expect(actual.isRight(), true); }); }); group('emailExists', () { test('should return [Right] if executor succeeds', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right(v2.EmailExistsResponse(emailExists: true)), ); diff --git a/test/core/network/network_request_executor_test.dart b/test/core/network/network_request_executor_test.dart index 32ba84905..7291fa330 100644 --- a/test/core/network/network_request_executor_test.dart +++ b/test/core/network/network_request_executor_test.dart @@ -36,36 +36,51 @@ void main() { ); } - test('should return [ServerFailure] if api call fails', () async { - // arrange - final testResponse = responseFromStatusCode(500, body: ''); + group('execute', () { + test('should return [ServerFailure] if api call fails', () async { + // arrange + final testResponse = responseFromStatusCode(500, body: ''); - // act - final actual = await executor(() async => testResponse); + // act + final actual = await executor.execute(() async => testResponse); - // assert - expect(actual, const Left(ServerFailure(Strings.unknownErrorOccured))); - }); + // assert + expect(actual, const Left(ServerFailure(Strings.unknownErrorOccured))); + }); + + test('should return response body if api call succeeds', () async { + // arrange + final testResponse = responseFromStatusCode(200, body: 'some string'); + + // act + final actual = await executor.execute(() async => testResponse); + + // assert + expect(actual, const Right('some string')); + }); - test('should return response body if api call succeeds', () async { - // arrange - final testResponse = responseFromStatusCode(200, body: 'some string'); + test('should return [ConnectionFailure] if exception is caught', () async { + // arrange + final testException = Exception('some error'); - // act - final actual = await executor(() async => testResponse); + // act + final actual = await executor.execute(() async => throw testException); - // assert - expect(actual, const Right('some string')); + // assert + expect(actual, const Left(ConnectionFailure())); + }); }); - test('should return [ServerFailure] if call throws [Exception]', () async { - // arrange - final testException = Exception('some error'); + group('executeAndDiscard', () { + test('should return [Unit] if api call succeeds', () async { + // arrange + final testResponse = responseFromStatusCode(200, body: 'some string'); - // act - final actual = await executor(() async => throw testException); + // act + final actual = await executor.executeAndDiscard(() async => testResponse); - // assert - expect(actual, const Left(ConnectionFailure())); + // assert + expect(actual, const Right(unit)); + }); }); } diff --git a/test/features/environment/data/datasources/environment_remote_data_source_test.dart b/test/features/environment/data/datasources/environment_remote_data_source_test.dart index 1b97b7061..5f94e013a 100644 --- a/test/features/environment/data/datasources/environment_remote_data_source_test.dart +++ b/test/features/environment/data/datasources/environment_remote_data_source_test.dart @@ -30,7 +30,7 @@ void main() { }); test('should call executor', () async { // arrange - when(executor.call(any)) + when(executor.execute(any)) .thenAnswer((_) async => Right(AppConfig(environmentType: 'Test'))); // act diff --git a/test/features/leaderboard/data/datasources/leaderboard_remote_data_source_test.dart b/test/features/leaderboard/data/datasources/leaderboard_remote_data_source_test.dart index d69966fbf..96abed054 100644 --- a/test/features/leaderboard/data/datasources/leaderboard_remote_data_source_test.dart +++ b/test/features/leaderboard/data/datasources/leaderboard_remote_data_source_test.dart @@ -35,7 +35,7 @@ void main() { group('getLeaderboard', () { test('should return [Right] when executor succeeds', () async { // arrange - when(executor.call>(any)).thenAnswer( + when(executor.execute>(any)).thenAnswer( (_) async => const Right([]), ); @@ -54,7 +54,7 @@ void main() { test('should return [Left] when executor fails', () async { // arrange - when(executor.call>(any)).thenAnswer( + when(executor.execute>(any)).thenAnswer( (_) async => const Left(ServerFailure(testErrorMessage)), ); @@ -72,7 +72,7 @@ void main() { group('getLeaderboardUser', () { test('should return [Right] when executor succeeds', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right( LeaderboardEntry( id: 0, @@ -105,7 +105,7 @@ void main() { test('should return [Left] when executor fails', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => const Left(ServerFailure(testErrorMessage)), ); diff --git a/test/features/occupation/data/datasources/occupation_remote_data_source_test.dart b/test/features/occupation/data/datasources/occupation_remote_data_source_test.dart index 45c4abe4a..618d13d93 100644 --- a/test/features/occupation/data/datasources/occupation_remote_data_source_test.dart +++ b/test/features/occupation/data/datasources/occupation_remote_data_source_test.dart @@ -28,7 +28,7 @@ void main() { group('getOccupations', () { test('should return [Left] if executor returns [Left]', () async { // arrange - when(executor.call>(any)).thenAnswer( + when(executor.execute>(any)).thenAnswer( (_) async => const Left(ServerFailure('some error')), ); @@ -43,7 +43,7 @@ void main() { 'should return [Right>] executor succeeds', () async { // arrange - when(executor.call>(any)).thenAnswer( + when(executor.execute>(any)).thenAnswer( (_) async => const Right([]), ); diff --git a/test/features/opening_hours/data/datasources/opening_hours_remote_data_source_test.dart b/test/features/opening_hours/data/datasources/opening_hours_remote_data_source_test.dart index 9d4d66d5e..5913d7801 100644 --- a/test/features/opening_hours/data/datasources/opening_hours_remote_data_source_test.dart +++ b/test/features/opening_hours/data/datasources/opening_hours_remote_data_source_test.dart @@ -1,6 +1,6 @@ import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart'; import 'package:coffeecard/generated/api/shiftplanning_api.swagger.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fpdart/fpdart.dart'; @@ -31,7 +31,7 @@ void main() { group('isOpen', () { test('should call executor', () async { // arrange - when(executor(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right(IsOpenDTO(open: true)), ); @@ -39,7 +39,7 @@ void main() { await dataSource.isOpen(); // assert - verify(executor(any)); + verify(executor.execute(any)); }); }); @@ -48,7 +48,7 @@ void main() { // arrange final List testOpeningHours = []; - when(executor>(any)).thenAnswer( + when(executor.execute>(any)).thenAnswer( (_) async => Right(testOpeningHours), ); @@ -56,7 +56,7 @@ void main() { await dataSource.getOpeningHours(); // assert - verify(executor>(any)); + verify(executor.execute>(any)); }); }); } diff --git a/test/features/opening_hours/data/repositories/opening_hours_repository_impl_test.dart b/test/features/opening_hours/data/repositories/opening_hours_repository_impl_test.dart deleted file mode 100644 index aa96bc32a..000000000 --- a/test/features/opening_hours/data/repositories/opening_hours_repository_impl_test.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'package:coffeecard/core/errors/failures.dart'; -import 'package:coffeecard/features/opening_hours/domain/entities/opening_hours.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; -import 'package:coffeecard/generated/api/shiftplanning_api.swagger.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:fpdart/fpdart.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'opening_hours_repository_impl_test.mocks.dart'; - -@GenerateMocks([OpeningHoursRemoteDataSource]) -void main() { - late MockOpeningHoursRemoteDataSource dataSource; - late OpeningHoursRepositoryImpl repository; - - setUp(() { - dataSource = MockOpeningHoursRemoteDataSource(); - repository = OpeningHoursRepositoryImpl(dataSource: dataSource); - - provideDummy>>( - const Left(ConnectionFailure()), - ); - }); - - group('getOpeningHours', () { - test('should propagate error if data source call fails', () async { - // arrange - when(dataSource.getOpeningHours()).thenAnswer( - (_) async => const Left(ServerFailure('some error')), - ); - - // act - final actual = await repository.getOpeningHours(0); - - // assert - expect(actual, const Left(ServerFailure('some error'))); - }); - - test('should return map if data source call succeeds', () async { - // arrange - when(dataSource.getOpeningHours()).thenAnswer( - (_) async => const Right([]), - ); - - // act - final actual = await repository.getOpeningHours(DateTime.monday); - - // assert - expect( - actual, - const Right( - OpeningHours( - allOpeningHours: { - DateTime.monday: 'Closed', - DateTime.tuesday: 'Closed', - DateTime.wednesday: 'Closed', - DateTime.thursday: 'Closed', - DateTime.friday: 'Closed', - DateTime.saturday: 'Closed', - DateTime.sunday: 'Closed', - }, - todaysOpeningHours: 'Mondays: Closed', - ), - ), - ); - }); - }); - - group('calculateTodaysOpeningHours', () { - test('should return correct hours given Monday', () { - // arrange - final openingHours = {DateTime.monday: '8 - 16'}; - - // act - final actual = - repository.calculateTodaysOpeningHours(DateTime.monday, openingHours); - - // assert - expect(actual, 'Mondays: 8 - 16'); - }); - - test('should return correct hours given Tuesday', () { - // arrange - final openingHours = {DateTime.tuesday: '8 - 16'}; - - // act - final actual = repository.calculateTodaysOpeningHours( - DateTime.tuesday, - openingHours, - ); - - // assert - expect(actual, 'Tuesdays: 8 - 16'); - }); - - test('should return correct hours given Wednesday', () { - // arrange - final openingHours = {DateTime.wednesday: '8 - 16'}; - - // act - final actual = repository.calculateTodaysOpeningHours( - DateTime.wednesday, - openingHours, - ); - - // assert - expect(actual, 'Wednesdays: 8 - 16'); - }); - - test('should return correct hours given Thursday', () { - // arrange - final openingHours = {DateTime.thursday: '8 - 16'}; - - // act - final actual = repository.calculateTodaysOpeningHours( - DateTime.thursday, - openingHours, - ); - - // assert - expect(actual, 'Thursdays: 8 - 16'); - }); - - test('should return correct hours given Fridays', () { - // arrange - final openingHours = {DateTime.friday: '8 - 16'}; - - // act - final actual = - repository.calculateTodaysOpeningHours(DateTime.friday, openingHours); - - // assert - expect(actual, 'Fridays: 8 - 16'); - }); - - test('should return correct hours given Saturday', () { - // arrange - final openingHours = {DateTime.saturday: '8 - 16'}; - - // act - final actual = repository.calculateTodaysOpeningHours( - DateTime.saturday, - openingHours, - ); - - // assert - expect(actual, 'Saturdays: 8 - 16'); - }); - - test('should return correct hours given Monday', () { - // arrange - final openingHours = {DateTime.sunday: '8 - 16'}; - - // act - final actual = - repository.calculateTodaysOpeningHours(DateTime.sunday, openingHours); - - // assert - expect(actual, 'Sundays: 8 - 16'); - }); - }); -} diff --git a/test/features/opening_hours/domain/usecases/check_open_status_test.dart b/test/features/opening_hours/domain/usecases/check_open_status_test.dart index f191d175b..58d130f58 100644 --- a/test/features/opening_hours/domain/usecases/check_open_status_test.dart +++ b/test/features/opening_hours/domain/usecases/check_open_status_test.dart @@ -1,6 +1,7 @@ import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/usecases/usecase.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart'; +import 'package:coffeecard/features/opening_hours/domain/usecases/check_open_status.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fpdart/fpdart.dart'; import 'package:mockito/annotations.dart'; diff --git a/test/features/opening_hours/domain/usecases/get_opening_hours_test.dart b/test/features/opening_hours/domain/usecases/get_opening_hours_test.dart index 401d53c77..67036596b 100644 --- a/test/features/opening_hours/domain/usecases/get_opening_hours_test.dart +++ b/test/features/opening_hours/domain/usecases/get_opening_hours_test.dart @@ -1,7 +1,8 @@ import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/usecases/usecase.dart'; +import 'package:coffeecard/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart'; import 'package:coffeecard/features/opening_hours/domain/entities/opening_hours.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/domain/usecases/get_opening_hours.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fpdart/fpdart.dart'; import 'package:mockito/annotations.dart'; @@ -9,35 +10,35 @@ import 'package:mockito/mockito.dart'; import 'get_opening_hours_test.mocks.dart'; -@GenerateMocks([OpeningHoursRepository]) +@GenerateMocks([OpeningHoursRemoteDataSource]) void main() { - late MockOpeningHoursRepository repository; + late MockOpeningHoursRemoteDataSource dataSource; late GetOpeningHours fetchOpeningHours; setUp(() { - repository = MockOpeningHoursRepository(); - fetchOpeningHours = GetOpeningHours(repository: repository); + dataSource = MockOpeningHoursRemoteDataSource(); + fetchOpeningHours = GetOpeningHours(dataSource: dataSource); provideDummy>( const Left(ConnectionFailure()), ); }); - test('should call repository', () async { + test('should call data source', () async { const theOpeningHours = OpeningHours( allOpeningHours: {}, todaysOpeningHours: '', ); // arrange - when(repository.getOpeningHours(any)) + when(dataSource.getOpeningHours()) .thenAnswer((_) async => const Right(theOpeningHours)); // act await fetchOpeningHours(NoParams()); // assert - verify(repository.getOpeningHours(any)); - verifyNoMoreInteractions(repository); + verify(dataSource.getOpeningHours()).called(1); + verifyNoMoreInteractions(dataSource); }); } diff --git a/test/features/opening_hours/presentation/cubit/opening_hours_cubit_test.dart b/test/features/opening_hours/presentation/cubit/opening_hours_cubit_test.dart index 9f24a82f7..dece75706 100644 --- a/test/features/opening_hours/presentation/cubit/opening_hours_cubit_test.dart +++ b/test/features/opening_hours/presentation/cubit/opening_hours_cubit_test.dart @@ -1,7 +1,9 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/features/opening_hours/domain/entities/opening_hours.dart'; -import 'package:coffeecard/features/opening_hours/opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/domain/usecases/check_open_status.dart'; +import 'package:coffeecard/features/opening_hours/domain/usecases/get_opening_hours.dart'; +import 'package:coffeecard/features/opening_hours/presentation/cubit/opening_hours_cubit.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fpdart/fpdart.dart'; import 'package:mockito/annotations.dart'; diff --git a/test/features/product/data/datasources/product_remote_data_source_test.dart b/test/features/product/data/datasources/product_remote_data_source_test.dart index 176f7a267..aa8fb8015 100644 --- a/test/features/product/data/datasources/product_remote_data_source_test.dart +++ b/test/features/product/data/datasources/product_remote_data_source_test.dart @@ -32,7 +32,7 @@ void main() { group('getProducts', () { test('should call executor', () async { // arrange - when(executor.call>(any)).thenAnswer( + when(executor.execute>(any)).thenAnswer( (_) async => Right([ ProductDto( id: 0, diff --git a/test/features/purchase/data/datasources/purchase_remote_data_source_test.dart b/test/features/purchase/data/datasources/purchase_remote_data_source_test.dart index 0a3d05e77..dc76482db 100644 --- a/test/features/purchase/data/datasources/purchase_remote_data_source_test.dart +++ b/test/features/purchase/data/datasources/purchase_remote_data_source_test.dart @@ -39,7 +39,7 @@ void main() { group('initiatePurchase', () { test('should call executor', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right( InitiatePurchaseResponse( dateCreated: DateTime.parse('2023-05-19'), @@ -89,7 +89,7 @@ void main() { group('getPurchase', () { test('should call executor', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right( SinglePurchaseResponse( dateCreated: DateTime.parse('2023-05-19'), diff --git a/test/features/receipt/data/datasources/receipt_remote_data_source_test.dart b/test/features/receipt/data/datasources/receipt_remote_data_source_test.dart index 9da108e91..c86126d05 100644 --- a/test/features/receipt/data/datasources/receipt_remote_data_source_test.dart +++ b/test/features/receipt/data/datasources/receipt_remote_data_source_test.dart @@ -37,7 +37,7 @@ void main() { group('getUsersUsedTicketsReceipts', () { test('should return [Left] if executor fails', () async { // arrange - when(executor>(any)) + when(executor.execute>(any)) .thenAnswer((_) async => const Left(ServerFailure('some error'))); // act @@ -49,7 +49,7 @@ void main() { test('should return [Right] if executor succeeds', () async { // arrange - when(executor>(any)) + when(executor.execute>(any)) .thenAnswer((_) async => const Right([])); // act @@ -64,7 +64,7 @@ void main() { group('getUserPurchasesReceipts', () { test('should return [Left] if executor fails', () async { // arrange - when(executor>(any)) + when(executor.execute>(any)) .thenAnswer((_) async => const Left(ServerFailure('some error'))); // act @@ -76,7 +76,7 @@ void main() { test('should return [Right] if executor succeeds', () async { // arrange - when(executor>(any)) + when(executor.execute>(any)) .thenAnswer((_) async => const Right([])); // act diff --git a/test/features/register/data/datasources/register_remote_data_source_test.dart b/test/features/register/data/datasources/register_remote_data_source_test.dart index 1b81de963..7fdce03a0 100644 --- a/test/features/register/data/datasources/register_remote_data_source_test.dart +++ b/test/features/register/data/datasources/register_remote_data_source_test.dart @@ -26,16 +26,14 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); - provideDummy>( - const Left(ConnectionFailure()), - ); + provideDummy>(const Left(ConnectionFailure())); }); group('register', () { test('should call executor', () async { // arrange - when(executor.call(any)).thenAnswer( - (_) async => Right(MessageResponseDto()), + when(executor.executeAndDiscard(any)).thenAnswer( + (_) async => const Right(unit), ); // act @@ -47,7 +45,7 @@ void main() { ); // assert - verify(executor.call(any)); + verify(executor.executeAndDiscard(any)).called(1); }); }); } diff --git a/test/features/register/domain/usecases/register_user_test.dart b/test/features/register/domain/usecases/register_user_test.dart index 12a834d63..9d9b270e0 100644 --- a/test/features/register/domain/usecases/register_user_test.dart +++ b/test/features/register/domain/usecases/register_user_test.dart @@ -17,7 +17,7 @@ void main() { remoteDataSource = MockRegisterRemoteDataSource(); usecase = RegisterUser(remoteDataSource: remoteDataSource); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); }); @@ -25,7 +25,7 @@ void main() { test('should call data source', () async { // arrange when(remoteDataSource.register(any, any, any, any)) - .thenAnswer((_) async => const Right(null)); + .thenAnswer((_) async => const Right(unit)); // act await usecase( diff --git a/test/features/register/presentation/cubit/register_cubit_test.dart b/test/features/register/presentation/cubit/register_cubit_test.dart index a5787122e..2caafe319 100644 --- a/test/features/register/presentation/cubit/register_cubit_test.dart +++ b/test/features/register/presentation/cubit/register_cubit_test.dart @@ -24,7 +24,7 @@ void main() { firebaseAnalyticsEventLogging: firebaseAnalyticsEventLogging, ); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); }); @@ -45,7 +45,7 @@ void main() { 'should emit [Success] if use case succeeds', build: () => cubit, setUp: () { - when(registerUser(any)).thenAnswer((_) async => const Right(null)); + when(registerUser(any)).thenAnswer((_) async => const Right(unit)); when(firebaseAnalyticsEventLogging.signUpEvent()); }, act: (_) => cubit.register('name', 'email', 'passcode', 0), diff --git a/test/features/ticket/data/datasources/ticket_remote_data_source_test.dart b/test/features/ticket/data/datasources/ticket_remote_data_source_test.dart index 9ba5fd74d..9418258a0 100644 --- a/test/features/ticket/data/datasources/ticket_remote_data_source_test.dart +++ b/test/features/ticket/data/datasources/ticket_remote_data_source_test.dart @@ -48,7 +48,7 @@ void main() { 'THEN a [Right] value is returned', () async { // arrange - when(executor.call>(any)) + when(executor.execute>(any)) .thenAnswer((_) async => const Right([])); // act @@ -65,7 +65,7 @@ void main() { 'THEN a [Left] value is returned', () async { // arrange - when(executor.call>(any)) + when(executor.execute>(any)) .thenAnswer((_) async => const Left(ServerFailure('some error'))); // act @@ -82,7 +82,7 @@ void main() { 'THEN a [TicketCountModel] with the count of tickets and joined ticket names is returned', () async { // arrange - when(executor.call>(any)).thenAnswer( + when(executor.execute>(any)).thenAnswer( (_) async => Right([ TicketResponse( id: 0, @@ -124,7 +124,7 @@ void main() { 'THEN a [Right] value is returned', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right( TicketDto( id: 0, @@ -149,7 +149,7 @@ void main() { 'THEN a [Left] value is returned', () async { // arrange - when(executor.call(any)) + when(executor.execute(any)) .thenAnswer((_) async => const Left(ServerFailure('some error'))); // act diff --git a/test/features/user/data/datasources/user_remote_data_source_test.dart b/test/features/user/data/datasources/user_remote_data_source_test.dart index 6e25ef926..a39f2fe3a 100644 --- a/test/features/user/data/datasources/user_remote_data_source_test.dart +++ b/test/features/user/data/datasources/user_remote_data_source_test.dart @@ -15,7 +15,10 @@ import 'package:mockito/mockito.dart'; import 'user_remote_data_source_test.mocks.dart'; -@GenerateMocks([CoffeecardApiV2, NetworkRequestExecutor]) +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), +]) void main() { late MockCoffeecardApiV2 apiV2; late MockNetworkRequestExecutor executor; @@ -32,7 +35,7 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); }); @@ -56,7 +59,7 @@ void main() { group('getUser', () { test('should return [Left] if executor returns [Left]', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => const Left(ServerFailure('some error')), ); @@ -69,7 +72,7 @@ void main() { test('should return [Right] executor succeeds', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right( UserResponse( id: 0, @@ -98,7 +101,7 @@ void main() { group('updateUserDetails', () { test('should return [Left] if executor returns [Left]', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => const Left(ServerFailure('some error')), ); @@ -111,7 +114,7 @@ void main() { test('should return [UserModel] if api call succeeds', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right( UserResponse( id: 0, @@ -138,22 +141,22 @@ void main() { }); group('requestAccountDeletion', () { - test('should return [Right] if executor succeeds', () async { + test('should return [Right] if executor succeeds', () async { // arrange - when(executor.call(any)).thenAnswer( - (_) async => const Right(null), + when(executor.executeAndDiscard(any)).thenAnswer( + (_) async => const Right(unit), ); // act final actual = await dataSource.requestAccountDeletion(); // assert - expect(actual, const Right(null)); + expect(actual.isRight(), true); }); test('should return [Left] if executor fails', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.executeAndDiscard(any)).thenAnswer( (_) async => const Left(ServerFailure('some error')), ); diff --git a/test/features/user/domain/usecases/get_user_test.dart b/test/features/user/domain/usecases/get_user_test.dart index 198e18397..365eed874 100644 --- a/test/features/user/domain/usecases/get_user_test.dart +++ b/test/features/user/domain/usecases/get_user_test.dart @@ -4,6 +4,7 @@ import 'package:coffeecard/features/occupation/domain/entities/occupation.dart'; import 'package:coffeecard/features/user/data/datasources/user_remote_data_source.dart'; import 'package:coffeecard/features/user/data/models/user_model.dart'; import 'package:coffeecard/features/user/domain/entities/role.dart'; +import 'package:coffeecard/features/user/domain/entities/user.dart'; import 'package:coffeecard/features/user/domain/usecases/get_user.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fpdart/fpdart.dart'; @@ -21,7 +22,7 @@ void main() { dataSource = MockUserRemoteDataSource(); usecase = GetUser(dataSource: dataSource); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); }); diff --git a/test/features/user/domain/usecases/request_account_deletion_test.dart b/test/features/user/domain/usecases/request_account_deletion_test.dart index 09ca12d24..459d1245b 100644 --- a/test/features/user/domain/usecases/request_account_deletion_test.dart +++ b/test/features/user/domain/usecases/request_account_deletion_test.dart @@ -18,7 +18,7 @@ void main() { dataSource = MockUserRemoteDataSource(); usecase = RequestAccountDeletion(dataSource: dataSource); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); }); @@ -26,7 +26,7 @@ void main() { test('should call repository', () async { // arrange when(dataSource.requestAccountDeletion()).thenAnswer( - (_) async => const Right(null), + (_) async => const Right(unit), ); // act diff --git a/test/features/user/domain/usecases/update_user_details_test.dart b/test/features/user/domain/usecases/update_user_details_test.dart index 43cd9c12a..34c25c1f4 100644 --- a/test/features/user/domain/usecases/update_user_details_test.dart +++ b/test/features/user/domain/usecases/update_user_details_test.dart @@ -3,6 +3,7 @@ import 'package:coffeecard/features/occupation/domain/entities/occupation.dart'; import 'package:coffeecard/features/user/data/datasources/user_remote_data_source.dart'; import 'package:coffeecard/features/user/data/models/user_model.dart'; import 'package:coffeecard/features/user/domain/entities/role.dart'; +import 'package:coffeecard/features/user/domain/entities/user.dart'; import 'package:coffeecard/features/user/domain/usecases/update_user_details.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fpdart/fpdart.dart'; @@ -20,7 +21,7 @@ void main() { dataSource = MockUserRemoteDataSource(); usecase = UpdateUserDetails(dataSource: dataSource); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); }); diff --git a/test/features/user/presentation/cubit/user_cubit_test.dart b/test/features/user/presentation/cubit/user_cubit_test.dart index a31f50378..f0b5c51a2 100644 --- a/test/features/user/presentation/cubit/user_cubit_test.dart +++ b/test/features/user/presentation/cubit/user_cubit_test.dart @@ -35,7 +35,7 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); }); @@ -142,7 +142,7 @@ void main() { test('should call use case', () { // arrange when(requestAccountDeletion(any)).thenAnswer( - (_) async => const Right(null), + (_) async => const Right(unit), ); // act diff --git a/test/features/voucher/data/datasources/voucher_remote_data_source_test.dart b/test/features/voucher/data/datasources/voucher_remote_data_source_test.dart index c21a76fc2..2abd304d1 100644 --- a/test/features/voucher/data/datasources/voucher_remote_data_source_test.dart +++ b/test/features/voucher/data/datasources/voucher_remote_data_source_test.dart @@ -10,7 +10,10 @@ import 'package:mockito/mockito.dart'; import 'voucher_remote_data_source_test.mocks.dart'; -@GenerateMocks([CoffeecardApi, NetworkRequestExecutor]) +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), +]) void main() { late VoucherRemoteDataSource remoteDataSource; late MockCoffeecardApi apiV1; @@ -32,7 +35,7 @@ void main() { group('redeemVoucher', () { test('should call executor and map data', () async { // arrange - when(executor.call(any)).thenAnswer( + when(executor.execute(any)).thenAnswer( (_) async => Right( PurchaseDto( id: 0, @@ -52,7 +55,7 @@ void main() { final actual = await remoteDataSource.redeemVoucher('voucher'); // assert - verify(executor.call(any)); + verify(executor.execute(any)).called(1); expect( actual, const Right(