]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts
import 15.2.5
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / pool / crush-rule-form-modal / crush-rule-form-modal.component.spec.ts
CommitLineData
9f95a23c
TL
1import { HttpClientTestingModule } from '@angular/common/http/testing';
2import { ComponentFixture, TestBed } from '@angular/core/testing';
3import { RouterTestingModule } from '@angular/router/testing';
4
5import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
6import { BsModalRef } from 'ngx-bootstrap/modal';
7import { ToastrModule } from 'ngx-toastr';
8import { of } from 'rxjs';
9
10import {
11 configureTestBed,
12 FixtureHelper,
13 FormHelper,
f6b5b4d7
TL
14 i18nProviders,
15 Mocks
9f95a23c
TL
16} from '../../../../testing/unit-test-helper';
17import { CrushRuleService } from '../../../shared/api/crush-rule.service';
18import { CrushNode } from '../../../shared/models/crush-node';
19import { CrushRuleConfig } from '../../../shared/models/crush-rule';
20import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
21import { PoolModule } from '../pool.module';
22import { CrushRuleFormModalComponent } from './crush-rule-form-modal.component';
23
24describe('CrushRuleFormComponent', () => {
25 let component: CrushRuleFormModalComponent;
26 let crushRuleService: CrushRuleService;
27 let fixture: ComponentFixture<CrushRuleFormModalComponent>;
28 let formHelper: FormHelper;
29 let fixtureHelper: FixtureHelper;
30 let data: { names: string[]; nodes: CrushNode[] };
31
9f95a23c
TL
32 // Object contains functions to get something
33 const get = {
34 nodeByName: (name: string): CrushNode => data.nodes.find((node) => node.name === name),
35 nodesByNames: (names: string[]): CrushNode[] => names.map(get.nodeByName)
36 };
37
38 // Expects that are used frequently
39 const assert = {
40 failureDomains: (nodes: CrushNode[], types: string[]) => {
41 const expectation = {};
42 types.forEach((type) => (expectation[type] = nodes.filter((node) => node.type === type)));
e306af50 43 const keys = component.failureDomainKeys;
9f95a23c
TL
44 expect(keys).toEqual(types);
45 keys.forEach((key) => {
46 expect(component.failureDomains[key].length).toBe(expectation[key].length);
47 });
48 },
49 formFieldValues: (root: CrushNode, failureDomain: string, device: string) => {
50 expect(component.form.value).toEqual({
51 name: '',
52 root,
53 failure_domain: failureDomain,
54 device_class: device
55 });
56 },
57 valuesOnRootChange: (
58 rootName: string,
59 expectedFailureDomain: string,
60 expectedDevice: string
61 ) => {
62 const node = get.nodeByName(rootName);
63 formHelper.setValue('root', node);
64 assert.formFieldValues(node, expectedFailureDomain, expectedDevice);
65 },
66 creation: (rule: CrushRuleConfig) => {
67 formHelper.setValue('name', rule.name);
68 fixture.detectChanges();
69 component.onSubmit();
70 expect(crushRuleService.create).toHaveBeenCalledWith(rule);
71 }
72 };
73
74 configureTestBed({
75 imports: [
76 HttpClientTestingModule,
77 RouterTestingModule,
78 ToastrModule.forRoot(),
79 PoolModule,
80 NgBootstrapFormValidationModule.forRoot()
81 ],
82 providers: [CrushRuleService, BsModalRef, i18nProviders]
83 });
84
85 beforeEach(() => {
86 fixture = TestBed.createComponent(CrushRuleFormModalComponent);
87 fixtureHelper = new FixtureHelper(fixture);
88 component = fixture.componentInstance;
89 formHelper = new FormHelper(component.form);
90 crushRuleService = TestBed.get(CrushRuleService);
91 data = {
92 names: ['rule1', 'rule2'],
93 /**
94 * Create the following test crush map:
95 * > default
96 * --> ssd-host
97 * ----> 3x osd with ssd
98 * --> mix-host
99 * ----> hdd-rack
100 * ------> 2x osd-rack with hdd
101 * ----> ssd-rack
102 * ------> 2x osd-rack with ssd
103 */
f6b5b4d7 104 nodes: Mocks.getCrushMap()
9f95a23c
TL
105 };
106 spyOn(crushRuleService, 'getInfo').and.callFake(() => of(data));
107 fixture.detectChanges();
108 });
109
110 it('should create', () => {
111 expect(component).toBeTruthy();
112 });
113
114 it('calls listing to get rules on ngInit', () => {
115 expect(crushRuleService.getInfo).toHaveBeenCalled();
116 expect(component.names.length).toBe(2);
e306af50 117 expect(component.buckets.length).toBe(5);
9f95a23c
TL
118 });
119
120 describe('lists', () => {
121 afterEach(() => {
122 // The available buckets should not change
123 expect(component.buckets).toEqual(
124 get.nodesByNames(['default', 'hdd-rack', 'mix-host', 'ssd-host', 'ssd-rack'])
125 );
126 });
127
128 it('has the following lists after init', () => {
129 assert.failureDomains(data.nodes, ['host', 'osd', 'osd-rack', 'rack']); // Not root as root only exist once
130 expect(component.devices).toEqual(['hdd', 'ssd']);
131 });
132
133 it('has the following lists after selection of ssd-host', () => {
134 formHelper.setValue('root', get.nodeByName('ssd-host'));
135 assert.failureDomains(get.nodesByNames(['osd.0', 'osd.1', 'osd.2']), ['osd']); // Not host as it only exist once
136 expect(component.devices).toEqual(['ssd']);
137 });
138
139 it('has the following lists after selection of mix-host', () => {
140 formHelper.setValue('root', get.nodeByName('mix-host'));
141 expect(component.devices).toEqual(['hdd', 'ssd']);
142 assert.failureDomains(
143 get.nodesByNames(['hdd-rack', 'ssd-rack', 'osd2.0', 'osd2.1', 'osd2.0', 'osd2.1']),
144 ['osd-rack', 'rack']
145 );
146 });
147 });
148
149 describe('selection', () => {
150 it('selects the first root after init automatically', () => {
151 assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
152 });
153
154 it('should select all values automatically by selecting "ssd-host" as root', () => {
155 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
156 });
157
158 it('selects automatically the most common failure domain', () => {
159 // Select mix-host as mix-host has multiple failure domains (osd-rack and rack)
160 assert.valuesOnRootChange('mix-host', 'osd-rack', '');
161 });
162
163 it('should override automatic selections', () => {
164 assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
165 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
166 assert.valuesOnRootChange('mix-host', 'osd-rack', '');
167 });
168
169 it('should not override manual selections if possible', () => {
170 formHelper.setValue('failure_domain', 'rack', true);
171 formHelper.setValue('device_class', 'ssd', true);
172 assert.valuesOnRootChange('mix-host', 'rack', 'ssd');
173 });
174
175 it('should preselect device by domain selection', () => {
176 formHelper.setValue('failure_domain', 'osd', true);
177 assert.formFieldValues(get.nodeByName('default'), 'osd', 'ssd');
178 });
179 });
180
181 describe('form validation', () => {
182 it(`isn't valid if name is not set`, () => {
183 expect(component.form.invalid).toBeTruthy();
184 formHelper.setValue('name', 'someProfileName');
185 expect(component.form.valid).toBeTruthy();
186 });
187
188 it('sets name invalid', () => {
189 component.names = ['awesomeProfileName'];
190 formHelper.expectErrorChange('name', 'awesomeProfileName', 'uniqueName');
191 formHelper.expectErrorChange('name', 'some invalid text', 'pattern');
192 formHelper.expectErrorChange('name', null, 'required');
193 });
194
195 it(`should show all default form controls`, () => {
196 // name
197 // root (preselected(first root))
198 // failure_domain (preselected=type that is most common)
199 // device_class (preselected=any if multiple or some type if only one device type)
200 fixtureHelper.expectIdElementsVisible(
201 ['name', 'root', 'failure_domain', 'device_class'],
202 true
203 );
204 });
205 });
206
207 describe('submission', () => {
208 beforeEach(() => {
209 const taskWrapper = TestBed.get(TaskWrapperService);
210 spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
211 spyOn(crushRuleService, 'create').and.stub();
212 });
213
214 it('creates a rule with only required fields', () => {
f6b5b4d7 215 assert.creation(Mocks.getCrushRuleConfig('default-rule', 'default', 'osd-rack'));
9f95a23c
TL
216 });
217
218 it('creates a rule with all fields', () => {
219 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
f6b5b4d7 220 assert.creation(Mocks.getCrushRuleConfig('ssd-host-rule', 'ssd-host', 'osd', 'ssd'));
9f95a23c
TL
221 });
222 });
223});