|
| 1 | +import 'package:flutter_test/flutter_test.dart'; |
| 2 | +import 'package:flutter_easy_cache/flutter_easy_cache.dart'; |
| 3 | +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; |
| 4 | +import 'package:shared_preferences/shared_preferences.dart'; |
| 5 | + |
| 6 | +/// These tests are designed to expose specific bugs found in code review |
| 7 | +void main() { |
| 8 | + TestWidgetsFlutterBinding.ensureInitialized(); |
| 9 | + |
| 10 | + late FlutterEasyCache cache; |
| 11 | + SharedPreferencesWithCache? sharedPreferences; |
| 12 | + FlutterSecureStorage? secureStorage; |
| 13 | + |
| 14 | + setUp(() async { |
| 15 | + FlutterEasyCache.setMockInitialValues(); |
| 16 | + secureStorage ??= const FlutterSecureStorage(); |
| 17 | + sharedPreferences ??= |
| 18 | + await SharedPreferencesWithCache.create(cacheOptions: const SharedPreferencesWithCacheOptions()); |
| 19 | + cache = FlutterEasyCache.create(sharedPreferences!, secureStorage!, enableLogging: true); |
| 20 | + }); |
| 21 | + |
| 22 | + tearDown(() async { |
| 23 | + await FlutterEasyCache.resetStatic(); |
| 24 | + }); |
| 25 | + |
| 26 | + group('Bug Reproduction Tests', () { |
| 27 | + // Bug #2: int.parse() should use tryParse() to avoid throwing |
| 28 | + test('BUG #2: Reading non-existent int from secure storage should not throw FormatException', () async { |
| 29 | + // First, add and then remove an int to ensure secure storage is initialized |
| 30 | + await cache.addOrUpdate(key: 'tempInt', value: 42, policy: CachePolicy.secure); |
| 31 | + await cache.remove(key: 'tempInt'); |
| 32 | + |
| 33 | + // Now try to read a key that was never set |
| 34 | + // With int.parse(), this will throw FormatException: Invalid radix-10 number (at character 1) |
| 35 | + // With int.tryParse(), this will return null gracefully |
| 36 | + |
| 37 | + int? retrievedValue; |
| 38 | + bool didThrow = false; |
| 39 | + |
| 40 | + try { |
| 41 | + retrievedValue = await cache.getValueOrNull<int>(key: 'neverSetIntKey'); |
| 42 | + } catch (e) { |
| 43 | + didThrow = true; |
| 44 | + // Exception caught: $e |
| 45 | + } |
| 46 | + |
| 47 | + expect(didThrow, false, reason: 'Should use int.tryParse() instead of int.parse() to avoid throwing'); |
| 48 | + expect(retrievedValue, null); |
| 49 | + }); |
| 50 | + |
| 51 | + // Bug #3: Null pointer when reading non-existent List<Map> |
| 52 | + test('BUG #3: Reading non-existent List<Map> from preferences should not throw', () async { |
| 53 | + // Initialize preferences |
| 54 | + await cache.addOrUpdate(key: 'dummy', value: 'value', policy: CachePolicy.appInstall); |
| 55 | + |
| 56 | + // Try to read a List<Map> that doesn't exist |
| 57 | + // Line 198: final stringList = _preferences?.getStringList(key) as List<String>; |
| 58 | + // This will throw TypeError: Null check operator used on a null value |
| 59 | + |
| 60 | + List<Map<String, dynamic>>? retrievedValue; |
| 61 | + bool didThrow = false; |
| 62 | + |
| 63 | + try { |
| 64 | + retrievedValue = await cache.getValueOrNull<List<Map<String, dynamic>>>(key: 'neverSetListMapKey'); |
| 65 | + } catch (e) { |
| 66 | + didThrow = true; |
| 67 | + // Exception caught: $e |
| 68 | + } |
| 69 | + |
| 70 | + expect(didThrow, false, reason: 'Should check for null before casting'); |
| 71 | + expect(retrievedValue, null); |
| 72 | + }); |
| 73 | + |
| 74 | + // Bug #1: Missing await in addOrUpdate switch statement |
| 75 | + test('BUG #1: addOrUpdate should await async operations in switch statement', () async { |
| 76 | + // This test tries to verify that the Future completes only after write finishes |
| 77 | + // In practice, this bug might not be caught by tests due to fast execution |
| 78 | + // But it violates the async contract |
| 79 | + |
| 80 | + const value = 'test-value'; |
| 81 | + |
| 82 | + // Create a list to track execution order |
| 83 | + final executionOrder = <String>[]; |
| 84 | + |
| 85 | + cache.addOrUpdate(key: 'testKey', value: value, policy: CachePolicy.appInstall).then((_) { |
| 86 | + executionOrder.add('addOrUpdate completed'); |
| 87 | + }); |
| 88 | + |
| 89 | + // Small delay to let async operations settle |
| 90 | + await Future.delayed(const Duration(milliseconds: 10)); |
| 91 | + executionOrder.add('after delay'); |
| 92 | + |
| 93 | + final retrievedValue = await cache.getValueOrNull<String>(key: 'testKey'); |
| 94 | + |
| 95 | + expect(retrievedValue, value); |
| 96 | + expect(executionOrder, contains('addOrUpdate completed')); |
| 97 | + }); |
| 98 | + }); |
| 99 | +} |
0 commit comments