github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/ReleaseDialog/ReleaseDialog.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 { EnvironmentListItem, ReleaseDialog, ReleaseDialogProps } from './ReleaseDialog'; 17 import { fireEvent, render } from '@testing-library/react'; 18 import { UpdateAction, UpdateOverview, UpdateRolloutStatus, UpdateSidebar } from '../../utils/store'; 19 import { Environment, EnvironmentGroup, Priority, Release, RolloutStatus, UndeploySummary } from '../../../api/api'; 20 import { Spy } from 'spy4js'; 21 import { SideBar } from '../SideBar/SideBar'; 22 import { MemoryRouter } from 'react-router-dom'; 23 24 const mock_FormattedDate = Spy.mockModule('../FormattedDate/FormattedDate', 'FormattedDate'); 25 26 describe('Release Dialog', () => { 27 const getNode = (overrides: ReleaseDialogProps) => ( 28 <MemoryRouter> 29 <ReleaseDialog {...overrides} /> 30 </MemoryRouter> 31 ); 32 const getWrapper = (overrides: ReleaseDialogProps) => render(getNode(overrides)); 33 34 interface dataT { 35 name: string; 36 props: ReleaseDialogProps; 37 rels: Release[]; 38 envs: Environment[]; 39 envGroups: EnvironmentGroup[]; 40 expect_message: boolean; 41 expect_queues: number; 42 data_length: number; 43 teamName: string; 44 rolloutStatus?: { 45 application: string; 46 environment: string; 47 rolloutStatus: RolloutStatus; 48 rolloutStatusName: string; 49 }[]; 50 } 51 interface dataTLocks { 52 name: string; 53 props: ReleaseDialogProps; 54 rels: Release[]; 55 envs: Environment[]; 56 envGroups: EnvironmentGroup[]; 57 expect_message: boolean; 58 expect_queues: number; 59 data_length: number; 60 teamName: string; 61 } 62 const dataLocks: dataTLocks[] = [ 63 { 64 name: 'without locks', 65 props: { 66 app: 'test1', 67 version: 2, 68 }, 69 rels: [ 70 { 71 version: 2, 72 sourceMessage: 'test1', 73 sourceAuthor: 'test', 74 sourceCommitId: 'commit', 75 createdAt: new Date(2002), 76 undeployVersion: false, 77 prNumber: '#1337', 78 displayVersion: '2', 79 }, 80 ], 81 envs: [ 82 { 83 name: 'prod', 84 locks: {}, 85 applications: { 86 test1: { 87 name: 'test1', 88 version: 2, 89 locks: {}, 90 queuedVersion: 0, 91 undeployVersion: false, 92 }, 93 }, 94 distanceToUpstream: 0, 95 priority: Priority.UPSTREAM, 96 }, 97 ], 98 envGroups: [ 99 { 100 // this data should never appear (group with no envs with a well-defined priority), but we'll make it for the sake of the test. 101 distanceToUpstream: 0, 102 environmentGroupName: 'prod', 103 environments: [], 104 priority: Priority.UPSTREAM, 105 }, 106 ], 107 expect_message: true, 108 expect_queues: 0, 109 data_length: 2, 110 teamName: '', 111 }, 112 ]; 113 const data: dataT[] = [ 114 { 115 name: 'normal release', 116 props: { 117 app: 'test1', 118 version: 2, 119 }, 120 rels: [ 121 { 122 version: 2, 123 sourceMessage: 'test1', 124 sourceAuthor: 'test', 125 sourceCommitId: 'commit', 126 createdAt: new Date(2002), 127 undeployVersion: false, 128 prNumber: '#1337', 129 displayVersion: '2', 130 }, 131 ], 132 envs: [ 133 { 134 name: 'prod', 135 locks: { envLock: { message: 'envLock', lockId: 'ui-envlock' } }, 136 applications: { 137 test1: { 138 name: 'test1', 139 version: 2, 140 locks: { applock: { message: 'appLock', lockId: 'ui-applock' } }, 141 queuedVersion: 0, 142 undeployVersion: false, 143 }, 144 }, 145 distanceToUpstream: 0, 146 priority: Priority.UPSTREAM, 147 }, 148 ], 149 envGroups: [ 150 { 151 // this data should never appear (group with no envs with a well-defined priority), but we'll make it for the sake of the test. 152 distanceToUpstream: 0, 153 environmentGroupName: 'prod', 154 environments: [], 155 priority: Priority.UPSTREAM, 156 }, 157 ], 158 expect_message: true, 159 expect_queues: 0, 160 data_length: 2, 161 teamName: '', 162 }, 163 { 164 name: 'normal release with deploymentMetadata set', 165 props: { 166 app: 'test1', 167 version: 2, 168 }, 169 rels: [ 170 { 171 version: 2, 172 sourceMessage: 'test1', 173 sourceAuthor: 'test', 174 sourceCommitId: 'commit', 175 createdAt: new Date(2002), 176 undeployVersion: false, 177 prNumber: '#1337', 178 displayVersion: '2', 179 }, 180 ], 181 envs: [ 182 { 183 name: 'prod', 184 locks: { envLock: { message: 'envLock', lockId: 'ui-envlock' } }, 185 applications: { 186 test1: { 187 name: 'test1', 188 version: 2, 189 locks: { applock: { message: 'appLock', lockId: 'ui-applock' } }, 190 queuedVersion: 0, 191 undeployVersion: false, 192 deploymentMetaData: { deployAuthor: 'test', deployTime: '1688467491' }, 193 }, 194 }, 195 distanceToUpstream: 0, 196 priority: Priority.UPSTREAM, 197 }, 198 ], 199 envGroups: [ 200 { 201 // this data should never appear (group with no envs with a well-defined priority), but we'll make it for the sake of the test. 202 distanceToUpstream: 0, 203 environmentGroupName: 'prod', 204 environments: [], 205 priority: Priority.UPSTREAM, 206 }, 207 ], 208 expect_message: true, 209 expect_queues: 0, 210 data_length: 2, 211 teamName: '', 212 }, 213 { 214 name: 'two envs release', 215 props: { 216 app: 'test1', 217 version: 2, 218 }, 219 envs: [ 220 { 221 name: 'prod', 222 locks: { envLock: { message: 'envLock', lockId: 'ui-envlock' } }, 223 applications: { 224 test1: { 225 name: 'test1', 226 version: 2, 227 locks: { applock: { message: 'appLock', lockId: 'ui-applock' } }, 228 queuedVersion: 0, 229 undeployVersion: false, 230 }, 231 }, 232 distanceToUpstream: 0, 233 priority: Priority.UPSTREAM, 234 }, 235 { 236 name: 'dev', 237 locks: { envLock: { message: 'envLock', lockId: 'ui-envlock' } }, 238 applications: { 239 test1: { 240 name: 'test1', 241 version: 3, 242 locks: { applock: { message: 'appLock', lockId: 'ui-applock' } }, 243 queuedVersion: 666, 244 undeployVersion: false, 245 }, 246 }, 247 distanceToUpstream: 0, 248 priority: Priority.UPSTREAM, 249 }, 250 ], 251 envGroups: [ 252 { 253 // this data should never appear (group with no envs with a well-defined priority), but we'll make it for the sake of the test. 254 distanceToUpstream: 0, 255 environmentGroupName: 'prod', 256 environments: [], 257 priority: Priority.UPSTREAM, 258 }, 259 ], 260 rels: [ 261 { 262 sourceCommitId: 'cafe', 263 sourceMessage: 'the other commit message 2', 264 version: 2, 265 createdAt: new Date(2002), 266 undeployVersion: false, 267 prNumber: 'PR123', 268 sourceAuthor: 'nobody', 269 displayVersion: '2', 270 }, 271 { 272 sourceCommitId: 'cafe', 273 sourceMessage: 'the other commit message 3', 274 version: 3, 275 createdAt: new Date(2002), 276 undeployVersion: false, 277 prNumber: 'PR123', 278 sourceAuthor: 'nobody', 279 displayVersion: '3', 280 }, 281 ], 282 rolloutStatus: [ 283 { 284 application: 'test1', 285 environment: 'prod', 286 rolloutStatus: RolloutStatus.ROLLOUT_STATUS_PENDING, 287 rolloutStatusName: 'pending', 288 }, 289 { 290 application: 'test1', 291 environment: 'dev', 292 rolloutStatus: RolloutStatus.ROLLOUT_STATUS_PROGRESSING, 293 rolloutStatusName: 'progressing', 294 }, 295 ], 296 expect_message: true, 297 expect_queues: 1, 298 data_length: 5, 299 teamName: 'test me team', 300 }, 301 { 302 name: 'undeploy version release', 303 props: { 304 app: 'test1', 305 version: 4, 306 }, 307 rels: [ 308 { 309 version: 4, 310 sourceAuthor: 'test1', 311 sourceMessage: '', 312 sourceCommitId: '', 313 prNumber: '', 314 createdAt: new Date(2002), 315 undeployVersion: true, 316 displayVersion: '4', 317 }, 318 ], 319 envs: [], 320 envGroups: [], 321 expect_message: false, 322 expect_queues: 0, 323 data_length: 0, 324 teamName: '', 325 }, 326 ]; 327 328 const setTheStore = (testcase: dataT) => { 329 const asMap: { [key: string]: Environment } = {}; 330 testcase.envs.forEach((obj) => { 331 asMap[obj.name] = obj; 332 }); 333 UpdateOverview.set({ 334 applications: { 335 [testcase.props.app]: { 336 name: testcase.props.app, 337 releases: testcase.rels, 338 team: testcase.teamName, 339 sourceRepoUrl: 'url', 340 undeploySummary: UndeploySummary.NORMAL, 341 warnings: [], 342 }, 343 }, 344 environmentGroups: [ 345 { 346 environmentGroupName: 'dev', 347 environments: testcase.envs, 348 distanceToUpstream: 2, 349 priority: Priority.UNRECOGNIZED, 350 }, 351 ], 352 }); 353 const status = testcase.rolloutStatus; 354 if (status !== undefined) { 355 for (const app of status) { 356 UpdateRolloutStatus({ 357 application: app.application, 358 environment: app.environment, 359 version: 1, 360 rolloutStatus: app.rolloutStatus, 361 }); 362 } 363 } 364 }; 365 366 describe.each(data)(`Renders a Release Dialog`, (testcase) => { 367 it(testcase.name, () => { 368 // when 369 setTheStore(testcase); 370 getWrapper(testcase.props); 371 if (testcase.expect_message) { 372 expect(document.querySelector('.release-dialog-message')?.textContent).toContain( 373 testcase.rels[0].sourceMessage 374 ); 375 } else { 376 expect(document.querySelector('.release-dialog-message') === undefined); 377 } 378 expect(document.querySelectorAll('.env-card-data')).toHaveLength(testcase.data_length); 379 expect(document.querySelectorAll('.env-card-data-queue')).toHaveLength(testcase.expect_queues); 380 }); 381 }); 382 383 describe.each(data)(`Renders the environment cards`, (testcase) => { 384 it(testcase.name, () => { 385 // when 386 setTheStore(testcase); 387 getWrapper(testcase.props); 388 expect(document.querySelector('.release-env-list')?.children).toHaveLength(testcase.envs.length); 389 }); 390 }); 391 392 describe.each(data)(`Renders the environment locks`, (testcase) => { 393 it(testcase.name, () => { 394 // given 395 mock_FormattedDate.FormattedDate.returns(<div>some formatted date</div>); 396 // when 397 setTheStore(testcase); 398 getWrapper(testcase.props); 399 expect(document.body).toMatchSnapshot(); 400 expect(document.querySelectorAll('.release-env-group-list')).toHaveLength(1); 401 402 testcase.envs.forEach((env) => { 403 expect(document.querySelector('.env-locks')?.children).toHaveLength(Object.values(env.locks).length); 404 }); 405 }); 406 }); 407 408 describe.each(data)(`Renders the queuedVersion`, (testcase) => { 409 it(testcase.name, () => { 410 // when 411 setTheStore(testcase); 412 getWrapper(testcase.props); 413 expect(document.querySelectorAll('.env-card-data-queue')).toHaveLength(testcase.expect_queues); 414 }); 415 }); 416 417 describe.each(data)(`Renders the rollout status`, (testcase) => { 418 const status = testcase.rolloutStatus; 419 if (status === undefined) { 420 return; 421 } 422 it(testcase.name, () => { 423 const statusCount: { [status: string]: number } = {}; 424 for (const app of status) { 425 statusCount[app.rolloutStatusName] = (statusCount[app.rolloutStatusName] ?? 0) + 1; 426 } 427 // when 428 setTheStore(testcase); 429 getWrapper(testcase.props); 430 for (const [descr, count] of Object.entries(statusCount)) { 431 expect(document.querySelectorAll('.rollout__description_' + descr)).toHaveLength(count); 432 } 433 }); 434 }); 435 436 const querySelectorSafe = (selectors: string): Element => { 437 const result = document.querySelector(selectors); 438 if (!result) { 439 throw new Error('did not find in selector in document ' + selectors); 440 } 441 return result; 442 }; 443 444 describe(`Test automatic cart opening`, () => { 445 it('Test using direct call to open function', () => { 446 UpdateSidebar.set({ shown: false }); 447 UpdateSidebar.set({ shown: true }); 448 expect(UpdateSidebar.get().shown).toBeTruthy(); 449 }); 450 451 describe.each(dataLocks)('click handling', (testcase) => { 452 it('Test using deploy button click simulation ' + testcase.name, () => { 453 UpdateSidebar.set({ shown: false }); 454 UpdateAction.set({ actions: [] }); 455 setTheStore(testcase); 456 457 render( 458 <EnvironmentListItem 459 env={testcase.envs[0]} 460 envGroup={testcase.envGroups[0]} 461 app={testcase.props.app} 462 queuedVersion={0} 463 release={{ ...testcase.rels[0], version: 3 }} 464 /> 465 ); 466 const result = querySelectorSafe('.env-card-deploy-btn'); 467 fireEvent.click(result); 468 expect(UpdateSidebar.get().shown).toBeTruthy(); 469 expect(UpdateAction.get().actions).toEqual([ 470 { 471 action: { 472 $case: 'deploy', 473 deploy: { 474 application: 'test1', 475 environment: 'prod', 476 ignoreAllLocks: false, 477 lockBehavior: 2, 478 version: 3, 479 }, 480 }, 481 }, 482 { 483 action: { 484 $case: 'createEnvironmentApplicationLock', 485 createEnvironmentApplicationLock: { 486 application: 'test1', 487 environment: 'prod', 488 lockId: '', 489 message: '', 490 }, 491 }, 492 }, 493 ]); 494 }); 495 }); 496 it('Test using add lock button click simulation', () => { 497 const testcase = data[0]; 498 UpdateSidebar.set({ shown: false }); 499 UpdateAction.set({ actions: [] }); 500 setTheStore(testcase); 501 502 getWrapper(testcase.props); 503 render( 504 <EnvironmentListItem 505 env={testcase.envs[0]} 506 envGroup={testcase.envGroups[0]} 507 app={testcase.props.app} 508 queuedVersion={0} 509 release={testcase.rels[0]} 510 /> 511 ); 512 render(<SideBar toggleSidebar={Spy()} />); 513 const result = querySelectorSafe('.env-card-add-lock-btn'); 514 fireEvent.click(result); 515 expect(UpdateSidebar.get().shown).toBeTruthy(); 516 expect(UpdateAction.get().actions).toEqual([ 517 { 518 action: { 519 $case: 'createEnvironmentApplicationLock', 520 createEnvironmentApplicationLock: { 521 application: 'test1', 522 environment: 'prod', 523 lockId: '', 524 message: '', 525 }, 526 }, 527 }, 528 ]); 529 }); 530 }); 531 });