From 5957b190a31137af9d298c46f0d5ea3c8e3b1791 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Tue, 17 Jun 2025 16:09:59 +0200 Subject: [PATCH] Bug 40180: (bug 40118 follow-up) Add Cypress tests This patch heavily reuses what has been introduced by the previous patches in the tree, and add a insertSampleHold task to be reusable easily in other tests. Test plan: All Cypress tests must pass --- .../integration/KohaTable/Holdings_spec.ts | 4 +- .../KohaTable/PendingHolds_spec.ts | 49 +++ t/cypress/integration/t/insertData.ts | 12 +- t/cypress/plugins/index.js | 12 +- t/cypress/plugins/insertData.js | 282 +++++++++++++----- 5 files changed, 278 insertions(+), 81 deletions(-) create mode 100644 t/cypress/integration/KohaTable/PendingHolds_spec.ts diff --git a/t/cypress/integration/KohaTable/Holdings_spec.ts b/t/cypress/integration/KohaTable/Holdings_spec.ts index 32fd124f74e..fda13f1951c 100644 --- a/t/cypress/integration/KohaTable/Holdings_spec.ts +++ b/t/cypress/integration/KohaTable/Holdings_spec.ts @@ -24,7 +24,7 @@ describe("catalogue/detail/holdings_table with items", () => { }); afterEach(function () { - cy.task("deleteSampleObjects", this.objects); + cy.task("deleteSampleObjects", [this.objects]); cy.set_syspref( "AlwaysShowHoldingsTableFilters", this.syspref_AlwaysShowHoldingsTableFilters @@ -279,7 +279,7 @@ describe("catalogue/detail/holdings_table without items", () => { }); afterEach(function () { - cy.task("deleteSampleObjects", this.objects); + cy.task("deleteSampleObjects", [this.objects]); cy.set_syspref( "AlwaysShowHoldingsTableFilters", this.syspref_AlwaysShowHoldingsTableFilters diff --git a/t/cypress/integration/KohaTable/PendingHolds_spec.ts b/t/cypress/integration/KohaTable/PendingHolds_spec.ts new file mode 100644 index 00000000000..160c3d3e987 --- /dev/null +++ b/t/cypress/integration/KohaTable/PendingHolds_spec.ts @@ -0,0 +1,49 @@ +describe("circ/pendingreserves/holdst", () => { + const table_id = "holdst"; + beforeEach(() => { + cy.login(); + cy.title().should("eq", "Koha staff interface"); + cy.task("insertSampleBiblio", { item_count: 1 }).then( + objects_biblio_1 => { + cy.wrap(objects_biblio_1).as("objects_biblio_1"); + cy.task("insertSampleHold", { + item: objects_biblio_1.items[0], + }).then(objects_hold_1 => { + cy.wrap(objects_hold_1).as("objects_hold_1"); + }); + + cy.task("insertSampleHold", { + item: objects_biblio_1.items[0], + }).then(objects_hold_2 => { + cy.wrap(objects_hold_2).as("objects_hold_2"); + }); + } + ); + cy.task("insertSampleBiblio", { item_count: 1 }).then( + objects_biblio_2 => { + cy.wrap(objects_biblio_2).as("objects_biblio_2"); + cy.task("insertSampleHold", { + item: objects_biblio_2.items[0], + }).then(objects_hold_3 => { + cy.wrap(objects_hold_3).as("objects_hold_3"); + }); + } + ); + }); + + afterEach(function () { + cy.task("deleteSampleObjects", [ + this.objects_hold_1, + this.objects_hold_2, + this.objects_hold_3, + this.objects_biblio_2, + this.objects_biblio_1, + ]); + }); + + it("test", function () { + cy.visit( + "/cgi-bin/koha/circ/pendingreserves.pl?from=2000-01-01&to=2999-12-31&run_report=Submit" + ); + }); +}); diff --git a/t/cypress/integration/t/insertData.ts b/t/cypress/integration/t/insertData.ts index ff163c606a7..9055067b541 100644 --- a/t/cypress/integration/t/insertData.ts +++ b/t/cypress/integration/t/insertData.ts @@ -1,11 +1,13 @@ const { query } = require("./../../plugins/db.js"); describe("insertSampleBiblio", () => { - it("should generate library and item type", () => { + it("insertSampleBiblio - should generate library and item type", () => { cy.task("insertSampleBiblio", { item_count: 3 }).then(objects => { - const biblio_id = objects.biblio.biblio_id; + expect(typeof objects.biblio.biblio_id).to.be.equal("number"); + expect(typeof objects.biblio.title).to.be.equal("string"); + expect(typeof objects.biblio.author).to.be.equal("string"); - expect(typeof biblio_id).to.be.equal("number"); + const biblio_id = objects.biblio.biblio_id; cy.task("query", { sql: "SELECT COUNT(*) as count FROM biblio WHERE biblionumber=?", @@ -30,7 +32,7 @@ describe("insertSampleBiblio", () => { cy.task("query", { sql: "SELECT COUNT(*) as count FROM branches WHERE branchcode=?", - values: [objects.library.library_id], + values: [objects.libraries[0].library_id], }).then(result => { expect(result[0].count).to.be.equal(1); }); @@ -60,7 +62,7 @@ describe("insertSampleBiblio", () => { cy.task("query", { sql: "SELECT COUNT(*) as count FROM branches WHERE branchcode=?", - values: [objects.library.library_id], + values: [objects.libraries[0].library_id], }).then(result => { expect(result[0].count).to.be.equal(0); }); diff --git a/t/cypress/plugins/index.js b/t/cypress/plugins/index.js index ff52883306a..a3cbd0c908b 100644 --- a/t/cypress/plugins/index.js +++ b/t/cypress/plugins/index.js @@ -4,6 +4,7 @@ const { buildSampleObject, buildSampleObjects } = require("./mockData.js"); const { insertSampleBiblio, + insertSampleHold, insertObject, deleteSampleObjects, } = require("./insertData.js"); @@ -36,11 +37,14 @@ module.exports = (on, config) => { }, buildSampleObject, buildSampleObjects, - insertSampleBiblio({ item_count }) { - return insertSampleBiblio(item_count, baseUrl, authHeader); + insertSampleBiblio(args) { + return insertSampleBiblio({ ...args, baseUrl, authHeader }); }, - insertObject({ type, object }) { - return insertObject(type, object, baseUrl, authHeader); + insertSampleHold(args) { + return insertSampleHold({ ...args, baseUrl, authHeader }); + }, + insertObject(args) { + return insertObject({ ...args, baseUrl, authHeader }); }, deleteSampleObjects, query, diff --git a/t/cypress/plugins/insertData.js b/t/cypress/plugins/insertData.js index 3063679d177..954a86a8fa5 100644 --- a/t/cypress/plugins/insertData.js +++ b/t/cypress/plugins/insertData.js @@ -3,27 +3,23 @@ const { query } = require("./db.js"); const { apiGet, apiPost } = require("./api-client.js"); -const insertSampleBiblio = async (item_count, baseUrl, authHeader) => { - let generated_objects = {}; - const objects = [{ object: "library" }, { object: "item_type" }]; - for (const { object } of objects) { - const attributes = await buildSampleObject({ object }); - generated_objects[object] = attributes; - } +const insertSampleBiblio = async ({ + item_count, + options, + baseUrl, + authHeader, +}) => { - const library = await insertObject( - "library", - generated_objects["library"], + const generatedItemType = await buildSampleObject({ object: "item_type" }); + const item_type = await insertObject({ + type: "item_type", + object: generatedItemType, baseUrl, - authHeader - ); - const item_type = await insertObject( - "item_type", - generated_objects["item_type"], - baseUrl, - authHeader - ); + authHeader, + }); + let title = "Some boring read"; + let author = "Some boring author"; let biblio = { leader: " nam a22 7a 4500", fields: [ @@ -32,14 +28,14 @@ const insertSampleBiblio = async (item_count, baseUrl, authHeader) => { 245: { ind1: "", ind2: "", - subfields: [{ a: "Some boring read" }], + subfields: [{ a: title }], }, }, { 100: { ind1: "", ind2: "", - subfields: [{ c: "Some boring author" }], + subfields: [{ c: author }], }, }, { @@ -66,6 +62,8 @@ const insertSampleBiblio = async (item_count, baseUrl, authHeader) => { // We might need to refine that in the future biblio = { biblio_id, + title, + author, }; let items = buildSampleObjects({ @@ -80,9 +78,8 @@ const insertSampleBiblio = async (item_count, baseUrl, authHeader) => { restricted_status: 0, new_status: null, issues: 0, + checked_out_date: null, item_type_id: item_type.item_type_id, - home_library_id: library.library_id, - holding_library_id: library.library_id, }, }); items = items.map( @@ -123,59 +120,168 @@ const insertSampleBiblio = async (item_count, baseUrl, authHeader) => { }) => rest ); let createdItems = []; - for (const item of items) { - await apiPost({ - endpoint: `/api/v1/biblios/${biblio_id}/items`, - body: item, + let libraries = []; + let commonLibrary; + if (!options || !options.different_libraries) { + const generatedLibrary = await buildSampleObject({ object: "library" }); + commonLibrary = await insertObject({ + type: "library", + object: generatedLibrary, baseUrl, authHeader, - }).then(i => createdItems.push(i)); + }); + libraries.push(commonLibrary); } - return { biblio, items: createdItems, library, item_type }; + for (const item of items) { + if (options?.different_libraries) { + const generatedLibrary = await buildSampleObject({ + object: "library", + }); + const library = await insertObject({ + type: "library", + object: generatedLibrary, + baseUrl, + authHeader, + }); + libraries.push(library); + item.home_library_id = library.library_id; + item.holding_library_id = library.library_id; + } else { + item.home_library_id = commonLibrary.library_id; + item.holding_library_id = commonLibrary.library_id; + } + + await apiPost({ + endpoint: `/api/v1/biblios/${biblio_id}/items`, + body: item, + baseUrl, + authHeader, + }) + .then(i => createdItems.push(i)); + } + return { biblio, items: createdItems, libraries, item_type }; +}; + +const insertSampleHold = async ({ + item, + biblio, + library_id, + baseUrl, + authHeader, +}) => { + + library_id ||= item.home_library_id; + const generatedPatron = await buildSampleObject({ + object: "patron", + values: { library_id }, + }); + + const patron = await insertObject({ + type: "patron", + object: generatedPatron, + baseUrl, + authHeader, + }); + + const generatedHold = buildSampleObject({ + object: "hold", + values: { + patron_id: patron.patron_id, + biblio_id: item?.biblio_id || biblio.biblio_id, + pickup_library_id: library_id, + item_id: item?.item_id || null, + }, + }); + const hold = await insertObject({ + type: "hold", + object: generatedHold, + baseUrl, + authHeader, + }); + return { hold, patron }; }; -const deleteSampleObjects = async objects => { - const deletionOrder = ["items", "biblio", "library", "item_type"]; +const deleteSampleObjects = async allObjects => { + if (!Array.isArray(allObjects)) { + allObjects = [allObjects]; + } + + const deletionOrder = [ + "hold", + "patron", + "item", + "items", + "biblio", + "library", + "libraries", + "item_type", + ]; + for (const type of deletionOrder) { - if (!objects.hasOwnProperty(type)) { - continue; - } - if (Array.isArray(objects[type]) && objects[type].length == 0) { - // Empty array - continue; - } - switch (type) { - case "biblio": - await query({ - sql: "DELETE FROM biblio WHERE biblionumber=?", - values: [objects[type].biblio_id], - }); - break; - case "items": - let item_ids = objects[type].map(i => i.item_id); - await query({ - sql: `DELETE FROM items WHERE itemnumber IN (${item_ids.map(() => "?").join(",")})`, - values: item_ids, - }); - break; - case "item": - await query({ - sql: "DELETE FROM items WHERE itemnumber = ?", - values: [objects[type].item_id], - }); - break; - case "library": - await query({ - sql: "DELETE FROM branches WHERE branchcode = ?", - values: [objects[type].library_id], - }); - break; - case "item_type": - await query({ - sql: "DELETE FROM itemtypes WHERE itemtype = ?", - values: [objects[type].item_type_id], - }); - break; + for (const objects of allObjects) { + if (!objects.hasOwnProperty(type)) { + continue; + } + if (Array.isArray(objects[type]) && objects[type].length == 0) { + // Empty array + continue; + } + switch (type) { + case "biblio": + await query({ + sql: "DELETE FROM biblio WHERE biblionumber=?", + values: [objects[type].biblio_id], + }); + break; + case "items": + let item_ids = objects[type].map(i => i.item_id); + await query({ + sql: `DELETE FROM items WHERE itemnumber IN (${item_ids.map(() => "?").join(",")})`, + values: item_ids, + }); + break; + case "item": + await query({ + sql: "DELETE FROM items WHERE itemnumber = ?", + values: [objects[type].item_id], + }); + break; + case "library": + await query({ + sql: "DELETE FROM branches WHERE branchcode = ?", + values: [objects[type].library_id], + }); + break; + case "libraries": + let library_ids = objects[type].map(i => i.library_id); + await query({ + sql: `DELETE FROM branches WHERE branchcode IN (${library_ids.map(() => "?").join(",")})`, + values: library_ids, + }); + break; + case "hold": + await query({ + sql: "DELETE FROM reserves WHERE reserve_id = ?", + values: [objects[type].hold_id], + }); + break; + case "item_type": + await query({ + sql: "DELETE FROM itemtypes WHERE itemtype = ?", + values: [objects[type].item_type_id], + }); + break; + case "patron": + await query({ + sql: "DELETE FROM borrowers WHERE borrowernumber = ?", + values: [objects[type].patron_id], + }); + break; + default: + console.log( + `Not implemented yet: cannot deleted object '${type}'` + ); + } } } return true; @@ -199,7 +305,7 @@ const insertLibrary = async (library, baseUrl, authHeader) => { }); }; -const insertObject = async (type, object, baseUrl, authHeader) => { +const insertObject = async ({ type, object, baseUrl, authHeader }) => { if (type == "patron") { await query({ sql: "SELECT COUNT(*) AS count FROM branches WHERE branchcode = ?", @@ -274,6 +380,41 @@ const insertObject = async (type, object, baseUrl, authHeader) => { }); }) .then(item_types => item_types[0]); + } else if (type == "hold") { + const { + hold_id, + deleted_biblio_id, + item_group_id, + desk_id, + cancellation_date, + cancellation_reason, + notes, + priority, + status, + timestamp, + waiting_date, + expiration_date, + lowest_priority, + suspended, + suspended_until, + non_priority, + item_type, + item_level, + cancellation_requested, + biblio, + deleted_biblio, + item, + pickup_library, + hold_date, + ...hold + } = object; + + return apiPost({ + endpoint: `/api/v1/holds`, + body: hold, + baseUrl, + authHeader, + }); } else { return false; } @@ -283,6 +424,7 @@ const insertObject = async (type, object, baseUrl, authHeader) => { module.exports = { insertSampleBiblio, + insertSampleHold, insertObject, deleteSampleObjects, }; -- 2.34.1