From 15a25832d0d080962608dd21c5c387934e6ba74b Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Fri, 7 Apr 2023 14:48:19 +0200 Subject: [PATCH] Bug 30708: Add tests Sponsored-by: BULAC - http://www.bulac.fr/ Signed-off-by: BULAC - http://www.bulac.fr/ Signed-off-by: Heather Hernandez Signed-off-by: Laurence Rault --- .../integration/Preservation/Settings.ts | 254 ++++++++ t/cypress/integration/Preservation/Trains.ts | 593 ++++++++++++++++++ .../integration/Preservation/WaitingList.ts | 185 ++++++ .../Koha/Preservation/Processings.t | 58 ++ t/db_dependent/Koha/Preservation/Trains.t | 102 +++ .../api/v1/preservation_Processings.t | 369 +++++++++++ t/db_dependent/api/v1/preservation_Trains.t | 525 ++++++++++++++++ .../api/v1/preservation_WaitingList.t | 167 +++++ 8 files changed, 2253 insertions(+) create mode 100644 t/cypress/integration/Preservation/Settings.ts create mode 100644 t/cypress/integration/Preservation/Trains.ts create mode 100644 t/cypress/integration/Preservation/WaitingList.ts create mode 100755 t/db_dependent/Koha/Preservation/Processings.t create mode 100755 t/db_dependent/Koha/Preservation/Trains.t create mode 100755 t/db_dependent/api/v1/preservation_Processings.t create mode 100755 t/db_dependent/api/v1/preservation_Trains.t create mode 100755 t/db_dependent/api/v1/preservation_WaitingList.t diff --git a/t/cypress/integration/Preservation/Settings.ts b/t/cypress/integration/Preservation/Settings.ts new file mode 100644 index 00000000000..e72d4b5858b --- /dev/null +++ b/t/cypress/integration/Preservation/Settings.ts @@ -0,0 +1,254 @@ +import { mount } from "@cypress/vue"; + +function get_attributes() { + return [ + { + processing_attribute_id: 1, + processing_id: 1, + name: "Country", + type: "authorised_value", + option_source: "COUNTRY", + }, + { + processing_attribute_id: 1, + processing_id: 1, + name: "DB", + type: "db_column", + option_source: "biblio.title", + }, + { + processing_attribute_id: 1, + processing_id: 1, + name: "Height", + type: "free_text", + option_source: null, + }, + ]; +} +function get_processing() { + return { + name: "test processing", + processing_id: 1, + attributes: get_attributes(), + }; +} +describe("Processings", () => { + beforeEach(() => { + cy.login(); + cy.title().should("eq", "Koha staff interface"); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationModule", + '{"value":"1"}' + ); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationNotForLoanWaitingListIn", + '{"value":"24"}' + ); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationNotForLoanDefaultTrainIn", + '{"value":"42"}' + ); + cy.intercept( + "GET", + "/api/v1/authorised_value_categories/NOT_LOAN/authorised_values", + [ + { + category_name: "NOT_LOAN", + description: "Ordered", + value: "-1", + }, + { + category_name: "NOT_LOAN", + description: "Not for loan", + value: "1", + }, + { + category_name: "NOT_LOAN", + description: "Staff collection", + value: "2", + }, + { + category_name: "NOT_LOAN", + description: "Added to bundle", + value: "3", + }, + { + category_name: "NOT_LOAN", + description: "In preservation", + value: "24", + }, + { + category_name: "NOT_LOAN", + description: "In preservation external", + value: "42", + }, + ] + ); + }); + + it("Settings", () => { + cy.visit("/cgi-bin/koha/preservation/home.pl"); + cy.get("#navmenulist").contains("Settings").click(); + cy.get("#not_for_loan_waiting_list_in .vs__selected").contains( + "In preservation" + ); + cy.get("#not_for_loan_default_train_in .vs__selected").contains( + "In preservation external" + ); + }); + it("List processing", () => { + cy.intercept("GET", "/api/v1/preservation/processings*", []); + cy.visit("/cgi-bin/koha/preservation/settings"); + cy.get("#processing_0").should("not.exist"); + cy.intercept("GET", "/api/v1/preservation/processings*", [ + get_processing(), + ]); + cy.visit("/cgi-bin/koha/preservation/settings"); + cy.get("#processing_0").should("exist"); + }); + + it("Add processing", () => { + cy.intercept("GET", "/api/v1/preservation/processings*", []); + cy.visit("/cgi-bin/koha/preservation/settings"); + let processing = get_processing(); + cy.contains("Add new processing").click(); + cy.get("#processing_name").type(processing.name); + cy.contains("Add new attribute").click(); + let attribute = processing.attributes[0]; + cy.get("#attribute_name_0").type(attribute.name); + cy.get("#attribute_type_0 .vs__search").type("Authorized{enter}", { + force: true, + }); + cy.get("#attribute_option_0 .vs__search").type( + attribute.option_source + "{enter}", + { force: true } + ); + cy.contains("Add new attribute").click(); + attribute = processing.attributes[1]; + cy.get("#attribute_name_1").type(attribute.name); + cy.get("#attribute_type_1 .vs__search").type("Database{enter}", { + force: true, + }); + cy.get("#attribute_option_1 .vs__search").type( + attribute.option_source + "{enter}", + { force: true } + ); + cy.contains("Add new attribute").click(); + attribute = processing.attributes[2]; + cy.get("#attribute_name_2").type(attribute.name); + cy.get("#attribute_type_2 .vs__search").type("Free{enter}", { + force: true, + }); + + // Submit the form, get 500 + cy.intercept("POST", "/api/v1/preservation/processings", { + statusCode: 500, + error: "Something went wrong", + }); + cy.get("#processings_add").contains("Submit").click(); + cy.get("main div[class='dialog alert']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept("POST", "/api/v1/preservation/processings", { + statusCode: 201, + body: processing, + }); + cy.intercept("GET", "/api/v1/preservation/processings*", { + statusCode: 200, + body: [processing], + }); + cy.get("#processings_add").contains("Submit").click(); + cy.get("main div[class='dialog message']").contains( + "Processing created" + ); + cy.get("#processing_0").contains(processing.name); + }); + + it("Edit processing", () => { + let processing = get_processing(); + cy.intercept("GET", "/api/v1/preservation/processings/*", { + statusCode: 200, + body: processing, + }); + cy.intercept("GET", "/api/v1/preservation/processings*", { + statusCode: 200, + body: [processing], + }); + cy.visit("/cgi-bin/koha/preservation/settings"); + cy.get("#processing_0").contains(processing.name); + cy.get("#processing_0").contains("Edit this processing").click(); + cy.get("#processing_name").should("have.value", processing.name); + let attribute = processing.attributes[0]; + cy.get("#attribute_name_0").should("have.value", attribute.name); + cy.get("#attribute_type_0 .vs__selected").contains("Authorized value"); + cy.get("#attribute_option_0 .vs__selected").contains( + attribute.option_source + ); + attribute = processing.attributes[1]; + cy.get("#attribute_name_1").should("have.value", attribute.name); + cy.get("#attribute_type_1 .vs__selected").contains("Database column"); + cy.get("#attribute_option_1 .vs__selected").contains( + attribute.option_source + ); + attribute = processing.attributes[2]; + cy.get("#attribute_name_2").should("have.value", attribute.name); + cy.get("#attribute_type_2 .vs__selected").contains("Free text"); + cy.get("#attribute_option_2").should("not.exist"); + + // Submit the form, get 500 + cy.intercept("PUT", "/api/v1/preservation/processings/*", { + statusCode: 500, + error: "Something went wrong", + }); + cy.get("#processings_add").contains("Submit").click(); + cy.get("main div[class='dialog alert']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept("PUT", "/api/v1/preservation/processings/*", { + statusCode: 200, + body: processing, + }); + cy.get("#processings_add").contains("Submit").click(); + cy.get("main div[class='dialog message']").contains( + "Processing updated" + ); + }); + + it("Delete processing", () => { + let processing = get_processing(); + cy.intercept("GET", "/api/v1/preservation/processings*", { + statusCode: 200, + body: [processing], + }); + + // Submit the form, get 500 + cy.intercept("DELETE", "/api/v1/preservation/processings/*", { + statusCode: 500, + error: "Something went wrong", + }); + cy.visit("/cgi-bin/koha/preservation/settings"); + cy.get("#processing_0").contains("Remove this processing").click(); + cy.contains("Yes, delete").click(); + cy.get("main div[class='dialog alert']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept("DELETE", "/api/v1/preservation/processings/*", { + statusCode: 204, + body: null, + }); + cy.get("#processing_0").contains("Remove this processing").click(); + cy.contains("Yes, delete").click(); + cy.get("main div[class='dialog message']").contains( + `Processing ${processing.name} deleted` + ); + }); +}); diff --git a/t/cypress/integration/Preservation/Trains.ts b/t/cypress/integration/Preservation/Trains.ts new file mode 100644 index 00000000000..94bd7732966 --- /dev/null +++ b/t/cypress/integration/Preservation/Trains.ts @@ -0,0 +1,593 @@ +import { mount } from "@cypress/vue"; + +function get_attributes() { + return [ + { + processing_attribute_id: 1, + processing_id: 1, + name: "Country", + type: "authorised_value", + option_source: "COUNTRY", + }, + { + processing_attribute_id: 2, + processing_id: 1, + name: "DB", + type: "db_column", + option_source: "biblio.title", + }, + { + processing_attribute_id: 3, + processing_id: 1, + name: "Height", + type: "free_text", + option_source: null, + }, + ]; +} +function get_other_attributes() { + return [ + { + processing_attribute_id: 4, + processing_id: 2, + name: "Country", + type: "authorised_value", + option_source: "COUNTRY", + }, + { + processing_attribute_id: 5, + processing_id: 2, + name: "Width", + type: "free_text", + option_source: null, + }, + ]; +} + +function get_processings() { + return [ + { + name: "new processing", + processing_id: 1, + attributes: get_attributes(), + }, + { + name: "an other processing", + processing_id: 2, + attributes: get_other_attributes(), + }, + ]; +} + +function get_items() { + // This is not a full item but it contains the info we are using + return [ + { + biblio: { + biblio_id: 1, + title: "a biblio title", + }, + external_id: "bc_1", + item_id: 1, + }, + { + biblio: { + biblio_id: 2, + title: "an other biblio title", + }, + external_id: "bc_2", + item_id: 2, + }, + { + biblio: { + biblio_id: 3, + title: "yet an other biblio title", + }, + external_id: "bc_3", + item_id: 3, + }, + ]; +} + +function get_train_items() { + let train_item_1 = get_items()[0]; + let processing_attributes = get_attributes(); + train_item_1.attributes = [ + { + processing_attribute: processing_attributes[0], + processing_attribute_id: + processing_attributes[0].processing_attribute_id, + value: "Argentina", + }, + { + processing_attribute: processing_attributes[1], + processing_attribute_id: + processing_attributes[1].processing_attribute_id, + value: "a biblio title modified", + }, + { + processing_attribute: processing_attributes[2], + processing_attribute_id: + processing_attributes[2].processing_attribute_id, + value: "12cm", + }, + ]; + train_item_1.added_on = "2023-03-31T12:23:34+00:00"; + train_item_1.processing_id = 1; + train_item_1.item_id = 1; + + let train_item_2 = get_items()[1]; + let processing_attributes = get_attributes(); + train_item_2.attributes = [ + { + processing_attribute: processing_attributes[0], + processing_attribute_id: + processing_attributes[0].processing_attribute_id, + value: "Uruguay", + }, + { + processing_attribute: processing_attributes[1], + processing_attribute_id: + processing_attributes[1].processing_attribute_id, + value: "an other modified title", + }, + { + processing_attribute: processing_attributes[2], + processing_attribute_id: + processing_attributes[2].processing_attribute_id, + value: "34cm", + }, + ]; + train_item_2.added_on = "2023-04-01T12:34:56+00:00"; + train_item_2.processing_id = 1; + train_item_2.item_id = 2; + + let train_item_3 = get_items()[0]; + let processing_attributes = get_other_attributes(); + train_item_3.attributes = [ + { + processing_attribute: processing_attributes[0], + processing_attribute_id: + processing_attributes[0].processing_attribute_id, + value: "Bolivia", + }, + { + processing_attribute: processing_attributes[1], + processing_attribute_id: + processing_attributes[1].processing_attribute_id, + value: "W 123cm", + }, + ]; + train_item_3.added_on = "2023-04-02T12:34:56+00:00"; + train_item_3.processing_id = 2; + train_item_3.item_id = 3; + + return [train_item_1, train_item_2, train_item_3]; +} + +function get_train() { + let processings = get_processings(); + return { + train_id: 1, + name: "My train", + description: "Just a train", + default_processing_id: processings[0].processing_id, + not_for_loan: "42", + created_on: "2023-04-05T10:16:27+00:00", + closed_on: null, + sent_on: null, + received_on: null, + items: [], + default_processing: processings[0], + }; +} + +describe("Trains", () => { + beforeEach(() => { + cy.login(); + cy.title().should("eq", "Koha staff interface"); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationModule", + '{"value":"1"}' + ); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationNotForLoanWaitingListIn", + '{"value":"24"}' + ); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationNotForLoanDefaultTrainIn", + '{"value":"42"}' + ); + cy.intercept( + "GET", + "/api/v1/authorised_value_categories/NOT_LOAN/authorised_values", + [ + { + category_name: "NOT_LOAN", + description: "Ordered", + value: "-1", + }, + { + category_name: "NOT_LOAN", + description: "Not for loan", + value: "1", + }, + { + category_name: "NOT_LOAN", + description: "Staff collection", + value: "2", + }, + { + category_name: "NOT_LOAN", + description: "Added to bundle", + value: "3", + }, + { + category_name: "NOT_LOAN", + description: "In preservation", + value: "24", + }, + { + category_name: "NOT_LOAN", + description: "In preservation external", + value: "42", + }, + { + category_name: "NOT_LOAN", + description: "In preservation other", + value: "43", + }, + ] + ); + }); + + it("List trains", () => { + // GET trains returns 500 + cy.intercept("GET", "/api/v1/preservation/trains*", { + statusCode: 500, + error: "Something went wrong", + }); + cy.visit("/cgi-bin/koha/preservation/home.pl"); + cy.get("#navmenulist").contains("Trains").click(); + cy.get("main div[class='dialog alert']").contains( + /Something went wrong/ + ); + + // GET trains returns empty list + cy.intercept("GET", "/api/v1/*", []); + cy.visit("/cgi-bin/koha/preservation/trains"); + cy.get("#trains_list").contains("There are no trains defined"); + + // GET trains returns something + let train = get_train(); + let trains = [train]; + + cy.intercept("GET", "/api/v1/preservation/trains*", { + statusCode: 200, + body: trains, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }); + cy.intercept("GET", "/api/v1/preservation/trains/*", train); + cy.visit("/cgi-bin/koha/preservation/trains"); + cy.get("#trains_list").contains("Showing 1 to 1 of 1 entries"); + }); + + it("Add train", () => { + cy.intercept("GET", "/api/v1/preservation/trains", []); + cy.intercept( + "GET", + "/api/v1/preservation/processings", + get_processings() + ); + cy.visit("/cgi-bin/koha/preservation/trains"); + let train = get_train(); + cy.contains("New train").click(); + cy.get("#train_name").type(train.name); + cy.get("#train_description").type(train.description); + // Confirm that the default not_for_loan is selected + cy.get("#not_for_loan .vs__selected").contains( + "In preservation external" + ); + // Change it + cy.get("#not_for_loan .vs__search").type( + "In preservation other{enter}" + ); + cy.get("#train_default_processing .vs__search").type( + "new processing{enter}" + ); + + // Submit the form, get 500 + cy.intercept("POST", "/api/v1/preservation/trains", { + statusCode: 500, + error: "Something went wrong", + }); + cy.get("#trains_add").contains("Submit").click(); + cy.get("main div[class='dialog alert']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept("POST", "/api/v1/preservation/trains", { + statusCode: 201, + body: train, + }); + cy.get("#trains_add").contains("Submit").click(); + cy.get("main div[class='dialog message']").contains("Train created"); + }); + + it("Edit train", () => { + let train = get_train(); + let processings = get_processings(); + cy.intercept("GET", "/api/v1/preservation/trains/*", train); + cy.intercept("GET", "/api/v1/preservation/trains", [train]); + cy.intercept("GET", "/api/v1/preservation/processings*", processings); + cy.visit("/cgi-bin/koha/preservation/trains"); + cy.get("#trains_list table tbody tr:first").contains("Edit").click(); + cy.get("#train_name").should("have.value", train.name); + cy.get("#train_description").should("have.value", train.description); + cy.get("#not_for_loan .vs__selected").contains( + "In preservation external" + ); + cy.get("#train_default_processing .vs__selected").contains( + train.default_processing.name + ); + + // Submit the form, get 500 + cy.intercept("PUT", "/api/v1/preservation/trains/*", { + statusCode: 500, + error: "Something went wrong", + }); + cy.get("#trains_add").contains("Submit").click(); + cy.get("main div[class='dialog alert']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept("PUT", "/api/v1/preservation/trains/*", { + statusCode: 200, + body: train, + }); + cy.intercept("GET", "/api/v1/preservation/trains", { + statusCode: 200, + body: [train], + }); + cy.get("#trains_add").contains("Submit").click(); + cy.get("main div[class='dialog message']").contains("Train updated"); + }); + + it("Simple show train", () => { + let train = get_train(); + let trains = [train]; + + cy.intercept("GET", "/api/v1/preservation/trains*", { + statusCode: 200, + body: trains, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }); + cy.intercept("GET", "/api/v1/preservation/trains/*", train); + cy.visit("/cgi-bin/koha/preservation/trains"); + let name_link = cy.get("#trains_list table tbody tr:first td:first a"); + name_link.should( + "have.text", + train.name + " (#" + train.train_id + ")" + ); + name_link.click(); + cy.get("#trains_show h2").contains("Train #" + train.train_id); + + cy.contains("Name:" + train.name); + cy.contains("Description:" + train.description); + cy.contains( + "Status for item added to this train:" + "In preservation external" + ); + cy.contains("Default processing:" + train.default_processing.name); + }); + + it("Show train close, send, receive", () => { + let train = get_train(); + cy.intercept("GET", "/api/v1/preservation/trains/" + train.train_id, { + statusCode: 200, + body: train, + }).as("get-train"); + cy.visit("/cgi-bin/koha/preservation/trains/" + train.train_id); + cy.wait("@get-train"); + cy.contains("Closed on:").should("not.exist"); + cy.contains("Sent on:").should("not.exist"); + cy.contains("Received on:").should("not.exist"); + + let closed_train = Object.assign({}, train); + closed_train.closed_on = "2022-10-27 12:34:56"; + cy.intercept("PUT", "/api/v1/preservation/trains/" + train.train_id, { + statusCode: 201, + body: closed_train, + }).as("set-train"); + cy.intercept("GET", "/api/v1/preservation/trains/" + train.train_id, { + statusCode: 200, + body: closed_train, + }).as("get-train"); + cy.get("#toolbar").contains("Close").click(); + cy.wait("@get-train"); + cy.contains("Closed on:").should("exist"); + cy.contains("Sent on:").should("not.exist"); + cy.contains("Received on:").should("not.exist"); + + let sent_train = Object.assign({}, closed_train); + sent_train.sent_on = "2022-10-28 12:34:56"; + cy.intercept("PUT", "/api/v1/preservation/trains/" + train.train_id, { + statusCode: 201, + body: sent_train, + }).as("set-train"); + cy.intercept("GET", "/api/v1/preservation/trains/" + train.train_id, { + statusCode: 200, + body: sent_train, + }).as("get-train"); + cy.get("#toolbar").contains("Send").click(); + cy.wait("@get-train"); + cy.contains("Closed on:").should("exist"); + cy.contains("Sent on:").should("exist"); + cy.contains("Received on:").should("not.exist"); + + let received_train = Object.assign({}, sent_train); + received_train.received_on = "2022-10-29 12:34:56"; + cy.intercept("PUT", "/api/v1/preservation/trains/" + train.train_id, { + statusCode: 201, + body: received_train, + }).as("set-train"); + cy.intercept("GET", "/api/v1/preservation/trains/" + train.train_id, { + statusCode: 200, + body: received_train, + }).as("get-train"); + cy.get("#toolbar").contains("Receive").click(); + cy.wait("@get-train"); + cy.contains("Closed on:").should("exist"); + cy.contains("Sent on:").should("exist"); + cy.contains("Received on:").should("exist"); + }); + + it("Delete train", () => { + let train = get_train(); + cy.intercept("GET", "/api/v1/preservation/trains*", { + statusCode: 200, + body: [train], + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }); + cy.visit("/cgi-bin/koha/preservation/trains"); + + // Submit the form, get 500 + cy.intercept( + "DELETE", + "/api/v1/preservation/trains/" + train.train_id, + { + statusCode: 500, + error: "Something went wrong", + } + ); + cy.get("#trains_list table tbody tr:first").contains("Delete").click(); + cy.contains("Yes, delete").click(); + cy.get("main div[class='dialog alert']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept( + "DELETE", + "/api/v1/preservation/trains/" + train.train_id, + { + statusCode: 201, + body: null, + } + ); + cy.get("#trains_list table tbody tr:first").contains("Delete").click(); + cy.contains("Yes, delete").click(); + cy.get("main div[class='dialog message']").contains( + `Train ${train.name} deleted` + ); + }); + + it("Add new item to a train", () => { + let train = get_train(); + cy.intercept( + "GET", + "/api/v1/preservation/trains/" + train.train_id, + train + ); + let processings = get_processings(); + cy.intercept("GET", "/api/v1/preservation/processings*", processings); + cy.intercept( + "GET", + "/api/v1/preservation/processings/" + processings[0].processing_id, + processings[0] + ); + cy.visit("/cgi-bin/koha/preservation/trains/" + train.train_id); + cy.contains("Add items").click(); + cy.get("#barcode").type("bc_1"); + cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", []); + cy.contains("Submit").click(); + cy.get("div[class='dialog alert modal']").contains( + "Cannot find item with this barcode. It must be in the waiting list." + ); + cy.get("#close_modal").click(); + + let item = get_items()[0]; + cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", [item]); + cy.contains("Submit").click(); + cy.intercept( + "POST", + `/api/v1/preservation/trains/${train.train_id}/items`, + { + statusCode: 201, + body: item, // Not correct but not important + } + ); + cy.contains("Itemnumber:" + item.item_id); + cy.get("#processing .vs__selected").contains( + train.default_processing.name + ); + cy.contains("Country:"); + cy.get("#attribute_0 .vs__search").type("Argentin{enter}"); + cy.contains("DB:"); + cy.get("#attribute_1").should("have.value", item.biblio.title); + cy.get("#attribute_1").type(" modified"); + cy.contains("Height:"); + cy.get("#attribute_2").type("42cm"); + + let train_items = get_train_items(); + let train_with_one_item = Object.assign({}, train); + train_with_one_item.items = [train_items[0]]; + + cy.intercept( + "GET", + "/api/v1/preservation/trains/" + train.train_id, + train_with_one_item + ); + cy.contains("Submit").click(); + cy.get("#trains_show").contains("Showing 1 to 1 of 1 entries"); + + let train_with_2_items = Object.assign({}, train); + train_with_2_items.items = [train_items[0], train_items[1]]; + cy.intercept( + "GET", + "/api/v1/preservation/trains/" + train.train_id, + train_with_2_items + ); + cy.visit("/cgi-bin/koha/preservation/trains/" + train.train_id); + cy.get("#trains_show table").should("exist"); + cy.get("#trains_show").contains("Showing 1 to 2 of 2 entries"); + train_with_2_items.items.forEach(train_item => { + train_item.attributes.forEach(attribute => { + cy.get("td").contains(attribute.value); + }); + }); + + let train_with_3_items = Object.assign({}, train); + train_with_3_items.items = [ + train_items[0], + train_items[1], + train_items[2], + ]; + cy.intercept( + "GET", + "/api/v1/preservation/trains/" + train.train_id, + train_with_3_items + ); + cy.visit("/cgi-bin/koha/preservation/trains/" + train.train_id); + cy.get("#trains_show table").should("not.exist"); + train_with_3_items.items.forEach((train_item, i) => { + train_item.attributes.forEach(attribute => { + let re = new RegExp(attribute.value); + cy.get(`#item_${i}`).contains(re); + }); + }); + }); +}); diff --git a/t/cypress/integration/Preservation/WaitingList.ts b/t/cypress/integration/Preservation/WaitingList.ts new file mode 100644 index 00000000000..4e4e1c2b5d1 --- /dev/null +++ b/t/cypress/integration/Preservation/WaitingList.ts @@ -0,0 +1,185 @@ +import { mount } from "@cypress/vue"; + +function get_items() { + // This is not a full item but it contains the info we are using + return [ + { + biblio: { + biblio_id: 1, + title: "a biblio title", + }, + external_id: "bc_1", + item_id: 1, + }, + { + biblio: { + biblio_id: 2, + title: "yet another biblio title", + }, + external_id: "bc_3", + item_id: 3, + }, + ]; +} +describe("WaitingList", () => { + beforeEach(() => { + cy.login(); + cy.title().should("eq", "Koha staff interface"); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationModule", + '{"value":"1"}' + ); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationNotForLoanWaitingListIn", + '{"value":"24"}' + ); + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationNotForLoanDefaultTrainIn", + '{"value":"42"}' + ); + }); + + it("List", () => { + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationNotForLoanWaitingListIn", + '{"value":""}' + ); + cy.visit("/cgi-bin/koha/preservation/home.pl"); + cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", []); + cy.get("#navmenulist").contains("Waiting list").click(); + cy.get("#waiting-list").contains( + "You need to configure this module first." + ); + + cy.intercept( + "GET", + "/cgi-bin/koha/svc/config/systempreferences/?pref=PreservationNotForLoanWaitingListIn", + '{"value":"42"}' + ); + cy.visit("/cgi-bin/koha/preservation/home.pl"); + cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", []); + cy.get("#navmenulist").contains("Waiting list").click(); + cy.get("#waiting-list").contains( + "There are no items in the waiting list" + ); + }); + + it("Add to waiting list", () => { + cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", []); + cy.visit("/cgi-bin/koha/preservation/waiting-list"); + cy.intercept("POST", "/api/v1/preservation/waiting-list/items", { + statusCode: 500, + error: "Something went wrong", + }); + cy.get("#waiting-list").contains("Add to waiting list").click(); + cy.get("#barcode_list").type("bc_1\nbc_2\nbc_3"); + cy.contains("Submit").click(); + cy.get("main div[class='dialog alert']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", { + statusCode: 200, + body: get_items(), + headers: { + "X-Base-Total-Count": "2", + "X-Total-Count": "2", + }, + }).as("get-items"); + cy.intercept("POST", "/api/v1/preservation/waiting-list/items", [ + { item_id: 1 }, + { item_id: 3 }, + ]); + cy.get("#waiting-list").contains("Add to waiting list").click(); + cy.get("#barcode_list").type("bc_1\nbc_2\nbc_3"); + cy.contains("Submit").click(); + cy.wait("@get-items"); + cy.get("main div[class='dialog message']").contains( + "2 new items added." + ); + }); + + it("Add to waiting list then add to a train", () => { + let train = { + description: "yet another train", + name: "a train", + train_id: 1, + }; + cy.intercept("GET", "/api/v1/preservation/trains*", [train]); + cy.visit("/cgi-bin/koha/preservation/waiting-list"); + + cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", { + statusCode: 200, + body: get_items(), + headers: { + "X-Base-Total-Count": "2", + "X-Total-Count": "2", + }, + }).as("get-items"); + cy.intercept("POST", "/api/v1/preservation/waiting-list/items", [ + { item_id: 1 }, + { item_id: 3 }, + ]); + cy.get("#waiting-list").contains("Add to waiting list").click(); + cy.get("#barcode_list").type("bc_1\nbc_2\nbc_3"); + cy.contains("Submit").click(); + cy.wait("@get-items"); + cy.get("main div[class='dialog message']").contains( + "2 new items added." + ); + cy.contains("Add last 2 items to a train").click(); + cy.get("#train_id .vs__search").type(train.name + "{enter}"); + cy.intercept( + "POST", + "/api/v1/preservation/trains/" + train.train_id + "/items/batch", + req => { + req.reply({ + statusCode: 201, + body: req.body, + }); + } + ); + cy.contains("Submit").click(); + cy.get("main div[class='dialog message']").contains( + `2 items have been added to train ${train.train_id}.` + ); + }); + + it("Remove item from waiting list", () => { + cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", { + statusCode: 200, + body: get_items(), + headers: { + "X-Base-Total-Count": "2", + "X-Total-Count": "2", + }, + }); //.as("get-items") + cy.visit("/cgi-bin/koha/preservation/waiting-list"); + + // Submit the form, get 500 + cy.intercept("DELETE", "/api/v1/preservation/waiting-list/items/*", { + statusCode: 500, + error: "Something went wrong", + }); + cy.get("#waiting-list table tbody tr:first").contains("Remove").click(); + cy.contains("Yes, remove").click(); + cy.get("main div[class='dialog alert']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept("DELETE", "/api/v1/preservation/waiting-list/items/*", { + statusCode: 204, + body: null, + }); + cy.get("#waiting-list table tbody tr:first").contains("Remove").click(); + cy.contains("Yes, remove").click(); + cy.get("main div[class='dialog message']").contains( + `Item removed from the waiting list` + ); + }); +}); diff --git a/t/db_dependent/Koha/Preservation/Processings.t b/t/db_dependent/Koha/Preservation/Processings.t new file mode 100755 index 00000000000..d0c1bb1d399 --- /dev/null +++ b/t/db_dependent/Koha/Preservation/Processings.t @@ -0,0 +1,58 @@ +#!/usr/bin/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 => 1; +use Test::Exception; +use Test::Warn; + +use Koha::Preservation::Processings; +use Koha::Database; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +subtest 'attributes' => sub { + + plan tests => 4; + + $schema->storage->txn_begin; + + my $processing = $builder->build_object( { class => 'Koha::Preservation::Processings' } ); + + my $attributes = [ + { name => 'color', type => 'authorised_value', option_source => 'COLORS' }, + { name => 'title', type => 'db_column', option_source => '245$a' }, + { + name => 'height', + type => 'free_text' + }, + ]; + $processing->attributes($attributes); + my $fetched_attributes = $processing->attributes; + is( ref($fetched_attributes), 'Koha::Preservation::Processing::Attributes' ); + is( $fetched_attributes->count, 3 ); + $processing->attributes( [] ); + is( ref($fetched_attributes), 'Koha::Preservation::Processing::Attributes' ); + is( $fetched_attributes->count, 0 ); + + $schema->storage->txn_rollback; +}; diff --git a/t/db_dependent/Koha/Preservation/Trains.t b/t/db_dependent/Koha/Preservation/Trains.t new file mode 100755 index 00000000000..9fd1ea2c3ce --- /dev/null +++ b/t/db_dependent/Koha/Preservation/Trains.t @@ -0,0 +1,102 @@ +#!/usr/bin/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 => 2; +use Test::Exception; +use Test::Warn; + +use Koha::Preservation::Trains; +use Koha::Database; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +subtest 'default_processing' => sub { + + plan tests => 3; + + $schema->storage->txn_begin; + + my $processing = $builder->build_object( { class => 'Koha::Preservation::Processings' } ); + my $another_processing = $builder->build_object( { class => 'Koha::Preservation::Processings' } ); + + my $train = $builder->build_object( { class => 'Koha::Preservation::Trains', value => { default_processing_id => $processing->processing_id } } ); + + my $default_processing = $train->default_processing; + is( ref($default_processing), 'Koha::Preservation::Processing', '->default_processing returns a Koha::Preservation::Processing object' ); + is( $default_processing->processing_id, $processing->processing_id, 'correct processing is returned' ); + $processing->delete; + $default_processing = $train->get_from_storage->default_processing; + is( $default_processing, undef, 'deleting the processing does not delete the train' ); + + $schema->storage->txn_rollback; +}; + +subtest 'add_items & items' => sub { + plan tests => 9; + + $schema->storage->txn_begin; + + my $not_for_loan_waiting_list_in = 24; + my $not_for_loan_train_in = 42; + my $train = $builder->build_object( + { + class => 'Koha::Preservation::Trains', + value => { + not_for_loan => $not_for_loan_train_in, + closed_on => undef, + sent_on => undef, + received_on => undef + } + } + ); + my $item_1 = $builder->build_sample_item; + my $item_2 = $builder->build_sample_item; + my $item_3 = $builder->build_sample_item; + + $builder->build_object( { class => 'Koha::AuthorisedValues', value => { category => 'NOT_LOAN', authorised_value => $not_for_loan_waiting_list_in } } ); + $item_1->notforloan($not_for_loan_waiting_list_in)->store; + $item_2->notforloan(0)->store; # item_2 is not in the waiting list + $item_3->notforloan($not_for_loan_waiting_list_in)->store; + t::lib::Mocks::mock_preference( 'PreservationNotForLoanWaitingListIn', $not_for_loan_waiting_list_in ); + warning_is { + $train->add_items( [ { item_id => $item_1->itemnumber }, { item_id => $item_2->itemnumber }, { barcode => $item_3->barcode } ] ); + } + 'Item not added to train: [Cannot add item to train, it is not in the waiting list]'; + my $items_train = $train->items; + is( $items_train->count, 2, '2 items added to the train' ); + my $item_train_1 = $items_train->find( { item_id => $item_1->itemnumber } ); + my $item_train_3 = $items_train->find( { item_id => $item_3->itemnumber } ); + my $catalogue_item_1 = $item_train_1->catalogue_item; + is( ref($catalogue_item_1), 'Koha::Item' ); + is( $catalogue_item_1->notforloan, $not_for_loan_train_in ); + + my $catalogue_item_3 = $item_train_3->catalogue_item; + is( ref($catalogue_item_3), 'Koha::Item' ); + is( $catalogue_item_3->notforloan, $not_for_loan_train_in ); + + is( $item_1->get_from_storage->notforloan, $not_for_loan_train_in ); + is( $item_2->get_from_storage->notforloan, 0 ); + is( $item_3->get_from_storage->notforloan, $not_for_loan_train_in ); + + $schema->storage->txn_rollback; +}; diff --git a/t/db_dependent/api/v1/preservation_Processings.t b/t/db_dependent/api/v1/preservation_Processings.t new file mode 100755 index 00000000000..f0d86aec10d --- /dev/null +++ b/t/db_dependent/api/v1/preservation_Processings.t @@ -0,0 +1,369 @@ +#!/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 t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::Preservation::Processings; +use Koha::Database; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +my $t = Test::Mojo->new('Koha::REST::V1'); +t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); + +subtest 'list() tests' => sub { + + plan tests => 8; + + $schema->storage->txn_begin; + + Koha::Preservation::Processings->search->delete; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + ## Authorized user tests + # No processings, so empty array should be returned + $t->get_ok("//$userid:$password@/api/v1/preservation/processings")->status_is(200) + ->json_is( [] ); + + my $processing = $builder->build_object( + { + class => 'Koha::Preservation::Processings', + } + ); + + # One processing created, should get returned + $t->get_ok("//$userid:$password@/api/v1/preservation/processings")->status_is(200) + ->json_is( [ $processing->to_api ] ); + + # Unauthorized access + $t->get_ok("//$unauth_userid:$password@/api/v1/preservation/processings") + ->status_is(403); + + $schema->storage->txn_rollback; +}; + +subtest 'get() tests' => sub { + + plan tests => 11; + + $schema->storage->txn_begin; + + my $processing = + $builder->build_object( { class => 'Koha::Preservation::Processings' } ); + my $attributes = [ + { + name => 'color', + type => 'authorised_value', + option_source => 'COLORS' + }, + { name => 'title', type => 'db_column', option_source => '245$a' }, + { + name => 'height', + type => 'free_text' + }, + ]; + $attributes = $processing->attributes($attributes); + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + # This processing exists, should get returned + $t->get_ok( "//$userid:$password@/api/v1/preservation/processings/" + . $processing->processing_id )->status_is(200) + ->json_is( $processing->to_api ); + + # Return one processing with attributes + $t->get_ok( "//$userid:$password@/api/v1/preservation/processings/" + . $processing->processing_id => {'x-koha-embed' => 'attributes'} )->status_is(200) + ->json_is( { %{ $processing->to_api }, attributes => $attributes->to_api }); + + # Unauthorized access + $t->get_ok( "//$unauth_userid:$password@/api/v1/preservation/processings/" + . $processing->processing_id )->status_is(403); + + # Attempt to get non-existent processing + my $processing_to_delete = + $builder->build_object( { class => 'Koha::Preservation::Processings' } ); + my $non_existent_id = $processing_to_delete->processing_id; + $processing_to_delete->delete; + + $t->get_ok("//$userid:$password@/api/v1/preservation/processings/$non_existent_id") + ->status_is(404)->json_is( '/error' => 'Processing not found' ); + + $schema->storage->txn_rollback; +}; + +subtest 'add() tests' => sub { + + plan tests => 15; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $default_processing = $builder->build_object({class => 'Koha::Preservation::Processings'}); + my $processing = { + name => "processing name", + }; + + # Unauthorized attempt to write + $t->post_ok( "//$unauth_userid:$password@/api/v1/preservation/processings" => json => + $processing )->status_is(403); + + # Authorized attempt to write invalid data + my $processing_with_invalid_field = { + blah => "processing Blah", + %$processing, + }; + + $t->post_ok( "//$userid:$password@/api/v1/preservation/processings" => json => + $processing_with_invalid_field )->status_is(400)->json_is( + "/errors" => [ + { + message => "Properties not allowed: blah.", + path => "/body" + } + ] + ); + + # Authorized attempt to write + my $processing_id = + $t->post_ok( + "//$userid:$password@/api/v1/preservation/processings" => json => $processing ) + ->status_is( 201, 'SWAGGER3.2.1' )->header_like( + Location => qr|^/api/v1/preservation/processings/\d*|, + 'SWAGGER3.4.1' + )->json_is( '/name' => $processing->{name} ) + ->tx->res->json->{processing_id}; + + # Authorized attempt to create with null id + $processing->{processing_id} = undef; + $t->post_ok( + "//$userid:$password@/api/v1/preservation/processings" => json => $processing ) + ->status_is(400)->json_has('/errors'); + + # Authorized attempt to create with existing id + $processing->{processing_id} = $processing_id; + $t->post_ok( + "//$userid:$password@/api/v1/preservation/processings" => json => $processing ) + ->status_is(400)->json_is( + "/errors" => [ + { + message => "Read-only.", + path => "/body/processing_id" + } + ] + ); + + $schema->storage->txn_rollback; +}; + +subtest 'update() tests' => sub { + + plan tests => 15; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $processing_id = + $builder->build_object( { class => 'Koha::Preservation::Processings' } )->processing_id; + + # Unauthorized attempt to update + $t->put_ok( + "//$unauth_userid:$password@/api/v1/preservation/processings/$processing_id" => + json => { name => 'New unauthorized name change' } )->status_is(403); + + # Attempt partial update on a PUT + my $processing_with_missing_field = { + }; + + $t->put_ok( + "//$userid:$password@/api/v1/preservation/processings/$processing_id" => json => + $processing_with_missing_field )->status_is(400) + ->json_is( "/errors" => + [ { message => "Missing property.", path => "/body/name" } ] ); + + my $default_processing = $builder->build_object({class => 'Koha::Preservation::Processings'}); + # Full object update on PUT + my $processing_with_updated_field = { + name => "New name", + }; + + $t->put_ok( + "//$userid:$password@/api/v1/preservation/processings/$processing_id" => json => + $processing_with_updated_field )->status_is(200) + ->json_is( '/name' => 'New name' ); + + # Authorized attempt to write invalid data + my $processing_with_invalid_field = { + blah => "processing Blah", + %$processing_with_updated_field, + }; + + $t->put_ok( + "//$userid:$password@/api/v1/preservation/processings/$processing_id" => json => + $processing_with_invalid_field )->status_is(400)->json_is( + "/errors" => [ + { + message => "Properties not allowed: blah.", + path => "/body" + } + ] + ); + + # Attempt to update non-existent processing + my $processing_to_delete = + $builder->build_object( { class => 'Koha::Preservation::Processings' } ); + my $non_existent_id = $processing_to_delete->processing_id; + $processing_to_delete->delete; + + $t->put_ok( "//$userid:$password@/api/v1/preservation/processings/$non_existent_id" => + json => $processing_with_updated_field )->status_is(404); + + # Wrong method (POST) + $processing_with_updated_field->{processing_id} = 2; + + $t->post_ok( + "//$userid:$password@/api/v1/preservation/processings/$processing_id" => json => + $processing_with_updated_field )->status_is(404); + + $schema->storage->txn_rollback; +}; + +subtest 'delete() tests' => sub { + + plan tests => 7; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $processing_id = + $builder->build_object( { class => 'Koha::Preservation::Processings' } )->processing_id; + + # Unauthorized attempt to delete + $t->delete_ok( + "//$unauth_userid:$password@/api/v1/preservation/processings/$processing_id") + ->status_is(403); + + # Delete existing processing + $t->delete_ok("//$userid:$password@/api/v1/preservation/processings/$processing_id") + ->status_is( 204, 'SWAGGER3.2.4' )->content_is( '', 'SWAGGER3.3.4' ); + + # Attempt to delete non-existent processing + $t->delete_ok("//$userid:$password@/api/v1/preservation/processings/$processing_id") + ->status_is(404); + + $schema->storage->txn_rollback; +}; diff --git a/t/db_dependent/api/v1/preservation_Trains.t b/t/db_dependent/api/v1/preservation_Trains.t new file mode 100755 index 00000000000..6f73bd64076 --- /dev/null +++ b/t/db_dependent/api/v1/preservation_Trains.t @@ -0,0 +1,525 @@ +#!/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 => 6; +use Test::Mojo; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::Preservation::Trains; +use Koha::Database; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +my $t = Test::Mojo->new('Koha::REST::V1'); +t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); + +subtest 'list() tests' => sub { + + plan tests => 8; + + $schema->storage->txn_begin; + + Koha::Preservation::Trains->search->delete; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + ## Authorized user tests + # No trains, so empty array should be returned + $t->get_ok("//$userid:$password@/api/v1/preservation/trains")->status_is(200) + ->json_is( [] ); + + my $train = $builder->build_object( + { + class => 'Koha::Preservation::Trains', + } + ); + + # One train created, should get returned + $t->get_ok("//$userid:$password@/api/v1/preservation/trains")->status_is(200) + ->json_is( [ $train->to_api ] ); + + # Unauthorized access + $t->get_ok("//$unauth_userid:$password@/api/v1/preservation/trains") + ->status_is(403); + + $schema->storage->txn_rollback; +}; + +subtest 'get() tests' => sub { + + plan tests => 14; + + $schema->storage->txn_begin; + + my $train = + $builder->build_object( { class => 'Koha::Preservation::Trains' } ); + my $default_processing = $train->default_processing; + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + # This train exists, should get returned + $t->get_ok( "//$userid:$password@/api/v1/preservation/trains/" + . $train->train_id )->status_is(200) + ->json_is( $train->to_api ); + + # Return one train with some embeds + $t->get_ok( "//$userid:$password@/api/v1/preservation/trains/" + . $train->train_id => {'x-koha-embed' => 'items,default_processing'} )->status_is(200) + ->json_is( { %{ $train->to_api }, items => [], default_processing => $default_processing->unblessed }); + + # Return one train with all embeds + $t->get_ok( "//$userid:$password@/api/v1/preservation/trains/" + . $train->train_id => { 'x-koha-embed' => 'items,items.attributes,items.attributes.processing_attribute,default_processing,default_processing.attributes' } ) + ->status_is(200)->json_is( { %{ $train->to_api }, items => [], default_processing => { %{ $default_processing->unblessed }, attributes => [] } } ); + + # Unauthorized access + $t->get_ok( "//$unauth_userid:$password@/api/v1/preservation/trains/" + . $train->train_id )->status_is(403); + + # Attempt to get non-existent train + my $train_to_delete = + $builder->build_object( { class => 'Koha::Preservation::Trains' } ); + my $non_existent_id = $train_to_delete->train_id; + $train_to_delete->delete; + + $t->get_ok("//$userid:$password@/api/v1/preservation/trains/$non_existent_id") + ->status_is(404)->json_is( '/error' => 'Train not found' ); + + $schema->storage->txn_rollback; +}; + +subtest 'add() tests' => sub { + + plan tests => 18; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $default_processing = $builder->build_object({class => 'Koha::Preservation::Processings'}); + my $train = { + name => "train name", + description => "train description", + default_processing_id => $default_processing->processing_id, + not_for_loan => 42, + }; + + # Unauthorized attempt to write + $t->post_ok( "//$unauth_userid:$password@/api/v1/preservation/trains" => json => + $train )->status_is(403); + + # Authorized attempt to write invalid data + my $train_with_invalid_field = { + blah => "train Blah", + %$train, + }; + + $t->post_ok( "//$userid:$password@/api/v1/preservation/trains" => json => + $train_with_invalid_field )->status_is(400)->json_is( + "/errors" => [ + { + message => "Properties not allowed: blah.", + path => "/body" + } + ] + ); + + # Authorized attempt to write + my $train_id = + $t->post_ok( + "//$userid:$password@/api/v1/preservation/trains" => json => $train ) + ->status_is( 201, 'SWAGGER3.2.1' )->header_like( + Location => qr|^/api/v1/preservation/trains/\d*|, + 'SWAGGER3.4.1' + )->json_is( '/name' => $train->{name} ) + ->json_is( '/description' => $train->{description} ) + ->json_is( '/default_processing_id' => $train->{default_processing_id} ) + ->json_is( '/not_for_loan' => $train->{not_for_loan} ) + ->tx->res->json->{train_id}; + + # Authorized attempt to create with null id + $train->{train_id} = undef; + $t->post_ok( + "//$userid:$password@/api/v1/preservation/trains" => json => $train ) + ->status_is(400)->json_has('/errors'); + + # Authorized attempt to create with existing id + $train->{train_id} = $train_id; + $t->post_ok( + "//$userid:$password@/api/v1/preservation/trains" => json => $train ) + ->status_is(400)->json_is( + "/errors" => [ + { + message => "Read-only.", + path => "/body/train_id" + } + ] + ); + + $schema->storage->txn_rollback; +}; + +subtest 'update() tests' => sub { + + plan tests => 15; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $train_id = + $builder->build_object( { class => 'Koha::Preservation::Trains' } )->train_id; + + # Unauthorized attempt to update + $t->put_ok( + "//$unauth_userid:$password@/api/v1/preservation/trains/$train_id" => + json => { name => 'New unauthorized name change' } )->status_is(403); + + # Attempt partial update on a PUT + my $train_with_missing_field = { + }; + + $t->put_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id" => json => + $train_with_missing_field )->status_is(400) + ->json_is( "/errors" => + [ { message => "Missing property.", path => "/body/name" } ] ); + + my $default_processing = $builder->build_object({class => 'Koha::Preservation::Processings'}); + # Full object update on PUT + my $train_with_updated_field = { + name => "New name", + description => "train description", + default_processing_id => $default_processing->processing_id, + not_for_loan => 42, + }; + + $t->put_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id" => json => + $train_with_updated_field )->status_is(200) + ->json_is( '/name' => 'New name' ); + + # Authorized attempt to write invalid data + my $train_with_invalid_field = { + blah => "train Blah", + %$train_with_updated_field, + }; + + $t->put_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id" => json => + $train_with_invalid_field )->status_is(400)->json_is( + "/errors" => [ + { + message => "Properties not allowed: blah.", + path => "/body" + } + ] + ); + + # Attempt to update non-existent train + my $train_to_delete = + $builder->build_object( { class => 'Koha::Preservation::Trains' } ); + my $non_existent_id = $train_to_delete->train_id; + $train_to_delete->delete; + + $t->put_ok( "//$userid:$password@/api/v1/preservation/trains/$non_existent_id" => + json => $train_with_updated_field )->status_is(404); + + # Wrong method (POST) + $train_with_updated_field->{train_id} = 2; + + $t->post_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id" => json => + $train_with_updated_field )->status_is(404); + + $schema->storage->txn_rollback; +}; + +subtest 'delete() tests' => sub { + + plan tests => 7; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $train_id = + $builder->build_object( { class => 'Koha::Preservation::Trains' } )->train_id; + + # Unauthorized attempt to delete + $t->delete_ok( + "//$unauth_userid:$password@/api/v1/preservation/trains/$train_id") + ->status_is(403); + + # Delete existing train + $t->delete_ok("//$userid:$password@/api/v1/preservation/trains/$train_id") + ->status_is( 204, 'SWAGGER3.2.4' )->content_is( '', 'SWAGGER3.3.4' ); + + # Attempt to delete non-existent train + $t->delete_ok("//$userid:$password@/api/v1/preservation/trains/$train_id") + ->status_is(404); + + $schema->storage->txn_rollback; +}; + +subtest '*_item() tests' => sub { + + plan tests => 26; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $not_for_loan_waiting_list_in = 24; + my $not_for_loan_train_in = 42; + + t::lib::Mocks::mock_preference( 'PreservationNotForLoanWaitingListIn', + $not_for_loan_waiting_list_in ); + + my $default_processing = + $builder->build_object( { class => 'Koha::Preservation::Processings' } ); + my $another_processing = + $builder->build_object( { class => 'Koha::Preservation::Processings' } ); + + my $attributes = [ + { + name => 'color', + type => 'authorised_value', + option_source => 'COLORS' + }, + { name => 'title', type => 'db_column', option_source => '245$a' }, + { + name => 'height', + type => 'free_text' + }, + ]; + my $processing_attributes = $default_processing->attributes($attributes); + my $color_attribute = + $processing_attributes->search( { name => 'color' } )->next; + my $title_attribute = + $processing_attributes->search( { name => 'title' } )->next; + my $height_attribute = + $processing_attributes->search( { name => 'height' } )->next; + + my $train = $builder->build_object( + { + class => 'Koha::Preservation::Trains', + value => { + not_for_loan => $not_for_loan_train_in, + default_processing_id => $default_processing->processing_id, + closed_on => undef, + sent_on => undef, + received_on => undef, + } + } + ); + my $train_id = $train->train_id; + + my $item_1 = $builder->build_sample_item; + my $item_2 = $builder->build_sample_item; + my $item_3 = $builder->build_sample_item; + + # Add item not in waiting list + $t->post_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id/items" => + json => { item_id => $item_1->itemnumber } )->status_is(400) + ->json_is( { error => 'Item not in waiting list' } ); + + $item_1->notforloan($not_for_loan_waiting_list_in)->store; + + # Add item in waiting list + my $item_attributes = [ + { + processing_attribute_id => $color_attribute->processing_attribute_id, + value => 'red' + }, + { + processing_attribute_id => $title_attribute->processing_attribute_id, + value => 'my title' + }, + ]; + my $train_item_id = + $t->post_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id/items" => + json => + { item_id => $item_1->itemnumber, attributes => $item_attributes } ) + ->status_is( 201, 'SWAGGER3.2.1' ) + ->json_is( '/item_id' => $item_1->itemnumber ) + ->json_is( '/processing_id' => $train->default_processing_id ) + ->json_has('/added_on')->tx->res->json->{train_item_id}; + my $train_item = Koha::Preservation::Train::Items->find($train_item_id); + + $t->get_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id/items/$train_item_id" + => { 'x-koha-embed' => 'attributes' } )->status_is(200)->json_is( + { + %{ $train_item->to_api }, + attributes => $train_item->attributes->to_api + } + ); + + is( $item_1->get_from_storage->notforloan, + $train->not_for_loan, + "Item not for loan has been set to train's not for loan" ); + + # Add deleted item + $item_2->delete; + $t->post_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id/items" => + json => { item_id => $item_2->itemnumber } )->status_is(404) + ->json_is( { error => 'Item not found' } ); + + # Update item + my $new_item_attributes = [ + { + processing_attribute_id => $title_attribute->processing_attribute_id, + value => 'my new title' + }, + { + processing_attribute_id => $height_attribute->processing_attribute_id, + value => '24cm' + }, + ]; + $t->put_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id/items/$train_item_id" + => json => { + item_id => $item_1->itemnumber, + attributes => $new_item_attributes, + } + )->status_is(200); + + $t->get_ok( + "//$userid:$password@/api/v1/preservation/trains/$train_id/items/$train_item_id" + => { 'x-koha-embed' => 'attributes' } )->status_is(200)->json_is( + { + %{ $train_item->to_api }, + attributes => $train_item->attributes->to_api + } + ); + + # Delete existing item + $t->delete_ok("//$userid:$password@/api/v1/preservation/trains/$train_id/items/$train_item_id") + ->status_is( 204, 'SWAGGER3.2.4' )->content_is( '', 'SWAGGER3.3.4' ); + + # Delete non existing item + $t->delete_ok("//$userid:$password@/api/v1/preservation/trains/$train_id/items/$train_item_id") + ->status_is(404)->json_is( '/error' => 'Train item not found' ); + + $schema->storage->txn_rollback; +}; diff --git a/t/db_dependent/api/v1/preservation_WaitingList.t b/t/db_dependent/api/v1/preservation_WaitingList.t new file mode 100755 index 00000000000..4575b3c6b0d --- /dev/null +++ b/t/db_dependent/api/v1/preservation_WaitingList.t @@ -0,0 +1,167 @@ +#!/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 => 1; +use Test::Mojo; +use Test::Warn; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::Preservation::Trains; +use Koha::Database; +use Koha::DateUtils qw( dt_from_string ); + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +my $t = Test::Mojo->new('Koha::REST::V1'); +t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); + +subtest 'add_item, list, remove_item tests' => sub { + + plan tests => 34; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**30 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $item_1 = $builder->build_sample_item; + my $item_2 = $builder->build_sample_item; + my $item_3 = $builder->build_sample_item; + + t::lib::Mocks::mock_preference( 'PreservationNotForLoanWaitingListIn', undef ); + $t->get_ok("//$userid:$password@/api/v1/preservation/waiting-list/items") + ->status_is(400)->json_is( + { + error => 'MissingSettings', + parameter => 'PreservationNotForLoanWaitingListIn' + } + ); + + my $not_for_loan_waiting_list_in = 24; + t::lib::Mocks::mock_preference( 'PreservationNotForLoanWaitingListIn', + $not_for_loan_waiting_list_in ); + + $t->get_ok("//$userid:$password@/api/v1/preservation/waiting-list/items") + ->status_is(200)->json_is( [] ); + + # Add items + $t->post_ok( + "//$userid:$password@/api/v1/preservation/waiting-list/items" => + json => [ + { item_id => $item_1->itemnumber }, + { barcode => $item_3->barcode } + ] + )->status_is(201)->json_is( + [ + { item_id => $item_1->itemnumber }, + { item_id => $item_3->itemnumber } + ] + ); + + is( $item_1->get_from_storage->notforloan, $not_for_loan_waiting_list_in ); + is( $item_2->get_from_storage->notforloan, 0 ); + is( $item_3->get_from_storage->notforloan, $not_for_loan_waiting_list_in ); + + $t->post_ok( + "//$userid:$password@/api/v1/preservation/waiting-list/items" => + json => [ + { item_id => $item_2->itemnumber }, + { barcode => $item_3->barcode } + ] + )->status_is(201)->json_is( [ { item_id => $item_2->itemnumber } ] ); + + is( $item_1->get_from_storage->notforloan, $not_for_loan_waiting_list_in ); + is( $item_2->get_from_storage->notforloan, $not_for_loan_waiting_list_in ); + is( $item_3->get_from_storage->notforloan, $not_for_loan_waiting_list_in ); + + $t->delete_ok( + "//$userid:$password@/api/v1/preservation/waiting-list/items/" + . $item_2->itemnumber )->status_is(204); + + is( $item_2->get_from_storage->notforloan, 0, + "Item removed from the waiting list has its notforloan status back to 0" + ); + + # Add item_1 to a non-received train + my $train = $builder->build_object( + { + class => 'Koha::Preservation::Trains', + value => { + not_for_loan => 42, + closed_on => undef, + sent_on => undef, + received_on => undef + } + } + ); + $train->add_item( { item_id => $item_1->itemnumber } ); + + # And try to add it to the waiting list + warning_like { + $t->post_ok( + "//$userid:$password@/api/v1/preservation/waiting-list/items" => + json => [ { item_id => $item_1->itemnumber } ] )->status_is(201) + ->json_is( [] ); + } + qr[Cannot add item to waiting list, it is already in a non-received train]; + + # Add item_3 to a train, receive the train and add the item to the waiting list + $train = $builder->build_object( + { + class => 'Koha::Preservation::Trains', + value => { + not_for_loan => 42, + closed_on => undef, + sent_on => undef, + received_on => undef + } + } + ); + $train->add_item( { item_id => $item_3->itemnumber } ); + $train->received_on(dt_from_string)->store; + + $t->post_ok( + "//$userid:$password@/api/v1/preservation/waiting-list/items" => + json => [ { item_id => $item_3->itemnumber } ] )->status_is(201) + ->json_is( [ { item_id => $item_3->itemnumber } ] ); + + $t->delete_ok( + "//$userid:$password@/api/v1/preservation/waiting-list/items/" + . $item_2->itemnumber )->status_is(204); + + $item_2->delete; + $t->delete_ok("//$userid:$password@/api/v1/preservation/waiting-list/items") + ->status_is(404); + + $t->delete_ok( + "//$userid:$password@/api/v1/preservation/waiting-list/items/" + . $item_2->itemnumber )->status_is(404); + + $schema->storage->txn_rollback; +}; -- 2.25.1