diff --git a/lib/openfoodfacts.dart b/lib/openfoodfacts.dart index 433d798e83..c05e07ec66 100644 --- a/lib/openfoodfacts.dart +++ b/lib/openfoodfacts.dart @@ -20,6 +20,7 @@ import 'package:openfoodfacts/model/TaxonomyLabel.dart'; import 'package:openfoodfacts/model/TaxonomyLanguage.dart'; import 'package:openfoodfacts/model/TaxonomyPackaging.dart'; import 'package:openfoodfacts/utils/AbstractQueryConfiguration.dart'; +import 'package:openfoodfacts/utils/AddableProductFields.dart'; import 'package:openfoodfacts/utils/CountryHelper.dart'; import 'package:openfoodfacts/utils/ImageHelper.dart'; import 'package:openfoodfacts/utils/OcrField.dart'; @@ -131,6 +132,37 @@ class OpenFoodAPIClient { return Status.fromApiResponse(response.body); } + /// Add the given product to the database. + /// Returns a Status object as result. + static Future addProductFields( + final User user, + final String barcode, + final Map map, { + QueryType? queryType, + }) async { + if (map.isEmpty) { + throw Exception('Please add at least one AddableProductField'); + } + + final Map parameterMap = {}; + parameterMap.addAll(user.toData()); + parameterMap['code'] = barcode; + for (final MapEntry entry in map.entries) { + parameterMap[entry.key.addableKey] = entry.value; + } + + final Response response = await HttpHelper().doPostRequest( + UriHelper.getPostUri( + path: '/cgi/product_jqm2.pl', + queryType: queryType, + ), + parameterMap, + user, + queryType: queryType, + ); + return Status.fromApiResponse(response.body); + } + /// Send one image to the server. /// The image will be added to the product specified in the SendImage /// Returns a Status object as result. diff --git a/lib/utils/AddableProductFields.dart b/lib/utils/AddableProductFields.dart new file mode 100644 index 0000000000..f849a898c1 --- /dev/null +++ b/lib/utils/AddableProductFields.dart @@ -0,0 +1,17 @@ +/// Fields of a [Product] that can be simply added. +enum AddableProductField { + BRANDS, + STORES, + COUNTRIES, +} + +extension AddableProductFieldExtension on AddableProductField { + static const Map _KEYS = { + AddableProductField.BRANDS: 'add_brands', + AddableProductField.STORES: 'add_stores', + AddableProductField.COUNTRIES: 'add_countries', + }; + + /// Returns the key of the product field + String get addableKey => _KEYS[this]!; +} diff --git a/test/api_addProductFields_test.dart b/test/api_addProductFields_test.dart new file mode 100644 index 0000000000..0268c31ed5 --- /dev/null +++ b/test/api_addProductFields_test.dart @@ -0,0 +1,114 @@ +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:openfoodfacts/utils/AddableProductFields.dart'; +import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart'; +import 'package:openfoodfacts/utils/QueryType.dart'; +import 'package:test/test.dart'; +import 'test_constants.dart'; + +/// Unit/Integration tests around OpenFoodAPIClient.addProductFields +void main() { + OpenFoodAPIConfiguration.globalQueryType = QueryType.TEST; + + group('$OpenFoodAPIClient add product fields', () { + const String barcode = '0048151623426'; + const List additionalValues = ['djobi', 'djoba']; + const OpenFoodFactsLanguage language = OpenFoodFactsLanguage.ENGLISH; + const User user = TestConstants.TEST_USER; + const Timeout timeout = Timeout(Duration(seconds: 90)); + + void _checkStatus(final Status status) { + expect(status.status, 1); + expect(status.statusVerbose, 'fields saved'); + } + + String? _getValue( + final AddableProductField field, + final Product product, + ) { + switch (field) { + case AddableProductField.BRANDS: + return product.brands; + case AddableProductField.COUNTRIES: + return product.countries; + case AddableProductField.STORES: + return product.stores; + } + } + + Future _checkValue( + final AddableProductField field, + final String expectedValue, + ) async { + final ProductQueryConfiguration configuration = ProductQueryConfiguration( + barcode, + language: language, + fields: [ProductField.ALL], + ); + + final ProductResult productResult = await OpenFoodAPIClient.getProduct( + configuration, + user: user, + ); + expect(productResult.product, isNotNull); + final String? actualValue = _getValue(field, productResult.product!); + expect(actualValue, equalsIgnoringCase(expectedValue)); + } + + Future _checkAddableField( + final AddableProductField field, + final Product initialProduct, + ) async { + late Status status; + late Product product; + + // from scratch + product = initialProduct; + status = await OpenFoodAPIClient.saveProduct(user, product); + _checkStatus(status); + + // cumulative list + String expectedValue = _getValue(field, product)!; + + await _checkValue(field, expectedValue); + + for (final String additionalValue in additionalValues) { + expectedValue += ', $additionalValue'; + + status = await OpenFoodAPIClient.addProductFields( + user, + barcode, + {field: additionalValue}, + ); + _checkStatus(status); + + await _checkValue(field, expectedValue); + } + } + + test( + 'add brand', + () async => _checkAddableField( + AddableProductField.BRANDS, + Product( + barcode: barcode, lang: language, brands: 'Golden Cookies'), + ), + timeout: timeout); + + test( + 'add country', + () async => _checkAddableField( + AddableProductField.COUNTRIES, + Product( + barcode: barcode, lang: language, countries: 'United States'), + ), + timeout: timeout); + + test( + 'add stores', + () async => _checkAddableField( + AddableProductField.STORES, + Product(barcode: barcode, lang: language, stores: 'Intermarché'), + ), + timeout: timeout); + }); +}