]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts
0cb297c3f1fe5416c92a939469ea15a27ad97c75
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / rgw / rgw-bucket-form / rgw-bucket-form.component.spec.ts
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { FormControl, ReactiveFormsModule } from '@angular/forms';
4 import { Router } from '@angular/router';
5 import { RouterTestingModule } from '@angular/router/testing';
6
7 import * as _ from 'lodash';
8 import { ToastrModule } from 'ngx-toastr';
9 import { of as observableOf } from 'rxjs';
10
11 import { configureTestBed, FormHelper, i18nProviders } from '../../../../testing/unit-test-helper';
12 import { RgwBucketService } from '../../../shared/api/rgw-bucket.service';
13 import { RgwSiteService } from '../../../shared/api/rgw-site.service';
14 import { NotificationType } from '../../../shared/enum/notification-type.enum';
15 import { NotificationService } from '../../../shared/services/notification.service';
16 import { SharedModule } from '../../../shared/shared.module';
17 import { RgwBucketMfaDelete } from '../models/rgw-bucket-mfa-delete';
18 import { RgwBucketVersioning } from '../models/rgw-bucket-versioning';
19 import { RgwBucketFormComponent } from './rgw-bucket-form.component';
20
21 describe('RgwBucketFormComponent', () => {
22 let component: RgwBucketFormComponent;
23 let fixture: ComponentFixture<RgwBucketFormComponent>;
24 let rgwBucketService: RgwBucketService;
25 let getPlacementTargetsSpy: jasmine.Spy;
26 let rgwBucketServiceGetSpy: jasmine.Spy;
27 let formHelper: FormHelper;
28
29 configureTestBed({
30 declarations: [RgwBucketFormComponent],
31 imports: [
32 HttpClientTestingModule,
33 ReactiveFormsModule,
34 RouterTestingModule,
35 SharedModule,
36 ToastrModule.forRoot()
37 ],
38 providers: [i18nProviders]
39 });
40
41 beforeEach(() => {
42 fixture = TestBed.createComponent(RgwBucketFormComponent);
43 component = fixture.componentInstance;
44 rgwBucketService = TestBed.get(RgwBucketService);
45 rgwBucketServiceGetSpy = spyOn(rgwBucketService, 'get');
46 getPlacementTargetsSpy = spyOn(TestBed.get(RgwSiteService), 'getPlacementTargets');
47 formHelper = new FormHelper(component.bucketForm);
48 });
49
50 it('should create', () => {
51 expect(component).toBeTruthy();
52 });
53
54 describe('bucketNameValidator', () => {
55 const testValidator = (name: string, valid: boolean) => {
56 const validatorFn = component.bucketNameValidator();
57 const ctrl = new FormControl(name);
58 ctrl.markAsDirty();
59 const validatorPromise = validatorFn(ctrl);
60 expect(validatorPromise instanceof Promise).toBeTruthy();
61 if (validatorPromise instanceof Promise) {
62 validatorPromise.then((resp) => {
63 if (valid) {
64 expect(resp).toBe(null);
65 } else {
66 expect(resp instanceof Object).toBeTruthy();
67 expect(resp.bucketNameInvalid).toBeTruthy();
68 }
69 });
70 }
71 };
72
73 it('should validate empty name', () => {
74 testValidator('', true);
75 });
76
77 it('bucket names cannot be formatted as IP address', () => {
78 testValidator('172.10.4.51', false);
79 });
80
81 it('bucket name must be >= 3 characters long (1/2)', () => {
82 testValidator('ab', false);
83 });
84
85 it('bucket name must be >= 3 characters long (2/2)', () => {
86 testValidator('abc', true);
87 });
88
89 it('bucket name must be <= than 63 characters long (1/2)', () => {
90 testValidator(_.repeat('a', 64), false);
91 });
92
93 it('bucket name must be <= than 63 characters long (2/2)', () => {
94 testValidator(_.repeat('a', 63), true);
95 });
96
97 it('bucket names must not contain uppercase characters or underscores (1/2)', () => {
98 testValidator('iAmInvalid', false);
99 });
100
101 it('bucket names must not contain uppercase characters or underscores (2/2)', () => {
102 testValidator('i_am_invalid', false);
103 });
104
105 it('bucket names with invalid labels (1/3)', () => {
106 testValidator('abc.1def.Ghi2', false);
107 });
108
109 it('bucket names with invalid labels (2/3)', () => {
110 testValidator('abc.1-xy', false);
111 });
112
113 it('bucket names with invalid labels (3/3)', () => {
114 testValidator('abc.*def', false);
115 });
116
117 it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (1/3)', () => {
118 testValidator('xyz.abc', true);
119 });
120
121 it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (2/3)', () => {
122 testValidator('abc.1-def', true);
123 });
124
125 it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (3/3)', () => {
126 testValidator('abc.ghi2', true);
127 });
128
129 it('bucket names must be unique', () => {
130 spyOn(rgwBucketService, 'enumerate').and.returnValue(observableOf(['abcd']));
131 const validatorFn = component.bucketNameValidator();
132 const ctrl = new FormControl('abcd');
133 ctrl.markAsDirty();
134 const validatorPromise = validatorFn(ctrl);
135 expect(validatorPromise instanceof Promise).toBeTruthy();
136 if (validatorPromise instanceof Promise) {
137 validatorPromise.then((resp) => {
138 expect(resp instanceof Object).toBeTruthy();
139 expect(resp.bucketNameExists).toBeTruthy();
140 });
141 }
142 });
143
144 it('should get zonegroup and placement targets', () => {
145 const payload: Record<string, any> = {
146 zonegroup: 'default',
147 placement_targets: [
148 {
149 name: 'default-placement',
150 data_pool: 'default.rgw.buckets.data'
151 },
152 {
153 name: 'placement-target2',
154 data_pool: 'placement-target2.rgw.buckets.data'
155 }
156 ]
157 };
158 getPlacementTargetsSpy.and.returnValue(observableOf(payload));
159 fixture.detectChanges();
160
161 expect(component.zonegroup).toBe(payload.zonegroup);
162 const placementTargets = [];
163 for (const placementTarget of payload['placement_targets']) {
164 placementTarget[
165 'description'
166 ] = `${placementTarget['name']} (pool: ${placementTarget['data_pool']})`;
167 placementTargets.push(placementTarget);
168 }
169 expect(component.placementTargets).toEqual(placementTargets);
170 });
171 });
172
173 describe('submit form', () => {
174 let notificationService: NotificationService;
175
176 beforeEach(() => {
177 spyOn(TestBed.get(Router), 'navigate').and.stub();
178 notificationService = TestBed.get(NotificationService);
179 spyOn(notificationService, 'show');
180 });
181
182 it('should validate name', () => {
183 component.editing = false;
184 component.createForm();
185 const control = component.bucketForm.get('bid');
186 expect(_.isFunction(control.asyncValidator)).toBeTruthy();
187 });
188
189 it('should not validate name', () => {
190 component.editing = true;
191 component.createForm();
192 const control = component.bucketForm.get('bid');
193 expect(control.asyncValidator).toBeNull();
194 });
195
196 it('tests create success notification', () => {
197 spyOn(rgwBucketService, 'create').and.returnValue(observableOf([]));
198 component.editing = false;
199 component.bucketForm.markAsDirty();
200 component.submit();
201 expect(notificationService.show).toHaveBeenCalledWith(
202 NotificationType.success,
203 'Created Object Gateway bucket ""'
204 );
205 });
206
207 it('tests update success notification', () => {
208 spyOn(rgwBucketService, 'update').and.returnValue(observableOf([]));
209 component.editing = true;
210 component.bucketForm.markAsDirty();
211 component.submit();
212 expect(notificationService.show).toHaveBeenCalledWith(
213 NotificationType.success,
214 'Updated Object Gateway bucket "".'
215 );
216 });
217 });
218
219 describe('mfa credentials', () => {
220 const checkMfaCredentialsVisibility = (
221 fakeResponse: object,
222 versioningChecked: boolean,
223 mfaDeleteChecked: boolean,
224 expectedVisibility: boolean
225 ) => {
226 component['route'].params = observableOf({ bid: 'bid' });
227 component.editing = true;
228 rgwBucketServiceGetSpy.and.returnValue(observableOf(fakeResponse));
229 component.ngOnInit();
230 component.isVersioningEnabled = versioningChecked;
231 component.isMfaDeleteEnabled = mfaDeleteChecked;
232 fixture.detectChanges();
233
234 const mfaTokenSerial = fixture.debugElement.nativeElement.querySelector('#mfa-token-serial');
235 const mfaTokenPin = fixture.debugElement.nativeElement.querySelector('#mfa-token-pin');
236 if (expectedVisibility) {
237 expect(mfaTokenSerial).toBeTruthy();
238 expect(mfaTokenPin).toBeTruthy();
239 } else {
240 expect(mfaTokenSerial).toBeFalsy();
241 expect(mfaTokenPin).toBeFalsy();
242 }
243 };
244
245 it('inputs should be visible when required', () => {
246 checkMfaCredentialsVisibility(
247 {
248 versioning: RgwBucketVersioning.SUSPENDED,
249 mfa_delete: RgwBucketMfaDelete.DISABLED
250 },
251 false,
252 false,
253 false
254 );
255 checkMfaCredentialsVisibility(
256 {
257 versioning: RgwBucketVersioning.SUSPENDED,
258 mfa_delete: RgwBucketMfaDelete.DISABLED
259 },
260 true,
261 false,
262 false
263 );
264 checkMfaCredentialsVisibility(
265 {
266 versioning: RgwBucketVersioning.ENABLED,
267 mfa_delete: RgwBucketMfaDelete.DISABLED
268 },
269 false,
270 false,
271 false
272 );
273 checkMfaCredentialsVisibility(
274 {
275 versioning: RgwBucketVersioning.ENABLED,
276 mfa_delete: RgwBucketMfaDelete.ENABLED
277 },
278 true,
279 true,
280 false
281 );
282 checkMfaCredentialsVisibility(
283 {
284 versioning: RgwBucketVersioning.SUSPENDED,
285 mfa_delete: RgwBucketMfaDelete.DISABLED
286 },
287 false,
288 true,
289 true
290 );
291 checkMfaCredentialsVisibility(
292 {
293 versioning: RgwBucketVersioning.SUSPENDED,
294 mfa_delete: RgwBucketMfaDelete.ENABLED
295 },
296 false,
297 false,
298 true
299 );
300 checkMfaCredentialsVisibility(
301 {
302 versioning: RgwBucketVersioning.SUSPENDED,
303 mfa_delete: RgwBucketMfaDelete.ENABLED
304 },
305 true,
306 true,
307 true
308 );
309 checkMfaCredentialsVisibility(
310 {
311 versioning: RgwBucketVersioning.ENABLED,
312 mfa_delete: RgwBucketMfaDelete.ENABLED
313 },
314 false,
315 true,
316 true
317 );
318 });
319 });
320
321 describe('object locking', () => {
322 const setDaysAndYears = (fn: (name: string) => void) => {
323 ['lock_retention_period_days', 'lock_retention_period_years'].forEach(fn);
324 };
325
326 const expectPatternLockError = (value: string) => {
327 formHelper.setValue('lock_enabled', true, true);
328 setDaysAndYears((name: string) => {
329 formHelper.setValue(name, value);
330 formHelper.expectError(name, 'pattern');
331 });
332 };
333
334 const expectValidLockInputs = (enabled: boolean, mode: string, days: string, years: string) => {
335 formHelper.setValue('lock_enabled', enabled);
336 formHelper.setValue('lock_mode', mode);
337 formHelper.setValue('lock_retention_period_days', days);
338 formHelper.setValue('lock_retention_period_years', years);
339 [
340 'lock_enabled',
341 'lock_mode',
342 'lock_retention_period_days',
343 'lock_retention_period_years'
344 ].forEach((name) => {
345 const control = component.bucketForm.get(name);
346 expect(control.valid).toBeTruthy();
347 expect(control.errors).toBeNull();
348 });
349 };
350
351 it('should check lock enabled checkbox [mode=create]', () => {
352 component.createForm();
353 const control = component.bucketForm.get('lock_enabled');
354 expect(control.disabled).toBeFalsy();
355 });
356
357 it('should check lock enabled checkbox [mode=edit]', () => {
358 component.editing = true;
359 component.createForm();
360 const control = component.bucketForm.get('lock_enabled');
361 expect(control.disabled).toBeTruthy();
362 });
363
364 it('should have the "eitherDaysOrYears" error', () => {
365 formHelper.setValue('lock_enabled', true);
366 setDaysAndYears((name: string) => {
367 const control = component.bucketForm.get(name);
368 control.updateValueAndValidity();
369 expect(control.value).toBe(0);
370 expect(control.invalid).toBeTruthy();
371 formHelper.expectError(control, 'eitherDaysOrYears');
372 });
373 });
374
375 it('should have the "pattern" error [1]', () => {
376 expectPatternLockError('-1');
377 });
378
379 it('should have the "pattern" error [2]', () => {
380 expectPatternLockError('1.2');
381 });
382
383 it('should have valid values [1]', () => {
384 expectValidLockInputs(true, 'Governance', '0', '1');
385 });
386
387 it('should have valid values [2]', () => {
388 expectValidLockInputs(false, 'Compliance', '100', '0');
389 });
390 });
391 });