import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import '../../../lib/data/local/local_storage_service.dart'; import '../../../lib/data/recipes/recipe_service.dart'; import '../../../lib/data/recipes/models/recipe_model.dart'; import '../../../lib/data/nostr/nostr_service.dart'; import '../../../lib/data/nostr/models/nostr_keypair.dart'; void main() { // Initialize Flutter bindings for path_provider to work in tests TestWidgetsFlutterBinding.ensureInitialized(); // Initialize FFI for testing setUpAll(() { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; }); group('RecipeService', () { late LocalStorageService localStorageService; late RecipeService recipeService; late String testDbPath; setUp(() async { // Create a temporary directory for test database final tempDir = await Directory.systemTemp.createTemp('recipe_test_'); testDbPath = path.join(tempDir.path, 'test_recipes.db'); final localStorageDbPath = path.join(tempDir.path, 'test_local_storage.db'); final cacheDir = Directory(path.join(tempDir.path, 'cache')); localStorageService = LocalStorageService( testDbPath: localStorageDbPath, testCacheDir: cacheDir, ); await localStorageService.initialize( sessionDbPath: localStorageDbPath, sessionCacheDir: cacheDir, ); recipeService = RecipeService( localStorage: localStorageService, testDbPath: testDbPath, ); await recipeService.initialize(sessionDbPath: testDbPath); }); tearDown(() async { // Clean up test database try { final dbFile = File(testDbPath); if (await dbFile.exists()) { await dbFile.delete(); } } catch (e) { // Ignore cleanup errors } }); test('creates a recipe successfully', () async { final recipe = RecipeModel( id: 'test-recipe-1', title: 'Test Recipe', description: 'A test recipe', tags: ['test', 'recipe'], rating: 4, isFavourite: true, imageUrls: ['https://example.com/image.jpg'], ); final created = await recipeService.createRecipe(recipe); expect(created.id, equals(recipe.id)); expect(created.title, equals(recipe.title)); expect(created.description, equals(recipe.description)); expect(created.tags, equals(recipe.tags)); expect(created.rating, equals(recipe.rating)); expect(created.isFavourite, equals(recipe.isFavourite)); expect(created.imageUrls, equals(recipe.imageUrls)); expect(created.createdAt, greaterThan(0)); expect(created.updatedAt, greaterThan(0)); }); test('gets a recipe by ID', () async { final recipe = RecipeModel( id: 'test-recipe-2', title: 'Test Recipe 2', tags: ['test'], ); await recipeService.createRecipe(recipe); final retrieved = await recipeService.getRecipe('test-recipe-2'); expect(retrieved, isNotNull); expect(retrieved!.id, equals(recipe.id)); expect(retrieved.title, equals(recipe.title)); }); test('returns null for non-existent recipe', () async { final retrieved = await recipeService.getRecipe('non-existent'); expect(retrieved, isNull); }); test('updates a recipe successfully', () async { final recipe = RecipeModel( id: 'test-recipe-3', title: 'Original Title', tags: ['original'], ); await recipeService.createRecipe(recipe); final updated = recipe.copyWith( title: 'Updated Title', tags: ['updated'], rating: 5, ); final result = await recipeService.updateRecipe(updated); expect(result.title, equals('Updated Title')); expect(result.tags, equals(['updated'])); expect(result.rating, equals(5)); expect(result.updatedAt, greaterThan(recipe.updatedAt)); }); test('deletes a recipe (marks as deleted)', () async { final recipe = RecipeModel( id: 'test-recipe-4', title: 'Recipe to Delete', tags: ['delete'], ); await recipeService.createRecipe(recipe); await recipeService.deleteRecipe('test-recipe-4'); // Recipe should not be found (soft delete) final retrieved = await recipeService.getRecipe('test-recipe-4'); expect(retrieved, isNull); // But should exist when including deleted final allRecipes = await recipeService.getAllRecipes(includeDeleted: true); final deletedRecipe = allRecipes.firstWhere((r) => r.id == 'test-recipe-4'); expect(deletedRecipe.isDeleted, isTrue); }); test('gets all recipes', () async { await recipeService.createRecipe(RecipeModel( id: 'recipe-1', title: 'Recipe 1', tags: ['tag1'], )); await recipeService.createRecipe(RecipeModel( id: 'recipe-2', title: 'Recipe 2', tags: ['tag2'], )); final allRecipes = await recipeService.getAllRecipes(); expect(allRecipes.length, equals(2)); expect(allRecipes.map((r) => r.id), containsAll(['recipe-1', 'recipe-2'])); }); test('gets recipes by tag', () async { await recipeService.createRecipe(RecipeModel( id: 'recipe-tag1', title: 'Recipe with Tag1', tags: ['tag1', 'common'], )); await recipeService.createRecipe(RecipeModel( id: 'recipe-tag2', title: 'Recipe with Tag2', tags: ['tag2', 'common'], )); final tag1Recipes = await recipeService.getRecipesByTag('tag1'); expect(tag1Recipes.length, equals(1)); expect(tag1Recipes.first.id, equals('recipe-tag1')); final commonRecipes = await recipeService.getRecipesByTag('common'); expect(commonRecipes.length, equals(2)); }); test('gets favourite recipes', () async { await recipeService.createRecipe(RecipeModel( id: 'favourite-1', title: 'Favourite Recipe 1', tags: ['favourite'], isFavourite: true, )); await recipeService.createRecipe(RecipeModel( id: 'not-favourite', title: 'Not Favourite', tags: ['normal'], isFavourite: false, )); final favourites = await recipeService.getFavouriteRecipes(); expect(favourites.length, equals(1)); expect(favourites.first.id, equals('favourite-1')); }); test('throws exception when updating non-existent recipe', () async { final recipe = RecipeModel( id: 'non-existent', title: 'Non Existent', tags: [], ); expect( () => recipeService.updateRecipe(recipe), throwsA(isA()), ); }); test('throws exception when deleting non-existent recipe', () async { expect( () => recipeService.deleteRecipe('non-existent'), throwsA(isA()), ); }); }); }