Simplify and add tests of confirm dialog
[portal/nonrtric-controlpanel.git] / webapp-frontend / src / app / policy / policy-instance / policy-instance.component.spec.ts
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2020 Nordix Foundation
6  * %%
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================LICENSE_END===================================
19  */
20
21 import { HarnessLoader } from "@angular/cdk/testing";
22 import { TestbedHarnessEnvironment } from "@angular/cdk/testing/testbed";
23 import { HttpResponse } from "@angular/common/http";
24 import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
25 import { ComponentFixture, TestBed } from "@angular/core/testing";
26 import { FormsModule, ReactiveFormsModule } from "@angular/forms";
27 import { MatButtonHarness } from "@angular/material/button/testing";
28 import { MatDialog } from "@angular/material/dialog";
29 import { MatIconModule } from "@angular/material/icon";
30 import { MatInputHarness } from "@angular/material/input/testing";
31 import { MatSortModule } from "@angular/material/sort";
32 import { MatSortHarness } from "@angular/material/sort/testing";
33 import { MatTableModule } from "@angular/material/table";
34 import { MatTableHarness } from "@angular/material/table/testing";
35 import { By } from "@angular/platform-browser";
36 import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
37 import {
38   PolicyInstance,
39   PolicyInstances,
40   PolicyStatus,
41   PolicyTypeSchema,
42 } from "@app/interfaces/policy.types";
43 import { PolicyService } from "@app/services/policy/policy.service";
44 import { ConfirmDialogService } from "@app/services/ui/confirm-dialog.service";
45 import { NotificationService } from "@app/services/ui/notification.service";
46 import { UiService } from "@app/services/ui/ui.service";
47 import { ToastrModule } from "ngx-toastr";
48 import { Observable, of } from "rxjs";
49 import { PolicyInstanceDialogComponent } from "../policy-instance-dialog/policy-instance-dialog.component";
50 import { PolicyInstanceComponent } from "./policy-instance.component";
51
52 const lastModifiedTime = "2021-01-26T13:15:11.895297Z";
53 describe("PolicyInstanceComponent", () => {
54   let hostComponent: PolicyInstanceComponentHostComponent;
55   let componentUnderTest: PolicyInstanceComponent;
56   let hostFixture: ComponentFixture<PolicyInstanceComponentHostComponent>;
57   let loader: HarnessLoader;
58   let policyServiceSpy: jasmine.SpyObj<PolicyService>;
59   let dialogSpy: jasmine.SpyObj<MatDialog>;
60   let notificationServiceSpy: jasmine.SpyObj<NotificationService>;
61   let confirmServiceSpy: jasmine.SpyObj<ConfirmDialogService>;
62
63   const policyInstances = {
64     policy_ids: ["policy1", "policy2"],
65   } as PolicyInstances;
66   const policyTypeSchema = JSON.parse(
67     '{"title": "1", "description": "Type 1 policy type"}'
68   );
69   const policy1 = {
70     policy_id: "policy1",
71     policy_data: "{}",
72     ric_id: "1",
73     service_id: "service",
74     lastModified: "Now",
75   } as PolicyInstance;
76   const policy2 = {
77     policy_id: "policy2",
78     policy_data: "{}",
79     ric_id: "2",
80     service_id: "service",
81     lastModified: "Now",
82   } as PolicyInstance;
83   const policy1Status = {
84     last_modified: lastModifiedTime,
85   } as PolicyStatus;
86   const policy2Status = {
87     last_modified: lastModifiedTime,
88   } as PolicyStatus;
89
90   const policyIdToInstanceMap = {
91     policy1: policy1,
92     policy2: policy2,
93   };
94   const policyIdToStatusMap = {
95     policy1: policy1Status,
96     policy2: policy2Status,
97   };
98
99   @Component({
100     selector: "policy-instance-compnent-host-component",
101     template:
102       "<nrcp-policy-instance [policyTypeSchema]=policyType></nrcp-policy-instance>",
103   })
104   class PolicyInstanceComponentHostComponent {
105     policyType = {
106       id: "type1",
107       name: "1",
108       schemaObject: policyTypeSchema,
109     } as PolicyTypeSchema;
110   }
111
112   beforeEach(async () => {
113     policyServiceSpy = jasmine.createSpyObj("PolicyService", [
114       "getPolicyInstancesByType",
115       "getPolicyInstance",
116       "getPolicyStatus",
117       "deletePolicy",
118     ]);
119     policyServiceSpy.getPolicyInstancesByType.and.returnValue(
120       of(policyInstances)
121     );
122     policyServiceSpy.getPolicyInstance.and.callFake(function (
123       policyId: string
124     ) {
125       return of(policyIdToInstanceMap[policyId]);
126     });
127     policyServiceSpy.getPolicyStatus.and.callFake(function (policyId: string) {
128       return of(policyIdToStatusMap[policyId]);
129     });
130
131     dialogSpy = jasmine.createSpyObj("MatDialog", ["open"]);
132     notificationServiceSpy = jasmine.createSpyObj("NotificationService", [
133       "success",
134       "warn",
135     ]);
136     confirmServiceSpy = jasmine.createSpyObj("ConfirmDialogService", [
137       "openConfirmDialog",
138     ]);
139
140     await TestBed.configureTestingModule({
141       imports: [
142         BrowserAnimationsModule,
143         FormsModule,
144         MatIconModule,
145         MatSortModule,
146         MatTableModule,
147         ReactiveFormsModule,
148         ToastrModule.forRoot(),
149       ],
150       schemas: [CUSTOM_ELEMENTS_SCHEMA],
151       declarations: [
152         PolicyInstanceComponent,
153         PolicyInstanceComponentHostComponent,
154       ],
155       providers: [
156         { provide: PolicyService, useValue: policyServiceSpy },
157         { provide: MatDialog, useValue: dialogSpy },
158         { provide: NotificationService, useValue: notificationServiceSpy },
159         { provide: ConfirmDialogService, useValue: confirmServiceSpy },
160         UiService,
161       ],
162     }).compileComponents();
163
164     hostFixture = TestBed.createComponent(PolicyInstanceComponentHostComponent);
165     hostComponent = hostFixture.componentInstance;
166     componentUnderTest = hostFixture.debugElement.query(
167       By.directive(PolicyInstanceComponent)
168     ).componentInstance;
169     hostFixture.detectChanges();
170     loader = TestbedHarnessEnvironment.loader(hostFixture);
171   });
172
173   it("should create", () => {
174     expect(hostComponent).toBeTruthy();
175
176     expect(componentUnderTest).toBeTruthy();
177   });
178
179   it("should set correct dark mode from UIService", () => {
180     const uiService: UiService = TestBed.inject(UiService);
181     expect(componentUnderTest.darkMode).toBeTruthy();
182
183     uiService.darkModeState.next(false);
184     hostFixture.detectChanges();
185     expect(componentUnderTest.darkMode).toBeFalsy();
186   });
187
188   it("should contain number of instances heading and value, create and refresh buttons, and policies table", async () => {
189     const instancesHeading = hostFixture.debugElement.nativeElement.querySelector(
190       "div"
191     );
192     expect(instancesHeading.innerText).toContain("Number of instances: 2");
193
194     const createButton: MatButtonHarness = await loader.getHarness(
195       MatButtonHarness.with({ selector: "#createButton" })
196     );
197     expect(createButton).toBeTruthy();
198     const createIcon = hostFixture.debugElement.nativeElement.querySelector(
199       "#createIcon"
200     );
201     expect(createIcon.innerText).toContain("add_box");
202
203     const refreshButton: MatButtonHarness = await loader.getHarness(
204       MatButtonHarness.with({ selector: "#refreshButton" })
205     );
206     expect(refreshButton).toBeTruthy();
207     const refreshIcon = hostFixture.debugElement.nativeElement.querySelector(
208       "#refreshIcon"
209     );
210     expect(refreshIcon.innerText).toContain("refresh");
211
212     const policiesTable = await loader.getHarness(
213       MatTableHarness.with({ selector: "#policiesTable" })
214     );
215     expect(policiesTable).toBeTruthy();
216   });
217
218   it("should open dialog to create policy and refresh policies after successful creation", async () => {
219     const dialogRefSpy = setupDialogRefSpy();
220     dialogSpy.open.and.returnValue(dialogRefSpy);
221
222     spyOn(componentUnderTest, "getPolicyInstances");
223
224     const createButton: MatButtonHarness = await loader.getHarness(
225       MatButtonHarness.with({ selector: "#createButton" })
226     );
227     await createButton.click();
228
229     expect(dialogSpy.open).toHaveBeenCalledWith(PolicyInstanceDialogComponent, {
230       maxWidth: "1200px",
231       maxHeight: "900px",
232       width: "900px",
233       role: "dialog",
234       disableClose: false,
235       panelClass: "dark-theme",
236       data: {
237         createSchema: policyTypeSchema,
238         instanceId: null,
239         instanceJson: null,
240         name: "1",
241         ric: null,
242       },
243     });
244     expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
245   });
246
247   it("should open dialog to edit policy and refresh policies after successful update", async () => {
248     const dialogRefSpy = setupDialogRefSpy();
249     dialogSpy.open.and.returnValue(dialogRefSpy);
250
251     spyOn(componentUnderTest, "getPolicyInstances");
252
253     const editButton: MatButtonHarness = await loader.getHarness(
254       MatButtonHarness.with({ selector: "#policy1EditButton" })
255     );
256     await editButton.click();
257
258     expect(dialogSpy.open).toHaveBeenCalledWith(PolicyInstanceDialogComponent, {
259       maxWidth: "1200px",
260       maxHeight: "900px",
261       width: "900px",
262       role: "dialog",
263       disableClose: false,
264       panelClass: "dark-theme",
265       data: {
266         createSchema: policyTypeSchema,
267         instanceId: "policy1",
268         instanceJson: "{}",
269         name: "1",
270         ric: "1",
271       },
272     });
273     expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
274   });
275
276   it("should open dialog to edit policy and not refresh policies when dialog closed wihtout submit", async () => {
277     const dialogRefSpy = setupDialogRefSpy(false);
278     dialogSpy.open.and.returnValue(dialogRefSpy);
279
280     spyOn(componentUnderTest, "getPolicyInstances");
281
282     const editButton: MatButtonHarness = await loader.getHarness(
283       MatButtonHarness.with({ selector: "#policy1EditButton" })
284     );
285     await editButton.click();
286
287     expect(componentUnderTest.getPolicyInstances).not.toHaveBeenCalled();
288   });
289
290   it("should open instance dialog when clicking in any policy cell in table", async () => {
291     spyOn(componentUnderTest, "modifyInstance");
292
293     const policiesTable = await loader.getHarness(
294       MatTableHarness.with({ selector: "#policiesTable" })
295     );
296     const firstRow = (await policiesTable.getRows())[0];
297     const idCell = (await firstRow.getCells())[0];
298     (await idCell.host()).click();
299     const ownerCell = (await firstRow.getCells())[1];
300     (await ownerCell.host()).click();
301     const serviceCell = (await firstRow.getCells())[2];
302     (await serviceCell.host()).click();
303     const lastModifiedCell = (await firstRow.getCells())[3];
304     (await lastModifiedCell.host()).click();
305
306     // Totally unnecessary call just to make the bloody framework count the number of calls to the spy correctly!
307     await policiesTable.getRows();
308
309     expect(componentUnderTest.modifyInstance).toHaveBeenCalledTimes(4);
310   });
311
312   it("should open dialog asking for delete and delete when ok response and refresh table afterwards", async () => {
313     const dialogRefSpy = setupDialogRefSpy();
314     confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
315     const createResponse = { status: 204 } as HttpResponse<Object>;
316     policyServiceSpy.deletePolicy.and.returnValue(of(createResponse));
317
318     spyOn(componentUnderTest, "getPolicyInstances");
319     const deleteButton: MatButtonHarness = await loader.getHarness(
320       MatButtonHarness.with({ selector: "#policy1DeleteButton" })
321     );
322     await deleteButton.click();
323
324     expect(confirmServiceSpy.openConfirmDialog).toHaveBeenCalledWith(
325       "Delete Policy",
326       "Are you sure you want to delete this policy instance?"
327     );
328     expect(policyServiceSpy.deletePolicy).toHaveBeenCalledWith("policy1");
329     expect(notificationServiceSpy.success).toHaveBeenCalledWith(
330       "Delete succeeded!"
331     );
332     expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
333   });
334
335   it("should open dialog asking for delete and not delete whith Cancel as response", async () => {
336     const dialogRefSpy = setupDialogRefSpy(false);
337     confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
338
339     const deleteButton: MatButtonHarness = await loader.getHarness(
340       MatButtonHarness.with({ selector: "#policy1DeleteButton" })
341     );
342     await deleteButton.click();
343
344     expect(policyServiceSpy.deletePolicy).not.toHaveBeenCalled();
345   });
346
347   it("should refresh table", async () => {
348     spyOn(componentUnderTest, "getPolicyInstances");
349
350     const refreshButton: MatButtonHarness = await loader.getHarness(
351       MatButtonHarness.with({ selector: "#refreshButton" })
352     );
353     await refreshButton.click();
354
355     expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
356   });
357
358   describe("#policiesTable", () => {
359     const expectedPolicy1Row = {
360       instanceId: "policy1",
361       ric: "1",
362       service: "service",
363       lastModified: toLocalTime(lastModifiedTime),
364       action: "editdelete",
365     };
366
367     it("should contain correct headings", async () => {
368       const policiesTable = await loader.getHarness(
369         MatTableHarness.with({ selector: "#policiesTable" })
370       );
371       const headerRow = (await policiesTable.getHeaderRows())[0];
372       const headers = await headerRow.getCellTextByColumnName();
373
374       expect(headers).toEqual({
375         instanceId: "Instance",
376         ric: "Target",
377         service: "Owner",
378         lastModified: "Last modified",
379         action: "Action",
380       });
381     });
382
383     it("should contain data after initialization", async () => {
384       const expectedJobRows = [
385         expectedPolicy1Row,
386         {
387           instanceId: "policy2",
388           ric: "2",
389           service: "service",
390           lastModified: toLocalTime(lastModifiedTime),
391           action: "editdelete",
392         },
393       ];
394       const policiesTable = await loader.getHarness(
395         MatTableHarness.with({ selector: "#policiesTable" })
396       );
397       const policyRows = await policiesTable.getRows();
398       expect(policyRows.length).toEqual(2);
399       policyRows.forEach((row) => {
400         row.getCellTextByColumnName().then((values) => {
401           expect(expectedJobRows).toContain(jasmine.objectContaining(values));
402         });
403       });
404     });
405
406     it("should have filtering for all four policy data headings", async () => {
407       const policiesTable = await loader.getHarness(
408         MatTableHarness.with({ selector: "#policiesTable" })
409       );
410
411       const idFilterInput = await loader.getHarness(
412         MatInputHarness.with({ selector: "#policyInstanceIdFilter" })
413       );
414       await idFilterInput.setValue("1");
415       const policyRows = await policiesTable.getRows();
416       expect(policyRows.length).toEqual(1);
417       expect(await policyRows[0].getCellTextByColumnName()).toEqual(
418         expectedPolicy1Row
419       );
420
421       const targetFilterInput = await loader.getHarness(
422         MatInputHarness.with({ selector: "#policyInstanceTargetFilter" })
423       );
424       expect(targetFilterInput).toBeTruthy();
425
426       const ownerFilterInput = await loader.getHarness(
427         MatInputHarness.with({ selector: "#policyInstanceOwnerFilter" })
428       );
429       expect(ownerFilterInput).toBeTruthy();
430
431       const lastModifiedFilterInput = await loader.getHarness(
432         MatInputHarness.with({ selector: "#policyInstanceLastModifiedFilter" })
433       );
434       expect(lastModifiedFilterInput).toBeTruthy();
435     });
436
437     it("should not sort when click in filter inputs", async () => {
438       spyOn(componentUnderTest, "stopSort").and.callThrough();
439
440       const idFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
441         "#idSortStop"
442       );
443       idFilterInputDiv.click();
444
445       const targetFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
446         "#targetSortStop"
447       );
448       targetFilterInputDiv.click();
449
450       const ownerFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
451         "#ownerSortStop"
452       );
453       ownerFilterInputDiv.click();
454
455       const lastModifiedFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
456         "#lastModifiedSortStop"
457       );
458       lastModifiedFilterInputDiv.click();
459
460       expect(componentUnderTest.stopSort).toHaveBeenCalledTimes(4);
461
462       const eventSpy = jasmine.createSpyObj("any", ["stopPropagation"]);
463       componentUnderTest.stopSort(eventSpy);
464       expect(eventSpy.stopPropagation).toHaveBeenCalled();
465     });
466
467     describe("#sorting", () => {
468       it("should verify sort functionality on the table", async () => {
469         const sort = await loader.getHarness(MatSortHarness);
470         const headers = await sort.getSortHeaders({ sortDirection: "" });
471         expect(headers.length).toBe(4);
472
473         await headers[0].click();
474         expect(await headers[0].isActive()).toBe(true);
475         expect(await headers[0].getSortDirection()).toBe("asc");
476
477         await headers[0].click();
478         expect(await headers[0].getSortDirection()).toBe("desc");
479       });
480
481       it("should sort table asc and desc by first header", async () => {
482         const sort = await loader.getHarness(MatSortHarness);
483         const policyTable = await loader.getHarness(
484           MatTableHarness.with({ selector: "#policiesTable" })
485         );
486         const firstHeader = (await sort.getSortHeaders())[0];
487         expect(await firstHeader.getSortDirection()).toBe("");
488
489         await firstHeader.click();
490         expect(await firstHeader.getSortDirection()).toBe("asc");
491         let policyRows = await policyTable.getRows();
492         expect(await policyRows[0].getCellTextByColumnName()).toEqual(
493           expectedPolicy1Row
494         );
495
496         await firstHeader.click();
497         expect(await firstHeader.getSortDirection()).toBe("desc");
498         policyRows = await policyTable.getRows();
499         expect(
500           await policyRows[policyRows.length - 1].getCellTextByColumnName()
501         ).toEqual(expectedPolicy1Row);
502       });
503
504       it("should sort table asc and desc by second header", async () => {
505         const sort = await loader.getHarness(MatSortHarness);
506         const jobsTable = await loader.getHarness(
507           MatTableHarness.with({ selector: "#policiesTable" })
508         );
509         const firstHeader = (await sort.getSortHeaders())[1];
510         expect(await firstHeader.getSortDirection()).toBe("");
511
512         await firstHeader.click();
513         expect(await firstHeader.getSortDirection()).toBe("asc");
514         let policyRows = await jobsTable.getRows();
515         policyRows = await jobsTable.getRows();
516         expect(await policyRows[0].getCellTextByColumnName()).toEqual(
517           expectedPolicy1Row
518         );
519
520         await firstHeader.click();
521         expect(await firstHeader.getSortDirection()).toBe("desc");
522         policyRows = await jobsTable.getRows();
523         expect(
524           await policyRows[policyRows.length - 1].getCellTextByColumnName()
525         ).toEqual(expectedPolicy1Row);
526       });
527
528       it("should sort table asc and desc by third header", async () => {
529         const sort = await loader.getHarness(MatSortHarness);
530         const jobsTable = await loader.getHarness(
531           MatTableHarness.with({ selector: "#policiesTable" })
532         );
533         const firstHeader = (await sort.getSortHeaders())[2];
534         expect(await firstHeader.getSortDirection()).toBe("");
535
536         await firstHeader.click();
537         expect(await firstHeader.getSortDirection()).toBe("asc");
538         let policyRows = await jobsTable.getRows();
539         policyRows = await jobsTable.getRows();
540         expect(await policyRows[0].getCellTextByColumnName()).toEqual(
541           expectedPolicy1Row
542         );
543
544         await firstHeader.click();
545         expect(await firstHeader.getSortDirection()).toBe("desc");
546         policyRows = await jobsTable.getRows();
547         expect(
548           await policyRows[policyRows.length - 1].getCellTextByColumnName()
549         ).toEqual(expectedPolicy1Row);
550       });
551
552       it("should sort table asc and desc by fourth header", async () => {
553         const sort = await loader.getHarness(MatSortHarness);
554         const jobsTable = await loader.getHarness(
555           MatTableHarness.with({ selector: "#policiesTable" })
556         );
557         const firstHeader = (await sort.getSortHeaders())[3];
558         expect(await firstHeader.getSortDirection()).toBe("");
559
560         await firstHeader.click();
561         expect(await firstHeader.getSortDirection()).toBe("asc");
562         let policyRows = await jobsTable.getRows();
563         policyRows = await jobsTable.getRows();
564         expect(await policyRows[0].getCellTextByColumnName()).toEqual(
565           expectedPolicy1Row
566         );
567
568         await firstHeader.click();
569         expect(await firstHeader.getSortDirection()).toBe("desc");
570         policyRows = await jobsTable.getRows();
571         expect(
572           await policyRows[policyRows.length - 1].getCellTextByColumnName()
573         ).toEqual(expectedPolicy1Row);
574       });
575     });
576   });
577 });
578
579 function setupDialogRefSpy(returnValue: boolean = true) {
580   const afterClosedObservable = new Observable((observer) => {
581     observer.next(returnValue);
582   });
583
584   const dialogRefSpy = jasmine.createSpyObj("MatDialogRef", ["afterClosed"]);
585   dialogRefSpy.afterClosed.and.returnValue(afterClosedObservable);
586   return dialogRefSpy;
587 }
588
589 function toLocalTime(utcTime: string): string {
590   const date = new Date(utcTime);
591   const toutc = date.toUTCString();
592   return new Date(toutc + " UTC").toLocaleString();
593 }