diff --git a/t/cypress/integration/KohaTable/Holdings_spec.ts b/t/cypress/integration/KohaTable/Holdings_spec.ts index 380ac9d2d8..32fd124f74 100644 --- a/t/cypress/integration/KohaTable/Holdings_spec.ts +++ b/t/cypress/integration/KohaTable/Holdings_spec.ts @@ -1,5 +1,5 @@ const RESTdefaultPageSize = "20"; // FIXME Mock this -const baseTotalCount = "42"; +const baseTotalCount = "21"; describe("catalogue/detail/holdings_table with items", () => { const table_id = "holdings_table"; @@ -10,108 +10,21 @@ describe("catalogue/detail/holdings_table with items", () => { win.localStorage.clear(); }); - // FIXME All the following code should not be reused as it - // It must be moved to a Cypress command or task "buildSampleBiblio" or even "insertSampleBiblio" - let generated_objects = {}; - const objects = [{ object: "library" }, { object: "item_type" }]; - cy.wrap(Promise.resolve()) - .then(() => { - return objects.reduce((chain, { object }) => { - return chain.then(() => { - return cy - .task("buildSampleObject", { object }) - .then(attributes => { - generated_objects[object] = attributes; - }); - }); - }, Promise.resolve()); - }) - .then(() => { - const library = generated_objects["library"]; - const item_type = generated_objects["item_type"]; - const queries = [ - { - query: "INSERT INTO branches(branchcode, branchname) VALUES (?, ?)", - values: [library.library_id, library.name], - }, - { - query: "INSERT INTO itemtypes(itemtype, description) VALUES (?, ?)", - values: [item_type.item_type_id, item_type.description], - }, - ]; - cy.wrap(Promise.resolve()) - .then(() => { - return queries.reduce((chain, { query, values }) => { - return chain.then(() => - cy.task("query", { sql: query, values }) - ); - }, Promise.resolve()); - }) - .then(() => { - let biblio = { - leader: " nam a22 7a 4500", - fields: [ - { "005": "20250120101920.0" }, - { - "245": { - ind1: "", - ind2: "", - subfields: [{ a: "Some boring read" }], - }, - }, - { - "100": { - ind1: "", - ind2: "", - subfields: [ - { c: "Some boring author" }, - ], - }, - }, - { - "942": { - ind1: "", - ind2: "", - subfields: [ - { c: item_type.item_type_id }, - ], - }, - }, - ], - }; - cy.request({ - method: "POST", - url: "/api/v1/biblios", - headers: { - "Content-Type": "application/marc-in-json", - "x-confirm-not-duplicate": 1, - }, - body: biblio, - }).then(response => { - const biblio_id = response.body.id; - cy.wrap(biblio_id).as("biblio_id"); - cy.request({ - method: "POST", - url: `/api/v1/biblios/${biblio_id}/items`, - headers: { - "Content-Type": "application/json", - }, - body: { - home_library_id: library.library_id, - holding_library_id: library.library_id, - }, - }); - }); - }); - }); cy.task("query", { sql: "SELECT value FROM systempreferences WHERE variable='AlwaysShowHoldingsTableFilters'", }).then(value => { cy.wrap(value).as("syspref_AlwaysShowHoldingsTableFilters"); }); + + cy.task("insertSampleBiblio", { item_count: baseTotalCount }).then( + objects => { + cy.wrap(objects).as("objects"); + } + ); }); afterEach(function () { + cy.task("deleteSampleObjects", this.objects); cy.set_syspref( "AlwaysShowHoldingsTableFilters", this.syspref_AlwaysShowHoldingsTableFilters @@ -119,33 +32,9 @@ describe("catalogue/detail/holdings_table with items", () => { }); it("Correctly init the table", function () { - // Do not use `() => {` or this.biblio_id won't be retrieved - const biblio_id = this.biblio_id; - cy.task("buildSampleObjects", { - object: "item", - count: RESTdefaultPageSize, - values: { - biblio_id, - checkout: null, - transfer: null, - lost_status: 0, - withdrawn: 0, - damaged_status: 0, - not_for_loan_status: 0, - course_item: null, - cover_image_ids: [], - _status: ["available"], - }, - }).then(items => { - cy.intercept("get", `/api/v1/biblios/${biblio_id}/items*`, { - statuscode: 200, - body: items, - headers: { - "X-Base-Total-Count": baseTotalCount, - "X-Total-Count": baseTotalCount, - }, - }); - + // Do not use `() => {` or this.objets won't be retrieved + const biblio_id = this.objects.biblio.biblio_id; + cy.set_syspref("AlwaysShowHoldingsTableFilters", 1).then(() => { cy.visit( "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + biblio_id ); @@ -162,248 +51,209 @@ describe("catalogue/detail/holdings_table with items", () => { }); it("Show filters", function () { - // Do not use `() => {` or this.biblio_id won't be retrieved - const biblio_id = this.biblio_id; - cy.task("buildSampleObjects", { - object: "item", - count: RESTdefaultPageSize, - values: { - biblio_id, - checkout: null, - transfer: null, - lost_status: 0, - withdrawn: 0, - damaged_status: 0, - not_for_loan_status: 0, - course_item: null, - cover_image_ids: [], - _status: ["available"], - }, - }).then(items => { - cy.intercept("get", `/api/v1/biblios/${biblio_id}/items*`, { - statuscode: 200, - body: items, - headers: { - "X-Base-Total-Count": baseTotalCount, - "X-Total-Count": baseTotalCount, + // Do not use `() => {` or this.objects won't be retrieved + const biblio_id = this.objects.biblio.biblio_id; + + cy.set_syspref("AlwaysShowHoldingsTableFilters", 0).then(() => { + cy.visit( + "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + biblio_id + ); + + // Hide the 'URL' column + cy.mock_table_settings( + { + columns: { uri: { is_hidden: 1 } }, }, + "items_table_settings.holdings" + ); + + cy.get("@columns").then(columns => { + cy.get(`#${table_id}_wrapper tbody tr`).should( + "have.length", + RESTdefaultPageSize + ); + + // Filters are not displayed + cy.get(`#${table_id} thead tr`).should("have.length", 1); + + cy.get(`#${table_id} th`).contains("Status"); + cy.get(`#${table_id} th`).contains("URL").should("not.exist"); + cy.get(`#${table_id} th`) + .contains("Course reserves") + .should("not.exist"); + + cy.get(`.${table_id}_table_controls .show_filters`).click(); + cy.get(`#${table_id}_wrapper .dt-info`).contains( + `Showing 1 to ${RESTdefaultPageSize} of ${baseTotalCount} entries` + ); + // Filters are displayed + cy.get(`#${table_id} thead tr`).should("have.length", 2); + + cy.get(`#${table_id} th`).contains("Status"); + cy.get(`#${table_id} th`).contains("URL").should("not.exist"); + cy.get(`#${table_id} th`) + .contains("Course reserves") + .should("not.exist"); }); + }); - cy.set_syspref("AlwaysShowHoldingsTableFilters", 0).then(() => { - cy.visit( - "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + - biblio_id + cy.set_syspref("AlwaysShowHoldingsTableFilters", 1).then(() => { + cy.visit( + "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + biblio_id + ); + + // Hide the 'URL' column + cy.mock_table_settings( + { + columns: { uri: { is_hidden: 1 } }, + }, + "items_table_settings.holdings" + ); + + cy.get("@columns").then(columns => { + cy.get(`#${table_id}_wrapper tbody tr`).should( + "have.length", + RESTdefaultPageSize ); - // Hide the 'URL' column - cy.mock_table_settings( - { - columns: { uri: { is_hidden: 1 } }, - }, - "items_table_settings.holdings" - ); + // Filters are displayed + cy.get(`#${table_id} thead tr`).should("have.length", 2); - cy.get("@columns").then(columns => { - cy.get(`#${table_id}_wrapper tbody tr`).should( - "have.length", - RESTdefaultPageSize - ); + cy.get(`.${table_id}_table_controls .hide_filters`).click(); - // Filters are not displayed - cy.get(`#${table_id} thead tr`).should("have.length", 1); - - cy.get(`#${table_id} th`).contains("Status"); - cy.get(`#${table_id} th`) - .contains("URL") - .should("not.exist"); - cy.get(`#${table_id} th`) - .contains("Course reserves") - .should("not.exist"); - - cy.get(`.${table_id}_table_controls .show_filters`).click(); - cy.get(`#${table_id}_wrapper .dt-info`).contains( - `Showing 1 to ${RESTdefaultPageSize} of ${baseTotalCount} entries` - ); - // Filters are displayed - cy.get(`#${table_id} thead tr`).should("have.length", 2); - - cy.get(`#${table_id} th`).contains("Status"); - cy.get(`#${table_id} th`) - .contains("URL") - .should("not.exist"); - cy.get(`#${table_id} th`) - .contains("Course reserves") - .should("not.exist"); - }); - }); - - cy.set_syspref("AlwaysShowHoldingsTableFilters", 1).then(() => { - cy.visit( - "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + - biblio_id - ); - - // Hide the 'URL' column - cy.mock_table_settings( - { - columns: { uri: { is_hidden: 1 } }, - }, - "items_table_settings.holdings" - ); - - cy.get("@columns").then(columns => { - cy.get(`#${table_id}_wrapper tbody tr`).should( - "have.length", - RESTdefaultPageSize - ); - - // Filters are displayed - cy.get(`#${table_id} thead tr`).should("have.length", 2); - - cy.get(`.${table_id}_table_controls .hide_filters`).click(); - - // Filters are not displayed - cy.get(`#${table_id} thead tr`).should("have.length", 1); - }); + // Filters are not displayed + cy.get(`#${table_id} thead tr`).should("have.length", 1); }); }); }); it("Filters by code and description", function () { - // Do not use `() => {` or this.biblio_id won't be retrieved - const biblio_id = this.biblio_id; - cy.task("buildSampleObjects", { - object: "item", - count: RESTdefaultPageSize, - values: { - biblio_id, - checkout: null, - transfer: null, - lost_status: 0, - withdrawn: 0, - damaged_status: 0, - not_for_loan_status: 0, - course_item: null, - cover_image_ids: [], - _status: ["available"], - }, - }).then(items => { - cy.intercept("get", `/api/v1/biblios/${biblio_id}/items*`, { - statuscode: 200, - body: items, - headers: { - "X-Base-Total-Count": baseTotalCount, - "X-Total-Count": baseTotalCount, - }, - }).as("searchItems"); + // Do not use `() => {` or this.objects won't be retrieved + const biblio_id = this.objects.biblio.biblio_id; - cy.visit( - "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + biblio_id - ); + cy.intercept("get", `/api/v1/biblios/${biblio_id}/items*`).as( + "searchItems" + ); - cy.window().then(win => { - win.coded_values.library = new Map( - items.map(i => [ - i.home_library.name, - i.home_library.library_id, - ]) - ); - win.coded_values.item_type = new Map( - items.map(i => [ - i.item_type.description, - i.item_type.item_type_id, - ]) - ); - }); - cy.wait("@searchItems"); + cy.visit("/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + biblio_id); - let library_id = items[0].home_library.library_id; - let library_name = items[0].home_library.name; - cy.get(`#${table_id}_wrapper input.dt-input`).type(library_id); + cy.wait("@searchItems"); - cy.wait("@searchItems").then(interception => { - const q = interception.request.query.q; - expect(q).to.match( - new RegExp( - `"me.home_library_id":{"like":"%${library_id}%"}` - ) - ); - }); + cy.task("query", { + sql: "SELECT homebranch FROM items WHERE biblionumber=? LIMIT 1", + values: [biblio_id], + }).then(result => { + let library_id = result[0].homebranch; + cy.task("query", { + sql: "SELECT branchname FROM branches WHERE branchcode=?", + values: [library_id], + }).then(result => { + let library_name = result[0].branchname; + cy.get(`#${table_id}_wrapper input.dt-input`).type(library_id); - cy.get(`#${table_id}_wrapper input.dt-input`).clear(); - cy.wait("@searchItems"); - cy.get(`#${table_id}_wrapper input.dt-input`).type(library_name); - - cy.wait("@searchItems").then(interception => { - const q = interception.request.query.q; - expect(q).to.match( - new RegExp(`"me.home_library_id":\\["${library_id}"\\]`) - ); - }); - - let item_type_id = items[0].item_type.item_type_id; - let item_type_description = items[0].item_type.description; - cy.get(`#${table_id}_wrapper input.dt-input`).clear(); - cy.wait("@searchItems"); - cy.get(`#${table_id}_wrapper input.dt-input`).type(item_type_id); - - cy.wait("@searchItems").then(interception => { - const q = interception.request.query.q; - expect(q).to.match( - new RegExp(`"me.item_type_id":{"like":"%${item_type_id}%"}`) - ); - }); - - cy.get(`#${table_id}_wrapper input.dt-input`).clear(); - cy.wait("@searchItems"); - cy.get(`#${table_id}_wrapper input.dt-input`).type( - item_type_description - ); - - cy.wait("@searchItems").then(interception => { - const q = interception.request.query.q; - expect(q).to.match( - new RegExp(`"me.item_type_id":\\["${item_type_id}"\\]`) - ); - }); - - cy.viewport(2999, 2999); - cy.get(`#${table_id}_wrapper input.dt-input`).clear(); - cy.wait("@searchItems"); - // Show filters if not there already - cy.get(`.${table_id}_table_controls .show_filters`) - .then(link => { - if (link.is(":visible")) { - cy.wrap(link).click(); - cy.wait("@searchItems"); - } - }) - .then(() => { - // Select first (non-empty) option - cy.get( - `#${table_id}_wrapper th#holdings_itype select` - ).then(select => { - const raw_value = select.find("option").eq(1).val(); - expect(raw_value).to.match(/^\^/); - expect(raw_value).to.match(/\$$/); - item_type_id = raw_value.replace(/^\^|\$$/g, ""); // Remove ^ and $ - }); - cy.get( - `#${table_id}_wrapper th#holdings_itype select option` - ) - .eq(1) - .then(o => { - cy.get( - `#${table_id}_wrapper th#holdings_itype select` - ).select(o.val(), { force: true }); - }); - cy.wait("@searchItems").then(interception => { - const q = interception.request.query.q; - expect(q).to.match( - new RegExp(`{"me.item_type_id":"${item_type_id}"}`) - ); - }); + cy.wait("@searchItems").then(interception => { + const q = interception.request.query.q; + expect(q).to.match( + new RegExp( + `"me.home_library_id":{"like":"%${library_id}%"}` + ) + ); }); + + cy.get(`#${table_id}_wrapper input.dt-input`).clear(); + cy.wait("@searchItems"); + cy.get(`#${table_id}_wrapper input.dt-input`).type( + library_name + ); + + cy.wait("@searchItems").then(interception => { + const q = interception.request.query.q; + expect(q).to.match( + new RegExp(`"me.home_library_id":\\["${library_id}"\\]`) + ); + }); + }); + }); + + cy.task("query", { + sql: "SELECT itype FROM items WHERE biblionumber=? LIMIT 1", + values: [biblio_id], + }).then(result => { + let item_type_id = result[0].itype; + cy.task("query", { + sql: "SELECT description FROM itemtypes WHERE itemtype=?", + values: [item_type_id], + }).then(result => { + let item_type_description = result[0].description; + + cy.get(`#${table_id}_wrapper input.dt-input`).clear(); + cy.wait("@searchItems"); + cy.get(`#${table_id}_wrapper input.dt-input`).type( + item_type_id + ); + + cy.wait("@searchItems").then(interception => { + const q = interception.request.query.q; + expect(q).to.match( + new RegExp( + `"me.item_type_id":{"like":"%${item_type_id}%"}` + ) + ); + }); + + cy.get(`#${table_id}_wrapper input.dt-input`).clear(); + cy.wait("@searchItems"); + cy.get(`#${table_id}_wrapper input.dt-input`).type( + item_type_description + ); + + cy.wait("@searchItems").then(interception => { + const q = interception.request.query.q; + expect(q).to.match( + new RegExp(`"me.item_type_id":\\["${item_type_id}"\\]`) + ); + }); + + cy.viewport(2999, 2999); + cy.get(`#${table_id}_wrapper input.dt-input`).clear(); + cy.wait("@searchItems"); + // Show filters if not there already + cy.get(`.${table_id}_table_controls .show_filters`) + .then(link => { + if (link.is(":visible")) { + cy.wrap(link).click(); + cy.wait("@searchItems"); + } + }) + .then(() => { + // Select first (non-empty) option + cy.get( + `#${table_id}_wrapper th#holdings_itype select` + ).then(select => { + const raw_value = select.find("option").eq(1).val(); + expect(raw_value).to.match(/^\^/); + expect(raw_value).to.match(/\$$/); + item_type_id = raw_value.replace(/^\^|\$$/g, ""); // Remove ^ and $ + }); + cy.get( + `#${table_id}_wrapper th#holdings_itype select option` + ) + .eq(1) + .then(o => { + cy.get( + `#${table_id}_wrapper th#holdings_itype select` + ).select(o.val(), { force: true }); + }); + cy.wait("@searchItems").then(interception => { + const q = interception.request.query.q; + expect(q).to.match( + new RegExp( + `{"me.item_type_id":"${item_type_id}"}` + ) + ); + }); + }); + }); }); }); }); @@ -417,92 +267,19 @@ describe("catalogue/detail/holdings_table without items", () => { win.localStorage.clear(); }); - // FIXME All the following code should not be reused as it - // It must be moved to a Cypress command or task "buildSampleBiblio" or even "insertSampleBiblio" - let generated_objects = {}; - const objects = [{ object: "library" }, { object: "item_type" }]; - cy.wrap(Promise.resolve()) - .then(() => { - return objects.reduce((chain, { object }) => { - return chain.then(() => { - return cy - .task("buildSampleObject", { object }) - .then(attributes => { - generated_objects[object] = attributes; - }); - }); - }, Promise.resolve()); - }) - .then(() => { - const item_type = generated_objects["item_type"]; - const queries = [ - { - sql: "INSERT INTO itemtypes(itemtype, description) VALUES (?, ?)", - values: [item_type.item_type_id, item_type.description], - }, - ]; - cy.wrap(Promise.resolve()) - .then(() => { - return queries.reduce((chain, { sql, values }) => { - return chain.then(() => - cy.task("query", { sql, values }) - ); - }, Promise.resolve()); - }) - .then(() => { - let biblio = { - leader: " nam a22 7a 4500", - fields: [ - { "005": "20250120101920.0" }, - { - "245": { - ind1: "", - ind2: "", - subfields: [{ a: "Some boring read" }], - }, - }, - { - "100": { - ind1: "", - ind2: "", - subfields: [ - { c: "Some boring author" }, - ], - }, - }, - { - "942": { - ind1: "", - ind2: "", - subfields: [ - { c: item_type.item_type_id }, - ], - }, - }, - ], - }; - cy.request({ - method: "POST", - url: "/api/v1/biblios", - headers: { - "Content-Type": "application/marc-in-json", - "x-confirm-not-duplicate": 1, - }, - body: biblio, - }).then(response => { - const biblio_id = response.body.id; - cy.wrap(biblio_id).as("biblio_id"); - }); - }); - }); cy.task("query", { sql: "SELECT value FROM systempreferences WHERE variable='AlwaysShowHoldingsTableFilters'", }).then(value => { cy.wrap(value).as("syspref_AlwaysShowHoldingsTableFilters"); }); + + cy.task("insertSampleBiblio", { item_count: 0 }).then(objects => { + cy.wrap(objects).as("objects"); + }); }); afterEach(function () { + cy.task("deleteSampleObjects", this.objects); cy.set_syspref( "AlwaysShowHoldingsTableFilters", this.syspref_AlwaysShowHoldingsTableFilters @@ -510,8 +287,8 @@ describe("catalogue/detail/holdings_table without items", () => { }); it("Do not display the table", function () { - // Do not use `() => {` or this.biblio_id won't be retrieved - const biblio_id = this.biblio_id; + // Do not use `() => {` or this.objects won't be retrieved + const biblio_id = this.objects.biblio.biblio_id; cy.visit("/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + biblio_id); diff --git a/t/cypress/integration/t/insertData.ts b/t/cypress/integration/t/insertData.ts new file mode 100644 index 0000000000..ff163c606a --- /dev/null +++ b/t/cypress/integration/t/insertData.ts @@ -0,0 +1,76 @@ +const { query } = require("./../../plugins/db.js"); + +describe("insertSampleBiblio", () => { + it("should generate library and item type", () => { + cy.task("insertSampleBiblio", { item_count: 3 }).then(objects => { + const biblio_id = objects.biblio.biblio_id; + + expect(typeof biblio_id).to.be.equal("number"); + + cy.task("query", { + sql: "SELECT COUNT(*) as count FROM biblio WHERE biblionumber=?", + values: [biblio_id], + }).then(result => { + expect(result[0].count).to.be.equal(1); + }); + + cy.task("query", { + sql: "SELECT COUNT(*) as count FROM items WHERE biblionumber=?", + values: [biblio_id], + }).then(result => { + expect(result[0].count).to.be.equal(3); + }); + + cy.task("query", { + sql: "SELECT DISTINCT(itype) as count FROM items WHERE biblionumber=?", + values: [biblio_id], + }).then(result => { + expect(result.length).to.be.equal(1); + }); + + cy.task("query", { + sql: "SELECT COUNT(*) as count FROM branches WHERE branchcode=?", + values: [objects.library.library_id], + }).then(result => { + expect(result[0].count).to.be.equal(1); + }); + + cy.task("query", { + sql: "SELECT COUNT(*) as count FROM itemtypes WHERE itemtype=?", + values: [objects.item_type.item_type_id], + }).then(result => { + expect(result[0].count).to.be.equal(1); + }); + + cy.task("deleteSampleObjects", objects); + + cy.task("query", { + sql: "SELECT COUNT(*) as count FROM biblio WHERE biblionumber=?", + values: [biblio_id], + }).then(result => { + expect(result[0].count).to.be.equal(0); + }); + + cy.task("query", { + sql: "SELECT COUNT(*) as count FROM items WHERE biblionumber=?", + values: [biblio_id], + }).then(result => { + expect(result[0].count).to.be.equal(0); + }); + + cy.task("query", { + sql: "SELECT COUNT(*) as count FROM branches WHERE branchcode=?", + values: [objects.library.library_id], + }).then(result => { + expect(result[0].count).to.be.equal(0); + }); + + cy.task("query", { + sql: "SELECT COUNT(*) as count FROM itemtypes WHERE itemtype=?", + values: [objects.item_type.item_type_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 285351d906..1045e9b691 100644 --- a/t/cypress/plugins/index.js +++ b/t/cypress/plugins/index.js @@ -2,11 +2,18 @@ const { startDevServer } = require("@cypress/webpack-dev-server"); const { buildSampleObject, buildSampleObjects } = require("./mockData.js"); +const { + insertSampleBiblio, + insertObject, + deleteSampleObjects, +} = require("./insertData.js"); + const { query } = require("./db.js"); const { apiGet, apiPost, apiPut, apiDelete } = require("./api-client.js"); module.exports = (on, config) => { + const baseUrl = config.baseUrl; on("dev-server:start", options => startDevServer({ options, @@ -16,6 +23,13 @@ module.exports = (on, config) => { on("task", { buildSampleObject, buildSampleObjects, + insertSampleBiblio({ item_count }) { + return insertSampleBiblio(item_count, baseUrl); + }, + insertObject({ type, object }) { + return insertObject(type, object, baseUrl, authHeader); + }, + deleteSampleObjects, query, apiGet(args) { diff --git a/t/cypress/plugins/insertData.js b/t/cypress/plugins/insertData.js new file mode 100644 index 0000000000..a9d63de45f --- /dev/null +++ b/t/cypress/plugins/insertData.js @@ -0,0 +1,304 @@ +const { buildSampleObject, buildSampleObjects } = require("./mockData.js"); +const { query } = require("./db.js"); + +const { APIClient } = require("./dist/api-client.cjs.js"); +const { Buffer } = require("buffer"); + +const insertSampleBiblio = async (item_count, baseUrl) => { + let client = APIClient.default; + 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 library = await insertObject( + "library", + generated_objects["library"], + baseUrl, + authHeader + ); + const item_type = await insertObject( + "item_type", + generated_objects["item_type"], + baseUrl, + authHeader + ); + + let biblio = { + leader: " nam a22 7a 4500", + fields: [ + { "005": "20250120101920.0" }, + { + 245: { + ind1: "", + ind2: "", + subfields: [{ a: "Some boring read" }], + }, + }, + { + 100: { + ind1: "", + ind2: "", + subfields: [{ c: "Some boring author" }], + }, + }, + { + 942: { + ind1: "", + ind2: "", + subfields: [{ c: item_type.item_type_id }], + }, + }, + ], + }; + const credentials = Buffer.from("koha:koha").toString("base64"); + let result = await client.koha.post({ + endpoint: `${baseUrl}/api/v1/biblios`, + headers: { + "Content-Type": "application/marc-in-json", + "x-confirm-not-duplicate": 1, + Authorization: "Basic " + credentials, + }, + body: biblio, + }); + const biblio_id = result.id; + // We do not have a route to get a biblio as it is stored in DB + // We might need to refine that in the future + biblio = { + biblio_id, + }; + + let items = buildSampleObjects({ + object: "item", + count: item_count, + values: { + biblio_id, + lost_status: 0, + withdrawn: 0, + damaged_status: 0, + not_for_loan_status: 0, + restricted_status: 0, + new_status: null, + issues: 0, + item_type_id: item_type.item_type_id, + home_library_id: library.library_id, + holding_library_id: library.library_id, + }, + }); + items = items.map( + ({ + item_id, + checkout, + transfer, + lost_date, + withdrawn_date, + damaged_date, + course_item, + _strings, + biblio, + bundle_host, + item_group_item, + recall, + return_claim, + return_claims, + serial_item, + first_hold, + checkouts_count, + renewals_count, + holds_count, + bundle_items_lost_count, + analytics_count, + effective_not_for_loan_status, + effective_item_type_id, + home_library, + holding_library, + bundle_items_not_lost_count, + item_type, + _status, + effective_bookable, + in_bundle, + cover_image_ids, + localuse, + ...rest + }) => rest + ); + let createdItems = []; + for (const item of items) { + await client.koha + .post({ + endpoint: `${baseUrl}/api/v1/biblios/${biblio_id}/items`, + body: item, + headers: { + "Content-Type": "application/json", + Authorization: "Basic " + credentials, + }, + }) + .then(i => createdItems.push(i)); + } + return { biblio, items: createdItems, library, item_type }; +}; + +const deleteSampleObjects = async objects => { + const deletionOrder = ["items", "biblio", "library", "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; + } + } + return true; +}; + +const insertLibrary = async (library, baseUrl, authHeader) => { + const { + pickup_items, + smtp_server, + cash_registers, + desks, + library_hours, + needs_override, + ...new_library + } = library; + let client = APIClient.default; + return client.koha.post({ + endpoint: `${baseUrl}/api/v1/libraries`, + body: new_library, + headers: { + "Content-Type": "application/json", + Authorization: authHeader, + }, + }); +}; + +const insertObject = async (type, object, baseUrl, authHeader) => { + let client = APIClient.default; + if (type == "patron") { + await query({ + sql: "SELECT COUNT(*) AS count FROM branches WHERE branchcode = ?", + values: [object.library_id], + }).then(result => { + if (!result[0].count) { + insertLibrary(object.library, baseUrl, authHeader); + } + }); + await query({ + sql: "SELECT COUNT(*) AS count FROM categories WHERE categorycode= ?", + values: [object.category_id], + }).then(result => { + if (!result[0].count) { + query({ + sql: "INSERT INTO categories(categorycode, description) VALUES (?, ?)", + values: [ + object.category_id, + `description for ${object.category_id}`, + ], + }); + } + }); + const { + _strings, + anonymized, + restricted, + expired, + extended_attributes, + library, + checkouts_count, + overdues_count, + account_balance, + lang, + login_attempts, + sms_provider_id, + ...patron + } = object; + + return client.koha.post({ + endpoint: `${baseUrl}/api/v1/patrons`, + body: patron, + headers: { + "Content-Type": "application/json", + Authorization: authHeader, + }, + }); + } else if (type == "library") { + const keysToKeep = ["library_id", "name"]; + const library = Object.fromEntries( + Object.entries(object).filter(([key]) => keysToKeep.includes(key)) + ); + return client.koha.post({ + endpoint: `${baseUrl}/api/v1/libraries`, + headers: { + "Content-Type": "application/json", + Authorization: authHeader, + }, + body: library, + }); + } else if (type == "item_type") { + const keysToKeep = ["item_type_id", "description"]; + const item_type = Object.fromEntries( + Object.entries(object).filter(([key]) => keysToKeep.includes(key)) + ); + return query({ + sql: "INSERT INTO itemtypes(itemtype, description) VALUES (?, ?)", + values: [item_type.item_type_id, item_type.description], + }) + .then(result => { + // FIXME We need /item_types/:item_type_id + return client.koha.get({ + endpoint: `${baseUrl}/api/v1/item_types?q={"item_type_id":"${item_type.item_type_id}"}`, + headers: { + "Content-Type": "application/json", + Authorization: authHeader, + }, + }); + }) + .then(item_types => item_types[0]); + } else { + return false; + } + + return true; +}; + +module.exports = { + insertSampleBiblio, + insertObject, + deleteSampleObjects, +}; diff --git a/t/cypress/plugins/mockData.js b/t/cypress/plugins/mockData.js index c2025abeeb..5791c68270 100644 --- a/t/cypress/plugins/mockData.js +++ b/t/cypress/plugins/mockData.js @@ -1,19 +1,22 @@ const { faker } = require("@faker-js/faker"); const { readYamlFile } = require("./../plugins/readYamlFile.js"); +const { query } = require("./db.js"); const fs = require("fs"); +const generatedDataCache = new Set(); + const generateMockData = (type, properties) => { switch (type) { case "string": if (properties?.maxLength) { - return faker.string.alpha({ + return (value = faker.string.alpha({ length: { min: properties.minLength || 1, max: properties.maxLength, }, - }); + })); } - return faker.lorem.words(3); + return (value = faker.lorem.words(3)); case "integer": return faker.number.int(); case "boolean": @@ -56,18 +59,58 @@ const generateDataFromSchema = (properties, values = {}) => { data = buildSampleObject({ object: "library" }); fk_name = "library_id"; break; + case "pickup_library": + data = buildSampleObject({ object: "library" }); + fk_name = "pickup_library_id"; + break; + case "library": + data = buildSampleObject({ object: "library" }); + fk_name = "library_id"; + break; case "item_type": data = buildSampleObject({ object: "item_type" }); fk_name = "item_type_id"; break; + case "item": + data = buildSampleObject({ object: "item" }); + fk_name = "item_id"; + break; default: - data = generateMockData(type, value); + try { + data = generateMockData(type, value); + } catch (e) { + throw new Error( + `Failed to generate data for (${key}): ${e}` + ); + } } if (typeof data === "object") { ids[key] = data[fk_name]; } } else { - data = generateMockData(type, value); + try { + if (key.match(/_id$/)) { + let attempts = 0; + + do { + data = generateMockData(type, value); + attempts++; + if (attempts > 10) { + throw new Error( + "Could not generate unique string after 10 attempts" + ); + } + } while (generatedDataCache.has(data)); + + generatedDataCache.add(data); + } else { + data = generateMockData(type, value); + } + } catch (e) { + throw new Error( + `Failed to generate data for ${key} (${type}): ${e}` + ); + } } mockData[key] = data; } @@ -93,9 +136,15 @@ const buildSampleObjects = ({ object, values, count = 1 }) => { ); } const schema = readYamlFile(yamlPath); - return Array.from({ length: count }, () => - generateDataFromSchema(schema.properties, values) - ); + let generatedObject; + try { + generatedObject = Array.from({ length: count }, () => + generateDataFromSchema(schema.properties, values) + ); + } catch (e) { + throw new Error(`Failed to generate data for object '${object}': ${e}`); + } + return generatedObject; }; const buildSampleObject = ({ object, values = {} }) => {