From 765c16fb47ce1b9c8dfc3171b166b1abc32ee5c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 10:09:34 +0000 Subject: [PATCH 1/3] Initial plan From 27f1d2fd1ddb254f3121808316e577695467129e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 10:17:22 +0000 Subject: [PATCH 2/3] Fix null id/name parsing and skip empty ids --- lib/models/drink.dart | 8 +++--- lib/services/beer_api_service.dart | 2 ++ test/beer_api_service_test.dart | 45 ++++++++++++++++++++++++++++++ test/models_test.dart | 25 +++++++++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/lib/models/drink.dart b/lib/models/drink.dart index 2486e1a1..cdb29a50 100644 --- a/lib/models/drink.dart +++ b/lib/models/drink.dart @@ -27,8 +27,8 @@ class Producer { } return Producer( - id: json['id'].toString(), - name: json['name'].toString(), + id: (json['id'] as String?) ?? '', + name: (json['name'] as String?) ?? '', location: (json['location'] ?? '').toString(), yearFounded: yearFounded, notes: json['notes']?.toString(), @@ -139,8 +139,8 @@ class Product { } return Product( - id: json['id'].toString(), - name: json['name'].toString(), + id: (json['id'] as String?) ?? '', + name: (json['name'] as String?) ?? '', category: (json['category'] ?? 'beer').toString(), style: json['style']?.toString(), dispense: (json['dispense'] ?? 'cask').toString(), diff --git a/lib/services/beer_api_service.dart b/lib/services/beer_api_service.dart index 5919a664..4941d8b6 100644 --- a/lib/services/beer_api_service.dart +++ b/lib/services/beer_api_service.dart @@ -109,7 +109,9 @@ class BeerApiService { for (final producerJson in producers) { final producer = Producer.fromJson(producerJson as Map); + if (producer.id.isEmpty) continue; for (final product in producer.products) { + if (product.id.isEmpty) continue; drinks.add(Drink( product: product, producer: producer, diff --git a/test/beer_api_service_test.dart b/test/beer_api_service_test.dart index 92bbb776..5eeba505 100644 --- a/test/beer_api_service_test.dart +++ b/test/beer_api_service_test.dart @@ -241,6 +241,51 @@ void main() { expect(drinks.first.festivalId, 'my-festival-id'); }); + + test('skips products with missing ids', () async { + service = BeerApiService(client: mockClient); + + const festival = Festival( + id: 'cbf2025', + name: 'Test Festival', + dataBaseUrl: 'https://example.com', + availableBeverageTypes: ['beer'], + ); + + final responseBody = json.encode({ + 'producers': [ + { + 'id': 'brewery-1', + 'name': 'Test Brewery', + 'location': 'Cambridge', + 'products': [ + { + 'id': null, + 'name': 'No Id Beer', + 'category': 'beer', + 'dispense': 'cask', + 'abv': '4.0', + }, + { + 'id': 'drink-2', + 'name': 'Valid Beer', + 'category': 'beer', + 'dispense': 'cask', + 'abv': '4.5', + }, + ], + }, + ], + }); + + when(mockClient.get(Uri.parse('https://example.com/beer.json'))) + .thenAnswer((_) async => http.Response(responseBody, 200)); + + final drinks = await service.fetchDrinks(festival, 'beer'); + + expect(drinks.length, 1); + expect(drinks.first.id, 'drink-2'); + }); }); group('fetchAllDrinks', () { diff --git a/test/models_test.dart b/test/models_test.dart index ba04f1e8..91997af4 100644 --- a/test/models_test.dart +++ b/test/models_test.dart @@ -52,6 +52,19 @@ void main() { expect(product.allergens, isEmpty); }); + test('fromJson maps missing id and name to empty strings', () { + final product = Product.fromJson({ + 'id': null, + 'name': null, + 'category': 'beer', + 'dispense': 'cask', + 'abv': '4.0', + }); + + expect(product.id, ''); + expect(product.name, ''); + }); + test('availabilityStatus returns correct values', () { expect( Product.fromJson({ @@ -722,6 +735,18 @@ void main() { expect(producer.location, ''); }); + test('fromJson maps missing id and name to empty strings', () { + final producer = Producer.fromJson({ + 'id': null, + 'name': null, + 'location': 'Somewhere', + 'products': [], + }); + + expect(producer.id, ''); + expect(producer.name, ''); + }); + group('toJson', () { test('converts producer to JSON correctly', () { const product = Product( From e0f555de3b54fcacc00694f6b74d8620ff300470 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 10:21:12 +0000 Subject: [PATCH 3/3] Add test for skipping producers with missing ids --- test/beer_api_service_test.dart | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/beer_api_service_test.dart b/test/beer_api_service_test.dart index 5eeba505..f4ec66be 100644 --- a/test/beer_api_service_test.dart +++ b/test/beer_api_service_test.dart @@ -286,6 +286,59 @@ void main() { expect(drinks.length, 1); expect(drinks.first.id, 'drink-2'); }); + + test('skips producers with missing ids', () async { + service = BeerApiService(client: mockClient); + + const festival = Festival( + id: 'cbf2025', + name: 'Test Festival', + dataBaseUrl: 'https://example.com', + availableBeverageTypes: ['beer'], + ); + + final responseBody = json.encode({ + 'producers': [ + { + 'id': null, + 'name': 'Missing Id Brewery', + 'location': 'Cambridge', + 'products': [ + { + 'id': 'drink-ignored', + 'name': 'Ignored Beer', + 'category': 'beer', + 'dispense': 'cask', + 'abv': '4.0', + }, + ], + }, + { + 'id': 'brewery-2', + 'name': 'Valid Brewery', + 'location': 'Cambridge', + 'products': [ + { + 'id': 'drink-2', + 'name': 'Valid Beer', + 'category': 'beer', + 'dispense': 'cask', + 'abv': '4.5', + }, + ], + }, + ], + }); + + when(mockClient.get(Uri.parse('https://example.com/beer.json'))) + .thenAnswer((_) async => http.Response(responseBody, 200)); + + final drinks = await service.fetchDrinks(festival, 'beer'); + + expect(drinks.length, 1); + expect(drinks.first.id, 'drink-2'); + expect(drinks.first.producer.id, 'brewery-2'); + }); }); group('fetchAllDrinks', () {