diff --git a/lib/src/model/parameter/page_size.dart b/lib/src/model/parameter/page_size.dart index d9afa4d6ab..59586e0fc0 100644 --- a/lib/src/model/parameter/page_size.dart +++ b/lib/src/model/parameter/page_size.dart @@ -1,6 +1,8 @@ import '../../interface/parameter.dart'; /// "Page size" search API parameter +/// +/// Typically defaults to 50 (used to be 24). Max value seems to be 100. class PageSize extends Parameter { @override String getName() => 'page_size'; diff --git a/lib/src/open_prices_api_client.dart b/lib/src/open_prices_api_client.dart index cbc99fdb26..d53c3e90a4 100644 --- a/lib/src/open_prices_api_client.dart +++ b/lib/src/open_prices_api_client.dart @@ -41,15 +41,28 @@ class OpenPricesAPIClient { static String _getHost(final UriProductHelper uriHelper) => uriHelper.getHost(_subdomain); + static Uri getUri({ + required final String path, + final Map? queryParameters, + final UriProductHelper uriHelper = uriHelperFoodProd, + final bool? addUserAgentParameters, + }) => + uriHelper.getUri( + path: path, + queryParameters: queryParameters, + forcedHost: _getHost(uriHelper), + addUserAgentParameters: addUserAgentParameters, + ); + static Future> getPrices( final GetPricesParameters parameters, { final UriProductHelper uriHelper = uriHelperFoodProd, final String? bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/prices', queryParameters: parameters.getQueryParameters(), - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -74,9 +87,9 @@ class OpenPricesAPIClient { required final LocationOSMType locationOSMType, final UriProductHelper uriHelper = uriHelperFoodProd, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/locations/osm/${locationOSMType.offTag}/$locationOSMId', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -98,10 +111,10 @@ class OpenPricesAPIClient { final UriProductHelper uriHelper = uriHelperFoodProd, final String? bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/locations', queryParameters: parameters.getQueryParameters(), - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -125,9 +138,9 @@ class OpenPricesAPIClient { final int locationId, { final UriProductHelper uriHelper = uriHelperFoodProd, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/locations/$locationId', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -148,9 +161,9 @@ class OpenPricesAPIClient { final int productId, { final UriProductHelper uriHelper = uriHelperFoodProd, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/products/$productId', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -173,9 +186,9 @@ class OpenPricesAPIClient { final String productCode, { final UriProductHelper uriHelper = uriHelperFoodProd, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/products/code/$productCode', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -201,9 +214,9 @@ class OpenPricesAPIClient { static Future> getStatus({ final UriProductHelper uriHelper = uriHelperFoodProd, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/status', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -234,9 +247,9 @@ class OpenPricesAPIClient { final bool setCookie = false, final UriProductHelper uriHelper = uriHelperFoodProd, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/auth${setCookie ? '?set_cookie=1' : ''}', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await post( uri, @@ -261,9 +274,9 @@ class OpenPricesAPIClient { final UriProductHelper uriHelper = uriHelperFoodProd, required final String bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/session', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -288,9 +301,9 @@ class OpenPricesAPIClient { final UriProductHelper uriHelper = uriHelperFoodProd, required final String bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/session', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doDeleteRequest( uri, @@ -308,9 +321,9 @@ class OpenPricesAPIClient { required final String bearerToken, final UriProductHelper uriHelper = uriHelperFoodProd, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/prices', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final StringBuffer body = StringBuffer(); body.write('{'); @@ -372,9 +385,9 @@ class OpenPricesAPIClient { final UriProductHelper uriHelper = uriHelperFoodProd, required final String bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/prices/$priceId', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doDeleteRequest( uri, @@ -393,10 +406,10 @@ class OpenPricesAPIClient { final UriProductHelper uriHelper = uriHelperFoodProd, required final String bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/proofs', queryParameters: parameters.getQueryParameters(), - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -423,9 +436,9 @@ class OpenPricesAPIClient { required final String bearerToken, final UriProductHelper uriHelper = uriHelperFoodProd, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/proofs/upload', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final http.MultipartRequest request = http.MultipartRequest('POST', uri); @@ -473,9 +486,9 @@ class OpenPricesAPIClient { final UriProductHelper uriHelper = uriHelperFoodProd, required final String bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/proofs/$proofId', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, @@ -501,9 +514,9 @@ class OpenPricesAPIClient { final UriProductHelper uriHelper = uriHelperFoodProd, required final String bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/proofs/$proofId', - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doDeleteRequest( uri, @@ -521,10 +534,10 @@ class OpenPricesAPIClient { final UriProductHelper uriHelper = uriHelperFoodProd, final String? bearerToken, }) async { - final Uri uri = uriHelper.getUri( + final Uri uri = getUri( path: '/api/v1/users', queryParameters: parameters.getQueryParameters(), - forcedHost: _getHost(uriHelper), + uriHelper: uriHelper, ); final Response response = await HttpHelper().doGetRequest( uri, diff --git a/lib/src/prices/currency.dart b/lib/src/prices/currency.dart index 2f8b8b415a..ef9b757340 100644 --- a/lib/src/prices/currency.dart +++ b/lib/src/prices/currency.dart @@ -927,4 +927,16 @@ enum Currency { /// Not really attached to a specific country at all. final bool noCountry; + + static Currency? fromName(final String? name) { + if (name == null) { + return null; + } + for (final Currency currency in values) { + if (currency.name == name) { + return currency; + } + } + return null; + } } diff --git a/lib/src/prices/price.dart b/lib/src/prices/price.dart index 4c1a320021..af37888dc9 100644 --- a/lib/src/prices/price.dart +++ b/lib/src/prices/price.dart @@ -106,31 +106,35 @@ class Price extends JsonObject { @JsonKey(name: 'proof_id') int? proofId; + /// Price ID. Read-only. @JsonKey() late int id; + /// Product ID. Read-only. @JsonKey(name: 'product_id') int? productId; + /// Location ID. Read-only. @JsonKey(name: 'location_id') int? locationId; - /// Proof. + /// Proof. Read-only. @JsonKey() Proof? proof; - /// Location. + /// Location. Read-only. @JsonKey() Location? location; - /// Product. + /// Product. Read-only. @JsonKey() PriceProduct? product; - /// Owner. + /// Owner. Read-only. @JsonKey() late String owner; + /// Creation timestamp. Read-only. @JsonKey(fromJson: JsonHelper.stringTimestampToDate) late DateTime created; diff --git a/lib/src/prices/proof.dart b/lib/src/prices/proof.dart index 214ece5548..5ad0d62271 100644 --- a/lib/src/prices/proof.dart +++ b/lib/src/prices/proof.dart @@ -2,7 +2,9 @@ import 'package:json_annotation/json_annotation.dart'; import 'proof_type.dart'; import '../interface/json_object.dart'; +import '../open_prices_api_client.dart'; import '../utils/json_helper.dart'; +import '../utils/uri_helper.dart'; part 'proof.g.dart'; @@ -11,25 +13,31 @@ part 'proof.g.dart'; /// cf. `ProofFull` in https://prices.openfoodfacts.org/api/docs @JsonSerializable() class Proof extends JsonObject { + /// Proof ID. Read-only. @JsonKey() late int id; + /// Image file path. Read-only. @JsonKey(name: 'file_path') String? filePath; + /// Mime type. Read-only. @JsonKey() late String mimetype; + /// Proof type. Read-only. @JsonKey() ProofType? type; - /// Number of prices for this proof. + /// Number of prices for this proof. Read-only. @JsonKey(name: 'price_count') late int priceCount; + /// Owner. Read-only. @JsonKey() late String owner; + /// Creation timestamp. Read-only. @JsonKey(fromJson: JsonHelper.stringTimestampToDate) late DateTime created; @@ -39,4 +47,14 @@ class Proof extends JsonObject { @override Map toJson() => _$ProofToJson(this); + + /// Returns the URL of the proof image. + Uri? getFileUrl({required final UriProductHelper uriProductHelper}) => + filePath == null + ? null + : OpenPricesAPIClient.getUri( + path: 'img/$filePath', + uriHelper: uriProductHelper, + addUserAgentParameters: false, + ); } diff --git a/lib/src/prices/proof_type.dart b/lib/src/prices/proof_type.dart index 255b776d83..061965527e 100644 --- a/lib/src/prices/proof_type.dart +++ b/lib/src/prices/proof_type.dart @@ -20,4 +20,8 @@ enum ProofType implements OffTagged { @override final String offTag; + + /// Returns the first [ProofType] that matches the [offTag]. + static ProofType? fromOffTag(final String? offTag) => + OffTagged.fromOffTag(offTag, ProofType.values) as ProofType?; } diff --git a/test/api_get_taxonomy_origins_server_test.dart b/test/api_get_taxonomy_origins_server_test.dart index 0096c9684f..ba97959290 100644 --- a/test/api_get_taxonomy_origins_server_test.dart +++ b/test/api_get_taxonomy_origins_server_test.dart @@ -1,5 +1,4 @@ import 'package:openfoodfacts/openfoodfacts.dart'; -import 'package:test/expect.dart'; import 'package:test/test.dart'; import 'test_constants.dart'; diff --git a/test/api_get_user_products_test.dart b/test/api_get_user_products_test.dart index ce93fd295c..a1803d0403 100644 --- a/test/api_get_user_products_test.dart +++ b/test/api_get_user_products_test.dart @@ -10,7 +10,7 @@ void main() { group('$OpenFoodAPIClient get user products', () { const String userId = 'monsieurtanuki'; // should be big enough to get everything on page1 - const int pageSize = 1000; + const int pageSize = 100; final String toBeCompletedTag = ProductState.COMPLETED.toBeCompletedTag; Future getCount( diff --git a/test/api_prices_test.dart b/test/api_prices_test.dart index d180a53e5a..79a11ed074 100644 --- a/test/api_prices_test.dart +++ b/test/api_prices_test.dart @@ -1,3 +1,5 @@ +import 'package:http/http.dart' as http; + import 'package:http_parser/http_parser.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:test/test.dart'; @@ -8,6 +10,7 @@ void main() { const UriProductHelper uriHelper = uriHelperFoodTest; const User user = TestConstants.TEST_USER; const String invalidBearerToken = 'invalid bearer token'; + const int HTTP_OK = 200; group('$OpenPricesAPIClient default', () { test('getStatus', () async { @@ -610,6 +613,11 @@ void main() { expect(maybeProof.value.mimetype, proof.mimetype); expect(maybeProof.value.created, proof.created); expect(maybeProof.value.filePath, proof.filePath); + if (proof.filePath != null) { + final Uri uri = proof.getFileUrl(uriProductHelper: uriHelper)!; + final http.Response response = await http.get(uri); + expect(response.statusCode, HTTP_OK); + } }); test('upload', () async { diff --git a/test/api_search_products_test.dart b/test/api_search_products_test.dart index 5f333f8295..e2edd196ec 100644 --- a/test/api_search_products_test.dart +++ b/test/api_search_products_test.dart @@ -9,6 +9,7 @@ void main() { OpenFoodAPIConfiguration.userAgent = TestConstants.TEST_USER_AGENT; OpenFoodAPIConfiguration.globalUser = TestConstants.PROD_USER; const ProductQueryVersion version = ProductQueryVersion.v3; + const int defaultPageSize = 50; // additional parameter for faster response time const Parameter optimParameter = SearchTerms(terms: ['pizza']); @@ -699,7 +700,7 @@ void main() { ); expect(result.page, 1); - expect(result.pageSize, 24); + expect(result.pageSize, defaultPageSize); expect(result.count, BARCODES.length - 1); expect(result.products, isNotNull); expect(result.products!.length, BARCODES.length - 1); @@ -780,9 +781,9 @@ void main() { ); expect(result.page, 3); - expect(result.pageSize, 24); + expect(result.pageSize, defaultPageSize); expect(result.products, isNotNull); - expect(result.products!.length, 24); + expect(result.products!.length, defaultPageSize); expect(result.products![0].runtimeType, Product); expect(result.count, greaterThan(1500)); }, skip: 'Temporarily not working (server issue)'); @@ -809,14 +810,13 @@ void main() { ); expect(result.page, 1); - expect(result.pageSize, 24); + expect(result.pageSize, defaultPageSize); expect(result.count, BARCODES.length - 1); expect(result.products, isNotNull); expect(result.products!.length, BARCODES.length - 1); }); test('nova filter', () async { - const int pageSize = 24; // Approximated min counts for FRANCE / Carrefour // There were too many results for FRANCE, and that made the server crash. // That's why we add a filter on STORES. @@ -841,7 +841,7 @@ void main() { tagFilterType: TagFilterType.STORES, tagName: 'Carrefour', ), - PageSize(size: pageSize), + PageSize(size: defaultPageSize), ], fields: [ProductField.BARCODE, ProductField.NOVA_GROUP], language: OpenFoodFactsLanguage.FRENCH, @@ -851,14 +851,14 @@ void main() { ); expect(result.page, 1); - expect(result.pageSize, pageSize); + expect(result.pageSize, defaultPageSize); expect( result.count, greaterThanOrEqualTo(novaMinCounts[novaGroup]!), reason: 'Not enough values for nova group $novaGroup', ); expect(result.products, isNotNull); - expect(result.products!.length, pageSize); + expect(result.products!.length, defaultPageSize); for (final Product product in result.products!) { expect(product.novaGroup, novaGroup); } diff --git a/test/api_suggestion_manager_test.dart b/test/api_suggestion_manager_test.dart index 0ca2681d84..9d58bf063c 100644 --- a/test/api_suggestion_manager_test.dart +++ b/test/api_suggestion_manager_test.dart @@ -64,7 +64,7 @@ void main() { } group('$OpenFoodAPIClient suggestion manager', () { - Future testToto(final Autocompleter autocompleter) async { + Future testSpeed(final Autocompleter autocompleter) async { final AutocompleteManager manager = AutocompleteManager(autocompleter); final List countries1 = await manager.getSuggestions(input1); final List countries2 = await manager.getSuggestions(input2); @@ -115,22 +115,20 @@ void main() { test( 'countries as TagType', - () async => testToto( + () async => testSpeed( TagTypeAutocompleter( tagType: tagType, language: language, - uriHelper: uriHelperFoodTest, ), ), ); test( 'countries as TaxonomyName', - () async => testToto( + () async => testSpeed( TaxonomyNameAutocompleter( taxonomyNames: [TaxonomyName.country], language: language, - uriHelper: uriHelperFoodTest, ), ), );