cloudfoundry-incubator/stratos

View on GitHub
src/test-e2e/application/application-e2e-helpers.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { browser, promise } from 'protractor';

import { IApp, IRoute, ISpace } from '../../frontend/packages/cloud-foundry/src/cf-api.types';
import { APIResource } from '../../frontend/packages/store/src/types/api.types';
import { e2e, E2ESetup } from '../e2e';
import { E2EConfigCloudFoundry } from '../e2e.types';
import { CFHelpers } from '../helpers/cf-e2e-helpers';
import { CFRequestHelpers } from '../helpers/cf-request-helpers';
import { E2EHelpers } from '../helpers/e2e-helpers';

const customAppLabel = E2EHelpers.e2eItemPrefix + (process.env.CUSTOM_APP_LABEL || process.env.USER);

export class ApplicationE2eHelper {

  cfRequestHelper: CFRequestHelpers;
  cfHelper: CFHelpers;

  constructor(public e2eSetup: E2ESetup) {
    this.cfRequestHelper = new CFRequestHelpers(e2eSetup);
    this.cfHelper = new CFHelpers(e2eSetup);
  }

  static createApplicationName = (isoTime?: string, postFix?: string): string =>
    E2EHelpers.createCustomName(customAppLabel + (postFix || ''), isoTime).toLowerCase()
  static createRouteName = (isoTime?: string): string =>
    CFHelpers.cleanRouteHost(E2EHelpers.createCustomName('route-' + customAppLabel, isoTime).toLowerCase())

  /**
   * Get default sanitized URL name for App
   * @param appName Name of the app
   * @returns URL friendly name
   */
  static getHostName = (appName) => appName.replace(/[\.:-]/g, '_');

  fetchAppInDefaultOrgSpace = (
    appName?: string,
    appGuid?: string,
    cfGuid?: string,
    spaceGuid?: string
  ): promise.Promise<{ cfGuid: string, app: APIResource<IApp> }> => {

    const cfGuidP: promise.Promise<string> = cfGuid ? promise.fullyResolved(cfGuid) : this.cfHelper.fetchDefaultCfGuid();
    const spaceGuidP: promise.Promise<string> = spaceGuid ? promise.fullyResolved(spaceGuid) : this.cfHelper.fetchDefaultSpaceGuid();

    const appP: promise.Promise<APIResource<IApp>> = promise.all([cfGuidP, spaceGuidP]).then(([cfGuid1, spaceGuid1]) => {
      return appName ? this.fetchApp(cfGuid1, spaceGuid1, appName) : this.fetchAppByGuid(cfGuid1, appGuid);
    });

    return appP.then(app => ({ cfGuid: CFHelpers.cachedDefaultCfGuid, app })).catch(e => {
      e2e.log('Failed to fetch application in default cf, org and space: ' + e);
      throw e;
    });
  }

  fetchApp = (cfGuid: string, spaceGuid: string, appName: string): promise.Promise<APIResource<IApp>> => {
    return this.cfHelper.basicFetchApp(cfGuid, spaceGuid, appName).then(json => {
      if (json.total_results < 1) {
        return null;
      } else if (json.total_results === 1) {
        return json.resources[0];
      } else {
        throw new Error('There should only be one app, found multiple. App Name: ' + appName);
      }
    });
  }

  fetchAppByGuid = (cfGuid: string, appGuid: string): promise.Promise<APIResource<IApp>> => {
    return this.cfRequestHelper.sendCfGet<APIResource<IApp>>(cfGuid, 'apps/' + appGuid);
  }

  private chain<T>(
    currentValue: T,
    nextChainFc: () => promise.Promise<T>,
    maxChain: number,
    abortChainFc: (val: T) => boolean,
    count = 0): promise.Promise<T> {
    if (count >= maxChain || abortChainFc(currentValue)) {
      return promise.fullyResolved(currentValue);
    }
    e2e.debugLog('Chaining requests. Count: ' + count);

    return nextChainFc().then(res => {
      if (abortChainFc(res)) {
        return promise.fullyResolved<T>(res);
      }
      browser.sleep(500);
      return this.chain<T>(res, nextChainFc, maxChain, abortChainFc, ++count);
    });
  }

  deleteApplication = (
    haveApp?: {
      cfGuid: string,
      app: APIResource<IApp>
    },
    needApp?: {
      appName?: string,
      appGuid?: string
    },
    pollForMissingRoutes = true
  ): promise.Promise<any> => {
    if (!haveApp && !needApp) {
      e2e.debugLog(`Skipping Deleting App...`);
      return;
    }

    let cfGuid = haveApp ? haveApp.cfGuid : null;

    const appP: promise.Promise<APIResource<IApp>> = haveApp ?
      this.fetchAppByGuid(haveApp.cfGuid, haveApp.app.metadata.guid) :
      this.fetchAppInDefaultOrgSpace(needApp.appName, needApp.appGuid).then(res => {
        cfGuid = res.cfGuid;
        return res.app;
      });

    e2e.debugLog(`Deleting App...`);

    return appP
      .then(app => {
        e2e.debugLog(`'${app.entity.name}': Found app to delete`);

        const promises = [];

        // Delete service instance
        const serviceBindings = app.entity.service_bindings || [];
        serviceBindings.forEach(serviceBinding => {
          const url = 'service_instances/' + serviceBinding.entity.service_instance_guid + '?recursive=true&async=false';
          promises.push(this.cfRequestHelper.sendCfDelete(cfGuid, url));
        });

        // Delete route
        // If we have zero routes, attempt 10 times to fetch a populated route list
        const routes: promise.Promise<APIResource<IRoute>[]> = this.chain<APIResource<IRoute>[]>(
          app.entity.routes,
          () => this.cfHelper.fetchAppRoutes(cfGuid, app.metadata.guid),
          pollForMissingRoutes ? 10 : 0,
          (res) => !!res && !!res.length
        );

        promises.push(routes.then(appRoutes => {
          if (!appRoutes || !appRoutes.length) {
            e2e.debugLog(`'${app.entity.name}': Deleting App Routes... None found'. `);
            return promise.fullyResolved({});
          }
          e2e.debugLog(`'${app.entity.name}': Deleting App Routes... '${appRoutes.map(route => route.entity.host).join(',')}'. `);
          return promise.all(appRoutes.map(route =>
            this.cfRequestHelper.sendCfDelete(cfGuid, 'routes/' + route.metadata.guid + '?q=recursive=true&async=false')
          ));
        }));

        const deps = promise.all(promises).catch(err => {
          const errorString = `Failed to delete routes or services attached to an app`;
          e2e.log(`${errorString}: ${err}`);
          return promise.rejected(errorString);
        });

        // Delete app
        return deps.then(() => this.cfHelper.basicDeleteApp(cfGuid, app.metadata.guid)).then(() => {
          e2e.debugLog(`'${app.entity.name}': Successfully deleted.`);
        });
      })
      .catch(err => {
        /* tslint:disable:no-console*/
        console.log(`Failed to delete app or associated dependencies: ${err}`);
        /* tslint:enable:no-console*/
        fail();
      });
  }

  createApp(cfGuid: string, orgName: string, spaceName: string, appName: string, endpoint: E2EConfigCloudFoundry) {
    return browser.driver.wait(
      this.cfHelper.addOrgIfMissingForEndpointUsers(cfGuid, endpoint, orgName)
        .then(org => this.cfHelper.addSpaceIfMissingForEndpointUsers(cfGuid, org.metadata.guid, spaceName, endpoint))
        .then(space => {
          expect(space).not.toBeNull();
          return promise.all([
            this.cfHelper.basicCreateApp(cfGuid, space.metadata.guid, appName),
            promise.fullyResolved(space)
          ]);
        })
        .then(([app, space]: [APIResource<IApp>, APIResource<ISpace>]) => {
          return this.fetchApp(cfGuid, space.metadata.guid, appName);
        })
    );
  }

}