Add tests for db cache

This commit is contained in:
Roland Geider
2024-09-27 11:11:35 +02:00
parent b1a49218ad
commit dbdec5c9dd
9 changed files with 116 additions and 62 deletions

View File

@@ -83,6 +83,8 @@ class $IngredientsTable extends Ingredients
class IngredientTable extends DataClass implements Insertable<IngredientTable> {
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});

View File

@@ -46,7 +46,7 @@ class NutritionPlansProvider with ChangeNotifier {
final WgerBaseProvider baseProvider;
late IngredientDatabase database;
List<NutritionalPlan> _plans = [];
List<Ingredient> _ingredients = [];
List<Ingredient> ingredients = [];
NutritionPlansProvider(this.baseProvider, List<NutritionalPlan> 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<Ingredient> fetchIngredient(int ingredientId) async {
Future<Ingredient> 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(

View File

@@ -422,16 +422,6 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
)),
) as _i10.Future<_i4.Exercise>);
@override
_i10.Future<void> checkExerciseCacheVersion() => (super.noSuchMethod(
Invocation.method(
#checkExerciseCacheVersion,
[],
),
returnValue: _i10.Future<void>.value(),
returnValueForMissingStub: _i10.Future<void>.value(),
) as _i10.Future<void>);
@override
_i10.Future<void> initCacheTimesLocalPrefs({dynamic forceInit = false}) =>
(super.noSuchMethod(

View File

@@ -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();

View File

@@ -13,18 +13,26 @@ import '../measurements/measurement_provider_test.mocks.dart';
void main() {
late NutritionPlansProvider nutritionProvider;
late MockWgerBaseProvider mockWgerBaseProvider;
late IngredientDatabase database;
late Map<String, dynamic> 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<String, dynamic> nutritionalPlanInfoResponse = jsonDecode(
fixture('nutrition/nutritional_plan_info_detail_response.json'),
@@ -35,7 +43,7 @@ void main() {
final List<dynamic> nutritionDiaryResponse = jsonDecode(
fixture('nutrition/nutrition_diary_response.json'),
)['results'];
final Map<String, dynamic> ingredient59887Response = jsonDecode(
ingredient59887Response = jsonDecode(
fixture('nutrition/ingredientinfo_59887.json'),
);
final Map<String, dynamic> 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));
});
});
}

View File

@@ -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<void>);
@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>);

View File

@@ -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<void>);
@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>);

View File

@@ -629,16 +629,6 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider {
)),
) as _i11.Future<_i6.Exercise>);
@override
_i11.Future<void> checkExerciseCacheVersion() => (super.noSuchMethod(
Invocation.method(
#checkExerciseCacheVersion,
[],
),
returnValue: _i11.Future<void>.value(),
returnValueForMissingStub: _i11.Future<void>.value(),
) as _i11.Future<void>);
@override
_i11.Future<void> initCacheTimesLocalPrefs({dynamic forceInit = false}) =>
(super.noSuchMethod(

View File

@@ -557,16 +557,6 @@ class MockExercisesProvider extends _i1.Mock implements _i19.ExercisesProvider {
)),
) as _i20.Future<_i4.Exercise>);
@override
_i20.Future<void> checkExerciseCacheVersion() => (super.noSuchMethod(
Invocation.method(
#checkExerciseCacheVersion,
[],
),
returnValue: _i20.Future<void>.value(),
returnValueForMissingStub: _i20.Future<void>.value(),
) as _i20.Future<void>);
@override
_i20.Future<void> initCacheTimesLocalPrefs({dynamic forceInit = false}) =>
(super.noSuchMethod(