Bug 33490: Make url consistent
[koha.git] / t / cypress / integration / ERM / Agreements_spec.ts
1 import { mount } from "@cypress/vue";
2 const dayjs = require("dayjs"); /* Cannot use our calendar JS code, it's in an include file (!)
3                                    Also note that moment.js is deprecated */
4
5 const dates = {
6     today_iso: dayjs().format("YYYY-MM-DD"),
7     today_us: dayjs().format("MM/DD/YYYY"),
8     tomorrow_iso: dayjs().add(1, "day").format("YYYY-MM-DD"),
9     tomorrow_us: dayjs().add(1, "day").format("MM/DD/YYYY"),
10 };
11 function get_agreement() {
12     return {
13         agreement_id: 1,
14         closure_reason: "",
15         description: "my first agreement",
16         is_perpetual: false,
17         license_info: "",
18         name: "agreement 1",
19         renewal_priority: "",
20         status: "active",
21         vendor_id: null,
22         periods: [
23             {
24                 started_on: dates["today_iso"],
25                 ended_on: dates["tomorrow_iso"],
26                 cancellation_deadline: null,
27                 notes: null,
28             },
29             {
30                 started_on: dates["today_iso"],
31                 ended_on: null,
32                 cancellation_deadline: dates["tomorrow_iso"],
33                 notes: "this is a note",
34             },
35         ],
36         user_roles: [],
37         agreement_licenses: [
38             {
39                 agreement_id: 1,
40                 agreement_license_id: 3,
41                 license: {
42                     description: "license description",
43                     license_id: 1,
44                     name: "license name",
45                     status: "expired",
46                     type: "alliance"
47                 },
48                 license_id:1,
49                 notes: "license notes",
50                 physical_location: "cupboard",
51                 status: "controlling",
52                 uri: "license uri"
53             },
54             {
55                 agreement_id: 1,
56                 agreement_license_id: 4,
57                 license: {
58                     description: "second license description",
59                     license_id: 2,
60                     name: "second license name",
61                     status: "expired",
62                     type: "alliance"
63                 },
64                 license_id:2,
65                 notes: "second license notes",
66                 physical_location: "cupboard",
67                 status: "future",
68                 uri: "license uri"
69             }
70         ],
71         agreement_relationships: [
72             {
73                 agreement_id: 1,
74                 notes: 'related agreement notes',
75                 related_agreement: {
76                     agreement_id: 2,
77                     description: "agreement description",
78                     name: "agreement name"
79                 },
80                 related_agreement_id: 2,
81                 relationship: "supersedes"
82             }
83         ],
84         agreement_packages: [],
85         documents: [
86             {
87                 agreement_id:1,
88                 file_description: "file description",
89                 file_name: "file.json",
90                 notes: "file notes",
91                 physical_location: "file physical location",
92                 uri: "file uri",
93                 uploaded_on: "2022-10-27T11:57:02+00:00"
94             }
95         ],
96     };
97 }
98
99 function get_licenses_to_relate() {
100     return [
101         {
102             license_id: 1,
103             description: "a license",
104             name: "first license name"
105         },
106         {
107             license_id: 2,
108             description: "a second license",
109             name: "second license name"
110         },
111         {
112             license_id: 3,
113             description: "a third license",
114             name: "third license name"
115         },
116     ]
117 }
118
119 describe("Agreement CRUD operations", () => {
120     beforeEach(() => {
121         cy.login();
122         cy.title().should("eq", "Koha staff interface");
123         cy.intercept("GET", "/cgi-bin/koha/svc/config/systempreferences/?pref=ERMModule", '{"value":"1"}');
124         cy.intercept("GET", "/cgi-bin/koha/svc/config/systempreferences/?pref=ERMProviders", '{"value":"local"}');
125     });
126
127     it("List agreements", () => {
128         // GET agreements returns 500
129         cy.intercept("GET", "/api/v1/erm/agreements*", {
130             statusCode: 500,
131             error: "Something went wrong",
132         });
133         cy.visit("/cgi-bin/koha/erm/erm.pl");
134         cy.get("#navmenulist").contains("Agreements").click();
135         cy.get("main div[class='dialog alert']").contains(
136             /Something went wrong/
137         );
138
139         // GET agreements returns empty list
140         cy.intercept("GET", "/api/v1/erm/agreements*", []);
141         cy.visit("/cgi-bin/koha/erm/agreements");
142         cy.get("#agreements_list").contains("There are no agreements defined");
143
144         // GET agreements returns something
145         let agreement = get_agreement();
146         let agreements = [agreement];
147
148         cy.intercept("GET", "/api/v1/erm/agreements*", {
149             statusCode: 200,
150             body: agreements,
151             headers: {
152                 "X-Base-Total-Count": "1",
153                 "X-Total-Count": "1",
154             },
155         });
156         cy.intercept("GET", "/api/v1/erm/agreements/*", agreement);
157         cy.visit("/cgi-bin/koha/erm/agreements");
158         cy.get("#agreements_list").contains("Showing 1 to 1 of 1 entries");
159         cy.get(".filters").find("label").should(($labels) => {
160               expect($labels).to.have.length(2)
161               expect($labels.eq(0)).to.contain('Filter by expired')
162               expect($labels.eq(1)).to.contain('Show mine only')
163         }); // Filter options appear
164
165         // Test filtering
166         cy.intercept("GET", "/api/v1/erm/agreements?max_expiration_date=*", []).as("getActiveAgreements");
167         cy.get("#expired_filter").check();
168         cy.get("#filter_table").click();
169         cy.wait('@getActiveAgreements')
170             .its('request.url')
171             .should('include', 'max_expiration_date='+dates["today_iso"]); // Defaults to today
172         cy.get("#max_expiration_date_filter").should("have.value", dates["today_iso"]); // Input box reflects default
173         cy.url().should('include', "/cgi-bin/koha/erm/agreements?by_expired=true&max_expiration_date="+dates["today_iso"]); // Browser url also updated
174
175         // Now test that the url for this particular state works
176         cy.visit("/cgi-bin/koha/erm/agreements?by_expired=true&max_expiration_date="+dates["today_iso"]);
177         cy.wait('@getActiveAgreements').its('request.url').should('include', 'max_expiration_date='+dates["today_iso"]);
178
179         // Now test with a user entered date
180         cy.get("#max_expiration_date_filter+input").click({ force: true });
181         cy.get(".flatpickr-calendar")
182             .eq(0)
183             .find("span.today")
184             .next("span")
185             .click(); // select tomorrow
186         cy.get("#filter_table").click();
187         cy.wait('@getActiveAgreements').its('request.url').should('include', 'max_expiration_date='+dates["tomorrow_iso"]);
188         cy.get("#max_expiration_date_filter").should("have.value", dates["tomorrow_iso"]);
189         // Assert that browser url changed again to reflect the user entered date
190         cy.url().should('include', "/cgi-bin/koha/erm/agreements?by_expired=true&max_expiration_date="+dates["tomorrow_iso"]);
191
192         // Now test that the url for the updated state works
193         cy.visit("/cgi-bin/koha/erm/agreements?by_expired=true&max_expiration_date="+dates["tomorrow_iso"]);
194         cy.wait('@getActiveAgreements').its('request.url').should('include', 'max_expiration_date='+dates["tomorrow_iso"]);
195
196         // Test filter button with show mine_only ticked
197     });
198
199     it("Add agreement", () => {
200         // No agreement, no license yet
201         cy.intercept("GET", "/api/v1/erm/agreements*", {
202             statusCode: 200,
203             body: [],
204         });
205         cy.intercept("GET", "/api/v1/erm/licenses*", {
206             statusCode: 200,
207             body: [],
208         });
209
210         // Click the button in the toolbar
211         cy.visit("/cgi-bin/koha/erm/agreements");
212         cy.contains("New agreement").click();
213         cy.get("#agreements_add h2").contains("New agreement");
214
215         // Fill in the form for normal attributes
216         let agreement = get_agreement();
217
218         cy.get("#agreements_add").contains("Submit").click();
219         cy.get("input:invalid,textarea:invalid,select:invalid").should(
220             "have.length",
221             2
222         );
223         cy.get("#agreement_name").type(agreement.name);
224         cy.get("#agreement_description").type(agreement.description);
225         cy.get("#agreements_add").contains("Submit").click();
226         cy.get("input:invalid,textarea:invalid,select:invalid").should(
227             "have.length",
228             1
229         ); // name, description, status
230         cy.get("#agreement_status .vs__search").type(
231             agreement.status + "{enter}",
232             { force: true }
233         );
234
235         cy.contains("Add new period").click();
236         cy.get("#agreements_add").contains("Submit").click();
237         cy.get("input:invalid,textarea:invalid,select:invalid").should(
238             "have.length",
239             1
240         ); // Start date
241
242         // Add new periods
243         cy.contains("Add new period").click();
244         cy.contains("Add new period").click();
245         cy.get("#agreement_periods > fieldset").should("have.length", 3);
246
247         cy.get("#agreement_period_1").contains("Remove this period").click();
248
249         cy.get("#agreement_periods > fieldset").should("have.length", 2);
250         cy.get("#agreement_period_0");
251         cy.get("#agreement_period_1");
252
253         // Selecting the flatpickr values is a bit tedious here...
254         // We have 3 date inputs per period
255         cy.get("#ended_on_0+input").click();
256         // Second flatpickr => ended_on for the first period
257         cy.get(".flatpickr-calendar")
258             .eq(1)
259             .find("span.today")
260             .click({ force: true }); // select today. No idea why we should force, but there is a random failure otherwise
261
262         cy.get("#started_on_0+input").click();
263         cy.get(".flatpickr-calendar")
264             .eq(0)
265             .find("span.today")
266             .next("span")
267             .click(); // select tomorrow
268
269         cy.get("#ended_on_0").should("have.value", ""); // Has been reset correctly
270
271         cy.get("#started_on_0+input").click();
272         cy.get(".flatpickr-calendar").eq(0).find("span.today").click(); // select today
273         cy.get("#ended_on_0+input").click({ force: true }); // No idea why we should force, but there is a random failure otherwise
274         cy.get(".flatpickr-calendar")
275             .eq(1)
276             .find("span.today")
277             .next("span")
278             .click(); // select tomorrow
279
280         // Second period
281         cy.get("#started_on_1+input").click({ force: true });
282         cy.get(".flatpickr-calendar").eq(3).find("span.today").click(); // select today
283         cy.get("#cancellation_deadline_1+input").click();
284         cy.get(".flatpickr-calendar")
285             .eq(5)
286             .find("span.today")
287             .next("span")
288             .click(); // select tomorrow
289         cy.get("#notes_1").type("this is a note");
290
291         // TODO Add a new user
292         // How to test a new window with cypresS?
293         //cy.contains("Add new user").click();
294         //cy.contains("Select user").click();
295
296         cy.get("#agreement_licenses").contains(
297             "There are no licenses created yet"
298         );
299         cy.get("#agreement_relationships").contains(
300             "There are no other agreements created yet"
301         );
302
303         // Add new document
304         cy.get("#documents").contains("Add new document").click();
305         cy.get("#document_0 input[id=file_0]").click();
306         cy.get('#document_0 input[id=file_0]').selectFile('t/cypress/fixtures/file.json');
307         cy.get("#document_0 .file_information span").contains("file.json");
308         cy.get('#document_0 input[id=file_description_0]').type('file description');
309         cy.get('#document_0 input[id=physical_location_0]').type('file physical location');
310         cy.get('#document_0 input[id=uri_0]').type('file URI');
311         cy.get('#document_0 input[id=notes_0]').type('file notes');
312
313         // Submit the form, get 500
314         cy.intercept("POST", "/api/v1/erm/agreements", {
315             statusCode: 500,
316             error: "Something went wrong",
317         });
318         cy.get("#agreements_add").contains("Submit").click();
319         cy.get("main div[class='dialog alert']").contains(
320             "Something went wrong: Error: Internal Server Error"
321         );
322
323         // Submit the form, success!
324         cy.intercept("POST", "/api/v1/erm/agreements", {
325             statusCode: 201,
326             body: agreement,
327         });
328         cy.get("#agreements_add").contains("Submit").click();
329         cy.get("main div[class='dialog message']").contains(
330             "Agreement created"
331         );
332
333         cy.intercept("GET", "/api/v1/erm/agreements*", {
334             statusCode: 200,
335             body: [{ agreement_id: 1, description: "an existing agreement" }],
336         });
337
338         // Add new license
339         let licenses_to_relate = get_licenses_to_relate();
340         let related_license = agreement.agreement_licenses[0];
341         cy.intercept("GET", "/api/v1/erm/licenses*", {
342             statusCode: 200,
343             body: licenses_to_relate,
344         });
345         cy.visit("/cgi-bin/koha/erm/agreements/add");
346         cy.get("#agreement_licenses").contains("Add new license").click();
347         cy.get("#agreement_license_0").contains("Agreement license 1");
348         cy.get("#agreement_license_0 #license_id_0 .vs__search").type(
349             related_license.license.name
350         );
351         cy.get("#agreement_license_0 #license_id_0 .vs__dropdown-menu li").eq(0).click( { force: true } ); //click first license suggestion
352         cy.get("#agreement_license_0 #license_status_0 .vs__search").type(
353             related_license.status + "{enter}",
354             { force: true }
355         );
356         cy.get("#agreement_license_0 #license_location_0 .vs__search").type(
357             related_license.physical_location + "{enter}",
358             { force: true }
359         );
360         cy.get("#agreement_license_0 #license_notes_0").type(related_license.notes);
361         cy.get("#agreement_license_0 #license_uri_0").type(related_license.uri);
362
363         // Add new related agreement
364         let related_agreement = agreement.agreement_relationships[0];
365         cy.intercept("GET", "/api/v1/erm/agreements*", {
366             statusCode: 200,
367             body: cy.get_agreements_to_relate(),
368         });
369         cy.visit("/cgi-bin/koha/erm/agreements/add");
370         cy.get("#agreement_relationships").contains("Add new related agreement").click();
371         cy.get("#related_agreement_0").contains("Related agreement 1");
372         cy.get("#related_agreement_0 #related_agreement_id_0 .vs__search").type(
373             related_agreement.related_agreement.name
374         );
375         cy.get("#related_agreement_0 #related_agreement_id_0 .vs__dropdown-menu li").eq(0).click( { force: true } ); //click first agreement suggestion
376         cy.get("#related_agreement_0 #related_agreement_notes_0").type(related_agreement.notes);
377         cy.get("#related_agreement_0 #related_agreement_relationship_0 .vs__search").type(
378             related_agreement.relationship + "{enter}",
379             { force: true }
380         );
381     });
382
383     it("Edit agreement", () => {
384         let licenses_to_relate = get_licenses_to_relate();
385         let agreement = get_agreement();
386         let agreements = [agreement];
387
388         // Intercept initial /agreements request once
389         cy.intercept(
390             {
391                 method: "GET",
392                 url: "/api/v1/erm/agreements*",
393                 times: 1
394             },
395             {
396                 body: agreements
397             }
398         );
399
400         // Intercept follow-up 'search' request after entering /agreements
401         cy.intercept("GET", "/api/v1/erm/agreements?_page*", {
402             statusCode: 200,
403             body: agreements,
404             headers: {
405                 "X-Base-Total-Count": "1",
406                 "X-Total-Count": "1",
407             },
408         }).as("get-single-agreement-search-result");
409         cy.visit("/cgi-bin/koha/erm/agreements");
410         cy.wait("@get-single-agreement-search-result");
411
412         // Intercept request after edit click
413         cy.intercept("GET", "/api/v1/erm/agreements/*", agreement).as(
414             "get-agreement"
415         );
416         // Intercept related licenses request after entering agreement edit
417         cy.intercept("GET", "/api/v1/erm/licenses*", {
418             statusCode: 200,
419             body: licenses_to_relate,
420         }).as("get-related-licenses");
421         // Intercept related agreements request after entering agreement edit
422         cy.intercept("GET", "/api/v1/erm/agreements*", {
423             statusCode: 200,
424             body: cy.get_agreements_to_relate(),
425         }).as("get-related-agreements");
426
427         // Click the 'Edit' button from the list
428         cy.get("#agreements_list table tbody tr:first")
429             .contains("Edit")
430             .click();
431         cy.wait("@get-agreement");
432         cy.wait(500); // Cypress is too fast! Vue hasn't populated the form yet!
433         cy.get("#agreements_add h2").contains("Edit agreement");
434
435         // Form has been correctly filled in
436         cy.get("#agreement_name").should("have.value", agreements[0].name);
437         cy.get("#agreement_description").should(
438             "have.value",
439             agreements[0].description
440         );
441         cy.get("#agreement_status .vs__selected").contains("Active");
442         cy.get("#agreement_is_perpetual_no").should("be.checked");
443         cy.get("#started_on_0").invoke("val").should("eq", dates["today_iso"]);
444         cy.get("#ended_on_0").invoke("val").should("eq", dates["tomorrow_iso"]);
445         cy.get("#cancellation_deadline_0").invoke("val").should("eq", "");
446         cy.get("#notes_0").should("have.value", "");
447         cy.get("#started_on_1").invoke("val").should("eq", dates["today_iso"]);
448         cy.get("#ended_on_1").invoke("val").should("eq", "");
449         cy.get("#cancellation_deadline_1")
450             .invoke("val")
451             .should("eq", dates["tomorrow_iso"]);
452         cy.get("#notes_1").should("have.value", "this is a note");
453
454         //Test related content
455         cy.get("#agreement_license_0 #license_id_0 .vs__selected").contains("first license name");
456         cy.get("#agreement_license_1 #license_id_1 .vs__selected").contains("second license name");
457         cy.get("#document_0 .file_information span").contains("file.json" );
458         cy.get("#related_agreement_0 #related_agreement_id_0 .vs__selected").contains("agreement name");
459
460         // Submit the form, get 500
461         cy.intercept("PUT", "/api/v1/erm/agreements/*", (req) => {
462             req.reply({
463                 statusCode: 500,
464                 error: "Something went wrong",
465                 delay: 1000,
466             });
467         });
468         cy.get("#agreements_add").contains("Submit").click();
469         cy.get("main div[class='modal_centered']").contains("Submitting...");
470         cy.wait(1000);
471         cy.get("main div[class='dialog alert']").contains(
472             "Something went wrong: Error: Internal Server Error"
473         );
474
475         // Submit the form, success!
476         cy.intercept("PUT", "/api/v1/erm/agreements/*", {
477             statusCode: 200,
478             body: agreement,
479         });
480         cy.get("#agreements_add").contains("Submit").click();
481         cy.get("main div[class='dialog message']").contains(
482             "Agreement updated"
483         );
484     });
485
486     it("Show agreement", () => {
487         let agreement = get_agreement();
488         let agreements = [agreement];
489         // Click the "name" link from the list
490         cy.intercept("GET", "/api/v1/erm/agreements*", {
491             statusCode: 200,
492             body: agreements,
493             headers: {
494                 "X-Base-Total-Count": "1",
495                 "X-Total-Count": "1",
496             },
497         });
498         cy.intercept("GET", "/api/v1/erm/agreements/*", agreement).as(
499             "get-agreement"
500         );
501         cy.visit("/cgi-bin/koha/erm/agreements");
502         let name_link = cy.get(
503             "#agreements_list table tbody tr:first td:first a"
504         );
505         name_link.should(
506             "have.text",
507             agreement.name + " (#" + agreement.agreement_id + ")"
508         );
509         name_link.click();
510         cy.wait("@get-agreement");
511         cy.wait(500); // Cypress is too fast! Vue hasn't populated the form yet!
512         cy.get("#agreements_show h2").contains(
513             "Agreement #" + agreement.agreement_id
514         );
515
516         // TODO There are more to test here:
517         // Dates correctly formatted
518         // Vendors displayed
519         // AV's libs displayed
520         // Tables for periods and users
521     });
522     it("Delete agreement", () => {
523         let agreement = get_agreement();
524         let agreements = [agreement];
525
526         // Delete from list
527         // Click the 'Delete' button from the list
528         cy.intercept("GET", "/api/v1/erm/agreements*", {
529             statusCode: 200,
530             body: agreements,
531             headers: {
532                 "X-Base-Total-Count": "1",
533                 "X-Total-Count": "1",
534             },
535         });
536         cy.intercept("GET", "/api/v1/erm/agreements/*", agreement);
537         cy.visit("/cgi-bin/koha/erm/agreements");
538
539         cy.get("#agreements_list table tbody tr:first")
540             .contains("Delete")
541             .click();
542         cy.get(".dialog.alert.confirmation h1").contains("remove this agreement");
543         cy.contains(agreement.name);
544
545         // Accept the confirmation dialog, get 500
546         cy.intercept("DELETE", "/api/v1/erm/agreements/*", {
547             statusCode: 500,
548             error: "Something went wrong",
549         });
550         cy.contains("Yes, delete").click();
551         cy.get("main div[class='dialog alert']").contains(
552             "Something went wrong: Error: Internal Server Error"
553         );
554
555         // Accept the confirmation dialog, success!
556         cy.intercept("DELETE", "/api/v1/erm/agreements/*", {
557             statusCode: 204,
558             body: null,
559         });
560         cy.get("#agreements_list table tbody tr:first")
561             .contains("Delete")
562             .click();
563         cy.get(".dialog.alert.confirmation h1").contains("remove this agreement");
564         cy.contains("Yes, delete").click();
565         cy.get("main div[class='dialog message']").contains("Agreement").contains("deleted");
566
567         // Delete from show
568         // Click the "name" link from the list
569         cy.intercept("GET", "/api/v1/erm/agreements*", {
570             statusCode: 200,
571             body: agreements,
572             headers: {
573                 "X-Base-Total-Count": "1",
574                 "X-Total-Count": "1",
575             },
576         });
577         cy.intercept("GET", "/api/v1/erm/agreements/*", agreement).as(
578             "get-agreement"
579         );
580         cy.visit("/cgi-bin/koha/erm/agreements");
581         let name_link = cy.get(
582             "#agreements_list table tbody tr:first td:first a"
583         );
584         name_link.should(
585             "have.text",
586             agreement.name + " (#" + agreement.agreement_id + ")"
587         );
588         name_link.click();
589         cy.wait("@get-agreement");
590         cy.wait(500); // Cypress is too fast! Vue hasn't populated the form yet!
591         cy.get("#agreements_show h2").contains(
592             "Agreement #" + agreement.agreement_id
593         );
594
595         cy.get('#agreements_show .action_links .fa-trash').click();
596         cy.get(".dialog.alert.confirmation h1").contains("remove this agreement");
597         cy.contains("Yes, delete").click();
598
599         //Make sure we return to list after deleting from show
600         cy.get("#agreements_list table tbody tr:first")
601     });
602 });