]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.spec.ts
update ceph source to reef 18.2.0
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / services / api-interceptor.service.spec.ts
CommitLineData
11fdf7f2
TL
1import { HttpClient, HttpErrorResponse } from '@angular/common/http';
2import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3import { fakeAsync, TestBed, tick } from '@angular/core/testing';
4import { Router } from '@angular/router';
5
494da23a 6import { ToastrService } from 'ngx-toastr';
11fdf7f2 7
f67539c2
TL
8import { AppModule } from '~/app/app.module';
9import { configureTestBed } from '~/testing/unit-test-helper';
9f95a23c 10import { NotificationType } from '../enum/notification-type.enum';
11fdf7f2
TL
11import { CdNotification, CdNotificationConfig } from '../models/cd-notification';
12import { ApiInterceptorService } from './api-interceptor.service';
13import { NotificationService } from './notification.service';
14
15describe('ApiInterceptorService', () => {
16 let notificationService: NotificationService;
17 let httpTesting: HttpTestingController;
18 let httpClient: HttpClient;
19 let router: Router;
20 const url = 'api/xyz';
21
f67539c2 22 const httpError = (error: any, errorOpts: object, done = (_resp: any): any => undefined) => {
11fdf7f2 23 httpClient.get(url).subscribe(
f67539c2 24 () => true,
11fdf7f2
TL
25 (resp) => {
26 // Error must have been forwarded by the interceptor.
27 expect(resp instanceof HttpErrorResponse).toBeTruthy();
28 done(resp);
29 }
30 );
31 httpTesting.expectOne(url).error(error, errorOpts);
32 };
33
9f95a23c 34 const runRouterTest = (errorOpts: object, expectedCallParams: any[]) => {
11fdf7f2
TL
35 httpError(new ErrorEvent('abc'), errorOpts);
36 httpTesting.verify();
37 expect(router.navigate).toHaveBeenCalledWith(...expectedCallParams);
38 };
39
9f95a23c
TL
40 const runNotificationTest = (
41 error: any,
42 errorOpts: object,
43 expectedCallParams: CdNotification
44 ) => {
11fdf7f2
TL
45 httpError(error, errorOpts);
46 httpTesting.verify();
47 expect(notificationService.show).toHaveBeenCalled();
48 expect(notificationService.save).toHaveBeenCalledWith(expectedCallParams);
49 };
50
9f95a23c
TL
51 const createCdNotification = (
52 type: NotificationType,
53 title?: string,
54 message?: string,
55 options?: any,
56 application?: string
57 ) => {
11fdf7f2
TL
58 return new CdNotification(new CdNotificationConfig(type, title, message, options, application));
59 };
60
61 configureTestBed({
62 imports: [AppModule, HttpClientTestingModule],
63 providers: [
64 NotificationService,
11fdf7f2 65 {
494da23a 66 provide: ToastrService,
11fdf7f2
TL
67 useValue: {
68 error: () => true
69 }
70 }
71 ]
72 });
73
74 beforeEach(() => {
75 const baseTime = new Date('2022-02-22');
76 spyOn(global, 'Date').and.returnValue(baseTime);
77
f67539c2
TL
78 httpClient = TestBed.inject(HttpClient);
79 httpTesting = TestBed.inject(HttpTestingController);
11fdf7f2 80
f67539c2 81 notificationService = TestBed.inject(NotificationService);
11fdf7f2
TL
82 spyOn(notificationService, 'show').and.callThrough();
83 spyOn(notificationService, 'save');
84
f67539c2 85 router = TestBed.inject(Router);
11fdf7f2
TL
86 spyOn(router, 'navigate');
87 });
88
89 it('should be created', () => {
f67539c2 90 const service = TestBed.inject(ApiInterceptorService);
11fdf7f2
TL
91 expect(service).toBeTruthy();
92 });
93
94 describe('test different error behaviours', () => {
95 beforeEach(() => {
96 spyOn(window, 'setTimeout').and.callFake((fn) => fn());
97 });
98
99 it('should redirect 401', () => {
100 runRouterTest(
101 {
102 status: 401
103 },
104 [['/login']]
105 );
106 });
107
108 it('should redirect 403', () => {
109 runRouterTest(
110 {
111 status: 403
112 },
f67539c2 113 [['error'], {'state': {'header': 'Access Denied', 'icon': 'fa fa-lock', 'message': 'Sorry, you don’t have permission to view this page or resource.', 'source': 'forbidden'}}] // prettier-ignore
11fdf7f2
TL
114 );
115 });
116
117 it('should show notification (error string)', () => {
118 runNotificationTest(
119 'foobar',
120 {
121 status: 500,
122 statusText: 'Foo Bar'
123 },
124 createCdNotification(0, '500 - Foo Bar', 'foobar')
125 );
126 });
127
128 it('should show notification (error object, triggered from backend)', () => {
129 runNotificationTest(
130 { detail: 'abc' },
131 {
132 status: 504,
133 statusText: 'AAA bbb CCC'
134 },
135 createCdNotification(0, '504 - AAA bbb CCC', 'abc')
136 );
137 });
138
139 it('should show notification (error object with unknown keys)', () => {
140 runNotificationTest(
141 { type: 'error' },
142 {
143 status: 0,
144 statusText: 'Unknown Error',
145 message: 'Http failure response for (unknown url): 0 Unknown Error',
146 name: 'HttpErrorResponse',
147 ok: false,
148 url: null
149 },
150 createCdNotification(
151 0,
152 '0 - Unknown Error',
153 'Http failure response for api/xyz: 0 Unknown Error'
154 )
155 );
156 });
157
158 it('should show notification (undefined error)', () => {
159 runNotificationTest(
160 undefined,
161 {
162 status: 502
163 },
164 createCdNotification(0, '502 - Unknown Error', 'Http failure response for api/xyz: 502 ')
165 );
166 });
167
168 it('should show 400 notification', () => {
169 spyOn(notificationService, 'notifyTask');
170 httpError({ task: { name: 'mytask', metadata: { component: 'foobar' } } }, { status: 400 });
171 httpTesting.verify();
172 expect(notificationService.show).toHaveBeenCalledTimes(0);
173 expect(notificationService.notifyTask).toHaveBeenCalledWith({
174 exception: { task: { metadata: { component: 'foobar' }, name: 'mytask' } },
175 metadata: { component: 'foobar' },
176 name: 'mytask',
177 success: false
178 });
179 });
180 });
181
182 describe('interceptor error handling', () => {
9f95a23c 183 const expectSaveToHaveBeenCalled = (called: boolean) => {
81eedcae
TL
184 tick(510);
185 if (called) {
186 expect(notificationService.save).toHaveBeenCalled();
187 } else {
188 expect(notificationService.save).not.toHaveBeenCalled();
189 }
190 };
191
11fdf7f2
TL
192 it('should show default behaviour', fakeAsync(() => {
193 httpError(undefined, { status: 500 });
81eedcae 194 expectSaveToHaveBeenCalled(true);
11fdf7f2
TL
195 }));
196
197 it('should prevent the default behaviour with preventDefault', fakeAsync(() => {
198 httpError(undefined, { status: 500 }, (resp) => resp.preventDefault());
81eedcae 199 expectSaveToHaveBeenCalled(false);
11fdf7f2
TL
200 }));
201
202 it('should be able to use preventDefault with 400 errors', fakeAsync(() => {
203 httpError(
204 { task: { name: 'someName', metadata: { component: 'someComponent' } } },
205 { status: 400 },
206 (resp) => resp.preventDefault()
207 );
81eedcae 208 expectSaveToHaveBeenCalled(false);
11fdf7f2
TL
209 }));
210
211 it('should prevent the default behaviour by status code', fakeAsync(() => {
212 httpError(undefined, { status: 500 }, (resp) => resp.ignoreStatusCode(500));
81eedcae 213 expectSaveToHaveBeenCalled(false);
11fdf7f2
TL
214 }));
215
216 it('should use different application icon (default Ceph) in error message', fakeAsync(() => {
217 const msg = 'Cannot connect to Alertmanager';
218 httpError(undefined, { status: 500 }, (resp) => {
219 (resp.application = 'Prometheus'), (resp.message = msg);
220 });
81eedcae 221 expectSaveToHaveBeenCalled(true);
11fdf7f2
TL
222 expect(notificationService.save).toHaveBeenCalledWith(
223 createCdNotification(0, '500 - Unknown Error', msg, undefined, 'Prometheus')
224 );
225 }));
226 });
227});