Bug 26635: Refined data structure and methods
[koha-ffzg.git] / t / db_dependent / Koha / REST / Plugin / Objects.t
old mode 100644 (file)
new mode 100755 (executable)
index 5a2b207..21f89f0
 use Modern::Perl;
 
 use Koha::Acquisition::Orders;
+use Koha::AuthorisedValueCategories;
+use Koha::AuthorisedValues;
 use Koha::Cities;
-use Koha::Holds;
 use Koha::Biblios;
+use Koha::Patrons;
+
+use Mojo::JSON qw(encode_json);
 
 # Dummy app for testing the plugin
 use Mojolicious::Lite;
@@ -38,6 +42,13 @@ get '/cities' => sub {
     $c->render( status => 200, json => $cities );
 };
 
+get '/cities/:city_id' => sub {
+    my $c = shift;
+    my $id = $c->stash("city_id");
+    my $city = $c->objects->find(Koha::Cities->new, $id);
+    $c->render( status => 200, json => $city );
+};
+
 get '/orders' => sub {
     my $c = shift;
     $c->stash('koha.embed', ( { fund => {} } ) );
@@ -46,14 +57,12 @@ get '/orders' => sub {
     $c->render( status => 200, json => $orders );
 };
 
-get '/patrons/:patron_id/holds' => sub {
+get '/orders/:order_id' => sub {
     my $c = shift;
-    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)} );
+    $c->stash('koha.embed', ( { fund => {} } ) );
+    my $id = $c->stash("order_id");
+    my $order = $c->objects->find(Koha::Acquisition::Orders->new, $id);
+    $c->render( status => 200, json => $order );
 };
 
 get '/biblios' => sub {
@@ -75,22 +84,54 @@ get '/biblios' => sub {
     $c->render( status => 200, json => {count => scalar(@$biblios), biblios => $biblios} );
 };
 
+get '/libraries/:library_id_1/:library_id_2' => sub {
+
+    my $c = shift;
+
+    # Emulate a public route by stashing the is_public value
+    $c->stash( 'is_public' => 1 );
+
+    my $library_id_1 = $c->param('library_id_1');
+    my $library_id_2 = $c->param('library_id_2');
+
+    my $libraries_rs = Koha::Libraries->search(
+        { branchcode => [ $library_id_1, $library_id_2 ] },
+        { order_by   => 'branchname' }
+    );
+    my $libraries    = $c->objects->search( $libraries_rs );
+
+    $c->render(
+        status => 200,
+        json   => $libraries
+    );
+};
+
+get '/my_patrons' => sub {
+
+    my $c = shift;
+
+    my $patrons = $c->objects->search( scalar Koha::Patrons->search( {}, { order_by   => 'borrowernumber' }) );
+
+    $c->render(
+        status => 200,
+        json   => $patrons
+    );
+};
 
 # The tests
-use Test::More tests => 10;
+use Test::More tests => 16;
 use Test::Mojo;
 
+use t::lib::Mocks;
 use t::lib::TestBuilder;
 use Koha::Database;
 
-my $t = Test::Mojo->new;
-
 my $schema  = Koha::Database->new()->schema();
 my $builder = t::lib::TestBuilder->new;
 
 subtest 'objects.search helper' => sub {
 
-    plan tests => 38;
+    plan tests => 50;
 
     $schema->storage->txn_begin;
 
@@ -118,6 +159,7 @@ subtest 'objects.search helper' => sub {
         }
     });
 
+    my $t = Test::Mojo->new;
     $t->get_ok('/cities?name=manuel&_per_page=1&_page=1')
         ->status_is(200)
         ->header_like( 'Link' => qr/<http:\/\/.*[\?&]_page=2.*>; rel="next",/ )
@@ -172,12 +214,43 @@ subtest 'objects.search helper' => sub {
         ->json_is('/2/name' => 'Manuelab')
         ->json_is('/3/name' => 'Emanuel');
 
+    # Add 20 more cities
+    for ( 1..20 ) {
+        $builder->build_object({ class => 'Koha::Cities' });
+    }
+
+    t::lib::Mocks::mock_preference('RESTdefaultPageSize', 20 );
+    $t->get_ok('/cities')
+      ->status_is(200);
+
+    my $response_count = scalar @{ $t->tx->res->json };
+    is( $response_count, 20, 'RESTdefaultPageSize is honoured by default (20)' );
+
+    t::lib::Mocks::mock_preference('RESTdefaultPageSize', 5 );
+    $t->get_ok('/cities')
+      ->status_is(200);
+
+    $response_count = scalar @{ $t->tx->res->json };
+    is( $response_count, 5, 'RESTdefaultPageSize is honoured by default (5)' );
+
+    $t->get_ok('/cities?_page=1&_per_page=-1')
+      ->status_is(200);
+
+    $response_count = scalar @{ $t->tx->res->json };
+    is( $response_count, 24, '_per_page=-1 means all resources' );
+
+    $t->get_ok('/cities?_page=100&_per_page=-1')
+      ->status_is(200);
+
+    $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;
+    plan tests => 42;
 
     $schema->storage->txn_begin;
 
@@ -186,22 +259,59 @@ subtest 'objects.search helper, sorting on mapped column' => sub {
 
     $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' } });
+    $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'C', city_country => 'Argentina' } });
+    $builder->build_object({ class => 'Koha::Cities', value => { city_name => 'C', city_country => 'Belarus' } });
 
-    $t->get_ok('/cities?_order_by=%2Bname&_order_by=+country')
+    my $t = Test::Mojo->new;
+    # CSV-param
+    $t->get_ok('/cities?_order_by=%2Bname,-country')
       ->status_is(200)
       ->json_has('/0')
       ->json_has('/1')
-      ->json_hasnt('/2')
       ->json_is('/0/name' => 'A')
-      ->json_is('/1/name' => 'B');
-
+      ->json_is('/1/name' => 'B')
+      ->json_is('/2/name' => 'C')
+      ->json_is('/2/country' => 'Belarus')
+      ->json_is('/3/name' => 'C')
+      ->json_is('/3/country' => 'Argentina')
+      ->json_hasnt('/4');
+
+    # Multi-param: traditional
+    $t->get_ok('/cities?_order_by=%2Bname&_order_by=-country')
+      ->status_is(200)
+      ->json_has('/0')
+      ->json_has('/1')
+      ->json_is('/0/name' => 'A')
+      ->json_is('/1/name' => 'B')
+      ->json_is('/2/name' => 'C')
+      ->json_is('/2/country' => 'Belarus')
+      ->json_is('/3/name' => 'C')
+      ->json_is('/3/country' => 'Argentina')
+      ->json_hasnt('/4');
+
+    # Multi-param: PHP Style, Passes validation as above, subsequntly explodes
+    $t->get_ok('/cities?_order_by[]=%2Bname&_order_by[]=-country')
+      ->status_is(200)
+      ->json_has('/0')
+      ->json_has('/1')
+      ->json_is('/0/name' => 'A')
+      ->json_is('/1/name' => 'B')
+      ->json_is('/2/name' => 'C')
+      ->json_is('/2/country' => 'Belarus')
+      ->json_is('/3/name' => 'C')
+      ->json_is('/3/country' => 'Argentina')
+      ->json_hasnt('/4');
+
+    # Single-param
     $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');
+      ->json_is('/0/name' => 'C')
+      ->json_is('/1/name' => 'C')
+      ->json_is('/2/name' => 'B')
+      ->json_is('/3/name' => 'A')
+      ->json_hasnt('/4');
 
     $schema->storage->txn_rollback;
 };
@@ -217,6 +327,7 @@ subtest 'objects.search helper, encoding' => sub {
     $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❤' } });
 
+    my $t = Test::Mojo->new;
     $t->get_ok('/cities?q={"country": "❤Argentina❤"}')
       ->status_is(200)
       ->json_has('/0')
@@ -226,54 +337,57 @@ subtest 'objects.search helper, encoding' => sub {
     $schema->storage->txn_rollback;
 };
 
-subtest 'objects.search helper, embed' => sub {
+subtest 'objects.search helper, X-Total-Count and X-Base-Total-Count' => sub {
 
-    plan tests => 2;
+    plan tests => 8;
 
     $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 => {} } ) }));
+    Koha::Cities->delete;
 
-    $schema->storage->txn_rollback;
-};
+    my $long_city_name = 'Llanfairpwllgwyngyll';
+    for my $i ( 1 .. length($long_city_name) ) {
+        $builder->build_object(
+            {
+                class => 'Koha::Cities',
+                value => {
+                    city_name    => substr( $long_city_name, 0, $i ),
+                    city_country => 'Wales'
+                }
+            }
+        );
+    }
 
-subtest 'objects.search helper, with path parameters and _match' => sub {
-    plan tests => 8;
+    my $t = Test::Mojo->new;
+    $t->get_ok('/cities?name=L&_per_page=10&_page=1&_match=starts_with')
+      ->status_is(200)
+      ->header_is( 'X-Total-Count' => 20, 'X-Total-Count header present' )
+      ->header_is( 'X-Base-Total-Count' => 20, 'X-Base-Total-Count header present' );
 
-    $schema->storage->txn_begin;
+    $t->get_ok('/cities?name=Llan&_per_page=10&_page=1&_match=starts_with')
+      ->status_is(200)
+      ->header_is( 'X-Total-Count' => 17, 'X-Total-Count header present' )
+      ->header_is('X-Base-Total-Count' => 20, 'X-Base-Total-Count header present' );
 
-    Koha::Holds->search()->delete;
+    $schema->storage->txn_rollback;
+};
 
-    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 }
-        }
-    );
+subtest 'objects.search helper, embed' => sub {
 
-    $t->get_ok('/patrons/1/holds?_match=exact')
-      ->json_is('/count' => 0, 'there should be no holds for borrower 1 with _match=exact');
+    plan tests => 2;
 
-    $t->get_ok('/patrons/1/holds?_match=contains')
-      ->json_is('/count' => 0, 'there should be no holds for borrower 1 with _match=contains');
+    $schema->storage->txn_begin;
 
-    $t->get_ok('/patrons/10/holds?_match=exact')
-      ->json_is('/count' => 1, 'there should be 1 hold for borrower 10 with _match=exact');
+    my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
 
-    $t->get_ok('/patrons/10/holds?_match=contains')
-      ->json_is('/count' => 1, 'there should be 1 hold for borrower 10 with _match=contains');
+    my $t = Test::Mojo->new;
+    $t->get_ok('/orders?order_id=' . $order->ordernumber)
+      ->json_is('/0',$order->to_api({ embed => ( { fund => {} } ) }));
 
     $schema->storage->txn_rollback;
 };
 
-subtest 'object.search helper with query parameter' => sub {
+subtest 'objects.search helper with query parameter' => sub {
     plan tests => 4;
 
     $schema->storage->txn_begin;
@@ -287,6 +401,7 @@ subtest 'object.search helper with query parameter' => sub {
     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} } );
 
+    my $t = Test::Mojo->new;
     $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');
 
@@ -296,7 +411,7 @@ subtest 'object.search helper with query parameter' => sub {
     $schema->storage->txn_rollback;
 };
 
-subtest 'object.search helper with q parameter' => sub {
+subtest 'objects.search helper with q parameter' => sub {
     plan tests => 4;
 
     $schema->storage->txn_begin;
@@ -310,6 +425,7 @@ subtest 'object.search helper with q parameter' => sub {
     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} } );
 
+    my $t = Test::Mojo->new;
     $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');
 
@@ -319,7 +435,7 @@ subtest 'object.search helper with q parameter' => sub {
     $schema->storage->txn_rollback;
 };
 
-subtest 'object.search helper with x-koha-query header' => sub {
+subtest 'objects.search helper with x-koha-query header' => sub {
     plan tests => 4;
 
     $schema->storage->txn_begin;
@@ -333,6 +449,7 @@ subtest 'object.search helper with x-koha-query header' => sub {
     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} } );
 
+    my $t = Test::Mojo->new;
     $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');
 
@@ -342,7 +459,7 @@ subtest 'object.search helper with x-koha-query header' => sub {
     $schema->storage->txn_rollback;
 };
 
-subtest 'object.search helper with all query methods' => sub {
+subtest 'objects.search helper with all query methods' => sub {
     plan tests => 6;
 
     $schema->storage->txn_begin;
@@ -356,6 +473,7 @@ subtest 'object.search helper with all query methods' => sub {
     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} } );
 
+    my $t = Test::Mojo->new;
     $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');
 
@@ -368,9 +486,11 @@ subtest 'object.search helper with all query methods' => sub {
     $schema->storage->txn_rollback;
 };
 
-subtest 'object.search helper order by embedded columns' => sub {
+subtest 'objects.search helper order by embedded columns' => sub {
     plan tests => 3;
 
+    $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;
@@ -378,9 +498,354 @@ subtest 'object.search helper order by embedded columns' => sub {
     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 $t = Test::Mojo->new;
     $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_rollback;
+};
+
+subtest 'objects.find helper' => sub {
+
+    plan tests => 9;
+
+    my $t = Test::Mojo->new;
+
+    $schema->storage->txn_begin;
+
+    my $city_1 = $builder->build_object( { class => 'Koha::Cities' } );
+    my $city_2 = $builder->build_object( { class => 'Koha::Cities' } );
+
+    $t->get_ok( '/cities/' . $city_1->id )
+      ->status_is(200)
+      ->json_is( $city_1->to_api );
+
+    $t->get_ok( '/cities/' . $city_2->id )
+      ->status_is(200)
+      ->json_is( $city_2->to_api );
+
+    # Remove the city
+    my $city_2_id = $city_2->id;
+    $city_2->delete;
+    $t->get_ok( '/cities/' . $city_2_id )
+      ->status_is(200)
+      ->json_is( undef );
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.find helper, embed' => sub {
+
+    plan tests => 2;
+
+    my $t = Test::Mojo->new;
+
+    $schema->storage->txn_begin;
+
+    my $order = $builder->build_object({ class => 'Koha::Acquisition::Orders' });
+
+    $t->get_ok( '/orders/' . $order->ordernumber )
+      ->json_is( $order->to_api( { embed => ( { fund => {} } ) } ) );
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.search helper, public requests' => sub {
+
+    plan tests => 3;
+
+    $schema->storage->txn_begin;
+
+    my $library_1 = $builder->build_object({ class => 'Koha::Libraries', value => { branchname => 'A' } });
+    my $library_2 = $builder->build_object({ class => 'Koha::Libraries', value => { branchname => 'B' } });
+
+    my $t = Test::Mojo->new;
+
+    $t->get_ok( '/libraries/'.$library_1->id.'/'.$library_2->id )
+      ->json_is('/0' => $library_1->to_api({ public => 1 }), 'Public representation of $library_1 is retrieved')
+      ->json_is('/1' => $library_2->to_api({ public => 1 }), 'Public representation of $library_2 is retrieved');
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.search helper, search_limited() tests' => sub {
+
+    plan tests => 9;
+
+    $schema->storage->txn_begin;
+
+    my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
+    my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
+
+    my $patron_1 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $library_1->id } });
+    my $patron_2 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $library_1->id } });
+    my $patron_3 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $library_2->id } });
+
+    my @libraries_where_can_see_patrons = ( $library_1->id, $library_2->id );
+
+    my $t = Test::Mojo->new;
+
+    my $mocked_patron = Test::MockModule->new('Koha::Patron');
+    $mocked_patron->mock( 'libraries_where_can_see_patrons', sub
+        {
+            return @libraries_where_can_see_patrons;
+        }
+    );
+
+    my $patron = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { flags => 2**4 }    # borrowers flag = 4
+        }
+    );
+
+    t::lib::Mocks::mock_userenv({ patron => $patron });
+
+    $t->get_ok( "/my_patrons?q=" . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) )
+      ->status_is(200)
+      ->json_is( '/0/patron_id' => $patron_1->id )
+      ->json_is( '/1/patron_id' => $patron_2->id )
+      ->json_is( '/2/patron_id' => $patron_3->id );
+
+    @libraries_where_can_see_patrons = ( $library_2->id );
+
+    my $res = $t->get_ok( "/my_patrons?q=" . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) )
+      ->status_is(200)
+      ->json_is( '/0/patron_id' => $patron_3->id, 'Returns the only allowed patron' )
+      ->tx->res->json;
+
+    is( scalar @{$res}, 1, 'Only one patron returned' );
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.find helper with expanded authorised values' => sub {
+
+    plan tests => 18;
+
+    $schema->storage->txn_begin;
+
+    my $t = Test::Mojo->new;
+
+    Koha::AuthorisedValues->search( { category => 'Countries' } )->delete;
+    Koha::AuthorisedValueCategories->search( { category_name => 'Countries' } )
+      ->delete;
+
+    my $cat = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValueCategories',
+            value => { category_name => 'Countries' }
+        }
+    );
+    my $fr = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValues',
+            value => {
+                authorised_value => 'FR',
+                lib              => 'France',
+                category         => $cat->category_name
+            }
+        }
+    );
+    my $us = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValues',
+            value => {
+                authorised_value => 'US',
+                lib              => 'United States of America',
+                category         => $cat->category_name
+            }
+        }
+    );
+    my $ar = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValues',
+            value => {
+                authorised_value => 'AR',
+                lib              => 'Argentina',
+                category         => $cat->category_name
+            }
+        }
+    );
+
+    my $city_class = Test::MockModule->new('Koha::City');
+    $city_class->mock(
+        'api_av_mapping',
+        sub {
+            my ($self, $params) = @_;
+            use Koha::AuthorisedValues;
+
+            my $av = Koha::AuthorisedValues->find(
+                {
+                    authorised_value => $self->city_country,
+                    category         => 'Countries'
+                }
+            );
+
+            return {
+                city_country => {
+                    category => $av->category,
+                    str      => ( $params->{public} ) ? $av->lib_opac : $av->lib,
+                    type     => 'av',
+                }
+            };
+        }
+    );
+
+    my $manuel = $builder->build_object(
+        {
+            class => 'Koha::Cities',
+            value => {
+                city_name    => 'Manuel',
+                city_country => 'AR'
+            }
+        }
+    );
+    my $manuela = $builder->build_object(
+        {
+            class => 'Koha::Cities',
+            value => {
+                city_name    => 'Manuela',
+                city_country => 'US'
+            }
+        }
+    );
+
+    $t->get_ok( '/cities/' . $manuel->id => { 'x-koha-av-expand' => 1 } )
+      ->status_is(200)->json_is( '/name' => 'Manuel' )
+      ->json_has('/_str')
+      ->json_is( '/_str/country/type'     => 'av' )
+      ->json_is( '/_str/country/category' => $cat->category_name )
+      ->json_is( '/_str/country/str'      => $ar->lib );
+
+    $t->get_ok( '/cities/' . $manuel->id => { 'x-koha-av-expand' => 0 } )
+      ->status_is(200)->json_is( '/name' => 'Manuel' )
+      ->json_hasnt('/_str');
+
+    $t->get_ok( '/cities/' . $manuela->id => { 'x-koha-av-expand' => 1 } )
+      ->status_is(200)->json_is( '/name' => 'Manuela' )
+      ->json_has('/_str')
+      ->json_is( '/_str/country/type'     => 'av' )
+      ->json_is( '/_str/country/category' => $cat->category_name )
+      ->json_is( '/_str/country/str'      => $us->lib );
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'objects.search helper with expanded authorised values' => sub {
+
+    plan tests => 24;
+
+    my $t = Test::Mojo->new;
+
     $schema->storage->txn_begin;
-}
+
+    Koha::AuthorisedValues->search( { category => 'Countries' } )->delete;
+    Koha::AuthorisedValueCategories->search( { category_name => 'Countries' } )
+      ->delete;
+
+    my $cat = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValueCategories',
+            value => { category_name => 'Countries' }
+        }
+    );
+    my $fr = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValues',
+            value => {
+                authorised_value => 'FR',
+                lib              => 'France',
+                category         => $cat->category_name
+            }
+        }
+    );
+    my $us = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValues',
+            value => {
+                authorised_value => 'US',
+                lib              => 'United States of America',
+                category         => $cat->category_name
+            }
+        }
+    );
+    my $ar = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValues',
+            value => {
+                authorised_value => 'AR',
+                lib              => 'Argentina',
+                category         => $cat->category_name
+            }
+        }
+    );
+
+    my $city_class = Test::MockModule->new('Koha::City');
+    $city_class->mock(
+        'api_av_mapping',
+        sub {
+            my ($self, $params) = @_;
+            use Koha::AuthorisedValues;
+
+            my $av = Koha::AuthorisedValues->find(
+                {
+                    authorised_value => $self->city_country,
+                    category         => 'Countries'
+                }
+            );
+
+            return {
+                city_country => {
+                    category => $av->category,
+                    str      => ( $params->{public} ) ? $av->lib_opac : $av->lib,
+                    type     => 'av',
+                }
+            };
+        }
+    );
+
+
+    $builder->build_object(
+        {
+            class => 'Koha::Cities',
+            value => {
+                city_name    => 'Manuel',
+                city_country => 'AR'
+            }
+        }
+    );
+    $builder->build_object(
+        {
+            class => 'Koha::Cities',
+            value => {
+                city_name    => 'Manuela',
+                city_country => 'US'
+            }
+        }
+    );
+
+    $t->get_ok( '/cities?name=manuel&_per_page=4&_page=1&_match=starts_with' =>
+          { 'x-koha-av-expand' => 1 } )->status_is(200)
+      ->json_has('/0')->json_has('/1')->json_hasnt('/2')
+      ->json_is( '/0/name' => 'Manuel' )
+      ->json_has('/0/_str')
+      ->json_is( '/0/_str/country/str'      => $ar->lib )
+      ->json_is( '/0/_str/country/type'     => 'av' )
+      ->json_is( '/0/_str/country/category' => $cat->category_name )
+      ->json_is( '/1/name' => 'Manuela' )
+      ->json_has('/1/_str')
+      ->json_is( '/1/_str/country/str' => $us->lib )
+      ->json_is( '/1/_str/country/type'     => 'av' )
+      ->json_is( '/1/_str/country/category' => $cat->category_name );
+
+    $t->get_ok( '/cities?name=manuel&_per_page=4&_page=1&_match=starts_with' =>
+          { 'x-koha-av-expand' => 0 } )->status_is(200)
+      ->json_has('/0')->json_has('/1')->json_hasnt('/2')
+      ->json_is( '/0/name' => 'Manuel' )->json_hasnt('/0/_str')
+      ->json_is( '/1/name' => 'Manuela' )->json_hasnt('/1/_str');
+
+
+    $schema->storage->txn_rollback;
+};