]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / testing / unit-test-helper.ts
index 81047f906377875b702311ffde261c3a505d78a8..66033f0330f07ccacbe6a1a8999ec8cdd4cd75a8 100644 (file)
@@ -1,37 +1,64 @@
-import { LOCALE_ID, TRANSLATIONS, TRANSLATIONS_FORMAT, Type } from '@angular/core';
+import { DebugElement, Type } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { AbstractControl } from '@angular/forms';
 import { By } from '@angular/platform-browser';
+import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
 
-import { I18n } from '@ngx-translate/i18n-polyfill';
+import { NgbModal, NgbNav, NgbNavItem } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
 import { configureTestSuite } from 'ng-bullet';
-import { BsModalRef } from 'ngx-bootstrap/modal';
-
-import { TableActionsComponent } from '../app/shared/datatable/table-actions/table-actions.component';
-import { Icons } from '../app/shared/enum/icons.enum';
-import { CdFormGroup } from '../app/shared/forms/cd-form-group';
-import { CdTableAction } from '../app/shared/models/cd-table-action';
-import { CdTableSelection } from '../app/shared/models/cd-table-selection';
-import { CrushNode } from '../app/shared/models/crush-node';
-import { CrushRule, CrushRuleConfig } from '../app/shared/models/crush-rule';
-import { Permission } from '../app/shared/models/permissions';
+import { of } from 'rxjs';
+
+import { InventoryDevice } from '~/app/ceph/cluster/inventory/inventory-devices/inventory-device.model';
+import { Pool } from '~/app/ceph/pool/pool';
+import { RgwDaemon } from '~/app/ceph/rgw/models/rgw-daemon';
+import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
+import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
+import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { CrushNode } from '~/app/shared/models/crush-node';
+import { CrushRule, CrushRuleConfig } from '~/app/shared/models/crush-rule';
+import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
+import { Permission } from '~/app/shared/models/permissions';
 import {
   AlertmanagerAlert,
   AlertmanagerNotification,
   AlertmanagerNotificationAlert,
   PrometheusRule
-} from '../app/shared/models/prometheus-alerts';
-
-export function configureTestBed(configuration: any) {
-  configureTestSuite(() => TestBed.configureTestingModule(configuration));
+} from '~/app/shared/models/prometheus-alerts';
+
+export function configureTestBed(configuration: any, entryComponents?: any) {
+  configureTestSuite(() => {
+    if (entryComponents) {
+      // Declare entryComponents without having to add them to a module
+      // This is needed since Jest doesn't yet support not declaring entryComponents
+      TestBed.configureTestingModule(configuration).overrideModule(BrowserDynamicTestingModule, {
+        set: { entryComponents: entryComponents }
+      });
+    } else {
+      TestBed.configureTestingModule(configuration);
+    }
+  });
 }
 
 export class PermissionHelper {
   tac: TableActionsComponent;
   permission: Permission;
+  selection: { single: object; multiple: object[] };
 
-  constructor(permission: Permission) {
+  /**
+   * @param permission The permissions used by this test.
+   * @param selection The selection used by this test. Configure this if
+   *   the table actions require a more complex selection object to perform
+   *   a correct test run.
+   *   Defaults to `{ single: {}, multiple: [{}, {}] }`.
+   */
+  constructor(permission: Permission, selection?: { single: object; multiple: object[] }) {
     this.permission = permission;
+    this.selection = _.defaultTo(selection, { single: {}, multiple: [{}, {}] });
   }
 
   setPermissionsAndGetActions(tableActions: CdTableAction[]): any {
@@ -75,11 +102,13 @@ export class PermissionHelper {
   testScenarios() {
     const result: any = {};
     // 'multiple selections'
-    result.multiple = this.testScenario([{}, {}]);
+    result.multiple = this.testScenario(this.selection.multiple);
     // 'select executing item'
-    result.executing = this.testScenario([{ cdExecuting: 'someAction' }]);
+    result.executing = this.testScenario([
+      _.merge({ cdExecuting: 'someAction' }, this.selection.single)
+    ]);
     // 'select non-executing item'
-    result.single = this.testScenario([{}]);
+    result.single = this.testScenario([this.selection.single]);
     // 'no selection'
     result.no = this.testScenario([]);
 
@@ -88,12 +117,13 @@ export class PermissionHelper {
 
   private testScenario(selection: object[]) {
     this.setSelection(selection);
-    const btn = this.tac.getCurrentButton();
-    return btn ? btn.name : '';
+    const action: CdTableAction = this.tac.currentAction;
+    return action ? action.name : '';
   }
 
   setSelection(selection: object[]) {
     this.tac.selection.selected = selection;
+    this.tac.onSelectionChange();
   }
 }
 
@@ -168,21 +198,18 @@ export class FormHelper {
 }
 
 /**
- * Use this to mock 'ModalService.show' to make the embedded component with it's fixture usable
+ * Use this to mock 'modalService.open' to make the embedded component with it's fixture usable
  * in tests. The function gives back all needed parts including the modal reference.
  *
  * Please make sure to call this function *inside* your mock and return the reference at the end.
  */
 export function modalServiceShow(componentClass: Type<any>, modalConfig: any) {
-  const ref = new BsModalRef();
-  const fixture = TestBed.createComponent(componentClass);
-  let component = fixture.componentInstance;
-  if (modalConfig.initialState) {
-    component = Object.assign(component, modalConfig.initialState);
-  }
-  fixture.detectChanges();
-  ref.content = component;
-  return { ref, fixture, component };
+  const modal: NgbModal = TestBed.inject(NgbModal);
+  const modalRef = modal.open(componentClass);
+  if (modalConfig) {
+    Object.assign(modalRef.componentInstance, modalConfig);
+  }
+  return modalRef;
 }
 
 export class FixtureHelper {
@@ -297,7 +324,7 @@ export class PrometheusHelper {
         severity: 'someSeverity'
       },
       annotations: {
-        summary: `${name} is ${state}`
+        description: `${name} is ${state}`
       },
       generatorURL: `http://${name}`,
       startsAt: new Date(new Date('2022-02-22').getTime() * timeMultiplier).toString()
@@ -311,7 +338,7 @@ export class PrometheusHelper {
         alertname: name
       },
       annotations: {
-        summary: `${name} is ${status}`
+        description: `${name} is ${status}`
       },
       generatorURL: `http://${name}`
     } as AlertmanagerNotificationAlert;
@@ -330,24 +357,6 @@ export class PrometheusHelper {
   }
 }
 
-const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
-<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
-  <file source-language="en" datatype="plaintext" original="ng2.template">
-    <body>
-    </body>
-  </file>
-</xliff>
-`;
-
-const i18nProviders = [
-  { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
-  { provide: TRANSLATIONS, useValue: XLIFF },
-  { provide: LOCALE_ID, useValue: 'en' },
-  I18n
-];
-
-export { i18nProviders };
-
 export function expectItemTasks(item: any, executing: string, percentage?: number) {
   if (executing) {
     executing = executing + '...';
@@ -378,6 +387,28 @@ export class IscsiHelper {
   }
 }
 
+export class RgwHelper {
+  static readonly daemons = RgwHelper.getDaemonList();
+  static readonly DAEMON_QUERY_PARAM = `daemon_name=${RgwHelper.daemons[0].id}`;
+
+  static getDaemonList() {
+    const daemonList: RgwDaemon[] = [];
+    for (let daemonIndex = 1; daemonIndex <= 3; daemonIndex++) {
+      const rgwDaemon = new RgwDaemon();
+      rgwDaemon.id = `daemon${daemonIndex}`;
+      rgwDaemon.default = daemonIndex === 2;
+      rgwDaemon.zonegroup_name = `zonegroup${daemonIndex}`;
+      daemonList.push(rgwDaemon);
+    }
+    return daemonList;
+  }
+
+  static selectDaemon() {
+    const service = TestBed.inject(RgwDaemonService);
+    service.selectDaemon(this.daemons[0]);
+  }
+}
+
 export class Mocks {
   static getCrushNode(
     name: string,
@@ -390,6 +421,18 @@ export class Mocks {
     return { name, type, type_id, id, children, device_class };
   }
 
+  static getPool = (name: string, id: number): Pool => {
+    return _.merge(new Pool(name), {
+      pool: id,
+      type: 'replicated',
+      pg_num: 256,
+      pg_placement_num: 256,
+      pg_num_target: 256,
+      pg_placement_num_target: 256,
+      size: 3
+    });
+  };
+
   /**
    * Create the following test crush map:
    * > default
@@ -541,4 +584,109 @@ export class Mocks {
     ];
     return rule;
   }
+
+  static getInventoryDevice(
+    hostname: string,
+    uid: string,
+    path = 'sda',
+    available = false
+  ): InventoryDevice {
+    return {
+      hostname,
+      uid,
+      path,
+      available,
+      sys_api: {
+        vendor: 'AAA',
+        model: 'aaa',
+        size: 1024,
+        rotational: 'false',
+        human_readable_size: '1 KB'
+      },
+      rejected_reasons: [''],
+      device_id: 'AAA-aaa-id0',
+      human_readable_type: 'nvme/ssd',
+      osd_ids: []
+    };
+  }
+}
+
+export class TabHelper {
+  static getNgbNav(fixture: ComponentFixture<any>) {
+    const debugElem: DebugElement = fixture.debugElement;
+    return debugElem.query(By.directive(NgbNav)).injector.get(NgbNav);
+  }
+
+  static getNgbNavItems(fixture: ComponentFixture<any>) {
+    const debugElems = this.getNgbNavItemsDebugElems(fixture);
+    return debugElems.map((de) => de.injector.get(NgbNavItem));
+  }
+
+  static getTextContents(fixture: ComponentFixture<any>) {
+    const debugElems = this.getNgbNavItemsDebugElems(fixture);
+    return debugElems.map((de) => de.nativeElement.textContent);
+  }
+
+  private static getNgbNavItemsDebugElems(fixture: ComponentFixture<any>) {
+    const debugElem: DebugElement = fixture.debugElement;
+    return debugElem.queryAll(By.directive(NgbNavItem));
+  }
+}
+
+export class OrchestratorHelper {
+  /**
+   * Mock Orchestrator status.
+   * @param available is the Orchestrator enabled?
+   * @param features A list of enabled Orchestrator features.
+   */
+  static mockStatus(available: boolean, features?: OrchestratorFeature[]) {
+    const orchStatus = { available: available, description: '', features: {} };
+    if (features) {
+      features.forEach((feature: OrchestratorFeature) => {
+        orchStatus.features[feature] = { available: true };
+      });
+    }
+    spyOn(TestBed.inject(OrchestratorService), 'status').and.callFake(() => of(orchStatus));
+  }
+}
+
+export class TableActionHelper {
+  /**
+   * Verify table action buttons, including the button disabled state and disable description.
+   *
+   * @param fixture  test fixture
+   * @param tableActions table actions
+   * @param expectResult expected values. e.g. {Create: { disabled: true, disableDesc: 'not supported'}}.
+   *                     Expect the Create button to be disabled with 'not supported' tooltip.
+   */
+  static verifyTableActions = async (
+    fixture: ComponentFixture<any>,
+    tableActions: CdTableAction[],
+    expectResult: {
+      [action: string]: { disabled: boolean; disableDesc: string };
+    }
+  ) => {
+    // click dropdown to update all actions buttons
+    const dropDownToggle = fixture.debugElement.query(By.css('.dropdown-toggle'));
+    dropDownToggle.triggerEventHandler('click', null);
+    fixture.detectChanges();
+    await fixture.whenStable();
+
+    const tableActionElement = fixture.debugElement.query(By.directive(TableActionsComponent));
+    const toClassName = TestBed.inject(TableActionsComponent).toClassName;
+    const getActionElement = (action: CdTableAction) =>
+      tableActionElement.query(By.css(`[ngbDropdownItem].${toClassName(action)}`));
+
+    const actions = {};
+    tableActions.forEach((action) => {
+      const actionElement = getActionElement(action);
+      if (expectResult[action.name]) {
+        actions[action.name] = {
+          disabled: actionElement.classes.disabled,
+          disableDesc: actionElement.properties.title
+        };
+      }
+    });
+    expect(actions).toEqual(expectResult);
+  };
 }