diff --git a/cgi/display.pl b/cgi/display.pl index 77d2807789e86..7e19e245a54e8 100755 --- a/cgi/display.pl +++ b/cgi/display.pl @@ -40,45 +40,63 @@ use Apache2::RequestRec (); use Apache2::Const qw(:common); -# The API V3 write product request uses POST / PUT / PATCH with a JSON body -# if we have such a request, we need to read the body before CGI.pm tries to read it to get multipart/form-data parameters - my $env_query_string = $ENV{QUERY_STRING}; -$log->debug("display.pl - start", {env_query_string => $env_query_string}); +my $request_ref = {}; -my $body_request_ref = {}; +my $r = Apache2::RequestUtil->request(); +$request_ref->{method} = $r->method(); -if ($env_query_string =~ /^\/?api\/v3(\.\d+)?\/product/) { - read_request_body($body_request_ref); -} +$log->debug("display.pl - start", {env_query_string => $env_query_string, request_ref => $request_ref}) + if $log->is_debug(); -# The nginx reverse proxy turns /somepath?someparam=somevalue to /cgi/display.pl?/somepath?someparam=somevalue -# so that all non /cgi/ queries are sent to display.pl and that we can get the path in the query string -# CGI.pm thus adds somepath? at the start of the name of the first parameter. -# we need to remove it so that we can use the CGI.pm param() function to later access the parameters - -my @params = multi_param(); -if (defined $params[0]) { - my $first_param = $params[0]; - my $first_param_value = single_param($first_param); - $log->debug( - "replacing first param to remove path from parameter name", - {first_param => $first_param, $first_param_value => $first_param_value} - ); - CGI::delete($first_param); - $first_param =~ s/^(.*?)\?//; - param($first_param, $first_param_value); -} +# Special behaviors for API v3 requests + +if ($env_query_string =~ /^\/?api\/v(3(\.\d+)?)\//) { + + # Record that we have an API v3 request, as errors (e.g. bad userid and password) will be handled differently + # (through API::process_api_request instead of returning an error page in HTML) + $request_ref->{api_version} = $1; -my $request_ref = ProductOpener::Display::init_request(); + # Initialize the api_response field for errors and warnings + init_api_response($request_ref); -# Add the HTTP request body if we have one -if (defined $body_request_ref->{body}) { - $request_ref->{body} = $body_request_ref->{body}; + # The API V3 write product request uses POST / PUT / PATCH with a JSON body + # if we have such a request, we need to read the body before CGI.pm tries to read it to get multipart/form-data parameters + # We also need to do this before the call to init_request() which calls init_user() + # so that authentification credentials user_id and password from the JSON body can be used to authenticate the user + if ($request_ref->{method} =~ /^(POST|PUT|PATCH)$/) { + read_request_body($request_ref); + decode_json_request_body($request_ref); + } } -$log->debug("before analyze_request", {query_string => $request_ref->{query_string}}); +if (($env_query_string !~ /^\/?api\/v(3(\.\d+)?)\//) or ($request_ref->{method} !~ /^(POST|PUT|PATCH)$/)) { + # Not an API v3 POST/PUT/PATCH request: we will use CGI.pm param() method to access query string or multipart/form-data parameters + + # The nginx reverse proxy turns /somepath?someparam=somevalue to /cgi/display.pl?/somepath?someparam=somevalue + # so that all non /cgi/ queries are sent to display.pl and that we can get the path in the query string + # CGI.pm thus adds somepath? at the start of the name of the first parameter. + # we need to remove it so that we can use the CGI.pm param() function to later access the parameters + + my @params = multi_param(); + if (defined $params[0]) { + my $first_param = $params[0]; + my $first_param_value = single_param($first_param); + $log->debug( + "replacing first param to remove path from parameter name", + {first_param => $first_param, $first_param_value => $first_param_value} + ); + CGI::delete($first_param); + $first_param =~ s/^(.*?)\?//; + param($first_param, $first_param_value); + } +} + +# Initialize the request object, and authenticate the user +init_request($request_ref); + +$log->debug("before analyze_request", {query_string => $request_ref->{query_string}}) if $log->is_debug(); # analyze request will fill request with action and parameters analyze_request($request_ref); @@ -101,7 +119,7 @@ user => $request_ref->{user}, query => $request_ref->{query} } -); +) if $log->is_debug(); # Only display texts if products are private and no owner is defined if ( diff --git a/docs/reference/api-v3.yml b/docs/reference/api-v3.yml index b22886f2f031c..a744cd4d3ebea 100644 --- a/docs/reference/api-v3.yml +++ b/docs/reference/api-v3.yml @@ -59,12 +59,12 @@ paths: name: fields description: |- Comma separated list of fields requested in the response. - + Special values: * "none": returns no fields * "raw": returns all fields as stored internally in the database * "all": returns all fields except generated fields that need to be explicitly requested such as "knowledge_panels". - + Defaults to "all" for READ requests. The "all" value can also be combined with fields like "attribute_groups" and "knowledge_panels".' responses: '200': @@ -176,7 +176,7 @@ paths: - $ref: ./requestBodies/fields_tags_lc.yaml - type: object properties: - userid: + user_id: type: string password: type: string diff --git a/lib/ProductOpener/API.pm b/lib/ProductOpener/API.pm index b19b96c60f3aa..a965c2747158b 100644 --- a/lib/ProductOpener/API.pm +++ b/lib/ProductOpener/API.pm @@ -43,6 +43,7 @@ use Log::Any qw($log); BEGIN { use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); @EXPORT_OK = qw( + &init_api_response &get_initialized_response &add_warning &add_error @@ -90,6 +91,8 @@ sub get_initialized_response() { sub init_api_response ($request_ref) { $request_ref->{api_response} = get_initialized_response(); + + $log->debug("init_api_response - done", {request => $request_ref}) if $log->is_debug(); return $request_ref->{api_response}; } @@ -170,7 +173,7 @@ sub decode_json_request_body ($request_ref) { ); } else { - eval {$request_ref->{request_body_json} = decode_json($request_ref->{body});}; + eval {$request_ref->{body_json} = decode_json($request_ref->{body});}; if ($@) { $log->error("JSON decoding error", {error => $@}) if $log->is_error(); add_error( @@ -340,41 +343,49 @@ sub process_api_request ($request_ref) { $log->debug("process_api_request - start", {request => $request_ref}) if $log->is_debug(); - my $response_ref = init_api_response($request_ref); + my $response_ref = $request_ref->{api_response}; - # Analyze the request body + # Check if we already have errors (e.g. authentification error, invalid JSON body) + if ((scalar @{$response_ref->{errors}}) > 0) { + $log->warn("process_api_request - we already have errors, skipping processing", {request => $request_ref}) + if $log->is_warn(); + } + else { - if ($request_ref->{api_action} eq "product") { + # Route the API request to the right processing function, based on API action (from path) and method - if ($request_ref->{api_method} eq "PATCH") { - write_product_api($request_ref); - } - elsif ($request_ref->{api_method} =~ /^(GET|HEAD|OPTIONS)$/) { - read_product_api($request_ref); + if ($request_ref->{api_action} eq "product") { + + if ($request_ref->{api_method} eq "PATCH") { + write_product_api($request_ref); + } + elsif ($request_ref->{api_method} =~ /^(GET|HEAD|OPTIONS)$/) { + read_product_api($request_ref); + } + else { + $log->warn("process_api_request - invalid method", {request => $request_ref}) if $log->is_warn(); + add_error( + $response_ref, + { + message => {id => "invalid_api_method"}, + field => {id => "api_method", value => $request_ref->{api_method}}, + impact => {id => "failure"}, + } + ); + } } else { - $log->warn("process_api_request - invalid method", {request => $request_ref}) if $log->is_warn(); + $log->warn("process_api_request - unknown action", {request => $request_ref}) if $log->is_warn(); add_error( $response_ref, { - message => {id => "invalid_api_method"}, - field => {id => "api_method", value => $request_ref->{api_method}}, + message => {id => "invalid_api_action"}, + field => {id => "api_action", value => $request_ref->{api_action}}, impact => {id => "failure"}, } ); } } - else { - $log->warn("process_api_request - unknown action", {request => $request_ref}) if $log->is_warn(); - add_error( - $response_ref, - { - message => {id => "invalid_api_action"}, - field => {id => "api_action", value => $request_ref->{api_action}}, - impact => {id => "failure"}, - } - ); - } determine_response_result($response_ref); @@ -536,7 +547,6 @@ sub customize_packagings ($request_ref, $product_ref) { } } } - push @$customized_packagings_ref, $customized_packaging_ref; } } diff --git a/lib/ProductOpener/APIProductWrite.pm b/lib/ProductOpener/APIProductWrite.pm index 0fcc5ed0aab64..f51cbb6d84e7b 100644 --- a/lib/ProductOpener/APIProductWrite.pm +++ b/lib/ProductOpener/APIProductWrite.pm @@ -60,7 +60,7 @@ Update product fields based on input product data. sub update_product_fields ($request_ref, $product_ref) { my $response_ref = $request_ref->{api_response}; - my $request_body_ref = $request_ref->{request_body_json}; + my $request_body_ref = $request_ref->{body_json}; $request_ref->{updated_product_fields} = {}; @@ -149,9 +149,7 @@ sub write_product_api ($request_ref) { $log->debug("write_product_api - start", {request => $request_ref}) if $log->is_debug(); my $response_ref = $request_ref->{api_response}; - - decode_json_request_body($request_ref); - my $request_body_ref = $request_ref->{request_body_json}; + my $request_body_ref = $request_ref->{body_json}; $log->debug("write_product_api - body", {request_body => $request_body_ref}) if $log->is_debug(); diff --git a/lib/ProductOpener/APITest.pm b/lib/ProductOpener/APITest.pm index 0925fd193c4b3..734dd43c0a2c5 100644 --- a/lib/ProductOpener/APITest.pm +++ b/lib/ProductOpener/APITest.pm @@ -119,7 +119,7 @@ sub wait_server() { $count++; if (($count % 3) == 0) { print("Waiting for backend to be ready since more than $count seconds...\n"); - print("Bad response from website:" . explain({url => $target_url, status => $response->code}) . "\n"); + diag explain({url => $target_url, status => $response->code, response => $response}); } confess("Waited too much for backend") if $count > 60; } @@ -421,42 +421,47 @@ sub execute_api_tests ($file, $tests_ref) { $response = $ua->request($request); } - # Check if we got the expected response status code - if (defined $test_ref->{expected_status_code}) { - is($response->code, $test_ref->{expected_status_code}) - or diag(explain($test_ref), "Response status line: " . $response->status_line); + # Check if we got the expected response status code, expect 200 if not provided + if (not defined $test_ref->{expected_status_code}) { + $test_ref->{expected_status_code} = 200; } - # Check that we got a JSON response - my $json = $response->decoded_content; - - my $decoded_json; - eval { - $decoded_json = decode_json($json); - 1; - } or do { - my $json_decode_error = $@; - diag("The $method request to $url returned a response that is not valid JSON: $json_decode_error"); - diag("Response content: " . $json); - fail($test_case); - next; - }; - - # normalize for comparison - if (defined $decoded_json->{'products'}) { - normalize_products_for_test_comparison($decoded_json->{'products'}); - } - if (defined $decoded_json->{'product'}) { - normalize_product_for_test_comparison($decoded_json->{'product'}); - } + is($response->code, $test_ref->{expected_status_code}) + or diag(explain($test_ref), "Response status line: " . $response->status_line); + + if (not((defined $test_ref->{expected_type}) and ($test_ref->{expected_type} eq "html"))) { + + # Check that we got a JSON response + my $json = $response->decoded_content; + + my $decoded_json; + eval { + $decoded_json = decode_json($json); + 1; + } or do { + my $json_decode_error = $@; + diag("The $method request to $url returned a response that is not valid JSON: $json_decode_error"); + diag("Response content: " . $json); + fail($test_case); + next; + }; + + # normalize for comparison + if (defined $decoded_json->{'products'}) { + normalize_products_for_test_comparison($decoded_json->{'products'}); + } + if (defined $decoded_json->{'product'}) { + normalize_product_for_test_comparison($decoded_json->{'product'}); + } - is( - compare_to_expected_results( - $decoded_json, "$expected_result_dir/$test_case.json", - $update_expected_results, $test_ref - ), - 1, - ); + is( + compare_to_expected_results( + $decoded_json, "$expected_result_dir/$test_case.json", + $update_expected_results, $test_ref + ), + 1, + ); + } } return; diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index 71fb0baae9514..d4158e378c62b 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -518,7 +518,7 @@ A scalar value for the parameter, or undef if the parameter is not defined. =cut sub request_param ($request_ref, $param_name) { - return (scalar param($param_name)) || deep_get($request_ref, "request_body_json", $param_name); + return (scalar param($param_name)) || deep_get($request_ref, "body_json", $param_name); } =head2 init_request () @@ -531,24 +531,34 @@ $lc : language code It also initializes a request object that is returned. +=head3 Parameters + +=head4 (optional) Request object reference $request_ref + +This function may be passed an existing request object reference +(e.g. pre-containing some fields of the request, like a JSON body). + +If not passed, a new request object will be created. + + =head3 Return value Reference to request object. =cut -sub init_request() { +sub init_request ($request_ref = {}) { + + $log->debug("init_request - start", {request_ref => $request_ref}) if $log->is_debug(); # Clear the context delete $log->context->{user_id}; delete $log->context->{user_session}; $log->context->{request} = generate_token(16); - # Create and initialize a request object - my $request_ref = { - 'original_query_string' => $ENV{QUERY_STRING}, - 'referer' => referer() - }; + # Initialize the request object + $request_ref->{referer} = referer(); + $request_ref->{original_query_string} = $ENV{QUERY_STRING}; # Depending on web server configuration, we may get or not get a / at the start of the QUERY_STRING environment variable # remove the / to normalize the query string, as we use it to build some redirect urls @@ -745,13 +755,36 @@ sub init_request() { my $error = ProductOpener::Users::init_user($request_ref); if ($error) { - # TODO: currently we always display an HTML message if we were passed a bad user_id and password combination - # even if the request is an API request + # We were sent bad user_id / password credentials + # If it is an API v3 query, the error will be handled by API::process_api_request() + if ((defined $request_ref->{api_version}) and ($request_ref->{api_version} >= 3)) { + $log->debug( + "init_request - init_user error - API v3: continue", + {init_user_error => $request_ref->{init_user_error}} + ) if $log->is_debug(); + add_error( + $request_ref->{api_response}, + { + message => {id => "invalid_user_id_and_password"}, + impact => {id => "failure"}, + } + ); + } + # /cgi/auth.pl returns a JSON body # for requests to /cgi/auth.pl, we will now return a JSON body, set in /cgi/auth.pl - # but it would be good to later have a more consistent behaviour for all API requests - if ($r->uri() !~ /\/cgi\/auth\.pl/) { - print $r->uri(); + elsif ($r->uri() =~ /\/cgi\/auth\.pl/) { + $log->debug( + "init_request - init_user error - /cgi/auth.pl: continue", + {init_user_error => $request_ref->{init_user_error}} + ) if $log->is_debug(); + } + # Otherwise we return an error page in HTML (including for v0 / v1 / v2 API queries) + else { + $log->debug( + "init_request - init_user error - display error page", + {init_user_error => $request_ref->{init_user_error}} + ) if $log->is_debug(); display_error_and_exit($error, 403); } } diff --git a/lib/ProductOpener/Users.pm b/lib/ProductOpener/Users.pm index 6bb08d0abef5a..9f41c1ec55c3c 100644 --- a/lib/ProductOpener/Users.pm +++ b/lib/ProductOpener/Users.pm @@ -895,7 +895,7 @@ sub init_user ($request_ref) { %Org = (); # Remove persistent cookie if user is logging out - if ((defined single_param('length')) and (single_param('length') eq 'logout')) { + if ((defined request_param($request_ref, 'length')) and (request_param($request_ref, 'length') eq 'logout')) { $log->debug("user logout") if $log->is_debug(); my $session = {}; $request_ref->{cookie} = cookie( @@ -908,12 +908,12 @@ sub init_user ($request_ref) { } # Retrieve user_id and password from form parameters - elsif ( (defined single_param('user_id')) - and (single_param('user_id') ne '') - and (((defined single_param('password')) and (single_param('password') ne '')))) + elsif ( (defined request_param($request_ref, 'user_id')) + and (request_param($request_ref, 'user_id') ne '') + and (((defined request_param($request_ref, 'password')) and (request_param($request_ref, 'password') ne '')))) { - $user_id = remove_tags_and_quote(single_param('user_id')); + $user_id = remove_tags_and_quote(request_param($request_ref, 'user_id')); if ($user_id =~ /\@/) { $log->info("got email while initializing user", {email => $user_id}) if $log->is_info(); @@ -950,7 +950,8 @@ sub init_user ($request_ref) { $user_id = $user_ref->{'userid'}; $log->context->{user_id} = $user_id; - my $hash_is_correct = check_password_hash(encode_utf8(decode utf8 => single_param('password')), + my $hash_is_correct + = check_password_hash(encode_utf8(decode utf8 => request_param($request_ref, 'password')), $user_ref->{'encrypted_password'}); # We don't have the right password if (not $hash_is_correct) { @@ -963,7 +964,8 @@ sub init_user ($request_ref) { return ($Lang{error_bad_login_password}{$lang}); } # We have the right login/password - elsif (not defined single_param('no_log')) # no need to store sessions for internal requests + elsif ( + not defined request_param($request_ref, 'no_log')) # no need to store sessions for internal requests { $log->info("correct password for user provided") if $log->is_info(); @@ -1002,9 +1004,6 @@ sub init_user ($request_ref) { if (defined $user_id) { my $user_file = "$data_root/users/" . get_string_id_for_lang("no_language", $user_id) . ".sto"; - if ($user_id =~ /f\/(.*)$/) { - $user_file = "$data_root/facebook_users/" . get_string_id_for_lang("no_language", $1) . ".sto"; - } if (-e $user_file) { $user_ref = retrieve($user_file); diff --git a/po/common/common.pot b/po/common/common.pot index ff3a9b98bcb7b..0cfda6f49cec4 100644 --- a/po/common/common.pot +++ b/po/common/common.pot @@ -6508,3 +6508,7 @@ msgctxt "packagings_complete" msgid "All the packaging parts of the product are listed." msgstr "All the packaging parts of the product are listed." +msgctxt "api_message_invalid_user_id_and_password" +msgid "Invalid user id and password" +msgstr "Invalid user id and password" + diff --git a/po/common/en.po b/po/common/en.po index 8dba2bc603ee8..2f46633ba3907 100644 --- a/po/common/en.po +++ b/po/common/en.po @@ -6451,3 +6451,8 @@ msgstr "Help categorize more {title} on Hunger Games" msgctxt "packagings_complete" msgid "All the packaging parts of the product are listed." msgstr "All the packaging parts of the product are listed." + +msgctxt "api_message_invalid_user_id_and_password" +msgid "Invalid user id and password" +msgstr "Invalid user id and password" + diff --git a/tests/integration/api_v2_product_read.t b/tests/integration/api_v2_product_read.t index 640ecbde4e072..345832b37c2fe 100644 --- a/tests/integration/api_v2_product_read.t +++ b/tests/integration/api_v2_product_read.t @@ -113,31 +113,49 @@ my $tests_ref = [ { test_case => 'get-fields-raw', method => 'GET', - path => '/api/v3/product/200000000034', + path => '/api/v2/product/200000000034', query_string => '?fields=raw', expected_status_code => 200, }, { test_case => 'get-fields-all', method => 'GET', - path => '/api/v3/product/200000000034', + path => '/api/v2/product/200000000034', query_string => '?fields=all', expected_status_code => 200, }, { test_case => 'get-fields-all-knowledge-panels', method => 'GET', - path => '/api/v3/product/200000000034', + path => '/api/v2/product/200000000034', query_string => '?fields=all,knowledge_panels', expected_status_code => 200, }, { test_case => 'get-fields-attribute-groups-all-knowledge-panels', method => 'GET', - path => '/api/v3/product/200000000034', + path => '/api/v2/product/200000000034', query_string => '?fields=attribute_groups,all,knowledge_panels', expected_status_code => 200, }, + # Test authentication + # (currently not needed for READ requests, but it could in the future, for instance to get personalized results) + { + test_case => 'get-auth-good-password', + method => 'GET', + path => '/api/v2/product/200000000034', + query_string => '?fields=code,product_name&user_id=tests&password=testtest', + expected_status_code => 200, + }, + # When authentification fails for a v2 request, we return a HTML page + { + test_case => 'get-auth-bad-user-password', + method => 'GET', + path => '/api/v2/product/200000000034', + query_string => '?fields=code,product_name&user_id=tests&password=bad_password', + expected_status_code => 200, + expected_type => "html", + }, ]; execute_api_tests(__FILE__, $tests_ref); diff --git a/tests/integration/api_v2_product_write.t b/tests/integration/api_v2_product_write.t index a126c27ab77eb..f5afc73b1e3e9 100644 --- a/tests/integration/api_v2_product_write.t +++ b/tests/integration/api_v2_product_write.t @@ -48,6 +48,61 @@ my $tests_ref = [ method => 'GET', path => '/api/v2/product/1234567890001', }, + # Test authentication + { + test_case => 'post-product-auth-good-password', + method => 'POST', + path => '/cgi/product_jqm_multilingual.pl', + form => { + user_id => "tests", + password => "testtest", + cc => "be", + lc => "fr", + code => "1234567890002", + product_name => "Product name", + categories => "Cookies", + quantity => "250 g", + serving_size => '20 g', + ingredients_text_fr => "Farine de blé, eau, sel, sucre", + labels => "Bio, Max Havelaar", + nutriment_salt => '50.2', + nutriment_salt_unit => 'mg', + nutriment_sugars => '12.5', + } + }, + { + test_case => 'get-product-auth-good-password', + method => 'GET', + path => '/api/v2/product/1234567890002', + }, + { + test_case => 'post-product-auth-bad-user-password', + method => 'POST', + path => '/cgi/product_jqm_multilingual.pl', + form => { + user_id => "tests", + password => "bad password", + cc => "be", + lc => "fr", + code => "1234567890003", + product_name => "Product name", + categories => "Cookies", + quantity => "250 g", + serving_size => '20 g', + ingredients_text_fr => "Farine de blé, eau, sel, sucre", + labels => "Bio, Max Havelaar", + nutriment_salt => '50.2', + nutriment_salt_unit => 'mg', + nutriment_sugars => '12.5', + }, + expected_type => "html", + }, + { + test_case => 'get-product-auth-bad-user-password', + method => 'GET', + path => '/api/v2/product/1234567890003', + expected_status_code => 404, + }, ]; execute_api_tests(__FILE__, $tests_ref); diff --git a/tests/integration/api_v3_product_read.t b/tests/integration/api_v3_product_read.t index 8b9d39630ebc7..5da99d69dc9af 100644 --- a/tests/integration/api_v3_product_read.t +++ b/tests/integration/api_v3_product_read.t @@ -143,6 +143,23 @@ my $tests_ref = [ query_string => '?fields=attribute_groups,all,knowledge_panels', expected_status_code => 200, }, + # Test authentication + # (currently not needed for READ requests, but it could in the future, for instance to get personalized results) + { + test_case => 'get-auth-good-password', + method => 'GET', + path => '/api/v3/product/200000000034', + query_string => '?fields=code,product_name&user_id=tests&password=testtest', + expected_status_code => 200, + }, + { + test_case => 'get-auth-bad-user-password', + method => 'GET', + path => '/api/v3/product/200000000034', + query_string => '?fields=code,product_name&user_id=tests&password=bad_password', + expected_status_code => 200, + }, + ]; execute_api_tests(__FILE__, $tests_ref); diff --git a/tests/integration/api_v3_product_write.t b/tests/integration/api_v3_product_write.t index 9871558f36b88..3c7da823d7998 100644 --- a/tests/integration/api_v3_product_write.t +++ b/tests/integration/api_v3_product_write.t @@ -393,7 +393,7 @@ my $tests_ref = [ "product": { "packagings": [ { - "number_of_units": 1, + "number_of_units": 1, "shape": {"lc_name": "Bottle"}, "weight_measured": 0.43 }, @@ -406,7 +406,48 @@ my $tests_ref = [ "number_of_units": 3, "shape": {"lc_name": "Lid"}, "weight_measured": "0,43" - } + } + ] + } + }' + }, + # Test authentication + { + test_case => 'patch-auth-good-password', + method => 'PATCH', + path => '/api/v3/product/1234567890014', + body => '{ + "user_id": "tests", + "password": "testtest", + "fields": "creator,editors_tags,packagings", + "tags_lc": "en", + "product": { + "packagings": [ + { + "number_of_units": 1, + "shape": {"lc_name": "can"}, + "recycling": {"lc_name": "recycle"} + } + ] + } + }' + }, + { + test_case => 'patch-auth-bad-user-password', + method => 'PATCH', + path => '/api/v3/product/1234567890015', + body => '{ + "user_id": "tests", + "password": "bad password", + "fields": "creator,editors_tags,packagings", + "tags_lc": "en", + "product": { + "packagings": [ + { + "number_of_units": 1, + "shape": {"lc_name": "can"}, + "recycling": {"lc_name": "recycle"} + } ] } }' diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-auth-good-password.json b/tests/integration/expected_test_results/api_v2_product_read/get-auth-good-password.json new file mode 100644 index 0000000000000..47cb4242de9b2 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_read/get-auth-good-password.json @@ -0,0 +1,9 @@ +{ + "code" : "200000000034", + "product" : { + "code" : "200000000034", + "product_name" : "Some product" + }, + "status" : 1, + "status_verbose" : "product found" +} diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-all-knowledge-panels.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-all-knowledge-panels.json index a57e633c0dba8..380e302106ca5 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-all-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-all-knowledge-panels.json @@ -1,6 +1,5 @@ { "code" : "200000000034", - "errors" : [], "product" : { "_id" : "200000000034", "_keywords" : [ @@ -1919,55 +1918,31 @@ "packaging_text_en" : "1 wooden box to recycle, 6 25cl glass bottles to reuse, 3 steel lids to recycle, 1 plastic film to discard", "packagings" : [ { - "material" : { - "id" : "en:wood" - }, + "material" : "en:wood", "number_of_units" : 1, - "recycling" : { - "id" : "en:recycle" - }, - "shape" : { - "id" : "en:box" - } + "recycling" : "en:recycle", + "shape" : "en:box" }, { - "material" : { - "id" : "en:glass" - }, + "material" : "en:glass", "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, - "recycling" : { - "id" : "en:reuse" - }, - "shape" : { - "id" : "en:bottle" - } + "recycling" : "en:reuse", + "shape" : "en:bottle" }, { - "material" : { - "id" : "en:steel" - }, + "material" : "en:steel", "number_of_units" : 3, - "recycling" : { - "id" : "en:recycle" - }, - "shape" : { - "id" : "en:lid" - } + "recycling" : "en:recycle", + "shape" : "en:lid" }, { - "material" : { - "id" : "en:plastic" - }, + "material" : "en:plastic", "number_of_units" : 1, - "recycling" : { - "id" : "en:discard" - }, - "shape" : { - "id" : "en:film" - } + "recycling" : "en:discard", + "shape" : "en:film" } ], "photographers_tags" : [], @@ -2031,11 +2006,6 @@ "unknown_nutrients_tags" : [], "vitamins_tags" : [] }, - "result" : { - "id" : "product_found", - "lc_name" : "Product found", - "name" : "Product found" - }, - "status" : "success", - "warnings" : [] + "status" : 1, + "status_verbose" : "product found" } diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-all.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-all.json index 3dabb5af4868d..571989cb6586e 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-all.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-all.json @@ -1,6 +1,5 @@ { "code" : "200000000034", - "errors" : [], "product" : { "_id" : "200000000034", "_keywords" : [ @@ -775,55 +774,31 @@ "packaging_text_en" : "1 wooden box to recycle, 6 25cl glass bottles to reuse, 3 steel lids to recycle, 1 plastic film to discard", "packagings" : [ { - "material" : { - "id" : "en:wood" - }, + "material" : "en:wood", "number_of_units" : 1, - "recycling" : { - "id" : "en:recycle" - }, - "shape" : { - "id" : "en:box" - } + "recycling" : "en:recycle", + "shape" : "en:box" }, { - "material" : { - "id" : "en:glass" - }, + "material" : "en:glass", "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, - "recycling" : { - "id" : "en:reuse" - }, - "shape" : { - "id" : "en:bottle" - } + "recycling" : "en:reuse", + "shape" : "en:bottle" }, { - "material" : { - "id" : "en:steel" - }, + "material" : "en:steel", "number_of_units" : 3, - "recycling" : { - "id" : "en:recycle" - }, - "shape" : { - "id" : "en:lid" - } + "recycling" : "en:recycle", + "shape" : "en:lid" }, { - "material" : { - "id" : "en:plastic" - }, + "material" : "en:plastic", "number_of_units" : 1, - "recycling" : { - "id" : "en:discard" - }, - "shape" : { - "id" : "en:film" - } + "recycling" : "en:discard", + "shape" : "en:film" } ], "photographers_tags" : [], @@ -887,11 +862,6 @@ "unknown_nutrients_tags" : [], "vitamins_tags" : [] }, - "result" : { - "id" : "product_found", - "lc_name" : "Product found", - "name" : "Product found" - }, - "status" : "success", - "warnings" : [] + "status" : 1, + "status_verbose" : "product found" } diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-attribute-groups-all-knowledge-panels.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-attribute-groups-all-knowledge-panels.json index c3e012990de08..932300f1c38e2 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-attribute-groups-all-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-attribute-groups-all-knowledge-panels.json @@ -1,6 +1,5 @@ { "code" : "200000000034", - "errors" : [], "product" : { "_id" : "200000000034", "_keywords" : [ @@ -2214,55 +2213,31 @@ "packaging_text_en" : "1 wooden box to recycle, 6 25cl glass bottles to reuse, 3 steel lids to recycle, 1 plastic film to discard", "packagings" : [ { - "material" : { - "id" : "en:wood" - }, + "material" : "en:wood", "number_of_units" : 1, - "recycling" : { - "id" : "en:recycle" - }, - "shape" : { - "id" : "en:box" - } + "recycling" : "en:recycle", + "shape" : "en:box" }, { - "material" : { - "id" : "en:glass" - }, + "material" : "en:glass", "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, - "recycling" : { - "id" : "en:reuse" - }, - "shape" : { - "id" : "en:bottle" - } + "recycling" : "en:reuse", + "shape" : "en:bottle" }, { - "material" : { - "id" : "en:steel" - }, + "material" : "en:steel", "number_of_units" : 3, - "recycling" : { - "id" : "en:recycle" - }, - "shape" : { - "id" : "en:lid" - } + "recycling" : "en:recycle", + "shape" : "en:lid" }, { - "material" : { - "id" : "en:plastic" - }, + "material" : "en:plastic", "number_of_units" : 1, - "recycling" : { - "id" : "en:discard" - }, - "shape" : { - "id" : "en:film" - } + "recycling" : "en:discard", + "shape" : "en:film" } ], "photographers_tags" : [], @@ -2326,11 +2301,6 @@ "unknown_nutrients_tags" : [], "vitamins_tags" : [] }, - "result" : { - "id" : "product_found", - "lc_name" : "Product found", - "name" : "Product found" - }, - "status" : "success", - "warnings" : [] + "status" : 1, + "status_verbose" : "product found" } diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-raw.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-raw.json index b39012911bd0d..8a6e89897be84 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-raw.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-raw.json @@ -1,6 +1,5 @@ { "code" : "200000000034", - "errors" : [], "product" : { "_id" : "200000000034", "_keywords" : [ @@ -858,11 +857,6 @@ "unknown_nutrients_tags" : [], "vitamins_tags" : [] }, - "result" : { - "id" : "product_found", - "lc_name" : "Product found", - "name" : "Product found" - }, - "status" : "success", - "warnings" : [] + "status" : 1, + "status_verbose" : "product found" } diff --git a/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-bad-user-password.json b/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-bad-user-password.json new file mode 100644 index 0000000000000..1e238a6f5760e --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-bad-user-password.json @@ -0,0 +1,5 @@ +{ + "code" : "1234567890003", + "status" : 0, + "status_verbose" : "product not found" +} diff --git a/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json b/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json new file mode 100644 index 0000000000000..0aed309b3e1dd --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json @@ -0,0 +1,803 @@ +{ + "code" : "1234567890002", + "product" : { + "_id" : "1234567890002", + "_keywords" : [ + "bio", + "cookie", + "havelaar", + "max", + "name", + "product" + ], + "added_countries_tags" : [], + "additives_n" : 0, + "additives_old_n" : 0, + "additives_old_tags" : [], + "additives_original_tags" : [], + "additives_tags" : [], + "allergens" : "", + "allergens_from_ingredients" : "en:gluten, blé", + "allergens_from_user" : "(fr) ", + "allergens_hierarchy" : [ + "en:gluten" + ], + "allergens_tags" : [ + "en:gluten" + ], + "amino_acids_tags" : [], + "categories" : "Cookies", + "categories_hierarchy" : [ + "en:snacks", + "en:sweet-snacks", + "en:biscuits-and-cakes", + "en:biscuits", + "en:drop-cookies" + ], + "categories_lc" : "fr", + "categories_properties" : { + "agribalyse_proxy_food_code:en" : "24000" + }, + "categories_properties_tags" : [ + "all-products", + "categories-known", + "agribalyse-food-code-unknown", + "agribalyse-proxy-food-code-24000", + "agribalyse-proxy-food-code-known", + "ciqual-food-code-unknown", + "agribalyse-known", + "agribalyse-24000" + ], + "categories_tags" : [ + "en:snacks", + "en:sweet-snacks", + "en:biscuits-and-cakes", + "en:biscuits", + "en:drop-cookies" + ], + "checkers_tags" : [], + "code" : "1234567890002", + "codes_tags" : [ + "code-13", + "1234567890xxx", + "123456789xxxx", + "12345678xxxxx", + "1234567xxxxxx", + "123456xxxxxxx", + "12345xxxxxxxx", + "1234xxxxxxxxx", + "123xxxxxxxxxx", + "12xxxxxxxxxxx", + "1xxxxxxxxxxxx" + ], + "complete" : 0, + "completeness" : 0.5, + "correctors_tags" : [], + "countries" : "en:belgium", + "countries_hierarchy" : [ + "en:belgium" + ], + "countries_tags" : [ + "en:belgium" + ], + "created_t" : "--ignore--", + "creator" : "tests", + "data_quality_bugs_tags" : [], + "data_quality_errors_tags" : [], + "data_quality_info_tags" : [ + "en:no-packaging-data", + "en:ingredients-percent-analysis-ok", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-known", + "en:food-groups-2-known", + "en:food-groups-3-unknown" + ], + "data_quality_tags" : [ + "en:no-packaging-data", + "en:ingredients-percent-analysis-ok", + "en:ecoscore-extended-data-not-computed", + "en:food-groups-1-known", + "en:food-groups-2-known", + "en:food-groups-3-unknown", + "en:nutrition-value-under-0-1-g-salt", + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label" + ], + "data_quality_warnings_tags" : [ + "en:nutrition-value-under-0-1-g-salt", + "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown", + "en:ecoscore-packaging-packaging-data-missing", + "en:ecoscore-production-system-no-label" + ], + "ecoscore_data" : { + "adjustments" : { + "origins_of_ingredients" : { + "aggregated_origins" : [ + { + "epi_score" : 0, + "origin" : "en:unknown", + "percent" : 100, + "transportation_score" : null + } + ], + "epi_score" : 0, + "epi_value" : -5, + "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" : { + "non_recyclable_and_non_biodegradable_materials" : 1, + "value" : -15, + "warning" : "packaging_data_missing" + }, + "production_system" : { + "labels" : [], + "value" : 0, + "warning" : "no_label" + }, + "threatened_species" : {} + }, + "agribalyse" : { + "agribalyse_proxy_food_code" : "24000", + "co2_agriculture" : 2.3889426, + "co2_consumption" : 0, + "co2_distribution" : 0.019530673, + "co2_packaging" : 0.11014808, + "co2_processing" : 0.22878446, + "co2_total" : 2.882859363, + "co2_transportation" : 0.13545355, + "code" : "24000", + "dqr" : "2.14", + "ef_agriculture" : 0.28329233, + "ef_consumption" : 0, + "ef_distribution" : 0.0048315303, + "ef_packaging" : 0.01096965, + "ef_processing" : 0.041686082, + "ef_total" : 0.3518903043, + "ef_transportation" : 0.011110712, + "is_beverage" : 0, + "name_en" : "Biscuit (cookie)", + "name_fr" : "Biscuit sec, sans précision", + "score" : 69, + "version" : "3.1" + }, + "grade" : "c", + "grades" : { + "ad" : "c", + "al" : "c", + "at" : "c", + "ax" : "c", + "ba" : "c", + "be" : "c", + "bg" : "c", + "ch" : "c", + "cy" : "c", + "cz" : "c", + "de" : "c", + "dk" : "c", + "dz" : "c", + "ee" : "c", + "eg" : "c", + "es" : "c", + "fi" : "c", + "fo" : "c", + "fr" : "c", + "gg" : "c", + "gi" : "c", + "gr" : "c", + "hr" : "c", + "hu" : "c", + "ie" : "c", + "il" : "c", + "im" : "c", + "is" : "c", + "it" : "c", + "je" : "c", + "lb" : "c", + "li" : "c", + "lt" : "c", + "lu" : "c", + "lv" : "c", + "ly" : "c", + "ma" : "c", + "mc" : "c", + "md" : "c", + "me" : "c", + "mk" : "c", + "mt" : "c", + "nl" : "c", + "no" : "c", + "pl" : "c", + "ps" : "c", + "pt" : "c", + "ro" : "c", + "rs" : "c", + "se" : "c", + "si" : "c", + "sj" : "c", + "sk" : "c", + "sm" : "c", + "sy" : "c", + "tn" : "c", + "tr" : "c", + "ua" : "c", + "uk" : "c", + "us" : "c", + "va" : "c", + "world" : "c", + "xk" : "c" + }, + "missing" : { + "labels" : 1, + "origins" : 1, + "packagings" : 1 + }, + "missing_data_warning" : 1, + "missing_key_data" : 1, + "score" : 49, + "scores" : { + "ad" : 49, + "al" : 49, + "at" : 49, + "ax" : 49, + "ba" : 49, + "be" : 49, + "bg" : 49, + "ch" : 49, + "cy" : 49, + "cz" : 49, + "de" : 49, + "dk" : 49, + "dz" : 49, + "ee" : 49, + "eg" : 49, + "es" : 49, + "fi" : 49, + "fo" : 49, + "fr" : 49, + "gg" : 49, + "gi" : 49, + "gr" : 49, + "hr" : 49, + "hu" : 49, + "ie" : 49, + "il" : 49, + "im" : 49, + "is" : 49, + "it" : 49, + "je" : 49, + "lb" : 49, + "li" : 49, + "lt" : 49, + "lu" : 49, + "lv" : 49, + "ly" : 49, + "ma" : 49, + "mc" : 49, + "md" : 49, + "me" : 49, + "mk" : 49, + "mt" : 49, + "nl" : 49, + "no" : 49, + "pl" : 49, + "ps" : 49, + "pt" : 49, + "ro" : 49, + "rs" : 49, + "se" : 49, + "si" : 49, + "sj" : 49, + "sk" : 49, + "sm" : 49, + "sy" : 49, + "tn" : 49, + "tr" : 49, + "ua" : 49, + "uk" : 49, + "us" : 49, + "va" : 49, + "world" : 49, + "xk" : 49 + }, + "status" : "known" + }, + "ecoscore_grade" : "c", + "ecoscore_score" : 49, + "ecoscore_tags" : [ + "c" + ], + "editors_tags" : [ + "tests" + ], + "entry_dates_tags" : "--ignore--", + "food_groups" : "en:biscuits-and-cakes", + "food_groups_tags" : [ + "en:sugary-snacks", + "en:biscuits-and-cakes" + ], + "grades" : {}, + "id" : "1234567890002", + "informers_tags" : [ + "tests" + ], + "ingredients" : [ + { + "id" : "en:wheat-flour", + "percent_estimate" : 62.5, + "percent_max" : 100, + "percent_min" : 25, + "text" : "Farine de blé", + "vegan" : "yes", + "vegetarian" : "yes" + }, + { + "id" : "en:water", + "percent_estimate" : 18.75, + "percent_max" : 50, + "percent_min" : 0, + "text" : "eau", + "vegan" : "yes", + "vegetarian" : "yes" + }, + { + "id" : "en:salt", + "percent_estimate" : 9.375, + "percent_max" : 33.3333333333333, + "percent_min" : 0, + "text" : "sel", + "vegan" : "yes", + "vegetarian" : "yes" + }, + { + "id" : "en:sugar", + "percent_estimate" : 9.375, + "percent_max" : 25, + "percent_min" : 0, + "text" : "sucre", + "vegan" : "yes", + "vegetarian" : "yes" + } + ], + "ingredients_analysis" : {}, + "ingredients_analysis_tags" : [ + "en:palm-oil-free", + "en:vegan", + "en:vegetarian" + ], + "ingredients_from_or_that_may_be_from_palm_oil_n" : 0, + "ingredients_from_palm_oil_n" : 0, + "ingredients_from_palm_oil_tags" : [], + "ingredients_hierarchy" : [ + "en:wheat-flour", + "en:cereal", + "en:flour", + "en:wheat", + "en:cereal-flour", + "en:water", + "en:salt", + "en:sugar", + "en:added-sugar", + "en:disaccharide" + ], + "ingredients_n" : 4, + "ingredients_n_tags" : [ + "4", + "1-10" + ], + "ingredients_original_tags" : [ + "en:wheat-flour", + "en:water", + "en:salt", + "en:sugar" + ], + "ingredients_percent_analysis" : 1, + "ingredients_tags" : [ + "en:wheat-flour", + "en:cereal", + "en:flour", + "en:wheat", + "en:cereal-flour", + "en:water", + "en:salt", + "en:sugar", + "en:added-sugar", + "en:disaccharide" + ], + "ingredients_text" : "Farine de blé, eau, sel, sucre", + "ingredients_text_fr" : "Farine de blé, eau, sel, sucre", + "ingredients_text_with_allergens" : "Farine de blé, eau, sel, sucre", + "ingredients_text_with_allergens_fr" : "Farine de blé, eau, sel, sucre", + "ingredients_that_may_be_from_palm_oil_n" : 0, + "ingredients_that_may_be_from_palm_oil_tags" : [], + "ingredients_with_specified_percent_n" : 0, + "ingredients_with_specified_percent_sum" : 0, + "ingredients_with_unspecified_percent_n" : 4, + "ingredients_with_unspecified_percent_sum" : 100, + "interface_version_created" : "20150316.jqm2", + "interface_version_modified" : "20150316.jqm2", + "known_ingredients_n" : 10, + "labels" : "Bio, Max Havelaar", + "labels_hierarchy" : [ + "en:organic", + "en:fair-trade", + "en:max-havelaar" + ], + "labels_lc" : "fr", + "labels_tags" : [ + "en:organic", + "en:fair-trade", + "en:max-havelaar" + ], + "lang" : "fr", + "languages" : { + "en:french" : 2 + }, + "languages_codes" : { + "fr" : 2 + }, + "languages_hierarchy" : [ + "en:french" + ], + "languages_tags" : [ + "en:french", + "en:1" + ], + "last_edit_dates_tags" : "--ignore--", + "last_editor" : "tests", + "last_modified_by" : "tests", + "last_modified_t" : "--ignore--", + "lc" : "fr", + "main_countries_tags" : [], + "minerals_tags" : [], + "misc_tags" : [ + "en:nutriscore-not-computed", + "en:nutrition-not-enough-data-to-compute-nutrition-score", + "en:nutriscore-missing-nutrition-data", + "en:nutriscore-missing-nutrition-data-energy", + "en:nutriscore-missing-nutrition-data-fat", + "en:nutriscore-missing-nutrition-data-saturated-fat", + "en:nutriscore-missing-nutrition-data-proteins", + "en:nutrition-no-fiber", + "en:packagings-not-complete", + "en:packagings-empty", + "en:ecoscore-extended-data-not-computed", + "en:ecoscore-missing-data-warning", + "en:ecoscore-missing-data-labels", + "en:ecoscore-missing-data-origins", + "en:ecoscore-missing-data-packagings", + "en:ecoscore-missing-data-no-packagings", + "en:ecoscore-computed", + "en:ecoscore-changed", + "en:ecoscore-grade-changed", + "en:main-countries-new-product" + ], + "nova_group" : 3, + "nova_group_debug" : "", + "nova_groups" : "3", + "nova_groups_markers" : { + "3" : [ + [ + "ingredients", + "en:salt" + ], + [ + "ingredients", + "en:sugar" + ], + [ + "categories", + "en:sweet-snacks" + ] + ] + }, + "nova_groups_tags" : [ + "en:3-processed-foods" + ], + "nucleotides_tags" : [], + "nutrient_levels" : { + "salt" : "low", + "sugars" : "moderate" + }, + "nutrient_levels_tags" : [ + "en:sugars-in-moderate-quantity", + "en:salt-in-low-quantity" + ], + "nutriments" : { + "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 0, + "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 0, + "nova-group" : 3, + "nova-group_100g" : 3, + "nova-group_serving" : 3, + "salt" : 0.0502, + "salt_100g" : 0.0502, + "salt_serving" : 0.01, + "salt_unit" : "mg", + "salt_value" : 50.2, + "sodium" : 0.02008, + "sodium_100g" : 0.02008, + "sodium_serving" : 0.00402, + "sodium_unit" : "mg", + "sodium_value" : 20.08, + "sugars" : 12.5, + "sugars_100g" : 12.5, + "sugars_serving" : 2.5, + "sugars_unit" : "g", + "sugars_value" : 12.5 + }, + "nutrition_data" : "on", + "nutrition_data_per" : "100g", + "nutrition_data_prepared_per" : "100g", + "nutrition_grades_tags" : [ + "unknown" + ], + "nutrition_score_beverage" : 0, + "nutrition_score_debug" : "missing energy - missing fat - missing saturated-fat - missing proteins", + "nutrition_score_warning_no_fiber" : 1, + "other_nutritional_substances_tags" : [], + "packagings" : [], + "photographers_tags" : [], + "pnns_groups_1" : "Sugary snacks", + "pnns_groups_1_tags" : [ + "sugary-snacks", + "known" + ], + "pnns_groups_2" : "Biscuits and cakes", + "pnns_groups_2_tags" : [ + "biscuits-and-cakes", + "known" + ], + "popularity_key" : 0, + "product_name" : "Product name", + "product_name_fr" : "Product name", + "product_quantity" : "250", + "quantity" : "250 g", + "removed_countries_tags" : [], + "rev" : 1, + "scores" : {}, + "serving_quantity" : "20", + "serving_size" : "20 g", + "states" : "en:to-be-completed, en:nutrition-facts-completed, en:ingredients-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-to-be-completed, en:packaging-to-be-completed, en:quantity-completed, en:product-name-completed, en:photos-to-be-uploaded", + "states_hierarchy" : [ + "en:to-be-completed", + "en:nutrition-facts-completed", + "en:ingredients-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-to-be-completed", + "en:packaging-to-be-completed", + "en:quantity-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "states_tags" : [ + "en:to-be-completed", + "en:nutrition-facts-completed", + "en:ingredients-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-to-be-completed", + "en:packaging-to-be-completed", + "en:quantity-completed", + "en:product-name-completed", + "en:photos-to-be-uploaded" + ], + "traces" : "", + "traces_from_ingredients" : "", + "traces_from_user" : "(fr) ", + "traces_hierarchy" : [], + "traces_tags" : [], + "unknown_ingredients_n" : 0, + "unknown_nutrients_tags" : [], + "vitamins_tags" : [] + }, + "status" : 1, + "status_verbose" : "product found" +} diff --git a/tests/integration/expected_test_results/api_v2_product_write/post-product-auth-good-password.json b/tests/integration/expected_test_results/api_v2_product_write/post-product-auth-good-password.json new file mode 100644 index 0000000000000..39d4261a9d3f0 --- /dev/null +++ b/tests/integration/expected_test_results/api_v2_product_write/post-product-auth-good-password.json @@ -0,0 +1,4 @@ +{ + "status" : 1, + "status_verbose" : "fields saved" +} diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-auth-bad-user-password.json b/tests/integration/expected_test_results/api_v3_product_read/get-auth-bad-user-password.json new file mode 100644 index 0000000000000..557df6ed3a545 --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_read/get-auth-bad-user-password.json @@ -0,0 +1,18 @@ +{ + "errors" : [ + { + "impact" : { + "id" : "failure", + "lc_name" : "Failure", + "name" : "Failure" + }, + "message" : { + "id" : "invalid_user_id_and_password", + "lc_name" : "Invalid user id and password", + "name" : "Invalid user id and password" + } + } + ], + "status" : "failure", + "warnings" : [] +} diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-auth-good-password.json b/tests/integration/expected_test_results/api_v3_product_read/get-auth-good-password.json new file mode 100644 index 0000000000000..7e6b07e248b1c --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_read/get-auth-good-password.json @@ -0,0 +1,15 @@ +{ + "code" : "200000000034", + "errors" : [], + "product" : { + "code" : "200000000034", + "product_name" : "Some product" + }, + "result" : { + "id" : "product_found", + "lc_name" : "Product found", + "name" : "Product found" + }, + "status" : "success", + "warnings" : [] +} diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-auth-bad-user-password.json b/tests/integration/expected_test_results/api_v3_product_write/patch-auth-bad-user-password.json new file mode 100644 index 0000000000000..557df6ed3a545 --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-auth-bad-user-password.json @@ -0,0 +1,18 @@ +{ + "errors" : [ + { + "impact" : { + "id" : "failure", + "lc_name" : "Failure", + "name" : "Failure" + }, + "message" : { + "id" : "invalid_user_id_and_password", + "lc_name" : "Invalid user id and password", + "name" : "Invalid user id and password" + } + } + ], + "status" : "failure", + "warnings" : [] +} diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-auth-good-password.json b/tests/integration/expected_test_results/api_v3_product_write/patch-auth-good-password.json new file mode 100644 index 0000000000000..5c21dfcdba740 --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-auth-good-password.json @@ -0,0 +1,42 @@ +{ + "code" : "1234567890014", + "errors" : [], + "product" : { + "creator" : "tests", + "editors_tags" : [ + "tests" + ], + "packagings" : [ + { + "number_of_units" : 1, + "recycling" : { + "id" : "en:recycle", + "lc_name" : "Recycle" + }, + "shape" : { + "id" : "en:can", + "lc_name" : "Can" + } + } + ] + }, + "status" : "success_with_warnings", + "warnings" : [ + { + "field" : { + "id" : "material", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + } + ] +}