From 7b7040609a0c3ed6d7eb9ffc981cfe0bae85dab5 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Mon, 23 Mar 2015 13:10:46 +0100 Subject: [PATCH] Bug 13895: Add API routes for issues retrieval and renewal GET /borrowers/{borrowernumber}/issues GET /borrowers/{borrowernumber}/issues/{itemnumber} PUT /borrowers/{borrowernumber}/issues/{itemnumber} + unit tests in t/db_dependent/api/v1/borrowers/issues.t Test plan: 1/ Go to http://api.yourlibrary/v1/doc and test the above routes with different values for each parameter 2/ Try to renew the same issue (with PUT) until the maximum number of renewals is reached (you should have a 403 error then) 3/ Run unit tests in t/db_dependent/api/v1/borrowers/issues.t Depends on bug 13799 --- Koha/REST/V1/Borrowers/Issues.pm | 60 ++++++++++++++ api/v1/swagger.json | 138 +++++++++++++++++++++++++++++++ t/db_dependent/api/v1/borrowers/issues.t | 129 +++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 Koha/REST/V1/Borrowers/Issues.pm create mode 100644 t/db_dependent/api/v1/borrowers/issues.t diff --git a/Koha/REST/V1/Borrowers/Issues.pm b/Koha/REST/V1/Borrowers/Issues.pm new file mode 100644 index 0000000..ee49f27 --- /dev/null +++ b/Koha/REST/V1/Borrowers/Issues.pm @@ -0,0 +1,60 @@ +package Koha::REST::V1::Borrowers::Issues; + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +use C4::Circulation; + +sub list_borrower_issues { + my ($c, $args, $cb) = @_; + + my $issues = C4::Circulation::GetIssues({ + borrowernumber => $args->{borrowernumber} + }); + + $c->$cb($issues, 200); +} + +sub get_borrower_issue { + my ($c, $args, $cb) = @_; + + my $borrowernumber = $args->{borrowernumber}; + my $itemnumber = $args->{itemnumber}; + + my ($issue) = @{ C4::Circulation::GetIssues({ itemnumber => $itemnumber }) }; + if (!$issue or $borrowernumber != $issue->{borrowernumber}) { + return $c->$cb({ + error => "Item $itemnumber is not issued to borrower $borrowernumber" + }, 404); + } + + return $c->$cb($issue, 200); +} + +sub renew_borrower_issue { + my ($c, $args, $cb) = @_; + + my $borrowernumber = $args->{borrowernumber}; + my $itemnumber = $args->{itemnumber}; + + my ($issue) = @{ C4::Circulation::GetIssues({ itemnumber => $itemnumber }) }; + if (!$issue or $borrowernumber != $issue->{borrowernumber}) { + return $c->$cb({ + error => "Item $itemnumber is not issued to borrower $borrowernumber" + }, 404); + } + + my ($can_renew, $error) = C4::Circulation::CanBookBeRenewed($borrowernumber, + $itemnumber); + if (!$can_renew) { + return $c->$cb({error => "Renewal not authorized ($error)"}, 403); + } + + AddRenewal($borrowernumber, $itemnumber, $issue->{branchcode}); + ($issue) = @{ C4::Circulation::GetIssues({ itemnumber => $itemnumber }) }; + + return $c->$cb($issue, 200); +} + +1; diff --git a/api/v1/swagger.json b/api/v1/swagger.json index 5e97d03..a8f7bf7 100644 --- a/api/v1/swagger.json +++ b/api/v1/swagger.json @@ -63,6 +63,81 @@ } } } + }, + "/borrowers/{borrowernumber}/issues": { + "get": { + "x-mojo-controller": "Koha::REST::V1::Borrowers::Issues", + "operationId": "listBorrowerIssues", + "tags": ["borrowers", "issues"], + "parameters": [ + { + "$ref": "#/parameters/borrowernumberPathParam" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "A list of issues", + "schema": { + "$ref": "#/definitions/issues" + } + }, + "404": { + "description": "Borrower not found", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/borrowers/{borrowernumber}/issues/{itemnumber}": { + "get": { + "x-mojo-controller": "Koha::REST::V1::Borrowers::Issues", + "operationId": "getBorrowerIssue", + "tags": ["borrowers", "issues"], + "parameters": [ + { "$ref": "#/parameters/borrowernumberPathParam" }, + { "$ref": "#/parameters/itemnumberPathParam" } + ], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Updated borrower's issue", + "schema": { "$ref": "#/definitions/issue" } + }, + "404": { + "description": "Issue not found", + "schema": { "$ref": "#/definitions/error" } + } + } + }, + "put": { + "x-mojo-controller": "Koha::REST::V1::Borrowers::Issues", + "operationId": "renewBorrowerIssue", + "tags": ["borrowers", "issues"], + "parameters": [ + { "$ref": "#/parameters/borrowernumberPathParam" }, + { "$ref": "#/parameters/itemnumberPathParam" } + ], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Updated borrower's issue", + "schema": { "$ref": "#/definitions/issue" } + }, + "403": { + "description": "Cannot renew issue", + "schema": { "$ref": "#/definitions/error" } + }, + "404": { + "description": "Issue not found", + "schema": { "$ref": "#/definitions/error" } + } + } + } } }, "definitions": { @@ -83,9 +158,65 @@ } } }, + "issues": { + "type": "array", + "items": { + "$ref": "#/definitions/issue" + } + }, + "issue": { + "type": "object", + "properties": { + "borrowernumber": { + "$ref": "#/definitions/borrowernumber" + }, + "itemnumber": { + "$ref": "#/definitions/itemnumber" + }, + "date_due": { + "description": "Due date" + }, + "branchcode": { + "$ref": "#/definitions/branchcode" + }, + "issuingbranch": { + "description": "Code of the branch where issue was made" + }, + "returndate": { + "description": "Date the item was returned" + }, + "lastreneweddate": { + "description": "Date the item was last renewed" + }, + "return": { + "description": "?" + }, + "renewals": { + "description": "Number of renewals" + }, + "auto_renew": { + "description": "Auto renewal" + }, + "timestamp": { + "description": "Last update time" + }, + "issuedate": { + "description": "Date the item was issued" + }, + "onsite_checkout": { + "description": "On site checkout" + } + } + }, "borrowernumber": { "description": "Borrower internal identifier" }, + "branchcode": { + "description": "Branch code" + }, + "itemnumber": { + "description": "Item internal identifier" + }, "error": { "type": "object", "properties": { @@ -103,6 +234,13 @@ "description": "Internal borrower identifier", "required": "true", "type": "integer" + }, + "itemnumberPathParam": { + "name": "itemnumber", + "in": "path", + "description": "Internal item identifier", + "required": "true", + "type": "integer" } } } diff --git a/t/db_dependent/api/v1/borrowers/issues.t b/t/db_dependent/api/v1/borrowers/issues.t new file mode 100644 index 0000000..7e9856d --- /dev/null +++ b/t/db_dependent/api/v1/borrowers/issues.t @@ -0,0 +1,129 @@ +#!/usr/bin/env perl + +use Modern::Perl; + +use Test::More tests => 33; +use Test::MockModule; +use Test::Mojo; + +use DateTime; +use MARC::Record; + +use C4::Context; +use C4::Biblio; +use C4::Circulation; +use C4::Items; + +use Koha::Database; +use Koha::Borrower; + +my $dbh = C4::Context->dbh; +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; + +my $c4_context = new Test::MockModule('C4::Context'); +$c4_context->mock('userenv', \&Mock_userenv); + +my $t = Test::Mojo->new('Koha::REST::V1'); + +my $schema = Koha::Database->new->schema; +my $categorycode = $schema->resultset('Category')->first->categorycode; +my $branchcode = $schema->resultset('Branch')->first->branchcode; + +my $borrower = Koha::Borrower->new; +$borrower->categorycode( $categorycode ); +$borrower->branchcode( $branchcode ); +$borrower->surname("Test Surname"); +$borrower->store; +my $borrowernumber = $borrower->borrowernumber; + +$t->get_ok("/v1/borrowers/$borrowernumber/issues") + ->status_is(200) + ->json_is([]); + +my $notexisting_borrowernumber = $borrowernumber + 1; +$t->get_ok("/v1/borrowers/$notexisting_borrowernumber/issues") + ->status_is(200) + ->json_is([]); + +my $biblionumber = create_biblio('RESTful Web APIs'); +my $itemnumber1 = create_item($biblionumber, 'TEST000001'); +my $itemnumber2 = create_item($biblionumber, 'TEST000002'); + +my $date_due = DateTime->now->add(weeks => 2); +my $date_due1 = C4::Circulation::AddIssue($borrower->unblessed, 'TEST000001', $date_due); +my $date_due2 = C4::Circulation::AddIssue($borrower->unblessed, 'TEST000002', $date_due); + +$t->get_ok("/v1/borrowers/$borrowernumber/issues") + ->status_is(200) + ->json_is('/0/borrowernumber' => $borrowernumber) + ->json_is('/0/itemnumber' => $itemnumber1) + ->json_is('/0/date_due' => $date_due1->ymd . ' ' . $date_due1->hms) + ->json_is('/1/borrowernumber' => $borrowernumber) + ->json_is('/1/itemnumber' => $itemnumber2) + ->json_is('/1/date_due' => $date_due2->ymd . ' ' . $date_due2->hms) + ->json_hasnt('/2'); + +$t->get_ok("/v1/borrowers/$borrowernumber/issues/$itemnumber1") + ->status_is(200) + ->json_is('/date_due' => $date_due1->ymd . ' ' . $date_due1->hms); + +$t->get_ok("/v1/borrowers/$borrowernumber/issues/$itemnumber2") + ->status_is(200) + ->json_is('/date_due' => $date_due2->ymd . ' ' . $date_due2->hms); + + +$dbh->do('DELETE FROM issuingrules'); +$dbh->do(q{ + INSERT INTO issuingrules (categorycode, branchcode, itemtype, issuelength, renewalsallowed) + VALUES (?, ?, ?, ?, ?) +}, {}, '*', '*', '*', 7, 1); + +my $expected_datedue = DateTime->now->add(days => 7)->set(hour => 23, minute => 59, second => 0); +$t->put_ok("/v1/borrowers/$borrowernumber/issues/$itemnumber1") + ->status_is(200) + ->json_is('/date_due' => $expected_datedue->ymd . ' ' . $expected_datedue->hms); + +$t->put_ok("/v1/borrowers/$borrowernumber/issues/$itemnumber1") + ->status_is(403) + ->json_is({ error => 'Renewal not authorized (too_many)' }); + +$t->put_ok("/v1/borrowers/$notexisting_borrowernumber/issues/$itemnumber1") + ->status_is(404) + ->json_is({ error => "Item $itemnumber1 is not issued to borrower $notexisting_borrowernumber" }); + +my $notexisting_itemnumber = $itemnumber2 + 1; +$t->put_ok("/v1/borrowers/$borrowernumber/issues/$notexisting_itemnumber") + ->status_is(404) + ->json_is({ error => "Item $notexisting_itemnumber is not issued to borrower $borrowernumber" }); + +$dbh->rollback; + +sub create_biblio { + my ($title) = @_; + + my $record = new MARC::Record; + $record->append_fields( + new MARC::Field('200', ' ', ' ', a => $title), + ); + + my ($biblionumber) = C4::Biblio::AddBiblio($record, ''); + + return $biblionumber; +} + +sub create_item { + my ($biblionumber, $barcode) = @_; + + my $item = { + barcode => $barcode, + }; + + my $itemnumber = C4::Items::AddItem($item, $biblionumber); + + return $itemnumber; +} + +sub Mock_userenv { + return { branch => $branchcode }; +} -- 1.9.1