import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:app_boilerplate/data/local/local_storage_service.dart'; import 'package:app_boilerplate/data/local/models/item.dart'; import 'package:path/path.dart' as path; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; void main() { // Initialize Flutter bindings for path_provider to work in tests TestWidgetsFlutterBinding.ensureInitialized(); // Initialize sqflite for testing (required for VM/desktop tests) sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; late LocalStorageService service; late Directory testDir; late String testDbPath; late Directory testCacheDir; setUp(() async { // Create a temporary directory for testing testDir = await Directory.systemTemp.createTemp('local_storage_test_'); testDbPath = path.join(testDir.path, 'test_local_storage.db'); testCacheDir = Directory(path.join(testDir.path, 'image_cache')); // Create service with test paths (bypasses path_provider platform channels) service = LocalStorageService( testDbPath: testDbPath, testCacheDir: testCacheDir, ); await service.initialize(); }); tearDown(() async { await service.close(); // Clean up test files and directory try { if (await testDir.exists()) { await testDir.delete(recursive: true); } } catch (_) { // Ignore cleanup errors } }); group('LocalStorageService - CRUD Operations', () { /// Tests that inserting an item successfully stores it in the database. test('insertItem - success', () async { // Arrange final item = Item( id: 'test-1', data: {'name': 'Test Item', 'value': 123}, ); // Act await service.insertItem(item); // Assert final retrieved = await service.getItem('test-1'); expect(retrieved, isNotNull); expect(retrieved!.id, equals('test-1')); expect(retrieved.data['name'], equals('Test Item')); expect(retrieved.data['value'], equals(123)); }); /// Tests that inserting an item with existing ID replaces it. test('insertItem - replaces existing item', () async { // Arrange final item1 = Item( id: 'test-1', data: {'name': 'Original'}, ); final item2 = Item( id: 'test-1', data: {'name': 'Updated'}, ); // Act await service.insertItem(item1); await service.insertItem(item2); // Assert final retrieved = await service.getItem('test-1'); expect(retrieved, isNotNull); expect(retrieved!.data['name'], equals('Updated')); }); /// Tests that getting a non-existent item returns null. test('getItem - returns null for non-existent item', () async { // Act final result = await service.getItem('non-existent'); // Assert expect(result, isNull); }); /// Tests that getting an item returns the correct data. test('getItem - success', () async { // Arrange final item = Item( id: 'test-2', data: {'title': 'Test Title', 'count': 42}, ); await service.insertItem(item); // Act final retrieved = await service.getItem('test-2'); // Assert expect(retrieved, isNotNull); expect(retrieved!.id, equals('test-2')); expect(retrieved.data['title'], equals('Test Title')); expect(retrieved.data['count'], equals(42)); }); /// Tests that getAllItems returns all stored items. test('getAllItems - returns all items', () async { // Arrange final item1 = Item(id: 'test-1', data: {'name': 'Item 1'}); final item2 = Item(id: 'test-2', data: {'name': 'Item 2'}); final item3 = Item(id: 'test-3', data: {'name': 'Item 3'}); await service.insertItem(item1); await service.insertItem(item2); await service.insertItem(item3); // Act final items = await service.getAllItems(); // Assert expect(items.length, equals(3)); expect(items.map((i) => i.id).toList(), containsAll(['test-1', 'test-2', 'test-3'])); }); /// Tests that getAllItems returns empty list when no items exist. test('getAllItems - returns empty list when no items', () async { // Act final items = await service.getAllItems(); // Assert expect(items, isEmpty); }); /// Tests that getAllItems returns items in descending order by creation date. test('getAllItems - returns items in descending order', () async { // Arrange final item1 = Item(id: 'test-1', data: {'name': 'First'}); await Future.delayed(const Duration(milliseconds: 10)); final item2 = Item(id: 'test-2', data: {'name': 'Second'}); await Future.delayed(const Duration(milliseconds: 10)); final item3 = Item(id: 'test-3', data: {'name': 'Third'}); await service.insertItem(item1); await service.insertItem(item2); await service.insertItem(item3); // Act final items = await service.getAllItems(); // Assert expect(items.length, equals(3)); expect(items[0].id, equals('test-3')); // Newest first expect(items[2].id, equals('test-1')); // Oldest last }); /// Tests that deleting an item removes it from the database. test('deleteItem - success', () async { // Arrange final item = Item(id: 'test-1', data: {'name': 'Test'}); await service.insertItem(item); // Act await service.deleteItem('test-1'); // Assert final retrieved = await service.getItem('test-1'); expect(retrieved, isNull); }); /// Tests that deleting a non-existent item throws an exception. test('deleteItem - throws exception for non-existent item', () async { // Act & Assert expect( () => service.deleteItem('non-existent'), throwsException, ); }); /// Tests that updating an item modifies it correctly. test('updateItem - success', () async { // Arrange final item = Item(id: 'test-1', data: {'name': 'Original'}); await service.insertItem(item); final originalTimestamp = item.updatedAt; await Future.delayed(const Duration(milliseconds: 10)); final updatedItem = item.copyWith( data: {'name': 'Updated'}, ); // Act await service.updateItem(updatedItem); // Assert final retrieved = await service.getItem('test-1'); expect(retrieved, isNotNull); expect(retrieved!.data['name'], equals('Updated')); expect(retrieved.updatedAt, greaterThan(originalTimestamp)); }); /// Tests that updating a non-existent item throws an exception. test('updateItem - throws exception for non-existent item', () async { // Arrange final item = Item(id: 'non-existent', data: {'name': 'Test'}); // Act & Assert expect( () => service.updateItem(item), throwsException, ); }); }); group('LocalStorageService - Image Caching', () { /// Tests that getCachedImage throws exception for invalid URL. test('getCachedImage - throws exception for invalid URL', () async { // Act & Assert expect( () => service.getCachedImage('not-a-valid-url'), throwsException, ); }); /// Tests that getCachedImage throws exception when not initialized. test('getCachedImage - throws exception when not initialized', () async { // Arrange final uninitializedService = LocalStorageService(); // Act & Assert expect( () => uninitializedService.getCachedImage('https://example.com/image.jpg'), throwsException, ); }); /// Tests that clearImageCache removes all cached images. test('clearImageCache - removes all cached images', () async { // Act await service.clearImageCache(); // Assert - should not throw expect(service, isNotNull); }); /// Tests cache hit scenario - file already exists in cache. test('getCachedImage - cache hit when file exists', () async { // Arrange - create a file in cache directory manually // Note: Full HTTP download test would require mocking http client // This test verifies the cache directory structure is correct // Act & Assert - service should be initialized expect(service, isNotNull); // The actual HTTP download test would require: // 1. Mocking http.get() with mockito or http_mock_adapter // 2. Verifying file creation in cache directory // For now, we verify error handling works correctly }); }); group('LocalStorageService - Error Handling', () { /// Tests that operations fail when service is not initialized. test('operations fail when not initialized', () async { // Arrange final uninitializedService = LocalStorageService(); final item = Item(id: 'test-1', data: {'name': 'Test'}); // Act & Assert expect( () => uninitializedService.insertItem(item), throwsException, ); expect( () => uninitializedService.getItem('test-1'), throwsException, ); expect( () => uninitializedService.deleteItem('test-1'), throwsException, ); expect( () => uninitializedService.updateItem(item), throwsException, ); }); /// Tests that getCachedImage fails when cache directory not initialized. test('getCachedImage fails when not initialized', () async { // Arrange final uninitializedService = LocalStorageService(); // Act & Assert expect( () => uninitializedService.getCachedImage('https://example.com/image.jpg'), throwsException, ); }); /// Tests handling of missing or invalid data. test('handles missing data gracefully', () async { // Arrange final item = Item(id: 'test-1', data: {}); // Act await service.insertItem(item); final retrieved = await service.getItem('test-1'); // Assert expect(retrieved, isNotNull); expect(retrieved!.data, isEmpty); }); /// Tests handling of complex nested data structures. test('handles complex nested data', () async { // Arrange final item = Item( id: 'test-1', data: { 'metadata': { 'author': 'Test Author', 'tags': ['tag1', 'tag2'], 'nested': { 'deep': 'value', }, }, }, ); // Act await service.insertItem(item); final retrieved = await service.getItem('test-1'); // Assert expect(retrieved, isNotNull); expect(retrieved!.data['metadata']['author'], equals('Test Author')); expect(retrieved.data['metadata']['tags'], isA()); expect(retrieved.data['metadata']['nested']['deep'], equals('value')); }); }); }