@centreon/js-config 24.11.8 → 24.11.10

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.
Files changed (33) hide show
  1. package/biome/base.json +210 -0
  2. package/cypress/component/commands.tsx +82 -22
  3. package/cypress/component/configuration.js +39 -16
  4. package/cypress/component/disableCssTransitions.ts +19 -0
  5. package/cypress/component/enableVisualTesting.ts +1 -1
  6. package/cypress/component/excludeNodeModulesFromCoverage.js +36 -0
  7. package/cypress/e2e/commands/configuration.ts +330 -1
  8. package/cypress/e2e/commands/monitoring.ts +225 -0
  9. package/cypress/e2e/commands.ts +727 -174
  10. package/cypress/e2e/configuration.ts +46 -40
  11. package/cypress/e2e/esbuild-preprocessor.ts +26 -0
  12. package/cypress/e2e/plugins.ts +52 -114
  13. package/cypress/e2e/reporter-config.js +13 -0
  14. package/cypress/e2e/tasks.ts +259 -0
  15. package/eslint/base.typescript.eslintrc.js +15 -3
  16. package/eslint/lambda/typescript.eslintrc.js +48 -0
  17. package/jest/index.js +5 -2
  18. package/jest/lambda/typescript.js +49 -0
  19. package/package.json +57 -45
  20. package/rspack/base/globalConfig.js +71 -0
  21. package/rspack/base/index.js +89 -0
  22. package/rspack/patch/dev.js +12 -0
  23. package/{webpack → rspack}/patch/devServer.js +4 -8
  24. package/rspack/patch/module.js +13 -0
  25. package/rspack/plugins/TransformPreloadScript.js +37 -0
  26. package/rspack/plugins/WriteRemoteEntryNameToModuleFederation.js +30 -0
  27. package/tsconfig/index.json +5 -4
  28. package/tsconfig/lambda/node20.tsconfig.json +12 -0
  29. package/tsconfig/lambda/tsconfig.json +14 -0
  30. package/tsconfig.json +21 -0
  31. package/webpack/base/index.js +0 -130
  32. package/webpack/patch/dev.js +0 -24
  33. package/webpack/patch/module.js +0 -46
@@ -1,11 +1,64 @@
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';
6
+ import './commands/monitoring';
4
7
 
8
+ import installLogsCollector from 'cypress-terminal-report/src/installLogsCollector';
9
+
10
+ installLogsCollector({
11
+ commandTimings: 'seconds',
12
+ enableExtendedCollector: true
13
+ });
14
+
15
+ const apiBase = '/centreon/api';
16
+ const apiActionV1 = `${apiBase}/index.php`;
5
17
  const apiLoginV2 = '/centreon/authentication/providers/configurations/local';
6
18
 
7
19
  const artifactIllegalCharactersMatcher = /[,\s/|<>*?:"]/g;
8
20
 
21
+ export enum PatternType {
22
+ contains = '*',
23
+ endsWith = '$',
24
+ equals = '',
25
+ startsWith = '^'
26
+ }
27
+
28
+ interface GetByLabelProps {
29
+ label: string;
30
+ patternType?: PatternType;
31
+ tag?: string;
32
+ }
33
+
34
+ Cypress.Commands.add(
35
+ 'getByLabel',
36
+ ({
37
+ tag = '',
38
+ patternType = PatternType.equals,
39
+ label
40
+ }: GetByLabelProps): Cypress.Chainable => {
41
+ return cy.get(`${tag}[aria-label${patternType}="${label}"]`);
42
+ }
43
+ );
44
+
45
+ interface GetByTestIdProps {
46
+ patternType?: PatternType;
47
+ tag?: string;
48
+ testId: string;
49
+ }
50
+
51
+ Cypress.Commands.add(
52
+ 'getByTestId',
53
+ ({
54
+ tag = '',
55
+ patternType = PatternType.equals,
56
+ testId
57
+ }: GetByTestIdProps): Cypress.Chainable => {
58
+ return cy.get(`${tag}[data-testid${patternType}="${testId}"]`);
59
+ }
60
+ );
61
+
9
62
  Cypress.Commands.add('getWebVersion', (): Cypress.Chainable => {
10
63
  return cy
11
64
  .exec(
@@ -23,7 +76,7 @@ Cypress.Commands.add('getWebVersion', (): Cypress.Chainable => {
23
76
 
24
77
  Cypress.Commands.add('getIframeBody', (): Cypress.Chainable => {
25
78
  return cy
26
- .get('iframe#main-content')
79
+ .get('iframe#main-content', { timeout: 10000 })
27
80
  .its('0.contentDocument.body')
28
81
  .should('not.be.empty')
29
82
  .then(cy.wrap);
@@ -39,6 +92,13 @@ Cypress.Commands.add(
39
92
  }
40
93
  );
41
94
 
95
+ Cypress.Commands.add(
96
+ 'clickSubRootMenuItem',
97
+ (page: string): Cypress.Chainable => {
98
+ return cy.get('div[data-cy="collapse"]').eq(1).contains(page).click();
99
+ }
100
+ );
101
+
42
102
  interface NavigateToProps {
43
103
  page: string;
44
104
  rootItemNumber: number;
@@ -47,12 +107,17 @@ interface NavigateToProps {
47
107
 
48
108
  Cypress.Commands.add(
49
109
  'navigateTo',
50
- ({ rootItemNumber, subMenu, page }): void => {
110
+ ({ rootItemNumber, subMenu, page }: NavigateToProps): void => {
51
111
  if (subMenu) {
52
112
  cy.hoverRootMenuItem(rootItemNumber)
53
113
  .contains(subMenu)
54
- .trigger('mouseover', { force: true });
55
- cy.contains(page).click({ force: true });
114
+ .trigger('mouseover')
115
+ .get('.MuiCollapse-wrapper')
116
+ .find('div[data-cy="collapse"]')
117
+ .should('be.visible')
118
+ .and('contain', page);
119
+
120
+ cy.clickSubRootMenuItem(page);
56
121
 
57
122
  return;
58
123
  }
@@ -83,34 +148,80 @@ Cypress.Commands.add(
83
148
  }
84
149
  );
85
150
 
151
+ Cypress.Commands.add('getContainerId', (containerName: string) => {
152
+ cy.log(`Getting container id of ${containerName}`);
153
+
154
+ return cy.task('getContainerId', containerName);
155
+ });
156
+
157
+ Cypress.Commands.add('getContainerIpAddress', (containerName: string) => {
158
+ cy.log(`Getting container ip address of ${containerName}`);
159
+
160
+ return cy.task('getContainerIpAddress', containerName);
161
+ });
162
+
163
+ Cypress.Commands.add('getContainersLogs', () => {
164
+ cy.log('Getting containers logs');
165
+
166
+ return cy.task('getContainersLogs');
167
+ });
168
+
86
169
  interface CopyFromContainerProps {
87
170
  destination: string;
171
+ name?: string;
88
172
  source: string;
89
173
  }
90
174
 
91
175
  Cypress.Commands.add(
92
176
  'copyFromContainer',
93
- ({ source, destination }: CopyFromContainerProps) => {
94
- return cy.exec(
95
- `docker cp ${Cypress.env('dockerName')}:${source} "${destination}"`
96
- );
177
+ ({ name = 'web', source, destination }: CopyFromContainerProps) => {
178
+ cy.log(`Copy content from ${name}:${source} to ${destination}`);
179
+
180
+ return cy.task('copyFromContainer', {
181
+ destination,
182
+ serviceName: name,
183
+ source
184
+ });
97
185
  }
98
186
  );
99
187
 
188
+ export enum CopyToContainerContentType {
189
+ Directory = 'directory',
190
+ File = 'file'
191
+ }
192
+
100
193
  interface CopyToContainerProps {
101
194
  destination: string;
195
+ name?: string;
102
196
  source: string;
197
+ type: CopyToContainerContentType;
103
198
  }
104
199
 
105
200
  Cypress.Commands.add(
106
201
  'copyToContainer',
107
- ({ source, destination }: CopyToContainerProps) => {
108
- return cy.exec(
109
- `docker cp ${source} ${Cypress.env('dockerName')}:${destination}`
110
- );
202
+ ({ name = 'web', source, destination, type }: CopyToContainerProps) => {
203
+ cy.log(`Copy content from ${source} to ${name}:${destination}`);
204
+
205
+ return cy.task('copyToContainer', {
206
+ destination,
207
+ serviceName: name,
208
+ source,
209
+ type
210
+ });
111
211
  }
112
212
  );
113
213
 
214
+ Cypress.Commands.add('loginAsAdminViaApiV2', (): Cypress.Chainable => {
215
+ return cy.request({
216
+ body: {
217
+ login: 'admin',
218
+ password: 'Centreon!2021'
219
+ },
220
+ method: 'POST',
221
+ url: apiLoginV2
222
+ });
223
+ });
224
+
114
225
  interface LoginByTypeOfUserProps {
115
226
  jsonName?: string;
116
227
  loginViaApi?: boolean;
@@ -118,7 +229,7 @@ interface LoginByTypeOfUserProps {
118
229
 
119
230
  Cypress.Commands.add(
120
231
  'loginByTypeOfUser',
121
- ({ jsonName, loginViaApi }): Cypress.Chainable => {
232
+ ({ jsonName = 'admin', loginViaApi = false }): Cypress.Chainable => {
122
233
  if (loginViaApi) {
123
234
  return cy
124
235
  .fixture(`users/${jsonName}.json`)
@@ -135,27 +246,56 @@ Cypress.Commands.add(
135
246
  .visit(`${Cypress.config().baseUrl}`)
136
247
  .wait('@getNavigationList');
137
248
  }
249
+
138
250
  cy.visit(`${Cypress.config().baseUrl}`)
139
251
  .fixture(`users/${jsonName}.json`)
140
252
  .then((credential) => {
141
- cy.getByLabel({ label: 'Alias', tag: 'input' }).type(credential.login);
253
+ cy.getByLabel({ label: 'Alias', tag: 'input' }).type(
254
+ `{selectAll}{backspace}${credential.login}`
255
+ );
142
256
  cy.getByLabel({ label: 'Password', tag: 'input' }).type(
143
- credential.password
257
+ `{selectAll}{backspace}${credential.password}`
144
258
  );
145
259
  })
146
260
  .getByLabel({ label: 'Connect', tag: 'button' })
147
261
  .click();
148
262
 
149
- return cy
150
- .get('.SnackbarContent-root > .MuiPaper-root')
151
- .then(($snackbar) => {
152
- if ($snackbar.text().includes('Login succeeded')) {
153
- cy.wait('@getNavigationList');
154
- }
155
- });
263
+ return cy.get('.MuiAlert-message').then(($snackbar) => {
264
+ if ($snackbar.text().includes('Login succeeded')) {
265
+ cy.wait('@getNavigationList');
266
+ cy.get('.MuiAlert-message').should('not.be.visible');
267
+ }
268
+ });
156
269
  }
157
270
  );
158
271
 
272
+ Cypress.Commands.add('logout', (): void => {
273
+ cy.getByLabel({ label: 'Profile' }).should('exist').click();
274
+
275
+ cy.intercept({
276
+ method: 'GET',
277
+ times: 1,
278
+ url: '/centreon/api/latest/authentication/logout'
279
+ }).as('logout');
280
+
281
+ cy.contains(/^Logout$/).click();
282
+
283
+ cy.wait('@logout').its('response.statusCode').should('eq', 302);
284
+
285
+ // https://github.com/cypress-io/cypress/issues/25841
286
+ cy.clearAllCookies();
287
+ });
288
+
289
+ Cypress.Commands.add('logoutViaAPI', (): Cypress.Chainable => {
290
+ return cy
291
+ .request({
292
+ method: 'GET',
293
+ url: '/centreon/authentication/logout'
294
+ })
295
+ .visit('/')
296
+ .getByLabel({ label: 'Alias', tag: 'input' });
297
+ });
298
+
159
299
  Cypress.Commands.add(
160
300
  'visitEmptyPage',
161
301
  (): Cypress.Chainable =>
@@ -167,32 +307,98 @@ Cypress.Commands.add(
167
307
  .visit('/waiting-page')
168
308
  );
169
309
 
170
- Cypress.Commands.add('waitForContainerAndSetToken', (): Cypress.Chainable => {
171
- return cy.setUserTokenApiV1();
172
- });
173
-
174
310
  interface ExecInContainerProps {
175
- command: string;
311
+ command: string | Array<string>;
176
312
  name: string;
177
313
  }
178
314
 
315
+ interface ExecInContainerOptions {
316
+ log: boolean;
317
+ }
318
+
319
+ interface ExecInContainerResult {
320
+ exitCode: number;
321
+ output: string;
322
+ }
323
+
179
324
  Cypress.Commands.add(
180
325
  'execInContainer',
181
- ({ command, name }: ExecInContainerProps): Cypress.Chainable => {
326
+ ({ command, name }, { log = true } = { log: true }): Cypress.Chainable => {
327
+ const commands =
328
+ typeof command === 'string' || command instanceof String
329
+ ? [command]
330
+ : command;
331
+
332
+ const results = commands.reduce(
333
+ (acc, runCommand) => {
334
+ cy.task<ExecInContainerResult>(
335
+ 'execInContainer',
336
+ { command: runCommand, name },
337
+ { log, timeout: 600000 }
338
+ ).then((result) => {
339
+ const displayedOutput = log ? result.output : 'hidden command output';
340
+ const displayedRunCommand = log ? runCommand : 'hidden run command';
341
+
342
+ if (result.exitCode) {
343
+ cy.log(displayedOutput);
344
+
345
+ // output will not be truncated
346
+ throw new Error(`
347
+ Execution of "${displayedRunCommand}" failed
348
+ Exit code: ${result.exitCode}
349
+ Output:\n${displayedOutput}`);
350
+ }
351
+
352
+ acc.output = `${acc.output}${displayedOutput}`;
353
+ });
354
+
355
+ return acc;
356
+ },
357
+ { exitCode: 0, output: '' }
358
+ );
359
+
360
+ return cy.wrap(results);
361
+ }
362
+ );
363
+
364
+ interface RequestOnDatabaseProps {
365
+ database: string;
366
+ query: string;
367
+ }
368
+
369
+ Cypress.Commands.add(
370
+ 'requestOnDatabase',
371
+ ({ database, query }: RequestOnDatabaseProps): Cypress.Chainable => {
372
+ return cy.task('requestOnDatabase', { database, query });
373
+ }
374
+ );
375
+
376
+ interface SetUserTokenApiV1Props {
377
+ login?: string;
378
+ password?: string;
379
+ }
380
+
381
+ Cypress.Commands.add(
382
+ 'setUserTokenApiV1',
383
+ ({
384
+ login = 'admin',
385
+ password = 'Centreon!2021'
386
+ }: SetUserTokenApiV1Props = {}): Cypress.Chainable => {
182
387
  return cy
183
- .exec(`docker exec -i ${name} ${command}`, { failOnNonZeroExit: false })
184
- .then((result) => {
185
- if (result.code) {
186
- // output will not be truncated
187
- throw new Error(`
188
- Execution of "${command}" failed
189
- Exit code: ${result.code}
190
- Stdout:\n${result.stdout}
191
- Stderr:\n${result.stderr}`);
192
- }
193
-
194
- return cy.wrap(result);
195
- });
388
+ .request({
389
+ body: {
390
+ password,
391
+ username: login
392
+ },
393
+ headers: {
394
+ 'Content-Type': 'application/x-www-form-urlencoded'
395
+ },
396
+ method: 'POST',
397
+ url: `${apiActionV1}?action=authenticate`
398
+ })
399
+ .then(({ body }) =>
400
+ window.localStorage.setItem('userTokenApiV1', body.authToken)
401
+ );
196
402
  }
197
403
  );
198
404
 
@@ -202,6 +408,7 @@ interface PortBinding {
202
408
  }
203
409
 
204
410
  interface StartContainerProps {
411
+ command?: string;
205
412
  image: string;
206
413
  name: string;
207
414
  portBindings: Array<PortBinding>;
@@ -209,206 +416,552 @@ interface StartContainerProps {
209
416
 
210
417
  Cypress.Commands.add(
211
418
  'startContainer',
212
- ({ name, image, portBindings }: StartContainerProps): Cypress.Chainable => {
213
- return cy
214
- .exec('docker image list --format "{{.Repository}}:{{.Tag}}"')
215
- .then(({ stdout }) => {
216
- if (
217
- stdout.match(
218
- new RegExp(
219
- `^${image.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}`,
220
- 'm'
221
- )
222
- )
223
- ) {
224
- cy.log(`Local docker image found : ${image}`);
225
-
226
- return cy.wrap(image);
227
- }
228
-
229
- cy.log(`Pulling remote docker image : ${image}`);
230
-
231
- return cy.exec(`docker pull ${image}`).then(() => cy.wrap(image));
232
- })
233
- .then((imageName) =>
234
- cy.task('startContainer', { image: imageName, name, portBindings })
235
- );
419
+ ({
420
+ command,
421
+ name,
422
+ image,
423
+ portBindings
424
+ }: StartContainerProps): Cypress.Chainable => {
425
+ cy.log(`Starting container ${name} from image ${image}`);
426
+
427
+ return cy.task(
428
+ 'startContainer',
429
+ { command, image, name, portBindings },
430
+ { timeout: 600000 } // 10 minutes because docker pull can be very slow
431
+ );
236
432
  }
237
433
  );
238
434
 
239
- interface StartWebContainerProps {
240
- name?: string;
241
- os?: string;
435
+ interface StartContainersProps {
436
+ composeFile?: string;
437
+ databaseImage?: string;
438
+ moduleName?: string;
439
+ openidImage?: string;
440
+ profiles?: Array<string>;
441
+ samlImage?: string;
242
442
  useSlim?: boolean;
243
- version?: string;
443
+ webOs?: string;
444
+ webVersion?: string;
244
445
  }
245
446
 
246
447
  Cypress.Commands.add(
247
- 'startWebContainer',
448
+ 'startContainers',
248
449
  ({
249
- name = Cypress.env('dockerName'),
250
- os = 'alma9',
450
+ composeFile,
451
+ databaseImage = Cypress.env('DATABASE_IMAGE'),
452
+ moduleName = 'centreon-web',
453
+ openidImage = `docker.centreon.com/centreon/keycloak:${Cypress.env(
454
+ 'OPENID_IMAGE_VERSION'
455
+ )}`,
456
+ profiles = [],
457
+ samlImage = `docker.centreon.com/centreon/keycloak:${Cypress.env(
458
+ 'SAML_IMAGE_VERSION'
459
+ )}`,
251
460
  useSlim = true,
252
- version = Cypress.env('WEB_IMAGE_VERSION')
253
- }: StartWebContainerProps = {}): Cypress.Chainable => {
461
+ webOs = Cypress.env('WEB_IMAGE_OS'),
462
+ webVersion = Cypress.env('WEB_IMAGE_VERSION')
463
+ }: StartContainersProps = {}): Cypress.Chainable => {
464
+ cy.log('Starting containers ...');
465
+
466
+ let composeFilePath = composeFile;
467
+ if (!composeFile) {
468
+ const cypressDir = path.dirname(Cypress.config('configFile'));
469
+ composeFilePath = `${cypressDir}/../../../.github/docker/docker-compose.yml`;
470
+ }
471
+
254
472
  const slimSuffix = useSlim ? '-slim' : '';
255
473
 
256
- const image = `docker.centreon.com/centreon/centreon-web${slimSuffix}-${os}:${version}`;
474
+ const webImage = `docker.centreon.com/centreon/${moduleName}${slimSuffix}-${webOs}:${webVersion}`;
257
475
 
258
476
  return cy
259
- .startContainer({
260
- image,
261
- name,
262
- portBindings: [{ destination: 4000, source: 80 }]
263
- })
477
+ .task(
478
+ 'startContainers',
479
+ {
480
+ composeFile: composeFilePath,
481
+ databaseImage,
482
+ openidImage,
483
+ profiles,
484
+ samlImage,
485
+ webImage
486
+ },
487
+ { timeout: 600000 } // 10 minutes because docker pull can be very slow
488
+ )
264
489
  .then(() => {
265
- const baseUrl = 'http://0.0.0.0:4000';
490
+ const baseUrl = 'http://127.0.0.1:4000';
266
491
 
267
492
  Cypress.config('baseUrl', baseUrl);
268
493
 
269
- return cy.exec(
270
- `npx wait-on ${baseUrl}/centreon/api/latest/platform/installation/status`
271
- );
494
+ return cy.wrap(null);
272
495
  })
273
496
  .visit('/') // this is necessary to refresh browser cause baseUrl has changed (flash appears in video)
274
497
  .setUserTokenApiV1();
275
498
  }
276
499
  );
277
500
 
278
- interface StopWebContainerProps {
279
- name?: string;
501
+ interface StopContainerProps {
502
+ name: string;
280
503
  }
281
504
 
282
505
  Cypress.Commands.add(
283
- 'stopWebContainer',
284
- ({
285
- name = Cypress.env('dockerName')
286
- }: StopWebContainerProps = {}): Cypress.Chainable => {
287
- const logDirectory = `cypress/results/logs/${Cypress.spec.name.replace(
288
- artifactIllegalCharactersMatcher,
289
- '_'
290
- )}/${Cypress.currentTest.title.replace(
291
- artifactIllegalCharactersMatcher,
292
- '_'
293
- )}`;
506
+ 'stopContainer',
507
+ ({ name }: StopContainerProps): Cypress.Chainable => {
508
+ cy.log(`Stopping container ${name}`);
294
509
 
295
- return cy
296
- .visitEmptyPage()
297
- .exec(`mkdir -p "${logDirectory}"`)
298
- .copyFromContainer({
299
- destination: `${logDirectory}/broker`,
300
- source: '/var/log/centreon-broker'
301
- })
302
- .copyFromContainer({
303
- destination: `${logDirectory}/engine`,
304
- source: '/var/log/centreon-engine'
305
- })
306
- .execInContainer({
307
- command: `bash -e <<EOF
308
- chmod 777 /var/log/centreon/centreon-web.log > /dev/null 2>&1 || :
309
- EOF`,
310
- name
311
- })
312
- .copyFromContainer({
313
- destination: `${logDirectory}/centreon`,
314
- source: '/var/log/centreon'
315
- })
316
- .stopContainer({ name });
510
+ return cy.task('stopContainer', { name });
317
511
  }
318
512
  );
319
513
 
320
- interface StopContainerProps {
514
+ Cypress.Commands.add('stopContainers', (): Cypress.Chainable => {
515
+ cy.log('Stopping containers ...');
516
+
517
+ const logDirectory = `results/logs/${Cypress.spec.name.replace(
518
+ artifactIllegalCharactersMatcher,
519
+ '_'
520
+ )}/${Cypress.currentTest.title.replace(
521
+ artifactIllegalCharactersMatcher,
522
+ '_'
523
+ )}`;
524
+
525
+ const name = 'web';
526
+
527
+ return cy
528
+ .visitEmptyPage()
529
+ .createDirectory(logDirectory)
530
+ .getContainersLogs()
531
+ .then((containersLogs: Array<Array<string>>) => {
532
+ if (!containersLogs) {
533
+ return;
534
+ }
535
+
536
+ Object.entries(containersLogs).forEach(([containerName, logs]) => {
537
+ cy.writeFile(
538
+ `results/logs/${Cypress.spec.name.replace(
539
+ artifactIllegalCharactersMatcher,
540
+ '_'
541
+ )}/${Cypress.currentTest.title.replace(
542
+ artifactIllegalCharactersMatcher,
543
+ '_'
544
+ )}/container-${containerName}.log`,
545
+ logs
546
+ );
547
+ });
548
+ })
549
+ .copyFromContainer({
550
+ destination: `${logDirectory}/broker`,
551
+ name,
552
+ source: '/var/log/centreon-broker'
553
+ })
554
+ .copyFromContainer({
555
+ destination: `${logDirectory}/engine`,
556
+ name,
557
+ source: '/var/log/centreon-engine'
558
+ })
559
+ .copyFromContainer({
560
+ destination: `${logDirectory}/centreon`,
561
+ name,
562
+ source: '/var/log/centreon'
563
+ })
564
+ .copyFromContainer({
565
+ destination: `${logDirectory}/centreon-gorgone`,
566
+ name,
567
+ source: '/var/log/centreon-gorgone'
568
+ })
569
+ .then(() => {
570
+ if (Cypress.env('WEB_IMAGE_OS').includes('alma')) {
571
+ return cy.copyFromContainer({
572
+ destination: `${logDirectory}/php`,
573
+ name,
574
+ source: '/var/log/php-fpm'
575
+ });
576
+ }
577
+
578
+ return cy.copyFromContainer(
579
+ {
580
+ destination: `${logDirectory}/php8.1-fpm-centreon-error.log`,
581
+ name,
582
+ source: '/var/log/php8.1-fpm-centreon-error.log'
583
+ },
584
+ { failOnNonZeroExit: false }
585
+ );
586
+ })
587
+ .then(() => {
588
+ if (Cypress.env('WEB_IMAGE_OS').includes('alma')) {
589
+ return cy.copyFromContainer({
590
+ destination: `${logDirectory}/httpd`,
591
+ name,
592
+ source: '/var/log/httpd'
593
+ });
594
+ }
595
+
596
+ return cy.copyFromContainer(
597
+ {
598
+ destination: `${logDirectory}/apache2`,
599
+ name,
600
+ source: '/var/log/apache2'
601
+ },
602
+ { failOnNonZeroExit: false }
603
+ );
604
+ })
605
+ .exec(`chmod -R 755 "${logDirectory}"`)
606
+ .task(
607
+ 'stopContainers',
608
+ {},
609
+ { timeout: 600000 } // 10 minutes because docker pull can be very slow
610
+ );
611
+ });
612
+
613
+ Cypress.Commands.add(
614
+ 'createDirectory',
615
+ (directoryPath: string): Cypress.Chainable => {
616
+ return cy.task('createDirectory', directoryPath);
617
+ }
618
+ );
619
+
620
+ interface Dashboard {
621
+ description?: string;
321
622
  name: string;
322
623
  }
323
624
 
324
625
  Cypress.Commands.add(
325
- 'stopContainer',
326
- ({ name }: StopContainerProps): Cypress.Chainable => {
327
- cy.exec(`docker logs ${name}`).then(({ stdout }) => {
328
- cy.writeFile(
329
- `cypress/results/logs/${Cypress.spec.name.replace(
330
- artifactIllegalCharactersMatcher,
331
- '_'
332
- )}/${Cypress.currentTest.title.replace(
333
- artifactIllegalCharactersMatcher,
334
- '_'
335
- )}/container-${name}.log`,
336
- stdout
626
+ 'insertDashboardList',
627
+ (fixtureFile: string): Cypress.Chainable => {
628
+ return cy.fixture(fixtureFile).then((dashboardList) => {
629
+ cy.wrap(
630
+ Promise.all(
631
+ dashboardList.map((dashboardBody: Dashboard) =>
632
+ cy.insertDashboard({ ...dashboardBody })
633
+ )
634
+ )
337
635
  );
338
636
  });
339
-
340
- return cy.task('stopContainer', { name });
341
637
  }
342
638
  );
343
639
 
344
640
  Cypress.Commands.add(
345
- 'waitForElementInIframe',
346
- (iframeSelector, elementSelector) => {
641
+ 'insertDashboard',
642
+ (dashboardBody: Dashboard): Cypress.Chainable => {
643
+ return cy.request({
644
+ body: {
645
+ ...dashboardBody
646
+ },
647
+ method: 'POST',
648
+ url: '/centreon/api/latest/configuration/dashboards'
649
+ });
650
+ }
651
+ );
652
+
653
+ Cypress.Commands.add('insertDashboardWithWidget', (dashboardBody, patchBody, widgetName, widgetType) => {
654
+ cy.request({
655
+ body: { ...dashboardBody },
656
+ method: 'POST',
657
+ url: '/centreon/api/latest/configuration/dashboards'
658
+ }).then((response) => {
659
+ const dashboardId = response.body.id;
660
+
347
661
  cy.waitUntil(
348
- () =>
349
- cy.get(iframeSelector).then(($iframe) => {
350
- const iframeBody = $iframe[0].contentDocument.body;
351
- if (iframeBody) {
352
- const $element = Cypress.$(iframeBody).find(elementSelector);
662
+ () => {
663
+ return cy.request({
664
+ method: 'GET',
665
+ url: `/centreon/api/latest/configuration/dashboards/${dashboardId}`
666
+ }).then((getResponse) => {
667
+ return getResponse.body && getResponse.body.id === dashboardId;
668
+ });
669
+ },
670
+ { timeout: 10000 }
671
+ );
353
672
 
354
- return $element.length > 0 && $element.is(':visible');
355
- }
673
+ const formData = new FormData();
674
+
675
+ formData.append('panels[0][name]', widgetName);
676
+ formData.append('panels[0][widget_type]', widgetType);
677
+
678
+ formData.append('panels[0][layout][x]', '0');
679
+ formData.append('panels[0][layout][y]', '0');
680
+ formData.append('panels[0][layout][width]', '12');
681
+ formData.append('panels[0][layout][height]', '3');
682
+ formData.append('panels[0][layout][min_width]', '2');
683
+ formData.append('panels[0][layout][min_height]', '2');
684
+
685
+ formData.append('panels[0][widget_settings]', JSON.stringify(patchBody));
686
+
687
+ cy.request({
688
+ method: 'POST',
689
+ url: `/centreon/api/latest/configuration/dashboards/${dashboardId}`,
690
+ body: formData,
691
+ headers: {
692
+ 'Content-Type': 'multipart/form-data',
693
+ },
694
+ }).then((patchResponse) => {
695
+ console.log('Widget added successfully:', patchResponse);
696
+ });
697
+ });
698
+ });
356
699
 
357
- return false;
358
- }),
359
- {
360
- errorMsg: 'The element is not visible within the iframe',
361
- interval: 5000,
362
- timeout: 100000
363
- }
364
- ).then((isVisible) => {
365
- if (!isVisible) {
366
- throw new Error('The element is not visible');
700
+ interface ShareDashboardToUserProps {
701
+ dashboardName: string;
702
+ role: string;
703
+ userName: string;
704
+ }
705
+
706
+ interface ListingRequestResult {
707
+ body: {
708
+ result: Array<{
709
+ id: number;
710
+ }>;
711
+ };
712
+ }
713
+
714
+ type PatchDashboardBody = {
715
+ widget_settings: {
716
+ data: {
717
+ resources: Array<{
718
+ resourceType: string;
719
+ resources: Array<{
720
+ id: number;
721
+ name: string;
722
+ }>;
723
+ }>;
724
+ metrics: Array<{
725
+ criticalHighThreshold: number;
726
+ criticalLowThreshold: number;
727
+ id: number;
728
+ name: string;
729
+ unit: string;
730
+ warningHighThreshold: number;
731
+ warningLowThreshold: number;
732
+ }>;
733
+ };
734
+ options: {
735
+ timeperiod: {
736
+ start: any;
737
+ end: any;
738
+ timePeriodType: number;
739
+ };
740
+ threshold: {
741
+ enabled: boolean;
742
+ customCritical: any;
743
+ criticalType: string;
744
+ customWarning: any;
745
+ warningType: string;
746
+ };
747
+ refreshInterval: string;
748
+ curveType: string;
749
+ description: {
750
+ content: string;
751
+ enabled: boolean;
752
+ };
753
+ };
754
+ };
755
+ };
756
+
757
+ Cypress.Commands.add(
758
+ 'shareDashboardToUser',
759
+ ({ dashboardName, userName, role }: ShareDashboardToUserProps): void => {
760
+ Promise.all([
761
+ cy.request({
762
+ method: 'GET',
763
+ url: `/centreon/api/latest/configuration/users?search={"name":"${userName}"}`
764
+ }),
765
+ cy.request({
766
+ method: 'GET',
767
+ url: `/centreon/api/latest/configuration/dashboards?search={"name":"${dashboardName}"}`
768
+ })
769
+ ]).then(
770
+ ([retrievedUser, retrievedDashboard]: [
771
+ ListingRequestResult,
772
+ ListingRequestResult
773
+ ]) => {
774
+ const userId = retrievedUser.body.result[0].id;
775
+ const dashboardId = retrievedDashboard.body.result[0].id;
776
+
777
+ cy.request({
778
+ body: {
779
+ id: userId,
780
+ role: `${role}`
781
+ },
782
+ method: 'POST',
783
+ url: `/centreon/api/latest/configuration/dashboards/${dashboardId}/access_rights/contacts`
784
+ });
367
785
  }
368
- });
786
+ );
369
787
  }
370
788
  );
371
789
 
790
+ Cypress.Commands.add('getTimeFromHeader', (): Cypress.Chainable => {
791
+ return cy
792
+ .get('header div[data-cy="clock"]', { timeout: 20000 })
793
+ .should('be.visible')
794
+ .then(($time) => {
795
+ const headerTime = $time.children()[1].textContent;
796
+ if (headerTime?.match(/\d+:\d+/)) {
797
+ cy.log(`header time is : ${headerTime}`);
798
+
799
+ return cy.wrap(headerTime);
800
+ }
801
+
802
+ throw new Error(`header time is not displayed`);
803
+ });
804
+ });
805
+
806
+ Cypress.Commands.add('insertDashboardWithDoubleWidget', (dashboardBody, patchBody1, patchBody2, widgetName, widgetType) => {
807
+ cy.request({
808
+ body: { ...dashboardBody },
809
+ method: 'POST',
810
+ url: '/centreon/api/latest/configuration/dashboards'
811
+ }).then((response) => {
812
+ const dashboardId = response.body.id;
813
+
814
+ cy.waitUntil(
815
+ () => {
816
+ return cy.request({
817
+ method: 'GET',
818
+ url: `/centreon/api/latest/configuration/dashboards/${dashboardId}`
819
+ }).then((getResponse) => {
820
+ return getResponse.body && getResponse.body.id === dashboardId;
821
+ });
822
+ },
823
+ { timeout: 10000 }
824
+ );
825
+
826
+ const formData = new FormData();
827
+
828
+ // Panel 1
829
+ formData.append('panels[0][name]', widgetName);
830
+ formData.append('panels[0][widget_type]', widgetType);
831
+ formData.append('panels[0][layout][x]', '0');
832
+ formData.append('panels[0][layout][y]', '0');
833
+ formData.append('panels[0][layout][width]', '12');
834
+ formData.append('panels[0][layout][height]', '3');
835
+ formData.append('panels[0][layout][min_width]', '2');
836
+ formData.append('panels[0][layout][min_height]', '2');
837
+ formData.append('panels[0][widget_settings]', JSON.stringify(patchBody1));
838
+
839
+ // Panel 2
840
+ formData.append('panels[1][name]', widgetName);
841
+ formData.append('panels[1][widget_type]', widgetType);
842
+ formData.append('panels[1][layout][x]', '0');
843
+ formData.append('panels[1][layout][y]', '3');
844
+ formData.append('panels[1][layout][width]', '12');
845
+ formData.append('panels[1][layout][height]', '3');
846
+ formData.append('panels[1][layout][min_width]', '2');
847
+ formData.append('panels[1][layout][min_height]', '2');
848
+ formData.append('panels[1][widget_settings]', JSON.stringify(patchBody2));
849
+
850
+ // Log form data
851
+ const dataToLog = {};
852
+ formData.forEach((value, key) => {
853
+ dataToLog[key] = value;
854
+ });
855
+
856
+ console.log('FormData before POST:', JSON.stringify(dataToLog, null, 2));
857
+
858
+ cy.request({
859
+ method: 'POST',
860
+ url: `/centreon/api/latest/configuration/dashboards/${dashboardId}`,
861
+ body: formData,
862
+ headers: {
863
+ 'Content-Type': 'multipart/form-data',
864
+ },
865
+ }).then((patchResponse) => {
866
+ console.log('Widget added successfully:', patchResponse);
867
+ });
868
+ });
869
+ });
870
+
372
871
  declare global {
373
872
  namespace Cypress {
374
873
  interface Chainable {
375
- copyFromContainer: (props: CopyFromContainerProps) => Cypress.Chainable;
376
- copyToContainer: (props: CopyToContainerProps) => Cypress.Chainable;
377
- execInContainer: ({
378
- command,
379
- name
380
- }: ExecInContainerProps) => Cypress.Chainable;
874
+ clickSubRootMenuItem: (page: string) => Cypress.Chainable;
875
+ copyFromContainer: (
876
+ props: CopyFromContainerProps,
877
+ options?: Partial<Cypress.ExecOptions>
878
+ ) => Cypress.Chainable;
879
+ copyToContainer: (
880
+ props: CopyToContainerProps,
881
+ options?: Partial<Cypress.ExecOptions>
882
+ ) => Cypress.Chainable;
883
+ createDirectory: (directoryPath: string) => Cypress.Chainable;
884
+ execInContainer: (
885
+ props: ExecInContainerProps,
886
+ options?: ExecInContainerOptions
887
+ ) => Cypress.Chainable;
888
+ getByLabel: ({
889
+ patternType,
890
+ tag,
891
+ label
892
+ }: GetByLabelProps) => Cypress.Chainable;
893
+ getByTestId: ({
894
+ patternType,
895
+ tag,
896
+ testId
897
+ }: GetByTestIdProps) => Cypress.Chainable;
898
+ getContainerId: (containerName: string) => Cypress.Chainable;
899
+ getContainerIpAddress: (containerName: string) => Cypress.Chainable;
900
+ getContainersLogs: () => Cypress.Chainable;
381
901
  getIframeBody: () => Cypress.Chainable;
902
+ getTimeFromHeader: () => Cypress.Chainable;
382
903
  getWebVersion: () => Cypress.Chainable;
383
904
  hoverRootMenuItem: (rootItemNumber: number) => Cypress.Chainable;
905
+ insertDashboard: (dashboard: Dashboard) => Cypress.Chainable;
906
+ insertDashboardList: (fixtureFile: string) => Cypress.Chainable;
907
+ insertDashboardWithWidget: (
908
+ dashboard: Dashboard,
909
+ patchBody: Record<string, any>,
910
+ widgetName:string,
911
+ widgetType:string
912
+ ) => Cypress.Chainable;
913
+ insertDashboardWithDoubleWidget: (
914
+ dashboard: Dashboard,
915
+ patchBody1: Record<string, any>,
916
+ patchBody2: Record<string, any>,
917
+ widgetName: string,
918
+ widgetType: string
919
+ ) => Cypress.Chainable;
920
+ loginAsAdminViaApiV2: () => Cypress.Chainable;
384
921
  loginByTypeOfUser: ({
385
- jsonName = 'admin',
386
- loginViaApi = false
922
+ jsonName,
923
+ loginViaApi
387
924
  }: LoginByTypeOfUserProps) => Cypress.Chainable;
925
+ logout: () => void;
926
+ logoutViaAPI: () => Cypress.Chainable;
388
927
  moveSortableElement: (direction: string) => Cypress.Chainable;
389
928
  navigateTo: ({
390
929
  page,
391
930
  rootItemNumber,
392
931
  subMenu
393
932
  }: NavigateToProps) => Cypress.Chainable;
933
+ requestOnDatabase: ({
934
+ database,
935
+ query
936
+ }: RequestOnDatabaseProps) => Cypress.Chainable;
937
+ setUserTokenApiV1: ({
938
+ login,
939
+ password
940
+ }?: SetUserTokenApiV1Props) => Cypress.Chainable;
941
+ shareDashboardToUser: ({
942
+ dashboardName,
943
+ userName,
944
+ role
945
+ }: ShareDashboardToUserProps) => Cypress.Chainable;
394
946
  startContainer: ({
947
+ command,
395
948
  name,
396
- image
949
+ image,
950
+ portBindings
397
951
  }: StartContainerProps) => Cypress.Chainable;
398
- startWebContainer: ({
399
- name,
400
- os,
952
+ startContainers: ({
953
+ composeFile,
954
+ databaseImage,
955
+ moduleName,
956
+ openidImage,
957
+ profiles,
401
958
  useSlim,
402
- version
403
- }?: StartWebContainerProps) => Cypress.Chainable;
404
- waitForElementInIframe: (
405
- iframeSelector: string,
406
- elementSelector: string
407
- ) => Cypress.Chainable;
959
+ webOs,
960
+ webVersion
961
+ }?: StartContainersProps) => Cypress.Chainable;
408
962
  stopContainer: ({ name }: StopContainerProps) => Cypress.Chainable;
409
- stopWebContainer: ({ name }?: StopWebContainerProps) => Cypress.Chainable;
963
+ stopContainers: () => Cypress.Chainable;
410
964
  visitEmptyPage: () => Cypress.Chainable;
411
- waitForContainerAndSetToken: () => Cypress.Chainable;
412
965
  }
413
966
  }
414
967
  }