diff --git a/lib/openfoodfacts.dart b/lib/openfoodfacts.dart index eb79912e50..e1c68c9ed2 100644 --- a/lib/openfoodfacts.dart +++ b/lib/openfoodfacts.dart @@ -28,6 +28,7 @@ export 'src/model/nutriments.dart'; // export 'src/model/product_list.dart'; // not needed export 'src/model/ocr_ingredients_result.dart'; export 'src/model/ocr_packaging_result.dart'; +export 'src/model/old_product_result.dart'; export 'src/model/ordered_nutrient.dart'; export 'src/model/ordered_nutrients.dart'; export 'src/model/origins_of_ingredients.dart'; diff --git a/lib/src/model/old_product_result.dart b/lib/src/model/old_product_result.dart new file mode 100644 index 0000000000..19adf68b31 --- /dev/null +++ b/lib/src/model/old_product_result.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; +import '../interface/json_object.dart'; +import 'product.dart'; + +part 'old_product_result.g.dart'; + +/// Product Result (old style). +// TODO(monsieurtanuki): get rid of it when OBF OPF OPFF support api v3 +@JsonSerializable() +class OldProductResult extends JsonObject { + final int? status; + @JsonKey(name: 'code') + final String? barcode; + @JsonKey(name: 'status_verbose') + final String? statusVerbose; + final Product? product; + + const OldProductResult( + {this.status, this.barcode, this.statusVerbose, this.product}); + + factory OldProductResult.fromJson(Map json) => + _$OldProductResultFromJson(json); + + @override + Map toJson() => _$OldProductResultToJson(this); +} diff --git a/lib/src/model/old_product_result.g.dart b/lib/src/model/old_product_result.g.dart new file mode 100644 index 0000000000..3a6cb5df26 --- /dev/null +++ b/lib/src/model/old_product_result.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'old_product_result.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +OldProductResult _$OldProductResultFromJson(Map json) => + OldProductResult( + status: json['status'] as int?, + barcode: json['code'] as String?, + statusVerbose: json['status_verbose'] as String?, + product: json['product'] == null + ? null + : Product.fromJson(json['product'] as Map), + ); + +Map _$OldProductResultToJson(OldProductResult instance) => + { + 'status': instance.status, + 'code': instance.barcode, + 'status_verbose': instance.statusVerbose, + 'product': instance.product, + }; diff --git a/lib/src/open_food_api_client.dart b/lib/src/open_food_api_client.dart index e85bd882f4..5379c9cc1e 100644 --- a/lib/src/open_food_api_client.dart +++ b/lib/src/open_food_api_client.dart @@ -7,6 +7,7 @@ import 'interface/json_object.dart'; import 'model/login_status.dart'; import 'model/ocr_ingredients_result.dart'; import 'model/ocr_packaging_result.dart'; +import 'model/old_product_result.dart'; import 'model/ordered_nutrients.dart'; import 'model/parameter/barcode_parameter.dart'; import 'model/product.dart'; @@ -277,6 +278,33 @@ class OpenFoodAPIClient { return result; } + /// Returns the product for the given barcode, with an old syntax. + /// + /// Temporarily needed for OBF, OPF and OPFF, that do not support api v3. + // TODO(monsieurtanuki): get rid of it when OBF OPF OPFF support api v3 + static Future getOldProduct( + final ProductQueryConfiguration configuration, { + final User? user, + final UriProductHelper uriHelper = uriHelperFoodProd, + }) async { + if (configuration.matchesV3()) { + Exception("The configuration must not match V3!"); + } + final String productString = await getProductString( + configuration, + user: user, + uriHelper: uriHelper, + ); + final String jsonStr = _replaceQuotes(productString); + final OldProductResult result = + OldProductResult.fromJson(jsonDecode(jsonStr)); + if (result.product != null) { + ProductHelper.removeImages(result.product!, configuration.language); + ProductHelper.createImageUrls(result.product!, uriHelper: uriHelper); + } + return result; + } + /// Returns the ids of all uploaded images for that product. /// /// To be used in combination with [ImageHelper.getUploadedImageUrl]. diff --git a/test/api_not_food_get_product_test.dart b/test/api_not_food_get_product_test.dart new file mode 100644 index 0000000000..908d11fdb0 --- /dev/null +++ b/test/api_not_food_get_product_test.dart @@ -0,0 +1,86 @@ +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:test/test.dart'; + +import 'test_constants.dart'; + +void main() { + OpenFoodAPIConfiguration.userAgent = TestConstants.TEST_USER_AGENT; + OpenFoodAPIConfiguration.globalUser = TestConstants.PROD_USER; + + const UriProductHelper uriHelperBeautyProd = UriProductHelper( + host: 'world.openbeautyfacts.org', + imageUrlBase: 'https://static.openbeautyfacts.org/images/products/', + ); + const UriProductHelper uriHelperProductsProd = UriProductHelper( + host: 'world.openproductsfacts.org', + imageUrlBase: 'https://static.openproductsfacts.org/images/products/', + ); + const UriProductHelper uriHelperPetFoodProd = UriProductHelper( + host: 'world.openpetfoodfacts.org', + imageUrlBase: 'https://static.openpetfoodfacts.org/images/products/', + ); + + const String beautyBarcode = '4056489234692'; + const String productsBarcode = '7898927451035'; + const String petFoodBarcode = '3564700266809'; + + group('$OpenFoodAPIClient get not food products', () { + Future findProduct( + final String barcode, + final UriProductHelper uriHelper, + final bool shouldBeThere, + ) async { + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + language: OpenFoodFactsLanguage.ENGLISH, + fields: [ProductField.ALL], + version: ProductQueryVersion(2), + ); + final OldProductResult result = await OpenFoodAPIClient.getOldProduct( + configurations, + uriHelper: uriHelper, + ); + if (shouldBeThere) { + expect(result.status, 1); + expect(result.barcode, barcode); + expect(result.product, isNotNull); + expect(result.product!.barcode, barcode); + } else { + expect(result.status, 0); + expect(result.barcode, barcode); + expect(result.product, isNull); + } + return result.product; + } + + test('get beauty product', () async { + final String barcode = beautyBarcode; + await findProduct(barcode, uriHelperBeautyProd, true); + await findProduct(barcode, uriHelperProductsProd, false); + await findProduct(barcode, uriHelperPetFoodProd, false); + await findProduct(barcode, uriHelperPetFoodProd, false); + await findProduct(barcode, uriHelperFoodProd, false); + }); + + test('get products product', () async { + final String barcode = productsBarcode; + await findProduct(barcode, uriHelperBeautyProd, false); + await findProduct(barcode, uriHelperProductsProd, true); + await findProduct(barcode, uriHelperPetFoodProd, false); + await findProduct(barcode, uriHelperFoodProd, false); + }); + + test('get pet food product', () async { + final String barcode = petFoodBarcode; + await findProduct(barcode, uriHelperBeautyProd, false); + await findProduct(barcode, uriHelperProductsProd, false); + await findProduct(barcode, uriHelperPetFoodProd, true); + await findProduct(barcode, uriHelperFoodProd, false); + }); + }, + timeout: Timeout( + // some tests can be slow here + Duration(seconds: 300), + )); +}