@centreon/js-config 24.4.20 → 24.4.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-namespace */
2
+ import path from 'path';
3
+ import 'cypress-wait-until';
2
4
 
3
5
  import './commands/configuration';
4
6
  import './commands/monitoring';
@@ -7,10 +9,53 @@ import installLogsCollector from 'cypress-terminal-report/src/installLogsCollect
7
9
 
8
10
  installLogsCollector({ enableExtendedCollector: true });
9
11
 
12
+ const apiBase = '/centreon/api';
13
+ const apiActionV1 = `${apiBase}/index.php`;
10
14
  const apiLoginV2 = '/centreon/authentication/providers/configurations/local';
11
15
 
12
16
  const artifactIllegalCharactersMatcher = /[,\s/|<>*?:"]/g;
13
17
 
18
+ export enum PatternType {
19
+ contains = '*',
20
+ endsWith = '$',
21
+ equals = '',
22
+ startsWith = '^'
23
+ }
24
+
25
+ interface GetByLabelProps {
26
+ label: string;
27
+ patternType?: PatternType;
28
+ tag?: string;
29
+ }
30
+
31
+ Cypress.Commands.add(
32
+ 'getByLabel',
33
+ ({
34
+ tag = '',
35
+ patternType = PatternType.equals,
36
+ label
37
+ }: GetByLabelProps): Cypress.Chainable => {
38
+ return cy.get(`${tag}[aria-label${patternType}="${label}"]`);
39
+ }
40
+ );
41
+
42
+ interface GetByTestIdProps {
43
+ patternType?: PatternType;
44
+ tag?: string;
45
+ testId: string;
46
+ }
47
+
48
+ Cypress.Commands.add(
49
+ 'getByTestId',
50
+ ({
51
+ tag = '',
52
+ patternType = PatternType.equals,
53
+ testId
54
+ }: GetByTestIdProps): Cypress.Chainable => {
55
+ return cy.get(`${tag}[data-testid${patternType}="${testId}"]`);
56
+ }
57
+ );
58
+
14
59
  Cypress.Commands.add('getWebVersion', (): Cypress.Chainable => {
15
60
  return cy
16
61
  .exec(
@@ -59,7 +104,7 @@ interface NavigateToProps {
59
104
 
60
105
  Cypress.Commands.add(
61
106
  'navigateTo',
62
- ({ rootItemNumber, subMenu, page }): void => {
107
+ ({ rootItemNumber, subMenu, page }: NavigateToProps): void => {
63
108
  if (subMenu) {
64
109
  cy.hoverRootMenuItem(rootItemNumber)
65
110
  .contains(subMenu)
@@ -100,6 +145,24 @@ Cypress.Commands.add(
100
145
  }
101
146
  );
102
147
 
148
+ Cypress.Commands.add('getContainerId', (containerName: string) => {
149
+ cy.log(`Getting container id of ${containerName}`);
150
+
151
+ return cy.task('getContainerId', containerName);
152
+ });
153
+
154
+ Cypress.Commands.add('getContainerIpAddress', (containerName: string) => {
155
+ cy.log(`Getting container ip address of ${containerName}`);
156
+
157
+ return cy.task('getContainerIpAddress', containerName);
158
+ });
159
+
160
+ Cypress.Commands.add('getContainersLogs', () => {
161
+ cy.log('Getting containers logs');
162
+
163
+ return cy.task('getContainersLogs');
164
+ });
165
+
103
166
  interface CopyFromContainerProps {
104
167
  destination: string;
105
168
  name?: string;
@@ -108,38 +171,54 @@ interface CopyFromContainerProps {
108
171
 
109
172
  Cypress.Commands.add(
110
173
  'copyFromContainer',
111
- (
112
- {
113
- name = Cypress.env('dockerName'),
114
- source,
115
- destination
116
- }: CopyFromContainerProps,
117
- options?: Partial<Cypress.ExecOptions>
118
- ) => {
119
- return cy.exec(`docker cp ${name}:${source} "${destination}"`, options);
174
+ ({ name = 'web', source, destination }: CopyFromContainerProps) => {
175
+ cy.log(`Copy content from ${name}:${source} to ${destination}`);
176
+
177
+ return cy.task('copyFromContainer', {
178
+ destination,
179
+ serviceName: name,
180
+ source
181
+ });
120
182
  }
121
183
  );
122
184
 
185
+ export enum CopyToContainerContentType {
186
+ Directory = 'directory',
187
+ File = 'file'
188
+ }
189
+
123
190
  interface CopyToContainerProps {
124
191
  destination: string;
125
192
  name?: string;
126
193
  source: string;
194
+ type: CopyToContainerContentType;
127
195
  }
128
196
 
129
197
  Cypress.Commands.add(
130
198
  'copyToContainer',
131
- (
132
- {
133
- name = Cypress.env('dockerName'),
199
+ ({ name = 'web', source, destination, type }: CopyToContainerProps) => {
200
+ cy.log(`Copy content from ${source} to ${name}:${destination}`);
201
+
202
+ return cy.task('copyToContainer', {
203
+ destination,
204
+ serviceName: name,
134
205
  source,
135
- destination
136
- }: CopyToContainerProps,
137
- options?: Partial<Cypress.ExecOptions>
138
- ) => {
139
- return cy.exec(`docker cp ${source} ${name}:${destination}`, options);
206
+ type
207
+ });
140
208
  }
141
209
  );
142
210
 
211
+ Cypress.Commands.add('loginAsAdminViaApiV2', (): Cypress.Chainable => {
212
+ return cy.request({
213
+ body: {
214
+ login: 'admin',
215
+ password: 'Centreon!2021'
216
+ },
217
+ method: 'POST',
218
+ url: apiLoginV2
219
+ });
220
+ });
221
+
143
222
  interface LoginByTypeOfUserProps {
144
223
  jsonName?: string;
145
224
  loginViaApi?: boolean;
@@ -186,6 +265,33 @@ Cypress.Commands.add(
186
265
  }
187
266
  );
188
267
 
268
+ Cypress.Commands.add('logout', (): void => {
269
+ cy.getByLabel({ label: 'Profile' }).should('exist').click();
270
+
271
+ cy.intercept({
272
+ method: 'GET',
273
+ times: 1,
274
+ url: '/centreon/api/latest/authentication/logout'
275
+ }).as('logout');
276
+
277
+ cy.contains(/^Logout$/).click();
278
+
279
+ cy.wait('@logout').its('response.statusCode').should('eq', 302);
280
+
281
+ // https://github.com/cypress-io/cypress/issues/25841
282
+ cy.clearAllCookies();
283
+ });
284
+
285
+ Cypress.Commands.add('logoutViaAPI', (): Cypress.Chainable => {
286
+ return cy
287
+ .request({
288
+ method: 'GET',
289
+ url: '/centreon/authentication/logout'
290
+ })
291
+ .visit('/')
292
+ .getByLabel({ label: 'Alias', tag: 'input' });
293
+ });
294
+
189
295
  Cypress.Commands.add(
190
296
  'visitEmptyPage',
191
297
  (): Cypress.Chainable =>
@@ -197,32 +303,91 @@ Cypress.Commands.add(
197
303
  .visit('/waiting-page')
198
304
  );
199
305
 
200
- Cypress.Commands.add('waitForContainerAndSetToken', (): Cypress.Chainable => {
201
- return cy.setUserTokenApiV1();
202
- });
203
-
204
306
  interface ExecInContainerProps {
205
- command: string;
307
+ command: string | Array<string>;
206
308
  name: string;
207
309
  }
208
310
 
311
+ interface ExecInContainerResult {
312
+ exitCode: number;
313
+ output: string;
314
+ }
315
+
209
316
  Cypress.Commands.add(
210
317
  'execInContainer',
211
318
  ({ command, name }: ExecInContainerProps): Cypress.Chainable => {
212
- return cy
213
- .exec(`docker exec -i ${name} ${command}`, { failOnNonZeroExit: false })
214
- .then((result) => {
215
- if (result.code) {
216
- // output will not be truncated
217
- throw new Error(`
218
- Execution of "${command}" failed
219
- Exit code: ${result.code}
220
- Stdout:\n${result.stdout}
221
- Stderr:\n${result.stderr}`);
222
- }
319
+ const commands =
320
+ typeof command === 'string' || command instanceof String
321
+ ? [command]
322
+ : command;
323
+
324
+ const results = commands.reduce(
325
+ (acc, runCommand) => {
326
+ cy.task<ExecInContainerResult>(
327
+ 'execInContainer',
328
+ { command: runCommand, name },
329
+ { timeout: 600000 }
330
+ ).then((result) => {
331
+ if (result.exitCode) {
332
+ cy.log(result.output);
333
+
334
+ // output will not be truncated
335
+ throw new Error(`
336
+ Execution of "${runCommand}" failed
337
+ Exit code: ${result.exitCode}
338
+ Output:\n${result.output}`);
339
+ }
340
+
341
+ acc.output = `${acc.output}${result.output}`;
342
+ });
223
343
 
224
- return cy.wrap(result);
225
- });
344
+ return acc;
345
+ },
346
+ { exitCode: 0, output: '' }
347
+ );
348
+
349
+ return cy.wrap(results);
350
+ }
351
+ );
352
+
353
+ interface RequestOnDatabaseProps {
354
+ database: string;
355
+ query: string;
356
+ }
357
+
358
+ Cypress.Commands.add(
359
+ 'requestOnDatabase',
360
+ ({ database, query }: RequestOnDatabaseProps): Cypress.Chainable => {
361
+ return cy.task('requestOnDatabase', { database, query });
362
+ }
363
+ );
364
+
365
+ interface SetUserTokenApiV1Props {
366
+ login?: string;
367
+ password?: string;
368
+ }
369
+
370
+ Cypress.Commands.add(
371
+ 'setUserTokenApiV1',
372
+ ({
373
+ login = 'admin',
374
+ password = 'Centreon!2021'
375
+ }: SetUserTokenApiV1Props = {}): Cypress.Chainable => {
376
+ return cy
377
+ .request({
378
+ body: {
379
+ password,
380
+ username: login
381
+ },
382
+ headers: {
383
+ 'Content-Type': 'application/x-www-form-urlencoded'
384
+ },
385
+ method: 'POST',
386
+ url: `${apiActionV1}?action=authenticate`
387
+ })
388
+ .then(({ body }) =>
389
+ window.localStorage.setItem('userTokenApiV1', body.authToken)
390
+ );
226
391
  }
227
392
  );
228
393
 
@@ -232,6 +397,7 @@ interface PortBinding {
232
397
  }
233
398
 
234
399
  interface StartContainerProps {
400
+ command?: string;
235
401
  image: string;
236
402
  name: string;
237
403
  portBindings: Array<PortBinding>;
@@ -239,145 +405,88 @@ interface StartContainerProps {
239
405
 
240
406
  Cypress.Commands.add(
241
407
  'startContainer',
242
- ({ name, image, portBindings }: StartContainerProps): Cypress.Chainable => {
408
+ ({
409
+ command,
410
+ name,
411
+ image,
412
+ portBindings
413
+ }: StartContainerProps): Cypress.Chainable => {
243
414
  cy.log(`Starting container ${name} from image ${image}`);
244
415
 
245
416
  return cy.task(
246
417
  'startContainer',
247
- { image, name, portBindings },
418
+ { command, image, name, portBindings },
248
419
  { timeout: 600000 } // 10 minutes because docker pull can be very slow
249
420
  );
250
421
  }
251
422
  );
252
423
 
253
- Cypress.Commands.add(
254
- 'createDirectory',
255
- (directoryPath: string): Cypress.Chainable => {
256
- return cy.task('createDirectory', directoryPath);
257
- }
258
- );
259
-
260
- interface StartWebContainerProps {
261
- name?: string;
262
- os?: string;
424
+ interface StartContainersProps {
425
+ composeFile?: string;
426
+ databaseImage?: string;
427
+ moduleName?: string;
428
+ openidImage?: string;
429
+ profiles?: Array<string>;
430
+ samlImage?: string;
263
431
  useSlim?: boolean;
264
- version?: string;
432
+ webOs?: string;
433
+ webVersion?: string;
265
434
  }
266
435
 
267
436
  Cypress.Commands.add(
268
- 'startWebContainer',
437
+ 'startContainers',
269
438
  ({
270
- name = Cypress.env('dockerName'),
271
- os = Cypress.env('WEB_IMAGE_OS'),
439
+ composeFile,
440
+ databaseImage = Cypress.env('DATABASE_IMAGE'),
441
+ moduleName = 'centreon-web',
442
+ openidImage = `docker.centreon.com/centreon/keycloak:${Cypress.env(
443
+ 'OPENID_IMAGE_VERSION'
444
+ )}`,
445
+ profiles = [],
446
+ samlImage = `docker.centreon.com/centreon/keycloak:${Cypress.env(
447
+ 'SAML_IMAGE_VERSION'
448
+ )}`,
272
449
  useSlim = true,
273
- version = Cypress.env('WEB_IMAGE_VERSION')
274
- }: StartWebContainerProps = {}): Cypress.Chainable => {
450
+ webOs = Cypress.env('WEB_IMAGE_OS'),
451
+ webVersion = Cypress.env('WEB_IMAGE_VERSION')
452
+ }: StartContainersProps = {}): Cypress.Chainable => {
453
+ cy.log('Starting containers ...');
454
+
455
+ let composeFilePath = composeFile;
456
+ if (!composeFile) {
457
+ const cypressDir = path.dirname(Cypress.config('configFile'));
458
+ composeFilePath = `${cypressDir}/../../../.github/docker/docker-compose.yml`;
459
+ }
460
+
275
461
  const slimSuffix = useSlim ? '-slim' : '';
276
462
 
277
- const image = `docker.centreon.com/centreon/centreon-web${slimSuffix}-${os}:${version}`;
463
+ const webImage = `docker.centreon.com/centreon/${moduleName}${slimSuffix}-${webOs}:${webVersion}`;
278
464
 
279
465
  return cy
280
- .startContainer({
281
- image,
282
- name,
283
- portBindings: [{ destination: 4000, source: 80 }]
284
- })
466
+ .task(
467
+ 'startContainers',
468
+ {
469
+ composeFile: composeFilePath,
470
+ databaseImage,
471
+ openidImage,
472
+ profiles,
473
+ samlImage,
474
+ webImage
475
+ },
476
+ { timeout: 600000 } // 10 minutes because docker pull can be very slow
477
+ )
285
478
  .then(() => {
286
479
  const baseUrl = 'http://127.0.0.1:4000';
287
480
 
288
481
  Cypress.config('baseUrl', baseUrl);
289
482
 
290
- return cy.task(
291
- 'waitOn',
292
- `${baseUrl}/centreon/api/latest/platform/installation/status`
293
- );
483
+ return cy.wrap(null);
294
484
  })
295
485
  .visit('/') // this is necessary to refresh browser cause baseUrl has changed (flash appears in video)
296
486
  .setUserTokenApiV1();
297
487
  }
298
488
  );
299
489
 
300
- interface StopWebContainerProps {
301
- name?: string;
302
- }
303
-
304
- Cypress.Commands.add(
305
- 'stopWebContainer',
306
- ({
307
- name = Cypress.env('dockerName')
308
- }: StopWebContainerProps = {}): Cypress.Chainable => {
309
- const logDirectory = `results/logs/${Cypress.spec.name.replace(
310
- artifactIllegalCharactersMatcher,
311
- '_'
312
- )}/${Cypress.currentTest.title.replace(
313
- artifactIllegalCharactersMatcher,
314
- '_'
315
- )}`;
316
-
317
- return cy
318
- .visitEmptyPage()
319
- .createDirectory(logDirectory)
320
- .copyFromContainer({
321
- destination: `${logDirectory}/broker`,
322
- name,
323
- source: '/var/log/centreon-broker'
324
- })
325
- .copyFromContainer({
326
- destination: `${logDirectory}/engine`,
327
- name,
328
- source: '/var/log/centreon-engine'
329
- })
330
- .copyFromContainer({
331
- destination: `${logDirectory}/centreon`,
332
- name,
333
- source: '/var/log/centreon'
334
- })
335
- .copyFromContainer({
336
- destination: `${logDirectory}/centreon-gorgone`,
337
- name,
338
- source: '/var/log/centreon-gorgone'
339
- })
340
- .then(() => {
341
- if (Cypress.env('WEB_IMAGE_OS').includes('alma')) {
342
- return cy.copyFromContainer({
343
- destination: `${logDirectory}/php`,
344
- name,
345
- source: '/var/log/php-fpm'
346
- });
347
- }
348
-
349
- return cy.copyFromContainer(
350
- {
351
- destination: `${logDirectory}/php8.1-fpm-centreon-error.log`,
352
- name,
353
- source: '/var/log/php8.1-fpm-centreon-error.log'
354
- },
355
- { failOnNonZeroExit: false }
356
- );
357
- })
358
- .then(() => {
359
- if (Cypress.env('WEB_IMAGE_OS').includes('alma')) {
360
- return cy.copyFromContainer({
361
- destination: `${logDirectory}/httpd`,
362
- name,
363
- source: '/var/log/httpd'
364
- });
365
- }
366
-
367
- return cy.copyFromContainer(
368
- {
369
- destination: `${logDirectory}/apache2`,
370
- name,
371
- source: '/var/log/apache2'
372
- },
373
- { failOnNonZeroExit: false }
374
- );
375
- })
376
- .exec(`chmod -R 755 "${logDirectory}"`)
377
- .stopContainer({ name });
378
- }
379
- );
380
-
381
490
  interface StopContainerProps {
382
491
  name: string;
383
492
  }
@@ -387,20 +496,109 @@ Cypress.Commands.add(
387
496
  ({ name }: StopContainerProps): Cypress.Chainable => {
388
497
  cy.log(`Stopping container ${name}`);
389
498
 
390
- cy.exec(`docker logs ${name}`).then(({ stdout }) => {
391
- cy.writeFile(
392
- `results/logs/${Cypress.spec.name.replace(
393
- artifactIllegalCharactersMatcher,
394
- '_'
395
- )}/${Cypress.currentTest.title.replace(
396
- artifactIllegalCharactersMatcher,
397
- '_'
398
- )}/container-${name}.log`,
399
- stdout
499
+ return cy.task('stopContainer', { name });
500
+ }
501
+ );
502
+
503
+ Cypress.Commands.add('stopContainers', (): Cypress.Chainable => {
504
+ cy.log('Stopping containers ...');
505
+
506
+ const logDirectory = `results/logs/${Cypress.spec.name.replace(
507
+ artifactIllegalCharactersMatcher,
508
+ '_'
509
+ )}/${Cypress.currentTest.title.replace(
510
+ artifactIllegalCharactersMatcher,
511
+ '_'
512
+ )}`;
513
+
514
+ const name = 'web';
515
+
516
+ return cy
517
+ .visitEmptyPage()
518
+ .createDirectory(logDirectory)
519
+ .getContainersLogs()
520
+ .then((containersLogs: Array<Array<string>>) => {
521
+ Object.entries(containersLogs).forEach(([containerName, logs]) => {
522
+ cy.writeFile(
523
+ `results/logs/${Cypress.spec.name.replace(
524
+ artifactIllegalCharactersMatcher,
525
+ '_'
526
+ )}/${Cypress.currentTest.title.replace(
527
+ artifactIllegalCharactersMatcher,
528
+ '_'
529
+ )}/container-${containerName}.log`,
530
+ logs
531
+ );
532
+ });
533
+ })
534
+ .copyFromContainer({
535
+ destination: `${logDirectory}/broker`,
536
+ name,
537
+ source: '/var/log/centreon-broker'
538
+ })
539
+ .copyFromContainer({
540
+ destination: `${logDirectory}/engine`,
541
+ name,
542
+ source: '/var/log/centreon-engine'
543
+ })
544
+ .copyFromContainer({
545
+ destination: `${logDirectory}/centreon`,
546
+ name,
547
+ source: '/var/log/centreon'
548
+ })
549
+ .copyFromContainer({
550
+ destination: `${logDirectory}/centreon-gorgone`,
551
+ name,
552
+ source: '/var/log/centreon-gorgone'
553
+ })
554
+ .then(() => {
555
+ if (Cypress.env('WEB_IMAGE_OS').includes('alma')) {
556
+ return cy.copyFromContainer({
557
+ destination: `${logDirectory}/php`,
558
+ name,
559
+ source: '/var/log/php-fpm'
560
+ });
561
+ }
562
+
563
+ return cy.copyFromContainer(
564
+ {
565
+ destination: `${logDirectory}/php8.1-fpm-centreon-error.log`,
566
+ name,
567
+ source: '/var/log/php8.1-fpm-centreon-error.log'
568
+ },
569
+ { failOnNonZeroExit: false }
570
+ );
571
+ })
572
+ .then(() => {
573
+ if (Cypress.env('WEB_IMAGE_OS').includes('alma')) {
574
+ return cy.copyFromContainer({
575
+ destination: `${logDirectory}/httpd`,
576
+ name,
577
+ source: '/var/log/httpd'
578
+ });
579
+ }
580
+
581
+ return cy.copyFromContainer(
582
+ {
583
+ destination: `${logDirectory}/apache2`,
584
+ name,
585
+ source: '/var/log/apache2'
586
+ },
587
+ { failOnNonZeroExit: false }
400
588
  );
401
- });
589
+ })
590
+ .exec(`chmod -R 755 "${logDirectory}"`)
591
+ .task(
592
+ 'stopContainers',
593
+ {},
594
+ { timeout: 600000 } // 10 minutes because docker pull can be very slow
595
+ );
596
+ });
402
597
 
403
- return cy.task('stopContainer', { name });
598
+ Cypress.Commands.add(
599
+ 'createDirectory',
600
+ (directoryPath: string): Cypress.Chainable => {
601
+ return cy.task('createDirectory', directoryPath);
404
602
  }
405
603
  );
406
604
 
@@ -576,6 +774,19 @@ declare global {
576
774
  command,
577
775
  name
578
776
  }: ExecInContainerProps) => Cypress.Chainable;
777
+ getByLabel: ({
778
+ patternType,
779
+ tag,
780
+ label
781
+ }: GetByLabelProps) => Cypress.Chainable;
782
+ getByTestId: ({
783
+ patternType,
784
+ tag,
785
+ testId
786
+ }: GetByTestIdProps) => Cypress.Chainable;
787
+ getContainerId: (containerName: string) => Cypress.Chainable;
788
+ getContainerIpAddress: (containerName: string) => Cypress.Chainable;
789
+ getContainersLogs: () => Cypress.Chainable;
579
790
  getIframeBody: () => Cypress.Chainable;
580
791
  getTimeFromHeader: () => Cypress.Chainable;
581
792
  getWebVersion: () => Cypress.Chainable;
@@ -586,36 +797,51 @@ declare global {
586
797
  dashboard: Dashboard,
587
798
  patch: PatchDashboardBody
588
799
  ) => Cypress.Chainable;
589
-
800
+ loginAsAdminViaApiV2: () => Cypress.Chainable;
590
801
  loginByTypeOfUser: ({
591
802
  jsonName,
592
803
  loginViaApi
593
804
  }: LoginByTypeOfUserProps) => Cypress.Chainable;
805
+ logout: () => void;
806
+ logoutViaAPI: () => Cypress.Chainable;
594
807
  moveSortableElement: (direction: string) => Cypress.Chainable;
595
808
  navigateTo: ({
596
809
  page,
597
810
  rootItemNumber,
598
811
  subMenu
599
812
  }: NavigateToProps) => Cypress.Chainable;
813
+ requestOnDatabase: ({
814
+ database,
815
+ query
816
+ }: RequestOnDatabaseProps) => Cypress.Chainable;
817
+ setUserTokenApiV1: ({
818
+ login,
819
+ password
820
+ }?: SetUserTokenApiV1Props) => Cypress.Chainable;
600
821
  shareDashboardToUser: ({
601
822
  dashboardName,
602
823
  userName,
603
824
  role
604
825
  }: ShareDashboardToUserProps) => Cypress.Chainable;
605
826
  startContainer: ({
827
+ command,
606
828
  name,
607
- image
829
+ image,
830
+ portBindings
608
831
  }: StartContainerProps) => Cypress.Chainable;
609
- startWebContainer: ({
610
- name,
611
- os,
832
+ startContainers: ({
833
+ composeFile,
834
+ databaseImage,
835
+ moduleName,
836
+ openidImage,
837
+ profiles,
612
838
  useSlim,
613
- version
614
- }?: StartWebContainerProps) => Cypress.Chainable;
839
+ webOs,
840
+ webVersion
841
+ }?: StartContainersProps) => Cypress.Chainable;
615
842
  stopContainer: ({ name }: StopContainerProps) => Cypress.Chainable;
616
- stopWebContainer: ({ name }?: StopWebContainerProps) => Cypress.Chainable;
843
+ stopContainers: () => Cypress.Chainable;
617
844
  visitEmptyPage: () => Cypress.Chainable;
618
- waitForContainerAndSetToken: () => Cypress.Chainable;
619
845
  }
620
846
  }
621
847
  }
@@ -13,7 +13,6 @@ import tasks from './tasks';
13
13
 
14
14
  interface ConfigurationOptions {
15
15
  cypressFolder?: string;
16
- dockerName?: string;
17
16
  env?: Record<string, unknown>;
18
17
  envFile?: string;
19
18
  isDevelopment?: boolean;
@@ -24,7 +23,6 @@ export default ({
24
23
  specPattern,
25
24
  cypressFolder,
26
25
  isDevelopment,
27
- dockerName,
28
26
  env,
29
27
  envFile
30
28
  }: ConfigurationOptions): Cypress.ConfigOptions => {
@@ -41,6 +39,7 @@ export default ({
41
39
  return defineConfig({
42
40
  chromeWebSecurity: false,
43
41
  defaultCommandTimeout: 6000,
42
+ downloadsFolder: `${resultsFolder}/downloads`,
44
43
  e2e: {
45
44
  excludeSpecPattern: ['*.js', '*.ts', '*.md'],
46
45
  fixturesFolder: 'fixtures',
@@ -60,16 +59,17 @@ export default ({
60
59
  },
61
60
  env: {
62
61
  ...env,
62
+ DATABASE_IMAGE: 'bitnami/mariadb:10.5',
63
63
  OPENID_IMAGE_VERSION: process.env.MAJOR || '24.04',
64
+ SAML_IMAGE_VERSION: process.env.MAJOR || '24.04',
64
65
  WEB_IMAGE_OS: 'alma9',
65
- WEB_IMAGE_VERSION: webImageVersion,
66
- dockerName: dockerName || 'centreon-dev'
66
+ WEB_IMAGE_VERSION: webImageVersion
67
67
  },
68
68
  execTimeout: 60000,
69
69
  requestTimeout: 10000,
70
70
  retries: 0,
71
71
  screenshotsFolder: `${resultsFolder}/screenshots`,
72
- video: true,
72
+ video: isDevelopment,
73
73
  videoCompression: 0,
74
74
  videosFolder: `${resultsFolder}/videos`
75
75
  });
@@ -11,7 +11,7 @@ export default (
11
11
  const width = 1920;
12
12
  const height = 1080;
13
13
 
14
- if (browser.family === 'chromium') {
14
+ if (browser.family === 'chromium' && browser.name !== 'electron') {
15
15
  if (browser.isHeadless) {
16
16
  launchOptions.args.push('--headless=new');
17
17
  }
@@ -1,10 +1,40 @@
1
+ /* eslint-disable no-console */
1
2
  import { execSync } from 'child_process';
2
3
  import { existsSync, mkdirSync } from 'fs';
4
+ import path from 'path';
3
5
 
4
- import Docker from 'dockerode';
6
+ import tar from 'tar-fs';
7
+ import {
8
+ DockerComposeEnvironment,
9
+ GenericContainer,
10
+ StartedDockerComposeEnvironment,
11
+ StartedTestContainer,
12
+ Wait,
13
+ getContainerRuntimeClient
14
+ } from 'testcontainers';
15
+ import { createConnection } from 'mysql2/promise';
16
+
17
+ interface Containers {
18
+ [key: string]: StartedTestContainer;
19
+ }
5
20
 
6
21
  export default (on: Cypress.PluginEvents): void => {
7
- const docker = new Docker();
22
+ let dockerEnvironment: StartedDockerComposeEnvironment | null = null;
23
+ const containers: Containers = {};
24
+
25
+ const getContainer = (containerName): StartedTestContainer => {
26
+ let container;
27
+
28
+ if (dockerEnvironment !== null) {
29
+ container = dockerEnvironment.getContainer(`${containerName}-1`);
30
+ } else if (containers[containerName]) {
31
+ container = containers[containerName];
32
+ } else {
33
+ throw new Error(`Cannot get container ${containerName}`);
34
+ }
35
+
36
+ return container;
37
+ };
8
38
 
9
39
  interface PortBinding {
10
40
  destination: number;
@@ -12,6 +42,7 @@ export default (on: Cypress.PluginEvents): void => {
12
42
  }
13
43
 
14
44
  interface StartContainerProps {
45
+ command?: string;
15
46
  image: string;
16
47
  name: string;
17
48
  portBindings: Array<PortBinding>;
@@ -22,6 +53,48 @@ export default (on: Cypress.PluginEvents): void => {
22
53
  }
23
54
 
24
55
  on('task', {
56
+ copyFromContainer: async ({ destination, serviceName, source }) => {
57
+ try {
58
+ if (dockerEnvironment !== null) {
59
+ const container = dockerEnvironment.getContainer(`${serviceName}-1`);
60
+
61
+ await container
62
+ .copyArchiveFromContainer(source)
63
+ .then((archiveStream) => {
64
+ return new Promise<void>((resolve) => {
65
+ const dest = tar.extract(destination);
66
+ archiveStream.pipe(dest);
67
+ dest.on('finish', resolve);
68
+ });
69
+ });
70
+ }
71
+ } catch (error) {
72
+ console.error(error);
73
+ }
74
+
75
+ return null;
76
+ },
77
+ copyToContainer: async ({ destination, serviceName, source, type }) => {
78
+ const container = getContainer(serviceName);
79
+
80
+ if (type === 'directory') {
81
+ await container.copyDirectoriesToContainer([
82
+ {
83
+ source,
84
+ target: destination
85
+ }
86
+ ]);
87
+ } else if (type === 'file') {
88
+ await container.copyFilesToContainer([
89
+ {
90
+ source,
91
+ target: destination
92
+ }
93
+ ]);
94
+ }
95
+
96
+ return null;
97
+ },
25
98
  createDirectory: async (directoryPath: string) => {
26
99
  if (!existsSync(directoryPath)) {
27
100
  mkdirSync(directoryPath, { recursive: true });
@@ -29,70 +102,150 @@ export default (on: Cypress.PluginEvents): void => {
29
102
 
30
103
  return null;
31
104
  },
105
+ execInContainer: async ({ command, name }) => {
106
+ const { exitCode, output } = await getContainer(name).exec([
107
+ 'bash',
108
+ '-c',
109
+ command
110
+ ]);
111
+
112
+ return { exitCode, output };
113
+ },
114
+ getContainerId: (containerName: string) =>
115
+ getContainer(containerName).getId(),
116
+ getContainerIpAddress: (containerName: string) => {
117
+ const container = getContainer(containerName);
118
+
119
+ const networkNames = container.getNetworkNames();
120
+
121
+ return container.getIpAddress(networkNames[0]);
122
+ },
123
+ getContainersLogs: async () => {
124
+ try {
125
+ const { dockerode } = (await getContainerRuntimeClient()).container;
126
+ const loggedContainers = await dockerode.listContainers();
127
+
128
+ return loggedContainers.reduce((acc, container) => {
129
+ const containerName = container.Names[0].replace('/', '');
130
+ acc[containerName] = execSync(`docker logs -t ${container.Id}`, {
131
+ stdio: 'pipe'
132
+ }).toString('utf8');
133
+
134
+ return acc;
135
+ }, {});
136
+ } catch (error) {
137
+ console.warn('Cannot get containers logs');
138
+ console.warn(error);
139
+
140
+ return null;
141
+ }
142
+ },
143
+ requestOnDatabase: async ({ database, query }) => {
144
+ let container: StartedTestContainer | null = null;
145
+
146
+ if (dockerEnvironment !== null) {
147
+ container = dockerEnvironment.getContainer('db-1');
148
+ } else {
149
+ container = getContainer('web');
150
+ }
151
+
152
+ const client = await createConnection({
153
+ database,
154
+ host: container.getHost(),
155
+ password: 'centreon',
156
+ port: container.getMappedPort(3306),
157
+ user: 'centreon'
158
+ });
159
+
160
+ const [rows, fields] = await client.execute(query);
161
+
162
+ await client.end();
163
+
164
+ return [rows, fields];
165
+ },
32
166
  startContainer: async ({
167
+ command,
33
168
  image,
34
169
  name,
35
170
  portBindings = []
36
171
  }: StartContainerProps) => {
37
- const imageList = execSync(
38
- 'docker image list --format "{{.Repository}}:{{.Tag}}"'
39
- ).toString('utf8');
40
-
41
- if (
42
- !imageList.match(
43
- new RegExp(
44
- `^${image.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}`,
45
- 'm'
46
- )
47
- )
48
- ) {
49
- execSync(`docker pull ${image}`);
50
- }
172
+ let container = await new GenericContainer(image).withName(name);
51
173
 
52
- const webContainers = await docker.listContainers({
53
- all: true,
54
- filters: { name: [name] }
174
+ portBindings.forEach(({ source, destination }) => {
175
+ container = container.withExposedPorts({
176
+ container: source,
177
+ host: destination
178
+ });
55
179
  });
56
- if (webContainers.length) {
57
- return webContainers[0];
58
- }
59
180
 
60
- const container = await docker.createContainer({
61
- AttachStderr: true,
62
- AttachStdin: false,
63
- AttachStdout: true,
64
- ExposedPorts: portBindings.reduce((accumulator, currentValue) => {
65
- accumulator[`${currentValue.source}/tcp`] = {};
66
-
67
- return accumulator;
68
- }, {}),
69
- HostConfig: {
70
- PortBindings: portBindings.reduce((accumulator, currentValue) => {
71
- accumulator[`${currentValue.source}/tcp`] = [
72
- {
73
- HostIP: '127.0.0.1',
74
- HostPort: `${currentValue.destination}`
75
- }
76
- ];
77
-
78
- return accumulator;
79
- }, {})
80
- },
81
- Image: image,
82
- OpenStdin: false,
83
- StdinOnce: false,
84
- Tty: true,
85
- name
86
- });
181
+ if (command) {
182
+ container
183
+ .withCommand(['bash', '-c', command])
184
+ .withWaitStrategy(Wait.forSuccessfulCommand('ls'));
185
+ }
87
186
 
88
- await container.start();
187
+ containers[name] = await container.start();
89
188
 
90
189
  return container;
91
190
  },
191
+ startContainers: async ({
192
+ composeFile,
193
+ databaseImage,
194
+ openidImage,
195
+ profiles,
196
+ samlImage,
197
+ webImage
198
+ }) => {
199
+ try {
200
+ const composeFileDir = path.dirname(composeFile);
201
+ const composeFileName = path.basename(composeFile);
202
+
203
+ dockerEnvironment = await new DockerComposeEnvironment(
204
+ composeFileDir,
205
+ composeFileName
206
+ )
207
+ .withEnvironment({
208
+ MYSQL_IMAGE: databaseImage,
209
+ OPENID_IMAGE: openidImage,
210
+ SAML_IMAGE: samlImage,
211
+ WEB_IMAGE: webImage
212
+ })
213
+ .withProfiles(...profiles)
214
+ .withWaitStrategy(
215
+ 'web-1',
216
+ Wait.forAll([
217
+ Wait.forHealthCheck(),
218
+ Wait.forLogMessage('Centreon is ready')
219
+ ])
220
+ )
221
+ .up();
222
+
223
+ return null;
224
+ } catch (error) {
225
+ if (error instanceof Error) {
226
+ console.error(error.message);
227
+ }
228
+
229
+ throw error;
230
+ }
231
+ },
92
232
  stopContainer: async ({ name }: StopContainerProps) => {
93
- const container = await docker.getContainer(name);
94
- await container.kill();
95
- await container.remove();
233
+ if (containers[name]) {
234
+ const container = containers[name];
235
+
236
+ await container.stop();
237
+
238
+ delete containers[name];
239
+ }
240
+
241
+ return null;
242
+ },
243
+ stopContainers: async () => {
244
+ if (dockerEnvironment !== null) {
245
+ await dockerEnvironment.down();
246
+
247
+ dockerEnvironment = null;
248
+ }
96
249
 
97
250
  return null;
98
251
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@centreon/js-config",
3
3
  "description": "Centreon Frontend shared build configuration",
4
- "version": "24.4.20",
4
+ "version": "24.4.22",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/centreon/centreon-frontend.git"
@@ -24,23 +24,23 @@
24
24
  "cypress"
25
25
  ],
26
26
  "peerDependencies": {
27
- "prettier": "^3.0.0",
28
- "eslint": "^8.53.0"
27
+ "eslint": "^8.53.0",
28
+ "prettier": "^3.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@badeball/cypress-cucumber-preprocessor": "^19.1.0",
31
+ "@badeball/cypress-cucumber-preprocessor": "^20.0.1",
32
32
  "@bahmutov/cypress-esbuild-preprocessor": "^2.2.0",
33
33
  "@esbuild-plugins/node-globals-polyfill": "^0.2.3",
34
34
  "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
35
35
  "@tsconfig/node16": "^16.1.1",
36
36
  "@tsconfig/node20": "^20.1.2",
37
37
  "@types/cypress-cucumber-preprocessor": "^4.0.5",
38
- "@types/dockerode": "^3.3.23",
38
+ "cypress": "^13.6.4",
39
39
  "cypress-multi-reporters": "^1.6.4",
40
- "cypress-terminal-report": "^5.3.9",
41
- "dockerode": "^4.0.0",
42
- "dotenv": "^16.3.1",
43
- "esbuild": "^0.19.5",
40
+ "cypress-terminal-report": "^6.0.0",
41
+ "cypress-wait-until": "^3.0.1",
42
+ "dotenv": "^16.4.1",
43
+ "esbuild": "^0.20.0",
44
44
  "eslint": "^8.53.0",
45
45
  "eslint-config-airbnb": "19.0.4",
46
46
  "eslint-config-prettier": "^8.5.0",
@@ -58,6 +58,9 @@
58
58
  "eslint-plugin-react-hooks": "^4.5.0",
59
59
  "eslint-plugin-sort-keys-fix": "^1.1.2",
60
60
  "eslint-plugin-typescript-sort-keys": "^2.1.0",
61
- "mochawesome": "^7.1.3"
61
+ "mochawesome": "^7.1.3",
62
+ "mysql2": "^3.9.1",
63
+ "tar-fs": "^3.0.4",
64
+ "testcontainers": "^10.7.1"
62
65
  }
63
66
  }