From dbdec5c9dd1cda6a8df70f21e179710079aa7069 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 27 Sep 2024 11:11:35 +0200 Subject: [PATCH] Add tests for db cache --- .../ingredients/ingredients_database.g.dart | 2 + lib/providers/nutrition.dart | 16 ++--- test/core/settings_test.mocks.dart | 10 --- test/exercises/exercise_provider_db_test.dart | 2 +- test/nutrition/nutrition_provider_test.dart | 66 ++++++++++++++++++- .../nutritional_meal_form_test.mocks.dart | 31 ++++++--- .../nutritional_plan_form_test.mocks.dart | 31 ++++++--- test/workout/gym_mode_screen_test.mocks.dart | 10 --- test/workout/workout_set_form_test.mocks.dart | 10 --- 9 files changed, 116 insertions(+), 62 deletions(-) diff --git a/lib/database/ingredients/ingredients_database.g.dart b/lib/database/ingredients/ingredients_database.g.dart index 9bfdd5ff..7376fe87 100644 --- a/lib/database/ingredients/ingredients_database.g.dart +++ b/lib/database/ingredients/ingredients_database.g.dart @@ -83,6 +83,8 @@ class $IngredientsTable extends Ingredients class IngredientTable extends DataClass implements Insertable { final int id; final String data; + + /// The date when the ingredient was last fetched from the server final DateTime lastFetched; const IngredientTable( {required this.id, required this.data, required this.lastFetched}); diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index 0fc93a11..079ddf7c 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -46,7 +46,7 @@ class NutritionPlansProvider with ChangeNotifier { final WgerBaseProvider baseProvider; late IngredientDatabase database; List _plans = []; - List _ingredients = []; + List ingredients = []; NutritionPlansProvider(this.baseProvider, List entries, {IngredientDatabase? database}) @@ -58,14 +58,10 @@ class NutritionPlansProvider with ChangeNotifier { return [..._plans]; } - set ingredients(items) { - _ingredients = items; - } - /// Clears all lists void clear() { _plans = []; - _ingredients = []; + ingredients = []; } /// Returns the current active nutritional plan. At the moment this is just @@ -299,11 +295,12 @@ class NutritionPlansProvider with ChangeNotifier { /// Fetch and return an ingredient /// /// If the ingredient is not known locally, it is fetched from the server - Future fetchIngredient(int ingredientId) async { + Future fetchIngredient(int ingredientId, {IngredientDatabase? database}) async { + database ??= this.database; Ingredient ingredient; try { - ingredient = _ingredients.firstWhere((e) => e.id == ingredientId); + ingredient = ingredients.firstWhere((e) => e.id == ingredientId); } on StateError { final ingredientDb = await (database.select(database.ingredients) ..where((e) => e.id.equals(ingredientId))) @@ -312,6 +309,7 @@ class NutritionPlansProvider with ChangeNotifier { // Try to fetch from local db if (ingredientDb != null) { ingredient = Ingredient.fromJson(jsonDecode(ingredientDb.data)); + ingredients.add(ingredient); log("Loaded ingredient '${ingredient.name}' from db cache"); // Prune old entries @@ -324,7 +322,7 @@ class NutritionPlansProvider with ChangeNotifier { baseProvider.makeUrl(_ingredientInfoPath, id: ingredientId), ); ingredient = Ingredient.fromJson(data); - _ingredients.add(ingredient); + ingredients.add(ingredient); database.into(database.ingredients).insert( IngredientsCompanion.insert( diff --git a/test/core/settings_test.mocks.dart b/test/core/settings_test.mocks.dart index 11a1e2bb..e9d85c05 100644 --- a/test/core/settings_test.mocks.dart +++ b/test/core/settings_test.mocks.dart @@ -422,16 +422,6 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider { )), ) as _i10.Future<_i4.Exercise>); - @override - _i10.Future checkExerciseCacheVersion() => (super.noSuchMethod( - Invocation.method( - #checkExerciseCacheVersion, - [], - ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); - @override _i10.Future initCacheTimesLocalPrefs({dynamic forceInit = false}) => (super.noSuchMethod( diff --git a/test/exercises/exercise_provider_db_test.dart b/test/exercises/exercise_provider_db_test.dart index 3f921f91..bd693af8 100644 --- a/test/exercises/exercise_provider_db_test.dart +++ b/test/exercises/exercise_provider_db_test.dart @@ -381,7 +381,7 @@ void main() { }); }); - group('Exercises', () { + group('Exercise cache DB', () { test('that if there is already valid data in the DB, the API is not hit', () async { // Arrange final prefs = await SharedPreferences.getInstance(); diff --git a/test/nutrition/nutrition_provider_test.dart b/test/nutrition/nutrition_provider_test.dart index b7d057b0..956de8c6 100644 --- a/test/nutrition/nutrition_provider_test.dart +++ b/test/nutrition/nutrition_provider_test.dart @@ -13,18 +13,26 @@ import '../measurements/measurement_provider_test.mocks.dart'; void main() { late NutritionPlansProvider nutritionProvider; late MockWgerBaseProvider mockWgerBaseProvider; + late IngredientDatabase database; + late Map ingredient59887Response; + + // Needs to be configured here, setUp runs on every test, setUpAll only once + setUpAll(() { + database = IngredientDatabase.inMemory(NativeDatabase.memory()); + }); setUp(() { mockWgerBaseProvider = MockWgerBaseProvider(); nutritionProvider = NutritionPlansProvider( mockWgerBaseProvider, [], - database: IngredientDatabase.inMemory(NativeDatabase.memory()), + database: database, ); const String planInfoUrl = 'nutritionplaninfo'; const String planUrl = 'nutritionplan'; const String diaryUrl = 'nutritiondiary'; + const String ingredientInfoUrl = 'ingredientinfo'; final Map nutritionalPlanInfoResponse = jsonDecode( fixture('nutrition/nutritional_plan_info_detail_response.json'), @@ -35,7 +43,7 @@ void main() { final List nutritionDiaryResponse = jsonDecode( fixture('nutrition/nutrition_diary_response.json'), )['results']; - final Map ingredient59887Response = jsonDecode( + ingredient59887Response = jsonDecode( fixture('nutrition/ingredientinfo_59887.json'), ); final Map ingredient10065Response = jsonDecode( @@ -68,9 +76,16 @@ void main() { host: 'localhost', path: 'api/v2/$diaryUrl', ); + final Uri ingredientUri = Uri( + scheme: 'http', + host: 'localhost', + path: 'api/v2/$ingredientInfoUrl', + ); when(mockWgerBaseProvider.makeUrl(planInfoUrl, id: anyNamed('id'))).thenReturn(planInfoUri); when(mockWgerBaseProvider.makeUrl(planUrl, id: anyNamed('id'))).thenReturn(planUri); when(mockWgerBaseProvider.makeUrl(diaryUrl, query: anyNamed('query'))).thenReturn(diaryUri); + when(mockWgerBaseProvider.makeUrl(ingredientInfoUrl, id: anyNamed('id'))) + .thenReturn(ingredientUri); when(mockWgerBaseProvider.fetch(planInfoUri)).thenAnswer( (realInvocation) => Future.value(nutritionalPlanInfoResponse), ); @@ -80,6 +95,9 @@ void main() { when(mockWgerBaseProvider.fetchPaginated(diaryUri)).thenAnswer( (realInvocation) => Future.value(nutritionDiaryResponse), ); + when(mockWgerBaseProvider.fetch(ingredientUri)).thenAnswer( + (realInvocation) => Future.value(ingredient10065Response), + ); }); group('fetchAndSetPlanFull', () { @@ -91,4 +109,48 @@ void main() { expect(nutritionProvider.items.isEmpty, false); }); }); + + group('Ingredient cache DB', () { + test('that if there is already valid data in the DB, the API is not hit', () async { + // Arrange + nutritionProvider.ingredients = []; + await database.into(database.ingredients).insert( + IngredientsCompanion.insert( + id: ingredient59887Response['id'], + data: json.encode(ingredient59887Response), + lastFetched: DateTime.now(), + ), + ); + + // Act + await nutritionProvider.fetchIngredient(59887, database: database); + + // Assert + expect(nutritionProvider.ingredients.length, 1); + expect(nutritionProvider.ingredients.first.id, 59887); + expect(nutritionProvider.ingredients.first.name, 'Baked Beans'); + verifyNever(mockWgerBaseProvider.fetchPaginated(any)); + }); + + test('fetching an ingredient not present in the DB, the API is hit', () async { + // Arrange + nutritionProvider.ingredients = []; + await database.into(database.ingredients).insert( + IngredientsCompanion.insert( + id: ingredient59887Response['id'], + data: json.encode(ingredient59887Response), + lastFetched: DateTime.now(), + ), + ); + + // Act + await nutritionProvider.fetchIngredient(10065, database: database); + + // Assert + expect(nutritionProvider.ingredients.length, 1); + expect(nutritionProvider.ingredients.first.id, 10065); + expect(nutritionProvider.ingredients.first.name, "'Old Times' Orange Fine Cut Marmalade"); + verify(mockWgerBaseProvider.fetch(any)); + }); + }); } diff --git a/test/nutrition/nutritional_meal_form_test.mocks.dart b/test/nutrition/nutritional_meal_form_test.mocks.dart index 6f21cf4b..61d3993d 100644 --- a/test/nutrition/nutritional_meal_form_test.mocks.dart +++ b/test/nutrition/nutritional_meal_form_test.mocks.dart @@ -128,21 +128,27 @@ class MockNutritionPlansProvider extends _i1.Mock returnValueForMissingStub: null, ); + @override + List<_i7.Ingredient> get ingredients => (super.noSuchMethod( + Invocation.getter(#ingredients), + returnValue: <_i7.Ingredient>[], + ) as List<_i7.Ingredient>); + + @override + set ingredients(List<_i7.Ingredient>? _ingredients) => super.noSuchMethod( + Invocation.setter( + #ingredients, + _ingredients, + ), + returnValueForMissingStub: null, + ); + @override List<_i4.NutritionalPlan> get items => (super.noSuchMethod( Invocation.getter(#items), returnValue: <_i4.NutritionalPlan>[], ) as List<_i4.NutritionalPlan>); - @override - set ingredients(dynamic items) => super.noSuchMethod( - Invocation.setter( - #ingredients, - items, - ), - returnValueForMissingStub: null, - ); - @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -367,17 +373,22 @@ class MockNutritionPlansProvider extends _i1.Mock ) as _i9.Future); @override - _i9.Future<_i7.Ingredient> fetchIngredient(int? ingredientId) => + _i9.Future<_i7.Ingredient> fetchIngredient( + int? ingredientId, { + _i3.IngredientDatabase? database, + }) => (super.noSuchMethod( Invocation.method( #fetchIngredient, [ingredientId], + {#database: database}, ), returnValue: _i9.Future<_i7.Ingredient>.value(_FakeIngredient_5( this, Invocation.method( #fetchIngredient, [ingredientId], + {#database: database}, ), )), ) as _i9.Future<_i7.Ingredient>); diff --git a/test/nutrition/nutritional_plan_form_test.mocks.dart b/test/nutrition/nutritional_plan_form_test.mocks.dart index 4b279f5e..fc951b39 100644 --- a/test/nutrition/nutritional_plan_form_test.mocks.dart +++ b/test/nutrition/nutritional_plan_form_test.mocks.dart @@ -128,21 +128,27 @@ class MockNutritionPlansProvider extends _i1.Mock returnValueForMissingStub: null, ); + @override + List<_i7.Ingredient> get ingredients => (super.noSuchMethod( + Invocation.getter(#ingredients), + returnValue: <_i7.Ingredient>[], + ) as List<_i7.Ingredient>); + + @override + set ingredients(List<_i7.Ingredient>? _ingredients) => super.noSuchMethod( + Invocation.setter( + #ingredients, + _ingredients, + ), + returnValueForMissingStub: null, + ); + @override List<_i4.NutritionalPlan> get items => (super.noSuchMethod( Invocation.getter(#items), returnValue: <_i4.NutritionalPlan>[], ) as List<_i4.NutritionalPlan>); - @override - set ingredients(dynamic items) => super.noSuchMethod( - Invocation.setter( - #ingredients, - items, - ), - returnValueForMissingStub: null, - ); - @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -367,17 +373,22 @@ class MockNutritionPlansProvider extends _i1.Mock ) as _i9.Future); @override - _i9.Future<_i7.Ingredient> fetchIngredient(int? ingredientId) => + _i9.Future<_i7.Ingredient> fetchIngredient( + int? ingredientId, { + _i3.IngredientDatabase? database, + }) => (super.noSuchMethod( Invocation.method( #fetchIngredient, [ingredientId], + {#database: database}, ), returnValue: _i9.Future<_i7.Ingredient>.value(_FakeIngredient_5( this, Invocation.method( #fetchIngredient, [ingredientId], + {#database: database}, ), )), ) as _i9.Future<_i7.Ingredient>); diff --git a/test/workout/gym_mode_screen_test.mocks.dart b/test/workout/gym_mode_screen_test.mocks.dart index cd76823d..5647a1f0 100644 --- a/test/workout/gym_mode_screen_test.mocks.dart +++ b/test/workout/gym_mode_screen_test.mocks.dart @@ -629,16 +629,6 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { )), ) as _i11.Future<_i6.Exercise>); - @override - _i11.Future checkExerciseCacheVersion() => (super.noSuchMethod( - Invocation.method( - #checkExerciseCacheVersion, - [], - ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); - @override _i11.Future initCacheTimesLocalPrefs({dynamic forceInit = false}) => (super.noSuchMethod( diff --git a/test/workout/workout_set_form_test.mocks.dart b/test/workout/workout_set_form_test.mocks.dart index c4fc39a9..4f0851bc 100644 --- a/test/workout/workout_set_form_test.mocks.dart +++ b/test/workout/workout_set_form_test.mocks.dart @@ -557,16 +557,6 @@ class MockExercisesProvider extends _i1.Mock implements _i19.ExercisesProvider { )), ) as _i20.Future<_i4.Exercise>); - @override - _i20.Future checkExerciseCacheVersion() => (super.noSuchMethod( - Invocation.method( - #checkExerciseCacheVersion, - [], - ), - returnValue: _i20.Future.value(), - returnValueForMissingStub: _i20.Future.value(), - ) as _i20.Future); - @override _i20.Future initCacheTimesLocalPrefs({dynamic forceInit = false}) => (super.noSuchMethod(