github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/job-deployments-test.js (about)

     1  import { currentURL } from '@ember/test-helpers';
     2  import { get } from '@ember/object';
     3  import { module, test } from 'qunit';
     4  import { setupApplicationTest } from 'ember-qunit';
     5  import { setupMirage } from 'ember-cli-mirage/test-support';
     6  import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
     7  import moment from 'moment';
     8  import Deployments from 'nomad-ui/tests/pages/jobs/job/deployments';
     9  
    10  const sum = (list, key, getter = (a) => a) =>
    11    list.reduce((sum, item) => sum + getter(get(item, key)), 0);
    12  
    13  let job;
    14  let deployments;
    15  let sortedDeployments;
    16  
    17  module('Acceptance | job deployments', function (hooks) {
    18    setupApplicationTest(hooks);
    19    setupMirage(hooks);
    20  
    21    hooks.beforeEach(function () {
    22      server.create('node');
    23      job = server.create('job');
    24      deployments = server.schema.deployments.where({ jobId: job.id });
    25      sortedDeployments = deployments.sort((a, b) => {
    26        const aVersion = server.db.jobVersions.findBy({
    27          jobId: a.jobId,
    28          version: a.versionNumber,
    29        });
    30        const bVersion = server.db.jobVersions.findBy({
    31          jobId: b.jobId,
    32          version: b.versionNumber,
    33        });
    34        if (aVersion.submitTime < bVersion.submitTime) {
    35          return 1;
    36        } else if (aVersion.submitTime > bVersion.submitTime) {
    37          return -1;
    38        }
    39        return 0;
    40      });
    41    });
    42  
    43    test('it passes an accessibility audit', async function (assert) {
    44      assert.expect(1);
    45  
    46      await Deployments.visit({ id: job.id });
    47      await a11yAudit(assert);
    48    });
    49  
    50    test('/jobs/:id/deployments should list all job deployments', async function (assert) {
    51      await Deployments.visit({ id: job.id });
    52  
    53      assert.equal(
    54        Deployments.deployments.length,
    55        deployments.length,
    56        'Each deployment gets a row in the timeline'
    57      );
    58      assert.equal(document.title, `Job ${job.name} deployments - Nomad`);
    59    });
    60  
    61    test('each deployment mentions the deployment shortId, status, version, and time since it was submitted', async function (assert) {
    62      await Deployments.visit({ id: job.id });
    63  
    64      const deployment = sortedDeployments.models[0];
    65      const version = server.db.jobVersions.findBy({
    66        jobId: deployment.jobId,
    67        version: deployment.versionNumber,
    68      });
    69      const deploymentRow = Deployments.deployments.objectAt(0);
    70  
    71      assert.ok(
    72        deploymentRow.text.includes(deployment.id.split('-')[0]),
    73        'Short ID'
    74      );
    75      assert.equal(deploymentRow.status, deployment.status, 'Status');
    76      assert.ok(
    77        deploymentRow.statusClass.includes(classForStatus(deployment.status)),
    78        'Status Class'
    79      );
    80      assert.ok(
    81        deploymentRow.version.includes(deployment.versionNumber),
    82        'Version #'
    83      );
    84      assert.ok(
    85        deploymentRow.submitTime.includes(
    86          moment(version.submitTime / 1000000).fromNow()
    87        ),
    88        'Submit time ago'
    89      );
    90    });
    91  
    92    test('when the deployment is running and needs promotion, the deployment item says so', async function (assert) {
    93      // Ensure the deployment needs deployment
    94      const deployment = sortedDeployments.models[0];
    95      const taskGroupSummary = deployment.deploymentTaskGroupSummaryIds.map(
    96        (id) => server.schema.deploymentTaskGroupSummaries.find(id)
    97      )[0];
    98  
    99      deployment.update('status', 'running');
   100      deployment.save();
   101  
   102      taskGroupSummary.update({
   103        desiredCanaries: 1,
   104        placedCanaries: [],
   105        promoted: false,
   106      });
   107  
   108      taskGroupSummary.save();
   109  
   110      await Deployments.visit({ id: job.id });
   111  
   112      const deploymentRow = Deployments.deployments.objectAt(0);
   113      assert.ok(
   114        deploymentRow.promotionIsRequired,
   115        'Requires Promotion badge found'
   116      );
   117    });
   118  
   119    test('each deployment item can be opened to show details', async function (assert) {
   120      await Deployments.visit({ id: job.id });
   121  
   122      const deploymentRow = Deployments.deployments.objectAt(0);
   123      assert.notOk(deploymentRow.hasDetails, 'No deployment body');
   124  
   125      await deploymentRow.toggle();
   126      assert.ok(deploymentRow.hasDetails, 'Deployment body found');
   127    });
   128  
   129    test('when open, a deployment shows the deployment metrics', async function (assert) {
   130      await Deployments.visit({ id: job.id });
   131  
   132      const deployment = sortedDeployments.models[0];
   133      const deploymentRow = Deployments.deployments.objectAt(0);
   134      const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(
   135        (id) => server.db.deploymentTaskGroupSummaries.find(id)
   136      );
   137  
   138      await deploymentRow.toggle();
   139  
   140      assert.equal(
   141        deploymentRow.metricFor('canaries').text,
   142        `${sum(taskGroupSummaries, 'placedCanaries', (a) => a.length)} / ${sum(
   143          taskGroupSummaries,
   144          'desiredCanaries'
   145        )}`,
   146        'Canaries, both places and desired, are in the metrics'
   147      );
   148  
   149      assert.equal(
   150        deploymentRow.metricFor('placed').text,
   151        sum(taskGroupSummaries, 'placedAllocs'),
   152        'Placed allocs aggregates across task groups'
   153      );
   154  
   155      assert.equal(
   156        deploymentRow.metricFor('desired').text,
   157        sum(taskGroupSummaries, 'desiredTotal'),
   158        'Desired allocs aggregates across task groups'
   159      );
   160  
   161      assert.equal(
   162        deploymentRow.metricFor('healthy').text,
   163        sum(taskGroupSummaries, 'healthyAllocs'),
   164        'Healthy allocs aggregates across task groups'
   165      );
   166  
   167      assert.equal(
   168        deploymentRow.metricFor('unhealthy').text,
   169        sum(taskGroupSummaries, 'unhealthyAllocs'),
   170        'Unhealthy allocs aggregates across task groups'
   171      );
   172  
   173      assert.equal(
   174        deploymentRow.notification,
   175        deployment.statusDescription,
   176        'Status description is in the metrics block'
   177      );
   178    });
   179  
   180    test('when open, a deployment shows a list of all task groups and their respective stats', async function (assert) {
   181      await Deployments.visit({ id: job.id });
   182  
   183      const deployment = sortedDeployments.models[0];
   184      const deploymentRow = Deployments.deployments.objectAt(0);
   185      const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(
   186        (id) => server.db.deploymentTaskGroupSummaries.find(id)
   187      );
   188  
   189      await deploymentRow.toggle();
   190  
   191      assert.ok(deploymentRow.hasTaskGroups, 'Task groups found');
   192  
   193      assert.equal(
   194        deploymentRow.taskGroups.length,
   195        taskGroupSummaries.length,
   196        'One row per task group'
   197      );
   198  
   199      const taskGroup = taskGroupSummaries[0];
   200      const taskGroupRow = deploymentRow.taskGroups.findOneBy(
   201        'name',
   202        taskGroup.name
   203      );
   204  
   205      assert.equal(taskGroupRow.name, taskGroup.name, 'Name');
   206      assert.equal(
   207        taskGroupRow.promotion,
   208        promotionTestForTaskGroup(taskGroup),
   209        'Needs Promotion'
   210      );
   211      assert.equal(
   212        taskGroupRow.autoRevert,
   213        taskGroup.autoRevert ? 'Yes' : 'No',
   214        'Auto Revert'
   215      );
   216      assert.equal(
   217        taskGroupRow.canaries,
   218        `${taskGroup.placedCanaries.length} / ${taskGroup.desiredCanaries}`,
   219        'Canaries'
   220      );
   221      assert.equal(
   222        taskGroupRow.allocs,
   223        `${taskGroup.placedAllocs} / ${taskGroup.desiredTotal}`,
   224        'Allocs'
   225      );
   226      assert.equal(
   227        taskGroupRow.healthy,
   228        taskGroup.healthyAllocs,
   229        'Healthy Allocs'
   230      );
   231      assert.equal(
   232        taskGroupRow.unhealthy,
   233        taskGroup.unhealthyAllocs,
   234        'Unhealthy Allocs'
   235      );
   236      assert.equal(
   237        taskGroupRow.progress,
   238        moment(taskGroup.requireProgressBy).format("MMM DD, 'YY HH:mm:ss ZZ"),
   239        'Progress By'
   240      );
   241    });
   242  
   243    test('when open, a deployment shows a list of all allocations for the deployment', async function (assert) {
   244      await Deployments.visit({ id: job.id });
   245  
   246      const deployment = sortedDeployments.models[0];
   247      const deploymentRow = Deployments.deployments.objectAt(0);
   248  
   249      // TODO: Make this less brittle. This logic is copied from the mirage config,
   250      // since there is no reference to allocations on the deployment model.
   251      const allocations = server.db.allocations
   252        .where({ jobId: deployment.jobId })
   253        .slice(0, 3);
   254      await deploymentRow.toggle();
   255  
   256      assert.ok(deploymentRow.hasAllocations, 'Allocations found');
   257      assert.equal(
   258        deploymentRow.allocations.length,
   259        allocations.length,
   260        'One row per allocation'
   261      );
   262  
   263      const allocation = allocations[0];
   264      const allocationRow = deploymentRow.allocations.objectAt(0);
   265  
   266      assert.equal(
   267        allocationRow.shortId,
   268        allocation.id.split('-')[0],
   269        'Allocation is as expected'
   270      );
   271    });
   272  
   273    test('when the job for the deployments is not found, an error message is shown, but the URL persists', async function (assert) {
   274      await Deployments.visit({ id: 'not-a-real-job' });
   275  
   276      assert.equal(
   277        server.pretender.handledRequests
   278          .filter((request) => !request.url.includes('policy'))
   279          .findBy('status', 404).url,
   280        '/v1/job/not-a-real-job',
   281        'A request to the nonexistent job is made'
   282      );
   283      assert.equal(
   284        currentURL(),
   285        '/jobs/not-a-real-job/deployments',
   286        'The URL persists'
   287      );
   288      assert.ok(Deployments.error.isPresent, 'Error message is shown');
   289      assert.equal(
   290        Deployments.error.title,
   291        'Not Found',
   292        'Error message is for 404'
   293      );
   294    });
   295  
   296    function classForStatus(status) {
   297      const classMap = {
   298        running: 'is-running',
   299        successful: 'is-primary',
   300        paused: 'is-light',
   301        failed: 'is-error',
   302        cancelled: 'is-cancelled',
   303      };
   304  
   305      return classMap[status] || 'is-dark';
   306    }
   307  
   308    function promotionTestForTaskGroup(taskGroup) {
   309      if (taskGroup.desiredCanaries > 0 && taskGroup.promoted === false) {
   310        return 'Yes';
   311      } else if (taskGroup.desiredCanaries > 0) {
   312        return 'No';
   313      }
   314      return 'N/A';
   315    }
   316  });