You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
11 KiB
354 lines
11 KiB
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<List>());
|
|
expect(retrieved.data['metadata']['nested']['deep'], equals('value'));
|
|
});
|
|
});
|
|
}
|
|
|