github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/ui/tests/acceptance/jobs-list-test.js (about)

     1  import { currentURL } from '@ember/test-helpers';
     2  import { module, test } from 'qunit';
     3  import { setupApplicationTest } from 'ember-qunit';
     4  import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
     5  import JobsList from 'nomad-ui/tests/pages/jobs/list';
     6  
     7  module('Acceptance | jobs list', function(hooks) {
     8    setupApplicationTest(hooks);
     9    setupMirage(hooks);
    10  
    11    hooks.beforeEach(function() {
    12      // Required for placing allocations (a result of creating jobs)
    13      server.create('node');
    14    });
    15  
    16    test('visiting /jobs', async function(assert) {
    17      await JobsList.visit();
    18  
    19      assert.equal(currentURL(), '/jobs');
    20    });
    21  
    22    test('/jobs should list the first page of jobs sorted by modify index', async function(assert) {
    23      const jobsCount = JobsList.pageSize + 1;
    24      server.createList('job', jobsCount, { createAllocations: false });
    25  
    26      await JobsList.visit();
    27  
    28      const sortedJobs = server.db.jobs.sortBy('modifyIndex').reverse();
    29      assert.equal(JobsList.jobs.length, JobsList.pageSize);
    30      JobsList.jobs.forEach((job, index) => {
    31        assert.equal(job.name, sortedJobs[index].name, 'Jobs are ordered');
    32      });
    33    });
    34  
    35    test('each job row should contain information about the job', async function(assert) {
    36      server.createList('job', 2);
    37      const job = server.db.jobs.sortBy('modifyIndex').reverse()[0];
    38      const taskGroups = server.db.taskGroups.where({ jobId: job.id });
    39  
    40      await JobsList.visit();
    41  
    42      const jobRow = JobsList.jobs.objectAt(0);
    43  
    44      assert.equal(jobRow.name, job.name, 'Name');
    45      assert.equal(jobRow.link, `/ui/jobs/${job.id}`, 'Detail Link');
    46      assert.equal(jobRow.status, job.status, 'Status');
    47      assert.equal(jobRow.type, typeForJob(job), 'Type');
    48      assert.equal(jobRow.priority, job.priority, 'Priority');
    49      assert.equal(jobRow.taskGroups, taskGroups.length, '# Groups');
    50    });
    51  
    52    test('each job row should link to the corresponding job', async function(assert) {
    53      server.create('job');
    54      const job = server.db.jobs[0];
    55  
    56      await JobsList.visit();
    57      await JobsList.jobs.objectAt(0).clickName();
    58  
    59      assert.equal(currentURL(), `/jobs/${job.id}`);
    60    });
    61  
    62    test('the new job button transitions to the new job page', async function(assert) {
    63      await JobsList.visit();
    64      await JobsList.runJob();
    65  
    66      assert.equal(currentURL(), '/jobs/run');
    67    });
    68  
    69    test('when there are no jobs, there is an empty message', async function(assert) {
    70      await JobsList.visit();
    71  
    72      assert.ok(JobsList.isEmpty, 'There is an empty message');
    73      assert.equal(JobsList.emptyState.headline, 'No Jobs', 'The message is appropriate');
    74    });
    75  
    76    test('when there are jobs, but no matches for a search result, there is an empty message', async function(assert) {
    77      server.create('job', { name: 'cat 1' });
    78      server.create('job', { name: 'cat 2' });
    79  
    80      await JobsList.visit();
    81  
    82      await JobsList.search('dog');
    83      assert.ok(JobsList.isEmpty, 'The empty message is shown');
    84      assert.equal(JobsList.emptyState.headline, 'No Matches', 'The message is appropriate');
    85    });
    86  
    87    test('searching resets the current page', async function(assert) {
    88      server.createList('job', JobsList.pageSize + 1, { createAllocations: false });
    89  
    90      await JobsList.visit();
    91      await JobsList.nextPage();
    92  
    93      assert.equal(currentURL(), '/jobs?page=2', 'Page query param captures page=2');
    94  
    95      await JobsList.search('foobar');
    96  
    97      assert.equal(currentURL(), '/jobs?search=foobar', 'No page query param');
    98    });
    99  
   100    test('when the namespace query param is set, only matching jobs are shown and the namespace value is forwarded to app state', async function(assert) {
   101      server.createList('namespace', 2);
   102      const job1 = server.create('job', { namespaceId: server.db.namespaces[0].id });
   103      const job2 = server.create('job', { namespaceId: server.db.namespaces[1].id });
   104  
   105      await JobsList.visit();
   106  
   107      assert.equal(JobsList.jobs.length, 1, 'One job in the default namespace');
   108      assert.equal(JobsList.jobs.objectAt(0).name, job1.name, 'The correct job is shown');
   109  
   110      const secondNamespace = server.db.namespaces[1];
   111      await JobsList.visit({ namespace: secondNamespace.id });
   112  
   113      assert.equal(JobsList.jobs.length, 1, `One job in the ${secondNamespace.name} namespace`);
   114      assert.equal(JobsList.jobs.objectAt(0).name, job2.name, 'The correct job is shown');
   115    });
   116  
   117    test('when accessing jobs is forbidden, show a message with a link to the tokens page', async function(assert) {
   118      server.pretender.get('/v1/jobs', () => [403, {}, null]);
   119  
   120      await JobsList.visit();
   121      assert.equal(JobsList.error.title, 'Not Authorized');
   122  
   123      await JobsList.error.seekHelp();
   124      assert.equal(currentURL(), '/settings/tokens');
   125    });
   126  
   127    function typeForJob(job) {
   128      return job.periodic ? 'periodic' : job.parameterized ? 'parameterized' : job.type;
   129    }
   130  
   131    test('the jobs list page has appropriate faceted search options', async function(assert) {
   132      await JobsList.visit();
   133  
   134      assert.ok(JobsList.facets.type.isPresent, 'Type facet found');
   135      assert.ok(JobsList.facets.status.isPresent, 'Status facet found');
   136      assert.ok(JobsList.facets.datacenter.isPresent, 'Datacenter facet found');
   137      assert.ok(JobsList.facets.prefix.isPresent, 'Prefix facet found');
   138    });
   139  
   140    testFacet('Type', {
   141      facet: JobsList.facets.type,
   142      paramName: 'type',
   143      expectedOptions: ['Batch', 'Parameterized', 'Periodic', 'Service', 'System'],
   144      async beforeEach() {
   145        server.createList('job', 2, { createAllocations: false, type: 'batch' });
   146        server.createList('job', 2, {
   147          createAllocations: false,
   148          type: 'batch',
   149          periodic: true,
   150          childrenCount: 0,
   151        });
   152        server.createList('job', 2, {
   153          createAllocations: false,
   154          type: 'batch',
   155          parameterized: true,
   156          childrenCount: 0,
   157        });
   158        server.createList('job', 2, { createAllocations: false, type: 'service' });
   159        await JobsList.visit();
   160      },
   161      filter(job, selection) {
   162        let displayType = job.type;
   163        if (job.parameterized) displayType = 'parameterized';
   164        if (job.periodic) displayType = 'periodic';
   165        return selection.includes(displayType);
   166      },
   167    });
   168  
   169    testFacet('Status', {
   170      facet: JobsList.facets.status,
   171      paramName: 'status',
   172      expectedOptions: ['Pending', 'Running', 'Dead'],
   173      async beforeEach() {
   174        server.createList('job', 2, {
   175          status: 'pending',
   176          createAllocations: false,
   177          childrenCount: 0,
   178        });
   179        server.createList('job', 2, {
   180          status: 'running',
   181          createAllocations: false,
   182          childrenCount: 0,
   183        });
   184        server.createList('job', 2, { status: 'dead', createAllocations: false, childrenCount: 0 });
   185        await JobsList.visit();
   186      },
   187      filter: (job, selection) => selection.includes(job.status),
   188    });
   189  
   190    testFacet('Datacenter', {
   191      facet: JobsList.facets.datacenter,
   192      paramName: 'dc',
   193      expectedOptions(jobs) {
   194        const allDatacenters = new Set(
   195          jobs.mapBy('datacenters').reduce((acc, val) => acc.concat(val), [])
   196        );
   197        return Array.from(allDatacenters).sort();
   198      },
   199      async beforeEach() {
   200        server.create('job', {
   201          datacenters: ['pdx', 'lax'],
   202          createAllocations: false,
   203          childrenCount: 0,
   204        });
   205        server.create('job', {
   206          datacenters: ['pdx', 'ord'],
   207          createAllocations: false,
   208          childrenCount: 0,
   209        });
   210        server.create('job', {
   211          datacenters: ['lax', 'jfk'],
   212          createAllocations: false,
   213          childrenCount: 0,
   214        });
   215        server.create('job', {
   216          datacenters: ['jfk', 'dfw'],
   217          createAllocations: false,
   218          childrenCount: 0,
   219        });
   220        server.create('job', { datacenters: ['pdx'], createAllocations: false, childrenCount: 0 });
   221        await JobsList.visit();
   222      },
   223      filter: (job, selection) => job.datacenters.find(dc => selection.includes(dc)),
   224    });
   225  
   226    testFacet('Prefix', {
   227      facet: JobsList.facets.prefix,
   228      paramName: 'prefix',
   229      expectedOptions: ['hashi (3)', 'nmd (2)', 'pre (2)'],
   230      async beforeEach() {
   231        [
   232          'pre-one',
   233          'hashi_one',
   234          'nmd.one',
   235          'one-alone',
   236          'pre_two',
   237          'hashi.two',
   238          'hashi-three',
   239          'nmd_two',
   240          'noprefix',
   241        ].forEach(name => {
   242          server.create('job', { name, createAllocations: false, childrenCount: 0 });
   243        });
   244        await JobsList.visit();
   245      },
   246      filter: (job, selection) => selection.find(prefix => job.name.startsWith(prefix)),
   247    });
   248  
   249    test('when the facet selections result in no matches, the empty state states why', async function(assert) {
   250      server.createList('job', 2, { status: 'pending', createAllocations: false, childrenCount: 0 });
   251  
   252      await JobsList.visit();
   253  
   254      await JobsList.facets.status.toggle();
   255      await JobsList.facets.status.options.objectAt(1).toggle();
   256      assert.ok(JobsList.isEmpty, 'There is an empty message');
   257      assert.equal(JobsList.emptyState.headline, 'No Matches', 'The message is appropriate');
   258    });
   259  
   260    test('the jobs list is immediately filtered based on query params', async function(assert) {
   261      server.create('job', { type: 'batch', createAllocations: false });
   262      server.create('job', { type: 'service', createAllocations: false });
   263  
   264      await JobsList.visit({ type: JSON.stringify(['batch']) });
   265  
   266      assert.equal(JobsList.jobs.length, 1, 'Only one job shown due to query param');
   267    });
   268  
   269    function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) {
   270      test(`the ${label} facet has the correct options`, async function(assert) {
   271        await beforeEach();
   272        await facet.toggle();
   273  
   274        let expectation;
   275        if (typeof expectedOptions === 'function') {
   276          expectation = expectedOptions(server.db.jobs);
   277        } else {
   278          expectation = expectedOptions;
   279        }
   280  
   281        assert.deepEqual(
   282          facet.options.map(option => option.label.trim()),
   283          expectation,
   284          'Options for facet are as expected'
   285        );
   286      });
   287  
   288      test(`the ${label} facet filters the jobs list by ${label}`, async function(assert) {
   289        let option;
   290  
   291        await beforeEach();
   292        await facet.toggle();
   293  
   294        option = facet.options.objectAt(0);
   295        await option.toggle();
   296  
   297        const selection = [option.key];
   298        const expectedJobs = server.db.jobs
   299          .filter(job => filter(job, selection))
   300          .sortBy('modifyIndex')
   301          .reverse();
   302  
   303        JobsList.jobs.forEach((job, index) => {
   304          assert.equal(
   305            job.id,
   306            expectedJobs[index].id,
   307            `Job at ${index} is ${expectedJobs[index].id}`
   308          );
   309        });
   310      });
   311  
   312      test(`selecting multiple options in the ${label} facet results in a broader search`, async function(assert) {
   313        const selection = [];
   314  
   315        await beforeEach();
   316        await facet.toggle();
   317  
   318        const option1 = facet.options.objectAt(0);
   319        const option2 = facet.options.objectAt(1);
   320        await option1.toggle();
   321        selection.push(option1.key);
   322        await option2.toggle();
   323        selection.push(option2.key);
   324  
   325        const expectedJobs = server.db.jobs
   326          .filter(job => filter(job, selection))
   327          .sortBy('modifyIndex')
   328          .reverse();
   329  
   330        JobsList.jobs.forEach((job, index) => {
   331          assert.equal(
   332            job.id,
   333            expectedJobs[index].id,
   334            `Job at ${index} is ${expectedJobs[index].id}`
   335          );
   336        });
   337      });
   338  
   339      test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) {
   340        const selection = [];
   341  
   342        await beforeEach();
   343        await facet.toggle();
   344  
   345        const option1 = facet.options.objectAt(0);
   346        const option2 = facet.options.objectAt(1);
   347        await option1.toggle();
   348        selection.push(option1.key);
   349        await option2.toggle();
   350        selection.push(option2.key);
   351  
   352        assert.equal(
   353          currentURL(),
   354          `/jobs?${paramName}=${encodeURIComponent(JSON.stringify(selection))}`,
   355          'URL has the correct query param key and value'
   356        );
   357      });
   358    }
   359  });