]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts
import 15.2.1 Octopus source
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / forms / cd-validators.spec.ts
CommitLineData
11fdf7f2
TL
1import { fakeAsync, tick } from '@angular/core/testing';
2import { FormControl, Validators } from '@angular/forms';
3
4import { of as observableOf } from 'rxjs';
5
6import { FormHelper } from '../../../testing/unit-test-helper';
7import { CdFormGroup } from './cd-form-group';
8import { CdValidators } from './cd-validators';
9
10describe('CdValidators', () => {
11 let formHelper: FormHelper;
12 let form: CdFormGroup;
13
9f95a23c
TL
14 const expectValid = (value: any) => formHelper.expectValidChange('x', value);
15 const expectPatternError = (value: any) => formHelper.expectErrorChange('x', value, 'pattern');
16 const updateValidity = (controlName: string) => form.get(controlName).updateValueAndValidity();
11fdf7f2
TL
17
18 beforeEach(() => {
19 form = new CdFormGroup({
20 x: new FormControl()
21 });
22 formHelper = new FormHelper(form);
23 });
24
25 describe('email', () => {
26 beforeEach(() => {
27 form.get('x').setValidators(CdValidators.email);
28 });
29
30 it('should not error on an empty email address', () => {
31 expectValid('');
32 });
33
34 it('should not error on valid email address', () => {
35 expectValid('dashboard@ceph.com');
36 });
37
38 it('should error on invalid email address', () => {
39 formHelper.expectErrorChange('x', 'xyz', 'email');
40 });
41 });
42
43 describe('ip validator', () => {
44 describe('IPv4', () => {
45 beforeEach(() => {
46 form.get('x').setValidators(CdValidators.ip(4));
47 });
48
49 it('should not error on empty addresses', () => {
50 expectValid('');
51 });
52
53 it('should accept valid address', () => {
54 expectValid('19.117.23.141');
55 });
56
57 it('should error containing whitespace', () => {
58 expectPatternError('155.144.133.122 ');
59 expectPatternError('155. 144.133 .122');
60 expectPatternError(' 155.144.133.122');
61 });
62
63 it('should error containing invalid char', () => {
64 expectPatternError('155.144.eee.122 ');
65 expectPatternError('155.1?.133 .1&2');
66 });
67
68 it('should error containing blocks higher than 255', () => {
69 expectPatternError('155.270.133.122');
70 expectPatternError('155.144.133.290');
71 });
72 });
73
74 describe('IPv4', () => {
75 beforeEach(() => {
76 form.get('x').setValidators(CdValidators.ip(6));
77 });
78
79 it('should not error on empty IPv6 addresses', () => {
80 expectValid('');
81 });
82
83 it('should accept valid IPv6 address', () => {
84 expectValid('c4dc:1475:cb0b:24ed:3c80:468b:70cd:1a95');
85 });
86
87 it('should error on IPv6 address containing too many blocks', () => {
88 formHelper.expectErrorChange(
89 'x',
90 'c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95:a3f3',
91 'pattern'
92 );
93 });
94
95 it('should error on IPv6 address containing more than 4 digits per block', () => {
96 expectPatternError('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95');
97 });
98
99 it('should error on IPv6 address containing whitespace', () => {
100 expectPatternError('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95 ');
101 expectPatternError('c4dc:14753 :cb0b:24ed:3c80 :468b:70cd :1a95');
102 expectPatternError(' c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95');
103 });
104
105 it('should error on IPv6 address containing invalid char', () => {
106 expectPatternError('c4dx:14753:cb0b:24ed:3c80:468b:70cd:1a95');
107 expectPatternError('c4da:14753:cb0b:24ed:3$80:468b:70cd:1a95');
108 });
109 });
110
111 it('should accept valid IPv4/6 addresses if not protocol version is given', () => {
112 const x = form.get('x');
113 x.setValidators(CdValidators.ip());
114 expectValid('19.117.23.141');
115 expectValid('c4dc:1475:cb0b:24ed:3c80:468b:70cd:1a95');
116 });
117 });
118
119 describe('uuid validator', () => {
9f95a23c 120 const expectUuidError = (value: string) =>
11fdf7f2
TL
121 formHelper.expectErrorChange('x', value, 'invalidUuid', true);
122 beforeEach(() => {
123 form.get('x').setValidators(CdValidators.uuid());
124 });
125
126 it('should accept empty value', () => {
127 expectValid('');
128 });
129
130 it('should accept valid version 1 uuid', () => {
131 expectValid('171af0b2-c305-11e8-a355-529269fb1459');
132 });
133
134 it('should accept valid version 4 uuid', () => {
135 expectValid('e33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
136 });
137
138 it('should error on uuid containing too many blocks', () => {
139 expectUuidError('e33bbcb6-fcc3-40b1-ae81-3f81706a35d5-23d3');
140 });
141
142 it('should error on uuid containing too many chars in block', () => {
143 expectUuidError('aae33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
144 });
145
146 it('should error on uuid containing invalid char', () => {
147 expectUuidError('x33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
148 expectUuidError('$33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
149 });
150 });
151
152 describe('number validator', () => {
153 beforeEach(() => {
154 form.get('x').setValidators(CdValidators.number());
155 });
156
157 it('should accept empty value', () => {
158 expectValid('');
159 });
160
161 it('should accept numbers', () => {
162 expectValid(42);
163 expectValid(-42);
164 expectValid('42');
165 });
166
167 it('should error on decimal numbers', () => {
168 expectPatternError(42.3);
169 expectPatternError(-42.3);
170 expectPatternError('42.3');
171 });
172
173 it('should error on chars', () => {
174 expectPatternError('char');
175 expectPatternError('42char');
176 });
177
178 it('should error on whitespaces', () => {
179 expectPatternError('42 ');
180 expectPatternError('4 2');
181 });
182 });
183
184 describe('number validator (without negative values)', () => {
185 beforeEach(() => {
186 form.get('x').setValidators(CdValidators.number(false));
187 });
188
189 it('should accept positive numbers', () => {
190 expectValid(42);
191 expectValid('42');
192 });
193
194 it('should error on negative numbers', () => {
195 expectPatternError(-42);
196 expectPatternError('-42');
197 });
198 });
199
200 describe('decimal number validator', () => {
201 beforeEach(() => {
202 form.get('x').setValidators(CdValidators.decimalNumber());
203 });
204
205 it('should accept empty value', () => {
206 expectValid('');
207 });
208
209 it('should accept numbers and decimal numbers', () => {
210 expectValid(42);
211 expectValid(-42);
212 expectValid(42.3);
213 expectValid(-42.3);
214 expectValid('42');
215 expectValid('42.3');
216 });
217
218 it('should error on chars', () => {
219 expectPatternError('42e');
220 expectPatternError('e42.3');
221 });
222
223 it('should error on whitespaces', () => {
224 expectPatternError('42.3 ');
225 expectPatternError('42 .3');
226 });
227 });
228
229 describe('decimal number validator (without negative values)', () => {
230 beforeEach(() => {
231 form.get('x').setValidators(CdValidators.decimalNumber(false));
232 });
233
234 it('should accept positive numbers and decimals', () => {
235 expectValid(42);
236 expectValid(42.3);
237 expectValid('42');
238 expectValid('42.3');
239 });
240
241 it('should error on negative numbers and decimals', () => {
242 expectPatternError(-42);
243 expectPatternError('-42');
244 expectPatternError(-42.3);
245 expectPatternError('-42.3');
246 });
247 });
248
249 describe('requiredIf', () => {
250 beforeEach(() => {
251 form = new CdFormGroup({
252 x: new FormControl(true),
253 y: new FormControl('abc'),
254 z: new FormControl('')
255 });
256 formHelper = new FormHelper(form);
257 });
258
259 it('should not error because all conditions are fulfilled', () => {
260 formHelper.setValue('z', 'zyx');
261 const validatorFn = CdValidators.requiredIf({
262 x: true,
263 y: 'abc'
264 });
265 expect(validatorFn(form.get('z'))).toBeNull();
266 });
267
268 it('should not error because of unmet prerequisites', () => {
269 // Define prereqs that do not match the current values of the form fields.
270 const validatorFn = CdValidators.requiredIf({
271 x: false,
272 y: 'xyz'
273 });
274 // The validator must succeed because the prereqs do not match, so the
275 // validation of the 'z' control will be skipped.
276 expect(validatorFn(form.get('z'))).toBeNull();
277 });
278
279 it('should error because of an empty value', () => {
280 // Define prereqs that force the validator to validate the value of
281 // the 'z' control.
282 const validatorFn = CdValidators.requiredIf({
283 x: true,
284 y: 'abc'
285 });
286 // The validator must fail because the value of control 'z' is empty.
287 expect(validatorFn(form.get('z'))).toEqual({ required: true });
288 });
289
290 it('should not error because of unsuccessful condition', () => {
291 formHelper.setValue('z', 'zyx');
292 // Define prereqs that force the validator to validate the value of
293 // the 'z' control.
294 const validatorFn = CdValidators.requiredIf(
295 {
296 x: true,
297 z: 'zyx'
298 },
299 () => false
300 );
301 expect(validatorFn(form.get('z'))).toBeNull();
302 });
303
304 it('should error because of successful condition', () => {
9f95a23c 305 const conditionFn = (value: string) => {
11fdf7f2
TL
306 return value === 'abc';
307 };
308 // Define prereqs that force the validator to validate the value of
309 // the 'y' control.
310 const validatorFn = CdValidators.requiredIf(
311 {
312 x: true,
313 z: ''
314 },
315 conditionFn
316 );
317 expect(validatorFn(form.get('y'))).toEqual({ required: true });
318 });
319 });
320
321 describe('custom validation', () => {
322 beforeEach(() => {
323 form = new CdFormGroup({
801d1391
TL
324 x: new FormControl(
325 3,
326 CdValidators.custom('odd', (x: number) => x % 2 === 1)
327 ),
11fdf7f2
TL
328 y: new FormControl(
329 5,
9f95a23c 330 CdValidators.custom('not-dividable-by-x', (y: number) => {
11fdf7f2
TL
331 const x = (form && form.get('x').value) || 1;
332 return y % x !== 0;
333 })
334 )
335 });
336 formHelper = new FormHelper(form);
337 });
338
339 it('should test error and valid condition for odd x', () => {
340 formHelper.expectError('x', 'odd');
341 expectValid(4);
342 });
343
344 it('should test error and valid condition for y if its dividable by x', () => {
345 updateValidity('y');
346 formHelper.expectError('y', 'not-dividable-by-x');
347 formHelper.expectValidChange('y', 6);
348 });
349 });
350
351 describe('validate if condition', () => {
352 beforeEach(() => {
353 form = new CdFormGroup({
354 x: new FormControl(3),
355 y: new FormControl(5)
356 });
357 CdValidators.validateIf(form.get('x'), () => ((form && form.get('y').value) || 0) > 10, [
9f95a23c
TL
358 CdValidators.custom('min', (x: number) => x < 7),
359 CdValidators.custom('max', (x: number) => x > 12)
11fdf7f2
TL
360 ]);
361 formHelper = new FormHelper(form);
362 });
363
364 it('should test min error', () => {
365 formHelper.setValue('y', 11);
366 updateValidity('x');
367 formHelper.expectError('x', 'min');
368 });
369
370 it('should test max error', () => {
371 formHelper.setValue('y', 11);
372 formHelper.setValue('x', 13);
373 formHelper.expectError('x', 'max');
374 });
375
376 it('should test valid number with validation', () => {
377 formHelper.setValue('y', 11);
378 formHelper.setValue('x', 12);
379 formHelper.expectValid('x');
380 });
381
382 it('should validate automatically if dependency controls are defined', () => {
383 CdValidators.validateIf(
384 form.get('x'),
385 () => ((form && form.getValue('y')) || 0) > 10,
386 [Validators.min(7), Validators.max(12)],
387 undefined,
388 [form.get('y')]
389 );
390
391 formHelper.expectValid('x');
392 formHelper.setValue('y', 13);
393 formHelper.expectError('x', 'min');
394 });
395
396 it('should always validate the permanentValidators', () => {
397 CdValidators.validateIf(
398 form.get('x'),
399 () => ((form && form.getValue('y')) || 0) > 10,
400 [Validators.min(7), Validators.max(12)],
401 [Validators.required],
402 [form.get('y')]
403 );
404
405 formHelper.expectValid('x');
406 formHelper.setValue('x', '');
407 formHelper.expectError('x', 'required');
408 });
409 });
410
411 describe('match', () => {
412 let y: FormControl;
413
414 beforeEach(() => {
415 y = new FormControl('aaa');
416 form = new CdFormGroup({
417 x: new FormControl('aaa'),
418 y: y
419 });
420 formHelper = new FormHelper(form);
421 });
422
423 it('should error when values are different', () => {
424 formHelper.setValue('y', 'aab');
425 CdValidators.match('x', 'y')(form);
426 formHelper.expectValid('x');
427 formHelper.expectError('y', 'match');
428 });
429
430 it('should not error when values are equal', () => {
431 CdValidators.match('x', 'y')(form);
432 formHelper.expectValid('x');
433 formHelper.expectValid('y');
434 });
435
436 it('should unset error when values are equal', () => {
437 y.setErrors({ match: true });
438 CdValidators.match('x', 'y')(form);
439 formHelper.expectValid('x');
440 formHelper.expectValid('y');
441 });
442
443 it('should keep other existing errors', () => {
444 y.setErrors({ match: true, notUnique: true });
445 CdValidators.match('x', 'y')(form);
446 formHelper.expectValid('x');
447 formHelper.expectError('y', 'notUnique');
448 });
449 });
450
451 describe('unique', () => {
452 beforeEach(() => {
453 form = new CdFormGroup({
454 x: new FormControl(
455 '',
456 null,
457 CdValidators.unique((value) => {
458 return observableOf('xyz' === value);
459 })
460 )
461 });
462 formHelper = new FormHelper(form);
463 });
464
465 it('should not error because of empty input', () => {
466 expectValid('');
467 });
468
469 it('should not error because of not existing input', fakeAsync(() => {
470 formHelper.setValue('x', 'abc', true);
471 tick(500);
472 formHelper.expectValid('x');
473 }));
474
475 it('should error because of already existing input', fakeAsync(() => {
476 formHelper.setValue('x', 'xyz', true);
477 tick(500);
478 formHelper.expectError('x', 'notUnique');
479 }));
480 });
9f95a23c
TL
481
482 describe('composeIf', () => {
483 beforeEach(() => {
484 form = new CdFormGroup({
485 x: new FormControl(true),
486 y: new FormControl('abc'),
487 z: new FormControl('')
488 });
489 formHelper = new FormHelper(form);
490 });
491
492 it('should not error because all conditions are fulfilled', () => {
493 formHelper.setValue('z', 'zyx');
494 const validatorFn = CdValidators.composeIf(
495 {
496 x: true,
497 y: 'abc'
498 },
499 [Validators.required]
500 );
501 expect(validatorFn(form.get('z'))).toBeNull();
502 });
503
504 it('should not error because of unmet prerequisites', () => {
505 // Define prereqs that do not match the current values of the form fields.
506 const validatorFn = CdValidators.composeIf(
507 {
508 x: false,
509 y: 'xyz'
510 },
511 [Validators.required]
512 );
513 // The validator must succeed because the prereqs do not match, so the
514 // validation of the 'z' control will be skipped.
515 expect(validatorFn(form.get('z'))).toBeNull();
516 });
517
518 it('should error because of an empty value', () => {
519 // Define prereqs that force the validator to validate the value of
520 // the 'z' control.
521 const validatorFn = CdValidators.composeIf(
522 {
523 x: true,
524 y: 'abc'
525 },
526 [Validators.required]
527 );
528 // The validator must fail because the value of control 'z' is empty.
529 expect(validatorFn(form.get('z'))).toEqual({ required: true });
530 });
531 });
532
533 describe('dimmlessBinary validators', () => {
534 const i18nMock = (a: string, b: { value: string }) => a.replace('{{value}}', b.value);
535
536 beforeEach(() => {
537 form = new CdFormGroup({
538 x: new FormControl('2 KiB', [CdValidators.binaryMin(1024), CdValidators.binaryMax(3072)])
539 });
540 formHelper = new FormHelper(form);
541 });
542
543 it('should not raise exception an exception for valid change', () => {
544 formHelper.expectValidChange('x', '2.5 KiB');
545 });
546
547 it('should not raise minimum error', () => {
548 formHelper.expectErrorChange('x', '0.5 KiB', 'binaryMin');
549 expect(form.get('x').getError('binaryMin')(i18nMock)).toBe(
550 'Size has to be at least 1 KiB or more'
551 );
552 });
553
554 it('should not raise maximum error', () => {
555 formHelper.expectErrorChange('x', '4 KiB', 'binaryMax');
556 expect(form.get('x').getError('binaryMax')(i18nMock)).toBe(
557 'Size has to be at most 3 KiB or less'
558 );
559 });
560 });
561
562 describe('passwordPolicy', () => {
563 let valid: boolean;
564 let callbackCalled: boolean;
565
566 const fakeUserService = {
567 validatePassword: () => {
568 return observableOf({ valid: valid, credits: 17, valuation: 'foo' });
569 }
570 };
571
572 beforeEach(() => {
573 callbackCalled = false;
574 form = new CdFormGroup({
575 x: new FormControl(
576 '',
577 null,
578 CdValidators.passwordPolicy(
579 fakeUserService,
580 () => 'admin',
581 () => {
582 callbackCalled = true;
583 }
584 )
585 )
586 });
587 formHelper = new FormHelper(form);
588 });
589
590 it('should not error because of empty input', () => {
591 expectValid('');
592 expect(callbackCalled).toBeTruthy();
593 });
594
595 it('should not error because password matches the policy', fakeAsync(() => {
596 valid = true;
597 formHelper.setValue('x', 'abc', true);
598 tick(500);
599 formHelper.expectValid('x');
600 }));
601
602 it('should error because password does not match the policy', fakeAsync(() => {
603 valid = false;
604 formHelper.setValue('x', 'xyz', true);
605 tick(500);
606 formHelper.expectError('x', 'passwordPolicy');
607 }));
608
609 it('should call the callback function', fakeAsync(() => {
610 formHelper.setValue('x', 'xyz', true);
611 tick(500);
612 expect(callbackCalled).toBeTruthy();
613 }));
614 });
11fdf7f2 615});