From 632505a99bb9f7838b1b3007d61dd6b68967a469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Fri, 25 Oct 2024 18:21:06 +0200 Subject: [PATCH 1/9] refactor: unified paths (start) --- cgi/product_multilingual.pl | 6 +- lib/ProductOpener/Config_off.pm | 8 - lib/ProductOpener/Paths.pm | 45 ---- lib/ProductOpener/Products.pm | 226 +++++++++++------- po/common/common.pot | 24 ++ po/common/en.po | 26 +- .../product_edit_form_display.tt.html | 8 + 7 files changed, 193 insertions(+), 150 deletions(-) diff --git a/cgi/product_multilingual.pl b/cgi/product_multilingual.pl index 9c8b9f80281db..250b1a35c6de4 100755 --- a/cgi/product_multilingual.pl +++ b/cgi/product_multilingual.pl @@ -848,14 +848,12 @@ ($product_ref, $field, $language, $request_ref) my $label_new_code = $Lang{new_code}{$lc}; - # 26/01/2017 - disallow barcode changes until we fix bug #677 - if ($User{moderator}) { - } - $template_data_ref_display->{org_id} = $Org_id; $template_data_ref_display->{label_new_code} = $label_new_code; $template_data_ref_display->{owner_id} = $Owner_id; + $template_data_ref_display->{product_types} = \@product_types; + # obsolete products: restrict to admin on public site # authorize owners on producers platform if ($User{moderator} or $Owner_id) { diff --git a/lib/ProductOpener/Config_off.pm b/lib/ProductOpener/Config_off.pm index 2e0fc1dc5e946..b97d1f29a7997 100644 --- a/lib/ProductOpener/Config_off.pm +++ b/lib/ProductOpener/Config_off.pm @@ -1099,30 +1099,22 @@ $options{current_server} = "off"; $options{other_servers} = { obf => { name => "Open Beauty Facts", - data_root => "/srv/obf", - www_root => "/srv/obf/html", mongodb => "obf", domain => "openbeautyfacts.org", }, off => { name => "Open Food Facts", - data_root => "/srv/off", - www_root => "/srv/off/html", mongodb => "off", domain => "openfoodfacts.org", }, opf => { name => "Open Products Facts", - data_root => "/srv/opf", - www_root => "/srv/opf/html", mongodb => "opf", domain => "openproductsfacts.org", }, opff => { prefix => "opff", name => "Open Pet Food Facts", - data_root => "/srv/opff", - www_root => "/srv/opff/html", mongodb => "opff", domain => "openpetfoodfacts.org", } diff --git a/lib/ProductOpener/Paths.pm b/lib/ProductOpener/Paths.pm index dc1b4fd0da056..579978334c31d 100644 --- a/lib/ProductOpener/Paths.pm +++ b/lib/ProductOpener/Paths.pm @@ -290,44 +290,8 @@ my @PRO_ONLY_PATHS = qw(SFTP_HOME); =head1 FUNCTIONS -=head2 products_dir($server_name) - -products directory for a foreign server - -=head3 Arguments - -=head4 $server_name - off/obf/opf/opff… - -=head3 Return - -String of path to base directory containing products sto - =cut -sub products_dir ($server_name) { - my $server_data_root = $options{other_servers}{$server_name}{data_root}; - return "$server_data_root/products"; -} - -=head2 products_images_dir($server_name) - -products images directory for a foreign server - -=head3 Arguments - -=head4 $server_name - off/obf/opf/opff… - -=head3 Return - -String of path to base directory containing products images - -=cut - -sub products_images_dir ($server_name) { - my $server_www_root = $options{other_servers}{$server_name}{www_root}; - return "$server_www_root/images/products"; -} - sub _source_dir() { # compute $src_root my $src_root = abs_path(dirname(__FILE__) . '/../..'); @@ -384,15 +348,6 @@ sub base_paths() { my %paths = (%BASE_DIRS); if (!$server_options{producers_platform}) { # on non pro instances, - # also add foreign projects dirs for products migrations - my $servers_options = $options{other_servers}; - foreach my $server_name (keys %{$servers_options}) { - if ($server_name eq $options{current_server}) { - next; - } - $paths{uc($server_name) . "_PRODUCTS_DIR"} = products_dir($server_name); - $paths{uc($server_name) . "_PRODUCTS_IMAGES_DIR"} = products_images_dir($server_name); - } # remove some paths foreach my $path (@PRO_ONLY_PATHS) { delete $paths{$path}; diff --git a/lib/ProductOpener/Products.pm b/lib/ProductOpener/Products.pm index 8f1781309af76..fef07f9e47a68 100644 --- a/lib/ProductOpener/Products.pm +++ b/lib/ProductOpener/Products.pm @@ -65,6 +65,10 @@ use Exporter qw< import >; BEGIN { use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); @EXPORT_OK = qw( + @product_types + %product_types_flavors + %flavors_product_types + &is_valid_code &normalize_code &normalize_code_with_gs1_ai @@ -170,6 +174,20 @@ use Scalar::Util qw(looks_like_number); use GS1::SyntaxEngine::FFI::GS1Encoder; +=head2 Available product types and flavors + +=cut + +@product_types = qw(food petfood beauty product); +%product_types_flavors = ( + food => "off", + petfood => "oppf", + beauty => "obf", + product => "opf" +); + +%flavors_product_types = reverse %product_types_flavors; + =head1 FUNCTIONS =head2 make_sure_numbers_are_stored_as_numbers ( PRODUCT_REF ) @@ -1024,52 +1042,68 @@ sub retrieve_product_rev ($product_id, $rev, $include_deleted = 0) { sub change_product_server_or_code ($product_ref, $new_code, $errors_ref) { - # Currently only called by admins, can cause issues because of bug #677 + # Currently only called by admins and moderators my $code = $product_ref->{code}; - my $new_server = ""; - my $new_data_root = $data_root; if ($new_code =~ /^([a-z]+)$/) { - $new_server = $1; - if ( (defined $options{other_servers}) - and (defined $options{other_servers}{$new_server}) - and ($options{other_servers}{$new_server}{data_root} ne $data_root)) - { - $new_code = $code; - $new_data_root = $options{other_servers}{$new_server}{data_root}; + my $new_server = $1; + if (defined $flavors_product_types{$new_server}) { + change_product_type($product_ref, $flavors_product_types{$new_server}, $errors_ref); } } - $new_code = normalize_code($new_code); - if (not is_valid_code($new_code)) { - push @$errors_ref, lang("invalid_barcode"); - } else { - # check that the new code is available - if (-e "$new_data_root/products/" . product_path_from_id($new_code)) { - push @{$errors_ref}, lang("error_new_code_already_exists"); - $log->warn( - "cannot change product code, because the new code already exists", - {code => $code, new_code => $new_code, new_server => $new_server} - ) if $log->is_warn(); + $new_code = normalize_code($new_code); + if (not is_valid_code($new_code)) { + push @$errors_ref, lang("invalid_barcode"); } else { - $product_ref->{old_code} = $code; - $code = $new_code; - $product_ref->{code} = $code; - if ($new_server ne '') { - $product_ref->{new_server} = $new_server; - } - $log->info("changing code", - {old_code => $product_ref->{old_code}, code => $code, new_server => $new_server}) - if $log->is_info(); + # check that the new code is available + if (-e "$data_root/products/" . product_path_from_id($new_code) . "/product.sto") { + push @{$errors_ref}, lang("error_new_code_already_exists"); + $log->warn("cannot change product code, because the new code already exists", + {code => $code, new_code => $new_code}) + if $log->is_warn(); + } + else { + $product_ref->{old_code} = $code; + $code = $new_code; + $product_ref->{code} = $code; + $log->info("changing code", {old_code => $product_ref->{old_code}, code => $code}) + if $log->is_info(); + } } } return; } +sub change_product_type ($product_ref, $new_product_type, $errors_ref) { + + # Currently only called by admins and moderators + + my $product_type = $product_ref->{product_type}; + + # Return if the product type is already the new product type, or if the new product type is not defined + if ((not defined $new_product_type) or ($product_type eq $new_product_type)) { + return; + } + + if (not defined $product_types_flavors{$new_product_type}) { + push @$errors_ref, lang("error_invalid_product_type"); + } + else { + $product_ref->{old_product_type} = $product_type; + $product_ref->{product_type} = $new_product_type; + $log->info("changing product type", + {old_product_type => $product_ref->{old_product_type}, product_type => $new_product_type}) + if $log->is_info(); + } + + return; +} + =head2 compute_sort_keys ( $product_ref ) Compute sort keys that are stored in the MongoDB database and used to order results of queries. @@ -1163,22 +1197,53 @@ sub store_product ($user_id, $product_ref, $comment) { } ) if $log->is_debug(); - # In case we need to move a product from OFF to OBF etc. - # the "new_server" value will be set to off, obf etc. - # we first move the existing files (product and images) - # and then store the product with a comment. + my $delete_from_previous_products_collection = 0; # if we have a "server" value (e.g. from an import), # we save the product on the corresponding server but we don't need to move an existing product + if (defined $product_ref->{server}) { + my $new_server = $product_ref->{server}; + # Update the product_type from the server + if (defined $flavors_product_types{$new_server}) { + my $errors_ref = {}; + change_product_type($product_ref, $flavors_product_types{$new_server}, $errors_ref); + } + delete $product_ref->{server}; + } - my $new_data_root = $data_root; - my $new_www_root = $www_root; + # In case we need to move a product from OFF to OBF etc. + # we will have a old_product_type field + + # Get the previous server and collection for the product + my $previous_server + = $product_types_flavors{$product_ref->{old_product_type} + || $product_ref->{product_type} + || $options{product_type}}; # We use the was_obsolete flag so that we can remove the product from its old collection # (either products or products_obsolete) if its obsolete status has changed - my $previous_products_collection = get_products_collection({obsolete => $product_ref->{was_obsolete}}); - my $new_products_collection = get_products_collection({obsolete => $product_ref->{obsolete}}); - my $delete_from_previous_products_collection = 0; + my $previous_products_collection = get_products_collection( + {database => $options{other_servers}{$previous_server}{mongodb}, obsolete => $product_ref->{was_obsolete}}); + + # Change of product type + if (defined $product_ref->{old_product_type}) { + $log->info("changing product type", + {old_product_type => $product_ref->{old_product_type}, product_type => $product_ref->{product_type}}) + if $log->is_info(); + $delete_from_previous_products_collection = 1; + delete $product_ref->{old_product_type}; + } + + # Get the server and collection for the product that we will write + my $new_server = $product_types_flavors{$product_ref->{product_type} || $options{product_type}}; + my $new_products_collection = get_products_collection( + {database => $options{other_servers}{$new_server}{mongodb}, obsolete => $product_ref->{obsolete}}); + + if ($previous_server ne $new_server) { + $log->info("changing server", {old_server => $previous_server, new_server => $new_server, code => $code}) + if $log->is_info(); + $delete_from_previous_products_collection = 1; + } # the obsolete (and was_obsolete) field is either undef or an empty string, or contains "on" if ( ($product_ref->{was_obsolete} and not $product_ref->{obsolete}) @@ -1204,36 +1269,17 @@ sub store_product ($user_id, $product_ref, $comment) { $action = "unarchived"; } } - delete $product_ref->{was_obsolete}; - if ( (defined $product_ref->{server}) - and (defined $options{other_servers}) - and (defined $options{other_servers}{$product_ref->{server}})) - { - my $server = $product_ref->{server}; - $new_data_root = $options{other_servers}{$server}{data_root}; - $new_www_root = $options{other_servers}{$server}{www_root}; - $new_products_collection = get_products_collection( - {database => $options{other_servers}{$server}{mongodb}, obsolete => $product_ref->{obsolete}}); - } + delete $product_ref->{was_obsolete}; + # Change of barcode if (defined $product_ref->{old_code}) { my $old_code = $product_ref->{old_code}; my $old_product_id = product_id_for_owner($Owner_id, $old_code); my $old_path = product_path_from_id($old_product_id); - if (defined $product_ref->{new_server}) { - my $new_server = $product_ref->{new_server}; - $new_data_root = $options{other_servers}{$new_server}{data_root}; - $new_www_root = $options{other_servers}{$new_server}{www_root}; - $new_products_collection = get_products_collection( - {database => $options{other_servers}{$new_server}{mongodb}, obsolete => $product_ref->{obsolete}}); - $product_ref->{server} = $product_ref->{new_server}; - delete $product_ref->{new_server}; - } - - $log->info("moving product", {old_code => $old_code, code => $code, new_data_root => $new_data_root}) + $log->info("moving product", {old_code => $old_code, code => $code}) if $log->is_info(); # Move directory @@ -1247,15 +1293,15 @@ sub store_product ($user_id, $product_ref, $comment) { $log->debug("creating product directories", {path => $path, prefix_path => $prefix_path}) if $log->is_debug(); # Create the directories for the product - ensure_dir_created_or_die("$new_data_root/products/$prefix_path"); - ensure_dir_created_or_die("$new_www_root/images/products/$prefix_path"); + ensure_dir_created_or_die("$data_root/products/$prefix_path"); + ensure_dir_created_or_die("$www_root/images/products/$prefix_path"); # Check if we are updating the product in place: # the code changed, but it is the same path # this can happen if the path is already normalized, but the code is not # in that case we just want to update the code, and remove the old one from MongoDB # we don't need to move the directories - if ("$BASE_DIRS{PRODUCTS}/$old_path" eq "$new_data_root/products/$path") { + if ("$BASE_DIRS{PRODUCTS}/$old_path" eq "$data_root/products/$path") { $log->debug("updating product code in place", {old_code => $old_code, code => $code}) if $log->is_debug(); delete $product_ref->{old_code}; # remove the old product from the previous collection @@ -1269,8 +1315,8 @@ sub store_product ($user_id, $product_ref, $comment) { $product_ref->{_id} = $product_ref->{code} . ''; # treat id as string; } - if ( (!-e "$new_data_root/products/$path") - and (!-e "$new_www_root/images/products/$path")) + if ( (!-e "$data_root/products/$path") + and (!-e "$www_root/images/products/$path")) { # File::Copy move() is intended to move files, not # directories. It does work on directories if the @@ -1289,7 +1335,7 @@ sub store_product ($user_id, $product_ref, $comment) { $log->debug("moving product data", {source => "$BASE_DIRS{PRODUCTS}/$old_path", destination => "$BASE_DIRS{PRODUCTS}/$path"}) if $log->is_debug(); - dirmove("$BASE_DIRS{PRODUCTS}/$old_path", "$new_data_root/products/$path") + dirmove("$BASE_DIRS{PRODUCTS}/$old_path", "$data_root/products/$path") or $log->error( "could not move product data", { @@ -1303,15 +1349,15 @@ sub store_product ($user_id, $product_ref, $comment) { "moving product images", { source => "$BASE_DIRS{PRODUCTS_IMAGES}/$old_path", - destination => "$new_www_root/images/products/$path" + destination => "$www_root/images/products/$path" } ) if $log->is_debug(); - dirmove("$BASE_DIRS{PRODUCTS_IMAGES}/$old_path", "$new_www_root/images/products/$path") + dirmove("$BASE_DIRS{PRODUCTS_IMAGES}/$old_path", "$www_root/images/products/$path") or $log->error( "could not move product images", { source => "$BASE_DIRS{PRODUCTS_IMAGES}/$old_path", - destination => "$new_www_root/images/products/$path", + destination => "$www_root/images/products/$path", error => $! } ); @@ -1329,15 +1375,15 @@ sub store_product ($user_id, $product_ref, $comment) { } else { - (-e "$new_data_root/products/$path") + (-e "$data_root/products/$path") and $log->error("cannot move product data, because the destination already exists", {source => "$BASE_DIRS{PRODUCTS}/$old_path", destination => "$BASE_DIRS{PRODUCTS}/$path"}); - (-e "$new_www_root/products/$path") + (-e "$www_root/products/$path") and $log->error( "cannot move product images data, because the destination already exists", { source => "$BASE_DIRS{PRODUCTS_IMAGES}/$old_path", - destination => "$new_www_root/images/products/$path" + destination => "$www_root/images/products/$path" } ); } @@ -1347,12 +1393,12 @@ sub store_product ($user_id, $product_ref, $comment) { if ($rev < 1) { # Create the directories for the product - ensure_dir_created_or_die("$new_data_root/products/$path"); - ensure_dir_created_or_die("$new_www_root/images/products/$path"); + ensure_dir_created_or_die("$data_root/products/$path"); + ensure_dir_created_or_die("$www_root/images/products/$path"); } # Check lock and previous version - my $changes_ref = retrieve("$new_data_root/products/$path/changes.sto"); + my $changes_ref = retrieve("$data_root/products/$path/changes.sto"); if (not defined $changes_ref) { $changes_ref = []; } @@ -1431,7 +1477,7 @@ sub store_product ($user_id, $product_ref, $comment) { my $blame_ref = {}; - compute_product_history_and_completeness($new_data_root, $product_ref, $changes_ref, $blame_ref); + compute_product_history_and_completeness($product_ref, $changes_ref, $blame_ref); compute_data_sources($product_ref, $changes_ref); @@ -1473,7 +1519,7 @@ sub store_product ($user_id, $product_ref, $comment) { } # First store the product data in a .sto file on disk - store("$new_data_root/products/$path/$rev.sto", $product_ref); + store("$data_root/products/$path/$rev.sto", $product_ref); # Also store the product in MongoDB, unless it was marked as deleted if ($product_ref->{deleted}) { @@ -1489,16 +1535,16 @@ sub store_product ($user_id, $product_ref, $comment) { } # Update link - my $link = "$new_data_root/products/$path/product.sto"; + my $link = "$data_root/products/$path/product.sto"; if (-l $link) { unlink($link) or $log->error("could not unlink old product.sto", {link => $link, error => $!}); } symlink("$rev.sto", $link) or $log->error("could not symlink to new revision", - {source => "$new_data_root/products/$path/$rev.sto", link => $link, error => $!}); + {source => "$data_root/products/$path/$rev.sto", link => $link, error => $!}); - store("$new_data_root/products/$path/changes.sto", $changes_ref); + store("$data_root/products/$path/changes.sto", $changes_ref); log_change($product_ref, $change_ref); $log->debug("store_product - done", {code => $code, product_id => $product_id}) if $log->is_debug(); @@ -2163,7 +2209,7 @@ sub record_user_edit_type ($users_ref, $user_type, $user_id) { return; } -sub compute_product_history_and_completeness ($product_data_root, $current_product_ref, $changes_ref, $blame_ref) { +sub compute_product_history_and_completeness ($current_product_ref, $changes_ref, $blame_ref) { my $code = $current_product_ref->{code}; my $product_id = $current_product_ref->{_id}; @@ -2213,13 +2259,13 @@ sub compute_product_history_and_completeness ($product_data_root, $current_produ # Read all previous versions to see which fields have been added or edited my @fields = ( - 'code', 'lang', - 'product_name', 'generic_name', - @ProductOpener::Config::product_fields, @ProductOpener::Config::product_other_fields, - 'no_nutrition_data', 'nutrition_data_per', - 'nutrition_data_prepared_per', 'serving_size', - 'allergens', 'traces', - 'ingredients_text' + 'product_type', 'code', + 'lang', 'product_name', + 'generic_name', @ProductOpener::Config::product_fields, + @ProductOpener::Config::product_other_fields, 'no_nutrition_data', + 'nutrition_data_per', 'nutrition_data_prepared_per', + 'serving_size', 'allergens', + 'traces', 'ingredients_text' ); my %previous = (uploaded_images => {}, selected_images => {}, fields => {}, nutriments => {}); @@ -2247,7 +2293,7 @@ sub compute_product_history_and_completeness ($product_data_root, $current_produ if (not defined $rev) { $rev = $revs; # was not set before June 2012 } - my $product_ref = retrieve("$product_data_root/products/$path/$rev.sto"); + my $product_ref = retrieve("$data_root/products/$path/$rev.sto"); # if not found, we may be be updating the product, with the latest rev not set yet if ((not defined $product_ref) or ($rev == $current_product_ref->{rev})) { diff --git a/po/common/common.pot b/po/common/common.pot index b399fbb7f1549..cf021b373422f 100644 --- a/po/common/common.pot +++ b/po/common/common.pot @@ -7211,3 +7211,27 @@ msgstr "Poor repairability" msgctxt "attribute_repairability_index_france_bad_description_short" msgid "Bad repairability" msgstr "Bad repairability" + +msgctxt "product_type" +msgid "Product type" +msgstr "Product type" + +# Food product type +msgctxt "product_type_food" +msgid "Food" +msgstr "Food" + +# Pet food product type +msgctxt "product_type_petfood" +msgid "Pet food" +msgstr "Pet food" + +# Beauty product type +msgctxt "product_type_beauty" +msgid "Beauty" +msgstr "Beauty" + +# General product product type +msgctxt "product_type_product" +msgid "Product" +msgstr "Product" \ No newline at end of file diff --git a/po/common/en.po b/po/common/en.po index 282b66755d97c..96b5d491b7a43 100644 --- a/po/common/en.po +++ b/po/common/en.po @@ -7201,6 +7201,26 @@ msgctxt "attribute_repairability_index_france_bad_description_short" msgid "Bad repairability" msgstr "Bad repairability" -msgctxt "concerned_categories" -msgid "Bad repairability" -msgstr "Bad repairability" +msgctxt "product_type" +msgid "Product type" +msgstr "Product type" + +# Food product type +msgctxt "product_type_food" +msgid "Food" +msgstr "Food" + +# Pet food product type +msgctxt "product_type_petfood" +msgid "Pet food" +msgstr "Pet food" + +# Beauty product type +msgctxt "product_type_beauty" +msgid "Beauty" +msgstr "Beauty" + +# General product product type +msgctxt "product_type_product" +msgid "Product" +msgstr "Product" \ No newline at end of file diff --git a/templates/web/pages/product_edit/product_edit_form_display.tt.html b/templates/web/pages/product_edit/product_edit_form_display.tt.html index 077516be3e12d..a0c42b65076cb 100644 --- a/templates/web/pages/product_edit/product_edit_form_display.tt.html +++ b/templates/web/pages/product_edit/product_edit_form_display.tt.html @@ -67,6 +67,14 @@

[% title %]


[% END %] + [% IF moderator || owner_id %] + + From 10e406eef608db10a82023b5b1f43a6a5df2c118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Tue, 29 Oct 2024 15:31:19 +0100 Subject: [PATCH 2/9] added tests --- ..._v2_product_code_and_product_type_change.t | 121 +++ .../change-product-code-moderator.json | 4 + .../change-product-code-not-a-moderator.json | 4 + .../change-product-code-to-obf.json | 4 + .../get-product-obf.json | 711 +++++++++++++++++ .../get-product-with-initial-code.json | 711 +++++++++++++++++ .../get-product-with-new-code.json | 716 ++++++++++++++++++ 7 files changed, 2271 insertions(+) create mode 100644 tests/integration/api_v2_product_code_and_product_type_change.t create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-moderator.json create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-not-a-moderator.json create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-to-obf.json create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json diff --git a/tests/integration/api_v2_product_code_and_product_type_change.t b/tests/integration/api_v2_product_code_and_product_type_change.t new file mode 100644 index 0000000000000..ad6fce154c9e6 --- /dev/null +++ b/tests/integration/api_v2_product_code_and_product_type_change.t @@ -0,0 +1,121 @@ +#!/usr/bin/perl -w + +use ProductOpener::PerlStandards; + +use Test2::V0; +use ProductOpener::APITest qw/:all/; +use ProductOpener::Test qw/remove_all_products remove_all_users/; +use ProductOpener::TestDefaults qw/%admin_user_form %default_user_form %moderator_user_form/; + +use File::Basename "dirname"; + +use Storable qw(dclone); + +remove_all_users(); + +remove_all_products(); + +wait_application_ready(); + +# Create an admin +my $admin_ua = new_client(); +my $resp = create_user($admin_ua, \%admin_user_form); +ok(!html_displays_error($resp)); + +# Create a normal user +my $ua = new_client(); +my %create_user_args = (%default_user_form, (email => 'bob@gmail.com')); +$resp = create_user($ua, \%create_user_args); +ok(!html_displays_error($resp)); + +# Create a moderator +my $moderator_ua = new_client(); +$resp = create_user($moderator_ua, \%moderator_user_form); +ok(!html_displays_error($resp)); + +# Admin gives moderator status +my %moderator_edit_form = ( + %moderator_user_form, + user_group_moderator => "1", + type => "edit", +); +$resp = edit_user($admin_ua, \%moderator_edit_form); +ok(!html_displays_error($resp)); + +# Note: expected results are stored in json files, see execute_api_tests +my $tests_ref = [ + + # Test setup - Create a product + { + setup => 1, + test_case => 'setup-create-product', + method => 'PATCH', + path => '/api/v3/product/1234567890100', + body => '{ + "product": { + "product_name_en": "Test product", + "brands_tags": ["Test brand"], + "categories_tags": ["en:beverages", "en:teas"], + "lang": "en", + "countries_tags": ["en:france"] + } + }', + }, + # Change the barcode + { + test_case => 'change-product-code-not-a-moderator', + method => 'POST', + path => '/cgi/product_jqm_multilingual.pl', + form => { + code => "1234567890100", + new_code => "1234567890101", + } + }, + # Get the product with the initial code + { + test_case => 'get-product-with-initial-code', + method => 'GET', + path => '/api/v3/product/1234567890100', + expected_status_code => 200, + }, + # Change the product with a moderator account + { + test_case => 'change-product-code-moderator', + method => 'POST', + path => '/cgi/product_jqm_multilingual.pl', + form => { + code => "1234567890100", + new_code => "1234567890102", + }, + ua => $moderator_ua, + }, + # Get the product with the new code + { + test_case => 'get-product-with-new-code', + method => 'GET', + path => '/api/v3/product/1234567890102', + expected_status_code => 200, + }, + # Send code=obf to move product to Open Beauty Facts + { + test_case => 'change-product-code-to-obf', + method => 'POST', + path => '/cgi/product_jqm_multilingual.pl', + form => { + code => "1234567890102", + new_code => "obf", + }, + ua => $moderator_ua, + }, + # Get the product + { + test_case => 'get-product-obf', + method => 'GET', + path => '/api/v3/product/1234567890102', + expected_status_code => 200, + }, +]; + +execute_api_tests(__FILE__, $tests_ref); + +done_testing(); diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-moderator.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-moderator.json new file mode 100644 index 0000000000000..39d4261a9d3f0 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-moderator.json @@ -0,0 +1,4 @@ +{ + "status" : 1, + "status_verbose" : "fields saved" +} diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-not-a-moderator.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-not-a-moderator.json new file mode 100644 index 0000000000000..39d4261a9d3f0 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-not-a-moderator.json @@ -0,0 +1,4 @@ +{ + "status" : 1, + "status_verbose" : "fields saved" +} diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-to-obf.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-to-obf.json new file mode 100644 index 0000000000000..39d4261a9d3f0 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-code-to-obf.json @@ -0,0 +1,4 @@ +{ + "status" : 1, + "status_verbose" : "fields saved" +} diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json new file mode 100644 index 0000000000000..652505f6c61be --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json @@ -0,0 +1,711 @@ +{ + "code" : "1234567890102", + "errors" : [], + "product" : { + "_id" : "1234567890102", + "_keywords" : [ + "beverage", + "brand", + "product", + "tea", + "test" + ], + "added_countries_tags" : [], + "allergens" : "", + "allergens_from_ingredients" : "", + "allergens_from_user" : "(en) ", + "allergens_hierarchy" : [], + "allergens_tags" : [], + "brands" : "Test brand", + "brands_tags" : [ + "test-brand" + ], + "categories" : "en:beverages,en:teas", + "categories_hierarchy" : [ + "en:beverages-and-beverages-preparations", + "en:plant-based-foods-and-beverages", + "en:beverages", + "en:hot-beverages", + "en:plant-based-beverages", + "en:teas" + ], + "categories_lc" : "en", + "categories_properties" : { + "agribalyse_food_code:en" : "18020", + "ciqual_food_code:en" : "18020" + }, + "categories_properties_tags" : [ + "all-products", + "categories-known", + "agribalyse-food-code-18020", + "agribalyse-food-code-known", + "agribalyse-proxy-food-code-unknown", + "ciqual-food-code-18020", + "ciqual-food-code-known", + "agribalyse-known", + "agribalyse-18020" + ], + "categories_tags" : [ + "en:beverages-and-beverages-preparations", + "en:plant-based-foods-and-beverages", + "en:beverages", + "en:hot-beverages", + "en:plant-based-beverages", + "en:teas" + ], + "checkers_tags" : [], + "code" : "1234567890102", + "codes_tags" : [ + "code-13", + "1234567890xxx", + "123456789xxxx", + "12345678xxxxx", + "1234567xxxxxx", + "123456xxxxxxx", + "12345xxxxxxxx", + "1234xxxxxxxxx", + "123xxxxxxxxxx", + "12xxxxxxxxxxx", + "1xxxxxxxxxxxx" + ], + "complete" : 0, + "completeness" : 0.3, + "correctors_tags" : [ + "moderator" + ], + "countries" : "en:france", + "countries_hierarchy" : [ + "en:france" + ], + "countries_lc" : "en", + "countries_tags" : [ + "en:france" + ], + "created_t" : "--ignore--", + "creator" : "openfoodfacts-contributors", + "data_quality_bugs_tags" : [], + "data_quality_errors_tags" : [], + "data_quality_info_tags" : [ + "en:no-packaging-data", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-unknown", + "en:food-groups-2-unknown", + "en:food-groups-3-unknown" + ], + "data_quality_tags" : [ + "en:no-packaging-data", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-unknown", + "en:food-groups-2-unknown", + "en:food-groups-3-unknown", + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label", + "en:ecoscore-threatened-species-ingredients-missing" + ], + "data_quality_warnings_tags" : [ + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label", + "en:ecoscore-threatened-species-ingredients-missing" + ], + "ecoscore_data" : { + "adjustments" : { + "origins_of_ingredients" : { + "aggregated_origins" : [ + { + "origin" : "en:unknown", + "percent" : 100 + } + ], + "epi_score" : 0, + "epi_value" : -5, + "origins_from_categories" : [ + "en:unknown" + ], + "origins_from_origins_field" : [ + "en:unknown" + ], + "transportation_scores" : { + "ad" : 0, + "al" : 0, + "at" : 0, + "ax" : 0, + "ba" : 0, + "be" : 0, + "bg" : 0, + "ch" : 0, + "cy" : 0, + "cz" : 0, + "de" : 0, + "dk" : 0, + "dz" : 0, + "ee" : 0, + "eg" : 0, + "es" : 0, + "fi" : 0, + "fo" : 0, + "fr" : 0, + "gg" : 0, + "gi" : 0, + "gr" : 0, + "hr" : 0, + "hu" : 0, + "ie" : 0, + "il" : 0, + "im" : 0, + "is" : 0, + "it" : 0, + "je" : 0, + "lb" : 0, + "li" : 0, + "lt" : 0, + "lu" : 0, + "lv" : 0, + "ly" : 0, + "ma" : 0, + "mc" : 0, + "md" : 0, + "me" : 0, + "mk" : 0, + "mt" : 0, + "nl" : 0, + "no" : 0, + "pl" : 0, + "ps" : 0, + "pt" : 0, + "ro" : 0, + "rs" : 0, + "se" : 0, + "si" : 0, + "sj" : 0, + "sk" : 0, + "sm" : 0, + "sy" : 0, + "tn" : 0, + "tr" : 0, + "ua" : 0, + "uk" : 0, + "us" : 0, + "va" : 0, + "world" : 0, + "xk" : 0 + }, + "transportation_values" : { + "ad" : 0, + "al" : 0, + "at" : 0, + "ax" : 0, + "ba" : 0, + "be" : 0, + "bg" : 0, + "ch" : 0, + "cy" : 0, + "cz" : 0, + "de" : 0, + "dk" : 0, + "dz" : 0, + "ee" : 0, + "eg" : 0, + "es" : 0, + "fi" : 0, + "fo" : 0, + "fr" : 0, + "gg" : 0, + "gi" : 0, + "gr" : 0, + "hr" : 0, + "hu" : 0, + "ie" : 0, + "il" : 0, + "im" : 0, + "is" : 0, + "it" : 0, + "je" : 0, + "lb" : 0, + "li" : 0, + "lt" : 0, + "lu" : 0, + "lv" : 0, + "ly" : 0, + "ma" : 0, + "mc" : 0, + "md" : 0, + "me" : 0, + "mk" : 0, + "mt" : 0, + "nl" : 0, + "no" : 0, + "pl" : 0, + "ps" : 0, + "pt" : 0, + "ro" : 0, + "rs" : 0, + "se" : 0, + "si" : 0, + "sj" : 0, + "sk" : 0, + "sm" : 0, + "sy" : 0, + "tn" : 0, + "tr" : 0, + "ua" : 0, + "uk" : 0, + "us" : 0, + "va" : 0, + "world" : 0, + "xk" : 0 + }, + "values" : { + "ad" : -5, + "al" : -5, + "at" : -5, + "ax" : -5, + "ba" : -5, + "be" : -5, + "bg" : -5, + "ch" : -5, + "cy" : -5, + "cz" : -5, + "de" : -5, + "dk" : -5, + "dz" : -5, + "ee" : -5, + "eg" : -5, + "es" : -5, + "fi" : -5, + "fo" : -5, + "fr" : -5, + "gg" : -5, + "gi" : -5, + "gr" : -5, + "hr" : -5, + "hu" : -5, + "ie" : -5, + "il" : -5, + "im" : -5, + "is" : -5, + "it" : -5, + "je" : -5, + "lb" : -5, + "li" : -5, + "lt" : -5, + "lu" : -5, + "lv" : -5, + "ly" : -5, + "ma" : -5, + "mc" : -5, + "md" : -5, + "me" : -5, + "mk" : -5, + "mt" : -5, + "nl" : -5, + "no" : -5, + "pl" : -5, + "ps" : -5, + "pt" : -5, + "ro" : -5, + "rs" : -5, + "se" : -5, + "si" : -5, + "sj" : -5, + "sk" : -5, + "sm" : -5, + "sy" : -5, + "tn" : -5, + "tr" : -5, + "ua" : -5, + "uk" : -5, + "us" : -5, + "va" : -5, + "world" : -5, + "xk" : -5 + }, + "warning" : "origins_are_100_percent_unknown" + }, + "packaging" : { + "value" : -15, + "warning" : "packaging_data_missing" + }, + "production_system" : { + "labels" : [], + "value" : 0, + "warning" : "no_label" + }, + "threatened_species" : { + "warning" : "ingredients_missing" + } + }, + "agribalyse" : { + "agribalyse_food_code" : "18020", + "co2_agriculture" : 0.022870449, + "co2_consumption" : 0.0112323, + "co2_distribution" : 0.0001567597, + "co2_packaging" : 0.0026323455, + "co2_processing" : 0, + "co2_total" : 0.0391613535, + "co2_transportation" : 0.0022694993, + "code" : "18020", + "dqr" : "2.98", + "ef_agriculture" : 0.0081036052, + "ef_consumption" : 0.0043474982, + "ef_distribution" : 4.4793367e-05, + "ef_packaging" : 0.00021662681, + "ef_processing" : 0, + "ef_total" : 0.012940544857, + "ef_transportation" : 0.00022802128, + "is_beverage" : 1, + "name_en" : "Tea, brewed, without sugar", + "name_fr" : "Thé infusé, non sucré", + "score" : 100, + "version" : "3.1.1" + }, + "grade" : "a", + "grades" : { + "ad" : "a", + "al" : "a", + "at" : "a", + "ax" : "a", + "ba" : "a", + "be" : "a", + "bg" : "a", + "ch" : "a", + "cy" : "a", + "cz" : "a", + "de" : "a", + "dk" : "a", + "dz" : "a", + "ee" : "a", + "eg" : "a", + "es" : "a", + "fi" : "a", + "fo" : "a", + "fr" : "a", + "gg" : "a", + "gi" : "a", + "gr" : "a", + "hr" : "a", + "hu" : "a", + "ie" : "a", + "il" : "a", + "im" : "a", + "is" : "a", + "it" : "a", + "je" : "a", + "lb" : "a", + "li" : "a", + "lt" : "a", + "lu" : "a", + "lv" : "a", + "ly" : "a", + "ma" : "a", + "mc" : "a", + "md" : "a", + "me" : "a", + "mk" : "a", + "mt" : "a", + "nl" : "a", + "no" : "a", + "pl" : "a", + "ps" : "a", + "pt" : "a", + "ro" : "a", + "rs" : "a", + "se" : "a", + "si" : "a", + "sj" : "a", + "sk" : "a", + "sm" : "a", + "sy" : "a", + "tn" : "a", + "tr" : "a", + "ua" : "a", + "uk" : "a", + "us" : "a", + "va" : "a", + "world" : "a", + "xk" : "a" + }, + "missing" : { + "ingredients" : 1, + "labels" : 1, + "origins" : 1, + "packagings" : 1 + }, + "missing_data_warning" : 1, + "missing_key_data" : 1, + "score" : 80, + "scores" : { + "ad" : 80, + "al" : 80, + "at" : 80, + "ax" : 80, + "ba" : 80, + "be" : 80, + "bg" : 80, + "ch" : 80, + "cy" : 80, + "cz" : 80, + "de" : 80, + "dk" : 80, + "dz" : 80, + "ee" : 80, + "eg" : 80, + "es" : 80, + "fi" : 80, + "fo" : 80, + "fr" : 80, + "gg" : 80, + "gi" : 80, + "gr" : 80, + "hr" : 80, + "hu" : 80, + "ie" : 80, + "il" : 80, + "im" : 80, + "is" : 80, + "it" : 80, + "je" : 80, + "lb" : 80, + "li" : 80, + "lt" : 80, + "lu" : 80, + "lv" : 80, + "ly" : 80, + "ma" : 80, + "mc" : 80, + "md" : 80, + "me" : 80, + "mk" : 80, + "mt" : 80, + "nl" : 80, + "no" : 80, + "pl" : 80, + "ps" : 80, + "pt" : 80, + "ro" : 80, + "rs" : 80, + "se" : 80, + "si" : 80, + "sj" : 80, + "sk" : 80, + "sm" : 80, + "sy" : 80, + "tn" : 80, + "tr" : 80, + "ua" : 80, + "uk" : 80, + "us" : 80, + "va" : 80, + "world" : 80, + "xk" : 80 + }, + "status" : "known" + }, + "ecoscore_grade" : "a", + "ecoscore_score" : 80, + "ecoscore_tags" : [ + "a" + ], + "editors_tags" : [ + "moderator", + "openfoodfacts-contributors" + ], + "entry_dates_tags" : "--ignore--", + "food_groups_tags" : [], + "id" : "1234567890100", + "informers_tags" : [ + "openfoodfacts-contributors" + ], + "ingredients_lc" : "en", + "interface_version_created" : "20221102/api/v3", + "interface_version_modified" : "20150316.jqm2", + "lang" : "en", + "languages" : { + "en:english" : 1 + }, + "languages_codes" : { + "en" : 1 + }, + "languages_hierarchy" : [ + "en:english" + ], + "languages_tags" : [ + "en:english", + "en:1" + ], + "last_edit_dates_tags" : "--ignore--", + "last_editor" : "moderator", + "last_modified_by" : "moderator", + "last_modified_t" : "--ignore--", + "last_updated_t" : "--ignore--", + "lc" : "en", + "main_countries_tags" : [], + "misc_tags" : [ + "en:ecoscore-computed", + "en:ecoscore-extended-data-not-computed", + "en:ecoscore-missing-data-labels", + "en:ecoscore-missing-data-no-packagings", + "en:ecoscore-missing-data-origins", + "en:ecoscore-missing-data-packagings", + "en:ecoscore-missing-data-warning", + "en:nutriscore-missing-nutrition-data", + "en:nutriscore-missing-nutrition-data-energy", + "en:nutriscore-missing-nutrition-data-fat", + "en:nutriscore-missing-nutrition-data-proteins", + "en:nutriscore-missing-nutrition-data-saturated-fat", + "en:nutriscore-missing-nutrition-data-sodium", + "en:nutriscore-missing-nutrition-data-sugars", + "en:nutriscore-not-computed", + "en:nutrition-no-fiber", + "en:nutrition-no-fiber-or-fruits-vegetables-nuts", + "en:nutrition-no-fruits-vegetables-nuts", + "en:nutrition-not-enough-data-to-compute-nutrition-score", + "en:packagings-empty", + "en:packagings-not-complete", + "en:packagings-number-of-components-0", + "en:main-countries-new-product" + ], + "nova_group_debug" : "no nova group when the product does not have ingredients", + "nova_group_error" : "missing_ingredients", + "nova_groups_tags" : [ + "unknown" + ], + "nutrient_levels" : {}, + "nutrient_levels_tags" : [], + "nutriments" : {}, + "nutriscore" : { + "2021" : { + "category_available" : 1, + "data" : { + "energy" : null, + "fiber" : 0, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, + "is_beverage" : 1, + "is_cheese" : 0, + "is_fat" : 0, + "is_water" : 0, + "proteins" : null, + "saturated_fat" : null, + "sodium" : null, + "sugars" : null + }, + "grade" : "unknown", + "nutrients_available" : 0, + "nutriscore_applicable" : 1, + "nutriscore_computed" : 0 + }, + "2023" : { + "category_available" : 1, + "data" : { + "energy" : null, + "fiber" : null, + "fruits_vegetables_legumes" : null, + "is_beverage" : 1, + "is_cheese" : 0, + "is_fat_oil_nuts_seeds" : 0, + "is_red_meat_product" : 0, + "is_water" : 0, + "non_nutritive_sweeteners" : null, + "proteins" : null, + "salt" : null, + "saturated_fat" : null, + "sugars" : null + }, + "grade" : "unknown", + "nutrients_available" : 0, + "nutriscore_applicable" : 1, + "nutriscore_computed" : 0 + } + }, + "nutriscore_2021_tags" : [ + "unknown" + ], + "nutriscore_2023_tags" : [ + "unknown" + ], + "nutriscore_grade" : "unknown", + "nutriscore_tags" : [ + "unknown" + ], + "nutriscore_version" : "2021", + "nutrition_data_per" : "100g", + "nutrition_data_prepared_per" : "100g", + "nutrition_grade_fr" : "unknown", + "nutrition_grades" : "unknown", + "nutrition_grades_tags" : [ + "not-applicable" + ], + "nutrition_score_beverage" : 1, + "nutrition_score_debug" : "missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", + "nutrition_score_warning_no_fiber" : 1, + "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, + "packaging_materials_tags" : [], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [], + "packagings" : [], + "packagings_materials" : {}, + "photographers_tags" : [], + "pnns_groups_1" : "unknown", + "pnns_groups_1_tags" : [ + "unknown", + "missing-association" + ], + "pnns_groups_2" : "unknown", + "pnns_groups_2_tags" : [ + "unknown", + "missing-association" + ], + "popularity_key" : 0, + "product_name" : "Test product", + "product_name_en" : "Test product", + "product_type" : "beauty", + "removed_countries_tags" : [], + "rev" : 4, + "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-completed, en:brands-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", + "states_hierarchy" : [ + "en:to-be-completed", + "en:nutrition-facts-to-be-completed", + "en:ingredients-to-be-completed", + "en:expiration-date-to-be-completed", + "en:packaging-code-to-be-completed", + "en:characteristics-to-be-completed", + "en:origins-to-be-completed", + "en:categories-completed", + "en:brands-completed", + "en:packaging-to-be-completed", + "en:quantity-to-be-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "states_tags" : [ + "en:to-be-completed", + "en:nutrition-facts-to-be-completed", + "en:ingredients-to-be-completed", + "en:expiration-date-to-be-completed", + "en:packaging-code-to-be-completed", + "en:characteristics-to-be-completed", + "en:origins-to-be-completed", + "en:categories-completed", + "en:brands-completed", + "en:packaging-to-be-completed", + "en:quantity-to-be-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "traces" : "", + "traces_from_ingredients" : "", + "traces_from_user" : "(en) ", + "traces_hierarchy" : [], + "traces_tags" : [], + "unknown_nutrients_tags" : [], + "weighers_tags" : [] + }, + "result" : { + "id" : "product_found", + "lc_name" : "Product found", + "name" : "Product found" + }, + "status" : "success", + "warnings" : [] +} diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json new file mode 100644 index 0000000000000..8bcac13f5c486 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json @@ -0,0 +1,711 @@ +{ + "code" : "1234567890100", + "errors" : [], + "product" : { + "_id" : "1234567890100", + "_keywords" : [ + "beverage", + "brand", + "product", + "tea", + "test" + ], + "added_countries_tags" : [], + "allergens" : "", + "allergens_from_ingredients" : "", + "allergens_from_user" : "(en) ", + "allergens_hierarchy" : [], + "allergens_tags" : [], + "brands" : "Test brand", + "brands_tags" : [ + "test-brand" + ], + "categories" : "en:beverages,en:teas", + "categories_hierarchy" : [ + "en:beverages-and-beverages-preparations", + "en:plant-based-foods-and-beverages", + "en:beverages", + "en:hot-beverages", + "en:plant-based-beverages", + "en:teas" + ], + "categories_lc" : "en", + "categories_properties" : { + "agribalyse_food_code:en" : "18020", + "ciqual_food_code:en" : "18020" + }, + "categories_properties_tags" : [ + "all-products", + "categories-known", + "agribalyse-food-code-18020", + "agribalyse-food-code-known", + "agribalyse-proxy-food-code-unknown", + "ciqual-food-code-18020", + "ciqual-food-code-known", + "agribalyse-known", + "agribalyse-18020" + ], + "categories_tags" : [ + "en:beverages-and-beverages-preparations", + "en:plant-based-foods-and-beverages", + "en:beverages", + "en:hot-beverages", + "en:plant-based-beverages", + "en:teas" + ], + "checkers_tags" : [], + "code" : "1234567890100", + "codes_tags" : [ + "code-13", + "1234567890xxx", + "123456789xxxx", + "12345678xxxxx", + "1234567xxxxxx", + "123456xxxxxxx", + "12345xxxxxxxx", + "1234xxxxxxxxx", + "123xxxxxxxxxx", + "12xxxxxxxxxxx", + "1xxxxxxxxxxxx" + ], + "complete" : 0, + "completeness" : 0.3, + "correctors_tags" : [], + "countries" : "en:france", + "countries_hierarchy" : [ + "en:france" + ], + "countries_lc" : "en", + "countries_tags" : [ + "en:france" + ], + "created_t" : "--ignore--", + "creator" : "openfoodfacts-contributors", + "data_quality_bugs_tags" : [], + "data_quality_errors_tags" : [], + "data_quality_info_tags" : [ + "en:no-packaging-data", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-unknown", + "en:food-groups-2-unknown", + "en:food-groups-3-unknown" + ], + "data_quality_tags" : [ + "en:no-packaging-data", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-unknown", + "en:food-groups-2-unknown", + "en:food-groups-3-unknown", + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label", + "en:ecoscore-threatened-species-ingredients-missing" + ], + "data_quality_warnings_tags" : [ + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label", + "en:ecoscore-threatened-species-ingredients-missing" + ], + "ecoscore_data" : { + "adjustments" : { + "origins_of_ingredients" : { + "aggregated_origins" : [ + { + "epi_score" : "0", + "origin" : "en:unknown", + "percent" : 100, + "transportation_score" : 0 + } + ], + "epi_score" : 0, + "epi_value" : -5, + "origins_from_categories" : [ + "en:unknown" + ], + "origins_from_origins_field" : [ + "en:unknown" + ], + "transportation_score" : 0, + "transportation_scores" : { + "ad" : 0, + "al" : 0, + "at" : 0, + "ax" : 0, + "ba" : 0, + "be" : 0, + "bg" : 0, + "ch" : 0, + "cy" : 0, + "cz" : 0, + "de" : 0, + "dk" : 0, + "dz" : 0, + "ee" : 0, + "eg" : 0, + "es" : 0, + "fi" : 0, + "fo" : 0, + "fr" : 0, + "gg" : 0, + "gi" : 0, + "gr" : 0, + "hr" : 0, + "hu" : 0, + "ie" : 0, + "il" : 0, + "im" : 0, + "is" : 0, + "it" : 0, + "je" : 0, + "lb" : 0, + "li" : 0, + "lt" : 0, + "lu" : 0, + "lv" : 0, + "ly" : 0, + "ma" : 0, + "mc" : 0, + "md" : 0, + "me" : 0, + "mk" : 0, + "mt" : 0, + "nl" : 0, + "no" : 0, + "pl" : 0, + "ps" : 0, + "pt" : 0, + "ro" : 0, + "rs" : 0, + "se" : 0, + "si" : 0, + "sj" : 0, + "sk" : 0, + "sm" : 0, + "sy" : 0, + "tn" : 0, + "tr" : 0, + "ua" : 0, + "uk" : 0, + "us" : 0, + "va" : 0, + "world" : 0, + "xk" : 0 + }, + "transportation_value" : 0, + "transportation_values" : { + "ad" : 0, + "al" : 0, + "at" : 0, + "ax" : 0, + "ba" : 0, + "be" : 0, + "bg" : 0, + "ch" : 0, + "cy" : 0, + "cz" : 0, + "de" : 0, + "dk" : 0, + "dz" : 0, + "ee" : 0, + "eg" : 0, + "es" : 0, + "fi" : 0, + "fo" : 0, + "fr" : 0, + "gg" : 0, + "gi" : 0, + "gr" : 0, + "hr" : 0, + "hu" : 0, + "ie" : 0, + "il" : 0, + "im" : 0, + "is" : 0, + "it" : 0, + "je" : 0, + "lb" : 0, + "li" : 0, + "lt" : 0, + "lu" : 0, + "lv" : 0, + "ly" : 0, + "ma" : 0, + "mc" : 0, + "md" : 0, + "me" : 0, + "mk" : 0, + "mt" : 0, + "nl" : 0, + "no" : 0, + "pl" : 0, + "ps" : 0, + "pt" : 0, + "ro" : 0, + "rs" : 0, + "se" : 0, + "si" : 0, + "sj" : 0, + "sk" : 0, + "sm" : 0, + "sy" : 0, + "tn" : 0, + "tr" : 0, + "ua" : 0, + "uk" : 0, + "us" : 0, + "va" : 0, + "world" : 0, + "xk" : 0 + }, + "value" : -5, + "values" : { + "ad" : -5, + "al" : -5, + "at" : -5, + "ax" : -5, + "ba" : -5, + "be" : -5, + "bg" : -5, + "ch" : -5, + "cy" : -5, + "cz" : -5, + "de" : -5, + "dk" : -5, + "dz" : -5, + "ee" : -5, + "eg" : -5, + "es" : -5, + "fi" : -5, + "fo" : -5, + "fr" : -5, + "gg" : -5, + "gi" : -5, + "gr" : -5, + "hr" : -5, + "hu" : -5, + "ie" : -5, + "il" : -5, + "im" : -5, + "is" : -5, + "it" : -5, + "je" : -5, + "lb" : -5, + "li" : -5, + "lt" : -5, + "lu" : -5, + "lv" : -5, + "ly" : -5, + "ma" : -5, + "mc" : -5, + "md" : -5, + "me" : -5, + "mk" : -5, + "mt" : -5, + "nl" : -5, + "no" : -5, + "pl" : -5, + "ps" : -5, + "pt" : -5, + "ro" : -5, + "rs" : -5, + "se" : -5, + "si" : -5, + "sj" : -5, + "sk" : -5, + "sm" : -5, + "sy" : -5, + "tn" : -5, + "tr" : -5, + "ua" : -5, + "uk" : -5, + "us" : -5, + "va" : -5, + "world" : -5, + "xk" : -5 + }, + "warning" : "origins_are_100_percent_unknown" + }, + "packaging" : { + "value" : -15, + "warning" : "packaging_data_missing" + }, + "production_system" : { + "labels" : [], + "value" : 0, + "warning" : "no_label" + }, + "threatened_species" : { + "warning" : "ingredients_missing" + } + }, + "agribalyse" : { + "agribalyse_food_code" : "18020", + "co2_agriculture" : 0.022870449, + "co2_consumption" : 0.0112323, + "co2_distribution" : 0.0001567597, + "co2_packaging" : 0.0026323455, + "co2_processing" : 0, + "co2_total" : 0.0391613535, + "co2_transportation" : 0.0022694993, + "code" : "18020", + "dqr" : "2.98", + "ef_agriculture" : 0.0081036052, + "ef_consumption" : 0.0043474982, + "ef_distribution" : 4.4793367e-05, + "ef_packaging" : 0.00021662681, + "ef_processing" : 0, + "ef_total" : 0.012940544857, + "ef_transportation" : 0.00022802128, + "is_beverage" : 1, + "name_en" : "Tea, brewed, without sugar", + "name_fr" : "Thé infusé, non sucré", + "score" : 100, + "version" : "3.1.1" + }, + "grade" : "a", + "grades" : { + "ad" : "a", + "al" : "a", + "at" : "a", + "ax" : "a", + "ba" : "a", + "be" : "a", + "bg" : "a", + "ch" : "a", + "cy" : "a", + "cz" : "a", + "de" : "a", + "dk" : "a", + "dz" : "a", + "ee" : "a", + "eg" : "a", + "es" : "a", + "fi" : "a", + "fo" : "a", + "fr" : "a", + "gg" : "a", + "gi" : "a", + "gr" : "a", + "hr" : "a", + "hu" : "a", + "ie" : "a", + "il" : "a", + "im" : "a", + "is" : "a", + "it" : "a", + "je" : "a", + "lb" : "a", + "li" : "a", + "lt" : "a", + "lu" : "a", + "lv" : "a", + "ly" : "a", + "ma" : "a", + "mc" : "a", + "md" : "a", + "me" : "a", + "mk" : "a", + "mt" : "a", + "nl" : "a", + "no" : "a", + "pl" : "a", + "ps" : "a", + "pt" : "a", + "ro" : "a", + "rs" : "a", + "se" : "a", + "si" : "a", + "sj" : "a", + "sk" : "a", + "sm" : "a", + "sy" : "a", + "tn" : "a", + "tr" : "a", + "ua" : "a", + "uk" : "a", + "us" : "a", + "va" : "a", + "world" : "a", + "xk" : "a" + }, + "missing" : { + "ingredients" : 1, + "labels" : 1, + "origins" : 1, + "packagings" : 1 + }, + "missing_data_warning" : 1, + "missing_key_data" : 1, + "score" : 80, + "scores" : { + "ad" : 80, + "al" : 80, + "at" : 80, + "ax" : 80, + "ba" : 80, + "be" : 80, + "bg" : 80, + "ch" : 80, + "cy" : 80, + "cz" : 80, + "de" : 80, + "dk" : 80, + "dz" : 80, + "ee" : 80, + "eg" : 80, + "es" : 80, + "fi" : 80, + "fo" : 80, + "fr" : 80, + "gg" : 80, + "gi" : 80, + "gr" : 80, + "hr" : 80, + "hu" : 80, + "ie" : 80, + "il" : 80, + "im" : 80, + "is" : 80, + "it" : 80, + "je" : 80, + "lb" : 80, + "li" : 80, + "lt" : 80, + "lu" : 80, + "lv" : 80, + "ly" : 80, + "ma" : 80, + "mc" : 80, + "md" : 80, + "me" : 80, + "mk" : 80, + "mt" : 80, + "nl" : 80, + "no" : 80, + "pl" : 80, + "ps" : 80, + "pt" : 80, + "ro" : 80, + "rs" : 80, + "se" : 80, + "si" : 80, + "sj" : 80, + "sk" : 80, + "sm" : 80, + "sy" : 80, + "tn" : 80, + "tr" : 80, + "ua" : 80, + "uk" : 80, + "us" : 80, + "va" : 80, + "world" : 80, + "xk" : 80 + }, + "status" : "known" + }, + "ecoscore_grade" : "a", + "ecoscore_score" : 80, + "ecoscore_tags" : [ + "a" + ], + "editors_tags" : [ + "openfoodfacts-contributors" + ], + "entry_dates_tags" : "--ignore--", + "food_groups_tags" : [], + "id" : "1234567890100", + "informers_tags" : [ + "openfoodfacts-contributors" + ], + "ingredients_lc" : "en", + "interface_version_created" : "20221102/api/v3", + "interface_version_modified" : "20150316.jqm2", + "lang" : "en", + "languages" : { + "en:english" : 1 + }, + "languages_codes" : { + "en" : 1 + }, + "languages_hierarchy" : [ + "en:english" + ], + "languages_tags" : [ + "en:english", + "en:1" + ], + "last_edit_dates_tags" : "--ignore--", + "last_modified_t" : "--ignore--", + "last_updated_t" : "--ignore--", + "lc" : "en", + "main_countries_tags" : [], + "misc_tags" : [ + "en:ecoscore-computed", + "en:ecoscore-extended-data-not-computed", + "en:ecoscore-missing-data-labels", + "en:ecoscore-missing-data-no-packagings", + "en:ecoscore-missing-data-origins", + "en:ecoscore-missing-data-packagings", + "en:ecoscore-missing-data-warning", + "en:nutriscore-missing-nutrition-data", + "en:nutriscore-missing-nutrition-data-energy", + "en:nutriscore-missing-nutrition-data-fat", + "en:nutriscore-missing-nutrition-data-proteins", + "en:nutriscore-missing-nutrition-data-saturated-fat", + "en:nutriscore-missing-nutrition-data-sodium", + "en:nutriscore-missing-nutrition-data-sugars", + "en:nutriscore-not-computed", + "en:nutrition-no-fiber", + "en:nutrition-no-fiber-or-fruits-vegetables-nuts", + "en:nutrition-no-fruits-vegetables-nuts", + "en:nutrition-not-enough-data-to-compute-nutrition-score", + "en:packagings-empty", + "en:packagings-not-complete", + "en:packagings-number-of-components-0", + "en:main-countries-new-product" + ], + "nova_group_debug" : "no nova group when the product does not have ingredients", + "nova_group_error" : "missing_ingredients", + "nova_groups_tags" : [ + "unknown" + ], + "nutrient_levels" : {}, + "nutrient_levels_tags" : [], + "nutriments" : {}, + "nutriscore" : { + "2021" : { + "category_available" : 1, + "data" : { + "energy" : null, + "fiber" : 0, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, + "is_beverage" : 1, + "is_cheese" : 0, + "is_fat" : 0, + "is_water" : 0, + "proteins" : null, + "saturated_fat" : null, + "sodium" : null, + "sugars" : null + }, + "grade" : "unknown", + "nutrients_available" : 0, + "nutriscore_applicable" : 1, + "nutriscore_computed" : 0 + }, + "2023" : { + "category_available" : 1, + "data" : { + "energy" : null, + "fiber" : null, + "fruits_vegetables_legumes" : null, + "is_beverage" : 1, + "is_cheese" : 0, + "is_fat_oil_nuts_seeds" : 0, + "is_red_meat_product" : 0, + "is_water" : 0, + "non_nutritive_sweeteners" : null, + "proteins" : null, + "salt" : null, + "saturated_fat" : null, + "sugars" : null + }, + "grade" : "unknown", + "nutrients_available" : 0, + "nutriscore_applicable" : 1, + "nutriscore_computed" : 0 + } + }, + "nutriscore_2021_tags" : [ + "unknown" + ], + "nutriscore_2023_tags" : [ + "unknown" + ], + "nutriscore_grade" : "unknown", + "nutriscore_tags" : [ + "unknown" + ], + "nutriscore_version" : "2021", + "nutrition_data_per" : "100g", + "nutrition_data_prepared_per" : "100g", + "nutrition_grade_fr" : "unknown", + "nutrition_grades" : "unknown", + "nutrition_grades_tags" : [ + "not-applicable" + ], + "nutrition_score_beverage" : 1, + "nutrition_score_debug" : "missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", + "nutrition_score_warning_no_fiber" : 1, + "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, + "packaging_materials_tags" : [], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [], + "packagings" : [], + "packagings_materials" : {}, + "photographers_tags" : [], + "pnns_groups_1" : "unknown", + "pnns_groups_1_tags" : [ + "unknown", + "missing-association" + ], + "pnns_groups_2" : "unknown", + "pnns_groups_2_tags" : [ + "unknown", + "missing-association" + ], + "popularity_key" : 0, + "product_name" : "Test product", + "product_name_en" : "Test product", + "product_type" : "food", + "removed_countries_tags" : [], + "rev" : 2, + "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-completed, en:brands-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", + "states_hierarchy" : [ + "en:to-be-completed", + "en:nutrition-facts-to-be-completed", + "en:ingredients-to-be-completed", + "en:expiration-date-to-be-completed", + "en:packaging-code-to-be-completed", + "en:characteristics-to-be-completed", + "en:origins-to-be-completed", + "en:categories-completed", + "en:brands-completed", + "en:packaging-to-be-completed", + "en:quantity-to-be-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "states_tags" : [ + "en:to-be-completed", + "en:nutrition-facts-to-be-completed", + "en:ingredients-to-be-completed", + "en:expiration-date-to-be-completed", + "en:packaging-code-to-be-completed", + "en:characteristics-to-be-completed", + "en:origins-to-be-completed", + "en:categories-completed", + "en:brands-completed", + "en:packaging-to-be-completed", + "en:quantity-to-be-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "traces" : "", + "traces_from_ingredients" : "", + "traces_from_user" : "(en) ", + "traces_hierarchy" : [], + "traces_tags" : [], + "unknown_nutrients_tags" : [], + "weighers_tags" : [] + }, + "result" : { + "id" : "product_found", + "lc_name" : "Product found", + "name" : "Product found" + }, + "status" : "success", + "warnings" : [] +} diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json new file mode 100644 index 0000000000000..abee34e0df848 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json @@ -0,0 +1,716 @@ +{ + "code" : "1234567890102", + "errors" : [], + "product" : { + "_id" : "1234567890102", + "_keywords" : [ + "beverage", + "brand", + "product", + "tea", + "test" + ], + "added_countries_tags" : [], + "allergens" : "", + "allergens_from_ingredients" : "", + "allergens_from_user" : "(en) ", + "allergens_hierarchy" : [], + "allergens_tags" : [], + "brands" : "Test brand", + "brands_tags" : [ + "test-brand" + ], + "categories" : "en:beverages,en:teas", + "categories_hierarchy" : [ + "en:beverages-and-beverages-preparations", + "en:plant-based-foods-and-beverages", + "en:beverages", + "en:hot-beverages", + "en:plant-based-beverages", + "en:teas" + ], + "categories_lc" : "en", + "categories_properties" : { + "agribalyse_food_code:en" : "18020", + "ciqual_food_code:en" : "18020" + }, + "categories_properties_tags" : [ + "all-products", + "categories-known", + "agribalyse-food-code-18020", + "agribalyse-food-code-known", + "agribalyse-proxy-food-code-unknown", + "ciqual-food-code-18020", + "ciqual-food-code-known", + "agribalyse-known", + "agribalyse-18020" + ], + "categories_tags" : [ + "en:beverages-and-beverages-preparations", + "en:plant-based-foods-and-beverages", + "en:beverages", + "en:hot-beverages", + "en:plant-based-beverages", + "en:teas" + ], + "checkers_tags" : [], + "code" : "1234567890102", + "codes_tags" : [ + "code-13", + "1234567890xxx", + "123456789xxxx", + "12345678xxxxx", + "1234567xxxxxx", + "123456xxxxxxx", + "12345xxxxxxxx", + "1234xxxxxxxxx", + "123xxxxxxxxxx", + "12xxxxxxxxxxx", + "1xxxxxxxxxxxx" + ], + "complete" : 0, + "completeness" : 0.3, + "correctors_tags" : [ + "moderator" + ], + "countries" : "en:france", + "countries_hierarchy" : [ + "en:france" + ], + "countries_lc" : "en", + "countries_tags" : [ + "en:france" + ], + "created_t" : "--ignore--", + "creator" : "openfoodfacts-contributors", + "data_quality_bugs_tags" : [], + "data_quality_errors_tags" : [], + "data_quality_info_tags" : [ + "en:no-packaging-data", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-unknown", + "en:food-groups-2-unknown", + "en:food-groups-3-unknown" + ], + "data_quality_tags" : [ + "en:no-packaging-data", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-unknown", + "en:food-groups-2-unknown", + "en:food-groups-3-unknown", + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label", + "en:ecoscore-threatened-species-ingredients-missing" + ], + "data_quality_warnings_tags" : [ + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label", + "en:ecoscore-threatened-species-ingredients-missing" + ], + "ecoscore_data" : { + "adjustments" : { + "origins_of_ingredients" : { + "aggregated_origins" : [ + { + "epi_score" : "0", + "origin" : "en:unknown", + "percent" : 100, + "transportation_score" : 0 + } + ], + "epi_score" : 0, + "epi_value" : -5, + "origins_from_categories" : [ + "en:unknown" + ], + "origins_from_origins_field" : [ + "en:unknown" + ], + "transportation_score" : 0, + "transportation_scores" : { + "ad" : 0, + "al" : 0, + "at" : 0, + "ax" : 0, + "ba" : 0, + "be" : 0, + "bg" : 0, + "ch" : 0, + "cy" : 0, + "cz" : 0, + "de" : 0, + "dk" : 0, + "dz" : 0, + "ee" : 0, + "eg" : 0, + "es" : 0, + "fi" : 0, + "fo" : 0, + "fr" : 0, + "gg" : 0, + "gi" : 0, + "gr" : 0, + "hr" : 0, + "hu" : 0, + "ie" : 0, + "il" : 0, + "im" : 0, + "is" : 0, + "it" : 0, + "je" : 0, + "lb" : 0, + "li" : 0, + "lt" : 0, + "lu" : 0, + "lv" : 0, + "ly" : 0, + "ma" : 0, + "mc" : 0, + "md" : 0, + "me" : 0, + "mk" : 0, + "mt" : 0, + "nl" : 0, + "no" : 0, + "pl" : 0, + "ps" : 0, + "pt" : 0, + "ro" : 0, + "rs" : 0, + "se" : 0, + "si" : 0, + "sj" : 0, + "sk" : 0, + "sm" : 0, + "sy" : 0, + "tn" : 0, + "tr" : 0, + "ua" : 0, + "uk" : 0, + "us" : 0, + "va" : 0, + "world" : 0, + "xk" : 0 + }, + "transportation_value" : 0, + "transportation_values" : { + "ad" : 0, + "al" : 0, + "at" : 0, + "ax" : 0, + "ba" : 0, + "be" : 0, + "bg" : 0, + "ch" : 0, + "cy" : 0, + "cz" : 0, + "de" : 0, + "dk" : 0, + "dz" : 0, + "ee" : 0, + "eg" : 0, + "es" : 0, + "fi" : 0, + "fo" : 0, + "fr" : 0, + "gg" : 0, + "gi" : 0, + "gr" : 0, + "hr" : 0, + "hu" : 0, + "ie" : 0, + "il" : 0, + "im" : 0, + "is" : 0, + "it" : 0, + "je" : 0, + "lb" : 0, + "li" : 0, + "lt" : 0, + "lu" : 0, + "lv" : 0, + "ly" : 0, + "ma" : 0, + "mc" : 0, + "md" : 0, + "me" : 0, + "mk" : 0, + "mt" : 0, + "nl" : 0, + "no" : 0, + "pl" : 0, + "ps" : 0, + "pt" : 0, + "ro" : 0, + "rs" : 0, + "se" : 0, + "si" : 0, + "sj" : 0, + "sk" : 0, + "sm" : 0, + "sy" : 0, + "tn" : 0, + "tr" : 0, + "ua" : 0, + "uk" : 0, + "us" : 0, + "va" : 0, + "world" : 0, + "xk" : 0 + }, + "value" : -5, + "values" : { + "ad" : -5, + "al" : -5, + "at" : -5, + "ax" : -5, + "ba" : -5, + "be" : -5, + "bg" : -5, + "ch" : -5, + "cy" : -5, + "cz" : -5, + "de" : -5, + "dk" : -5, + "dz" : -5, + "ee" : -5, + "eg" : -5, + "es" : -5, + "fi" : -5, + "fo" : -5, + "fr" : -5, + "gg" : -5, + "gi" : -5, + "gr" : -5, + "hr" : -5, + "hu" : -5, + "ie" : -5, + "il" : -5, + "im" : -5, + "is" : -5, + "it" : -5, + "je" : -5, + "lb" : -5, + "li" : -5, + "lt" : -5, + "lu" : -5, + "lv" : -5, + "ly" : -5, + "ma" : -5, + "mc" : -5, + "md" : -5, + "me" : -5, + "mk" : -5, + "mt" : -5, + "nl" : -5, + "no" : -5, + "pl" : -5, + "ps" : -5, + "pt" : -5, + "ro" : -5, + "rs" : -5, + "se" : -5, + "si" : -5, + "sj" : -5, + "sk" : -5, + "sm" : -5, + "sy" : -5, + "tn" : -5, + "tr" : -5, + "ua" : -5, + "uk" : -5, + "us" : -5, + "va" : -5, + "world" : -5, + "xk" : -5 + }, + "warning" : "origins_are_100_percent_unknown" + }, + "packaging" : { + "value" : -15, + "warning" : "packaging_data_missing" + }, + "production_system" : { + "labels" : [], + "value" : 0, + "warning" : "no_label" + }, + "threatened_species" : { + "warning" : "ingredients_missing" + } + }, + "agribalyse" : { + "agribalyse_food_code" : "18020", + "co2_agriculture" : 0.022870449, + "co2_consumption" : 0.0112323, + "co2_distribution" : 0.0001567597, + "co2_packaging" : 0.0026323455, + "co2_processing" : 0, + "co2_total" : 0.0391613535, + "co2_transportation" : 0.0022694993, + "code" : "18020", + "dqr" : "2.98", + "ef_agriculture" : 0.0081036052, + "ef_consumption" : 0.0043474982, + "ef_distribution" : 4.4793367e-05, + "ef_packaging" : 0.00021662681, + "ef_processing" : 0, + "ef_total" : 0.012940544857, + "ef_transportation" : 0.00022802128, + "is_beverage" : 1, + "name_en" : "Tea, brewed, without sugar", + "name_fr" : "Thé infusé, non sucré", + "score" : 100, + "version" : "3.1.1" + }, + "grade" : "a", + "grades" : { + "ad" : "a", + "al" : "a", + "at" : "a", + "ax" : "a", + "ba" : "a", + "be" : "a", + "bg" : "a", + "ch" : "a", + "cy" : "a", + "cz" : "a", + "de" : "a", + "dk" : "a", + "dz" : "a", + "ee" : "a", + "eg" : "a", + "es" : "a", + "fi" : "a", + "fo" : "a", + "fr" : "a", + "gg" : "a", + "gi" : "a", + "gr" : "a", + "hr" : "a", + "hu" : "a", + "ie" : "a", + "il" : "a", + "im" : "a", + "is" : "a", + "it" : "a", + "je" : "a", + "lb" : "a", + "li" : "a", + "lt" : "a", + "lu" : "a", + "lv" : "a", + "ly" : "a", + "ma" : "a", + "mc" : "a", + "md" : "a", + "me" : "a", + "mk" : "a", + "mt" : "a", + "nl" : "a", + "no" : "a", + "pl" : "a", + "ps" : "a", + "pt" : "a", + "ro" : "a", + "rs" : "a", + "se" : "a", + "si" : "a", + "sj" : "a", + "sk" : "a", + "sm" : "a", + "sy" : "a", + "tn" : "a", + "tr" : "a", + "ua" : "a", + "uk" : "a", + "us" : "a", + "va" : "a", + "world" : "a", + "xk" : "a" + }, + "missing" : { + "ingredients" : 1, + "labels" : 1, + "origins" : 1, + "packagings" : 1 + }, + "missing_data_warning" : 1, + "missing_key_data" : 1, + "score" : 80, + "scores" : { + "ad" : 80, + "al" : 80, + "at" : 80, + "ax" : 80, + "ba" : 80, + "be" : 80, + "bg" : 80, + "ch" : 80, + "cy" : 80, + "cz" : 80, + "de" : 80, + "dk" : 80, + "dz" : 80, + "ee" : 80, + "eg" : 80, + "es" : 80, + "fi" : 80, + "fo" : 80, + "fr" : 80, + "gg" : 80, + "gi" : 80, + "gr" : 80, + "hr" : 80, + "hu" : 80, + "ie" : 80, + "il" : 80, + "im" : 80, + "is" : 80, + "it" : 80, + "je" : 80, + "lb" : 80, + "li" : 80, + "lt" : 80, + "lu" : 80, + "lv" : 80, + "ly" : 80, + "ma" : 80, + "mc" : 80, + "md" : 80, + "me" : 80, + "mk" : 80, + "mt" : 80, + "nl" : 80, + "no" : 80, + "pl" : 80, + "ps" : 80, + "pt" : 80, + "ro" : 80, + "rs" : 80, + "se" : 80, + "si" : 80, + "sj" : 80, + "sk" : 80, + "sm" : 80, + "sy" : 80, + "tn" : 80, + "tr" : 80, + "ua" : 80, + "uk" : 80, + "us" : 80, + "va" : 80, + "world" : 80, + "xk" : 80 + }, + "status" : "known" + }, + "ecoscore_grade" : "a", + "ecoscore_score" : 80, + "ecoscore_tags" : [ + "a" + ], + "editors_tags" : [ + "moderator", + "openfoodfacts-contributors" + ], + "entry_dates_tags" : "--ignore--", + "food_groups_tags" : [], + "id" : "1234567890100", + "informers_tags" : [ + "openfoodfacts-contributors" + ], + "ingredients_lc" : "en", + "interface_version_created" : "20221102/api/v3", + "interface_version_modified" : "20150316.jqm2", + "lang" : "en", + "languages" : { + "en:english" : 1 + }, + "languages_codes" : { + "en" : 1 + }, + "languages_hierarchy" : [ + "en:english" + ], + "languages_tags" : [ + "en:english", + "en:1" + ], + "last_edit_dates_tags" : "--ignore--", + "last_editor" : "moderator", + "last_modified_by" : "moderator", + "last_modified_t" : "--ignore--", + "last_updated_t" : "--ignore--", + "lc" : "en", + "main_countries_tags" : [], + "misc_tags" : [ + "en:ecoscore-computed", + "en:ecoscore-extended-data-not-computed", + "en:ecoscore-missing-data-labels", + "en:ecoscore-missing-data-no-packagings", + "en:ecoscore-missing-data-origins", + "en:ecoscore-missing-data-packagings", + "en:ecoscore-missing-data-warning", + "en:nutriscore-missing-nutrition-data", + "en:nutriscore-missing-nutrition-data-energy", + "en:nutriscore-missing-nutrition-data-fat", + "en:nutriscore-missing-nutrition-data-proteins", + "en:nutriscore-missing-nutrition-data-saturated-fat", + "en:nutriscore-missing-nutrition-data-sodium", + "en:nutriscore-missing-nutrition-data-sugars", + "en:nutriscore-not-computed", + "en:nutrition-no-fiber", + "en:nutrition-no-fiber-or-fruits-vegetables-nuts", + "en:nutrition-no-fruits-vegetables-nuts", + "en:nutrition-not-enough-data-to-compute-nutrition-score", + "en:packagings-empty", + "en:packagings-not-complete", + "en:packagings-number-of-components-0", + "en:main-countries-new-product" + ], + "nova_group_debug" : "no nova group when the product does not have ingredients", + "nova_group_error" : "missing_ingredients", + "nova_groups_tags" : [ + "unknown" + ], + "nutrient_levels" : {}, + "nutrient_levels_tags" : [], + "nutriments" : {}, + "nutriscore" : { + "2021" : { + "category_available" : 1, + "data" : { + "energy" : null, + "fiber" : 0, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, + "is_beverage" : 1, + "is_cheese" : 0, + "is_fat" : 0, + "is_water" : 0, + "proteins" : null, + "saturated_fat" : null, + "sodium" : null, + "sugars" : null + }, + "grade" : "unknown", + "nutrients_available" : 0, + "nutriscore_applicable" : 1, + "nutriscore_computed" : 0 + }, + "2023" : { + "category_available" : 1, + "data" : { + "energy" : null, + "fiber" : null, + "fruits_vegetables_legumes" : null, + "is_beverage" : 1, + "is_cheese" : 0, + "is_fat_oil_nuts_seeds" : 0, + "is_red_meat_product" : 0, + "is_water" : 0, + "non_nutritive_sweeteners" : null, + "proteins" : null, + "salt" : null, + "saturated_fat" : null, + "sugars" : null + }, + "grade" : "unknown", + "nutrients_available" : 0, + "nutriscore_applicable" : 1, + "nutriscore_computed" : 0 + } + }, + "nutriscore_2021_tags" : [ + "unknown" + ], + "nutriscore_2023_tags" : [ + "unknown" + ], + "nutriscore_grade" : "unknown", + "nutriscore_tags" : [ + "unknown" + ], + "nutriscore_version" : "2021", + "nutrition_data_per" : "100g", + "nutrition_data_prepared_per" : "100g", + "nutrition_grade_fr" : "unknown", + "nutrition_grades" : "unknown", + "nutrition_grades_tags" : [ + "not-applicable" + ], + "nutrition_score_beverage" : 1, + "nutrition_score_debug" : "missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", + "nutrition_score_warning_no_fiber" : 1, + "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, + "packaging_materials_tags" : [], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [], + "packagings" : [], + "packagings_materials" : {}, + "photographers_tags" : [], + "pnns_groups_1" : "unknown", + "pnns_groups_1_tags" : [ + "unknown", + "missing-association" + ], + "pnns_groups_2" : "unknown", + "pnns_groups_2_tags" : [ + "unknown", + "missing-association" + ], + "popularity_key" : 0, + "product_name" : "Test product", + "product_name_en" : "Test product", + "product_type" : "food", + "removed_countries_tags" : [], + "rev" : 3, + "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-completed, en:brands-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", + "states_hierarchy" : [ + "en:to-be-completed", + "en:nutrition-facts-to-be-completed", + "en:ingredients-to-be-completed", + "en:expiration-date-to-be-completed", + "en:packaging-code-to-be-completed", + "en:characteristics-to-be-completed", + "en:origins-to-be-completed", + "en:categories-completed", + "en:brands-completed", + "en:packaging-to-be-completed", + "en:quantity-to-be-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "states_tags" : [ + "en:to-be-completed", + "en:nutrition-facts-to-be-completed", + "en:ingredients-to-be-completed", + "en:expiration-date-to-be-completed", + "en:packaging-code-to-be-completed", + "en:characteristics-to-be-completed", + "en:origins-to-be-completed", + "en:categories-completed", + "en:brands-completed", + "en:packaging-to-be-completed", + "en:quantity-to-be-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "traces" : "", + "traces_from_ingredients" : "", + "traces_from_user" : "(en) ", + "traces_hierarchy" : [], + "traces_tags" : [], + "unknown_nutrients_tags" : [], + "weighers_tags" : [] + }, + "result" : { + "id" : "product_found", + "lc_name" : "Product found", + "name" : "Product found" + }, + "status" : "success", + "warnings" : [] +} From eb98e4a17194522443d5d2f64522d432a47db9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Tue, 29 Oct 2024 15:38:24 +0100 Subject: [PATCH 3/9] update tests --- ..._v2_product_code_and_product_type_change.t | 37 +- .../change-product-type-to-opf.json | 4 + .../get-product-obf.json | 253 ++------- .../get-product-opf.json | 522 ++++++++++++++++++ .../get-product-with-initial-code.json | 254 ++------- .../get-product-with-new-code.json | 254 ++------- 6 files changed, 657 insertions(+), 667 deletions(-) create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-type-to-opf.json create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.json diff --git a/tests/integration/api_v2_product_code_and_product_type_change.t b/tests/integration/api_v2_product_code_and_product_type_change.t index ad6fce154c9e6..adf35ebadb58f 100644 --- a/tests/integration/api_v2_product_code_and_product_type_change.t +++ b/tests/integration/api_v2_product_code_and_product_type_change.t @@ -53,10 +53,7 @@ my $tests_ref = [ path => '/api/v3/product/1234567890100', body => '{ "product": { - "product_name_en": "Test product", - "brands_tags": ["Test brand"], - "categories_tags": ["en:beverages", "en:teas"], - "lang": "en", + "product_name_en": "Test product 1", "countries_tags": ["en:france"] } }', @@ -114,6 +111,38 @@ my $tests_ref = [ path => '/api/v3/product/1234567890102', expected_status_code => 200, }, + # Create a new product + { + setup => 1, + test_case => 'setup-create-product-2', + method => 'PATCH', + path => '/api/v3/product/1234567890200', + body => '{ + "product": { + "product_name_en": "Test product 2", + "lang": "en", + "countries_tags": ["en:france"] + } + }', + }, + # Change the product_type field to opf + { + test_case => 'change-product-type-to-opf', + method => 'POST', + path => '/cgi/product_jqm_multilingual.pl', + form => { + code => "1234567890200", + new_code => "opf", + }, + ua => $moderator_ua, + }, + # Get the product + { + test_case => 'get-product-opf', + method => 'GET', + path => '/api/v3/product/1234567890200', + expected_status_code => 200, + }, ]; execute_api_tests(__FILE__, $tests_ref); diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-type-to-opf.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-type-to-opf.json new file mode 100644 index 0000000000000..39d4261a9d3f0 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/change-product-type-to-opf.json @@ -0,0 +1,4 @@ +{ + "status" : 1, + "status_verbose" : "fields saved" +} diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json index 652505f6c61be..8a0b2d656f1ee 100644 --- a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json @@ -4,10 +4,7 @@ "product" : { "_id" : "1234567890102", "_keywords" : [ - "beverage", - "brand", "product", - "tea", "test" ], "added_countries_tags" : [], @@ -16,42 +13,14 @@ "allergens_from_user" : "(en) ", "allergens_hierarchy" : [], "allergens_tags" : [], - "brands" : "Test brand", - "brands_tags" : [ - "test-brand" - ], - "categories" : "en:beverages,en:teas", - "categories_hierarchy" : [ - "en:beverages-and-beverages-preparations", - "en:plant-based-foods-and-beverages", - "en:beverages", - "en:hot-beverages", - "en:plant-based-beverages", - "en:teas" - ], - "categories_lc" : "en", - "categories_properties" : { - "agribalyse_food_code:en" : "18020", - "ciqual_food_code:en" : "18020" - }, + "categories_properties" : {}, "categories_properties_tags" : [ "all-products", - "categories-known", - "agribalyse-food-code-18020", - "agribalyse-food-code-known", + "categories-unknown", + "agribalyse-food-code-unknown", "agribalyse-proxy-food-code-unknown", - "ciqual-food-code-18020", - "ciqual-food-code-known", - "agribalyse-known", - "agribalyse-18020" - ], - "categories_tags" : [ - "en:beverages-and-beverages-preparations", - "en:plant-based-foods-and-beverages", - "en:beverages", - "en:hot-beverages", - "en:plant-based-beverages", - "en:teas" + "ciqual-food-code-unknown", + "agribalyse-unknown" ], "checkers_tags" : [], "code" : "1234567890102", @@ -69,7 +38,7 @@ "1xxxxxxxxxxxx" ], "complete" : 0, - "completeness" : 0.3, + "completeness" : 0.1, "correctors_tags" : [ "moderator" ], @@ -337,175 +306,22 @@ } }, "agribalyse" : { - "agribalyse_food_code" : "18020", - "co2_agriculture" : 0.022870449, - "co2_consumption" : 0.0112323, - "co2_distribution" : 0.0001567597, - "co2_packaging" : 0.0026323455, - "co2_processing" : 0, - "co2_total" : 0.0391613535, - "co2_transportation" : 0.0022694993, - "code" : "18020", - "dqr" : "2.98", - "ef_agriculture" : 0.0081036052, - "ef_consumption" : 0.0043474982, - "ef_distribution" : 4.4793367e-05, - "ef_packaging" : 0.00021662681, - "ef_processing" : 0, - "ef_total" : 0.012940544857, - "ef_transportation" : 0.00022802128, - "is_beverage" : 1, - "name_en" : "Tea, brewed, without sugar", - "name_fr" : "Thé infusé, non sucré", - "score" : 100, - "version" : "3.1.1" - }, - "grade" : "a", - "grades" : { - "ad" : "a", - "al" : "a", - "at" : "a", - "ax" : "a", - "ba" : "a", - "be" : "a", - "bg" : "a", - "ch" : "a", - "cy" : "a", - "cz" : "a", - "de" : "a", - "dk" : "a", - "dz" : "a", - "ee" : "a", - "eg" : "a", - "es" : "a", - "fi" : "a", - "fo" : "a", - "fr" : "a", - "gg" : "a", - "gi" : "a", - "gr" : "a", - "hr" : "a", - "hu" : "a", - "ie" : "a", - "il" : "a", - "im" : "a", - "is" : "a", - "it" : "a", - "je" : "a", - "lb" : "a", - "li" : "a", - "lt" : "a", - "lu" : "a", - "lv" : "a", - "ly" : "a", - "ma" : "a", - "mc" : "a", - "md" : "a", - "me" : "a", - "mk" : "a", - "mt" : "a", - "nl" : "a", - "no" : "a", - "pl" : "a", - "ps" : "a", - "pt" : "a", - "ro" : "a", - "rs" : "a", - "se" : "a", - "si" : "a", - "sj" : "a", - "sk" : "a", - "sm" : "a", - "sy" : "a", - "tn" : "a", - "tr" : "a", - "ua" : "a", - "uk" : "a", - "us" : "a", - "va" : "a", - "world" : "a", - "xk" : "a" + "warning" : "missing_agribalyse_match" }, "missing" : { + "categories" : 1, "ingredients" : 1, "labels" : 1, "origins" : 1, "packagings" : 1 }, - "missing_data_warning" : 1, + "missing_agribalyse_match_warning" : 1, "missing_key_data" : 1, - "score" : 80, - "scores" : { - "ad" : 80, - "al" : 80, - "at" : 80, - "ax" : 80, - "ba" : 80, - "be" : 80, - "bg" : 80, - "ch" : 80, - "cy" : 80, - "cz" : 80, - "de" : 80, - "dk" : 80, - "dz" : 80, - "ee" : 80, - "eg" : 80, - "es" : 80, - "fi" : 80, - "fo" : 80, - "fr" : 80, - "gg" : 80, - "gi" : 80, - "gr" : 80, - "hr" : 80, - "hu" : 80, - "ie" : 80, - "il" : 80, - "im" : 80, - "is" : 80, - "it" : 80, - "je" : 80, - "lb" : 80, - "li" : 80, - "lt" : 80, - "lu" : 80, - "lv" : 80, - "ly" : 80, - "ma" : 80, - "mc" : 80, - "md" : 80, - "me" : 80, - "mk" : 80, - "mt" : 80, - "nl" : 80, - "no" : 80, - "pl" : 80, - "ps" : 80, - "pt" : 80, - "ro" : 80, - "rs" : 80, - "se" : 80, - "si" : 80, - "sj" : 80, - "sk" : 80, - "sm" : 80, - "sy" : 80, - "tn" : 80, - "tr" : 80, - "ua" : 80, - "uk" : 80, - "us" : 80, - "va" : 80, - "world" : 80, - "xk" : 80 - }, - "status" : "known" + "status" : "unknown" }, - "ecoscore_grade" : "a", - "ecoscore_score" : 80, + "ecoscore_grade" : "unknown", "ecoscore_tags" : [ - "a" + "unknown" ], "editors_tags" : [ "moderator", @@ -542,13 +358,9 @@ "lc" : "en", "main_countries_tags" : [], "misc_tags" : [ - "en:ecoscore-computed", "en:ecoscore-extended-data-not-computed", - "en:ecoscore-missing-data-labels", - "en:ecoscore-missing-data-no-packagings", - "en:ecoscore-missing-data-origins", - "en:ecoscore-missing-data-packagings", - "en:ecoscore-missing-data-warning", + "en:ecoscore-not-computed", + "en:nutriscore-missing-category", "en:nutriscore-missing-nutrition-data", "en:nutriscore-missing-nutrition-data-energy", "en:nutriscore-missing-nutrition-data-fat", @@ -576,12 +388,12 @@ "nutriments" : {}, "nutriscore" : { "2021" : { - "category_available" : 1, + "category_available" : 0, "data" : { "energy" : null, "fiber" : 0, "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, - "is_beverage" : 1, + "is_beverage" : 0, "is_cheese" : 0, "is_fat" : 0, "is_water" : 0, @@ -592,21 +404,20 @@ }, "grade" : "unknown", "nutrients_available" : 0, - "nutriscore_applicable" : 1, + "nutriscore_applicable" : 0, "nutriscore_computed" : 0 }, "2023" : { - "category_available" : 1, + "category_available" : 0, "data" : { "energy" : null, "fiber" : null, "fruits_vegetables_legumes" : null, - "is_beverage" : 1, + "is_beverage" : 0, "is_cheese" : 0, "is_fat_oil_nuts_seeds" : 0, "is_red_meat_product" : 0, "is_water" : 0, - "non_nutritive_sweeteners" : null, "proteins" : null, "salt" : null, "saturated_fat" : null, @@ -614,7 +425,7 @@ }, "grade" : "unknown", "nutrients_available" : 0, - "nutriscore_applicable" : 1, + "nutriscore_applicable" : 0, "nutriscore_computed" : 0 } }, @@ -634,10 +445,10 @@ "nutrition_grade_fr" : "unknown", "nutrition_grades" : "unknown", "nutrition_grades_tags" : [ - "not-applicable" + "unknown" ], - "nutrition_score_beverage" : 1, - "nutrition_score_debug" : "missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", + "nutrition_score_beverage" : 0, + "nutrition_score_debug" : "no score when the product does not have a category - missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", "nutrition_score_warning_no_fiber" : 1, "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, "packaging_materials_tags" : [], @@ -649,20 +460,20 @@ "pnns_groups_1" : "unknown", "pnns_groups_1_tags" : [ "unknown", - "missing-association" + "missing-category" ], "pnns_groups_2" : "unknown", "pnns_groups_2_tags" : [ "unknown", - "missing-association" + "missing-category" ], "popularity_key" : 0, - "product_name" : "Test product", - "product_name_en" : "Test product", + "product_name" : "Test product 1", + "product_name_en" : "Test product 1", "product_type" : "beauty", "removed_countries_tags" : [], "rev" : 4, - "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-completed, en:brands-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", + "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-to-be-completed, en:brands-to-be-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", "states_hierarchy" : [ "en:to-be-completed", "en:nutrition-facts-to-be-completed", @@ -671,8 +482,8 @@ "en:packaging-code-to-be-completed", "en:characteristics-to-be-completed", "en:origins-to-be-completed", - "en:categories-completed", - "en:brands-completed", + "en:categories-to-be-completed", + "en:brands-to-be-completed", "en:packaging-to-be-completed", "en:quantity-to-be-completed", "en:product-name-completed", @@ -686,8 +497,8 @@ "en:packaging-code-to-be-completed", "en:characteristics-to-be-completed", "en:origins-to-be-completed", - "en:categories-completed", - "en:brands-completed", + "en:categories-to-be-completed", + "en:brands-to-be-completed", "en:packaging-to-be-completed", "en:quantity-to-be-completed", "en:product-name-completed", diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.json new file mode 100644 index 0000000000000..23ef6d58e4b52 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.json @@ -0,0 +1,522 @@ +{ + "code" : "1234567890200", + "errors" : [], + "product" : { + "_id" : "1234567890200", + "_keywords" : [ + "product", + "test" + ], + "added_countries_tags" : [], + "allergens" : "", + "allergens_from_ingredients" : "", + "allergens_from_user" : "(en) ", + "allergens_hierarchy" : [], + "allergens_tags" : [], + "categories_properties" : {}, + "categories_properties_tags" : [ + "all-products", + "categories-unknown", + "agribalyse-food-code-unknown", + "agribalyse-proxy-food-code-unknown", + "ciqual-food-code-unknown", + "agribalyse-unknown" + ], + "checkers_tags" : [], + "code" : "1234567890200", + "codes_tags" : [ + "code-13", + "1234567890xxx", + "123456789xxxx", + "12345678xxxxx", + "1234567xxxxxx", + "123456xxxxxxx", + "12345xxxxxxxx", + "1234xxxxxxxxx", + "123xxxxxxxxxx", + "12xxxxxxxxxxx", + "1xxxxxxxxxxxx" + ], + "complete" : 0, + "completeness" : 0.1, + "correctors_tags" : [ + "moderator" + ], + "countries" : "en:france", + "countries_hierarchy" : [ + "en:france" + ], + "countries_lc" : "en", + "countries_tags" : [ + "en:france" + ], + "created_t" : "--ignore--", + "creator" : "openfoodfacts-contributors", + "data_quality_bugs_tags" : [], + "data_quality_errors_tags" : [], + "data_quality_info_tags" : [ + "en:no-packaging-data", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-unknown", + "en:food-groups-2-unknown", + "en:food-groups-3-unknown" + ], + "data_quality_tags" : [ + "en:no-packaging-data", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-unknown", + "en:food-groups-2-unknown", + "en:food-groups-3-unknown", + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label", + "en:ecoscore-threatened-species-ingredients-missing" + ], + "data_quality_warnings_tags" : [ + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label", + "en:ecoscore-threatened-species-ingredients-missing" + ], + "ecoscore_data" : { + "adjustments" : { + "origins_of_ingredients" : { + "aggregated_origins" : [ + { + "origin" : "en:unknown", + "percent" : 100 + } + ], + "epi_score" : 0, + "epi_value" : -5, + "origins_from_categories" : [ + "en:unknown" + ], + "origins_from_origins_field" : [ + "en:unknown" + ], + "transportation_scores" : { + "ad" : 0, + "al" : 0, + "at" : 0, + "ax" : 0, + "ba" : 0, + "be" : 0, + "bg" : 0, + "ch" : 0, + "cy" : 0, + "cz" : 0, + "de" : 0, + "dk" : 0, + "dz" : 0, + "ee" : 0, + "eg" : 0, + "es" : 0, + "fi" : 0, + "fo" : 0, + "fr" : 0, + "gg" : 0, + "gi" : 0, + "gr" : 0, + "hr" : 0, + "hu" : 0, + "ie" : 0, + "il" : 0, + "im" : 0, + "is" : 0, + "it" : 0, + "je" : 0, + "lb" : 0, + "li" : 0, + "lt" : 0, + "lu" : 0, + "lv" : 0, + "ly" : 0, + "ma" : 0, + "mc" : 0, + "md" : 0, + "me" : 0, + "mk" : 0, + "mt" : 0, + "nl" : 0, + "no" : 0, + "pl" : 0, + "ps" : 0, + "pt" : 0, + "ro" : 0, + "rs" : 0, + "se" : 0, + "si" : 0, + "sj" : 0, + "sk" : 0, + "sm" : 0, + "sy" : 0, + "tn" : 0, + "tr" : 0, + "ua" : 0, + "uk" : 0, + "us" : 0, + "va" : 0, + "world" : 0, + "xk" : 0 + }, + "transportation_values" : { + "ad" : 0, + "al" : 0, + "at" : 0, + "ax" : 0, + "ba" : 0, + "be" : 0, + "bg" : 0, + "ch" : 0, + "cy" : 0, + "cz" : 0, + "de" : 0, + "dk" : 0, + "dz" : 0, + "ee" : 0, + "eg" : 0, + "es" : 0, + "fi" : 0, + "fo" : 0, + "fr" : 0, + "gg" : 0, + "gi" : 0, + "gr" : 0, + "hr" : 0, + "hu" : 0, + "ie" : 0, + "il" : 0, + "im" : 0, + "is" : 0, + "it" : 0, + "je" : 0, + "lb" : 0, + "li" : 0, + "lt" : 0, + "lu" : 0, + "lv" : 0, + "ly" : 0, + "ma" : 0, + "mc" : 0, + "md" : 0, + "me" : 0, + "mk" : 0, + "mt" : 0, + "nl" : 0, + "no" : 0, + "pl" : 0, + "ps" : 0, + "pt" : 0, + "ro" : 0, + "rs" : 0, + "se" : 0, + "si" : 0, + "sj" : 0, + "sk" : 0, + "sm" : 0, + "sy" : 0, + "tn" : 0, + "tr" : 0, + "ua" : 0, + "uk" : 0, + "us" : 0, + "va" : 0, + "world" : 0, + "xk" : 0 + }, + "values" : { + "ad" : -5, + "al" : -5, + "at" : -5, + "ax" : -5, + "ba" : -5, + "be" : -5, + "bg" : -5, + "ch" : -5, + "cy" : -5, + "cz" : -5, + "de" : -5, + "dk" : -5, + "dz" : -5, + "ee" : -5, + "eg" : -5, + "es" : -5, + "fi" : -5, + "fo" : -5, + "fr" : -5, + "gg" : -5, + "gi" : -5, + "gr" : -5, + "hr" : -5, + "hu" : -5, + "ie" : -5, + "il" : -5, + "im" : -5, + "is" : -5, + "it" : -5, + "je" : -5, + "lb" : -5, + "li" : -5, + "lt" : -5, + "lu" : -5, + "lv" : -5, + "ly" : -5, + "ma" : -5, + "mc" : -5, + "md" : -5, + "me" : -5, + "mk" : -5, + "mt" : -5, + "nl" : -5, + "no" : -5, + "pl" : -5, + "ps" : -5, + "pt" : -5, + "ro" : -5, + "rs" : -5, + "se" : -5, + "si" : -5, + "sj" : -5, + "sk" : -5, + "sm" : -5, + "sy" : -5, + "tn" : -5, + "tr" : -5, + "ua" : -5, + "uk" : -5, + "us" : -5, + "va" : -5, + "world" : -5, + "xk" : -5 + }, + "warning" : "origins_are_100_percent_unknown" + }, + "packaging" : { + "value" : -15, + "warning" : "packaging_data_missing" + }, + "production_system" : { + "labels" : [], + "value" : 0, + "warning" : "no_label" + }, + "threatened_species" : { + "warning" : "ingredients_missing" + } + }, + "agribalyse" : { + "warning" : "missing_agribalyse_match" + }, + "missing" : { + "categories" : 1, + "ingredients" : 1, + "labels" : 1, + "origins" : 1, + "packagings" : 1 + }, + "missing_agribalyse_match_warning" : 1, + "missing_key_data" : 1, + "status" : "unknown" + }, + "ecoscore_grade" : "unknown", + "ecoscore_tags" : [ + "unknown" + ], + "editors_tags" : [ + "moderator", + "openfoodfacts-contributors" + ], + "entry_dates_tags" : "--ignore--", + "food_groups_tags" : [], + "id" : "1234567890200", + "informers_tags" : [ + "openfoodfacts-contributors" + ], + "ingredients_lc" : "en", + "interface_version_created" : "20221102/api/v3", + "interface_version_modified" : "20150316.jqm2", + "lang" : "en", + "languages" : { + "en:english" : 1 + }, + "languages_codes" : { + "en" : 1 + }, + "languages_hierarchy" : [ + "en:english" + ], + "languages_tags" : [ + "en:english", + "en:1" + ], + "last_edit_dates_tags" : "--ignore--", + "last_editor" : "moderator", + "last_modified_by" : "moderator", + "last_modified_t" : "--ignore--", + "last_updated_t" : "--ignore--", + "lc" : "en", + "main_countries_tags" : [], + "misc_tags" : [ + "en:ecoscore-extended-data-not-computed", + "en:ecoscore-not-computed", + "en:nutriscore-missing-category", + "en:nutriscore-missing-nutrition-data", + "en:nutriscore-missing-nutrition-data-energy", + "en:nutriscore-missing-nutrition-data-fat", + "en:nutriscore-missing-nutrition-data-proteins", + "en:nutriscore-missing-nutrition-data-saturated-fat", + "en:nutriscore-missing-nutrition-data-sodium", + "en:nutriscore-missing-nutrition-data-sugars", + "en:nutriscore-not-computed", + "en:nutrition-no-fiber", + "en:nutrition-no-fiber-or-fruits-vegetables-nuts", + "en:nutrition-no-fruits-vegetables-nuts", + "en:nutrition-not-enough-data-to-compute-nutrition-score", + "en:packagings-empty", + "en:packagings-not-complete", + "en:packagings-number-of-components-0", + "en:main-countries-new-product" + ], + "nova_group_debug" : "no nova group when the product does not have ingredients", + "nova_group_error" : "missing_ingredients", + "nova_groups_tags" : [ + "unknown" + ], + "nutrient_levels" : {}, + "nutrient_levels_tags" : [], + "nutriments" : {}, + "nutriscore" : { + "2021" : { + "category_available" : 0, + "data" : { + "energy" : null, + "fiber" : 0, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, + "is_beverage" : 0, + "is_cheese" : 0, + "is_fat" : 0, + "is_water" : 0, + "proteins" : null, + "saturated_fat" : null, + "sodium" : null, + "sugars" : null + }, + "grade" : "unknown", + "nutrients_available" : 0, + "nutriscore_applicable" : 0, + "nutriscore_computed" : 0 + }, + "2023" : { + "category_available" : 0, + "data" : { + "energy" : null, + "fiber" : null, + "fruits_vegetables_legumes" : null, + "is_beverage" : 0, + "is_cheese" : 0, + "is_fat_oil_nuts_seeds" : 0, + "is_red_meat_product" : 0, + "is_water" : 0, + "proteins" : null, + "salt" : null, + "saturated_fat" : null, + "sugars" : null + }, + "grade" : "unknown", + "nutrients_available" : 0, + "nutriscore_applicable" : 0, + "nutriscore_computed" : 0 + } + }, + "nutriscore_2021_tags" : [ + "unknown" + ], + "nutriscore_2023_tags" : [ + "unknown" + ], + "nutriscore_grade" : "unknown", + "nutriscore_tags" : [ + "unknown" + ], + "nutriscore_version" : "2021", + "nutrition_data_per" : "100g", + "nutrition_data_prepared_per" : "100g", + "nutrition_grade_fr" : "unknown", + "nutrition_grades" : "unknown", + "nutrition_grades_tags" : [ + "unknown" + ], + "nutrition_score_beverage" : 0, + "nutrition_score_debug" : "no score when the product does not have a category - missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", + "nutrition_score_warning_no_fiber" : 1, + "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, + "packaging_materials_tags" : [], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [], + "packagings" : [], + "packagings_materials" : {}, + "photographers_tags" : [], + "pnns_groups_1" : "unknown", + "pnns_groups_1_tags" : [ + "unknown", + "missing-category" + ], + "pnns_groups_2" : "unknown", + "pnns_groups_2_tags" : [ + "unknown", + "missing-category" + ], + "popularity_key" : 0, + "product_name" : "Test product 2", + "product_name_en" : "Test product 2", + "product_type" : "product", + "removed_countries_tags" : [], + "rev" : 2, + "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-to-be-completed, en:brands-to-be-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", + "states_hierarchy" : [ + "en:to-be-completed", + "en:nutrition-facts-to-be-completed", + "en:ingredients-to-be-completed", + "en:expiration-date-to-be-completed", + "en:packaging-code-to-be-completed", + "en:characteristics-to-be-completed", + "en:origins-to-be-completed", + "en:categories-to-be-completed", + "en:brands-to-be-completed", + "en:packaging-to-be-completed", + "en:quantity-to-be-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "states_tags" : [ + "en:to-be-completed", + "en:nutrition-facts-to-be-completed", + "en:ingredients-to-be-completed", + "en:expiration-date-to-be-completed", + "en:packaging-code-to-be-completed", + "en:characteristics-to-be-completed", + "en:origins-to-be-completed", + "en:categories-to-be-completed", + "en:brands-to-be-completed", + "en:packaging-to-be-completed", + "en:quantity-to-be-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "traces" : "", + "traces_from_ingredients" : "", + "traces_from_user" : "(en) ", + "traces_hierarchy" : [], + "traces_tags" : [], + "unknown_nutrients_tags" : [], + "weighers_tags" : [] + }, + "result" : { + "id" : "product_found", + "lc_name" : "Product found", + "name" : "Product found" + }, + "status" : "success", + "warnings" : [] +} diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json index 8bcac13f5c486..a452ef9fff1d8 100644 --- a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json @@ -4,10 +4,7 @@ "product" : { "_id" : "1234567890100", "_keywords" : [ - "beverage", - "brand", "product", - "tea", "test" ], "added_countries_tags" : [], @@ -16,42 +13,14 @@ "allergens_from_user" : "(en) ", "allergens_hierarchy" : [], "allergens_tags" : [], - "brands" : "Test brand", - "brands_tags" : [ - "test-brand" - ], - "categories" : "en:beverages,en:teas", - "categories_hierarchy" : [ - "en:beverages-and-beverages-preparations", - "en:plant-based-foods-and-beverages", - "en:beverages", - "en:hot-beverages", - "en:plant-based-beverages", - "en:teas" - ], - "categories_lc" : "en", - "categories_properties" : { - "agribalyse_food_code:en" : "18020", - "ciqual_food_code:en" : "18020" - }, + "categories_properties" : {}, "categories_properties_tags" : [ "all-products", - "categories-known", - "agribalyse-food-code-18020", - "agribalyse-food-code-known", + "categories-unknown", + "agribalyse-food-code-unknown", "agribalyse-proxy-food-code-unknown", - "ciqual-food-code-18020", - "ciqual-food-code-known", - "agribalyse-known", - "agribalyse-18020" - ], - "categories_tags" : [ - "en:beverages-and-beverages-preparations", - "en:plant-based-foods-and-beverages", - "en:beverages", - "en:hot-beverages", - "en:plant-based-beverages", - "en:teas" + "ciqual-food-code-unknown", + "agribalyse-unknown" ], "checkers_tags" : [], "code" : "1234567890100", @@ -69,7 +38,7 @@ "1xxxxxxxxxxxx" ], "complete" : 0, - "completeness" : 0.3, + "completeness" : 0.1, "correctors_tags" : [], "countries" : "en:france", "countries_hierarchy" : [ @@ -340,175 +309,23 @@ } }, "agribalyse" : { - "agribalyse_food_code" : "18020", - "co2_agriculture" : 0.022870449, - "co2_consumption" : 0.0112323, - "co2_distribution" : 0.0001567597, - "co2_packaging" : 0.0026323455, - "co2_processing" : 0, - "co2_total" : 0.0391613535, - "co2_transportation" : 0.0022694993, - "code" : "18020", - "dqr" : "2.98", - "ef_agriculture" : 0.0081036052, - "ef_consumption" : 0.0043474982, - "ef_distribution" : 4.4793367e-05, - "ef_packaging" : 0.00021662681, - "ef_processing" : 0, - "ef_total" : 0.012940544857, - "ef_transportation" : 0.00022802128, - "is_beverage" : 1, - "name_en" : "Tea, brewed, without sugar", - "name_fr" : "Thé infusé, non sucré", - "score" : 100, - "version" : "3.1.1" - }, - "grade" : "a", - "grades" : { - "ad" : "a", - "al" : "a", - "at" : "a", - "ax" : "a", - "ba" : "a", - "be" : "a", - "bg" : "a", - "ch" : "a", - "cy" : "a", - "cz" : "a", - "de" : "a", - "dk" : "a", - "dz" : "a", - "ee" : "a", - "eg" : "a", - "es" : "a", - "fi" : "a", - "fo" : "a", - "fr" : "a", - "gg" : "a", - "gi" : "a", - "gr" : "a", - "hr" : "a", - "hu" : "a", - "ie" : "a", - "il" : "a", - "im" : "a", - "is" : "a", - "it" : "a", - "je" : "a", - "lb" : "a", - "li" : "a", - "lt" : "a", - "lu" : "a", - "lv" : "a", - "ly" : "a", - "ma" : "a", - "mc" : "a", - "md" : "a", - "me" : "a", - "mk" : "a", - "mt" : "a", - "nl" : "a", - "no" : "a", - "pl" : "a", - "ps" : "a", - "pt" : "a", - "ro" : "a", - "rs" : "a", - "se" : "a", - "si" : "a", - "sj" : "a", - "sk" : "a", - "sm" : "a", - "sy" : "a", - "tn" : "a", - "tr" : "a", - "ua" : "a", - "uk" : "a", - "us" : "a", - "va" : "a", - "world" : "a", - "xk" : "a" + "warning" : "missing_agribalyse_match" }, "missing" : { + "categories" : 1, "ingredients" : 1, "labels" : 1, "origins" : 1, "packagings" : 1 }, - "missing_data_warning" : 1, + "missing_agribalyse_match_warning" : 1, "missing_key_data" : 1, - "score" : 80, - "scores" : { - "ad" : 80, - "al" : 80, - "at" : 80, - "ax" : 80, - "ba" : 80, - "be" : 80, - "bg" : 80, - "ch" : 80, - "cy" : 80, - "cz" : 80, - "de" : 80, - "dk" : 80, - "dz" : 80, - "ee" : 80, - "eg" : 80, - "es" : 80, - "fi" : 80, - "fo" : 80, - "fr" : 80, - "gg" : 80, - "gi" : 80, - "gr" : 80, - "hr" : 80, - "hu" : 80, - "ie" : 80, - "il" : 80, - "im" : 80, - "is" : 80, - "it" : 80, - "je" : 80, - "lb" : 80, - "li" : 80, - "lt" : 80, - "lu" : 80, - "lv" : 80, - "ly" : 80, - "ma" : 80, - "mc" : 80, - "md" : 80, - "me" : 80, - "mk" : 80, - "mt" : 80, - "nl" : 80, - "no" : 80, - "pl" : 80, - "ps" : 80, - "pt" : 80, - "ro" : 80, - "rs" : 80, - "se" : 80, - "si" : 80, - "sj" : 80, - "sk" : 80, - "sm" : 80, - "sy" : 80, - "tn" : 80, - "tr" : 80, - "ua" : 80, - "uk" : 80, - "us" : 80, - "va" : 80, - "world" : 80, - "xk" : 80 - }, - "status" : "known" + "scores" : {}, + "status" : "unknown" }, - "ecoscore_grade" : "a", - "ecoscore_score" : 80, + "ecoscore_grade" : "unknown", "ecoscore_tags" : [ - "a" + "unknown" ], "editors_tags" : [ "openfoodfacts-contributors" @@ -542,13 +359,9 @@ "lc" : "en", "main_countries_tags" : [], "misc_tags" : [ - "en:ecoscore-computed", "en:ecoscore-extended-data-not-computed", - "en:ecoscore-missing-data-labels", - "en:ecoscore-missing-data-no-packagings", - "en:ecoscore-missing-data-origins", - "en:ecoscore-missing-data-packagings", - "en:ecoscore-missing-data-warning", + "en:ecoscore-not-computed", + "en:nutriscore-missing-category", "en:nutriscore-missing-nutrition-data", "en:nutriscore-missing-nutrition-data-energy", "en:nutriscore-missing-nutrition-data-fat", @@ -576,12 +389,12 @@ "nutriments" : {}, "nutriscore" : { "2021" : { - "category_available" : 1, + "category_available" : 0, "data" : { "energy" : null, "fiber" : 0, "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, - "is_beverage" : 1, + "is_beverage" : 0, "is_cheese" : 0, "is_fat" : 0, "is_water" : 0, @@ -592,21 +405,20 @@ }, "grade" : "unknown", "nutrients_available" : 0, - "nutriscore_applicable" : 1, + "nutriscore_applicable" : 0, "nutriscore_computed" : 0 }, "2023" : { - "category_available" : 1, + "category_available" : 0, "data" : { "energy" : null, "fiber" : null, "fruits_vegetables_legumes" : null, - "is_beverage" : 1, + "is_beverage" : 0, "is_cheese" : 0, "is_fat_oil_nuts_seeds" : 0, "is_red_meat_product" : 0, "is_water" : 0, - "non_nutritive_sweeteners" : null, "proteins" : null, "salt" : null, "saturated_fat" : null, @@ -614,7 +426,7 @@ }, "grade" : "unknown", "nutrients_available" : 0, - "nutriscore_applicable" : 1, + "nutriscore_applicable" : 0, "nutriscore_computed" : 0 } }, @@ -634,10 +446,10 @@ "nutrition_grade_fr" : "unknown", "nutrition_grades" : "unknown", "nutrition_grades_tags" : [ - "not-applicable" + "unknown" ], - "nutrition_score_beverage" : 1, - "nutrition_score_debug" : "missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", + "nutrition_score_beverage" : 0, + "nutrition_score_debug" : "no score when the product does not have a category - missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", "nutrition_score_warning_no_fiber" : 1, "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, "packaging_materials_tags" : [], @@ -649,20 +461,20 @@ "pnns_groups_1" : "unknown", "pnns_groups_1_tags" : [ "unknown", - "missing-association" + "missing-category" ], "pnns_groups_2" : "unknown", "pnns_groups_2_tags" : [ "unknown", - "missing-association" + "missing-category" ], "popularity_key" : 0, - "product_name" : "Test product", - "product_name_en" : "Test product", + "product_name" : "Test product 1", + "product_name_en" : "Test product 1", "product_type" : "food", "removed_countries_tags" : [], "rev" : 2, - "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-completed, en:brands-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", + "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-to-be-completed, en:brands-to-be-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", "states_hierarchy" : [ "en:to-be-completed", "en:nutrition-facts-to-be-completed", @@ -671,8 +483,8 @@ "en:packaging-code-to-be-completed", "en:characteristics-to-be-completed", "en:origins-to-be-completed", - "en:categories-completed", - "en:brands-completed", + "en:categories-to-be-completed", + "en:brands-to-be-completed", "en:packaging-to-be-completed", "en:quantity-to-be-completed", "en:product-name-completed", @@ -686,8 +498,8 @@ "en:packaging-code-to-be-completed", "en:characteristics-to-be-completed", "en:origins-to-be-completed", - "en:categories-completed", - "en:brands-completed", + "en:categories-to-be-completed", + "en:brands-to-be-completed", "en:packaging-to-be-completed", "en:quantity-to-be-completed", "en:product-name-completed", diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json index abee34e0df848..66eca2ce40fe5 100644 --- a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json @@ -4,10 +4,7 @@ "product" : { "_id" : "1234567890102", "_keywords" : [ - "beverage", - "brand", "product", - "tea", "test" ], "added_countries_tags" : [], @@ -16,42 +13,14 @@ "allergens_from_user" : "(en) ", "allergens_hierarchy" : [], "allergens_tags" : [], - "brands" : "Test brand", - "brands_tags" : [ - "test-brand" - ], - "categories" : "en:beverages,en:teas", - "categories_hierarchy" : [ - "en:beverages-and-beverages-preparations", - "en:plant-based-foods-and-beverages", - "en:beverages", - "en:hot-beverages", - "en:plant-based-beverages", - "en:teas" - ], - "categories_lc" : "en", - "categories_properties" : { - "agribalyse_food_code:en" : "18020", - "ciqual_food_code:en" : "18020" - }, + "categories_properties" : {}, "categories_properties_tags" : [ "all-products", - "categories-known", - "agribalyse-food-code-18020", - "agribalyse-food-code-known", + "categories-unknown", + "agribalyse-food-code-unknown", "agribalyse-proxy-food-code-unknown", - "ciqual-food-code-18020", - "ciqual-food-code-known", - "agribalyse-known", - "agribalyse-18020" - ], - "categories_tags" : [ - "en:beverages-and-beverages-preparations", - "en:plant-based-foods-and-beverages", - "en:beverages", - "en:hot-beverages", - "en:plant-based-beverages", - "en:teas" + "ciqual-food-code-unknown", + "agribalyse-unknown" ], "checkers_tags" : [], "code" : "1234567890102", @@ -69,7 +38,7 @@ "1xxxxxxxxxxxx" ], "complete" : 0, - "completeness" : 0.3, + "completeness" : 0.1, "correctors_tags" : [ "moderator" ], @@ -342,175 +311,23 @@ } }, "agribalyse" : { - "agribalyse_food_code" : "18020", - "co2_agriculture" : 0.022870449, - "co2_consumption" : 0.0112323, - "co2_distribution" : 0.0001567597, - "co2_packaging" : 0.0026323455, - "co2_processing" : 0, - "co2_total" : 0.0391613535, - "co2_transportation" : 0.0022694993, - "code" : "18020", - "dqr" : "2.98", - "ef_agriculture" : 0.0081036052, - "ef_consumption" : 0.0043474982, - "ef_distribution" : 4.4793367e-05, - "ef_packaging" : 0.00021662681, - "ef_processing" : 0, - "ef_total" : 0.012940544857, - "ef_transportation" : 0.00022802128, - "is_beverage" : 1, - "name_en" : "Tea, brewed, without sugar", - "name_fr" : "Thé infusé, non sucré", - "score" : 100, - "version" : "3.1.1" - }, - "grade" : "a", - "grades" : { - "ad" : "a", - "al" : "a", - "at" : "a", - "ax" : "a", - "ba" : "a", - "be" : "a", - "bg" : "a", - "ch" : "a", - "cy" : "a", - "cz" : "a", - "de" : "a", - "dk" : "a", - "dz" : "a", - "ee" : "a", - "eg" : "a", - "es" : "a", - "fi" : "a", - "fo" : "a", - "fr" : "a", - "gg" : "a", - "gi" : "a", - "gr" : "a", - "hr" : "a", - "hu" : "a", - "ie" : "a", - "il" : "a", - "im" : "a", - "is" : "a", - "it" : "a", - "je" : "a", - "lb" : "a", - "li" : "a", - "lt" : "a", - "lu" : "a", - "lv" : "a", - "ly" : "a", - "ma" : "a", - "mc" : "a", - "md" : "a", - "me" : "a", - "mk" : "a", - "mt" : "a", - "nl" : "a", - "no" : "a", - "pl" : "a", - "ps" : "a", - "pt" : "a", - "ro" : "a", - "rs" : "a", - "se" : "a", - "si" : "a", - "sj" : "a", - "sk" : "a", - "sm" : "a", - "sy" : "a", - "tn" : "a", - "tr" : "a", - "ua" : "a", - "uk" : "a", - "us" : "a", - "va" : "a", - "world" : "a", - "xk" : "a" + "warning" : "missing_agribalyse_match" }, "missing" : { + "categories" : 1, "ingredients" : 1, "labels" : 1, "origins" : 1, "packagings" : 1 }, - "missing_data_warning" : 1, + "missing_agribalyse_match_warning" : 1, "missing_key_data" : 1, - "score" : 80, - "scores" : { - "ad" : 80, - "al" : 80, - "at" : 80, - "ax" : 80, - "ba" : 80, - "be" : 80, - "bg" : 80, - "ch" : 80, - "cy" : 80, - "cz" : 80, - "de" : 80, - "dk" : 80, - "dz" : 80, - "ee" : 80, - "eg" : 80, - "es" : 80, - "fi" : 80, - "fo" : 80, - "fr" : 80, - "gg" : 80, - "gi" : 80, - "gr" : 80, - "hr" : 80, - "hu" : 80, - "ie" : 80, - "il" : 80, - "im" : 80, - "is" : 80, - "it" : 80, - "je" : 80, - "lb" : 80, - "li" : 80, - "lt" : 80, - "lu" : 80, - "lv" : 80, - "ly" : 80, - "ma" : 80, - "mc" : 80, - "md" : 80, - "me" : 80, - "mk" : 80, - "mt" : 80, - "nl" : 80, - "no" : 80, - "pl" : 80, - "ps" : 80, - "pt" : 80, - "ro" : 80, - "rs" : 80, - "se" : 80, - "si" : 80, - "sj" : 80, - "sk" : 80, - "sm" : 80, - "sy" : 80, - "tn" : 80, - "tr" : 80, - "ua" : 80, - "uk" : 80, - "us" : 80, - "va" : 80, - "world" : 80, - "xk" : 80 - }, - "status" : "known" + "scores" : {}, + "status" : "unknown" }, - "ecoscore_grade" : "a", - "ecoscore_score" : 80, + "ecoscore_grade" : "unknown", "ecoscore_tags" : [ - "a" + "unknown" ], "editors_tags" : [ "moderator", @@ -547,13 +364,9 @@ "lc" : "en", "main_countries_tags" : [], "misc_tags" : [ - "en:ecoscore-computed", "en:ecoscore-extended-data-not-computed", - "en:ecoscore-missing-data-labels", - "en:ecoscore-missing-data-no-packagings", - "en:ecoscore-missing-data-origins", - "en:ecoscore-missing-data-packagings", - "en:ecoscore-missing-data-warning", + "en:ecoscore-not-computed", + "en:nutriscore-missing-category", "en:nutriscore-missing-nutrition-data", "en:nutriscore-missing-nutrition-data-energy", "en:nutriscore-missing-nutrition-data-fat", @@ -581,12 +394,12 @@ "nutriments" : {}, "nutriscore" : { "2021" : { - "category_available" : 1, + "category_available" : 0, "data" : { "energy" : null, "fiber" : 0, "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, - "is_beverage" : 1, + "is_beverage" : 0, "is_cheese" : 0, "is_fat" : 0, "is_water" : 0, @@ -597,21 +410,20 @@ }, "grade" : "unknown", "nutrients_available" : 0, - "nutriscore_applicable" : 1, + "nutriscore_applicable" : 0, "nutriscore_computed" : 0 }, "2023" : { - "category_available" : 1, + "category_available" : 0, "data" : { "energy" : null, "fiber" : null, "fruits_vegetables_legumes" : null, - "is_beverage" : 1, + "is_beverage" : 0, "is_cheese" : 0, "is_fat_oil_nuts_seeds" : 0, "is_red_meat_product" : 0, "is_water" : 0, - "non_nutritive_sweeteners" : null, "proteins" : null, "salt" : null, "saturated_fat" : null, @@ -619,7 +431,7 @@ }, "grade" : "unknown", "nutrients_available" : 0, - "nutriscore_applicable" : 1, + "nutriscore_applicable" : 0, "nutriscore_computed" : 0 } }, @@ -639,10 +451,10 @@ "nutrition_grade_fr" : "unknown", "nutrition_grades" : "unknown", "nutrition_grades_tags" : [ - "not-applicable" + "unknown" ], - "nutrition_score_beverage" : 1, - "nutrition_score_debug" : "missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", + "nutrition_score_beverage" : 0, + "nutrition_score_debug" : "no score when the product does not have a category - missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", "nutrition_score_warning_no_fiber" : 1, "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, "packaging_materials_tags" : [], @@ -654,20 +466,20 @@ "pnns_groups_1" : "unknown", "pnns_groups_1_tags" : [ "unknown", - "missing-association" + "missing-category" ], "pnns_groups_2" : "unknown", "pnns_groups_2_tags" : [ "unknown", - "missing-association" + "missing-category" ], "popularity_key" : 0, - "product_name" : "Test product", - "product_name_en" : "Test product", + "product_name" : "Test product 1", + "product_name_en" : "Test product 1", "product_type" : "food", "removed_countries_tags" : [], "rev" : 3, - "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-completed, en:brands-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", + "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-to-be-completed, en:brands-to-be-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", "states_hierarchy" : [ "en:to-be-completed", "en:nutrition-facts-to-be-completed", @@ -676,8 +488,8 @@ "en:packaging-code-to-be-completed", "en:characteristics-to-be-completed", "en:origins-to-be-completed", - "en:categories-completed", - "en:brands-completed", + "en:categories-to-be-completed", + "en:brands-to-be-completed", "en:packaging-to-be-completed", "en:quantity-to-be-completed", "en:product-name-completed", @@ -691,8 +503,8 @@ "en:packaging-code-to-be-completed", "en:characteristics-to-be-completed", "en:origins-to-be-completed", - "en:categories-completed", - "en:brands-completed", + "en:categories-to-be-completed", + "en:brands-to-be-completed", "en:packaging-to-be-completed", "en:quantity-to-be-completed", "en:product-name-completed", From 1bf5c02eb5c9c9e8f71f0b86759f1c201d7c5f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Tue, 29 Oct 2024 17:12:20 +0100 Subject: [PATCH 4/9] update tests --- ..._v2_product_code_and_product_type_change.t | 19 +++++++++++++++++-- .../get-product-with-web-interface.html | 9 +++++++++ .../search-all-products.json | 8 ++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/search-all-products.json diff --git a/tests/integration/api_v2_product_code_and_product_type_change.t b/tests/integration/api_v2_product_code_and_product_type_change.t index adf35ebadb58f..6f5af56701e11 100644 --- a/tests/integration/api_v2_product_code_and_product_type_change.t +++ b/tests/integration/api_v2_product_code_and_product_type_change.t @@ -104,7 +104,15 @@ my $tests_ref = [ }, ua => $moderator_ua, }, - # Get the product + # Get the product with web interface + { + test_case => 'get-product-with-web-interface', + method => 'GET', + path => '/product/1234567890102', + expected_status_code => 302, + expected_type => 'html', + }, + # Get the product with API v3 { test_case => 'get-product-obf', method => 'GET', @@ -136,13 +144,20 @@ my $tests_ref = [ }, ua => $moderator_ua, }, - # Get the product + # Get the product with API v3 { test_case => 'get-product-opf', method => 'GET', path => '/api/v3/product/1234567890200', expected_status_code => 200, }, + # Search all products to check moved products are not on the off MongoDB database anymore + { + test_case => 'search-all-products', + method => 'GET', + path => '/cgi/search.pl?action=process&json=1&no_cache=1', + expected_status_code => 200, + }, ]; execute_api_tests(__FILE__, $tests_ref); diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html new file mode 100644 index 0000000000000..2169ef6527c2a --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html @@ -0,0 +1,9 @@ + + +302 Found + +

Found

+

The document has moved here.

+
+
Apache/2.4.62 (Debian) Server at world.openfoodfacts.localhost Port 80
+ diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/search-all-products.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/search-all-products.json new file mode 100644 index 0000000000000..8c298a45fd1a0 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/search-all-products.json @@ -0,0 +1,8 @@ +{ + "count" : 0, + "page" : 1, + "page_count" : 0, + "page_size" : 50, + "products" : [], + "skip" : 0 +} From 268f6542e1dcdcbaa864e097daccac141cf0d74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Tue, 29 Oct 2024 17:27:42 +0100 Subject: [PATCH 5/9] fix tests, add redirect to product's flavor --- lib/ProductOpener/APITest.pm | 13 +- lib/ProductOpener/Config.pm | 135 ++++++++++++++++++ lib/ProductOpener/Config_off.pm | 15 -- lib/ProductOpener/Display.pm | 7 + lib/ProductOpener/URL.pm | 15 +- .../get-product-with-web-interface.html | 2 +- 6 files changed, 167 insertions(+), 20 deletions(-) diff --git a/lib/ProductOpener/APITest.pm b/lib/ProductOpener/APITest.pm index 73c1805efe698..19aaf0eaab5dc 100644 --- a/lib/ProductOpener/APITest.pm +++ b/lib/ProductOpener/APITest.pm @@ -428,6 +428,14 @@ sub execute_request ($test_ref, $ua) { my $response; + # For some tests, we don't want to follow redirects. We want to see the 302 responses, not the response to the final destination + if ((defined $test_ref->{expected_status_code}) and ($test_ref->{expected_status_code} == 302)) { + $ua->max_redirect(0); + } + else { + $ua->max_redirect(3); + } + # Send the request if ($method eq 'OPTIONS') { # not yet supported by our (system) version of HTTP::Request::Common @@ -541,9 +549,10 @@ sub check_request_response ($test_ref, $response, $test_id, $test_dir, $expected my $response_content = $response->decoded_content; - # Check that we don't get an errore message generated by the Apache Server + # Check that we don't get an error message generated by the Apache Server # e.g. "Apache/2.4.56 (Debian) Server at world.openfoodfacts.localhost Port 80" - if ($response_content =~ /Apache.*Server/) { + # unless it is a redirect + if (($response_content =~ /Apache.*Server/) and not($response->code =~ /^3\d\d$/)) { fail("Received an Apache Server generated error message for test $test_case"); diag("Response content: " . $response_content); } diff --git a/lib/ProductOpener/Config.pm b/lib/ProductOpener/Config.pm index bd58f65a3b6e0..93d35196f11fc 100644 --- a/lib/ProductOpener/Config.pm +++ b/lib/ProductOpener/Config.pm @@ -33,6 +33,141 @@ if (not defined $flavor) { } use Module::Load; + autoload("ProductOpener::Config_$flavor"); +# Add values common to all flavors + +# define the normalization applied to change a string to a tag id (in particular for taxonomies) +# tag ids are also used in URLs. + +# unaccent: +# - useful when accents are sometimes ommited (e.g. in French accents are often not present on capital letters), +# either in print, or when typed by users. +# - dangerous if different words (in the same context like ingredients or category names) have the same unaccented form +# lowercase: +# - useful when the same word appears in lowercase, with a first capital letter, or in all caps. + +# IMPORTANT: if you change it, you need to change $BUILD_TAGS_VERSION in Tags.pm + +%ProductOpener::Config::string_normalization_for_lang = ( + # no_language is used for strings that are not in a specific language (e.g. user names) + no_language => { + unaccent => 1, + lowercase => 1, + }, + # default is used for languages that do not have specified values + default => { + unaccent => 0, + lowercase => 1, + }, + # German umlauts should not be converted (e.g. ä -> ae) as there are many conflicts + de => { + unaccent => 0, + lowercase => 1, + }, + # French has very few actual conflicts caused by unaccenting (one counter example is "pâtes" and "pâtés") + # Accents or often not present in capital letters (beginning of word, or in all caps text). + fr => { + unaccent => 1, + lowercase => 1, + }, + # Same for Spanish, Italian and Portuguese + ca => { + unaccent => 1, + lowercase => 1, + }, + es => { + unaccent => 1, + lowercase => 1, + }, + it => { + unaccent => 1, + lowercase => 1, + }, + nl => { + unaccent => 1, + lowercase => 1, + }, + pt => { + unaccent => 1, + lowercase => 1, + }, + sk => { + unaccent => 1, + lowercase => 1, + }, + # English has very few accented words, and they are very often not accented by users or in ingredients lists etc. + en => { + unaccent => 1, + lowercase => 1, + }, + # xx: language less entries, also deaccent + xx => { + unaccent => 1, + lowercase => 1, + }, +); + +%ProductOpener::Config::admins = map {$_ => 1} qw( + alex-off + cha-delh + charlesnepote + gala-nafikova + hangy + manoncorneille + raphael0202 + stephane + tacinte + teolemon + g123k + valimp +); + +=head2 Available product types and flavors + +=cut + +$ProductOpener::Config::options{product_types} = qw(food petfood beauty product); +$ProductOpener::Config::options{product_types_flavors} = { + food => "off", + petfood => "opff", + beauty => "obf", + product => "opf" +}; + +$ProductOpener::Config::options{flavors_product_types} + = {reverse %{$ProductOpener::Config::options{product_types_flavors}}}; + +$ProductOpener::Config::options{product_types_domains} = { + food => "openfoodfacts.org", + petfood => "openpetfoodfacts.org", + beauty => "openbeautyfacts.org", + product => "openproductsfacts.org" +}; + +$ProductOpener::Config::options{other_servers} = { + obf => { + name => "Open Beauty Facts", + mongodb => "obf", + domain => "openbeautyfacts.org", + }, + off => { + name => "Open Food Facts", + mongodb => "off", + domain => "openfoodfacts.org", + }, + opf => { + name => "Open Products Facts", + mongodb => "opf", + domain => "openproductsfacts.org", + }, + opff => { + prefix => "opff", + name => "Open Pet Food Facts", + mongodb => "opff", + domain => "openpetfoodfacts.org", + } +}; + 1; diff --git a/lib/ProductOpener/Config_off.pm b/lib/ProductOpener/Config_off.pm index 4ad4381e84594..fb7be2d05a378 100644 --- a/lib/ProductOpener/Config_off.pm +++ b/lib/ProductOpener/Config_off.pm @@ -177,21 +177,6 @@ $flavor = 'off'; }, ); -%admins = map {$_ => 1} qw( - alex-off - cha-delh - charlesnepote - gala-nafikova - hangy - manoncorneille - raphael0202 - stephane - tacinte - teolemon - g123k - valimp -); - %options = ( site_name => "Open Food Facts", product_type => "food", diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index f9ac54dba4106..95c6b3eedbe2c 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -7918,6 +7918,13 @@ JS $request_ref->{canon_url} = get_world_subdomain() . product_url($product_ref); } + # If the product has a product_type and it is not the product_type of the server, redirect to the correct server + + if ((defined $product_ref->{product_type}) and ($product_ref->{product_type} ne $options{product_type})) { + redirect_to_url($request_ref, 302, + format_subdomain($subdomain, $product_ref->{product_type}) . product_url($product_ref)); + } + # Old UPC-12 in url? Redirect to EAN-13 url # TODO - 2024/10/02 - Temporarily disabled so that we can migrate short barcodes with digits not equal to 8 or greater or equal to 13 # Reenable after all products are migrated. diff --git a/lib/ProductOpener/URL.pm b/lib/ProductOpener/URL.pm index fe253558629cc..11baec22954f8 100644 --- a/lib/ProductOpener/URL.pm +++ b/lib/ProductOpener/URL.pm @@ -62,6 +62,8 @@ use experimental 'smartmatch'; use ProductOpener::Config qw/:all/; use ProductOpener::Paths qw/%BASE_DIRS/; +use Data::DeepAccess qw(deep_get); + =head1 FUNCTIONS =head2 format_subdomain( SUBDOMAIN ) @@ -70,15 +72,22 @@ C returns URL on the basis of subdomain and scheme (http/htt =head3 Arguments +=head4 subdomain + A scalar variable to indicate the subdomain (e.g. "us" or "static") needs to be passed as an argument. +=head4 product_type (optional) + +Defaults to the current server product type. If passed, use the domain for that product type. +(e.g. "beauty" -> "openbeautyfacts.org") + =head3 Return Values The function returns a URL by concatenating scheme, subdomain and server-domain. =cut -sub format_subdomain ($sd) { +sub format_subdomain ($sd, $product_type = undef) { return $sd unless $sd; my $scheme; @@ -89,7 +98,9 @@ sub format_subdomain ($sd) { $scheme = 'http'; } - return $scheme . '://' . $sd . '.' . $server_domain; + my $domain = deep_get(\%options, "product_types_domains", $product_type) || $server_domain; + + return $scheme . '://' . $sd . '.' . $domain; } diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html index 2169ef6527c2a..b8f908730d61c 100644 --- a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html @@ -3,7 +3,7 @@ 302 Found

Found

-

The document has moved here.

+

The document has moved here.


Apache/2.4.62 (Debian) Server at world.openfoodfacts.localhost Port 80
From 9c750061ee60a5d9d18a68cd21d36c086113ae94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Wed, 30 Oct 2024 13:58:39 +0100 Subject: [PATCH 6/9] fix tests --- cgi/product_jqm_multilingual.pl | 9 + cgi/product_multilingual.pl | 9 + lib/ProductOpener/APIProductRead.pm | 10 +- lib/ProductOpener/APIProductWrite.pm | 12 +- lib/ProductOpener/APITest.pm | 6 +- lib/ProductOpener/Config.pm | 2 +- lib/ProductOpener/Display.pm | 14 +- lib/ProductOpener/URL.pm | 5 +- ..._v2_product_code_and_product_type_change.t | 34 +- ...oduct-with-web-interface-display-form.html | 9 + ...oduct-with-web-interface-process-form.html | 9 + .../get-obf-product-with-api-v3.html | 9 + ...> get-obf-product-with-web-interface.html} | 0 .../get-product-obf.json | 522 ------------------ .../get-product-opf.html | 9 + .../get-product-opf.json | 522 ------------------ 16 files changed, 118 insertions(+), 1063 deletions(-) create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/edit-obf-product-with-web-interface-display-form.html create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/edit-obf-product-with-web-interface-process-form.html create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-obf-product-with-api-v3.html rename tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/{get-product-with-web-interface.html => get-obf-product-with-web-interface.html} (100%) delete mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json create mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.html delete mode 100644 tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.json diff --git a/cgi/product_jqm_multilingual.pl b/cgi/product_jqm_multilingual.pl index 16afbb472676e..198807f96f397 100755 --- a/cgi/product_jqm_multilingual.pl +++ b/cgi/product_jqm_multilingual.pl @@ -114,6 +114,15 @@ =head1 DESCRIPTION $product_ref = init_product($User_id, $Org_id, $code, $country); $product_ref->{interface_version_created} = $interface_version; } + else { + # There is an existing product + # If the product has a product_type and it is not the product_type of the server, redirect to the correct server + + if ((defined $product_ref->{product_type}) and ($product_ref->{product_type} ne $options{product_type})) { + redirect_to_url($request_ref, 307, + format_subdomain($subdomain, $product_ref->{product_type}) . '/cgi/product_jqm.pl?code=' . $code); + } + } # Process edit rules diff --git a/cgi/product_multilingual.pl b/cgi/product_multilingual.pl index 250b1a35c6de4..fa869426c6393 100755 --- a/cgi/product_multilingual.pl +++ b/cgi/product_multilingual.pl @@ -342,6 +342,15 @@ ($product_ref) if (not defined $product_ref) { display_error_and_exit($request_ref, sprintf(lang("no_product_for_barcode"), $code), 404); } + else { + # There is an existing product + # If the product has a product_type and it is not the product_type of the server, redirect to the correct server + # We use a 302 redirect so that browsers issue a GET request to display the form (even if we received a POST request) + if ((defined $product_ref->{product_type}) and ($product_ref->{product_type} ne $options{product_type})) { + redirect_to_url($request_ref, 302, + format_subdomain($subdomain, $product_ref->{product_type}) . '/cgi/product.pl?code=' . $code); + } + } } } diff --git a/lib/ProductOpener/APIProductRead.pm b/lib/ProductOpener/APIProductRead.pm index 7e9f287a77b9a..f8a8bec91d338 100644 --- a/lib/ProductOpener/APIProductRead.pm +++ b/lib/ProductOpener/APIProductRead.pm @@ -45,12 +45,13 @@ use vars @EXPORT_OK; use ProductOpener::Config qw/:all/; use ProductOpener::Paths qw/%BASE_DIRS/; -use ProductOpener::Display qw/request_param single_param/; +use ProductOpener::Display qw/$subdomain redirect_to_url request_param single_param/; use ProductOpener::Users qw/$Owner_id/; use ProductOpener::Lang qw/$lc/; use ProductOpener::Products qw/:all/; use ProductOpener::Ingredients qw/flatten_sub_ingredients/; use ProductOpener::API qw/add_error customize_response_for_product normalize_requested_code/; +use ProductOpener::URL qw(format_subdomain); my $cc; @@ -138,6 +139,13 @@ sub read_product_api ($request_ref) { $response_ref->{result} = {id => "product_not_found"}; } else { + # If the product has a product_type and it is not the product_type of the server, redirect to the correct server + + if ((defined $product_ref->{product_type}) and ($product_ref->{product_type} ne $options{product_type})) { + redirect_to_url($request_ref, 302, + format_subdomain($subdomain, $product_ref->{product_type}) . $request_ref->{original_query_string}); + } + $response_ref->{result} = {id => "product_found"}; add_images_urls_to_product($product_ref, $lc); diff --git a/lib/ProductOpener/APIProductWrite.pm b/lib/ProductOpener/APIProductWrite.pm index abe224347b228..8f4a8e9764534 100644 --- a/lib/ProductOpener/APIProductWrite.pm +++ b/lib/ProductOpener/APIProductWrite.pm @@ -45,7 +45,7 @@ BEGIN { use vars @EXPORT_OK; use ProductOpener::Config qw/:all/; -use ProductOpener::Display qw/$country request_param single_param/; +use ProductOpener::Display qw/$subdomain redirect_to_url $country request_param single_param/; use ProductOpener::Users qw/$Org_id $Owner_id $User_id/; use ProductOpener::Lang qw/$lc/; use ProductOpener::Products qw/:all/; @@ -54,6 +54,7 @@ use ProductOpener::Packaging qw/add_or_combine_packaging_component_data get_checked_and_taxonomized_packaging_component_data/; use ProductOpener::Text qw/remove_tags_and_quote/; use ProductOpener::Tags qw/%language_fields %writable_tags_fields add_tags_to_field compute_field_tags/; +use ProductOpener::URL qw(format_subdomain); use Encode; @@ -430,6 +431,15 @@ sub write_product_api ($request_ref) { $product_ref = init_product($User_id, $Org_id, $code, $country); $product_ref->{interface_version_created} = "20221102/api/v3"; } + else { + # There is an existing product + # If the product has a product_type and it is not the product_type of the server, redirect to the correct server + + if ((defined $product_ref->{product_type}) and ($product_ref->{product_type} ne $options{product_type})) { + redirect_to_url($request_ref, 307, + format_subdomain($subdomain, $product_ref->{product_type}) . '/api/v3/product/' . $code); + } + } # Use default request language if we did not get tags_lc if (not defined $request_body_ref->{tags_lc}) { diff --git a/lib/ProductOpener/APITest.pm b/lib/ProductOpener/APITest.pm index 19aaf0eaab5dc..ee212d33d4409 100644 --- a/lib/ProductOpener/APITest.pm +++ b/lib/ProductOpener/APITest.pm @@ -429,11 +429,11 @@ sub execute_request ($test_ref, $ua) { my $response; # For some tests, we don't want to follow redirects. We want to see the 302 responses, not the response to the final destination - if ((defined $test_ref->{expected_status_code}) and ($test_ref->{expected_status_code} == 302)) { - $ua->max_redirect(0); + if ((defined $test_ref->{expected_status_code}) and (int($test_ref->{expected_status_code} / 100) == 3)) { + $test_ua->max_redirect(0); } else { - $ua->max_redirect(3); + $test_ua->max_redirect(3); } # Send the request diff --git a/lib/ProductOpener/Config.pm b/lib/ProductOpener/Config.pm index 93d35196f11fc..484946e1a714d 100644 --- a/lib/ProductOpener/Config.pm +++ b/lib/ProductOpener/Config.pm @@ -128,7 +128,7 @@ autoload("ProductOpener::Config_$flavor"); =cut -$ProductOpener::Config::options{product_types} = qw(food petfood beauty product); +$ProductOpener::Config::options{product_types} = [qw(food petfood beauty product)]; $ProductOpener::Config::options{product_types_flavors} = { food => "off", petfood => "opff", diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index 95c6b3eedbe2c..893626a9f9d59 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -7899,6 +7899,13 @@ JS display_error_and_exit($request_ref, sprintf(lang("no_product_for_barcode"), $code), 404); } + # If the product has a product_type and it is not the product_type of the server, redirect to the correct server + + if ((defined $product_ref->{product_type}) and ($product_ref->{product_type} ne $options{product_type})) { + redirect_to_url($request_ref, 302, + format_subdomain($subdomain, $product_ref->{product_type}) . product_url($product_ref)); + } + $title = product_name_brand_quantity($product_ref); my $titleid = get_string_id_for_lang($lc, product_name_brand($product_ref)); @@ -7918,13 +7925,6 @@ JS $request_ref->{canon_url} = get_world_subdomain() . product_url($product_ref); } - # If the product has a product_type and it is not the product_type of the server, redirect to the correct server - - if ((defined $product_ref->{product_type}) and ($product_ref->{product_type} ne $options{product_type})) { - redirect_to_url($request_ref, 302, - format_subdomain($subdomain, $product_ref->{product_type}) . product_url($product_ref)); - } - # Old UPC-12 in url? Redirect to EAN-13 url # TODO - 2024/10/02 - Temporarily disabled so that we can migrate short barcodes with digits not equal to 8 or greater or equal to 13 # Reenable after all products are migrated. diff --git a/lib/ProductOpener/URL.pm b/lib/ProductOpener/URL.pm index 11baec22954f8..e7d2bcd562ae8 100644 --- a/lib/ProductOpener/URL.pm +++ b/lib/ProductOpener/URL.pm @@ -66,7 +66,7 @@ use Data::DeepAccess qw(deep_get); =head1 FUNCTIONS -=head2 format_subdomain( SUBDOMAIN ) +=head2 format_subdomain($sd, $product_type = undef)) C returns URL on the basis of subdomain and scheme (http/https) @@ -98,7 +98,8 @@ sub format_subdomain ($sd, $product_type = undef) { $scheme = 'http'; } - my $domain = deep_get(\%options, "product_types_domains", $product_type) || $server_domain; + my $domain + = deep_get(\%options, "product_types_domains", $product_type || $options{product_type}) || $server_domain; return $scheme . '://' . $sd . '.' . $domain; diff --git a/tests/integration/api_v2_product_code_and_product_type_change.t b/tests/integration/api_v2_product_code_and_product_type_change.t index 6f5af56701e11..dcc0e0a8f435e 100644 --- a/tests/integration/api_v2_product_code_and_product_type_change.t +++ b/tests/integration/api_v2_product_code_and_product_type_change.t @@ -106,7 +106,7 @@ my $tests_ref = [ }, # Get the product with web interface { - test_case => 'get-product-with-web-interface', + test_case => 'get-obf-product-with-web-interface', method => 'GET', path => '/product/1234567890102', expected_status_code => 302, @@ -114,10 +114,35 @@ my $tests_ref = [ }, # Get the product with API v3 { - test_case => 'get-product-obf', + test_case => 'get-obf-product-with-api-v3', method => 'GET', path => '/api/v3/product/1234567890102', - expected_status_code => 200, + expected_status_code => 302, + expected_type => 'html', + }, + # Edit the product with web interface (display the form) + { + test_case => 'edit-obf-product-with-web-interface-display-form', + method => 'GET', + path => '/cgi/product.pl?type=edit&code=1234567890102', + expected_status_code => 302, + expected_type => 'html', + ua => $ua, + }, + # Edit the product with web interface (process the form) + { + test_case => 'edit-obf-product-with-web-interface-process-form', + method => 'POST', + path => '/cgi/product.pl', + form => { + type => "edit", + action => "process", + code => "1234567890102", + product_name => "Test product 2 - updated", + }, + expected_status_code => 302, + expected_type => 'html', + ua => $ua, }, # Create a new product { @@ -149,7 +174,8 @@ my $tests_ref = [ test_case => 'get-product-opf', method => 'GET', path => '/api/v3/product/1234567890200', - expected_status_code => 200, + expected_status_code => 302, + expected_type => 'html', }, # Search all products to check moved products are not on the off MongoDB database anymore { diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/edit-obf-product-with-web-interface-display-form.html b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/edit-obf-product-with-web-interface-display-form.html new file mode 100644 index 0000000000000..5fc79a4e65fde --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/edit-obf-product-with-web-interface-display-form.html @@ -0,0 +1,9 @@ + + +302 Found + +

Found

+

The document has moved here.

+
+
Apache/2.4.62 (Debian) Server at world.openfoodfacts.localhost Port 80
+ diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/edit-obf-product-with-web-interface-process-form.html b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/edit-obf-product-with-web-interface-process-form.html new file mode 100644 index 0000000000000..5fc79a4e65fde --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/edit-obf-product-with-web-interface-process-form.html @@ -0,0 +1,9 @@ + + +302 Found + +

Found

+

The document has moved here.

+
+
Apache/2.4.62 (Debian) Server at world.openfoodfacts.localhost Port 80
+ diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-obf-product-with-api-v3.html b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-obf-product-with-api-v3.html new file mode 100644 index 0000000000000..f8bbf0a5e4afb --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-obf-product-with-api-v3.html @@ -0,0 +1,9 @@ + + +302 Found + +

Found

+

The document has moved here.

+
+
Apache/2.4.62 (Debian) Server at world.openfoodfacts.localhost Port 80
+ diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-obf-product-with-web-interface.html similarity index 100% rename from tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-web-interface.html rename to tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-obf-product-with-web-interface.html diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json deleted file mode 100644 index 8a0b2d656f1ee..0000000000000 --- a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-obf.json +++ /dev/null @@ -1,522 +0,0 @@ -{ - "code" : "1234567890102", - "errors" : [], - "product" : { - "_id" : "1234567890102", - "_keywords" : [ - "product", - "test" - ], - "added_countries_tags" : [], - "allergens" : "", - "allergens_from_ingredients" : "", - "allergens_from_user" : "(en) ", - "allergens_hierarchy" : [], - "allergens_tags" : [], - "categories_properties" : {}, - "categories_properties_tags" : [ - "all-products", - "categories-unknown", - "agribalyse-food-code-unknown", - "agribalyse-proxy-food-code-unknown", - "ciqual-food-code-unknown", - "agribalyse-unknown" - ], - "checkers_tags" : [], - "code" : "1234567890102", - "codes_tags" : [ - "code-13", - "1234567890xxx", - "123456789xxxx", - "12345678xxxxx", - "1234567xxxxxx", - "123456xxxxxxx", - "12345xxxxxxxx", - "1234xxxxxxxxx", - "123xxxxxxxxxx", - "12xxxxxxxxxxx", - "1xxxxxxxxxxxx" - ], - "complete" : 0, - "completeness" : 0.1, - "correctors_tags" : [ - "moderator" - ], - "countries" : "en:france", - "countries_hierarchy" : [ - "en:france" - ], - "countries_lc" : "en", - "countries_tags" : [ - "en:france" - ], - "created_t" : "--ignore--", - "creator" : "openfoodfacts-contributors", - "data_quality_bugs_tags" : [], - "data_quality_errors_tags" : [], - "data_quality_info_tags" : [ - "en:no-packaging-data", - "en:ecoscore-extended-data-not-computed", - "en:food-groups-1-unknown", - "en:food-groups-2-unknown", - "en:food-groups-3-unknown" - ], - "data_quality_tags" : [ - "en:no-packaging-data", - "en:ecoscore-extended-data-not-computed", - "en:food-groups-1-unknown", - "en:food-groups-2-unknown", - "en:food-groups-3-unknown", - "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", - "en:ecoscore-packaging-packaging-data-missing", - "en:ecoscore-production-system-no-label", - "en:ecoscore-threatened-species-ingredients-missing" - ], - "data_quality_warnings_tags" : [ - "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", - "en:ecoscore-packaging-packaging-data-missing", - "en:ecoscore-production-system-no-label", - "en:ecoscore-threatened-species-ingredients-missing" - ], - "ecoscore_data" : { - "adjustments" : { - "origins_of_ingredients" : { - "aggregated_origins" : [ - { - "origin" : "en:unknown", - "percent" : 100 - } - ], - "epi_score" : 0, - "epi_value" : -5, - "origins_from_categories" : [ - "en:unknown" - ], - "origins_from_origins_field" : [ - "en:unknown" - ], - "transportation_scores" : { - "ad" : 0, - "al" : 0, - "at" : 0, - "ax" : 0, - "ba" : 0, - "be" : 0, - "bg" : 0, - "ch" : 0, - "cy" : 0, - "cz" : 0, - "de" : 0, - "dk" : 0, - "dz" : 0, - "ee" : 0, - "eg" : 0, - "es" : 0, - "fi" : 0, - "fo" : 0, - "fr" : 0, - "gg" : 0, - "gi" : 0, - "gr" : 0, - "hr" : 0, - "hu" : 0, - "ie" : 0, - "il" : 0, - "im" : 0, - "is" : 0, - "it" : 0, - "je" : 0, - "lb" : 0, - "li" : 0, - "lt" : 0, - "lu" : 0, - "lv" : 0, - "ly" : 0, - "ma" : 0, - "mc" : 0, - "md" : 0, - "me" : 0, - "mk" : 0, - "mt" : 0, - "nl" : 0, - "no" : 0, - "pl" : 0, - "ps" : 0, - "pt" : 0, - "ro" : 0, - "rs" : 0, - "se" : 0, - "si" : 0, - "sj" : 0, - "sk" : 0, - "sm" : 0, - "sy" : 0, - "tn" : 0, - "tr" : 0, - "ua" : 0, - "uk" : 0, - "us" : 0, - "va" : 0, - "world" : 0, - "xk" : 0 - }, - "transportation_values" : { - "ad" : 0, - "al" : 0, - "at" : 0, - "ax" : 0, - "ba" : 0, - "be" : 0, - "bg" : 0, - "ch" : 0, - "cy" : 0, - "cz" : 0, - "de" : 0, - "dk" : 0, - "dz" : 0, - "ee" : 0, - "eg" : 0, - "es" : 0, - "fi" : 0, - "fo" : 0, - "fr" : 0, - "gg" : 0, - "gi" : 0, - "gr" : 0, - "hr" : 0, - "hu" : 0, - "ie" : 0, - "il" : 0, - "im" : 0, - "is" : 0, - "it" : 0, - "je" : 0, - "lb" : 0, - "li" : 0, - "lt" : 0, - "lu" : 0, - "lv" : 0, - "ly" : 0, - "ma" : 0, - "mc" : 0, - "md" : 0, - "me" : 0, - "mk" : 0, - "mt" : 0, - "nl" : 0, - "no" : 0, - "pl" : 0, - "ps" : 0, - "pt" : 0, - "ro" : 0, - "rs" : 0, - "se" : 0, - "si" : 0, - "sj" : 0, - "sk" : 0, - "sm" : 0, - "sy" : 0, - "tn" : 0, - "tr" : 0, - "ua" : 0, - "uk" : 0, - "us" : 0, - "va" : 0, - "world" : 0, - "xk" : 0 - }, - "values" : { - "ad" : -5, - "al" : -5, - "at" : -5, - "ax" : -5, - "ba" : -5, - "be" : -5, - "bg" : -5, - "ch" : -5, - "cy" : -5, - "cz" : -5, - "de" : -5, - "dk" : -5, - "dz" : -5, - "ee" : -5, - "eg" : -5, - "es" : -5, - "fi" : -5, - "fo" : -5, - "fr" : -5, - "gg" : -5, - "gi" : -5, - "gr" : -5, - "hr" : -5, - "hu" : -5, - "ie" : -5, - "il" : -5, - "im" : -5, - "is" : -5, - "it" : -5, - "je" : -5, - "lb" : -5, - "li" : -5, - "lt" : -5, - "lu" : -5, - "lv" : -5, - "ly" : -5, - "ma" : -5, - "mc" : -5, - "md" : -5, - "me" : -5, - "mk" : -5, - "mt" : -5, - "nl" : -5, - "no" : -5, - "pl" : -5, - "ps" : -5, - "pt" : -5, - "ro" : -5, - "rs" : -5, - "se" : -5, - "si" : -5, - "sj" : -5, - "sk" : -5, - "sm" : -5, - "sy" : -5, - "tn" : -5, - "tr" : -5, - "ua" : -5, - "uk" : -5, - "us" : -5, - "va" : -5, - "world" : -5, - "xk" : -5 - }, - "warning" : "origins_are_100_percent_unknown" - }, - "packaging" : { - "value" : -15, - "warning" : "packaging_data_missing" - }, - "production_system" : { - "labels" : [], - "value" : 0, - "warning" : "no_label" - }, - "threatened_species" : { - "warning" : "ingredients_missing" - } - }, - "agribalyse" : { - "warning" : "missing_agribalyse_match" - }, - "missing" : { - "categories" : 1, - "ingredients" : 1, - "labels" : 1, - "origins" : 1, - "packagings" : 1 - }, - "missing_agribalyse_match_warning" : 1, - "missing_key_data" : 1, - "status" : "unknown" - }, - "ecoscore_grade" : "unknown", - "ecoscore_tags" : [ - "unknown" - ], - "editors_tags" : [ - "moderator", - "openfoodfacts-contributors" - ], - "entry_dates_tags" : "--ignore--", - "food_groups_tags" : [], - "id" : "1234567890100", - "informers_tags" : [ - "openfoodfacts-contributors" - ], - "ingredients_lc" : "en", - "interface_version_created" : "20221102/api/v3", - "interface_version_modified" : "20150316.jqm2", - "lang" : "en", - "languages" : { - "en:english" : 1 - }, - "languages_codes" : { - "en" : 1 - }, - "languages_hierarchy" : [ - "en:english" - ], - "languages_tags" : [ - "en:english", - "en:1" - ], - "last_edit_dates_tags" : "--ignore--", - "last_editor" : "moderator", - "last_modified_by" : "moderator", - "last_modified_t" : "--ignore--", - "last_updated_t" : "--ignore--", - "lc" : "en", - "main_countries_tags" : [], - "misc_tags" : [ - "en:ecoscore-extended-data-not-computed", - "en:ecoscore-not-computed", - "en:nutriscore-missing-category", - "en:nutriscore-missing-nutrition-data", - "en:nutriscore-missing-nutrition-data-energy", - "en:nutriscore-missing-nutrition-data-fat", - "en:nutriscore-missing-nutrition-data-proteins", - "en:nutriscore-missing-nutrition-data-saturated-fat", - "en:nutriscore-missing-nutrition-data-sodium", - "en:nutriscore-missing-nutrition-data-sugars", - "en:nutriscore-not-computed", - "en:nutrition-no-fiber", - "en:nutrition-no-fiber-or-fruits-vegetables-nuts", - "en:nutrition-no-fruits-vegetables-nuts", - "en:nutrition-not-enough-data-to-compute-nutrition-score", - "en:packagings-empty", - "en:packagings-not-complete", - "en:packagings-number-of-components-0", - "en:main-countries-new-product" - ], - "nova_group_debug" : "no nova group when the product does not have ingredients", - "nova_group_error" : "missing_ingredients", - "nova_groups_tags" : [ - "unknown" - ], - "nutrient_levels" : {}, - "nutrient_levels_tags" : [], - "nutriments" : {}, - "nutriscore" : { - "2021" : { - "category_available" : 0, - "data" : { - "energy" : null, - "fiber" : 0, - "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, - "is_beverage" : 0, - "is_cheese" : 0, - "is_fat" : 0, - "is_water" : 0, - "proteins" : null, - "saturated_fat" : null, - "sodium" : null, - "sugars" : null - }, - "grade" : "unknown", - "nutrients_available" : 0, - "nutriscore_applicable" : 0, - "nutriscore_computed" : 0 - }, - "2023" : { - "category_available" : 0, - "data" : { - "energy" : null, - "fiber" : null, - "fruits_vegetables_legumes" : null, - "is_beverage" : 0, - "is_cheese" : 0, - "is_fat_oil_nuts_seeds" : 0, - "is_red_meat_product" : 0, - "is_water" : 0, - "proteins" : null, - "salt" : null, - "saturated_fat" : null, - "sugars" : null - }, - "grade" : "unknown", - "nutrients_available" : 0, - "nutriscore_applicable" : 0, - "nutriscore_computed" : 0 - } - }, - "nutriscore_2021_tags" : [ - "unknown" - ], - "nutriscore_2023_tags" : [ - "unknown" - ], - "nutriscore_grade" : "unknown", - "nutriscore_tags" : [ - "unknown" - ], - "nutriscore_version" : "2021", - "nutrition_data_per" : "100g", - "nutrition_data_prepared_per" : "100g", - "nutrition_grade_fr" : "unknown", - "nutrition_grades" : "unknown", - "nutrition_grades_tags" : [ - "unknown" - ], - "nutrition_score_beverage" : 0, - "nutrition_score_debug" : "no score when the product does not have a category - missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", - "nutrition_score_warning_no_fiber" : 1, - "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, - "packaging_materials_tags" : [], - "packaging_recycling_tags" : [], - "packaging_shapes_tags" : [], - "packagings" : [], - "packagings_materials" : {}, - "photographers_tags" : [], - "pnns_groups_1" : "unknown", - "pnns_groups_1_tags" : [ - "unknown", - "missing-category" - ], - "pnns_groups_2" : "unknown", - "pnns_groups_2_tags" : [ - "unknown", - "missing-category" - ], - "popularity_key" : 0, - "product_name" : "Test product 1", - "product_name_en" : "Test product 1", - "product_type" : "beauty", - "removed_countries_tags" : [], - "rev" : 4, - "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-to-be-completed, en:brands-to-be-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", - "states_hierarchy" : [ - "en:to-be-completed", - "en:nutrition-facts-to-be-completed", - "en:ingredients-to-be-completed", - "en:expiration-date-to-be-completed", - "en:packaging-code-to-be-completed", - "en:characteristics-to-be-completed", - "en:origins-to-be-completed", - "en:categories-to-be-completed", - "en:brands-to-be-completed", - "en:packaging-to-be-completed", - "en:quantity-to-be-completed", - "en:product-name-completed", - "en:photos-to-be-uploaded" - ], - "states_tags" : [ - "en:to-be-completed", - "en:nutrition-facts-to-be-completed", - "en:ingredients-to-be-completed", - "en:expiration-date-to-be-completed", - "en:packaging-code-to-be-completed", - "en:characteristics-to-be-completed", - "en:origins-to-be-completed", - "en:categories-to-be-completed", - "en:brands-to-be-completed", - "en:packaging-to-be-completed", - "en:quantity-to-be-completed", - "en:product-name-completed", - "en:photos-to-be-uploaded" - ], - "traces" : "", - "traces_from_ingredients" : "", - "traces_from_user" : "(en) ", - "traces_hierarchy" : [], - "traces_tags" : [], - "unknown_nutrients_tags" : [], - "weighers_tags" : [] - }, - "result" : { - "id" : "product_found", - "lc_name" : "Product found", - "name" : "Product found" - }, - "status" : "success", - "warnings" : [] -} diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.html b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.html new file mode 100644 index 0000000000000..501bcd5effc28 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.html @@ -0,0 +1,9 @@ + + +302 Found + +

Found

+

The document has moved here.

+
+
Apache/2.4.62 (Debian) Server at world.openfoodfacts.localhost Port 80
+ diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.json deleted file mode 100644 index 23ef6d58e4b52..0000000000000 --- a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-opf.json +++ /dev/null @@ -1,522 +0,0 @@ -{ - "code" : "1234567890200", - "errors" : [], - "product" : { - "_id" : "1234567890200", - "_keywords" : [ - "product", - "test" - ], - "added_countries_tags" : [], - "allergens" : "", - "allergens_from_ingredients" : "", - "allergens_from_user" : "(en) ", - "allergens_hierarchy" : [], - "allergens_tags" : [], - "categories_properties" : {}, - "categories_properties_tags" : [ - "all-products", - "categories-unknown", - "agribalyse-food-code-unknown", - "agribalyse-proxy-food-code-unknown", - "ciqual-food-code-unknown", - "agribalyse-unknown" - ], - "checkers_tags" : [], - "code" : "1234567890200", - "codes_tags" : [ - "code-13", - "1234567890xxx", - "123456789xxxx", - "12345678xxxxx", - "1234567xxxxxx", - "123456xxxxxxx", - "12345xxxxxxxx", - "1234xxxxxxxxx", - "123xxxxxxxxxx", - "12xxxxxxxxxxx", - "1xxxxxxxxxxxx" - ], - "complete" : 0, - "completeness" : 0.1, - "correctors_tags" : [ - "moderator" - ], - "countries" : "en:france", - "countries_hierarchy" : [ - "en:france" - ], - "countries_lc" : "en", - "countries_tags" : [ - "en:france" - ], - "created_t" : "--ignore--", - "creator" : "openfoodfacts-contributors", - "data_quality_bugs_tags" : [], - "data_quality_errors_tags" : [], - "data_quality_info_tags" : [ - "en:no-packaging-data", - "en:ecoscore-extended-data-not-computed", - "en:food-groups-1-unknown", - "en:food-groups-2-unknown", - "en:food-groups-3-unknown" - ], - "data_quality_tags" : [ - "en:no-packaging-data", - "en:ecoscore-extended-data-not-computed", - "en:food-groups-1-unknown", - "en:food-groups-2-unknown", - "en:food-groups-3-unknown", - "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", - "en:ecoscore-packaging-packaging-data-missing", - "en:ecoscore-production-system-no-label", - "en:ecoscore-threatened-species-ingredients-missing" - ], - "data_quality_warnings_tags" : [ - "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", - "en:ecoscore-packaging-packaging-data-missing", - "en:ecoscore-production-system-no-label", - "en:ecoscore-threatened-species-ingredients-missing" - ], - "ecoscore_data" : { - "adjustments" : { - "origins_of_ingredients" : { - "aggregated_origins" : [ - { - "origin" : "en:unknown", - "percent" : 100 - } - ], - "epi_score" : 0, - "epi_value" : -5, - "origins_from_categories" : [ - "en:unknown" - ], - "origins_from_origins_field" : [ - "en:unknown" - ], - "transportation_scores" : { - "ad" : 0, - "al" : 0, - "at" : 0, - "ax" : 0, - "ba" : 0, - "be" : 0, - "bg" : 0, - "ch" : 0, - "cy" : 0, - "cz" : 0, - "de" : 0, - "dk" : 0, - "dz" : 0, - "ee" : 0, - "eg" : 0, - "es" : 0, - "fi" : 0, - "fo" : 0, - "fr" : 0, - "gg" : 0, - "gi" : 0, - "gr" : 0, - "hr" : 0, - "hu" : 0, - "ie" : 0, - "il" : 0, - "im" : 0, - "is" : 0, - "it" : 0, - "je" : 0, - "lb" : 0, - "li" : 0, - "lt" : 0, - "lu" : 0, - "lv" : 0, - "ly" : 0, - "ma" : 0, - "mc" : 0, - "md" : 0, - "me" : 0, - "mk" : 0, - "mt" : 0, - "nl" : 0, - "no" : 0, - "pl" : 0, - "ps" : 0, - "pt" : 0, - "ro" : 0, - "rs" : 0, - "se" : 0, - "si" : 0, - "sj" : 0, - "sk" : 0, - "sm" : 0, - "sy" : 0, - "tn" : 0, - "tr" : 0, - "ua" : 0, - "uk" : 0, - "us" : 0, - "va" : 0, - "world" : 0, - "xk" : 0 - }, - "transportation_values" : { - "ad" : 0, - "al" : 0, - "at" : 0, - "ax" : 0, - "ba" : 0, - "be" : 0, - "bg" : 0, - "ch" : 0, - "cy" : 0, - "cz" : 0, - "de" : 0, - "dk" : 0, - "dz" : 0, - "ee" : 0, - "eg" : 0, - "es" : 0, - "fi" : 0, - "fo" : 0, - "fr" : 0, - "gg" : 0, - "gi" : 0, - "gr" : 0, - "hr" : 0, - "hu" : 0, - "ie" : 0, - "il" : 0, - "im" : 0, - "is" : 0, - "it" : 0, - "je" : 0, - "lb" : 0, - "li" : 0, - "lt" : 0, - "lu" : 0, - "lv" : 0, - "ly" : 0, - "ma" : 0, - "mc" : 0, - "md" : 0, - "me" : 0, - "mk" : 0, - "mt" : 0, - "nl" : 0, - "no" : 0, - "pl" : 0, - "ps" : 0, - "pt" : 0, - "ro" : 0, - "rs" : 0, - "se" : 0, - "si" : 0, - "sj" : 0, - "sk" : 0, - "sm" : 0, - "sy" : 0, - "tn" : 0, - "tr" : 0, - "ua" : 0, - "uk" : 0, - "us" : 0, - "va" : 0, - "world" : 0, - "xk" : 0 - }, - "values" : { - "ad" : -5, - "al" : -5, - "at" : -5, - "ax" : -5, - "ba" : -5, - "be" : -5, - "bg" : -5, - "ch" : -5, - "cy" : -5, - "cz" : -5, - "de" : -5, - "dk" : -5, - "dz" : -5, - "ee" : -5, - "eg" : -5, - "es" : -5, - "fi" : -5, - "fo" : -5, - "fr" : -5, - "gg" : -5, - "gi" : -5, - "gr" : -5, - "hr" : -5, - "hu" : -5, - "ie" : -5, - "il" : -5, - "im" : -5, - "is" : -5, - "it" : -5, - "je" : -5, - "lb" : -5, - "li" : -5, - "lt" : -5, - "lu" : -5, - "lv" : -5, - "ly" : -5, - "ma" : -5, - "mc" : -5, - "md" : -5, - "me" : -5, - "mk" : -5, - "mt" : -5, - "nl" : -5, - "no" : -5, - "pl" : -5, - "ps" : -5, - "pt" : -5, - "ro" : -5, - "rs" : -5, - "se" : -5, - "si" : -5, - "sj" : -5, - "sk" : -5, - "sm" : -5, - "sy" : -5, - "tn" : -5, - "tr" : -5, - "ua" : -5, - "uk" : -5, - "us" : -5, - "va" : -5, - "world" : -5, - "xk" : -5 - }, - "warning" : "origins_are_100_percent_unknown" - }, - "packaging" : { - "value" : -15, - "warning" : "packaging_data_missing" - }, - "production_system" : { - "labels" : [], - "value" : 0, - "warning" : "no_label" - }, - "threatened_species" : { - "warning" : "ingredients_missing" - } - }, - "agribalyse" : { - "warning" : "missing_agribalyse_match" - }, - "missing" : { - "categories" : 1, - "ingredients" : 1, - "labels" : 1, - "origins" : 1, - "packagings" : 1 - }, - "missing_agribalyse_match_warning" : 1, - "missing_key_data" : 1, - "status" : "unknown" - }, - "ecoscore_grade" : "unknown", - "ecoscore_tags" : [ - "unknown" - ], - "editors_tags" : [ - "moderator", - "openfoodfacts-contributors" - ], - "entry_dates_tags" : "--ignore--", - "food_groups_tags" : [], - "id" : "1234567890200", - "informers_tags" : [ - "openfoodfacts-contributors" - ], - "ingredients_lc" : "en", - "interface_version_created" : "20221102/api/v3", - "interface_version_modified" : "20150316.jqm2", - "lang" : "en", - "languages" : { - "en:english" : 1 - }, - "languages_codes" : { - "en" : 1 - }, - "languages_hierarchy" : [ - "en:english" - ], - "languages_tags" : [ - "en:english", - "en:1" - ], - "last_edit_dates_tags" : "--ignore--", - "last_editor" : "moderator", - "last_modified_by" : "moderator", - "last_modified_t" : "--ignore--", - "last_updated_t" : "--ignore--", - "lc" : "en", - "main_countries_tags" : [], - "misc_tags" : [ - "en:ecoscore-extended-data-not-computed", - "en:ecoscore-not-computed", - "en:nutriscore-missing-category", - "en:nutriscore-missing-nutrition-data", - "en:nutriscore-missing-nutrition-data-energy", - "en:nutriscore-missing-nutrition-data-fat", - "en:nutriscore-missing-nutrition-data-proteins", - "en:nutriscore-missing-nutrition-data-saturated-fat", - "en:nutriscore-missing-nutrition-data-sodium", - "en:nutriscore-missing-nutrition-data-sugars", - "en:nutriscore-not-computed", - "en:nutrition-no-fiber", - "en:nutrition-no-fiber-or-fruits-vegetables-nuts", - "en:nutrition-no-fruits-vegetables-nuts", - "en:nutrition-not-enough-data-to-compute-nutrition-score", - "en:packagings-empty", - "en:packagings-not-complete", - "en:packagings-number-of-components-0", - "en:main-countries-new-product" - ], - "nova_group_debug" : "no nova group when the product does not have ingredients", - "nova_group_error" : "missing_ingredients", - "nova_groups_tags" : [ - "unknown" - ], - "nutrient_levels" : {}, - "nutrient_levels_tags" : [], - "nutriments" : {}, - "nutriscore" : { - "2021" : { - "category_available" : 0, - "data" : { - "energy" : null, - "fiber" : 0, - "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, - "is_beverage" : 0, - "is_cheese" : 0, - "is_fat" : 0, - "is_water" : 0, - "proteins" : null, - "saturated_fat" : null, - "sodium" : null, - "sugars" : null - }, - "grade" : "unknown", - "nutrients_available" : 0, - "nutriscore_applicable" : 0, - "nutriscore_computed" : 0 - }, - "2023" : { - "category_available" : 0, - "data" : { - "energy" : null, - "fiber" : null, - "fruits_vegetables_legumes" : null, - "is_beverage" : 0, - "is_cheese" : 0, - "is_fat_oil_nuts_seeds" : 0, - "is_red_meat_product" : 0, - "is_water" : 0, - "proteins" : null, - "salt" : null, - "saturated_fat" : null, - "sugars" : null - }, - "grade" : "unknown", - "nutrients_available" : 0, - "nutriscore_applicable" : 0, - "nutriscore_computed" : 0 - } - }, - "nutriscore_2021_tags" : [ - "unknown" - ], - "nutriscore_2023_tags" : [ - "unknown" - ], - "nutriscore_grade" : "unknown", - "nutriscore_tags" : [ - "unknown" - ], - "nutriscore_version" : "2021", - "nutrition_data_per" : "100g", - "nutrition_data_prepared_per" : "100g", - "nutrition_grade_fr" : "unknown", - "nutrition_grades" : "unknown", - "nutrition_grades_tags" : [ - "unknown" - ], - "nutrition_score_beverage" : 0, - "nutrition_score_debug" : "no score when the product does not have a category - missing energy_100g - missing fat_100g - missing saturated-fat_100g - missing sugars_100g - missing sodium_100g - missing proteins_100g", - "nutrition_score_warning_no_fiber" : 1, - "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, - "packaging_materials_tags" : [], - "packaging_recycling_tags" : [], - "packaging_shapes_tags" : [], - "packagings" : [], - "packagings_materials" : {}, - "photographers_tags" : [], - "pnns_groups_1" : "unknown", - "pnns_groups_1_tags" : [ - "unknown", - "missing-category" - ], - "pnns_groups_2" : "unknown", - "pnns_groups_2_tags" : [ - "unknown", - "missing-category" - ], - "popularity_key" : 0, - "product_name" : "Test product 2", - "product_name_en" : "Test product 2", - "product_type" : "product", - "removed_countries_tags" : [], - "rev" : 2, - "states" : "en:to-be-completed, en:nutrition-facts-to-be-completed, en:ingredients-to-be-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-to-be-completed, en:brands-to-be-completed, en:packaging-to-be-completed, en:quantity-to-be-completed, en:product-name-completed, en:photos-to-be-uploaded", - "states_hierarchy" : [ - "en:to-be-completed", - "en:nutrition-facts-to-be-completed", - "en:ingredients-to-be-completed", - "en:expiration-date-to-be-completed", - "en:packaging-code-to-be-completed", - "en:characteristics-to-be-completed", - "en:origins-to-be-completed", - "en:categories-to-be-completed", - "en:brands-to-be-completed", - "en:packaging-to-be-completed", - "en:quantity-to-be-completed", - "en:product-name-completed", - "en:photos-to-be-uploaded" - ], - "states_tags" : [ - "en:to-be-completed", - "en:nutrition-facts-to-be-completed", - "en:ingredients-to-be-completed", - "en:expiration-date-to-be-completed", - "en:packaging-code-to-be-completed", - "en:characteristics-to-be-completed", - "en:origins-to-be-completed", - "en:categories-to-be-completed", - "en:brands-to-be-completed", - "en:packaging-to-be-completed", - "en:quantity-to-be-completed", - "en:product-name-completed", - "en:photos-to-be-uploaded" - ], - "traces" : "", - "traces_from_ingredients" : "", - "traces_from_user" : "(en) ", - "traces_hierarchy" : [], - "traces_tags" : [], - "unknown_nutrients_tags" : [], - "weighers_tags" : [] - }, - "result" : { - "id" : "product_found", - "lc_name" : "Product found", - "name" : "Product found" - }, - "status" : "success", - "warnings" : [] -} From 73a5ad14c18f616435e6db2e5a31835088da9972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Thu, 31 Oct 2024 16:35:21 +0100 Subject: [PATCH 7/9] use current domain for current product type --- lib/ProductOpener/URL.pm | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ProductOpener/URL.pm b/lib/ProductOpener/URL.pm index e7d2bcd562ae8..303d282629262 100644 --- a/lib/ProductOpener/URL.pm +++ b/lib/ProductOpener/URL.pm @@ -98,11 +98,14 @@ sub format_subdomain ($sd, $product_type = undef) { $scheme = 'http'; } - my $domain - = deep_get(\%options, "product_types_domains", $product_type || $options{product_type}) || $server_domain; + my $domain = $server_domain; + # If we have a product_type, different from the product_type of the server, use the domain for that product_type + if ((defined $product_type) and ($product_type ne $options{product_type})) { + + $domain = deep_get(\%options, "product_types_domains", $product_type || $options{product_type}) || $server_domain; + } return $scheme . '://' . $sd . '.' . $domain; - } =head2 subdomain_supports_https( SUBDOMAIN ) From 7e43ad77e3d8f91eb2ed936ee692d6d0b4c22048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Thu, 31 Oct 2024 17:05:20 +0100 Subject: [PATCH 8/9] lint --- lib/ProductOpener/URL.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ProductOpener/URL.pm b/lib/ProductOpener/URL.pm index 303d282629262..a36354e6bf3d9 100644 --- a/lib/ProductOpener/URL.pm +++ b/lib/ProductOpener/URL.pm @@ -101,8 +101,9 @@ sub format_subdomain ($sd, $product_type = undef) { my $domain = $server_domain; # If we have a product_type, different from the product_type of the server, use the domain for that product_type if ((defined $product_type) and ($product_type ne $options{product_type})) { - - $domain = deep_get(\%options, "product_types_domains", $product_type || $options{product_type}) || $server_domain; + + $domain + = deep_get(\%options, "product_types_domains", $product_type || $options{product_type}) || $server_domain; } return $scheme . '://' . $sd . '.' . $domain; From be987f7700b8215f27a1069797eb132ebc85fe3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Thu, 31 Oct 2024 17:40:12 +0100 Subject: [PATCH 9/9] fixes --- cgi/product_multilingual.pl | 14 +++++--- lib/ProductOpener/Products.pm | 33 +++++-------------- .../get-product-with-initial-code.json | 2 +- .../get-product-with-new-code.json | 2 +- .../web_html/fr-edit-product.html | 4 ++- .../web_html/world-edit-product.html | 4 ++- 6 files changed, 25 insertions(+), 34 deletions(-) diff --git a/cgi/product_multilingual.pl b/cgi/product_multilingual.pl index fa869426c6393..d7d065137f5fa 100755 --- a/cgi/product_multilingual.pl +++ b/cgi/product_multilingual.pl @@ -408,11 +408,15 @@ ($product_ref) exists $product_ref->{new_server} and delete $product_ref->{new_server}; - # 26/01/2017 - disallow barcode changes until we fix bug #677 - if ($User{moderator} and (defined single_param("new_code")) and (single_param("new_code") ne "")) { + if ($User{moderator}) { + if ((defined single_param("new_code")) and (single_param("new_code") ne "")) { - change_product_server_or_code($product_ref, single_param("new_code"), \@errors); - $code = $product_ref->{code}; + change_product_server_or_code($product_ref, single_param("new_code"), \@errors); + $code = $product_ref->{code}; + } + if ((defined single_param("product_type")) and (single_param("product_type") ne "")) { + change_product_type($product_ref, single_param("product_type"), \@errors); + } } my @param_fields = (); @@ -861,7 +865,7 @@ ($product_ref, $field, $language, $request_ref) $template_data_ref_display->{label_new_code} = $label_new_code; $template_data_ref_display->{owner_id} = $Owner_id; - $template_data_ref_display->{product_types} = \@product_types; + $template_data_ref_display->{product_types} = $options{product_types}; # obsolete products: restrict to admin on public site # authorize owners on producers platform diff --git a/lib/ProductOpener/Products.pm b/lib/ProductOpener/Products.pm index fef07f9e47a68..cc862cd5bd4f4 100644 --- a/lib/ProductOpener/Products.pm +++ b/lib/ProductOpener/Products.pm @@ -65,10 +65,6 @@ use Exporter qw< import >; BEGIN { use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); @EXPORT_OK = qw( - @product_types - %product_types_flavors - %flavors_product_types - &is_valid_code &normalize_code &normalize_code_with_gs1_ai @@ -111,6 +107,7 @@ BEGIN { &make_sure_numbers_are_stored_as_numbers &change_product_server_or_code + &change_product_type &find_and_replace_user_id_in_products @@ -174,20 +171,6 @@ use Scalar::Util qw(looks_like_number); use GS1::SyntaxEngine::FFI::GS1Encoder; -=head2 Available product types and flavors - -=cut - -@product_types = qw(food petfood beauty product); -%product_types_flavors = ( - food => "off", - petfood => "oppf", - beauty => "obf", - product => "opf" -); - -%flavors_product_types = reverse %product_types_flavors; - =head1 FUNCTIONS =head2 make_sure_numbers_are_stored_as_numbers ( PRODUCT_REF ) @@ -1048,8 +1031,8 @@ sub change_product_server_or_code ($product_ref, $new_code, $errors_ref) { if ($new_code =~ /^([a-z]+)$/) { my $new_server = $1; - if (defined $flavors_product_types{$new_server}) { - change_product_type($product_ref, $flavors_product_types{$new_server}, $errors_ref); + if (defined $options{flavors_product_types}{$new_server}) { + change_product_type($product_ref, $options{flavors_product_types}{$new_server}, $errors_ref); } } @@ -1090,7 +1073,7 @@ sub change_product_type ($product_ref, $new_product_type, $errors_ref) { return; } - if (not defined $product_types_flavors{$new_product_type}) { + if (not defined $options{product_types_flavors}{$new_product_type}) { push @$errors_ref, lang("error_invalid_product_type"); } else { @@ -1204,9 +1187,9 @@ sub store_product ($user_id, $product_ref, $comment) { if (defined $product_ref->{server}) { my $new_server = $product_ref->{server}; # Update the product_type from the server - if (defined $flavors_product_types{$new_server}) { + if (defined $options{flavors_product_types}{$new_server}) { my $errors_ref = {}; - change_product_type($product_ref, $flavors_product_types{$new_server}, $errors_ref); + change_product_type($product_ref, $options{flavors_product_types}{$new_server}, $errors_ref); } delete $product_ref->{server}; } @@ -1216,7 +1199,7 @@ sub store_product ($user_id, $product_ref, $comment) { # Get the previous server and collection for the product my $previous_server - = $product_types_flavors{$product_ref->{old_product_type} + = $options{product_types_flavors}{$product_ref->{old_product_type} || $product_ref->{product_type} || $options{product_type}}; @@ -1235,7 +1218,7 @@ sub store_product ($user_id, $product_ref, $comment) { } # Get the server and collection for the product that we will write - my $new_server = $product_types_flavors{$product_ref->{product_type} || $options{product_type}}; + my $new_server = $options{product_types_flavors}{$product_ref->{product_type} || $options{product_type}}; my $new_products_collection = get_products_collection( {database => $options{other_servers}{$new_server}{mongodb}, obsolete => $product_ref->{obsolete}}); diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json index a452ef9fff1d8..21d9b467427c5 100644 --- a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-initial-code.json @@ -81,7 +81,7 @@ "origins_of_ingredients" : { "aggregated_origins" : [ { - "epi_score" : "0", + "epi_score" : 0, "origin" : "en:unknown", "percent" : 100, "transportation_score" : 0 diff --git a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json index 66eca2ce40fe5..582e8fbd512d6 100644 --- a/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json +++ b/tests/integration/expected_test_results/api_v2_product_code_and_product_type_change/get-product-with-new-code.json @@ -83,7 +83,7 @@ "origins_of_ingredients" : { "aggregated_origins" : [ { - "epi_score" : "0", + "epi_score" : 0, "origin" : "en:unknown", "percent" : 100, "transportation_score" : 0 diff --git a/tests/integration/expected_test_results/web_html/fr-edit-product.html b/tests/integration/expected_test_results/web_html/fr-edit-product.html index 4d9ac5aa954ba..f679024163eba 100644 --- a/tests/integration/expected_test_results/web_html/fr-edit-product.html +++ b/tests/integration/expected_test_results/web_html/fr-edit-product.html @@ -448,6 +448,8 @@

+ +