From 85169f083ad7bc1c83c67e817de32a942700eb92 Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Sat, 4 Dec 2021 14:47:44 +0100 Subject: [PATCH] feat: #301 - new method getProductUri (#302) Impacted files: * `api_getProduct_test.dart`: new test "get product uri" * `api_saveProduct_test.dart`: clearer test * `CountryHelper.dart`: replaced 'gb' with the OFF standard 'uk' * `openfoodfacts.dart`: new method `getProductUri` * `UriHelper.dart`: new method `replaceSubdomain` --- lib/openfoodfacts.dart | 26 ++++++++++++++++ lib/utils/CountryHelper.dart | 3 +- lib/utils/UriHelper.dart | 34 ++++++++++++++++++++ test/api_getProduct_test.dart | 57 ++++++++++++++++++++++++++++++++++ test/api_saveProduct_test.dart | 41 ++++++++++++++---------- 5 files changed, 144 insertions(+), 17 deletions(-) diff --git a/lib/openfoodfacts.dart b/lib/openfoodfacts.dart index beeb65cbf5..8fe486c052 100644 --- a/lib/openfoodfacts.dart +++ b/lib/openfoodfacts.dart @@ -17,6 +17,7 @@ import 'package:openfoodfacts/model/TaxonomyIngredient.dart'; import 'package:openfoodfacts/model/TaxonomyLabel.dart'; import 'package:openfoodfacts/model/TaxonomyLanguage.dart'; import 'package:openfoodfacts/utils/AbstractQueryConfiguration.dart'; +import 'package:openfoodfacts/utils/CountryHelper.dart'; import 'package:openfoodfacts/utils/OcrField.dart'; import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart'; import 'package:openfoodfacts/utils/PnnsGroupQueryConfiguration.dart'; @@ -213,6 +214,31 @@ class OpenFoodAPIClient { return str.replaceAll('"', '\\"'); } + /// Returns the URI to the product page on a website + /// + /// If the target website supports different domains for country + language, + /// [replaceSubdomain] should be set to true. + static Uri getProductUri( + final String barcode, { + final OpenFoodFactsLanguage? language, + final OpenFoodFactsCountry? country, + final QueryType? queryType, + required final bool replaceSubdomain, + }) { + final Uri uri = UriHelper.getUri( + path: 'product/$barcode', + queryType: queryType, + ); + if (!replaceSubdomain) { + return uri; + } + return UriHelper.replaceSubdomain( + uri, + language: language, + country: country, + ); + } + /// Search the OpenFoodFacts product database with the given parameters. /// Returns the list of products as SearchResult. /// Query the language specific host from OpenFoodFacts. diff --git a/lib/utils/CountryHelper.dart b/lib/utils/CountryHelper.dart index f1dd8e2e97..0d92b9f0e0 100644 --- a/lib/utils/CountryHelper.dart +++ b/lib/utils/CountryHelper.dart @@ -826,7 +826,8 @@ extension OpenFoodFactsCoutryExtension on OpenFoodFactsCountry { OpenFoodFactsCountry.FAROE_ISLANDS: 'fo', OpenFoodFactsCountry.FRANCE: 'fr', OpenFoodFactsCountry.GABON: 'ga', - OpenFoodFactsCountry.UNITED_KINGDOM: 'gb', + // in OFF this is not 'gb' + OpenFoodFactsCountry.UNITED_KINGDOM: 'uk', OpenFoodFactsCountry.GRENADA: 'gd', OpenFoodFactsCountry.GEORGIA: 'ge', OpenFoodFactsCountry.FRENCH_GUIANA: 'gf', diff --git a/lib/utils/UriHelper.dart b/lib/utils/UriHelper.dart index e473e3ff97..da3f58bad6 100644 --- a/lib/utils/UriHelper.dart +++ b/lib/utils/UriHelper.dart @@ -1,3 +1,5 @@ +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:openfoodfacts/utils/CountryHelper.dart'; import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart'; import 'QueryType.dart'; @@ -37,4 +39,36 @@ class UriHelper { queryParameters: queryParameters, ); } + + /// Replaces the subdomain of an URI with specific country and language + /// + /// For instance + /// * https://world.xxx... would be standard + /// * https://world-fr.xxx... would be "standard country" in French + /// * https://fr.xxx... would be France + /// * https://fr-es.xxx... would be France in Spanish + static Uri replaceSubdomain( + final Uri uri, { + OpenFoodFactsLanguage? language, + OpenFoodFactsCountry? country, + }) { + final String initialSubdomain = uri.host.split('.')[0]; + final String countryCode = country?.iso2Code ?? + OpenFoodAPIConfiguration.globalCountry?.iso2Code ?? + initialSubdomain; + final String? languageCode = language?.code ?? + (OpenFoodAPIConfiguration.globalLanguages != null && + OpenFoodAPIConfiguration.globalLanguages!.isNotEmpty + ? OpenFoodAPIConfiguration.globalLanguages![0].code + : null); + final String subdomain; + if (languageCode != null) { + subdomain = '$countryCode-$languageCode'; + } else { + subdomain = countryCode; + } + return uri.replace( + host: uri.host.replaceFirst('$initialSubdomain.', '$subdomain.'), + ); + } } diff --git a/test/api_getProduct_test.dart b/test/api_getProduct_test.dart index 865ae2f207..2efc59872e 100644 --- a/test/api_getProduct_test.dart +++ b/test/api_getProduct_test.dart @@ -10,6 +10,7 @@ import 'package:openfoodfacts/personalized_search/matched_product.dart'; import 'package:openfoodfacts/personalized_search/preference_importance.dart'; import 'package:openfoodfacts/personalized_search/product_preferences_manager.dart'; import 'package:openfoodfacts/personalized_search/product_preferences_selection.dart'; +import 'package:openfoodfacts/utils/CountryHelper.dart'; import 'package:openfoodfacts/utils/InvalidBarcodes.dart'; import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart'; import 'package:openfoodfacts/utils/QueryType.dart'; @@ -1590,4 +1591,60 @@ void main() { } } }); + + test('get product uri', () async { + const String barcode = _BARCODE_DANISH_BUTTER_COOKIES; + expect( + OpenFoodAPIClient.getProductUri( + barcode, + language: OpenFoodFactsLanguage.SPANISH, + country: OpenFoodFactsCountry.GERMANY, + replaceSubdomain: true, + ).host, + 'de-es.openfoodfacts.net', + ); + expect( + OpenFoodAPIClient.getProductUri( + barcode, + language: OpenFoodFactsLanguage.SPANISH, + country: OpenFoodFactsCountry.GERMANY, + replaceSubdomain: false, + ).host, + 'world.openfoodfacts.net', + ); + + OpenFoodAPIConfiguration.globalCountry = + OpenFoodFactsCountry.UNITED_KINGDOM; + expect( + OpenFoodAPIClient.getProductUri(barcode, replaceSubdomain: true).host, + 'uk.openfoodfacts.net', + ); + expect( + OpenFoodAPIClient.getProductUri( + barcode, + language: OpenFoodFactsLanguage.SPANISH, + country: OpenFoodFactsCountry.GERMANY, + replaceSubdomain: true, + ).host, + 'de-es.openfoodfacts.net', + ); + + OpenFoodAPIConfiguration.globalLanguages = [ + OpenFoodFactsLanguage.BRETON, + OpenFoodFactsLanguage.FRENCH + ]; + expect( + OpenFoodAPIClient.getProductUri(barcode, replaceSubdomain: true).host, + 'uk-br.openfoodfacts.net', + ); + expect( + OpenFoodAPIClient.getProductUri( + barcode, + language: OpenFoodFactsLanguage.SPANISH, + country: OpenFoodFactsCountry.GERMANY, + replaceSubdomain: true, + ).host, + 'de-es.openfoodfacts.net', + ); + }); } diff --git a/test/api_saveProduct_test.dart b/test/api_saveProduct_test.dart index d42a5bd355..c670fdd405 100644 --- a/test/api_saveProduct_test.dart +++ b/test/api_saveProduct_test.dart @@ -91,17 +91,22 @@ void main() { Duration(seconds: 90), )); + String _getRandomTimestamp({int random = 100000}) => + DateTime.now().toString() + + ' (' + + Random().nextInt(random).toString() + + ')'; + test('dont overwrite language', () async { - String barcode = '4008391212596'; + const String barcode = '4008391212596'; // Assign random product names, to make sure we won't fail to update the // product and then read a previously written value - String frenchProductName = "Flocons d'epeautre au blé complet " + - Random().nextInt(100000).toString(); - String germanProductName = - 'Dinkelflakes' + Random().nextInt(100000).toString(); + final String frenchProductName = + "Flocons d'epeautre au blé complet " + _getRandomTimestamp(); + final String germanProductName = 'Dinkelflakes' + _getRandomTimestamp(); // save french product name - Product frenchProduct = Product( + final Product frenchProduct = Product( barcode: barcode, productNameInLanguages: { OpenFoodFactsLanguage.FRENCH: frenchProductName @@ -111,7 +116,7 @@ void main() { lang: OpenFoodFactsLanguage.FRENCH, ); - Status frenchStatus = await OpenFoodAPIClient.saveProduct( + final Status frenchStatus = await OpenFoodAPIClient.saveProduct( TestConstants.TEST_USER, frenchProduct, ); @@ -119,7 +124,7 @@ void main() { expect(frenchStatus.statusVerbose, 'fields saved'); // save german product name - Product germanProduct = Product( + final Product germanProduct = Product( barcode: barcode, productNameInLanguages: { OpenFoodFactsLanguage.GERMAN: germanProductName @@ -129,7 +134,7 @@ void main() { lang: OpenFoodFactsLanguage.GERMAN, ); - Status germanStatus = await OpenFoodAPIClient.saveProduct( + final Status germanStatus = await OpenFoodAPIClient.saveProduct( TestConstants.TEST_USER, germanProduct, ); @@ -137,7 +142,7 @@ void main() { expect(germanStatus.statusVerbose, 'fields saved'); // get french fields for product - ProductQueryConfiguration frenchConfig = ProductQueryConfiguration( + final ProductQueryConfiguration frenchConfig = ProductQueryConfiguration( barcode, language: OpenFoodFactsLanguage.FRENCH, fields: [ @@ -145,14 +150,14 @@ void main() { ProductField.BRANDS, ProductField.QUANTITY ]); - var frenchResult = await OpenFoodAPIClient.getProduct( + final frenchResult = await OpenFoodAPIClient.getProduct( frenchConfig, ); expect(frenchResult.product, isNotNull); expect(frenchResult.product!.productName, frenchProductName); // get german fields for product - ProductQueryConfiguration germanConfig = ProductQueryConfiguration( + final ProductQueryConfiguration germanConfig = ProductQueryConfiguration( barcode, language: OpenFoodFactsLanguage.GERMAN, fields: [ @@ -160,7 +165,7 @@ void main() { ProductField.BRANDS, ProductField.QUANTITY ]); - var germanResult = await OpenFoodAPIClient.getProduct( + final germanResult = await OpenFoodAPIClient.getProduct( germanConfig, ); @@ -168,7 +173,7 @@ void main() { expect(germanResult.product!.productName, germanProductName); // get preferably French, then German fields for product - ProductQueryConfiguration frenchGermanConfig = + final ProductQueryConfiguration frenchGermanConfig = ProductQueryConfiguration(barcode, languages: [ OpenFoodFactsLanguage.FRENCH, OpenFoodFactsLanguage.GERMAN, @@ -177,13 +182,17 @@ void main() { ProductField.BRANDS, ProductField.QUANTITY ]); - var frenchGermanResult = await OpenFoodAPIClient.getProduct( + final frenchGermanResult = await OpenFoodAPIClient.getProduct( frenchGermanConfig, ); expect(frenchGermanResult.product, isNotNull); expect(frenchGermanResult.product!.productName, frenchProductName); - }); + }, + timeout: Timeout( + // this guy is rather slow + Duration(seconds: 90), + )); test('add new product test 2', () async { Product product = Product(