github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/ServiceLane/ServiceLane.test.tsx (about)

     1  /*This file is part of kuberpult.
     2  
     3  Kuberpult is free software: you can redistribute it and/or modify
     4  it under the terms of the Expat(MIT) License as published by
     5  the Free Software Foundation.
     6  
     7  Kuberpult is distributed in the hope that it will be useful,
     8  but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    10  MIT License for more details.
    11  
    12  You should have received a copy of the MIT License
    13  along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.
    14  
    15  Copyright 2023 freiheit.com*/
    16  import { render } from '@testing-library/react';
    17  import { ServiceLane } from './ServiceLane';
    18  import { UpdateOverview } from '../../utils/store';
    19  import { Spy } from 'spy4js';
    20  import {
    21      Application,
    22      BatchAction,
    23      Environment,
    24      Environment_Application,
    25      Priority,
    26      Release,
    27      UndeploySummary,
    28  } from '../../../api/api';
    29  import { MemoryRouter } from 'react-router-dom';
    30  import { elementQuerySelectorSafe, makeRelease } from '../../../setupTests';
    31  
    32  const mock_ReleaseCard = Spy.mockReactComponents('../../components/ReleaseCard/ReleaseCard', 'ReleaseCard');
    33  const mock_addAction = Spy.mockModule('../../utils/store', 'addAction');
    34  
    35  const extendRelease = (props: Partial<Release>): Release => ({
    36      version: 123,
    37      displayVersion: '123',
    38      sourceCommitId: 'id',
    39      sourceAuthor: 'author',
    40      sourceMessage: 'source',
    41      undeployVersion: false,
    42      prNumber: 'pr',
    43      ...props,
    44  });
    45  
    46  describe('Service Lane', () => {
    47      const getNode = (overrides: { application: Application }) => (
    48          <MemoryRouter>
    49              <ServiceLane {...overrides} />
    50          </MemoryRouter>
    51      );
    52      const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
    53      it('Renders a row of releases', () => {
    54          // when
    55          const sampleApp: Application = {
    56              name: 'test2',
    57              releases: [extendRelease({ version: 5 }), extendRelease({ version: 2 }), extendRelease({ version: 3 })],
    58              sourceRepoUrl: 'http://test2.com',
    59              team: 'example',
    60              undeploySummary: UndeploySummary.NORMAL,
    61              warnings: [],
    62          };
    63          UpdateOverview.set({
    64              applications: {
    65                  test2: sampleApp,
    66              },
    67          });
    68          getWrapper({ application: sampleApp });
    69  
    70          // then releases are sorted and Release card is called with props:
    71          expect(mock_ReleaseCard.ReleaseCard.getCallArgument(0, 0)).toStrictEqual({ app: sampleApp.name, version: 5 });
    72          expect(mock_ReleaseCard.ReleaseCard.getCallArgument(1, 0)).toStrictEqual({ app: sampleApp.name, version: 3 });
    73          expect(mock_ReleaseCard.ReleaseCard.getCallArgument(2, 0)).toStrictEqual({ app: sampleApp.name, version: 2 });
    74          mock_ReleaseCard.ReleaseCard.wasCalled(3);
    75      });
    76  });
    77  
    78  type TestData = {
    79      name: string;
    80      envs: Environment[];
    81  };
    82  
    83  type TestDataDiff = TestData & { diff: string; releases: Release[] };
    84  
    85  const data: TestDataDiff[] = [
    86      {
    87          name: 'test same version',
    88          diff: '-1',
    89          releases: [makeRelease(1)],
    90          envs: [
    91              {
    92                  name: 'foo',
    93                  applications: {
    94                      test2: {
    95                          version: 1,
    96                          name: '',
    97                          locks: {},
    98                          queuedVersion: 0,
    99                          undeployVersion: false,
   100                      },
   101                  },
   102                  distanceToUpstream: 0,
   103                  priority: Priority.UPSTREAM,
   104                  locks: {},
   105              },
   106              {
   107                  name: 'foo2',
   108                  applications: {
   109                      test2: {
   110                          version: 1,
   111                          name: '',
   112                          locks: {},
   113                          queuedVersion: 0,
   114                          undeployVersion: false,
   115                      },
   116                  },
   117                  distanceToUpstream: 0,
   118                  priority: Priority.UPSTREAM,
   119                  locks: {},
   120              },
   121          ],
   122      },
   123      {
   124          name: 'test no diff',
   125          diff: '0',
   126          releases: [makeRelease(1), makeRelease(2)],
   127          envs: [
   128              {
   129                  name: 'foo',
   130                  applications: {
   131                      test2: {
   132                          version: 1,
   133                          name: '',
   134                          locks: {},
   135                          queuedVersion: 0,
   136                          undeployVersion: false,
   137                      },
   138                  },
   139                  distanceToUpstream: 0,
   140                  priority: Priority.UPSTREAM,
   141                  locks: {},
   142              },
   143              {
   144                  name: 'foo2',
   145                  applications: {
   146                      test2: {
   147                          version: 2,
   148                          name: '',
   149                          locks: {},
   150                          queuedVersion: 0,
   151                          undeployVersion: false,
   152                      },
   153                  },
   154                  distanceToUpstream: 0,
   155                  priority: Priority.UPSTREAM,
   156                  locks: {},
   157              },
   158          ],
   159      },
   160      {
   161          name: 'test diff by one',
   162          diff: '1',
   163          releases: [makeRelease(1), makeRelease(4), makeRelease(2)],
   164          envs: [
   165              {
   166                  name: 'foo',
   167                  applications: {
   168                      test2: {
   169                          name: 'test2',
   170                          version: 1,
   171                          locks: {},
   172                          queuedVersion: 0,
   173                          undeployVersion: false,
   174                      },
   175                  },
   176                  locks: {},
   177                  distanceToUpstream: 0,
   178                  priority: Priority.UPSTREAM,
   179              },
   180              {
   181                  name: 'foo2',
   182                  applications: {
   183                      test2: {
   184                          name: 'test2',
   185                          version: 4,
   186                          locks: {},
   187                          queuedVersion: 0,
   188                          undeployVersion: false,
   189                      },
   190                  },
   191                  locks: {},
   192                  distanceToUpstream: 0,
   193                  priority: Priority.UPSTREAM,
   194              },
   195          ],
   196      },
   197      {
   198          name: 'test diff by two',
   199          diff: '2',
   200          releases: [makeRelease(2), makeRelease(4), makeRelease(3), makeRelease(5)],
   201          envs: [
   202              {
   203                  name: 'foo',
   204                  applications: {
   205                      test2: {
   206                          version: 2,
   207                          name: '',
   208                          locks: {},
   209                          queuedVersion: 0,
   210                          undeployVersion: false,
   211                      },
   212                  },
   213                  distanceToUpstream: 0,
   214                  priority: Priority.UPSTREAM,
   215                  locks: {},
   216              },
   217              {
   218                  name: 'foo2',
   219                  applications: {
   220                      test2: {
   221                          version: 5,
   222                          name: '',
   223                          locks: {},
   224                          queuedVersion: 0,
   225                          undeployVersion: false,
   226                      },
   227                  },
   228                  distanceToUpstream: 0,
   229                  priority: Priority.UPSTREAM,
   230                  locks: {},
   231              },
   232          ],
   233      },
   234  ];
   235  
   236  describe('Service Lane Diff', () => {
   237      const getNode = (overrides: { application: Application }) => (
   238          <MemoryRouter>
   239              <ServiceLane {...overrides} />
   240          </MemoryRouter>
   241      );
   242      const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
   243      describe.each(data)('Service Lane diff number', (testcase) => {
   244          it(testcase.name, () => {
   245              UpdateOverview.set({
   246                  applications: {
   247                      test2: {
   248                          releases: testcase.releases,
   249                          name: '',
   250                          team: '',
   251                          sourceRepoUrl: '',
   252                          undeploySummary: UndeploySummary.MIXED,
   253                          warnings: [],
   254                      },
   255                  },
   256                  environmentGroups: [
   257                      {
   258                          environments: testcase.envs,
   259                          environmentGroupName: 'group1',
   260                          distanceToUpstream: 0,
   261                          priority: Priority.UNRECOGNIZED,
   262                      },
   263                  ],
   264              });
   265              const sampleApp: Application = {
   266                  undeploySummary: UndeploySummary.NORMAL,
   267                  name: 'test2',
   268                  releases: [],
   269                  sourceRepoUrl: 'http://test2.com',
   270                  team: 'example',
   271                  warnings: [],
   272              };
   273              const { container } = getWrapper({ application: sampleApp });
   274  
   275              // check for the diff between versions
   276              if (testcase.diff === '-1' || testcase.diff === '0') {
   277                  expect(document.querySelector('.service-lane__diff--number') === undefined);
   278              } else {
   279                  expect(container.querySelector('.service-lane__diff--number')?.textContent).toContain(testcase.diff);
   280              }
   281          });
   282      });
   283  });
   284  
   285  type TestDataImportantRels = { name: string; releases: Release[]; currentlyDeployedVersion: number };
   286  
   287  const dataImportantRels: TestDataImportantRels[] = [
   288      {
   289          name: 'Gets deployed release first and 5 trailing releases',
   290          currentlyDeployedVersion: 9,
   291          releases: [
   292              makeRelease(9),
   293              makeRelease(7),
   294              makeRelease(6),
   295              makeRelease(5),
   296              makeRelease(4),
   297              makeRelease(3),
   298              makeRelease(2),
   299              makeRelease(1), // not important
   300          ],
   301      },
   302      {
   303          name: 'Gets latest release first, then deployed release and 4 trailing releases',
   304          currentlyDeployedVersion: 7,
   305          releases: [
   306              makeRelease(9),
   307              makeRelease(7),
   308              makeRelease(6),
   309              makeRelease(5),
   310              makeRelease(4),
   311              makeRelease(3),
   312              makeRelease(2),
   313              makeRelease(1), // not important
   314          ],
   315      },
   316      {
   317          name: 'jumps over not important second release',
   318          currentlyDeployedVersion: 6,
   319          releases: [
   320              makeRelease(9),
   321              makeRelease(7), // not important
   322              makeRelease(6),
   323              makeRelease(5),
   324              makeRelease(4),
   325              makeRelease(3),
   326              makeRelease(2),
   327              makeRelease(1), // not important
   328          ],
   329      },
   330  ];
   331  
   332  describe('Service Lane Important Releases', () => {
   333      const getNode = (overrides: { application: Application }) => (
   334          <MemoryRouter>
   335              <ServiceLane {...overrides} />
   336          </MemoryRouter>
   337      );
   338      const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
   339      describe.each(dataImportantRels)('Service Lane important releases', (testcase) => {
   340          it(testcase.name, () => {
   341              // given
   342              const sampleApp: Application = {
   343                  releases: testcase.releases,
   344                  name: 'test2',
   345                  team: 'test2',
   346                  sourceRepoUrl: 'test2',
   347                  undeploySummary: UndeploySummary.MIXED,
   348                  warnings: [],
   349              };
   350              UpdateOverview.set({
   351                  applications: {
   352                      test2: sampleApp,
   353                  },
   354                  environmentGroups: [
   355                      {
   356                          environments: [
   357                              {
   358                                  name: 'foo',
   359                                  applications: {
   360                                      test2: {
   361                                          name: 'test2',
   362                                          version: testcase.currentlyDeployedVersion,
   363                                          locks: {},
   364                                          undeployVersion: false,
   365                                          queuedVersion: 0,
   366                                      },
   367                                  },
   368                                  distanceToUpstream: 0,
   369                                  priority: Priority.UPSTREAM,
   370                                  locks: {},
   371                              },
   372                          ],
   373                          environmentGroupName: 'group1',
   374                          distanceToUpstream: 0,
   375                          priority: Priority.UNRECOGNIZED,
   376                      },
   377                  ],
   378              });
   379              // when
   380              getWrapper({ application: sampleApp });
   381  
   382              // then - the latest release is always important and is displayed first
   383              expect(mock_ReleaseCard.ReleaseCard.getCallArgument(0)).toMatchObject({
   384                  version: testcase.releases[0].version,
   385              });
   386              if (testcase.currentlyDeployedVersion !== testcase.releases[0].version) {
   387                  // then - the currently deployed version always important and displayed second after latest
   388                  expect(mock_ReleaseCard.ReleaseCard.getCallArgument(1)).toMatchObject({
   389                      version: testcase.currentlyDeployedVersion,
   390                  });
   391              }
   392              if (testcase.releases[1].version > testcase.currentlyDeployedVersion) {
   393                  // then - second release not deployed and not latest -> not important
   394                  mock_ReleaseCard.ReleaseCard.wasNotCalledWith(
   395                      { app: 'test2', version: testcase.releases[1].version },
   396                      Spy.IGNORE
   397                  );
   398              }
   399              // then - the old release is not important and not displayed
   400              mock_ReleaseCard.ReleaseCard.wasNotCalledWith(
   401                  { app: 'test2', version: testcase.releases[7].version },
   402                  Spy.IGNORE
   403              );
   404          });
   405      });
   406  });
   407  
   408  type TestDataUndeploy = TestData & {
   409      renderedApp: Application;
   410      expectedUndeployButton: string | null;
   411      expectedAction: BatchAction;
   412  };
   413  const dataUndeploy: TestDataUndeploy[] = (() => {
   414      const result: TestDataUndeploy[] = [
   415          {
   416              name: 'test no prepareUndeploy',
   417              renderedApp: {
   418                  name: 'test1',
   419                  releases: [],
   420                  sourceRepoUrl: 'http://test2.com',
   421                  team: 'example',
   422                  undeploySummary: UndeploySummary.NORMAL,
   423                  warnings: [],
   424              },
   425              envs: [
   426                  {
   427                      name: 'foo2',
   428                      applications: {},
   429                      distanceToUpstream: 0,
   430                      priority: Priority.UPSTREAM,
   431                      locks: {},
   432                  },
   433              ],
   434              expectedUndeployButton: '⋮',
   435              expectedAction: {
   436                  action: {
   437                      $case: 'prepareUndeploy',
   438                      prepareUndeploy: { application: 'test1' },
   439                  },
   440              },
   441          },
   442          {
   443              name: 'test no undeploy',
   444              renderedApp: {
   445                  name: 'test1',
   446                  releases: [],
   447                  sourceRepoUrl: 'http://test2.com',
   448                  team: 'example',
   449                  undeploySummary: UndeploySummary.UNDEPLOY,
   450                  warnings: [],
   451              },
   452              envs: [
   453                  {
   454                      name: 'foo2',
   455                      applications: {},
   456                      distanceToUpstream: 0,
   457                      priority: Priority.UPSTREAM,
   458                      locks: {},
   459                  },
   460              ],
   461              expectedUndeployButton: '⋮',
   462              expectedAction: {
   463                  action: {
   464                      $case: 'undeploy',
   465                      undeploy: { application: 'test1' },
   466                  },
   467              },
   468          },
   469      ];
   470      return result;
   471  })();
   472  
   473  describe('Service Lane ⋮ menu', () => {
   474      const getNode = (overrides: { application: Application }) => (
   475          <MemoryRouter>
   476              <ServiceLane {...overrides} />
   477          </MemoryRouter>
   478      );
   479      const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
   480      describe.each(dataUndeploy)('Undeploy Buttons', (testcase) => {
   481          it(testcase.name, () => {
   482              mock_addAction.addAction.returns(undefined);
   483  
   484              UpdateOverview.set({
   485                  applications: {
   486                      test1: testcase.renderedApp,
   487                  },
   488                  environmentGroups: [
   489                      {
   490                          environments: testcase.envs,
   491                          environmentGroupName: 'group1',
   492                          distanceToUpstream: 0,
   493                          priority: Priority.UNRECOGNIZED,
   494                      },
   495                  ],
   496              });
   497  
   498              const { container } = getWrapper({ application: testcase.renderedApp });
   499  
   500              const undeployButton = elementQuerySelectorSafe(container, '.dots-menu-hidden');
   501              const label = elementQuerySelectorSafe(undeployButton, 'span');
   502              expect(label?.textContent).toBe(testcase.expectedUndeployButton);
   503  
   504              mock_addAction.addAction.wasNotCalled();
   505          });
   506      });
   507  });
   508  
   509  type TestDataAppLockSummary = TestData & {
   510      renderedApp: Application;
   511      expected: string | undefined;
   512  };
   513  const dataAppLockSummary: TestDataAppLockSummary[] = (() => {
   514      const appWith1Lock: Environment_Application = {
   515          name: 'test1',
   516          version: 123,
   517          queuedVersion: 0,
   518          undeployVersion: false,
   519          locks: {
   520              l1: { message: 'test lock', lockId: '321' },
   521          },
   522      };
   523      const appWith2Locks: Environment_Application = {
   524          name: 'test1',
   525          version: 123,
   526          queuedVersion: 0,
   527          undeployVersion: false,
   528          locks: {
   529              l1: { message: 'test lock', lockId: '321' },
   530              l2: { message: 'test lock', lockId: '321' },
   531          },
   532      };
   533      const result: TestDataAppLockSummary[] = [
   534          {
   535              name: 'test no prepareUndeploy',
   536              renderedApp: {
   537                  name: 'test1',
   538                  releases: [],
   539                  sourceRepoUrl: 'http://test2.com',
   540                  team: 'example',
   541                  undeploySummary: UndeploySummary.NORMAL,
   542                  warnings: [],
   543              },
   544              envs: [
   545                  {
   546                      name: 'foo2',
   547                      applications: {},
   548                      distanceToUpstream: 0,
   549                      priority: Priority.UPSTREAM,
   550                      locks: {
   551                          envLockThatDoesNotMatter: {
   552                              message: 'I am an env lock, I should not count',
   553                              lockId: '487329463874223',
   554                          },
   555                      },
   556                  },
   557              ],
   558              expected: undefined,
   559          },
   560          {
   561              name: 'test one lock',
   562              renderedApp: {
   563                  name: 'test1',
   564                  releases: [],
   565                  sourceRepoUrl: 'http://test2.com',
   566                  team: 'example',
   567                  undeploySummary: UndeploySummary.NORMAL,
   568                  warnings: [],
   569              },
   570              envs: [
   571                  {
   572                      name: 'foo2',
   573                      applications: {
   574                          foo2: appWith1Lock,
   575                      },
   576                      distanceToUpstream: 0,
   577                      priority: Priority.UPSTREAM,
   578                      locks: {},
   579                  },
   580              ],
   581              expected: '"test1" has 1 application lock. Click on a tile to see details.',
   582          },
   583          {
   584              name: 'test two locks',
   585              renderedApp: {
   586                  name: 'test1',
   587                  releases: [],
   588                  sourceRepoUrl: 'http://test2.com',
   589                  team: 'example',
   590                  undeploySummary: UndeploySummary.NORMAL,
   591                  warnings: [],
   592              },
   593              envs: [
   594                  {
   595                      name: 'foo2',
   596                      applications: {
   597                          foo2: appWith2Locks,
   598                      },
   599                      distanceToUpstream: 0,
   600                      priority: Priority.UPSTREAM,
   601                      locks: {},
   602                  },
   603              ],
   604              expected: '"test1" has 2 application locks. Click on a tile to see details.',
   605          },
   606      ];
   607      return result;
   608  })();
   609  
   610  describe('Service Lane AppLockSummary', () => {
   611      const getNode = (overrides: { application: Application }) => (
   612          <MemoryRouter>
   613              <ServiceLane {...overrides} />
   614          </MemoryRouter>
   615      );
   616      const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
   617      describe.each(dataAppLockSummary)('diff', (testcase) => {
   618          it(testcase.name, () => {
   619              mock_addAction.addAction.returns(undefined);
   620  
   621              UpdateOverview.set({
   622                  applications: {
   623                      test1: testcase.renderedApp,
   624                  },
   625                  environmentGroups: [
   626                      {
   627                          environments: testcase.envs,
   628                          environmentGroupName: 'group1',
   629                          distanceToUpstream: 0,
   630                          priority: Priority.UNRECOGNIZED,
   631                      },
   632                  ],
   633              });
   634  
   635              const { container } = getWrapper({ application: testcase.renderedApp });
   636  
   637              const appLockSummary = container.querySelector('.test-app-lock-summary div');
   638              expect(appLockSummary?.attributes.getNamedItem('title')?.value).toBe(testcase.expected);
   639          });
   640      });
   641  });