@centreon/js-config 24.10.3 → 24.10.5

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