<commit_before>var _ = require("underscore");
var expect = require("chai").expect;
var React = require("react/addons");
var ReactContext = require("react/lib/ReactContext");
var TestUtils = React.addons.TestUtils;
var Util = require("../js/helpers/Util");

/**
 * This *nasty* hack is needed because we want to prevent TooltipMixin from
 * actually requiring vendor/tooltip.js due do it depending on the DOM.
 *
 * Let's get rid of this once we have jsDom or similar tools in our tests.
 * TODO: https://github.com/mesosphere/marathon/issues/1796
 */
var TooltipMixin = require("../js/mixins/TooltipMixin");
TooltipMixin.init = _.noop;
TooltipMixin.getNewTooltip = _.noop;
TooltipMixin.tip_destroyAllTips = _.noop;

var AppsActions = require("../js/actions/AppsActions");
var AppComponent = require("../js/components/AppComponent");
var AppHealthComponent = require("../js/components/AppHealthComponent");
var AppPageComponent = require("../js/components/AppPageComponent");
var AppStatusComponent = require("../js/components/AppStatusComponent");
var appScheme = require("../js/stores/schemes/appScheme");
var AppsEvents = require("../js/events/AppsEvents");
var AppsStore = require("../js/stores/AppsStore");
var AppStatus = require("../js/constants/AppStatus");
var HealthStatus = require("../js/constants/HealthStatus");
var QueueActions = require("../js/actions/QueueActions");
var QueueStore = require("../js/stores/QueueStore");
var States = require("../js/constants/States");
var config = require("../js/config/config");

var expectAsync = require("./helpers/expectAsync");
var HttpServer = require("./helpers/HttpServer").HttpServer;

var server = new HttpServer(config.localTestserverURI);
config.apiURL = "http://" + server.address + ":" + server.port + "/";

describe("Apps", function () {

  beforeEach(function (done) {
    this.server = server
    .setup({
      "apps": [{
        id: "/app-1",
        instances: 5,
        mem: 100,
        cpus: 4
      }, {
        id: "/app-2"
      }]
    }, 200)
    .start(function () {
      AppsStore.once(AppsEvents.CHANGE, done);
      AppsActions.requestApps();
    });
  });

  afterEach(function (done) {
    this.server.stop(done);
  });

  describe("on apps request", function () {

    it("updates the AppsStore on success", function (done) {
      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
        }, done);
      });

      AppsActions.requestApps();
    });

    it("calculate total resources", function (done) {
      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.apps[0].totalMem).to.equal(500);
          expect(AppsStore.apps[0].totalCpus).to.equal(20);
        }, done);
      });

      AppsActions.requestApps();
    });

    it("handles failure gracefully", function (done) {
      this.server.setup({message: "Guru Meditation"}, 404);

      AppsStore.once(AppsEvents.REQUEST_APPS_ERROR, function (error) {
        expectAsync(function () {
          expect(error.message).to.equal("Guru Meditation");
        }, done);
      });

      AppsActions.requestApps();
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.REQUEST_APPS_ERROR,
          function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.requestApps();
    });

    describe("App", function () {
      beforeEach(function () {
        this.server.setup({
          "apps": [{
            id: "/app-1",
            tasksHealthy: 2,
            tasksUnhealthy: 2,
            tasksRunning: 5,
            tasksStaged: 2,
            instances: 10
          }]
        }, 200);
      });

      it("has correct health weight", function (done) {
        AppsStore.once(AppsEvents.CHANGE, function () {
          expectAsync(function () {
            expect(AppsStore.apps[0].healthWeight).to.equal(47);
          }, done);
        });

        AppsActions.requestApps();
      });

      it("has correct health data object", function (done) {
        AppsStore.once(AppsEvents.CHANGE, function () {
          expectAsync(function () {
            expect(AppsStore.apps[0].health).to.deep.equal([
              { quantity: 2, state: HealthStatus.HEALTHY },
              { quantity: 2, state: HealthStatus.UNHEALTHY },
              { quantity: 1, state: HealthStatus.UNKNOWN },
              { quantity: 2, state: HealthStatus.STAGED },
              { quantity: 0, state: HealthStatus.OVERCAPACITY },
              { quantity: 3, state: HealthStatus.UNSCHEDULED }
            ]);
          }, done);
        });

        AppsActions.requestApps();
      });
    });

  });

  describe("on single app request", function () {

    it("updates the AppsStore on success", function (done) {
      this.server.setup({
        "app": {
          "id": "/single-app"
        }
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.currentApp.id).to.equal("/single-app");
        }, done);
      });

      AppsActions.requestApp("/single-app");
    });

    it("has the correct app status (running)", function (done) {
      this.server.setup({
        "app": {
          "id": "/single-app",
          "instances": 1,
          "tasksRunning": 1
        }
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.currentApp.status).to.equal(0);
        }, done);
      });

      AppsActions.requestApp("/single-app");
    });

    it("has the correct app status (deploying)", function (done) {
      this.server.setup({
        "app": {
          "id": "/single-app",
          "deployments": ["deployment-1"]
        }
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.currentApp.status).to.equal(1);
        }, done);
      });

      AppsActions.requestApp("/single-app");
    });

    it("has the correct app status (suspended)", function (done) {
      this.server.setup({
        "app": {
          "id": "/single-app"
        }
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.currentApp.status).to.equal(2);
        }, done);
      });

      AppsActions.requestApp("/single-app");
    });

    it("handles failure gracefully", function (done) {
      this.server.setup({message: "Guru Meditation"}, 404);

      AppsStore.once(AppsEvents.REQUEST_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(error.message).to.equal("Guru Meditation");
        }, done);
      });

      AppsActions.requestApp("/non-existing-app");
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.REQUEST_APP_ERROR,
          function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.requestApp("/non-existing-app");
    });

  });

  describe("on queue update", function () {

    it("has the correct app status (delayed)", function (done) {
      this.server.setup({
        "queue": [
          {
            "app": {
              "id": "/app-1",
              "maxLaunchDelaySeconds": 3600
            },
            "delay": {
              "overdue": false,
              "timeLeftSeconds": 784
            }
          }
        ]
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(_.findWhere(AppsStore.apps, {id: "/app-1"}).status)
          .to.equal(3);
        }, done);
      });

      QueueActions.requestQueue();
    });

    it("has the correct app status (waiting)", function (done) {
      this.server.setup({
        "queue": [
          {
            "app": {
              "id": "/app-1",
              "maxLaunchDelaySeconds": 3600
            },
            "delay": {
              "overdue": true,
              "timeLeftSeconds": 123
            }
          }
        ]
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(_.findWhere(AppsStore.apps, {id: "/app-1"}).status)
          .to.equal(4);
        }, done);
      });

      QueueActions.requestQueue();
    });

    it("does not trigger a change event if it doesn't update an app status",
        function (done) {
      var initialTimeout = this.timeout();
      this.timeout(25);

      this.server.setup({
        "queue": [
          {
            "app": {
              "id": "/app-1",
              "maxLaunchDelaySeconds": 3600
            },
            "delay": {
              "overdue": false,
              "timeLeftSeconds": 0
            }
          }
        ]
      }, 200);

      var onChange = function () {
        expectAsync(function () {
          done(new Error("AppsEvents.CHANGE shouldn't be called."));
        }, done);
      };

      AppsStore.once(AppsEvents.CHANGE, onChange);

      setTimeout(() => {
        AppsStore.removeListener(AppsEvents.CHANGE, onChange);
        this.timeout(initialTimeout);
        done();
      }, 10);

      QueueActions.requestQueue();
    });

  });

  describe("on app creation", function () {

    it("updates the AppsStore on success", function (done) {
      this.server.setup({
          "id": "/app-3"
        }, 201);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(3);
          expect(_.where(AppsStore.apps, {
            id: "/app-3"
          })).to.be.not.empty;
        }, done);
      });

      AppsActions.createApp({
        "id": "/app-3",
        "cmd": "app command"
      });
    });

    it("sends create event on success", function (done) {
      this.server.setup({
          "id": "/app-3"
        }, 201);

      AppsStore.once(AppsEvents.CREATE_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(3);
        }, done);
      });

      AppsActions.createApp({
        "id": "/app-3"
      });
    });

    it("handles bad request", function (done) {
      this.server.setup({message: "Guru Meditation"}, 400);

      AppsStore.once(AppsEvents.CREATE_APP_ERROR, function (error, status) {
        expectAsync(function () {
          expect(error.message).to.equal("Guru Meditation");
          expect(status).to.equal(400);
        }, done);
      });

      AppsActions.createApp({
        cmd: "app command"
      });
    });

    it("passes response status", function (done) {
      this.server.setup({message: "Guru Meditation"}, 400);

      AppsStore.once(AppsEvents.CREATE_APP_ERROR, function (error, status) {
        expectAsync(function () {
          expect(status).to.equal(400);
        }, done);
      });

      AppsActions.createApp({
        cmd: "app command"
      });
    });

    it("handles atttribute value error", function (done) {
      this.server.setup({
        errors: [{
            attribute: "id",
            error: "attribute has invalid value"
          }
        ]}, 422);

      AppsStore.once(AppsEvents.CREATE_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(error.errors[0].attribute).to.equal("id");
          expect(error.errors[0].error).to.equal("attribute has invalid value");
        }, done);
      });

      AppsActions.createApp({
        id: "app 1"
      });
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.CREATE_APP_ERROR, function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.createApp({id: "app 1"});
    });

  });

  describe("on app deletion", function () {

    it("deletes an app on success", function (done) {
      // A successful response with a payload of a new delete-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-deletes-app",
          "version": "v1"
        }, 200);

      AppsStore.once(AppsEvents.DELETE_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(1);

          expect(_.where(AppsStore.apps, {
            id: "/app-1"
          })).to.be.empty;
        }, done);
      });

      AppsActions.deleteApp("/app-1");
    });

    it("receives a delete error", function (done) {
      this.server.setup({message: "delete error"}, 404);

      AppsStore.once(AppsEvents.DELETE_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("delete error");
        }, done);
      });

      AppsActions.deleteApp("/non-existing-app");
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.DELETE_APP_ERROR, function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.deleteApp("/app-1");
    });

  });

  describe("on app restart", function () {

    it("restarts an app on success", function (done) {
      // A successful response with a payload of a new restart-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-restarts-app",
          "version": "v1"
        }, 200);

      AppsStore.once(AppsEvents.RESTART_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
        }, done);
      });

      AppsActions.restartApp("/app-1");
    });

    it("receives a restart error on non existing app", function (done) {
      this.server.setup({message: "restart error"}, 404);

      AppsStore.once(AppsEvents.RESTART_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("restart error");
        }, done);
      });

      AppsActions.restartApp("/non-existing-app");
    });

    it("receives a restart error on locked app", function (done) {
      this.server.setup({message: "app locked by deployment"}, 409);

      AppsStore.once(AppsEvents.RESTART_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("app locked by deployment");
        }, done);
      });

      AppsActions.restartApp("/app-1");
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.RESTART_APP_ERROR,
          function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.restartApp("/app-1");
    });

  });

  describe("on app scale", function () {

    it("scales an app on success", function (done) {
      // A successful response with a payload of a new scale-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-scales-app",
          "version": "v1"
        }, 200);

      AppsStore.once(AppsEvents.SCALE_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
        }, done);
      });

      AppsActions.scaleApp("/app-1", 10);
    });

    it("receives a scale error on non existing app", function (done) {
      this.server.setup({message: "scale error"}, 404);

      AppsStore.once(AppsEvents.SCALE_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("scale error");
        }, done);
      });

      AppsActions.scaleApp("/non-existing-app");
    });

    it("receives a scale error on bad data", function (done) {
      this.server.setup({message: "scale bad data error"}, 400);

      AppsStore.once(AppsEvents.SCALE_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("scale bad data error");
        }, done);
      });

      AppsActions.scaleApp("/app-1", "needs a number! :P");
    });

  });

  describe("on app apply", function () {

    it("applies app settings on success", function (done) {
      // A successful response with a payload of a apply-settings-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-applies-new-settings",
          "version": "v2"
        }, 200);

      AppsStore.once(AppsEvents.APPLY_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
        }, done);
      });

      AppsActions.applySettingsOnApp("/app-1", {
        "cmd": "sleep 10",
        "id": "/app-1",
        "instances": 15
      }, true);
    });

    it("receives an apply error on bad data", function (done) {
      this.server.setup({message: "apply bad data error"}, 400);

      AppsStore.once(AppsEvents.APPLY_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("apply bad data error");
        }, done);
      });

      AppsActions.applySettingsOnApp("/app-1", {
        "cmd": "sleep 10",
        "id": "/app-1",
        "instances": "needs a number! :P"
      }, true);
    });

    it("it passes isEditing-flag on success", function (done) {
      // A successful response with a payload of a apply-settings-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-applies-new-settings",
          "version": "v2"
        }, 200);

      AppsStore.once(AppsEvents.APPLY_APP, function (isEditing) {
        expectAsync(function () {
          expect(isEditing).to.be.true;
        }, done);
      });

      AppsActions.applySettingsOnApp("/app-1", {}, true);
    });

    it("it passes isEditing-flag on error", function (done) {
      this.server.setup({message: "apply bad data error"}, 400);

      AppsStore.once(AppsEvents.APPLY_APP_ERROR, function (error, isEditing) {
        expectAsync(function () {
          expect(isEditing).to.be.true;
        }, done);
      });

      AppsActions.applySettingsOnApp("/app-1", {}, true);
    });

  });

});

describe("App component", function () {

  beforeEach(function () {
    var model = {
      id: "app-123",
      deployments: [],
      tasksRunning: 4,
      instances: 5,
      mem: 100,
      totalMem: 500,
      cpus: 4,
      totalCpus: 20,
      status: 0
    };
    this.renderer = TestUtils.createRenderer();
    this.renderer.render(<AppComponent model={model} />);
    this.component = this.renderer.getRenderOutput();
  });

  afterEach(function () {
    this.renderer.unmount();
  });

  it("has the correct app id", function () {
    var cellContent = this.component.props.children[0].props.children[0];
    expect(cellContent).to.equal("app-123");
  });

  it("has the correct amount of total memory", function () {
    var cellContent = this.component.props.children[1].props.children;
    expect(cellContent).to.equal(500);
  });

  it("has the correct amount of total cpus", function () {
    var cellContent = this.component.props.children[2].props.children;
    expect(cellContent).to.equal(20);
  });

  it("has correct number of tasks running", function () {
    var tasksRunning = this.component.props.children[3].props.children[0].props.children;
    expect(tasksRunning).to.equal(4);
  });

  it("has correct number of instances", function () {
    var totalSteps = this.component.props.children[3].props.children[2];
    expect(totalSteps).to.equal(5);
  });

});

describe("App Health component", function () {

  beforeEach(function () {
    var model = {
      id: "app-123",
      health: [<commit_msg>Fix app task and instance test<commit_after>var _ = require("underscore");
var expect = require("chai").expect;
var React = require("react/addons");
var ReactContext = require("react/lib/ReactContext");
var TestUtils = React.addons.TestUtils;
var Util = require("../js/helpers/Util");

/**
 * This *nasty* hack is needed because we want to prevent TooltipMixin from
 * actually requiring vendor/tooltip.js due do it depending on the DOM.
 *
 * Let's get rid of this once we have jsDom or similar tools in our tests.
 * TODO: https://github.com/mesosphere/marathon/issues/1796
 */
var TooltipMixin = require("../js/mixins/TooltipMixin");
TooltipMixin.init = _.noop;
TooltipMixin.getNewTooltip = _.noop;
TooltipMixin.tip_destroyAllTips = _.noop;

var AppsActions = require("../js/actions/AppsActions");
var AppComponent = require("../js/components/AppComponent");
var AppHealthComponent = require("../js/components/AppHealthComponent");
var AppPageComponent = require("../js/components/AppPageComponent");
var AppStatusComponent = require("../js/components/AppStatusComponent");
var appScheme = require("../js/stores/schemes/appScheme");
var AppsEvents = require("../js/events/AppsEvents");
var AppsStore = require("../js/stores/AppsStore");
var AppStatus = require("../js/constants/AppStatus");
var HealthStatus = require("../js/constants/HealthStatus");
var QueueActions = require("../js/actions/QueueActions");
var QueueStore = require("../js/stores/QueueStore");
var States = require("../js/constants/States");
var config = require("../js/config/config");

var expectAsync = require("./helpers/expectAsync");
var HttpServer = require("./helpers/HttpServer").HttpServer;

var server = new HttpServer(config.localTestserverURI);
config.apiURL = "http://" + server.address + ":" + server.port + "/";

describe("Apps", function () {

  beforeEach(function (done) {
    this.server = server
    .setup({
      "apps": [{
        id: "/app-1",
        instances: 5,
        mem: 100,
        cpus: 4
      }, {
        id: "/app-2"
      }]
    }, 200)
    .start(function () {
      AppsStore.once(AppsEvents.CHANGE, done);
      AppsActions.requestApps();
    });
  });

  afterEach(function (done) {
    this.server.stop(done);
  });

  describe("on apps request", function () {

    it("updates the AppsStore on success", function (done) {
      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
        }, done);
      });

      AppsActions.requestApps();
    });

    it("calculate total resources", function (done) {
      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.apps[0].totalMem).to.equal(500);
          expect(AppsStore.apps[0].totalCpus).to.equal(20);
        }, done);
      });

      AppsActions.requestApps();
    });

    it("handles failure gracefully", function (done) {
      this.server.setup({message: "Guru Meditation"}, 404);

      AppsStore.once(AppsEvents.REQUEST_APPS_ERROR, function (error) {
        expectAsync(function () {
          expect(error.message).to.equal("Guru Meditation");
        }, done);
      });

      AppsActions.requestApps();
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.REQUEST_APPS_ERROR,
          function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.requestApps();
    });

    describe("App", function () {
      beforeEach(function () {
        this.server.setup({
          "apps": [{
            id: "/app-1",
            tasksHealthy: 2,
            tasksUnhealthy: 2,
            tasksRunning: 5,
            tasksStaged: 2,
            instances: 10
          }]
        }, 200);
      });

      it("has correct health weight", function (done) {
        AppsStore.once(AppsEvents.CHANGE, function () {
          expectAsync(function () {
            expect(AppsStore.apps[0].healthWeight).to.equal(47);
          }, done);
        });

        AppsActions.requestApps();
      });

      it("has correct health data object", function (done) {
        AppsStore.once(AppsEvents.CHANGE, function () {
          expectAsync(function () {
            expect(AppsStore.apps[0].health).to.deep.equal([
              { quantity: 2, state: HealthStatus.HEALTHY },
              { quantity: 2, state: HealthStatus.UNHEALTHY },
              { quantity: 1, state: HealthStatus.UNKNOWN },
              { quantity: 2, state: HealthStatus.STAGED },
              { quantity: 0, state: HealthStatus.OVERCAPACITY },
              { quantity: 3, state: HealthStatus.UNSCHEDULED }
            ]);
          }, done);
        });

        AppsActions.requestApps();
      });
    });

  });

  describe("on single app request", function () {

    it("updates the AppsStore on success", function (done) {
      this.server.setup({
        "app": {
          "id": "/single-app"
        }
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.currentApp.id).to.equal("/single-app");
        }, done);
      });

      AppsActions.requestApp("/single-app");
    });

    it("has the correct app status (running)", function (done) {
      this.server.setup({
        "app": {
          "id": "/single-app",
          "instances": 1,
          "tasksRunning": 1
        }
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.currentApp.status).to.equal(0);
        }, done);
      });

      AppsActions.requestApp("/single-app");
    });

    it("has the correct app status (deploying)", function (done) {
      this.server.setup({
        "app": {
          "id": "/single-app",
          "deployments": ["deployment-1"]
        }
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.currentApp.status).to.equal(1);
        }, done);
      });

      AppsActions.requestApp("/single-app");
    });

    it("has the correct app status (suspended)", function (done) {
      this.server.setup({
        "app": {
          "id": "/single-app"
        }
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.currentApp.status).to.equal(2);
        }, done);
      });

      AppsActions.requestApp("/single-app");
    });

    it("handles failure gracefully", function (done) {
      this.server.setup({message: "Guru Meditation"}, 404);

      AppsStore.once(AppsEvents.REQUEST_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(error.message).to.equal("Guru Meditation");
        }, done);
      });

      AppsActions.requestApp("/non-existing-app");
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.REQUEST_APP_ERROR,
          function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.requestApp("/non-existing-app");
    });

  });

  describe("on queue update", function () {

    it("has the correct app status (delayed)", function (done) {
      this.server.setup({
        "queue": [
          {
            "app": {
              "id": "/app-1",
              "maxLaunchDelaySeconds": 3600
            },
            "delay": {
              "overdue": false,
              "timeLeftSeconds": 784
            }
          }
        ]
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(_.findWhere(AppsStore.apps, {id: "/app-1"}).status)
          .to.equal(3);
        }, done);
      });

      QueueActions.requestQueue();
    });

    it("has the correct app status (waiting)", function (done) {
      this.server.setup({
        "queue": [
          {
            "app": {
              "id": "/app-1",
              "maxLaunchDelaySeconds": 3600
            },
            "delay": {
              "overdue": true,
              "timeLeftSeconds": 123
            }
          }
        ]
      }, 200);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(_.findWhere(AppsStore.apps, {id: "/app-1"}).status)
          .to.equal(4);
        }, done);
      });

      QueueActions.requestQueue();
    });

    it("does not trigger a change event if it doesn't update an app status",
        function (done) {
      var initialTimeout = this.timeout();
      this.timeout(25);

      this.server.setup({
        "queue": [
          {
            "app": {
              "id": "/app-1",
              "maxLaunchDelaySeconds": 3600
            },
            "delay": {
              "overdue": false,
              "timeLeftSeconds": 0
            }
          }
        ]
      }, 200);

      var onChange = function () {
        expectAsync(function () {
          done(new Error("AppsEvents.CHANGE shouldn't be called."));
        }, done);
      };

      AppsStore.once(AppsEvents.CHANGE, onChange);

      setTimeout(() => {
        AppsStore.removeListener(AppsEvents.CHANGE, onChange);
        this.timeout(initialTimeout);
        done();
      }, 10);

      QueueActions.requestQueue();
    });

  });

  describe("on app creation", function () {

    it("updates the AppsStore on success", function (done) {
      this.server.setup({
          "id": "/app-3"
        }, 201);

      AppsStore.once(AppsEvents.CHANGE, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(3);
          expect(_.where(AppsStore.apps, {
            id: "/app-3"
          })).to.be.not.empty;
        }, done);
      });

      AppsActions.createApp({
        "id": "/app-3",
        "cmd": "app command"
      });
    });

    it("sends create event on success", function (done) {
      this.server.setup({
          "id": "/app-3"
        }, 201);

      AppsStore.once(AppsEvents.CREATE_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(3);
        }, done);
      });

      AppsActions.createApp({
        "id": "/app-3"
      });
    });

    it("handles bad request", function (done) {
      this.server.setup({message: "Guru Meditation"}, 400);

      AppsStore.once(AppsEvents.CREATE_APP_ERROR, function (error, status) {
        expectAsync(function () {
          expect(error.message).to.equal("Guru Meditation");
          expect(status).to.equal(400);
        }, done);
      });

      AppsActions.createApp({
        cmd: "app command"
      });
    });

    it("passes response status", function (done) {
      this.server.setup({message: "Guru Meditation"}, 400);

      AppsStore.once(AppsEvents.CREATE_APP_ERROR, function (error, status) {
        expectAsync(function () {
          expect(status).to.equal(400);
        }, done);
      });

      AppsActions.createApp({
        cmd: "app command"
      });
    });

    it("handles atttribute value error", function (done) {
      this.server.setup({
        errors: [{
            attribute: "id",
            error: "attribute has invalid value"
          }
        ]}, 422);

      AppsStore.once(AppsEvents.CREATE_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(error.errors[0].attribute).to.equal("id");
          expect(error.errors[0].error).to.equal("attribute has invalid value");
        }, done);
      });

      AppsActions.createApp({
        id: "app 1"
      });
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.CREATE_APP_ERROR, function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.createApp({id: "app 1"});
    });

  });

  describe("on app deletion", function () {

    it("deletes an app on success", function (done) {
      // A successful response with a payload of a new delete-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-deletes-app",
          "version": "v1"
        }, 200);

      AppsStore.once(AppsEvents.DELETE_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(1);

          expect(_.where(AppsStore.apps, {
            id: "/app-1"
          })).to.be.empty;
        }, done);
      });

      AppsActions.deleteApp("/app-1");
    });

    it("receives a delete error", function (done) {
      this.server.setup({message: "delete error"}, 404);

      AppsStore.once(AppsEvents.DELETE_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("delete error");
        }, done);
      });

      AppsActions.deleteApp("/non-existing-app");
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.DELETE_APP_ERROR, function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.deleteApp("/app-1");
    });

  });

  describe("on app restart", function () {

    it("restarts an app on success", function (done) {
      // A successful response with a payload of a new restart-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-restarts-app",
          "version": "v1"
        }, 200);

      AppsStore.once(AppsEvents.RESTART_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
        }, done);
      });

      AppsActions.restartApp("/app-1");
    });

    it("receives a restart error on non existing app", function (done) {
      this.server.setup({message: "restart error"}, 404);

      AppsStore.once(AppsEvents.RESTART_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("restart error");
        }, done);
      });

      AppsActions.restartApp("/non-existing-app");
    });

    it("receives a restart error on locked app", function (done) {
      this.server.setup({message: "app locked by deployment"}, 409);

      AppsStore.once(AppsEvents.RESTART_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("app locked by deployment");
        }, done);
      });

      AppsActions.restartApp("/app-1");
    });

    it("handles unauthorized errors gracefully", function (done) {
      this.server.setup({message: "Unauthorized access"}, 401);

      AppsStore.once(AppsEvents.RESTART_APP_ERROR,
          function (error, statusCode) {
        expectAsync(function () {
          expect(statusCode).to.equal(401);
        }, done);
      });

      AppsActions.restartApp("/app-1");
    });

  });

  describe("on app scale", function () {

    it("scales an app on success", function (done) {
      // A successful response with a payload of a new scale-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-scales-app",
          "version": "v1"
        }, 200);

      AppsStore.once(AppsEvents.SCALE_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
        }, done);
      });

      AppsActions.scaleApp("/app-1", 10);
    });

    it("receives a scale error on non existing app", function (done) {
      this.server.setup({message: "scale error"}, 404);

      AppsStore.once(AppsEvents.SCALE_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("scale error");
        }, done);
      });

      AppsActions.scaleApp("/non-existing-app");
    });

    it("receives a scale error on bad data", function (done) {
      this.server.setup({message: "scale bad data error"}, 400);

      AppsStore.once(AppsEvents.SCALE_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("scale bad data error");
        }, done);
      });

      AppsActions.scaleApp("/app-1", "needs a number! :P");
    });

  });

  describe("on app apply", function () {

    it("applies app settings on success", function (done) {
      // A successful response with a payload of a apply-settings-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-applies-new-settings",
          "version": "v2"
        }, 200);

      AppsStore.once(AppsEvents.APPLY_APP, function () {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
        }, done);
      });

      AppsActions.applySettingsOnApp("/app-1", {
        "cmd": "sleep 10",
        "id": "/app-1",
        "instances": 15
      }, true);
    });

    it("receives an apply error on bad data", function (done) {
      this.server.setup({message: "apply bad data error"}, 400);

      AppsStore.once(AppsEvents.APPLY_APP_ERROR, function (error) {
        expectAsync(function () {
          expect(AppsStore.apps).to.have.length(2);
          expect(error.message).to.equal("apply bad data error");
        }, done);
      });

      AppsActions.applySettingsOnApp("/app-1", {
        "cmd": "sleep 10",
        "id": "/app-1",
        "instances": "needs a number! :P"
      }, true);
    });

    it("it passes isEditing-flag on success", function (done) {
      // A successful response with a payload of a apply-settings-deployment,
      // like the API would do.
      // Indeed the payload isn't processed by the store yet.
      this.server.setup({
          "deploymentId": "deployment-that-applies-new-settings",
          "version": "v2"
        }, 200);

      AppsStore.once(AppsEvents.APPLY_APP, function (isEditing) {
        expectAsync(function () {
          expect(isEditing).to.be.true;
        }, done);
      });

      AppsActions.applySettingsOnApp("/app-1", {}, true);
    });

    it("it passes isEditing-flag on error", function (done) {
      this.server.setup({message: "apply bad data error"}, 400);

      AppsStore.once(AppsEvents.APPLY_APP_ERROR, function (error, isEditing) {
        expectAsync(function () {
          expect(isEditing).to.be.true;
        }, done);
      });

      AppsActions.applySettingsOnApp("/app-1", {}, true);
    });

  });

});

describe("App component", function () {

  beforeEach(function () {
    var model = {
      id: "app-123",
      deployments: [],
      tasksRunning: 4,
      instances: 5,
      mem: 100,
      totalMem: 500,
      cpus: 4,
      totalCpus: 20,
      status: 0
    };
    this.renderer = TestUtils.createRenderer();
    this.renderer.render(<AppComponent model={model} />);
    this.component = this.renderer.getRenderOutput();
  });

  afterEach(function () {
    this.renderer.unmount();
  });

  it("has the correct app id", function () {
    var cellContent = this.component.props.children[0].props.children[0];
    expect(cellContent).to.equal("app-123");
  });

  it("has the correct amount of total memory", function () {
    var cellContent = this.component.props.children[1].props.children;
    expect(cellContent).to.equal(500);
  });

  it("has the correct amount of total cpus", function () {
    var cellContent = this.component.props.children[2].props.children;
    expect(cellContent).to.equal(20);
  });

  it("has correct number of tasks running", function () {
    var tasksRunning = this.component.props.children[4].props.children[0].props.children;
    expect(tasksRunning).to.equal(4);
  });

  it("has correct number of instances", function () {
    var totalSteps = this.component.props.children[4].props.children[2];
    expect(totalSteps).to.equal(5);
  });

});

describe("App Health component", function () {

  beforeEach(function () {
    var model = {
      id: "app-123",
      health: [
{'fraction_non_alphanumeric': 0.1547354592141407, 'fraction_numerical': 0.013185238265854533, 'mean_word_length': 2.2697594501718212, 'pattern_counts': {'":': 112, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3671898', 'n_tokens_mistral': 12968, 'n_tokens_neox': 12316, 'n_words': 2427}