1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { RouterTestingModule } from '@angular/router/testing';
5 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
6 import { BsModalRef } from 'ngx-bootstrap/modal';
7 import { ToastrModule } from 'ngx-toastr';
8 import { of } from 'rxjs';
15 } from '../../../../testing/unit-test-helper';
16 import { CrushRuleService } from '../../../shared/api/crush-rule.service';
17 import { CrushNode } from '../../../shared/models/crush-node';
18 import { CrushRuleConfig } from '../../../shared/models/crush-rule';
19 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
20 import { PoolModule } from '../pool.module';
21 import { CrushRuleFormModalComponent } from './crush-rule-form-modal.component';
23 describe('CrushRuleFormComponent', () => {
24 let component: CrushRuleFormModalComponent;
25 let crushRuleService: CrushRuleService;
26 let fixture: ComponentFixture<CrushRuleFormModalComponent>;
27 let formHelper: FormHelper;
28 let fixtureHelper: FixtureHelper;
29 let data: { names: string[]; nodes: CrushNode[] };
31 // Object contains mock functions
41 return { name, type, type_id, id, children, device_class };
46 failure_domain: string,
48 ): CrushRuleConfig => ({
56 // Object contains functions to get something
58 nodeByName: (name: string): CrushNode => data.nodes.find((node) => node.name === name),
59 nodesByNames: (names: string[]): CrushNode[] => names.map(get.nodeByName)
62 // Expects that are used frequently
64 failureDomains: (nodes: CrushNode[], types: string[]) => {
65 const expectation = {};
66 types.forEach((type) => (expectation[type] = nodes.filter((node) => node.type === type)));
67 const keys = component.failureDomainKeys();
68 expect(keys).toEqual(types);
69 keys.forEach((key) => {
70 expect(component.failureDomains[key].length).toBe(expectation[key].length);
73 formFieldValues: (root: CrushNode, failureDomain: string, device: string) => {
74 expect(component.form.value).toEqual({
77 failure_domain: failureDomain,
83 expectedFailureDomain: string,
84 expectedDevice: string
86 const node = get.nodeByName(rootName);
87 formHelper.setValue('root', node);
88 assert.formFieldValues(node, expectedFailureDomain, expectedDevice);
90 creation: (rule: CrushRuleConfig) => {
91 formHelper.setValue('name', rule.name);
92 fixture.detectChanges();
94 expect(crushRuleService.create).toHaveBeenCalledWith(rule);
100 HttpClientTestingModule,
102 ToastrModule.forRoot(),
104 NgBootstrapFormValidationModule.forRoot()
106 providers: [CrushRuleService, BsModalRef, i18nProviders]
110 fixture = TestBed.createComponent(CrushRuleFormModalComponent);
111 fixtureHelper = new FixtureHelper(fixture);
112 component = fixture.componentInstance;
113 formHelper = new FormHelper(component.form);
114 crushRuleService = TestBed.get(CrushRuleService);
116 names: ['rule1', 'rule2'],
118 * Create the following test crush map:
121 * ----> 3x osd with ssd
124 * ------> 2x osd-rack with hdd
126 * ------> 2x osd-rack with ssd
130 mock.node('default', -1, 'root', 11, [-2, -3]),
132 mock.node('ssd-host', -2, 'host', 1, [1, 0, 2]),
133 mock.node('osd.0', 0, 'osd', 0, undefined, 'ssd'),
134 mock.node('osd.1', 1, 'osd', 0, undefined, 'ssd'),
135 mock.node('osd.2', 2, 'osd', 0, undefined, 'ssd'),
136 // SSD and HDD mixed devices host
137 mock.node('mix-host', -3, 'host', 1, [-4, -5]),
139 mock.node('hdd-rack', -4, 'rack', 3, [3, 4]),
140 mock.node('osd2.0', 3, 'osd-rack', 0, undefined, 'hdd'),
141 mock.node('osd2.1', 4, 'osd-rack', 0, undefined, 'hdd'),
143 mock.node('ssd-rack', -5, 'rack', 3, [5, 6]),
144 mock.node('osd2.0', 5, 'osd-rack', 0, undefined, 'ssd'),
145 mock.node('osd2.1', 6, 'osd-rack', 0, undefined, 'ssd')
148 spyOn(crushRuleService, 'getInfo').and.callFake(() => of(data));
149 fixture.detectChanges();
152 it('should create', () => {
153 expect(component).toBeTruthy();
156 it('calls listing to get rules on ngInit', () => {
157 expect(crushRuleService.getInfo).toHaveBeenCalled();
158 expect(component.names.length).toBe(2);
159 expect(component['nodes'].length).toBe(12);
162 describe('lists', () => {
164 // The available buckets should not change
165 expect(component.buckets).toEqual(
166 get.nodesByNames(['default', 'hdd-rack', 'mix-host', 'ssd-host', 'ssd-rack'])
170 it('has the following lists after init', () => {
171 assert.failureDomains(data.nodes, ['host', 'osd', 'osd-rack', 'rack']); // Not root as root only exist once
172 expect(component.devices).toEqual(['hdd', 'ssd']);
175 it('has the following lists after selection of ssd-host', () => {
176 formHelper.setValue('root', get.nodeByName('ssd-host'));
177 assert.failureDomains(get.nodesByNames(['osd.0', 'osd.1', 'osd.2']), ['osd']); // Not host as it only exist once
178 expect(component.devices).toEqual(['ssd']);
181 it('has the following lists after selection of mix-host', () => {
182 formHelper.setValue('root', get.nodeByName('mix-host'));
183 expect(component.devices).toEqual(['hdd', 'ssd']);
184 assert.failureDomains(
185 get.nodesByNames(['hdd-rack', 'ssd-rack', 'osd2.0', 'osd2.1', 'osd2.0', 'osd2.1']),
191 describe('selection', () => {
192 it('selects the first root after init automatically', () => {
193 assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
196 it('should select all values automatically by selecting "ssd-host" as root', () => {
197 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
200 it('selects automatically the most common failure domain', () => {
201 // Select mix-host as mix-host has multiple failure domains (osd-rack and rack)
202 assert.valuesOnRootChange('mix-host', 'osd-rack', '');
205 it('should override automatic selections', () => {
206 assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
207 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
208 assert.valuesOnRootChange('mix-host', 'osd-rack', '');
211 it('should not override manual selections if possible', () => {
212 formHelper.setValue('failure_domain', 'rack', true);
213 formHelper.setValue('device_class', 'ssd', true);
214 assert.valuesOnRootChange('mix-host', 'rack', 'ssd');
217 it('should preselect device by domain selection', () => {
218 formHelper.setValue('failure_domain', 'osd', true);
219 assert.formFieldValues(get.nodeByName('default'), 'osd', 'ssd');
223 describe('form validation', () => {
224 it(`isn't valid if name is not set`, () => {
225 expect(component.form.invalid).toBeTruthy();
226 formHelper.setValue('name', 'someProfileName');
227 expect(component.form.valid).toBeTruthy();
230 it('sets name invalid', () => {
231 component.names = ['awesomeProfileName'];
232 formHelper.expectErrorChange('name', 'awesomeProfileName', 'uniqueName');
233 formHelper.expectErrorChange('name', 'some invalid text', 'pattern');
234 formHelper.expectErrorChange('name', null, 'required');
237 it(`should show all default form controls`, () => {
239 // root (preselected(first root))
240 // failure_domain (preselected=type that is most common)
241 // device_class (preselected=any if multiple or some type if only one device type)
242 fixtureHelper.expectIdElementsVisible(
243 ['name', 'root', 'failure_domain', 'device_class'],
249 describe('submission', () => {
251 const taskWrapper = TestBed.get(TaskWrapperService);
252 spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
253 spyOn(crushRuleService, 'create').and.stub();
256 it('creates a rule with only required fields', () => {
257 assert.creation(mock.rule('default-rule', 'default', 'osd-rack'));
260 it('creates a rule with all fields', () => {
261 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
262 assert.creation(mock.rule('ssd-host-rule', 'ssd-host', 'osd', 'ssd'));