From adf252d96cff7b0125839da63d87ad05e2a52c9b Mon Sep 17 00:00:00 2001 From: Nick Clemens Date: Thu, 16 Sep 2021 15:54:19 +0000 Subject: [PATCH] Bug 17170: Add API route for SearchFilters This adds the API routes and tests Sponsored-by: Sponsored by: Round Rock Public Library [https://www.roundrocktexas.gov/departments/library/] Signed-off-by: Martin Renvoize Signed-off-by: Tomas Cohen Arazi --- Koha/REST/V1/SearchFilter.pm | 153 ++++++++ api/v1/swagger/definitions.yaml | 65 ++++ api/v1/swagger/definitions/search_filter.yaml | 33 ++ api/v1/swagger/paths/search_filters.yaml | 226 +++++++++++ api/v1/swagger/swagger.yaml | 12 + t/db_dependent/api/v1/search_filters.t | 353 ++++++++++++++++++ t/lib/TestBuilder.pm | 4 + 7 files changed, 846 insertions(+) create mode 100644 Koha/REST/V1/SearchFilter.pm create mode 100644 api/v1/swagger/definitions.yaml create mode 100644 api/v1/swagger/definitions/search_filter.yaml create mode 100644 api/v1/swagger/paths/search_filters.yaml create mode 100755 t/db_dependent/api/v1/search_filters.t diff --git a/Koha/REST/V1/SearchFilter.pm b/Koha/REST/V1/SearchFilter.pm new file mode 100644 index 0000000000..05c72e7e9b --- /dev/null +++ b/Koha/REST/V1/SearchFilter.pm @@ -0,0 +1,153 @@ +package Koha::REST::V1::SearchFilter; + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; +use Koha::SearchFilters; + +use Try::Tiny qw( catch try ); + +=head1 Name + +Koha::REST::V1::SearchFilters + +=head1 API + +=head2 Methods + +=head3 list + +Controller function that handles listing Koha::SearchFilter objects + +=cut + +sub list { + my $c = shift->openapi->valid_input or return; + return try { + my $filters_set = Koha::SearchFilters->search({}); + my $filters = $c->objects->search( $filters_set ); + return $c->render( + status => 200, + openapi => $filters + ); + } + catch { + $c->unhandled_exception($_); + }; + +} + +=head3 get + +Controller function that handles retrieving a single Koha::AdvancedEditorMacro + +=cut + +sub get { + my $c = shift->openapi->valid_input or return; + my $filter = Koha::SearchFilters->find({ + id => $c->validation->param('search_filter_id'), + }); + unless ($filter) { + return $c->render( status => 404, + openapi => { error => "Search filter not found" } ); + } + + return $c->render( status => 200, openapi => $filter->to_api ); +} + +=head3 add + +Controller function that handles adding a new Koha::SearchFilter object + +=cut + +sub add { + my $c = shift->openapi->valid_input or return; + + return try { + my $filter = Koha::SearchFilter->new_from_api( $c->validation->param('body') ); + $filter->store->discard_changes; + $c->res->headers->location( $c->req->url->to_string . '/' . $filter->id ); + return $c->render( + status => 201, + openapi => $filter->to_api + ); + } + catch { + if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) { + return $c->render( + status => 409, + openapi => { error => $_->error, conflict => $_->duplicate_id } + ); + } + $c->unhandled_exception($_); + }; +} + +=head3 update + +Controller function that handles updating a Koha::SearchFilter object + +=cut + +sub update { + my $c = shift->openapi->valid_input or return; + + my $filter = Koha::SearchFilters->find( $c->validation->param('search_filter_id') ); + + if ( not defined $filter ) { + return $c->render( status => 404, + openapi => { error => "Object not found" } ); + } + + return try { + my $params = $c->req->json; + $filter->set_from_api( $params ); + $filter->store->discard_changes; + return $c->render( status => 200, openapi => $filter->to_api ); + } + catch { + $c->unhandled_exception($_); + }; +} + +=head3 delete + +Controller function that handles deleting a Koha::SearchFilter object + +=cut + +sub delete { + my $c = shift->openapi->valid_input or return; + + my $filter = Koha::SearchFilters->find( $c->validation->param('search_filter_id') ); + if ( not defined $filter ) { + return $c->render( status => 404, + openapi => { error => "Object not found" } ); + } + + return try { + $filter->delete; + return $c->render( status => 204, openapi => q{} ); + } + catch { + $c->unhandled_exception($_); + }; +} + +1; diff --git a/api/v1/swagger/definitions.yaml b/api/v1/swagger/definitions.yaml new file mode 100644 index 0000000000..015aefd59e --- /dev/null +++ b/api/v1/swagger/definitions.yaml @@ -0,0 +1,65 @@ +--- +account_line: + $ref: definitions/account_line.yaml +advancededitormacro: + $ref: definitions/advancededitormacro.yaml +allows_renewal: + $ref: definitions/allows_renewal.yaml +basket: + $ref: definitions/basket.yaml +cashup: + $ref: definitions/cashup.yaml +checkout: + $ref: definitions/checkout.yaml +checkouts: + $ref: definitions/checkouts.yaml +circ-rule-kind: + $ref: definitions/circ-rule-kind.yaml +city: + $ref: definitions/city.yaml +error: + $ref: definitions/error.yaml +fund: + $ref: definitions/fund.yaml +hold: + $ref: definitions/hold.yaml +holds: + $ref: definitions/holds.yaml +ill_backend: + $ref: definitions/ill_backend.yaml +ill_backends: + $ref: definitions/ill_backends.yaml +import_batch_profile: + $ref: definitions/import_batch_profile.yaml +import_batch_profiles: + $ref: definitions/import_batch_profiles.yaml +invoice: + $ref: definitions/invoice.yaml +item: + $ref: definitions/item.yaml +library: + $ref: definitions/library.yaml +order: + $ref: definitions/order.yaml +patron: + $ref: definitions/patron.yaml +patron_account_credit: + $ref: definitions/patron_account_credit.yaml +patron_balance: + $ref: definitions/patron_balance.yaml +patron_extended_attribute: + $ref: definitions/patron_extended_attribute.yaml +quote: + $ref: definitions/quote.yaml +return_claim: + $ref: definitions/return_claim.yaml +smtp_server: + $ref: definitions/smtp_server.yaml +suggestion: + $ref: definitions/suggestion.yaml +search_filter: + $ref: definitions/search_filter.yaml +transfer_limit: + $ref: definitions/transfer_limit.yaml +vendor: + $ref: definitions/vendor.yaml diff --git a/api/v1/swagger/definitions/search_filter.yaml b/api/v1/swagger/definitions/search_filter.yaml new file mode 100644 index 0000000000..a60e3f0ad4 --- /dev/null +++ b/api/v1/swagger/definitions/search_filter.yaml @@ -0,0 +1,33 @@ +--- +type: object +properties: + search_filter_id: + type: integer + description: internally assigned search filter identifier + readOnly: true + name: + description: filter name + type: string + filter_query: + description: filter query part + type: + - string + - 'null' + filter_limits: + description: filter limits part + type: + - string + - 'null' + opac: + description: visible on opac + type: + - boolean + - 'null' + staff_client: + description: visible in staff client + type: + - boolean + - 'null' +additionalProperties: false +required: +- name diff --git a/api/v1/swagger/paths/search_filters.yaml b/api/v1/swagger/paths/search_filters.yaml new file mode 100644 index 0000000000..5aa61e7350 --- /dev/null +++ b/api/v1/swagger/paths/search_filters.yaml @@ -0,0 +1,226 @@ +--- +"/search_filters": + get: + x-mojo-to: SearchFilter#list + operationId: listFilters + tags: + - search_filters + summary: List search filters + produces: + - application/json + parameters: + - name: name + in: query + description: Case insensitive search on filter name + required: false + type: string + - name: filter_query + in: query + description: Search on filter query part + required: false + type: string + - name: filter_limits + in: query + description: Search on filter limits + required: false + type: string + - name: opac + in: query + description: Display in OPAC + required: false + type: boolean + - name: staff_client + in: query + description: Display on staff client + required: false + type: boolean + - $ref: "../swagger.yaml#/parameters/match" + - $ref: "../swagger.yaml#/parameters/order_by" + - $ref: "../swagger.yaml#/parameters/page" + - $ref: "../swagger.yaml#/parameters/per_page" + - $ref: "../swagger.yaml#/parameters/q_param" + - $ref: "../swagger.yaml#/parameters/q_body" + - $ref: "../swagger.yaml#/parameters/q_header" + - $ref: "../swagger.yaml#/parameters/request_id_header" + responses: + '200': + description: A list of search filters + schema: + type: array + items: + $ref: "../swagger.yaml#/definitions/search_filter" + '403': + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + '500': + description: Internal error + schema: + $ref: "../swagger.yaml#/definitions/error" + '503': + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + parameters: manage_search_filters + post: + x-mojo-to: SearchFilter#add + operationId: addSearchFilter + tags: + - search_filters + summary: Add search filter + parameters: + - name: body + in: body + description: A JSON object containing informations about the new search filter + required: true + schema: + $ref: "../swagger.yaml#/definitions/search_filter" + produces: + - application/json + responses: + '201': + description: Search filter added + schema: + $ref: "../swagger.yaml#/definitions/search_filter" + '401': + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + '403': + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + "409": + description: Conflict in creating the resource + schema: + $ref: ../swagger.yaml#/definitions/error + '500': + description: Internal error + schema: + $ref: "../swagger.yaml#/definitions/error" + '503': + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + parameters: manage_search_filters +"/search_filters/{search_filter_id}": + get: + x-mojo-to: SearchFilter#get + operationId: getSearchFilter + tags: + - search_filters + summary: Get search filter + parameters: + - $ref: "../swagger.yaml#/parameters/search_filter_id_pp" + produces: + - application/json + responses: + '200': + description: A search filter + schema: + $ref: "../swagger.yaml#/definitions/search_filter" + '403': + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + '404': + description: SearchFilter not found + schema: + $ref: "../swagger.yaml#/definitions/error" + '500': + description: Internal error + schema: + $ref: "../swagger.yaml#/definitions/error" + '503': + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + parameters: manage_search_filters + put: + x-mojo-to: SearchFilter#update + operationId: updateSearchFilter + tags: + - search_filters + summary: Update search filter + parameters: + - $ref: "../swagger.yaml#/parameters/search_filter_id_pp" + - name: body + in: body + description: A search filter object + required: true + schema: + $ref: "../swagger.yaml#/definitions/search_filter" + produces: + - application/json + responses: + '200': + description: An search_filter + schema: + $ref: "../swagger.yaml#/definitions/search_filter" + '401': + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + '403': + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + '404': + description: Search filter not found + schema: + $ref: "../swagger.yaml#/definitions/error" + '500': + description: Internal error + schema: + $ref: "../swagger.yaml#/definitions/error" + '503': + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + parameters: manage_search_filters + delete: + x-mojo-to: SearchFilter#delete + operationId: deleteSearchFilter + tags: + - macros + summary: Delete search filter + parameters: + - $ref: "../swagger.yaml#/parameters/search_filter_id_pp" + produces: + - application/json + responses: + '204': + description: Searc filter deleted + schema: + type: string + '401': + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + '403': + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + '404': + description: Search filter not found + schema: + $ref: "../swagger.yaml#/definitions/error" + '500': + description: Internal error + schema: + $ref: "../swagger.yaml#/definitions/error" + '503': + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + parameters: manage_search_filters diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index 5bdbab2fa8..50a35a1ef5 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -68,6 +68,8 @@ definitions: $ref: ./definitions/renewals.yaml return_claim: $ref: ./definitions/return_claim.yaml + search_filter: + $ref: ./definitions/search_filter.yaml smtp_server: $ref: ./definitions/smtp_server.yaml suggestion: @@ -97,6 +99,10 @@ paths: $ref: ./paths/advancededitormacros.yaml#/~1advanced_editor~1macros /advanced_editor/macros/shared: $ref: ./paths/advancededitormacros.yaml#/~1advanced_editor~1macros~1shared + /search_filters: + $ref: ./paths/search_filters.yaml#/~1search_filters + "/search_filters/{search_filter_id}": + $ref: "./paths/search_filters.yaml#/~1search_filters~1{search_filter_id}" "/advanced_editor/macros/shared/{advancededitormacro_id}": $ref: "./paths/advancededitormacros.yaml#/~1advanced_editor~1macros~1shared~1{advancededitormacro_id}" "/advanced_editor/macros/{advancededitormacro_id}": @@ -427,6 +433,12 @@ parameters: name: x-koha-request-id required: false type: integer + search_filter_id_pp: + name: search_filter_id + in: path + description: Search filter internal identifier + required: true + type: integer seen_pp: description: Item was seen flag in: query diff --git a/t/db_dependent/api/v1/search_filters.t b/t/db_dependent/api/v1/search_filters.t new file mode 100755 index 0000000000..c0b1bb705a --- /dev/null +++ b/t/db_dependent/api/v1/search_filters.t @@ -0,0 +1,353 @@ +#!/usr/bin/env perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::More tests => 5; +use Test::Mojo; +use Test::Warn; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::SearchFilters; +use Koha::Database; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); + +my $t = Test::Mojo->new('Koha::REST::V1'); + +$schema->storage->txn_begin; + +subtest 'list() tests' => sub { + plan tests => 10; + + Koha::SearchFilters->search()->delete(); + + my $patron_1 = $builder->build_object({ + class => 'Koha::Patrons', + value => { flags => 3 } + }); + my $password = 'thePassword123'; + $patron_1->set_password({ password => $password, skip_validation => 1 }); + my $userid = $patron_1->userid; + + # Create test context + my $search_filter_1 = $builder->build_object({ class => 'Koha::SearchFilters', value => + { + name => 'Test1', + query => 'kw:this', + limits => 'mc-itype,phr:BK', + opac => 1, + staff_client => 1 + } + }); + my $search_filter_2 = $builder->build_object({ class => 'Koha::SearchFilters', value => + { + name => 'Test2', + query => 'kw:that', + limits => 'mc-itype,phr:BK', + opac => 0, + staff_client => 1 + } + }); + my $search_filter_3 = $builder->build_object({ class => 'Koha::SearchFilters', value => + { + name => 'Test3', + query => 'kw:any', + limits => 'mc-itype,phr:CD', + opac => 0, + staff_client => 0 + } + }); + my $search_filter_4 = $builder->build_object({ class => 'Koha::SearchFilters', value => + { + name => 'Test4', + query => 'kw:some', + limits => 'mc-itype,phr:CD', + opac => 1, + staff_client => 0 + } + }); + + # Make sure we are returned with the correct amount of macros + $t->get_ok( "//$userid:$password@/api/v1/search_filters" ) + ->status_is( 200, 'SWAGGER3.2.2' ) + ->json_has('/0/search_filter_id') + ->json_has('/1/search_filter_id') + ->json_has('/2/search_filter_id') + ->json_has('/3/search_filter_id'); + + subtest 'query parameters' => sub { + + plan tests => 12; + $t->get_ok("//$userid:$password@/api/v1/search_filters?name=" . $search_filter_2->name) + ->status_is(200) + ->json_is( [ $search_filter_2->to_api ] ); + $t->get_ok("//$userid:$password@/api/v1/search_filters?name=NotAName") + ->status_is(200) + ->json_is( [ ] ); + $t->get_ok("//$userid:$password@/api/v1/search_filters?filter_query=kw:any") + ->status_is(200) + ->json_is( [ $search_filter_3->to_api ] ); + $t->get_ok("//$userid:$password@/api/v1/search_filters?filter_limits=mc-itype,phr:BK") + ->status_is(200) + ->json_is( [ $search_filter_1->to_api, $search_filter_2->to_api ] ); + }; + + # Warn on unsupported query parameter + $t->get_ok( "//$userid:$password@/api/v1/search_filters?filter_blah=blah" ) + ->status_is(400) + ->json_is( [{ path => '/query/filter_blah', message => 'Malformed query string'}] ); + +}; + +subtest 'get() tests' => sub { + + plan tests => 9; + + my $patron = $builder->build_object({ + class => 'Koha::Patrons', + value => { flags => 3 } + }); + my $password = 'thePassword123'; + $patron->set_password({ password => $password, skip_validation => 1 }); + my $userid = $patron->userid; + + my $search_filter_1 = $builder->build_object( { class => 'Koha::SearchFilters' } ); + my $search_filter_2 = $builder->build_object( { class => 'Koha::SearchFilters' } ); + my $search_filter_3 = $builder->build_object( { class => 'Koha::SearchFilters' } ); + + $t->get_ok( "//$userid:$password@/api/v1/search_filters/" . $search_filter_1->id ) + ->status_is( 200, 'Filter retrieved correctly' ) + ->json_is( $search_filter_1->to_api ); + + my $non_existent_code = $search_filter_1->id; + $search_filter_1->delete; + + $t->get_ok( "//$userid:$password@/api/v1/search_filters/" . $non_existent_code ) + ->status_is(404) + ->json_is( '/error' => 'Search filter not found' ); + + $patron->flags(4)->store; + $t->get_ok( "//$userid:$password/api/v1/search_filters/" . $search_filter_2->id ) + ->status_is( 401, 'Cannot search filters without permission' ) + ->json_is( '/error' => 'Authentication failure.' ); + +}; + +subtest 'add() tests' => sub { + + plan tests => 17; + + my $authorized_patron = $builder->build_object({ + class => 'Koha::Patrons', + value => { flags => 0 } + }); + $builder->build({ + source => 'UserPermission', + value => { + borrowernumber => $authorized_patron->borrowernumber, + module_bit => 3, + code => 'manage_search_filters', + }, + }); + + my $password = 'thePassword123'; + $authorized_patron->set_password({ password => $password, skip_validation => 1 }); + my $auth_userid = $authorized_patron->userid; + + my $unauthorized_patron = $builder->build_object({ + class => 'Koha::Patrons', + value => { flags => 0 } + }); + $unauthorized_patron->set_password({ password => $password, skip_validation => 1 }); + my $unauth_userid = $unauthorized_patron->userid; + + my $search_filter = $builder->build_object({ class => 'Koha::SearchFilters' }); + my $search_filter_values = $search_filter->to_api; + delete $search_filter_values->{search_filter_id}; + $search_filter->delete; + + # Unauthorized attempt to write + $t->post_ok( "//$unauth_userid:$password@/api/v1/search_filters" => json => $search_filter_values ) + ->status_is(403); + + # Authorized attempt to write invalid data + my $search_filter_with_invalid_field = { %$search_filter_values }; + $search_filter_with_invalid_field->{'coffee_filter'} = 'Chemex'; + + $t->post_ok( "//$auth_userid:$password@/api/v1/search_filters" => json => $search_filter_with_invalid_field ) + ->status_is(400) + ->json_is( + "/errors" => [ + { + message => "Properties not allowed: coffee_filter.", + path => "/body" + } + ] + ); + + # Authorized attempt to write + $t->post_ok( "//$auth_userid:$password@/api/v1/search_filters" => json => $search_filter_values ) + ->status_is( 201, 'SWAGGER3.2.1' ) + ->json_has( '/search_filter_id', 'We generated a new id' ) + ->json_is( '/name' => $search_filter_values->{name}, 'The name matches what we supplied' ) + ->json_is( '/query' => $search_filter_values->{query}, 'The query matches what we supplied' ) + ->json_is( '/limits' => $search_filter_values->{limits}, 'The limits match what we supplied' ) + ->json_is( '/opac' => $search_filter_values->{opac}, 'The limits match what we supplied' ) + ->json_is( '/staff_client' => $search_filter_values->{staff_client}, 'The limits match what we supplied' ) + ->header_like( Location => qr|^\/api\/v1\/search_filters\/d*|, 'Correct location' ); + + # save the library_id + my $search_filter_id = 999; + + # Authorized attempt to create with existing id + $search_filter_values->{search_filter_id} = $search_filter_id; + + $t->post_ok( "//$auth_userid:$password@/api/v1/search_filters" => json => $search_filter_values ) + ->status_is(400) + ->json_is( '/errors' => [ + { + message => "Read-only.", + path => "/body/search_filter_id" + } + ] + ); + +}; + +subtest 'update() tests' => sub { + plan tests => 15; + + my $authorized_patron = $builder->build_object({ + class => 'Koha::Patrons', + value => { flags => 0 } + }); + $builder->build({ + source => 'UserPermission', + value => { + borrowernumber => $authorized_patron->borrowernumber, + module_bit => 3, + code => 'manage_search_filters', + }, + }); + + my $password = 'thePassword123'; + $authorized_patron->set_password({ password => $password, skip_validation => 1 }); + my $auth_userid = $authorized_patron->userid; + + my $unauthorized_patron = $builder->build_object({ + class => 'Koha::Patrons', + value => { flags => 0 } + }); + $unauthorized_patron->set_password({ password => $password, skip_validation => 1 }); + my $unauth_userid = $unauthorized_patron->userid; + + my $search_filter = $builder->build_object({ class => 'Koha::SearchFilters' }); + my $search_filter_id = $search_filter->id; + my $search_filter_values = $search_filter->to_api; + delete $search_filter_values->{search_filter_id}; + + # Unauthorized attempt to update + $t->put_ok( "//$unauth_userid:$password@/api/v1/search_filters/$search_filter_id" + => json => { name => 'New unauthorized name change' } ) + ->status_is(403); + + my $search_filter_update = { + name => "Filter update", + filter_query => "ti:The hobbit", + filter_limits => "mc-ccode:fantasy", + }; + + my $test = $t->put_ok( "//$auth_userid:$password@/api/v1/search_filters/$search_filter_id" => json => $search_filter_update ) + ->status_is(200, 'Authorized user can update a macro') + ->json_is( '/search_filter_id' => $search_filter_id, 'We get back the id' ) + ->json_is( '/name' => $search_filter_update->{name}, 'We get back the name' ) + ->json_is( '/filter_query' => $search_filter_update->{filter_query}, 'We get back our query' ) + ->json_is( '/filter_limits' => $search_filter_update->{filter_limits}, 'We get back our limits' ) + ->json_is( '/opac' => 1, 'We get back our opac visibility unchanged' ) + ->json_is( '/staff_client' => 1, 'We get back our staff client visibility unchanged' ); + + # Authorized attempt to write invalid data + my $search_filter_with_invalid_field = { %$search_filter_update }; + $search_filter_with_invalid_field->{'coffee_filter'} = 'Chemex'; + + $t->put_ok( "//$auth_userid:$password@/api/v1/search_filters/$search_filter_id" => json => $search_filter_with_invalid_field ) + ->status_is(400) + ->json_is( + "/errors" => [ + { + message => "Properties not allowed: coffee_filter.", + path => "/body" + } + ] + ); + + my $non_existent_macro = $builder->build_object({class => 'Koha::SearchFilters'}); + my $non_existent_code = $non_existent_macro->id; + $non_existent_macro->delete; + + $t->put_ok("//$auth_userid:$password@/api/v1/search_filters/$non_existent_code" => json => $search_filter_update) + ->status_is(404); + +}; + +subtest 'delete() tests' => sub { + plan tests => 4; + + my $authorized_patron = $builder->build_object({ + class => 'Koha::Patrons', + value => { flags => 0 } + }); + $builder->build({ + source => 'UserPermission', + value => { + borrowernumber => $authorized_patron->borrowernumber, + module_bit => 3, + code => 'manage_search_filters', + }, + }); + + my $password = 'thePassword123'; + $authorized_patron->set_password({ password => $password, skip_validation => 1 }); + my $auth_userid = $authorized_patron->userid; + + my $unauthorized_patron = $builder->build_object({ + class => 'Koha::Patrons', + value => { flags => 0 } + }); + $unauthorized_patron->set_password({ password => $password, skip_validation => 1 }); + my $unauth_userid = $unauthorized_patron->userid; + + my $search_filter = $builder->build_object({ class => 'Koha::SearchFilters' }); + my $search_filter_2 = $builder->build_object({ class => 'Koha::SearchFilters' }); + my $search_filter_id = $search_filter->id; + my $search_filter_2_id = $search_filter_2->id; + + # Unauthorized attempt to delete + $t->delete_ok( "//$unauth_userid:$password@/api/v1/search_filters/$search_filter_2_id") + ->status_is(403, "Cannot delete search filter without permission"); + + $t->delete_ok( "//$auth_userid:$password@/api/v1/search_filters/$search_filter_id") + ->status_is( 204, 'Can delete search filter with permission'); + +}; + +$schema->storage->txn_rollback; diff --git a/t/lib/TestBuilder.pm b/t/lib/TestBuilder.pm index d6e88e7b8a..f648af7f41 100644 --- a/t/lib/TestBuilder.pm +++ b/t/lib/TestBuilder.pm @@ -628,6 +628,10 @@ sub _gen_default_values { status => 'staged', import_error => undef }, + SearchFilter => { + opac => 1, + staff_client => 1 + }, }; }