Fix PolicyInstanceComponent and add test coverage
[portal/nonrtric-controlpanel.git] / webapp-frontend / src / app / policy / policy-instance-dialog / policy-instance-dialog.component.spec.ts
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2019 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 { BrowserAnimationsModule } from "@angular/platform-browser/animations";
22 import { By } from "@angular/platform-browser";
23 import { ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
24 import { ComponentFixture, TestBed } from "@angular/core/testing";
25 import { HarnessLoader } from "@angular/cdk/testing";
26 import { MatButtonModule } from "@angular/material/button";
27 import { MatButtonHarness } from "@angular/material/button/testing";
28 import {
29   MatDialogModule,
30   MatDialogRef,
31   MAT_DIALOG_DATA,
32 } from "@angular/material/dialog";
33 import { MatSelectModule } from "@angular/material/select";
34 import { MatInputModule } from "@angular/material/input";
35 import { of } from "rxjs";
36 import { ReactiveFormsModule } from "@angular/forms";
37 import { TestbedHarnessEnvironment } from "@angular/cdk/testing/testbed";
38 import { ToastrModule } from "ngx-toastr";
39 import { MockComponent } from "ng-mocks";
40
41 import { PolicyService } from "@services/policy/policy.service";
42 import { UiService } from "@services/ui/ui.service";
43 import { PolicyInstanceDialogComponent } from "./policy-instance-dialog.component";
44 import { TypedPolicyEditorComponent } from "@policy/typed-policy-editor/typed-policy-editor.component";
45 import { RicSelectorComponent } from "@policy/ric-selector/ric-selector.component";
46 import { NoTypePolicyEditorComponent } from "@policy/no-type-policy-editor/no-type-policy-editor.component";
47 import { CreatePolicyInstance } from "@interfaces/policy.types";
48 import { NotificationService } from "@services/ui/notification.service";
49 import * as uuid from "uuid";
50 import { HttpErrorResponse } from "@angular/common/http";
51
52 describe("PolicyInstanceDialogComponent", () => {
53   const untypedSchema = JSON.parse("{}");
54   const typedSchema = JSON.parse(
55     '{ "description": "Type 1 policy type", "title": "1", "type": "object", "properties": { "priorityLevel": "number" }}'
56   );
57
58   let component: PolicyInstanceDialogComponent;
59   let fixture: ComponentFixture<PolicyInstanceDialogComponent>;
60   let loader: HarnessLoader;
61   let dialogRefSpy: MatDialogRef<PolicyInstanceDialogComponent>;
62   let policyServiceSpy: jasmine.SpyObj<PolicyService>;
63   let notificationServiceSpy: NotificationService;
64
65   beforeEach(async () => {
66     dialogRefSpy = jasmine.createSpyObj("MatDialogRef", ["close"]);
67     policyServiceSpy = jasmine.createSpyObj("PolicyService", ["putPolicy"]);
68     notificationServiceSpy = jasmine.createSpyObj("NotificationService", [
69       "success",
70     ]);
71
72     TestBed.configureTestingModule({
73       imports: [
74         BrowserAnimationsModule,
75         MatButtonModule,
76         MatDialogModule,
77         MatInputModule,
78         MatSelectModule,
79         ReactiveFormsModule,
80         ToastrModule.forRoot(),
81       ],
82       schemas: [CUSTOM_ELEMENTS_SCHEMA],
83       declarations: [
84         PolicyInstanceDialogComponent,
85         MockComponent(RicSelectorComponent),
86         MockComponent(NoTypePolicyEditorComponent),
87         MockComponent(TypedPolicyEditorComponent),
88       ],
89       providers: [
90         ChangeDetectorRef,
91         { provide: MatDialogRef, useValue: dialogRefSpy },
92         { provide: PolicyService, useValue: policyServiceSpy },
93         { provide: NotificationService, useValue: notificationServiceSpy },
94         { provide: MAT_DIALOG_DATA, useValue: true },
95         UiService,
96       ],
97     });
98   });
99
100   it("should set correct dark mode from UIService", () => {
101     const policyData = {
102       createSchema: untypedSchema,
103     };
104     TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: policyData }); // Should be provided with a policy
105     ({ fixture, component, loader } = compileAndGetComponents(
106       fixture,
107       component,
108       loader
109     ));
110     const uiService: UiService = TestBed.inject(UiService);
111     expect(component.darkMode).toBeTruthy();
112
113     uiService.darkModeState.next(false);
114     fixture.detectChanges();
115     expect(component.darkMode).toBeFalsy();
116   });
117
118   describe("creating policy without type", () => {
119     beforeEach(async () => {
120       const policyData = {
121         createSchema: untypedSchema,
122       };
123       TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: policyData }); // Should be provided with a policy
124       ({ fixture, component, loader } = compileAndGetComponents(
125         fixture,
126         component,
127         loader
128       ));
129       jasmine.addCustomEqualityTester(policyTester);
130     });
131
132     it("should contain oran logo and create title and no instance info", async () => {
133       let ele = fixture.debugElement.nativeElement.querySelector("img");
134       expect(ele.src).toContain("assets/oran-logo.png");
135
136       ele = fixture.debugElement.nativeElement.querySelector("text");
137       expect(ele.textContent).toEqual(
138         "Create new policy instance of type < No Type >"
139       );
140
141       ele = fixture.debugElement.nativeElement.querySelector("#instanceInfo");
142       expect(ele).toBeFalsy();
143     });
144
145     it("should contain ric select with no policy type and no ric selected", async () => {
146       const ricSelector: RicSelectorComponent = fixture.debugElement.query(
147         By.directive(RicSelectorComponent)
148       ).componentInstance;
149       expect(ricSelector).toBeTruthy();
150       expect(ricSelector.policyTypeName).toBeFalsy();
151       expect(component.policyInstance.ric_id).toBeFalsy();
152     });
153
154     it("should contain json editor with no JSON", async () => {
155       const noTypePolicyEditor: NoTypePolicyEditorComponent = fixture.debugElement.query(
156         By.directive(NoTypePolicyEditorComponent)
157       ).componentInstance;
158       expect(noTypePolicyEditor).toBeTruthy();
159       expect(noTypePolicyEditor.policyJson).toBeFalsy();
160     });
161
162     it("should contain enabled Close button and Submit button", async () => {
163       component.ngOnInit();
164
165       let closeButton: MatButtonHarness = await loader.getHarness(
166         MatButtonHarness.with({ selector: "#closeButton" })
167       );
168       expect(await closeButton.isDisabled()).toBeFalsy();
169       expect(await closeButton.getText()).toEqual("Close");
170
171       let submitButton: MatButtonHarness = await loader.getHarness(
172         MatButtonHarness.with({ selector: "#submitButton" })
173       );
174       expect(await submitButton.getText()).toEqual("Submit");
175     });
176
177     it("should enable Submit button when ric is selected and json is valid", async () => {
178       const ricSelector: RicSelectorComponent = fixture.debugElement.query(
179         By.directive(RicSelectorComponent)
180       ).componentInstance;
181       const noTypePolicyEditor: NoTypePolicyEditorComponent = fixture.debugElement.query(
182         By.directive(NoTypePolicyEditorComponent)
183       ).componentInstance;
184       let submitButton: MatButtonHarness = await loader.getHarness(
185         MatButtonHarness.with({ selector: "#submitButton" })
186       );
187
188       noTypePolicyEditor.validJson.emit(null);
189       expect(await submitButton.isDisabled()).toBeTruthy();
190
191       ricSelector.selectedRic.emit("ric1");
192       expect(await submitButton.isDisabled()).toBeTruthy();
193
194       noTypePolicyEditor.validJson.emit("{}");
195       expect(await submitButton.isDisabled()).toBeFalsy();
196     });
197
198     it("should generate policy ID when submitting new policy and close dialog", async () => {
199       const ricSelector: RicSelectorComponent = fixture.debugElement.query(
200         By.directive(RicSelectorComponent)
201       ).componentInstance;
202       const noTypePolicyEditor: NoTypePolicyEditorComponent = fixture.debugElement.query(
203         By.directive(NoTypePolicyEditorComponent)
204       ).componentInstance;
205       let submitButton: MatButtonHarness = await loader.getHarness(
206         MatButtonHarness.with({ selector: "#submitButton" })
207       );
208
209       spyOn(uuid, "v4").and.returnValue("1234567890");
210       ricSelector.selectedRic.emit("ric1");
211       noTypePolicyEditor.validJson.emit("{}");
212
213       policyServiceSpy.putPolicy.and.returnValue(of("Success"));
214
215       await submitButton.click();
216
217       const policyInstance = {} as CreatePolicyInstance;
218       policyInstance.policy_data = JSON.parse("{}");
219       policyInstance.policy_id = "1234567890";
220       policyInstance.policytype_id = "";
221       policyInstance.ric_id = "ric1";
222       policyInstance.service_id = "controlpanel";
223       expect(policyServiceSpy.putPolicy).toHaveBeenCalledWith(policyInstance);
224
225       expect(dialogRefSpy.close).toHaveBeenCalledWith("ok");
226     });
227
228     it("should not close dialog when error from server", async () => {
229       let submitButton: MatButtonHarness = await loader.getHarness(
230         MatButtonHarness.with({ selector: "#submitButton" })
231       );
232
233       const errorResponse = {
234         status: 400,
235         statusText: "Bad Request",
236       } as HttpErrorResponse;
237       policyServiceSpy.putPolicy.and.returnValue(errorResponse);
238
239       await submitButton.click();
240
241       expect(policyServiceSpy.putPolicy).toHaveBeenCalled();
242
243       expect(dialogRefSpy.close).not.toHaveBeenCalled();
244     });
245   });
246
247   describe("content when creating policy with type", () => {
248     beforeEach(async () => {
249       const policyData = {
250         name: "Type 1",
251         createSchema: typedSchema,
252       };
253       TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: policyData }); // Should be provided with a policy
254       ({ fixture, component, loader } = compileAndGetComponents(
255         fixture,
256         component,
257         loader
258       ));
259     });
260
261     it("should contain oran logo and create title and no instance info", async () => {
262       let ele = fixture.debugElement.nativeElement.querySelector("img");
263       expect(ele.src).toContain("assets/oran-logo.png");
264
265       ele = fixture.debugElement.nativeElement.querySelector("text");
266       expect(ele.textContent).toEqual(
267         "Create new policy instance of type Type 1"
268       );
269
270       ele = fixture.debugElement.nativeElement.querySelector("#instanceInfo");
271       expect(ele).toBeFalsy();
272     });
273
274     it("should contain ric select with provided policy type", async () => {
275       const ricSelector: RicSelectorComponent = fixture.debugElement.query(
276         By.directive(RicSelectorComponent)
277       ).componentInstance;
278       expect(ricSelector).toBeTruthy();
279       expect(ricSelector.policyTypeName).toEqual("Type 1");
280     });
281
282     it("should contain typed json editor with empty JSON, schema and dark mode true", async () => {
283       const typedPolicyEditor: TypedPolicyEditorComponent = fixture.debugElement.query(
284         By.directive(TypedPolicyEditorComponent)
285       ).componentInstance;
286       expect(typedPolicyEditor).toBeTruthy();
287       expect(typedPolicyEditor.jsonObject).toBeFalsy();
288       expect(typedPolicyEditor.jsonSchemaObject).toEqual(typedSchema);
289       expect(typedPolicyEditor.darkMode).toBeTruthy();
290     });
291
292     it("should contain enabled Close button and Submit button", async () => {
293       component.ngOnInit();
294
295       let closeButton: MatButtonHarness = await loader.getHarness(
296         MatButtonHarness.with({ selector: "#closeButton" })
297       );
298       expect(await closeButton.isDisabled()).toBeFalsy();
299       expect(await closeButton.getText()).toEqual("Close");
300
301       let submitButton: MatButtonHarness = await loader.getHarness(
302         MatButtonHarness.with({ selector: "#submitButton" })
303       );
304       expect(await submitButton.getText()).toEqual("Submit");
305     });
306
307     it("should enable Submit button when ric is selected and json is valid", async () => {
308       const ricSelector: RicSelectorComponent = fixture.debugElement.query(
309         By.directive(RicSelectorComponent)
310       ).componentInstance;
311       const typedPolicyEditor: TypedPolicyEditorComponent = fixture.debugElement.query(
312         By.directive(TypedPolicyEditorComponent)
313       ).componentInstance;
314       let submitButton: MatButtonHarness = await loader.getHarness(
315         MatButtonHarness.with({ selector: "#submitButton" })
316       );
317
318       typedPolicyEditor.validJson.emit(null);
319       expect(await submitButton.isDisabled()).toBeTruthy();
320
321       ricSelector.selectedRic.emit("ric1");
322       expect(await submitButton.isDisabled()).toBeTruthy();
323
324       typedPolicyEditor.validJson.emit("{}");
325       expect(await submitButton.isDisabled()).toBeFalsy();
326     });
327   });
328
329   describe("content when editing policy without type", () => {
330     const instanceJson = JSON.parse(
331       '{"qosObjectives": {"priorityLevel": 3100}}'
332     );
333     beforeEach(async () => {
334       const policyData = {
335         createSchema: untypedSchema,
336         instanceId: "instanceId",
337         instanceJson: instanceJson,
338         ric: "ric1",
339       };
340       TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: policyData }); // Should be provided with a policy
341       ({ fixture, component, loader } = compileAndGetComponents(
342         fixture,
343         component,
344         loader
345       ));
346       jasmine.addCustomEqualityTester(policyTester);
347     });
348
349     it("should contain oran logo and instance info", async () => {
350       let ele = fixture.debugElement.nativeElement.querySelector("img");
351       expect(ele.src).toContain("assets/oran-logo.png");
352
353       ele = fixture.debugElement.nativeElement.querySelector("text");
354       expect(ele.childNodes[0].childNodes[0]).toBeFalsy(); // No create title
355
356       ele = fixture.debugElement.nativeElement.querySelector("#instanceInfo");
357       expect(ele).toBeTruthy();
358       expect(ele.innerText).toEqual("[ric1] Instance ID: instanceId");
359     });
360
361     it("should not contain ric select", async () => {
362       const ricSelector = fixture.debugElement.query(
363         By.directive(RicSelectorComponent)
364       );
365       expect(ricSelector).toBeFalsy();
366     });
367
368     it("should contain json editor with json data", async () => {
369       const noTypePolicyEditor: NoTypePolicyEditorComponent = fixture.debugElement.query(
370         By.directive(NoTypePolicyEditorComponent)
371       ).componentInstance;
372       expect(noTypePolicyEditor).toBeTruthy();
373       expect(noTypePolicyEditor.policyJson).toEqual(instanceJson);
374     });
375
376     it("should contain enabled Close and Submit buttons when all inputs are valid", async () => {
377       let closeButton: MatButtonHarness = await loader.getHarness(
378         MatButtonHarness.with({ selector: "#closeButton" })
379       );
380       expect(await closeButton.isDisabled()).toBeFalsy();
381       expect(await closeButton.getText()).toEqual("Close");
382
383       let submitButton: MatButtonHarness = await loader.getHarness(
384         MatButtonHarness.with({ selector: "#submitButton" })
385       );
386       expect(await submitButton.isDisabled()).toBeFalsy();
387       expect(await submitButton.getText()).toEqual("Submit");
388     });
389
390     it("should submit policy with correct data, close dialog and notify user about success", async () => {
391       policyServiceSpy.putPolicy.and.returnValue(of("ok"));
392       let submitButton: MatButtonHarness = await loader.getHarness(
393         MatButtonHarness.with({ selector: "#submitButton" })
394       );
395
396       await submitButton.click();
397
398       const policyInstance = {} as CreatePolicyInstance;
399       policyInstance.policy_data = instanceJson;
400       policyInstance.policy_id = "instanceId";
401       policyInstance.policytype_id = "";
402       policyInstance.ric_id = "ric1";
403       policyInstance.service_id = "controlpanel";
404       expect(policyServiceSpy.putPolicy).toHaveBeenCalledWith(policyInstance);
405
406       expect(dialogRefSpy.close).toHaveBeenCalled();
407       expect(notificationServiceSpy.success).toHaveBeenCalledWith(
408         "Policy instanceId submitted"
409       );
410     });
411   });
412
413   describe("content when editing policy with type", () => {
414     const instanceJson = '{"qosObjectives": {"priorityLevel": 3100}}';
415     beforeEach(async () => {
416       const policyData = {
417         createSchema: typedSchema,
418         instanceId: "instanceId",
419         instanceJson: instanceJson,
420         name: "name",
421         ric: "ric1",
422       };
423       TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: policyData }); // Should be provided with a policy
424       ({ fixture, component, loader } = compileAndGetComponents(
425         fixture,
426         component,
427         loader
428       ));
429     });
430
431     it("should contain oran logo and instance info", async () => {
432       let ele = fixture.debugElement.nativeElement.querySelector("img");
433       expect(ele.src).toContain("assets/oran-logo.png");
434
435       ele = fixture.debugElement.nativeElement.querySelector("text");
436       expect(ele.childNodes[0].childNodes[0]).toBeFalsy(); // No create title
437
438       ele = fixture.debugElement.nativeElement.querySelector("#instanceInfo");
439       expect(ele).toBeTruthy();
440       expect(ele.innerText).toEqual("[ric1] Instance ID: instanceId");
441     });
442
443     it("should not contain ric select", async () => {
444       const ricSelector = fixture.debugElement.query(
445         By.directive(RicSelectorComponent)
446       );
447       expect(ricSelector).toBeFalsy();
448     });
449
450     it("should contain typed json editor with instance JSON, schema and dark mode true", async () => {
451       const typedPolicyEditor: TypedPolicyEditorComponent = fixture.debugElement.query(
452         By.directive(TypedPolicyEditorComponent)
453       ).componentInstance;
454       expect(typedPolicyEditor).toBeTruthy();
455       expect(unescapeQuotes(typedPolicyEditor.jsonObject)).toEqual(
456         instanceJson
457       );
458       expect(typedPolicyEditor.jsonSchemaObject).toEqual(typedSchema);
459       expect(typedPolicyEditor.darkMode).toBeTruthy();
460     });
461
462     it("should contain enabled Close and Submit buttons when all inputs are valid", async () => {
463       let closeButton: MatButtonHarness = await loader.getHarness(
464         MatButtonHarness.with({ selector: "#closeButton" })
465       );
466       expect(await closeButton.isDisabled()).toBeFalsy();
467       expect(await closeButton.getText()).toEqual("Close");
468
469       let submitButton: MatButtonHarness = await loader.getHarness(
470         MatButtonHarness.with({ selector: "#submitButton" })
471       );
472       expect(await submitButton.isDisabled()).toBeFalsy();
473       expect(await submitButton.getText()).toEqual("Submit");
474     });
475   });
476 });
477
478 function compileAndGetComponents(
479   fixture: ComponentFixture<PolicyInstanceDialogComponent>,
480   component: PolicyInstanceDialogComponent,
481   loader: HarnessLoader
482 ) {
483   TestBed.compileComponents();
484
485   fixture = TestBed.createComponent(PolicyInstanceDialogComponent);
486   component = fixture.componentInstance;
487   fixture.detectChanges();
488   loader = TestbedHarnessEnvironment.loader(fixture);
489   return { fixture, component, loader };
490 }
491
492 function unescapeQuotes(string: string): string {
493   return string.replace(/\\"/g, '"');
494 }
495
496 function policyTester(first, second) {
497   if (typeof first[0] === "object" && typeof second[0] === "object") {
498     const policy1 = first[0] as CreatePolicyInstance;
499     const policy2 = second[0] as CreatePolicyInstance;
500     return (
501       typeof policy1.policy_data === "object" &&
502       typeof policy2.policy_data === "object" &&
503       JSON.stringify(policy1.policy_data) ===
504         JSON.stringify(policy2.policy_data) &&
505       policy1.policy_id === policy2.policy_id &&
506       policy1.policytype_id === policy2.policytype_id &&
507       policy1.ric_id === policy2.ric_id &&
508       policy1.service_id === policy2.service_id
509     );
510   }
511 }