From 669cab64d7741a2ab9e3744b45042b9e19588972 Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Thu, 24 May 2018 11:41:45 +0100 Subject: [PATCH] mgr/dashboard: Add unit test to the frontend services Signed-off-by: Tiago Melo --- .../ceph/block/rbd-list/rbd-list.component.ts | 2 +- .../services/auth-guard.service.spec.ts | 51 +++++++ .../services/auth-storage.service.spec.ts | 35 +++++ .../shared/services/formatter.service.spec.ts | 5 +- .../module-status-guard.service.spec.ts | 107 ++++++++++++++ .../services/notification.service.spec.ts | 138 +++++++++++++++--- .../shared/services/notification.service.ts | 22 +-- .../shared/services/summary.service.spec.ts | 60 ++++++-- .../app/shared/services/summary.service.ts | 4 +- .../task-manager-message.service.spec.ts | 51 +++++++ .../services/task-manager.service.spec.ts | 71 +++++++++ 11 files changed, 502 insertions(+), 44 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-guard.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.spec.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts index fb8a6e4f59eaf..7b967cf95561a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts @@ -116,7 +116,7 @@ export class RbdListComponent implements OnInit, OnDestroy { } ]; - this.summaryService.get().then(resp => { + this.summaryService.get().subscribe((resp: any) => { this.loadImages(resp.executing_tasks); this.summaryDataSubscription = this.summaryService.summaryData$.subscribe((data: any) => { this.loadImages(data.executing_tasks); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-guard.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-guard.service.spec.ts new file mode 100644 index 0000000000000..6be3273e18587 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-guard.service.spec.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { Router, Routes } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { AuthGuardService } from './auth-guard.service'; +import { AuthStorageService } from './auth-storage.service'; + +describe('AuthGuardService', () => { + let service: AuthGuardService; + + @Component({selector: 'cd-login', template: ''}) + class LoginComponent { } + + const routes: Routes = [{ path: 'login', component: LoginComponent }]; + + const fakeService = { + isLoggedIn: () => true + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule.withRoutes(routes)], + providers: [AuthGuardService, { provide: AuthStorageService, useValue: fakeService }], + declarations: [LoginComponent] + }); + + service = TestBed.get(AuthGuardService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should allow the user if loggedIn', () => { + expect(service.canActivate(null, null)).toBe(true); + }); + + it( + 'should prevent user if not loggedIn and redirect to login page', + fakeAsync(() => { + const router = TestBed.get(Router); + const authStorageService = TestBed.get(AuthStorageService); + spyOn(authStorageService, 'isLoggedIn').and.returnValue(false); + + expect(service.canActivate(null, null)).toBe(false); + tick(); + expect(router.url).toBe('/login'); + }) + ); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts new file mode 100644 index 0000000000000..068a1e1d6a0a1 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts @@ -0,0 +1,35 @@ +import { AuthStorageService } from './auth-storage.service'; + +describe('AuthStorageService', () => { + let service: AuthStorageService; + const username = 'foobar'; + + beforeEach(() => { + service = new AuthStorageService(); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should store username', () => { + service.set(username); + expect(localStorage.getItem('dashboard_username')).toBe(username); + }); + + it('should remove username', () => { + service.set(username); + service.remove(); + expect(localStorage.getItem('dashboard_username')).toBe(null); + }); + + it('should be loggedIn', () => { + service.set(username); + expect(service.isLoggedIn()).toBe(true); + }); + + it('should not be loggedIn', () => { + service.remove(); + expect(service.isLoggedIn()).toBe(false); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts index 42f64e9b20d10..3cac2b4aa6cbb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts @@ -39,7 +39,7 @@ describe('FormatterService', () => { expect(service.truncate(value, 6)).toBe('1234.567899'); expect(service.truncate(value, 7)).toBe('1234.567899'); expect(service.truncate(value, 10)).toBe('1234.567899'); - expect(service.truncate(100.00, 4)).toBe('100'); + expect(service.truncate(100.0, 4)).toBe('100'); }); }); @@ -57,6 +57,9 @@ describe('FormatterService', () => { expect(service.format_number('1', 1024, formats)).toBe('1B'); expect(service.format_number('1024', 1024, formats)).toBe('1KiB'); expect(service.format_number(23.45678 * Math.pow(1024, 3), 1024, formats)).toBe('23.4568GiB'); + expect(service.format_number(23.45678 * Math.pow(1024, 3), 1024, formats, 2)).toBe( + '23.46GiB' + ); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts new file mode 100644 index 0000000000000..6ffe06433f80a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts @@ -0,0 +1,107 @@ +import { HttpClient } from '@angular/common/http'; +import { Component } from '@angular/core'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { ActivatedRouteSnapshot, Router, Routes } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; + +import 'rxjs/add/observable/of'; +import { Observable } from 'rxjs/Observable'; + +import { ModuleStatusGuardService } from './module-status-guard.service'; + +describe('ModuleStatusGuardService', () => { + let service: ModuleStatusGuardService; + + @Component({ selector: 'cd-foo', template: '' }) + class FooComponent {} + + const fakeService = { + get: () => true + }; + + const routes: Routes = [{ path: '**', component: FooComponent }]; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule.withRoutes(routes)], + providers: [ModuleStatusGuardService, { provide: HttpClient, useValue: fakeService }], + declarations: [FooComponent] + }); + + service = TestBed.get(ModuleStatusGuardService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it( + 'should test canActivate with status available', + fakeAsync(() => { + let result = false; + const route = new ActivatedRouteSnapshot(); + route.data = { + moduleStatusGuardConfig: { + apiPath: 'bar', + redirectTo: 'foo' + } + }; + const httpClient = TestBed.get(HttpClient); + spyOn(httpClient, 'get').and.returnValue(Observable.of({ available: true, message: 'foo' })); + service.canActivate(route, null).subscribe((resp) => { + result = resp; + }); + + tick(); + expect(result).toBe(true); + }) + ); + + it( + 'should test canActivateChild with status unavailable', + fakeAsync(() => { + let result = true; + const route = new ActivatedRouteSnapshot(); + route.data = { + moduleStatusGuardConfig: { + apiPath: 'bar', + redirectTo: '/foo' + } + }; + const httpClient = TestBed.get(HttpClient); + const router = TestBed.get(Router); + spyOn(httpClient, 'get').and.returnValue(Observable.of({ available: false, message: null })); + service.canActivateChild(route, null).subscribe((resp) => { + result = resp; + }); + + tick(); + expect(result).toBe(false); + expect(router.url).toBe('/foo/'); + }) + ); + + it( + 'should test canActivateChild with status unavailable', + fakeAsync(() => { + let result = true; + const route = new ActivatedRouteSnapshot(); + route.data = { + moduleStatusGuardConfig: { + apiPath: 'bar', + redirectTo: '/foo' + } + }; + const httpClient = TestBed.get(HttpClient); + const router = TestBed.get(Router); + spyOn(httpClient, 'get').and.returnValue(Observable.of(null)); + service.canActivateChild(route, null).subscribe((resp) => { + result = resp; + }); + + tick(); + expect(result).toBe(false); + expect(router.url).toBe('/foo'); + }) + ); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts index e7f5e99b68e53..8a5676aff4701 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts @@ -1,36 +1,134 @@ -import { inject, TestBed } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { ToastOptions, ToastsManager } from 'ng2-toastr'; +import * as _ from 'lodash'; +import { ToastsManager } from 'ng2-toastr'; import { NotificationType } from '../enum/notification-type.enum'; +import { FinishedTask } from '../models/finished-task'; import { NotificationService } from './notification.service'; import { TaskManagerMessageService } from './task-manager-message.service'; -import { TaskManagerService } from './task-manager.service'; describe('NotificationService', () => { + let notificationService: NotificationService; + const fakeService = { + // ToastsManager + error: () => true, + info: () => true, + success: () => true, + // TaskManagerMessageService + getDescription: () => true, + getErrorMessage: () => true, + getSuccessMessage: () => true + }; + beforeEach(() => { TestBed.configureTestingModule({ providers: [ NotificationService, - ToastsManager, - ToastOptions, - TaskManagerService, - TaskManagerMessageService + { provide: TaskManagerMessageService, useValue: fakeService }, + { provide: ToastsManager, useValue: fakeService } ] }); + + notificationService = TestBed.get(NotificationService); + notificationService.removeAll(); + }); + + it('should be created', () => { + expect(notificationService).toBeTruthy(); }); - it('should be created', - inject([NotificationService], (service: NotificationService) => { - expect(service).toBeTruthy(); - })); - - it('should not create a notification', - inject([NotificationService], (service: NotificationService) => { - expect(service).toBeTruthy(); - service.removeAll(); - const timeoutId = service.show(NotificationType.error, 'Simple test'); - service.cancel(timeoutId); - expect(service['dataSource'].getValue().length).toBe(0); - })); + it('should read empty notification list', () => { + localStorage.setItem('cdNotifications', '[]'); + expect(notificationService['dataSource'].getValue()).toEqual([]); + }); + + it( + 'should read old notifications', + fakeAsync(() => { + localStorage.setItem( + 'cdNotifications', + '[{"type":2,"message":"foobar","timestamp":"2018-05-24T09:41:32.726Z"}]' + ); + const service = new NotificationService(null, null); + expect(service['dataSource'].getValue().length).toBe(1); + }) + ); + + it( + 'should cancel a notification', + fakeAsync(() => { + const timeoutId = notificationService.show(NotificationType.error, 'Simple test'); + notificationService.cancel(timeoutId); + tick(5000); + expect(notificationService['dataSource'].getValue().length).toBe(0); + }) + ); + + it( + 'should create a success notification and save it', + fakeAsync(() => { + notificationService.show(NotificationType.success, 'Simple test'); + tick(100); + expect(notificationService['dataSource'].getValue().length).toBe(1); + expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.success); + }) + ); + + it( + 'should create an error notification and save it', + fakeAsync(() => { + notificationService.show(NotificationType.error, 'Simple test'); + tick(100); + expect(notificationService['dataSource'].getValue().length).toBe(1); + expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.error); + }) + ); + + it( + 'should create an info notification and save it', + fakeAsync(() => { + notificationService.show(NotificationType.info, 'Simple test'); + tick(100); + expect(notificationService['dataSource'].getValue().length).toBe(1); + expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.info); + }) + ); + + it( + 'should never have more then 10 notifications', + fakeAsync(() => { + for (let index = 0; index < 15; index++) { + notificationService.show(NotificationType.info, 'Simple test'); + tick(100); + } + expect(notificationService['dataSource'].getValue().length).toBe(10); + }) + ); + + it( + 'should show a success task notification', + fakeAsync(() => { + const task = _.assign(new FinishedTask(), { + success: true + }); + notificationService.notifyTask(task, true); + tick(100); + expect(notificationService['dataSource'].getValue().length).toBe(1); + expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.success); + }) + ); + + it( + 'should show a error task notification', + fakeAsync(() => { + const task = _.assign(new FinishedTask(), { + success: false + }); + notificationService.notifyTask(task); + tick(100); + expect(notificationService['dataSource'].getValue().length).toBe(1); + expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.error); + }) + ); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts index 5057e9af722ea..a80d259303134 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts @@ -19,8 +19,10 @@ export class NotificationService { KEY = 'cdNotifications'; - constructor(public toastr: ToastsManager, - private taskManagerMessageService: TaskManagerMessageService) { + constructor( + public toastr: ToastsManager, + private taskManagerMessageService: TaskManagerMessageService + ) { const stringNotifications = localStorage.getItem(this.KEY); let notifications: CdNotification[] = []; @@ -89,12 +91,16 @@ export class NotificationService { notifyTask(finishedTask: FinishedTask, success: boolean = true) { if (finishedTask.success && success) { - this.show(NotificationType.success, - this.taskManagerMessageService.getSuccessMessage(finishedTask)); + this.show( + NotificationType.success, + this.taskManagerMessageService.getSuccessMessage(finishedTask) + ); } else { - this.show(NotificationType.error, + this.show( + NotificationType.error, this.taskManagerMessageService.getErrorMessage(finishedTask), - this.taskManagerMessageService.getDescription(finishedTask)); + this.taskManagerMessageService.getDescription(finishedTask) + ); } } @@ -103,8 +109,6 @@ export class NotificationService { * @param {number} timeoutId A number representing the ID of the timeout to be canceled. */ cancel(timeoutId) { - if (timeoutId) { - clearTimeout(timeoutId); - } + clearTimeout(timeoutId); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts index 2f0c4f9cc33c6..697e9401a084b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts @@ -1,21 +1,61 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { inject, TestBed } from '@angular/core/testing'; +import { HttpClient } from '@angular/common/http'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; + +import 'rxjs/add/observable/of'; +import { Observable } from 'rxjs/Observable'; import { AuthStorageService } from './auth-storage.service'; import { SummaryService } from './summary.service'; describe('SummaryService', () => { + let summaryService: SummaryService; + let authStorageService: AuthStorageService; + + const httpClientSpy = { + get: () => + Observable.of({ + executing_tasks: [], + health_status: 'HEALTH_OK', + mgr_id: 'x', + rbd_mirroring: { errors: 0, warnings: 0 }, + rbd_pools: [], + have_mon_connection: true, + finished_tasks: [], + filesystems: [{ id: 1, name: 'cephfs_a' }] + }) + }; + beforeEach(() => { TestBed.configureTestingModule({ - providers: [SummaryService, AuthStorageService], - imports: [HttpClientTestingModule] + providers: [ + SummaryService, + AuthStorageService, + { provide: HttpClient, useValue: httpClientSpy } + ] }); + + summaryService = TestBed.get(SummaryService); + authStorageService = TestBed.get(AuthStorageService); }); - it( - 'should be created', - inject([SummaryService], (service: SummaryService) => { - expect(service).toBeTruthy(); - }) - ); + it('should be created', () => { + expect(summaryService).toBeTruthy(); + }); + + it('should call refresh', fakeAsync(() => { + authStorageService.set('foobar'); + let result = false; + summaryService.refresh(); + summaryService.summaryData$.subscribe((res) => { + result = true; + }); + tick(5000); + spyOn(summaryService, 'refresh').and.callFake(() => true); + tick(5000); + expect(result).toEqual(true); + })); + + it('should get summary', () => { + expect(summaryService.get()).toEqual(jasmine.any(Object)); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts index 2701f741c6338..a6fb493bcc6d3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts @@ -38,8 +38,6 @@ export class SummaryService { } get() { - return this.http.get('api/summary').toPromise().then((resp: any) => { - return resp; - }); + return this.http.get('api/summary'); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts new file mode 100644 index 0000000000000..5dade8f70bc07 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts @@ -0,0 +1,51 @@ +import * as _ from 'lodash'; + +import { FinishedTask } from '../models/finished-task'; +import { TaskException } from '../models/task-exception'; +import { TaskManagerMessageService } from './task-manager-message.service'; + +describe('TaskManagerMessageService', () => { + let service: TaskManagerMessageService; + let finishedTask: FinishedTask; + + beforeEach(() => { + service = new TaskManagerMessageService(); + finishedTask = new FinishedTask(); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should getDescription', () => { + finishedTask.name = 'foo'; + finishedTask.exception = _.assign(new TaskException(), { + code: 1 + }); + finishedTask.metadata = {}; + + const message = service.getDescription(finishedTask); + expect(message).toBe('Unknown Task'); + }); + + it('should getErrorMessage', () => { + finishedTask.exception = _.assign(new TaskException(), { + code: 1 + }); + const message = service.getErrorMessage(finishedTask); + expect(message).toBe(undefined); + }); + + it('should getSuccessMessage', () => { + const message = service.getSuccessMessage(finishedTask); + expect(message).toBe('Task executed successfully'); + }); + + it('should test if all messages methods are defined', () => { + _.forIn(service.messages, (value, key) => { + expect(value.descr({})).toBeTruthy(); + expect(value.success({})).toBeTruthy(); + expect(value.error({})).toBeTruthy(); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.spec.ts new file mode 100644 index 0000000000000..1daa3044c3c8c --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.spec.ts @@ -0,0 +1,71 @@ +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; + +import * as _ from 'lodash'; +import 'rxjs/add/observable/of'; +import { Subject } from 'rxjs/Subject'; + +import { SummaryService } from './summary.service'; +import { TaskManagerService } from './task-manager.service'; + +describe('TaskManagerService', () => { + let taskManagerService: TaskManagerService; + + const summaryDataSource = new Subject(); + const fakeService = { + summaryData$: summaryDataSource.asObservable() + }; + + const summary = { + executing_tasks: [], + health_status: 'HEALTH_OK', + mgr_id: 'x', + rbd_mirroring: { errors: 0, warnings: 0 }, + rbd_pools: [], + have_mon_connection: true, + finished_tasks: [{ name: 'foo', metadata: {} }], + filesystems: [{ id: 1, name: 'cephfs_a' }] + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [TaskManagerService, { provide: SummaryService, useValue: fakeService }] + }); + + taskManagerService = TestBed.get(TaskManagerService); + }); + + it('should be created', () => { + expect(taskManagerService).toBeTruthy(); + }); + + it( + 'should subscribe and be notified when task is finished', + fakeAsync(() => { + let called = false; + taskManagerService.subscribe('foo', {}, () => (called = true)); + expect(taskManagerService.subscriptions.length).toBe(1); + + summaryDataSource.next(summary); + tick(); + + expect(called).toEqual(true); + expect(taskManagerService.subscriptions).toEqual([]); + }) + ); + + it( + 'should subscribe and process executing taks', + fakeAsync(() => { + let called = false; + taskManagerService.subscribe('foo', {}, () => (called = true)); + const original_subscriptions = _.cloneDeep(taskManagerService.subscriptions); + const new_summary = _.assign(summary, { + executing_tasks: [{ name: 'foo', metadata: {} }], + finished_tasks: [] + }); + summaryDataSource.next(summary); + tick(); + expect(taskManagerService.subscriptions).toEqual(original_subscriptions); + }) + ); +}); -- 2.39.5