From da379ba16395fe26f5345e1853b3de0175bd8423 Mon Sep 17 00:00:00 2001 From: KIvanow Date: Fri, 23 May 2025 11:34:06 +0300 Subject: [PATCH 01/20] RI-7091 - Add an environment variable to skip the EULA screen - updated privacy link approach --- redisinsight/api/src/constants/agreements-spec.json | 6 ++++++ .../consents-settings/ConsentOption/ConsentOption.tsx | 6 ++---- .../consents-settings/ConsentsPrivacy/ConsentsPrivacy.tsx | 1 - .../src/components/consents-settings/ConsentsSettings.tsx | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/redisinsight/api/src/constants/agreements-spec.json b/redisinsight/api/src/constants/agreements-spec.json index e99bc0549b..001c218789 100644 --- a/redisinsight/api/src/constants/agreements-spec.json +++ b/redisinsight/api/src/constants/agreements-spec.json @@ -7,6 +7,7 @@ "required": false, "editable": true, "disabled": false, + "linkToPrivacyPolicy": true, "category": "privacy", "since": "1.0.1", "title": "Usage Data", @@ -19,6 +20,7 @@ "required": false, "editable": true, "disabled": false, + "linkToPrivacyPolicy": false, "category": "notifications", "since": "1.0.6", "title": "Notification", @@ -37,6 +39,7 @@ "required": false, "editable": true, "disabled": false, + "linkToPrivacyPolicy": false, "category": "privacy", "since": "1.0.3", "title": "Encryption", @@ -49,6 +52,7 @@ "required": false, "editable": true, "disabled": true, + "linkToPrivacyPolicy": false, "category": "privacy", "since": "1.0.3", "title": "Encryption", @@ -61,6 +65,7 @@ "required": false, "editable": true, "disabled": true, + "linkToPrivacyPolicy": false, "category": "privacy", "since": "1.0.5", "title": "Encryption", @@ -75,6 +80,7 @@ "required": true, "editable": false, "disabled": false, + "linkToPrivacyPolicy": false, "since": "1.0.4", "title": "Server Side Public License", "label": "I have read and understood the Terms", diff --git a/redisinsight/ui/src/components/consents-settings/ConsentOption/ConsentOption.tsx b/redisinsight/ui/src/components/consents-settings/ConsentOption/ConsentOption.tsx index 3f439819c5..9da0b46b37 100644 --- a/redisinsight/ui/src/components/consents-settings/ConsentOption/ConsentOption.tsx +++ b/redisinsight/ui/src/components/consents-settings/ConsentOption/ConsentOption.tsx @@ -15,7 +15,6 @@ interface Props { checked: boolean isSettingsPage?: boolean withoutSpacer?: boolean - linkToPrivacyPolicy?: boolean } const ConsentOption = (props: Props) => { @@ -25,7 +24,6 @@ const ConsentOption = (props: Props) => { checked, isSettingsPage = false, withoutSpacer = false, - linkToPrivacyPolicy = false, } = props return ( @@ -38,7 +36,7 @@ const ConsentOption = (props: Props) => { color="subdued" style={{ marginTop: '12px' }} > - + @@ -66,7 +64,7 @@ const ConsentOption = (props: Props) => { color="subdued" style={{ marginTop: '12px' }} > - + )} diff --git a/redisinsight/ui/src/components/consents-settings/ConsentsPrivacy/ConsentsPrivacy.tsx b/redisinsight/ui/src/components/consents-settings/ConsentsPrivacy/ConsentsPrivacy.tsx index 599480169e..8139d4304b 100644 --- a/redisinsight/ui/src/components/consents-settings/ConsentsPrivacy/ConsentsPrivacy.tsx +++ b/redisinsight/ui/src/components/consents-settings/ConsentsPrivacy/ConsentsPrivacy.tsx @@ -97,7 +97,6 @@ const ConsentsPrivacy = () => { onChangeAgreement={onChangeAgreement} isSettingsPage key={consent.agreementName} - linkToPrivacyPolicy /> ))} diff --git a/redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx b/redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx index 2cf52b45ad..0d8aa47e1e 100644 --- a/redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx +++ b/redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx @@ -38,6 +38,7 @@ export interface IConsent { required: boolean editable: boolean disabled: boolean + linkToPrivacyPolicy: boolean category?: string since: string title: string @@ -287,7 +288,6 @@ const ConsentsSettings = ({ onSubmitted }: Props) => { checked={formik.values[consent.agreementName] ?? false} onChangeAgreement={onChangeAgreement} key={consent.agreementName} - linkToPrivacyPolicy /> ))} {!!notificationConsents.length && ( From 949e2c4d05820200ebdad76ddc7637a4fc7c7f8e Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Fri, 23 May 2025 14:58:25 +0300 Subject: [PATCH 02/20] RI-7091 - Add an environment variable to skip the EULA screen - updated existing settings check --- .../settings/repositories/local.agreements.repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts b/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts index a828db01c7..f55717279e 100644 --- a/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts +++ b/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts @@ -20,7 +20,7 @@ export class LocalAgreementsRepository extends AgreementsRepository { defaultOptions: DefaultAgreementsOptions = {} ): Promise { let entity = await this.repository.findOneBy({}); - if (!entity) { + if (!entity?.data) { try { entity = await this.repository.save( classToClass(AgreementsEntity, plainToInstance(Agreements, { From 4c0f105dafc936697988d6e1f5bfcc021123debb Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Fri, 23 May 2025 14:58:37 +0300 Subject: [PATCH 03/20] RI-7091 - Add an environment variable to skip the EULA screen - updated text - out of regular scope --- .../components/consents-settings/ConsentsSettings.tsx | 11 ----------- .../a-first-start-form/user-agreements-form.e2e.ts | 7 +------ .../a-first-start-form/user-agreements-form.e2e.ts | 5 ----- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx b/redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx index 0d8aa47e1e..6e23034f60 100644 --- a/redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx +++ b/redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx @@ -223,17 +223,6 @@ const ConsentsSettings = ({ onSubmitted }: Props) => { {consents.length > 1 && ( <> - - - To avoid automatic execution of malicious code, when adding new - Workbench plugins, use files from trusted authors only. - - - diff --git a/tests/e2e/tests/electron/critical-path/a-first-start-form/user-agreements-form.e2e.ts b/tests/e2e/tests/electron/critical-path/a-first-start-form/user-agreements-form.e2e.ts index 50faeec5fd..61fed84ac5 100644 --- a/tests/e2e/tests/electron/critical-path/a-first-start-form/user-agreements-form.e2e.ts +++ b/tests/e2e/tests/electron/critical-path/a-first-start-form/user-agreements-form.e2e.ts @@ -17,14 +17,9 @@ test('Verify that user should accept User Agreements to continue working with th await t.expect(userAgreementDialog.submitButton.hasAttribute('disabled')).ok('Submit button not disabled by default'); await t.expect(myRedisDatabasePage.AddRedisDatabaseDialog.customSettingsButton.exists).notOk('User can\'t add a database'); }); -test('Verify that the encryption enabled by default and specific message', async t => { - const expectedPluginText = 'To avoid automatic execution of malicious code, when adding new Workbench plugins, use files from trusted authors only.'; - // Verify that section with plugin warning is displayed +test('Verify that the encryption enabled by default and specific message', async t => { await t.expect(userAgreementDialog.pluginSectionWithText.exists).ok('Plugin text is not displayed'); // Verify that text that is displayed in window is 'While adding new visualization plugins, use files only from trusted authors to avoid automatic execution of malicious code.' - const pluginText = userAgreementDialog.pluginSectionWithText.innerText; - await t.expect(pluginText).eql(expectedPluginText, 'Plugin text is incorrect'); - // unskip the verification when encription will be fixed for test builds // // Verify that encryption enabled by default // await t.expect(userAgreementDialog.switchOptionEncryption.withAttribute('aria-checked', 'true').exists).ok('Encryption enabled by default'); diff --git a/tests/e2e/tests/web/critical-path/a-first-start-form/user-agreements-form.e2e.ts b/tests/e2e/tests/web/critical-path/a-first-start-form/user-agreements-form.e2e.ts index 552ef9bf30..b99fbfeaae 100644 --- a/tests/e2e/tests/web/critical-path/a-first-start-form/user-agreements-form.e2e.ts +++ b/tests/e2e/tests/web/critical-path/a-first-start-form/user-agreements-form.e2e.ts @@ -22,12 +22,7 @@ test('Verify that user should accept User Agreements to continue working with th await t.expect(myRedisDatabasePage.AddRedisDatabaseDialog.customSettingsButton.exists).notOk('User can\'t add a database'); }); test('Verify that the encryption enabled by default and specific message', async t => { - const expectedPluginText = 'To avoid automatic execution of malicious code, when adding new Workbench plugins, use files from trusted authors only.'; - // Verify that section with plugin warning is displayed await t.expect(userAgreementDialog.pluginSectionWithText.exists).ok('Plugin text is not displayed'); - // Verify that text that is displayed in window is 'While adding new visualization plugins, use files only from trusted authors to avoid automatic execution of malicious code.' - const pluginText = userAgreementDialog.pluginSectionWithText.innerText; - await t.expect(pluginText).eql(expectedPluginText, 'Plugin text is incorrect'); // Verify that encryption enabled by default await t.expect(userAgreementDialog.switchOptionEncryption.withAttribute('aria-checked', 'true').exists).ok('Encryption enabled by default'); }); From eaff41e812d1f7c6aabb133458e7ef3a2e1e8cd4 Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Tue, 27 May 2025 18:13:46 +0300 Subject: [PATCH 04/20] RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery --- .../src/modules/settings/settings.service.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/redisinsight/api/src/modules/settings/settings.service.ts b/redisinsight/api/src/modules/settings/settings.service.ts index 94e89fd453..cab90f1334 100644 --- a/redisinsight/api/src/modules/settings/settings.service.ts +++ b/redisinsight/api/src/modules/settings/settings.service.ts @@ -51,6 +51,26 @@ export class SettingsService { private eventEmitter: EventEmitter2, ) {} + /** + * Discovers databases after EULA has been accepted + * @param sessionMetadata + * @private + */ + private async discoverDatabasesAfterEulaAccepted( + sessionMetadata: SessionMetadata, + ): Promise { + try { + await this.databaseDiscoveryService.discover(sessionMetadata, true); + } catch (e) { + // ignore error + this.logger.error( + 'Failed discover databases after eula accepted.', + e, + sessionMetadata, + ); + } + } + /** * Method to get settings */ @@ -75,6 +95,9 @@ export class SettingsService { }, version: (await this.getAgreementsSpec()).version, }; + + // If eula is automatically accepted, also discover databases + await this.discoverDatabasesAfterEulaAccepted(sessionMetadata); } const agreements = await this.agreementRepository.getOrCreate(sessionMetadata, defaultOptions); @@ -153,16 +176,7 @@ export class SettingsService { // Discover databases from envs or autodiscovery flow when eula accept if (!oldAppSettings?.agreements?.eula && results?.agreements?.eula) { - try { - await this.databaseDiscoveryService.discover(sessionMetadata, true); - } catch (e) { - // ignore error - this.logger.error( - 'Failed discover databases after eula accepted.', - e, - sessionMetadata, - ); - } + await this.discoverDatabasesAfterEulaAccepted(sessionMetadata); } return results; From a461c72d160a1589a2c96394e4b4406377630575 Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Tue, 27 May 2025 19:08:12 +0300 Subject: [PATCH 05/20] RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery --- .../api/src/modules/settings/settings.service.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/redisinsight/api/src/modules/settings/settings.service.ts b/redisinsight/api/src/modules/settings/settings.service.ts index cab90f1334..b4cbe363bd 100644 --- a/redisinsight/api/src/modules/settings/settings.service.ts +++ b/redisinsight/api/src/modules/settings/settings.service.ts @@ -94,10 +94,7 @@ export class SettingsService { notifications: false, }, version: (await this.getAgreementsSpec()).version, - }; - - // If eula is automatically accepted, also discover databases - await this.discoverDatabasesAfterEulaAccepted(sessionMetadata); + }; } const agreements = await this.agreementRepository.getOrCreate(sessionMetadata, defaultOptions); @@ -107,6 +104,10 @@ export class SettingsService { sessionMetadata, ); + if (SERVER_CONFIG.acceptTermsAndConditions) { + await this.discoverDatabasesAfterEulaAccepted(sessionMetadata); + } + return classToClass(GetAppSettingsResponse, { ...settings?.data, From 0b2993b02a576c7bc835f9347b7785ca22075057 Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Tue, 27 May 2025 19:39:29 +0300 Subject: [PATCH 06/20] RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery --- redisinsight/api/src/modules/settings/settings.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/redisinsight/api/src/modules/settings/settings.service.ts b/redisinsight/api/src/modules/settings/settings.service.ts index b4cbe363bd..dd2f47ffd5 100644 --- a/redisinsight/api/src/modules/settings/settings.service.ts +++ b/redisinsight/api/src/modules/settings/settings.service.ts @@ -105,7 +105,9 @@ export class SettingsService { ); if (SERVER_CONFIG.acceptTermsAndConditions) { - await this.discoverDatabasesAfterEulaAccepted(sessionMetadata); + process.nextTick(async () => { + await this.discoverDatabasesAfterEulaAccepted(sessionMetadata); + }); } From 15be851564a80bef2542e32301940f0477cbeaa5 Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Tue, 27 May 2025 20:01:47 +0300 Subject: [PATCH 07/20] RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery --- .../src/modules/settings/settings.service.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/redisinsight/api/src/modules/settings/settings.service.ts b/redisinsight/api/src/modules/settings/settings.service.ts index dd2f47ffd5..2194ae4dd9 100644 --- a/redisinsight/api/src/modules/settings/settings.service.ts +++ b/redisinsight/api/src/modules/settings/settings.service.ts @@ -5,6 +5,7 @@ import { Injectable, InternalServerErrorException, Logger, + OnApplicationBootstrap, } from '@nestjs/common'; import { difference, isEmpty, map, cloneDeep } from 'lodash'; import { readFile } from 'fs-extra'; @@ -35,7 +36,7 @@ import { EncryptionService } from '../encryption/encryption.service'; const SERVER_CONFIG = config.get('server') as Config['server']; @Injectable() -export class SettingsService { +export class SettingsService implements OnApplicationBootstrap { private logger = new Logger('SettingsService'); constructor( @@ -51,6 +52,20 @@ export class SettingsService { private eventEmitter: EventEmitter2, ) {} + async onApplicationBootstrap() { + // Check if we need to run discovery due to environment variables + if (SERVER_CONFIG.acceptTermsAndConditions) { + const sessionMetadata = { requestId: 'system-init' }; + // Add small delay to ensure all services are fully ready + setTimeout(() => { + this.discoverDatabasesAfterEulaAccepted(null) + .catch(err => { + this.logger.error('Bootstrap database discovery failed', err); + }); + }, 1000); + } + } + /** * Discovers databases after EULA has been accepted * @param sessionMetadata @@ -104,13 +119,6 @@ export class SettingsService { sessionMetadata, ); - if (SERVER_CONFIG.acceptTermsAndConditions) { - process.nextTick(async () => { - await this.discoverDatabasesAfterEulaAccepted(sessionMetadata); - }); - } - - return classToClass(GetAppSettingsResponse, { ...settings?.data, acceptTermsAndConditionsOverwritten: SERVER_CONFIG.acceptTermsAndConditions, From c82e8d865624042f5d84085087eea3f4d62309dc Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Tue, 27 May 2025 20:42:25 +0300 Subject: [PATCH 08/20] RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery --- .../src/modules/settings/settings.service.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/redisinsight/api/src/modules/settings/settings.service.ts b/redisinsight/api/src/modules/settings/settings.service.ts index 2194ae4dd9..084aea549a 100644 --- a/redisinsight/api/src/modules/settings/settings.service.ts +++ b/redisinsight/api/src/modules/settings/settings.service.ts @@ -38,6 +38,8 @@ const SERVER_CONFIG = config.get('server') as Config['server']; @Injectable() export class SettingsService implements OnApplicationBootstrap { private logger = new Logger('SettingsService'); + private triggerAutoDiscoveryDueToEnv = false; + private autoDiscoveryDueToEnvTriggered = false; constructor( @Inject(forwardRef(() => DatabaseDiscoveryService)) @@ -54,15 +56,13 @@ export class SettingsService implements OnApplicationBootstrap { async onApplicationBootstrap() { // Check if we need to run discovery due to environment variables - if (SERVER_CONFIG.acceptTermsAndConditions) { - const sessionMetadata = { requestId: 'system-init' }; - // Add small delay to ensure all services are fully ready - setTimeout(() => { - this.discoverDatabasesAfterEulaAccepted(null) - .catch(err => { - this.logger.error('Bootstrap database discovery failed', err); - }); - }, 1000); + if ( + SERVER_CONFIG.acceptTermsAndConditions + && this.triggerAutoDiscoveryDueToEnv + && !this.autoDiscoveryDueToEnvTriggered + ) { + this.autoDiscoveryDueToEnvTriggered = true; + process.nextTick(() => this.discoverDatabasesAfterEulaAccepted(null)); } } @@ -99,6 +99,7 @@ export class SettingsService implements OnApplicationBootstrap { let defaultOptions: object; if (SERVER_CONFIG.acceptTermsAndConditions) { + this.triggerAutoDiscoveryDueToEnv = true; const isEncryptionAvailable = await this.encryptionService.isEncryptionAvailable(); defaultOptions = { From 1e8c0994aec2b0a8a0d2b8c53cfbc526eb5c5729 Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Tue, 27 May 2025 21:02:46 +0300 Subject: [PATCH 09/20] RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery --- redisinsight/api/src/modules/settings/settings.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisinsight/api/src/modules/settings/settings.service.ts b/redisinsight/api/src/modules/settings/settings.service.ts index 084aea549a..ca5d8b448d 100644 --- a/redisinsight/api/src/modules/settings/settings.service.ts +++ b/redisinsight/api/src/modules/settings/settings.service.ts @@ -110,7 +110,7 @@ export class SettingsService implements OnApplicationBootstrap { notifications: false, }, version: (await this.getAgreementsSpec()).version, - }; + }; } const agreements = await this.agreementRepository.getOrCreate(sessionMetadata, defaultOptions); From 8fc033373e08d2a8960f0489ef21fd4cc1e04dd7 Mon Sep 17 00:00:00 2001 From: ArtemHoruzhenko Date: Wed, 28 May 2025 09:00:05 +0300 Subject: [PATCH 10/20] RI-7091 fix regular autodiscovery --- .../src/modules/init/local.init.service.ts | 2 +- .../local.agreements.repository.ts | 2 +- .../src/modules/settings/settings.service.ts | 51 +++++-------------- 3 files changed, 14 insertions(+), 41 deletions(-) diff --git a/redisinsight/api/src/modules/init/local.init.service.ts b/redisinsight/api/src/modules/init/local.init.service.ts index 199244d147..ef1d77b139 100644 --- a/redisinsight/api/src/modules/init/local.init.service.ts +++ b/redisinsight/api/src/modules/init/local.init.service.ts @@ -32,7 +32,7 @@ export class LocalInitService extends InitService { await this.initAnalytics(firstStart); await this.featureService.recalculateFeatureFlags(sessionMetadata); await this.redisClientFactory.init(); - await this.databaseDiscoveryService.discover(sessionMetadata); + await this.databaseDiscoveryService.discover(sessionMetadata, firstStart); } async initAnalytics(firstStart: boolean) { diff --git a/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts b/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts index f55717279e..12dce2b4fe 100644 --- a/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts +++ b/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts @@ -17,7 +17,7 @@ export class LocalAgreementsRepository extends AgreementsRepository { async getOrCreate( sessionMetadata: SessionMetadata, - defaultOptions: DefaultAgreementsOptions = {} + defaultOptions: DefaultAgreementsOptions = {}, ): Promise { let entity = await this.repository.findOneBy({}); if (!entity?.data) { diff --git a/redisinsight/api/src/modules/settings/settings.service.ts b/redisinsight/api/src/modules/settings/settings.service.ts index ca5d8b448d..040127fb0c 100644 --- a/redisinsight/api/src/modules/settings/settings.service.ts +++ b/redisinsight/api/src/modules/settings/settings.service.ts @@ -5,7 +5,6 @@ import { Injectable, InternalServerErrorException, Logger, - OnApplicationBootstrap, } from '@nestjs/common'; import { difference, isEmpty, map, cloneDeep } from 'lodash'; import { readFile } from 'fs-extra'; @@ -36,10 +35,8 @@ import { EncryptionService } from '../encryption/encryption.service'; const SERVER_CONFIG = config.get('server') as Config['server']; @Injectable() -export class SettingsService implements OnApplicationBootstrap { +export class SettingsService { private logger = new Logger('SettingsService'); - private triggerAutoDiscoveryDueToEnv = false; - private autoDiscoveryDueToEnvTriggered = false; constructor( @Inject(forwardRef(() => DatabaseDiscoveryService)) @@ -54,38 +51,6 @@ export class SettingsService implements OnApplicationBootstrap { private eventEmitter: EventEmitter2, ) {} - async onApplicationBootstrap() { - // Check if we need to run discovery due to environment variables - if ( - SERVER_CONFIG.acceptTermsAndConditions - && this.triggerAutoDiscoveryDueToEnv - && !this.autoDiscoveryDueToEnvTriggered - ) { - this.autoDiscoveryDueToEnvTriggered = true; - process.nextTick(() => this.discoverDatabasesAfterEulaAccepted(null)); - } - } - - /** - * Discovers databases after EULA has been accepted - * @param sessionMetadata - * @private - */ - private async discoverDatabasesAfterEulaAccepted( - sessionMetadata: SessionMetadata, - ): Promise { - try { - await this.databaseDiscoveryService.discover(sessionMetadata, true); - } catch (e) { - // ignore error - this.logger.error( - 'Failed discover databases after eula accepted.', - e, - sessionMetadata, - ); - } - } - /** * Method to get settings */ @@ -99,8 +64,7 @@ export class SettingsService implements OnApplicationBootstrap { let defaultOptions: object; if (SERVER_CONFIG.acceptTermsAndConditions) { - this.triggerAutoDiscoveryDueToEnv = true; - const isEncryptionAvailable = await this.encryptionService.isEncryptionAvailable(); + const isEncryptionAvailable = await this.encryptionService.isEncryptionAvailable(); defaultOptions = { data: { @@ -188,7 +152,16 @@ export class SettingsService implements OnApplicationBootstrap { // Discover databases from envs or autodiscovery flow when eula accept if (!oldAppSettings?.agreements?.eula && results?.agreements?.eula) { - await this.discoverDatabasesAfterEulaAccepted(sessionMetadata); + try { + await this.databaseDiscoveryService.discover(sessionMetadata, true); + } catch (e) { + // ignore error + this.logger.error( + 'Failed discover databases after eula accepted.', + e, + sessionMetadata, + ); + } } return results; From a81ed328bb18183bff00add273c97326dbe7f45b Mon Sep 17 00:00:00 2001 From: KIvanow Date: Wed, 28 May 2025 10:36:37 +0300 Subject: [PATCH 11/20] RI-7091 - Add an environment variable to skip the EULA screen - testing a work around fix on top of Artem's suggestion --- redisinsight/api/src/modules/init/local.init.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/redisinsight/api/src/modules/init/local.init.service.ts b/redisinsight/api/src/modules/init/local.init.service.ts index ef1d77b139..39eea0a262 100644 --- a/redisinsight/api/src/modules/init/local.init.service.ts +++ b/redisinsight/api/src/modules/init/local.init.service.ts @@ -32,7 +32,9 @@ export class LocalInitService extends InitService { await this.initAnalytics(firstStart); await this.featureService.recalculateFeatureFlags(sessionMetadata); await this.redisClientFactory.init(); - await this.databaseDiscoveryService.discover(sessionMetadata, firstStart); + process.nextTick(async () => { + await this.databaseDiscoveryService.discover(sessionMetadata, firstStart); + }); } async initAnalytics(firstStart: boolean) { From 5c49cbdce87d0221654f78a31abd9862d0166f08 Mon Sep 17 00:00:00 2001 From: KIvanow Date: Wed, 28 May 2025 15:20:57 +0300 Subject: [PATCH 12/20] testing delaying of the autodiscovery as a way to avoid the odd race condition happening --- .../auto.database-discovery.service.ts | 13 ++++- .../local.database-discovery.service.ts | 55 ++++++++++++------- .../utils/autodiscovery.util.ts | 8 +++ .../src/modules/init/local.init.service.ts | 4 +- 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts b/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts index bb82d32e47..220aa325e1 100644 --- a/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts +++ b/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts @@ -15,6 +15,7 @@ import { ConstantsProvider } from 'src/modules/constants/providers/constants.pro @Injectable() export class AutoDatabaseDiscoveryService { private logger = new Logger('AutoDatabaseDiscoveryService'); + private isDiscoveryRunning = false; constructor( private redisClientFactory: RedisClientFactory, @@ -22,14 +23,22 @@ export class AutoDatabaseDiscoveryService { ) {} /** - * Try to add standalone databases without auth from processes running on the host machine listening on TCP4 + * Start discovery process in background without blocking the main thread * Database alias will be "host:port" */ async discover(sessionMetadata: SessionMetadata) { + // Return immediately to not block the main thread + if (this.isDiscoveryRunning) { + return; + } + + // Perform the actual discovery in background + this.isDiscoveryRunning = true; try { // additional check for existing databases // We should not start auto discover if any database already exists if ((await this.databaseService.list(sessionMetadata)).length) { + this.isDiscoveryRunning = false; return; } @@ -46,6 +55,8 @@ export class AutoDatabaseDiscoveryService { ]); } catch (e) { this.logger.warn('Unable to discover redis database', e); + } finally { + this.isDiscoveryRunning = false; } } diff --git a/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts b/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts index aa10e73787..c4abca8762 100644 --- a/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts +++ b/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts @@ -11,6 +11,7 @@ const SERVER_CONFIG = config.get('server') as Config['server']; @Injectable() export class LocalDatabaseDiscoveryService extends DatabaseDiscoveryService { private logger = new Logger('LocalDatabaseDiscoveryService'); + private isDiscoveryRunning = false; constructor( @Inject(forwardRef(() => SettingsService)) @@ -21,33 +22,49 @@ export class LocalDatabaseDiscoveryService extends DatabaseDiscoveryService { super(); } + /** + * Non-blocking implementation of database discovery + * This returns quickly and performs the actual discovery work in the background + */ async discover( sessionMetadata: SessionMetadata, firstRun?: boolean, ): Promise { - try { - // no need to auto discover for Redis Stack - if (SERVER_CONFIG.buildType === 'REDIS_STACK') { - return; - } + // Return immediately if discovery is already in progress + if (this.isDiscoveryRunning) { + return; + } + + // No need to auto discover for Redis Stack - quick check + if (SERVER_CONFIG.buildType === 'REDIS_STACK') { + return; + } - // check agreements to understand if it is first launch - const settings = - await this.settingsService.getAppSettings(sessionMetadata); + // Mark as running and perform discovery in background + this.isDiscoveryRunning = true; + setImmediate(async () => { + try { + // check agreements to understand if it is first launch + const settings = + await this.settingsService.getAppSettings(sessionMetadata); - if (!settings?.agreements?.eula) { - return; - } + if (!settings?.agreements?.eula) { + this.isDiscoveryRunning = false; + return; + } - const { discovered } = - await this.preSetupDatabaseDiscoveryService.discover(sessionMetadata); + const { discovered } = + await this.preSetupDatabaseDiscoveryService.discover(sessionMetadata); - if (!discovered && firstRun) { - await this.autoDatabaseDiscoveryService.discover(sessionMetadata); + if (!discovered && firstRun) { + await this.autoDatabaseDiscoveryService.discover(sessionMetadata); + } + } catch (e) { + // ignore error + this.logger.error('Unable to discover databases', e); + } finally { + this.isDiscoveryRunning = false; } - } catch (e) { - // ignore error - this.logger.error('Unable to discover databases', e); - } + }); } } diff --git a/redisinsight/api/src/modules/database-discovery/utils/autodiscovery.util.ts b/redisinsight/api/src/modules/database-discovery/utils/autodiscovery.util.ts index 0c644c2ad8..a8f3d74772 100644 --- a/redisinsight/api/src/modules/database-discovery/utils/autodiscovery.util.ts +++ b/redisinsight/api/src/modules/database-discovery/utils/autodiscovery.util.ts @@ -29,15 +29,23 @@ export const getRunningProcesses = async (): Promise => let stdoutData = ''; const proc = spawn(...getSpawnArgs()); + // Add timeout to ensure we don't wait forever + const timeout = setTimeout(() => { + proc.kill(); + resolve([]); // Return empty array on timeout + }, 1000); + proc.stdout.on('data', (data) => { stdoutData += data.toString(); }); proc.on('error', (e) => { + clearTimeout(timeout); reject(e); }); proc.stdout.on('end', () => { + clearTimeout(timeout); resolve(stdoutData.split('\n')); }); } catch (e) { diff --git a/redisinsight/api/src/modules/init/local.init.service.ts b/redisinsight/api/src/modules/init/local.init.service.ts index 39eea0a262..ef1d77b139 100644 --- a/redisinsight/api/src/modules/init/local.init.service.ts +++ b/redisinsight/api/src/modules/init/local.init.service.ts @@ -32,9 +32,7 @@ export class LocalInitService extends InitService { await this.initAnalytics(firstStart); await this.featureService.recalculateFeatureFlags(sessionMetadata); await this.redisClientFactory.init(); - process.nextTick(async () => { - await this.databaseDiscoveryService.discover(sessionMetadata, firstStart); - }); + await this.databaseDiscoveryService.discover(sessionMetadata, firstStart); } async initAnalytics(firstStart: boolean) { From 3f07f39e0f85779b0f7bf7d238fc300f61473431 Mon Sep 17 00:00:00 2001 From: KIvanow Date: Wed, 28 May 2025 16:33:54 +0300 Subject: [PATCH 13/20] removed setImmediate to check --- .../local.database-discovery.service.ts | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts b/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts index c4abca8762..d8e1029ef7 100644 --- a/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts +++ b/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts @@ -42,29 +42,27 @@ export class LocalDatabaseDiscoveryService extends DatabaseDiscoveryService { // Mark as running and perform discovery in background this.isDiscoveryRunning = true; - setImmediate(async () => { - try { - // check agreements to understand if it is first launch - const settings = - await this.settingsService.getAppSettings(sessionMetadata); + try { + // check agreements to understand if it is first launch + const settings = + await this.settingsService.getAppSettings(sessionMetadata); - if (!settings?.agreements?.eula) { - this.isDiscoveryRunning = false; - return; - } + if (!settings?.agreements?.eula) { + this.isDiscoveryRunning = false; + return; + } - const { discovered } = - await this.preSetupDatabaseDiscoveryService.discover(sessionMetadata); + const { discovered } = + await this.preSetupDatabaseDiscoveryService.discover(sessionMetadata); - if (!discovered && firstRun) { - await this.autoDatabaseDiscoveryService.discover(sessionMetadata); - } - } catch (e) { - // ignore error - this.logger.error('Unable to discover databases', e); - } finally { - this.isDiscoveryRunning = false; + if (!discovered && firstRun) { + await this.autoDatabaseDiscoveryService.discover(sessionMetadata); } - }); + } catch (e) { + // ignore error + this.logger.error('Unable to discover databases', e); + } finally { + this.isDiscoveryRunning = false; + } } } From 77057c4e317e2ae51f64797b4c3338084208e1bc Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Wed, 28 May 2025 18:33:33 +0300 Subject: [PATCH 14/20] removed setTimeouts --- .../database-discovery/utils/autodiscovery.util.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/redisinsight/api/src/modules/database-discovery/utils/autodiscovery.util.ts b/redisinsight/api/src/modules/database-discovery/utils/autodiscovery.util.ts index a8f3d74772..0c644c2ad8 100644 --- a/redisinsight/api/src/modules/database-discovery/utils/autodiscovery.util.ts +++ b/redisinsight/api/src/modules/database-discovery/utils/autodiscovery.util.ts @@ -29,23 +29,15 @@ export const getRunningProcesses = async (): Promise => let stdoutData = ''; const proc = spawn(...getSpawnArgs()); - // Add timeout to ensure we don't wait forever - const timeout = setTimeout(() => { - proc.kill(); - resolve([]); // Return empty array on timeout - }, 1000); - proc.stdout.on('data', (data) => { stdoutData += data.toString(); }); proc.on('error', (e) => { - clearTimeout(timeout); reject(e); }); proc.stdout.on('end', () => { - clearTimeout(timeout); resolve(stdoutData.split('\n')); }); } catch (e) { From 5f4ffd39efacb638325048c554f265842fb4e4ad Mon Sep 17 00:00:00 2001 From: KIvanow Date: Thu, 29 May 2025 10:30:16 +0300 Subject: [PATCH 15/20] RI-7091 - extra logs and removed extra code --- .../auto.database-discovery.service.ts | 1 + .../local.database-discovery.service.ts | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts b/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts index 220aa325e1..84b30ad94c 100644 --- a/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts +++ b/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts @@ -27,6 +27,7 @@ export class AutoDatabaseDiscoveryService { * Database alias will be "host:port" */ async discover(sessionMetadata: SessionMetadata) { + console.log('AutoDatabaseDiscoveryService discover called', this.isDiscoveryRunning); // Return immediately to not block the main thread if (this.isDiscoveryRunning) { return; diff --git a/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts b/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts index d8e1029ef7..dcc4521797 100644 --- a/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts +++ b/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts @@ -11,7 +11,6 @@ const SERVER_CONFIG = config.get('server') as Config['server']; @Injectable() export class LocalDatabaseDiscoveryService extends DatabaseDiscoveryService { private logger = new Logger('LocalDatabaseDiscoveryService'); - private isDiscoveryRunning = false; constructor( @Inject(forwardRef(() => SettingsService)) @@ -30,25 +29,17 @@ export class LocalDatabaseDiscoveryService extends DatabaseDiscoveryService { sessionMetadata: SessionMetadata, firstRun?: boolean, ): Promise { - // Return immediately if discovery is already in progress - if (this.isDiscoveryRunning) { - return; - } - // No need to auto discover for Redis Stack - quick check if (SERVER_CONFIG.buildType === 'REDIS_STACK') { return; } - // Mark as running and perform discovery in background - this.isDiscoveryRunning = true; try { // check agreements to understand if it is first launch const settings = await this.settingsService.getAppSettings(sessionMetadata); if (!settings?.agreements?.eula) { - this.isDiscoveryRunning = false; return; } @@ -61,8 +52,6 @@ export class LocalDatabaseDiscoveryService extends DatabaseDiscoveryService { } catch (e) { // ignore error this.logger.error('Unable to discover databases', e); - } finally { - this.isDiscoveryRunning = false; } } } From 3b9294296f2f447e1f6656002f401d5ac5b3d33b Mon Sep 17 00:00:00 2001 From: KIvanow Date: Thu, 29 May 2025 11:27:23 +0300 Subject: [PATCH 16/20] - --- .../auto.database-discovery.service.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts b/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts index 84b30ad94c..a4e15d5837 100644 --- a/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts +++ b/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts @@ -15,7 +15,6 @@ import { ConstantsProvider } from 'src/modules/constants/providers/constants.pro @Injectable() export class AutoDatabaseDiscoveryService { private logger = new Logger('AutoDatabaseDiscoveryService'); - private isDiscoveryRunning = false; constructor( private redisClientFactory: RedisClientFactory, @@ -27,19 +26,10 @@ export class AutoDatabaseDiscoveryService { * Database alias will be "host:port" */ async discover(sessionMetadata: SessionMetadata) { - console.log('AutoDatabaseDiscoveryService discover called', this.isDiscoveryRunning); - // Return immediately to not block the main thread - if (this.isDiscoveryRunning) { - return; - } - - // Perform the actual discovery in background - this.isDiscoveryRunning = true; try { // additional check for existing databases // We should not start auto discover if any database already exists if ((await this.databaseService.list(sessionMetadata)).length) { - this.isDiscoveryRunning = false; return; } @@ -56,8 +46,6 @@ export class AutoDatabaseDiscoveryService { ]); } catch (e) { this.logger.warn('Unable to discover redis database', e); - } finally { - this.isDiscoveryRunning = false; } } From 6f1ff6ad8d592b8e5193c91f6ff0b6314c3b251e Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Fri, 30 May 2025 10:39:28 +0300 Subject: [PATCH 17/20] - --- .../auto.database-discovery.service.ts | 2 +- .../local.database-discovery.service.ts | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts b/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts index a4e15d5837..bb82d32e47 100644 --- a/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts +++ b/redisinsight/api/src/modules/database-discovery/auto.database-discovery.service.ts @@ -22,7 +22,7 @@ export class AutoDatabaseDiscoveryService { ) {} /** - * Start discovery process in background without blocking the main thread + * Try to add standalone databases without auth from processes running on the host machine listening on TCP4 * Database alias will be "host:port" */ async discover(sessionMetadata: SessionMetadata) { diff --git a/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts b/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts index dcc4521797..3f5aec0cac 100644 --- a/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts +++ b/redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts @@ -21,20 +21,16 @@ export class LocalDatabaseDiscoveryService extends DatabaseDiscoveryService { super(); } - /** - * Non-blocking implementation of database discovery - * This returns quickly and performs the actual discovery work in the background - */ async discover( sessionMetadata: SessionMetadata, firstRun?: boolean, ): Promise { - // No need to auto discover for Redis Stack - quick check - if (SERVER_CONFIG.buildType === 'REDIS_STACK') { - return; - } - try { + // No need to auto discover for Redis Stack - quick check + if (SERVER_CONFIG.buildType === 'REDIS_STACK') { + return; + } + // check agreements to understand if it is first launch const settings = await this.settingsService.getAppSettings(sessionMetadata); From ea03c96534820ad3e8301b396419c53efa39b9ad Mon Sep 17 00:00:00 2001 From: Kristiyan Ivanov Date: Mon, 2 Jun 2025 09:03:42 +0300 Subject: [PATCH 18/20] RI-7091 - Add an environment variable to skip the EULA screen - fixed integration tests --- .../api/test/api/settings/GET-settings-agreements-spec.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/redisinsight/api/test/api/settings/GET-settings-agreements-spec.test.ts b/redisinsight/api/test/api/settings/GET-settings-agreements-spec.test.ts index 7a23bbd783..96e64e9276 100644 --- a/redisinsight/api/test/api/settings/GET-settings-agreements-spec.test.ts +++ b/redisinsight/api/test/api/settings/GET-settings-agreements-spec.test.ts @@ -17,6 +17,7 @@ const agreementItemSchema = Joi.object().keys({ category: Joi.string().optional(), description: Joi.string().optional(), requiredText: Joi.string().optional(), + linkToPrivacyPolicy: Joi.boolean().required(), }); const responseSchema = Joi.object() From b5e238dee7cdc6b840a1a821852c2ddfdbfd108e Mon Sep 17 00:00:00 2001 From: KIvanow Date: Tue, 3 Jun 2025 18:15:44 +0300 Subject: [PATCH 19/20] RI-7091 - Add an environment variable to skip the EULA screen - added BE tests --- .../encryption/encryption.service.spec.ts | 38 +++++++++++++++ .../local.agreements.repository.spec.ts | 48 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/redisinsight/api/src/modules/encryption/encryption.service.spec.ts b/redisinsight/api/src/modules/encryption/encryption.service.spec.ts index bed86e9db3..f163134213 100644 --- a/redisinsight/api/src/modules/encryption/encryption.service.spec.ts +++ b/redisinsight/api/src/modules/encryption/encryption.service.spec.ts @@ -103,6 +103,44 @@ describe('EncryptionService', () => { }); }); + describe('isEncryptionAvailable', () => { + it('should return true when multiple strategies are available (KEYTAR and PLAIN)', async () => { + keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true); + keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false); + + const result = await service.isEncryptionAvailable(); + + expect(result).toBe(true); + }); + + it('should return true when multiple strategies are available (KEY and PLAIN)', async () => { + keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(false); + keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true); + + const result = await service.isEncryptionAvailable(); + + expect(result).toBe(true); + }); + + it('should return true when all strategies are available (KEY, KEYTAR and PLAIN)', async () => { + keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true); + keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true); + + const result = await service.isEncryptionAvailable(); + + expect(result).toBe(true); + }); + + it('should return false when only PLAIN strategy is available', async () => { + keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(false); + keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false); + + const result = await service.isEncryptionAvailable(); + + expect(result).toBe(false); + }); + }); + describe('getEncryptionStrategy', () => { it('Should return KEYTAR strategy based on app agreements', async () => { expect(await service.getEncryptionStrategy()).toEqual( diff --git a/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.spec.ts b/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.spec.ts index 11d48623ed..3feab3361f 100644 --- a/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.spec.ts +++ b/redisinsight/api/src/modules/settings/repositories/local.agreements.repository.spec.ts @@ -57,6 +57,54 @@ describe('LocalAgreementsRepository', () => { data: undefined, }); }); + it('should create new agreements when entity exists but has no data', async () => { + // Mock an entity that exists but has no data property + const entityWithoutData = Object.assign(new AgreementsEntity(), { + id: mockUserId, + version: '1.0.0', + data: undefined, // This should trigger the !entity?.data check + }); + + repository.findOneBy.mockResolvedValueOnce(entityWithoutData); + + const result = await service.getOrCreate(mockSessionMetadata); + + // Verify that save was called to create a new entity + expect(repository.save).toHaveBeenCalledWith({ + id: 1, + data: undefined, + }); + + expect(result).toEqual({ + ...mockAgreements, + version: undefined, + data: undefined, + }); + }); + it('should create new agreements when entity exists but has empty string data', async () => { + // Mock an entity that exists but has empty string data + const entityWithEmptyData = Object.assign(new AgreementsEntity(), { + id: mockUserId, + version: '1.0.0', + data: '', // This should also trigger the !entity?.data check + }); + + repository.findOneBy.mockResolvedValueOnce(entityWithEmptyData); + + const result = await service.getOrCreate(mockSessionMetadata); + + // Verify that save was called to create a new entity + expect(repository.save).toHaveBeenCalledWith({ + id: 1, + data: undefined, + }); + + expect(result).toEqual({ + ...mockAgreements, + version: undefined, + data: undefined, + }); + }); it('should fail to create with unique constraint and return existing', async () => { repository.findOneBy.mockResolvedValueOnce(null); repository.findOneBy.mockResolvedValueOnce(mockAgreements); From 55bf39210a98bc9d6ccc729dca4b6f481d53228f Mon Sep 17 00:00:00 2001 From: KIvanow Date: Tue, 3 Jun 2025 18:52:27 +0300 Subject: [PATCH 20/20] RI-7091 - Add an environment variable to skip the EULA screen - added FE tests --- .../ConsentOption/ConsentOption.spec.tsx | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 redisinsight/ui/src/components/consents-settings/ConsentOption/ConsentOption.spec.tsx diff --git a/redisinsight/ui/src/components/consents-settings/ConsentOption/ConsentOption.spec.tsx b/redisinsight/ui/src/components/consents-settings/ConsentOption/ConsentOption.spec.tsx new file mode 100644 index 0000000000..ddab11b853 --- /dev/null +++ b/redisinsight/ui/src/components/consents-settings/ConsentOption/ConsentOption.spec.tsx @@ -0,0 +1,126 @@ +import React from 'react' +import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' +import ConsentOption from './ConsentOption' +import { IConsent } from '../ConsentsSettings' + +const mockConsent: IConsent = { + agreementName: 'analytics', + title: 'Analytics', + label: 'Share usage data', + description: 'Help us improve Redis Insight by sharing usage data.', + required: false, + editable: true, + disabled: false, + defaultValue: false, + displayInSetting: true, + since: '1.0.0', + linkToPrivacyPolicy: false, +} + +const mockOnChangeAgreement = jest.fn() + +const defaultProps = { + consent: mockConsent, + onChangeAgreement: mockOnChangeAgreement, + checked: false, +} + +describe('ConsentOption', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should render', () => { + expect(render()).toBeTruthy() + }) + + it('should render switch with correct test id', () => { + render() + expect(screen.getByTestId('switch-option-analytics')).toBeInTheDocument() + }) + + it('should call onChangeAgreement when switch is clicked', () => { + render() + + fireEvent.click(screen.getByTestId('switch-option-analytics')) + + expect(mockOnChangeAgreement).toHaveBeenCalledWith(true, 'analytics') + }) + + it('should render description without privacy policy link when linkToPrivacyPolicy is false', () => { + const consentWithDescription = { + ...mockConsent, + description: 'Help us improve Redis Insight by sharing usage data.', + linkToPrivacyPolicy: false, + } + + render() + + expect(screen.getByText('Help us improve Redis Insight by sharing usage data.')).toBeInTheDocument() + expect(screen.queryByText('Privacy Policy')).not.toBeInTheDocument() + }) + + it('should render description with privacy policy link when linkToPrivacyPolicy is true', () => { + const consentWithPrivacyLink = { + ...mockConsent, + description: 'Help us improve Redis Insight by sharing usage data.', + linkToPrivacyPolicy: true, + } + + render() + + // Verify that the Privacy Policy link is rendered + expect(screen.getByText('Privacy Policy')).toBeInTheDocument() + + const privacyPolicyLink = screen.getByText('Privacy Policy') + expect(privacyPolicyLink.closest('a')).toHaveAttribute( + 'href', + 'https://redis.io/legal/privacy-policy/?utm_source=redisinsight&utm_medium=app&utm_campaign=telemetry' + ) + }) + + it('should render description with privacy policy link on settings page when linkToPrivacyPolicy is true', () => { + const consentWithPrivacyLink = { + ...mockConsent, + description: 'Help us improve Redis Insight by sharing usage data.', + linkToPrivacyPolicy: true, + } + + render() + + // Verify that the Privacy Policy link is rendered + expect(screen.getByText('Privacy Policy')).toBeInTheDocument() + }) + + it('should not render privacy policy link on settings page when linkToPrivacyPolicy is false', () => { + const consentWithoutPrivacyLink = { + ...mockConsent, + description: 'Help us improve Redis Insight by sharing usage data.', + linkToPrivacyPolicy: false, + } + + render() + + expect(screen.getByText('Help us improve Redis Insight by sharing usage data.')).toBeInTheDocument() + expect(screen.queryByText('Privacy Policy')).not.toBeInTheDocument() + }) + + it('should render disabled switch when consent is disabled', () => { + const disabledConsent = { + ...mockConsent, + disabled: true, + } + + render() + + const switchElement = screen.getByTestId('switch-option-analytics') + expect(switchElement).toBeDisabled() + }) + + it('should render checked switch when checked prop is true', () => { + render() + + const switchElement = screen.getByTestId('switch-option-analytics') + expect(switchElement).toBeChecked() + }) +}) \ No newline at end of file