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 { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
6 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
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 functions to get something
33 nodeByName: (name: string): CrushNode => data.nodes.find((node) => node.name === name),
34 nodesByNames: (names: string[]): CrushNode[] => names.map(get.nodeByName)
37 // Expects that are used frequently
39 failureDomains: (nodes: CrushNode[], types: string[]) => {
40 const expectation = {};
41 types.forEach((type) => (expectation[type] = nodes.filter((node) => node.type === type)));
42 const keys = component.failureDomainKeys;
43 expect(keys).toEqual(types);
44 keys.forEach((key) => {
45 expect(component.failureDomains[key].length).toBe(expectation[key].length);
48 formFieldValues: (root: CrushNode, failureDomain: string, device: string) => {
49 expect(component.form.value).toEqual({
52 failure_domain: failureDomain,
58 expectedFailureDomain: string,
59 expectedDevice: string
61 const node = get.nodeByName(rootName);
62 formHelper.setValue('root', node);
63 assert.formFieldValues(node, expectedFailureDomain, expectedDevice);
65 creation: (rule: CrushRuleConfig) => {
66 formHelper.setValue('name', rule.name);
67 fixture.detectChanges();
69 expect(crushRuleService.create).toHaveBeenCalledWith(rule);
75 HttpClientTestingModule,
77 ToastrModule.forRoot(),
79 NgBootstrapFormValidationModule.forRoot()
81 providers: [CrushRuleService, NgbActiveModal]
85 fixture = TestBed.createComponent(CrushRuleFormModalComponent);
86 fixtureHelper = new FixtureHelper(fixture);
87 component = fixture.componentInstance;
88 formHelper = new FormHelper(component.form);
89 crushRuleService = TestBed.inject(CrushRuleService);
91 names: ['rule1', 'rule2'],
93 * Create the following test crush map:
96 * ----> 3x osd with ssd
99 * ------> 2x osd-rack with hdd
101 * ------> 2x osd-rack with ssd
103 nodes: Mocks.getCrushMap()
105 spyOn(crushRuleService, 'getInfo').and.callFake(() => of(data));
106 fixture.detectChanges();
109 it('should create', () => {
110 expect(component).toBeTruthy();
113 it('calls listing to get rules on ngInit', () => {
114 expect(crushRuleService.getInfo).toHaveBeenCalled();
115 expect(component.names.length).toBe(2);
116 expect(component.buckets.length).toBe(5);
119 describe('lists', () => {
121 // The available buckets should not change
122 expect(component.buckets).toEqual(
123 get.nodesByNames(['default', 'hdd-rack', 'mix-host', 'ssd-host', 'ssd-rack'])
127 it('has the following lists after init', () => {
128 assert.failureDomains(data.nodes, ['host', 'osd', 'osd-rack', 'rack']); // Not root as root only exist once
129 expect(component.devices).toEqual(['hdd', 'ssd']);
132 it('has the following lists after selection of ssd-host', () => {
133 formHelper.setValue('root', get.nodeByName('ssd-host'));
134 assert.failureDomains(get.nodesByNames(['osd.0', 'osd.1', 'osd.2']), ['osd']); // Not host as it only exist once
135 expect(component.devices).toEqual(['ssd']);
138 it('has the following lists after selection of mix-host', () => {
139 formHelper.setValue('root', get.nodeByName('mix-host'));
140 expect(component.devices).toEqual(['hdd', 'ssd']);
141 assert.failureDomains(
142 get.nodesByNames(['hdd-rack', 'ssd-rack', 'osd2.0', 'osd2.1', 'osd2.0', 'osd2.1']),
148 describe('selection', () => {
149 it('selects the first root after init automatically', () => {
150 assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
153 it('should select all values automatically by selecting "ssd-host" as root', () => {
154 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
157 it('selects automatically the most common failure domain', () => {
158 // Select mix-host as mix-host has multiple failure domains (osd-rack and rack)
159 assert.valuesOnRootChange('mix-host', 'osd-rack', '');
162 it('should override automatic selections', () => {
163 assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
164 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
165 assert.valuesOnRootChange('mix-host', 'osd-rack', '');
168 it('should not override manual selections if possible', () => {
169 formHelper.setValue('failure_domain', 'rack', true);
170 formHelper.setValue('device_class', 'ssd', true);
171 assert.valuesOnRootChange('mix-host', 'rack', 'ssd');
174 it('should preselect device by domain selection', () => {
175 formHelper.setValue('failure_domain', 'osd', true);
176 assert.formFieldValues(get.nodeByName('default'), 'osd', 'ssd');
180 describe('form validation', () => {
181 it(`isn't valid if name is not set`, () => {
182 expect(component.form.invalid).toBeTruthy();
183 formHelper.setValue('name', 'someProfileName');
184 expect(component.form.valid).toBeTruthy();
187 it('sets name invalid', () => {
188 component.names = ['awesomeProfileName'];
189 formHelper.expectErrorChange('name', 'awesomeProfileName', 'uniqueName');
190 formHelper.expectErrorChange('name', 'some invalid text', 'pattern');
191 formHelper.expectErrorChange('name', null, 'required');
194 it(`should show all default form controls`, () => {
196 // root (preselected(first root))
197 // failure_domain (preselected=type that is most common)
198 // device_class (preselected=any if multiple or some type if only one device type)
199 fixtureHelper.expectIdElementsVisible(
200 ['name', 'root', 'failure_domain', 'device_class'],
206 describe('submission', () => {
208 const taskWrapper = TestBed.inject(TaskWrapperService);
209 spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
210 spyOn(crushRuleService, 'create').and.stub();
213 it('creates a rule with only required fields', () => {
214 assert.creation(Mocks.getCrushRuleConfig('default-rule', 'default', 'osd-rack'));
217 it('creates a rule with all fields', () => {
218 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
219 assert.creation(Mocks.getCrushRuleConfig('ssd-host-rule', 'ssd-host', 'osd', 'ssd'));