Bug 26384: Fix executable flags
[koha-ffzg.git] / t / db_dependent / Koha / REST / Plugin / Objects.t
old mode 100644 (file)
new mode 100755 (executable)
index 04d7aab..d5db5e7
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use Koha::Patrons;
+
+use Koha::Acquisition::Orders;
+use Koha::Cities;
+use Koha::Holds;
+use Koha::Biblios;
 
 # Dummy app for testing the plugin
 use Mojolicious::Lite;
@@ -27,208 +31,388 @@ plugin 'Koha::REST::Plugin::Objects';
 plugin 'Koha::REST::Plugin::Query';
 plugin 'Koha::REST::Plugin::Pagination';
 
-get '/patrons' => sub {
+get '/cities' => sub {
     my $c = shift;
     $c->validation->output($c->req->params->to_hash);
-    my $patrons = $c->objects->search(Koha::Patrons->new);
-    $c->render( status => 200, json => $patrons );
+    my $cities = $c->objects->search(Koha::Cities->new);
+    $c->render( status => 200, json => $cities );
 };
 
-get '/patrons_to_model' => sub {
+get '/orders' => sub {
     my $c = shift;
+    $c->stash('koha.embed', ( { fund => {} } ) );
     $c->validation->output($c->req->params->to_hash);
-    my $patrons_set = Koha::Patrons->new;
-    my $patrons = $c->objects->search( $patrons_set, \&to_model );
-    $c->render( status => 200, json => $patrons );
+    my $orders = $c->objects->search(Koha::Acquisition::Orders->new);
+    $c->render( status => 200, json => $orders );
 };
 
-get '/patrons_to_model_to_api' => sub {
+get '/patrons/:patron_id/holds' => sub {
     my $c = shift;
-    $c->validation->output($c->req->params->to_hash);
-    my $patrons_set = Koha::Patrons->new;
-    my $patrons = $c->objects->search( $patrons_set, \&to_model, \&to_api );
-    $c->render( status => 200, json => $patrons );
+    my $params = $c->req->params->to_hash;
+    $params->{patron_id} = $c->stash("patron_id");
+    $c->validation->output($params);
+    my $holds_set = Koha::Holds->new;
+    my $holds     = $c->objects->search( $holds_set );
+    $c->render( status => 200, json => {count => scalar(@$holds)} );
 };
 
-sub to_model {
-    my $params = shift;
-
-    if ( exists $params->{nombre} ) {
-        $params->{firstname} = delete $params->{nombre};
-    }
-
-    return $params;
-}
-
-sub to_api {
-    my $params = shift;
-
-    if ( exists $params->{firstname} ) {
-        $params->{nombre} = delete $params->{firstname};
-    }
+get '/biblios' => sub {
+    my $c = shift;
+    my $output = $c->req->params->to_hash;
+    $output->{query} = $c->req->json if defined $c->req->json;
+    my $headers = $c->req->headers->to_hash;
+    $output->{'x-koha-query'} = $headers->{'x-koha-query'} if defined $headers->{'x-koha-query'};
+    $c->validation->output($output);
+    my $biblios_set = Koha::Biblios->new;
+    $c->stash("koha.embed", {
+        "suggestions" => {
+            children => {
+                "suggester" => {}
+            }
+        }
+    });
+    my $biblios = $c->objects->search($biblios_set);
+    $c->render( status => 200, json => {count => scalar(@$biblios), biblios => $biblios} );
+};
 
-    return $params;
-}
 
 # The tests
-use Test::More tests => 1;
+use Test::More tests => 10;
 use Test::Mojo;
 
+use t::lib::Mocks;
 use t::lib::TestBuilder;
 use Koha::Database;
 
-my $schema = Koha::Database->new()->schema();
-
+my $t = Test::Mojo->new;
 
+my $schema  = Koha::Database->new()->schema();
 my $builder = t::lib::TestBuilder->new;
 
 subtest 'objects.search helper' => sub {
 
-    plan tests => 90;
-
-    my $t = Test::Mojo->new;
+    plan tests => 50;
 
     $schema->storage->txn_begin;
 
-    # Delete existing patrons
-    Koha::Patrons->search->delete;
-    # Create two sample patrons that match the query
+    # Remove existing cities to have more control on the search results
+    Koha::Cities->delete;
+
+    # Create three sample cities that match the query. This makes sure we
+    # always have a "next" link regardless of Mojolicious::Plugin::OpenAPI version.
+    $builder->build_object({
+        class => 'Koha::Cities',
+        value => {
+            city_name => 'Manuel'
+        }
+    });
     $builder->build_object({
-        class => 'Koha::Patrons',
+        class => 'Koha::Cities',
         value => {
-            firstname => 'Manuel'
+            city_name => 'Manuela'
         }
     });
     $builder->build_object({
-        class => 'Koha::Patrons',
+        class => 'Koha::Cities',
         value => {
-            firstname => 'Manuela'
+            city_name => 'Manuelab'
         }
     });
 
-    $t->get_ok('/patrons?firstname=manuel&_per_page=1&_page=1')
+    $t->get_ok('/cities?name=manuel&_per_page=1&_page=1')
         ->status_is(200)
-        ->header_like( 'Link' => qr/<http:\/\/.*\?.*&_page=2.*>; rel="next",/ )
+        ->header_like( 'Link' => qr/<http:\/\/.*[\?&]_page=2.*>; rel="next",/ )
         ->json_has('/0')
         ->json_hasnt('/1')
-        ->json_is('/0/firstname' => 'Manuel');
+        ->json_is('/0/name' => 'Manuel');
 
     $builder->build_object({
-        class => 'Koha::Patrons',
+        class => 'Koha::Cities',
         value => {
-            firstname => 'Emanuel'
+            city_name => 'Emanuel'
         }
     });
 
     # _match=starts_with
-    $t->get_ok('/patrons?firstname=manuel&_per_page=3&_page=1&_match=starts_with')
+    $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=starts_with')
         ->status_is(200)
         ->json_has('/0')
         ->json_has('/1')
-        ->json_hasnt('/2')
-        ->json_is('/0/firstname' => 'Manuel')
-        ->json_is('/1/firstname' => 'Manuela');
+        ->json_has('/2')
+        ->json_hasnt('/3')
+        ->json_is('/0/name' => 'Manuel')
+        ->json_is('/1/name' => 'Manuela')
+        ->json_is('/2/name' => 'Manuelab');
 
     # _match=ends_with
-    $t->get_ok('/patrons?firstname=manuel&_per_page=3&_page=1&_match=ends_with')
+    $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=ends_with')
         ->status_is(200)
         ->json_has('/0')
         ->json_has('/1')
         ->json_hasnt('/2')
-        ->json_is('/0/firstname' => 'Manuel')
-        ->json_is('/1/firstname' => 'Emanuel');
+        ->json_is('/0/name' => 'Manuel')
+        ->json_is('/1/name' => 'Emanuel');
 
     # _match=exact
-    $t->get_ok('/patrons?firstname=manuel&_per_page=3&_page=1&_match=exact')
+    $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=exact')
         ->status_is(200)
         ->json_has('/0')
         ->json_hasnt('/1')
-        ->json_is('/0/firstname' => 'Manuel');
+        ->json_is('/0/name' => 'Manuel');
 
     # _match=contains
-    $t->get_ok('/patrons?firstname=manuel&_per_page=3&_page=1&_match=contains')
+    $t->get_ok('/cities?name=manuel&_per_page=4&_page=1&_match=contains')
         ->status_is(200)
         ->json_has('/0')
         ->json_has('/1')
         ->json_has('/2')
-        ->json_hasnt('/3')
-        ->json_is('/0/firstname' => 'Manuel')
-        ->json_is('/1/firstname' => 'Manuela')
-        ->json_is('/2/firstname' => 'Emanuel');
+        ->json_has('/3')
+        ->json_hasnt('/4')
+        ->json_is('/0/name' => 'Manuel')
+        ->json_is('/1/name' => 'Manuela')
+        ->json_is('/2/name' => 'Manuelab')
+        ->json_is('/3/name' => 'Emanuel');
+
+    # Add 20 more cities
+    for ( 1..20 ) {
+        $builder->build_object({ class => 'Koha::Cities' });
+    }
 
-    ## _to_model tests
-    # _match=starts_with
-    $t->get_ok('/patrons_to_model?nombre=manuel&_per_page=3&_page=1&_match=starts_with')
-        ->status_is(200)
-        ->json_has('/0')
-        ->json_has('/1')
-        ->json_hasnt('/2')
-        ->json_is('/0/firstname' => 'Manuel')
-        ->json_is('/1/firstname' => 'Manuela');
+    t::lib::Mocks::mock_preference('RESTdefaultPageSize', 20 );
+    $t->get_ok('/cities')
+      ->status_is(200);
 
-    # _match=ends_with
-    $t->get_ok('/patrons_to_model?nombre=manuel&_per_page=3&_page=1&_match=ends_with')
-        ->status_is(200)
-        ->json_has('/0')
-        ->json_has('/1')
-        ->json_hasnt('/2')
-        ->json_is('/0/firstname' => 'Manuel')
-        ->json_is('/1/firstname' => 'Emanuel');
+    my $response_count = scalar @{ $t->tx->res->json };
+    is( $response_count, 20, 'RESTdefaultPageSize is honoured by default (20)' );
 
-    # _match=exact
-    $t->get_ok('/patrons_to_model?nombre=manuel&_per_page=3&_page=1&_match=exact')
-        ->status_is(200)
-        ->json_has('/0')
-        ->json_hasnt('/1')
-        ->json_is('/0/firstname' => 'Manuel');
+    t::lib::Mocks::mock_preference('RESTdefaultPageSize', 5 );
+    $t->get_ok('/cities')
+      ->status_is(200);
 
-    # _match=contains
-    $t->get_ok('/patrons_to_model?nombre=manuel&_per_page=3&_page=1&_match=contains')
-        ->status_is(200)
-        ->json_has('/0')
-        ->json_has('/1')
-        ->json_has('/2')
-        ->json_hasnt('/3')
-        ->json_is('/0/firstname' => 'Manuel')
-        ->json_is('/1/firstname' => 'Manuela')
-        ->json_is('/2/firstname' => 'Emanuel');
+    $response_count = scalar @{ $t->tx->res->json };
+    is( $response_count, 5, 'RESTdefaultPageSize is honoured by default (5)' );
 
-    ## _to_model && _to_api tests
-    # _match=starts_with
-    $t->get_ok('/patrons_to_model_to_api?nombre=manuel&_per_page=3&_page=1&_match=starts_with')
-        ->status_is(200)
-        ->json_has('/0')
-        ->json_has('/1')
-        ->json_hasnt('/2')
-        ->json_is('/0/nombre' => 'Manuel')
-        ->json_is('/1/nombre' => 'Manuela');
+    $t->get_ok('/cities?_page=1&_per_page=-1')
+      ->status_is(200);
 
-    # _match=ends_with
-    $t->get_ok('/patrons_to_model_to_api?nombre=manuel&_per_page=3&_page=1&_match=ends_with')
-        ->status_is(200)
-        ->json_has('/0')
-        ->json_has('/1')
-        ->json_hasnt('/2')
-        ->json_is('/0/nombre' => 'Manuel')
-        ->json_is('/1/nombre' => 'Emanuel');
+    $response_count = scalar @{ $t->tx->res->json };
+    is( $response_count, 24, '_per_page=-1 means all resources' );
 
-    # _match=exact
-    $t->get_ok('/patrons_to_model_to_api?nombre=manuel&_per_page=3&_page=1&_match=exact')
-        ->status_is(200)
-        ->json_has('/0')
-        ->json_hasnt('/1')
-        ->json_is('/0/nombre' => 'Manuel');
+    $t->get_ok('/cities?_page=100&_per_page=-1')
+      ->status_is(200);
 
-    # _match=contains
-    $t->get_ok('/patrons_to_model_to_api?nombre=manuel&_per_page=3&_page=1&_match=contains')
-        ->status_is(200)
-        ->json_has('/0')
-        ->json_has('/1')
-        ->json_has('/2')
-        ->json_hasnt('/3')
-        ->json_is('/0/nombre' => 'Manuel')
-        ->json_is('/1/nombre' => 'Manuela')
-        ->json_is('/2/nombre' => 'Emanuel');
+    $response_count = scalar @{ $t->tx->res->json };
+    is( $response_count, 24, 'When _per_page=-1 the page param is not considered' );
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.search helper, sorting on mapped column' => sub {
+
+    plan tests => 14;
+
+    $schema->storage->txn_begin;
+
+    # Have complete control over the existing cities to ease testing
+    Koha::Cities->delete;
+
+    $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'A', city_country => 'Argentina' } });
+    $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'B', city_country => 'Argentina' } });
+
+    $t->get_ok('/cities?_order_by=%2Bname&_order_by=+country')
+      ->status_is(200)
+      ->json_has('/0')
+      ->json_has('/1')
+      ->json_hasnt('/2')
+      ->json_is('/0/name' => 'A')
+      ->json_is('/1/name' => 'B');
+
+    $t->get_ok('/cities?_order_by=-name')
+      ->status_is(200)
+      ->json_has('/0')
+      ->json_has('/1')
+      ->json_hasnt('/2')
+      ->json_is('/0/name' => 'B')
+      ->json_is('/1/name' => 'A');
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.search helper, encoding' => sub {
+
+    plan tests => 5;
+
+    $schema->storage->txn_begin;
+
+    Koha::Cities->delete;
+
+    $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'A', city_country => 'Argentina' } });
+    $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'B', city_country => '❤Argentina❤' } });
+
+    $t->get_ok('/cities?q={"country": "❤Argentina❤"}')
+      ->status_is(200)
+      ->json_has('/0')
+      ->json_hasnt('/1')
+      ->json_is('/0/name' => 'B');
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.search helper, embed' => sub {
+
+    plan tests => 2;
+
+    $schema->storage->txn_begin;
+
+    my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
+
+    $t->get_ok('/orders?order_id=' . $order->ordernumber)
+      ->json_is('/0',$order->to_api({ embed => ( { fund => {} } ) }));
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.search helper, with path parameters and _match' => sub {
+    plan tests => 8;
+
+    $schema->storage->txn_begin;
+
+    Koha::Holds->search()->delete;
+
+    my $patron = Koha::Patrons->find(10);
+    $patron->delete if $patron;
+    $patron = $builder->build_object( { class => "Koha::Patrons" } );
+    $patron->borrowernumber(10)->store;
+    $builder->build_object(
+        {
+            class => "Koha::Holds",
+            value => { borrowernumber => $patron->borrowernumber }
+        }
+    );
+
+    $t->get_ok('/patrons/1/holds?_match=exact')
+      ->json_is('/count' => 0, 'there should be no holds for borrower 1 with _match=exact');
+
+    $t->get_ok('/patrons/1/holds?_match=contains')
+      ->json_is('/count' => 0, 'there should be no holds for borrower 1 with _match=contains');
+
+    $t->get_ok('/patrons/10/holds?_match=exact')
+      ->json_is('/count' => 1, 'there should be 1 hold for borrower 10 with _match=exact');
+
+    $t->get_ok('/patrons/10/holds?_match=contains')
+      ->json_is('/count' => 1, 'there should be 1 hold for borrower 10 with _match=contains');
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'object.search helper with query parameter' => sub {
+    plan tests => 4;
+
+    $schema->storage->txn_begin;
+
+    my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
+    my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
+    my $biblio1 = $builder->build_sample_biblio;
+    my $biblio2 = $builder->build_sample_biblio;
+    my $biblio3 = $builder->build_sample_biblio;
+    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
+    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
+    my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
+
+    $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron1->borrowernumber })
+      ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
+
+    $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron2->borrowernumber })
+      ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'object.search helper with q parameter' => sub {
+    plan tests => 4;
+
+    $schema->storage->txn_begin;
+
+    my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
+    my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
+    my $biblio1 = $builder->build_sample_biblio;
+    my $biblio2 = $builder->build_sample_biblio;
+    my $biblio3 = $builder->build_sample_biblio;
+    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
+    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
+    my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
+
+    $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}')
+      ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
+
+    $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}')
+      ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'object.search helper with x-koha-query header' => sub {
+    plan tests => 4;
+
+    $schema->storage->txn_begin;
+
+    my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
+    my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
+    my $biblio1 = $builder->build_sample_biblio;
+    my $biblio2 = $builder->build_sample_biblio;
+    my $biblio3 = $builder->build_sample_biblio;
+    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
+    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
+    my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
+
+    $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}'})
+      ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
+
+    $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'})
+      ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
 
     $schema->storage->txn_rollback;
 };
+
+subtest 'object.search helper with all query methods' => sub {
+    plan tests => 6;
+
+    $schema->storage->txn_begin;
+
+    my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron1'} } );
+    my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron2'} } );
+    my $biblio1 = $builder->build_sample_biblio;
+    my $biblio2 = $builder->build_sample_biblio;
+    my $biblio3 = $builder->build_sample_biblio;
+    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
+    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
+    my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
+
+    $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron1->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron1->cardnumber})
+      ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
+
+    $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron2->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron2->cardnumber})
+      ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
+
+    $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron1->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron2->cardnumber})
+      ->json_is('/count' => 0, 'there shouldn\'t be biblios where suggester has patron1 fistname and patron2 id');
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'object.search helper order by embedded columns' => sub {
+    plan tests => 3;
+
+    my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron1'} } );
+    my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {firstname=>'patron2'} } );
+    my $biblio1 = $builder->build_sample_biblio;
+    my $biblio2 = $builder->build_sample_biblio;
+    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
+    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
+
+    $t->get_ok('/biblios?_order_by=-suggestions.suggester.firstname' => json => [{"me.biblio_id" => $biblio1->biblionumber}, {"me.biblio_id" => $biblio2->biblionumber}])
+      ->json_is('/biblios/0/biblio_id' => $biblio2->biblionumber, 'Biblio 2 should be first')
+      ->json_is('/biblios/1/biblio_id' => $biblio1->biblionumber, 'Biblio 1 should be second');
+
+    $schema->storage->txn_begin;
+}