diff --git a/lib/data/datasources/pokemon_local_datasource.dart b/lib/data/datasources/pokemon_local_datasource.dart index 96acf48..c08d686 100644 --- a/lib/data/datasources/pokemon_local_datasource.dart +++ b/lib/data/datasources/pokemon_local_datasource.dart @@ -4,10 +4,10 @@ import '../dto/pokemon_dto.dart'; /// Accès SQLite local au Pokédex. Schéma et migrations identiques à l'ancien PokedexDatabase. class PokemonLocalDataSource { - Database? _database; + Future? _dbFuture; - Future _getDb() async { - _database ??= await openDatabase( + Future _getDb() { + return _dbFuture ??= openDatabase( 'pokedex.db', version: 2, onUpgrade: (db, oldVersion, newVersion) async { @@ -22,7 +22,6 @@ class PokemonLocalDataSource { 'CREATE TABLE IF NOT EXISTS pokemon (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type1 TEXT NOT NULL, type2 TEXT, hp INTEGER NOT NULL, atk INTEGER NOT NULL, def INTEGER NOT NULL, spd INTEGER NOT NULL, description TEXT, isCaught INTEGER NOT NULL DEFAULT 0, isSeen INTEGER NOT NULL DEFAULT 0)'); }, ); - return _database!; } Future> getAll() async { diff --git a/lib/data/dto/pokemon_dto.dart b/lib/data/dto/pokemon_dto.dart index 8229d80..5387ff4 100644 --- a/lib/data/dto/pokemon_dto.dart +++ b/lib/data/dto/pokemon_dto.dart @@ -32,7 +32,9 @@ class PokemonDto { /// Construit une entité depuis la réponse Tyradex (objet unique ou élément de liste). /// [fallbackId] sert quand le JSON ne contient pas `pokedex_id`. static Pokemon fromTyradexJson(Map json, {int? fallbackId}) { - final id = (json['pokedex_id'] as int?) ?? fallbackId!; + final id = (json['pokedex_id'] as int?) ?? + fallbackId ?? + (throw ArgumentError('pokedex_id absent et fallbackId non fourni')); final nameMap = json['name'] as Map?; final name = nameMap?['fr'] ?? nameMap?['en'] ?? 'unknown'; @@ -77,9 +79,15 @@ class PokemonDto { return Pokemon( name: row['name'], id: row['id'], - type1: PokemonType.values.firstWhere((e) => e.name == row['type1']), + type1: PokemonType.values.firstWhere( + (e) => e.name == row['type1'], + orElse: () => PokemonType.unknown, + ), type2: row['type2'] != null - ? PokemonType.values.firstWhere((e) => e.name == row['type2']) + ? PokemonType.values.firstWhere( + (e) => e.name == row['type2'], + orElse: () => PokemonType.unknown, + ) : null, hp: row['hp'] ?? 0, atk: row['atk'] ?? 0, diff --git a/test/data/pokemon_repository_test.dart b/test/data/pokemon_repository_test.dart index 71c77c8..a1577cc 100644 --- a/test/data/pokemon_repository_test.dart +++ b/test/data/pokemon_repository_test.dart @@ -49,6 +49,13 @@ class FakeRemote implements PokemonRemoteDataSource { } } +class ThrowingRemote implements PokemonRemoteDataSource { + @override + Future> getAll() async => throw Exception('network'); + @override + Future getById(int id) async => throw Exception('network'); +} + void main() { group('getById', () { test('retourne le cache local sans appeler l\'API', () async { @@ -103,4 +110,18 @@ void main() { expect(list.length, 3); }); }); + + group('repli sur erreur', () { + test('getById retourne null si l\'API échoue et le cache est vide', () async { + final repo = PokemonRepositoryImpl(remote: ThrowingRemote(), local: FakeLocal()); + expect(await repo.getById(42), isNull); + }); + + test('getAll retourne le cache existant si la synchro API échoue', () async { + final local = FakeLocal({1: _mk(1)}); // cache incomplet -> tente l'API, qui échoue + final repo = PokemonRepositoryImpl(remote: ThrowingRemote(), local: local); + final list = await repo.getAll(); + expect(list.length, 1); // repli sur le cache + }); + }); }