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