Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
monsieurtanuki committed Dec 4, 2021
1 parent 0bfef3f commit 85169f0
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 17 deletions.
26 changes: 26 additions & 0 deletions lib/openfoodfacts.dart
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion lib/utils/CountryHelper.dart
Expand Up @@ -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',
Expand Down
34 changes: 34 additions & 0 deletions 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';
Expand Down Expand Up @@ -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.'),
);
}
}
57 changes: 57 additions & 0 deletions test/api_getProduct_test.dart
Expand Up @@ -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';
Expand Down Expand Up @@ -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',
);
});
}
41 changes: 25 additions & 16 deletions test/api_saveProduct_test.dart
Expand Up @@ -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
Expand All @@ -111,15 +116,15 @@ void main() {
lang: OpenFoodFactsLanguage.FRENCH,
);

Status frenchStatus = await OpenFoodAPIClient.saveProduct(
final Status frenchStatus = await OpenFoodAPIClient.saveProduct(
TestConstants.TEST_USER,
frenchProduct,
);
expect(frenchStatus.status, 1);
expect(frenchStatus.statusVerbose, 'fields saved');

// save german product name
Product germanProduct = Product(
final Product germanProduct = Product(
barcode: barcode,
productNameInLanguages: {
OpenFoodFactsLanguage.GERMAN: germanProductName
Expand All @@ -129,46 +134,46 @@ void main() {
lang: OpenFoodFactsLanguage.GERMAN,
);

Status germanStatus = await OpenFoodAPIClient.saveProduct(
final Status germanStatus = await OpenFoodAPIClient.saveProduct(
TestConstants.TEST_USER,
germanProduct,
);
expect(germanStatus.status, 1);
expect(germanStatus.statusVerbose, 'fields saved');

// get french fields for product
ProductQueryConfiguration frenchConfig = ProductQueryConfiguration(
final ProductQueryConfiguration frenchConfig = ProductQueryConfiguration(
barcode,
language: OpenFoodFactsLanguage.FRENCH,
fields: [
ProductField.NAME,
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: [
ProductField.NAME,
ProductField.BRANDS,
ProductField.QUANTITY
]);
var germanResult = await OpenFoodAPIClient.getProduct(
final germanResult = await OpenFoodAPIClient.getProduct(
germanConfig,
);

expect(germanResult.product, isNotNull);
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,
Expand All @@ -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(
Expand Down

0 comments on commit 85169f0

Please sign in to comment.