summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts')
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts210
1 files changed, 210 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts
new file mode 100644
index 000000000..2b8c9e5cf
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts
@@ -0,0 +1,210 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { ToastrModule } from 'ngx-toastr';
+import { of } from 'rxjs';
+
+import { CrushRuleService } from '~/app/shared/api/crush-rule.service';
+import { CrushNode } from '~/app/shared/models/crush-node';
+import { CrushRuleConfig } from '~/app/shared/models/crush-rule';
+import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+import { configureTestBed, FixtureHelper, FormHelper, Mocks } from '~/testing/unit-test-helper';
+import { PoolModule } from '../pool.module';
+import { CrushRuleFormModalComponent } from './crush-rule-form-modal.component';
+
+describe('CrushRuleFormComponent', () => {
+ let component: CrushRuleFormModalComponent;
+ let crushRuleService: CrushRuleService;
+ let fixture: ComponentFixture<CrushRuleFormModalComponent>;
+ let formHelper: FormHelper;
+ let fixtureHelper: FixtureHelper;
+ let data: { names: string[]; nodes: CrushNode[] };
+
+ // Object contains functions to get something
+ const get = {
+ nodeByName: (name: string): CrushNode => data.nodes.find((node) => node.name === name),
+ nodesByNames: (names: string[]): CrushNode[] => names.map(get.nodeByName)
+ };
+
+ // Expects that are used frequently
+ const assert = {
+ failureDomains: (nodes: CrushNode[], types: string[]) => {
+ const expectation = {};
+ types.forEach((type) => (expectation[type] = nodes.filter((node) => node.type === type)));
+ const keys = component.failureDomainKeys;
+ expect(keys).toEqual(types);
+ keys.forEach((key) => {
+ expect(component.failureDomains[key].length).toBe(expectation[key].length);
+ });
+ },
+ formFieldValues: (root: CrushNode, failureDomain: string, device: string) => {
+ expect(component.form.value).toEqual({
+ name: '',
+ root,
+ failure_domain: failureDomain,
+ device_class: device
+ });
+ },
+ valuesOnRootChange: (
+ rootName: string,
+ expectedFailureDomain: string,
+ expectedDevice: string
+ ) => {
+ const node = get.nodeByName(rootName);
+ formHelper.setValue('root', node);
+ assert.formFieldValues(node, expectedFailureDomain, expectedDevice);
+ },
+ creation: (rule: CrushRuleConfig) => {
+ formHelper.setValue('name', rule.name);
+ fixture.detectChanges();
+ component.onSubmit();
+ expect(crushRuleService.create).toHaveBeenCalledWith(rule);
+ }
+ };
+
+ configureTestBed({
+ imports: [HttpClientTestingModule, RouterTestingModule, ToastrModule.forRoot(), PoolModule],
+ providers: [CrushRuleService, NgbActiveModal]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CrushRuleFormModalComponent);
+ fixtureHelper = new FixtureHelper(fixture);
+ component = fixture.componentInstance;
+ formHelper = new FormHelper(component.form);
+ crushRuleService = TestBed.inject(CrushRuleService);
+ data = {
+ names: ['rule1', 'rule2'],
+ /**
+ * Create the following test crush map:
+ * > default
+ * --> ssd-host
+ * ----> 3x osd with ssd
+ * --> mix-host
+ * ----> hdd-rack
+ * ------> 2x osd-rack with hdd
+ * ----> ssd-rack
+ * ------> 2x osd-rack with ssd
+ */
+ nodes: Mocks.getCrushMap()
+ };
+ spyOn(crushRuleService, 'getInfo').and.callFake(() => of(data));
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('calls listing to get rules on ngInit', () => {
+ expect(crushRuleService.getInfo).toHaveBeenCalled();
+ expect(component.names.length).toBe(2);
+ expect(component.buckets.length).toBe(5);
+ });
+
+ describe('lists', () => {
+ afterEach(() => {
+ // The available buckets should not change
+ expect(component.buckets).toEqual(
+ get.nodesByNames(['default', 'hdd-rack', 'mix-host', 'ssd-host', 'ssd-rack'])
+ );
+ });
+
+ it('has the following lists after init', () => {
+ assert.failureDomains(data.nodes, ['host', 'osd', 'osd-rack', 'rack']); // Not root as root only exist once
+ expect(component.devices).toEqual(['hdd', 'ssd']);
+ });
+
+ it('has the following lists after selection of ssd-host', () => {
+ formHelper.setValue('root', get.nodeByName('ssd-host'));
+ assert.failureDomains(get.nodesByNames(['osd.0', 'osd.1', 'osd.2']), ['osd']); // Not host as it only exist once
+ expect(component.devices).toEqual(['ssd']);
+ });
+
+ it('has the following lists after selection of mix-host', () => {
+ formHelper.setValue('root', get.nodeByName('mix-host'));
+ expect(component.devices).toEqual(['hdd', 'ssd']);
+ assert.failureDomains(
+ get.nodesByNames(['hdd-rack', 'ssd-rack', 'osd2.0', 'osd2.1', 'osd2.0', 'osd2.1']),
+ ['osd-rack', 'rack']
+ );
+ });
+ });
+
+ describe('selection', () => {
+ it('selects the first root after init automatically', () => {
+ assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
+ });
+
+ it('should select all values automatically by selecting "ssd-host" as root', () => {
+ assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
+ });
+
+ it('selects automatically the most common failure domain', () => {
+ // Select mix-host as mix-host has multiple failure domains (osd-rack and rack)
+ assert.valuesOnRootChange('mix-host', 'osd-rack', '');
+ });
+
+ it('should override automatic selections', () => {
+ assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
+ assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
+ assert.valuesOnRootChange('mix-host', 'osd-rack', '');
+ });
+
+ it('should not override manual selections if possible', () => {
+ formHelper.setValue('failure_domain', 'rack', true);
+ formHelper.setValue('device_class', 'ssd', true);
+ assert.valuesOnRootChange('mix-host', 'rack', 'ssd');
+ });
+
+ it('should preselect device by domain selection', () => {
+ formHelper.setValue('failure_domain', 'osd', true);
+ assert.formFieldValues(get.nodeByName('default'), 'osd', 'ssd');
+ });
+ });
+
+ describe('form validation', () => {
+ it(`isn't valid if name is not set`, () => {
+ expect(component.form.invalid).toBeTruthy();
+ formHelper.setValue('name', 'someProfileName');
+ expect(component.form.valid).toBeTruthy();
+ });
+
+ it('sets name invalid', () => {
+ component.names = ['awesomeProfileName'];
+ formHelper.expectErrorChange('name', 'awesomeProfileName', 'uniqueName');
+ formHelper.expectErrorChange('name', 'some invalid text', 'pattern');
+ formHelper.expectErrorChange('name', null, 'required');
+ });
+
+ it(`should show all default form controls`, () => {
+ // name
+ // root (preselected(first root))
+ // failure_domain (preselected=type that is most common)
+ // device_class (preselected=any if multiple or some type if only one device type)
+ fixtureHelper.expectIdElementsVisible(
+ ['name', 'root', 'failure_domain', 'device_class'],
+ true
+ );
+ });
+ });
+
+ describe('submission', () => {
+ beforeEach(() => {
+ const taskWrapper = TestBed.inject(TaskWrapperService);
+ spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
+ spyOn(crushRuleService, 'create').and.stub();
+ });
+
+ it('creates a rule with only required fields', () => {
+ assert.creation(Mocks.getCrushRuleConfig('default-rule', 'default', 'osd-rack'));
+ });
+
+ it('creates a rule with all fields', () => {
+ assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
+ assert.creation(Mocks.getCrushRuleConfig('ssd-host-rule', 'ssd-host', 'osd', 'ssd'));
+ });
+ });
+});