Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 995 - support of the new "product type" filter for "get product" #1004

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/openfoodfacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export 'src/model/per_size.dart';
export 'src/model/product.dart';
export 'src/model/product_freshness.dart';
export 'src/model/product_type.dart';
export 'src/model/product_type_filter.dart';
export 'src/model/product_image.dart';
export 'src/model/product_packaging.dart';
export 'src/model/product_result_field_answer.dart';
Expand Down
3 changes: 2 additions & 1 deletion lib/src/model/old_product_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ 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
// TODO: deprecated from 2024-11-28; remove when old enough
@Deprecated('Use getProductV3 and ProductResultV3 instead')
@JsonSerializable()
class OldProductResult extends JsonObject {
final int? status;
Expand Down
15 changes: 15 additions & 0 deletions lib/src/model/product_type_filter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'off_tagged.dart';
import 'product_type.dart';

/// Filter on product type for API get product queries.
class ProductTypeFilter implements OffTagged {
const ProductTypeFilter._(this.offTag);

ProductTypeFilter(final ProductType productType)
: offTag = productType.offTag;

static const all = ProductTypeFilter._('all');
Copy link
Contributor

@g123k g123k Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you've missed the type here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Strange the lint check didn't send a warning.
I'll fix that.


@override
final String offTag;
}
3 changes: 2 additions & 1 deletion lib/src/open_food_api_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ class OpenFoodAPIClient {
/// 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
// TODO: deprecated from 2024-11-28; remove when old enough
@Deprecated('Use getProductV3 and ProductResultV3 instead')
static Future<OldProductResult> getOldProduct(
final ProductQueryConfiguration configuration, {
final User? user,
Expand Down
18 changes: 16 additions & 2 deletions lib/src/utils/product_query_configurations.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:http/http.dart';

import '../model/product_type_filter.dart';
import '../model/user.dart';
import 'abstract_query_configuration.dart';
import 'http_helper.dart';
Expand Down Expand Up @@ -27,6 +28,9 @@ class ProductQueryConfiguration extends AbstractQueryConfiguration {
/// The API version
final ProductQueryVersion version;

/// Filter on a specific server.
final ProductTypeFilter? productTypeFilter;

/// See [AbstractQueryConfiguration.languages] for
/// parameter's description.
ProductQueryConfiguration(
Expand All @@ -36,8 +40,18 @@ class ProductQueryConfiguration extends AbstractQueryConfiguration {
super.languages,
super.country,
super.fields,
this.productTypeFilter,
});

@override
Map<String, String> getParametersMap() {
final Map<String, String> result = super.getParametersMap();
if (productTypeFilter != null) {
result['product_type'] = productTypeFilter!.offTag;
}
return result;
}

/// If the provided [ProductQueryVersion] matches the API V3 requirements
bool matchesV3() => version.matchesV3();

Expand All @@ -49,8 +63,8 @@ class ProductQueryConfiguration extends AbstractQueryConfiguration {
final User? user,
final UriProductHelper uriHelper,
) async {
if (version == ProductQueryVersion.v3) {
return await HttpHelper().doGetRequest(
if (matchesV3()) {
return HttpHelper().doGetRequest(
uriHelper.getUri(
path: getUriPath(),
queryParameters: getParametersMap(),
Expand Down
132 changes: 81 additions & 51 deletions test/api_not_food_get_product_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,60 +18,90 @@ void main() {
ProductType.beauty: '3600551054476',
ProductType.product: '7898927451035',
ProductType.petFood: '3564700266809',
ProductType.food: '7300400481588',
};

group('$OpenFoodAPIClient get not food products', () {
Future<void> findProduct(
final String barcode,
final ProductType expectedProductType,
final ProductType serverProductType,
) async {
final ProductQueryConfiguration configurations =
ProductQueryConfiguration(
barcode,
language: OpenFoodFactsLanguage.ENGLISH,
fields: [
ProductField.BARCODE,
ProductField.PRODUCT_TYPE,
],
version: ProductQueryVersion(2),
);
await getProductTooManyRequestsManager.waitIfNeeded();
final OldProductResult result = await OpenFoodAPIClient.getOldProduct(
configurations,
uriHelper: UriProductHelper(
domain: domains[serverProductType]!,
),
);
if (expectedProductType == serverProductType) {
expect(result.status, 1);
expect(result.barcode, barcode);
expect(result.product, isNotNull);
expect(result.product!.barcode, barcode);
expect(result.product!.productType, expectedProductType);
} else {
expect(result.status, 0);
expect(result.barcode, barcode);
expect(result.product, isNull);
}
Future<void> findProduct(
final String barcode,
final ProductType expectedProductType,
final ProductType serverProductType,
final ProductTypeFilter? productTypeFilter,
) async {
final ProductQueryConfiguration configurations = ProductQueryConfiguration(
barcode,
language: OpenFoodFactsLanguage.ENGLISH,
fields: [
ProductField.BARCODE,
ProductField.PRODUCT_TYPE,
],
version: ProductQueryVersion.v3,
productTypeFilter: productTypeFilter,
);
await getProductTooManyRequestsManager.waitIfNeeded();
final bool shouldSucceed = expectedProductType == serverProductType ||
productTypeFilter == ProductTypeFilter.all ||
productTypeFilter?.offTag == expectedProductType.offTag;
final ProductResultV3 result = await OpenFoodAPIClient.getProductV3(
configurations,
uriHelper: UriProductHelper(
domain: domains[serverProductType]!,
),
);
if (shouldSucceed) {
expect(result.status, ProductResultV3.statusSuccess);
expect(result.barcode, barcode);
expect(result.product, isNotNull);
expect(result.product!.barcode, barcode);
expect(result.product!.productType, expectedProductType);
} else {
expect(result.status, ProductResultV3.statusFailure);
expect(result.barcode, barcode);
expect(result.product, isNull);
}
}

test('get OxF product', () async {
for (MapEntry<ProductType, String> item in barcodes.entries) {
final ProductType productType = item.key;
final String barcode = item.value;
for (final ProductType serverProductType in ProductType.values) {
await findProduct(
barcode,
productType,
serverProductType,
);
}
Future<void> checkProduct(
final ProductTypeFilter? filter,
) async {
for (MapEntry<ProductType, String> item in barcodes.entries) {
final ProductType productType = item.key;
final String barcode = item.value;
for (final ProductType serverProductType in domains.keys) {
await findProduct(
barcode,
productType,
serverProductType,
filter,
);
}
});
},
timeout: Timeout(
// some tests can be slow here
Duration(seconds: 300),
));
}
}

group(
'$OpenFoodAPIClient get not food products v3',
() {
test(
'get OxF product without filter',
() async => checkProduct(null),
);

test(
'get OxF product with ALL filter',
() async => checkProduct(ProductTypeFilter.all),
);

test(
'get OxF product with specific filter',
() async {
for (final ProductType productType in ProductType.values) {
await checkProduct(ProductTypeFilter(productType));
}
},
);
},
timeout: Timeout(
// some tests can be slow here
Duration(seconds: 300),
),
);
}