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