diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt index e0fb55441ac..29424e92db5 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt @@ -93,6 +93,8 @@ interface UserConfigRepository { suspend fun getSupportedProtocols(): Either> fun setConferenceCallingEnabled(enabled: Boolean): Either fun isConferenceCallingEnabled(): Either + fun setUseSFTForOneOnOneCalls(shouldUse: Boolean): Either + fun shouldUseSFTForOneOnOneCalls(): Either fun setSecondFactorPasswordChallengeStatus(isRequired: Boolean): Either fun isSecondFactorPasswordChallengeRequired(): Either fun isReadReceiptsEnabled(): Flow> @@ -292,6 +294,14 @@ internal class UserConfigDataSource internal constructor( userConfigStorage.isConferenceCallingEnabled() } + override fun setUseSFTForOneOnOneCalls(shouldUse: Boolean): Either = wrapStorageRequest { + userConfigStorage.persistUseSftForOneOnOneCalls(shouldUse) + } + + override fun shouldUseSFTForOneOnOneCalls(): Either = wrapStorageRequest { + userConfigStorage.shouldUseSftForOneOnOneCalls() + } + override fun setSecondFactorPasswordChallengeStatus(isRequired: Boolean): Either = wrapStorageRequest { userConfigStorage.persistSecondFactorPasswordChallengeStatus(isRequired) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigMapper.kt index 40d0c4cf22e..2aceb2a1fb8 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigMapper.kt @@ -131,7 +131,8 @@ class FeatureConfigMapperImpl : FeatureConfigMapper { override fun fromDTO(data: FeatureConfigData.ConferenceCalling): ConferenceCallingModel = ConferenceCallingModel( - status = fromDTO(data.status) + status = fromDTO(data.status), + useSFTForOneOnOneCalls = data.config.useSFTForOneToOneCalls ) override fun fromDTO(data: FeatureConfigData.E2EI?): E2EIModel = diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigModel.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigModel.kt index 17d630a2025..1fa12985a76 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigModel.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigModel.kt @@ -90,7 +90,8 @@ data class MLSMigrationModel( ) data class ConferenceCallingModel( - val status: Status + val status: Status, + val useSFTForOneOnOneCalls: Boolean ) data class E2EIModel( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCase.kt index 9299f7a54f7..3ab938a9753 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCase.kt @@ -71,7 +71,7 @@ internal class SyncFeatureConfigsUseCaseImpl( conferenceCallingConfigHandler.handle(it.conferenceCallingModel) passwordChallengeConfigHandler.handle(it.secondFactorPasswordChallengeModel) selfDeletingMessagesConfigHandler.handle(it.selfDeletingMessagesModel) - it.e2EIModel?.let { e2EIModel -> e2EIConfigHandler.handle(e2EIModel) } + it.e2EIModel.let { e2EIModel -> e2EIConfigHandler.handle(e2EIModel) } appLockConfigHandler.handle(it.appLockModel) Either.Right(Unit) }.onFailure { networkFailure -> diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandler.kt index 673aa533ca8..c014dbfbfe8 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandler.kt @@ -22,12 +22,16 @@ import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.featureConfig.ConferenceCallingModel import com.wire.kalium.logic.data.featureConfig.Status import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.functional.flatMap class ConferenceCallingConfigHandler( private val userConfigRepository: UserConfigRepository ) { fun handle(conferenceCallingConfig: ConferenceCallingModel): Either { val conferenceCallingEnabled = conferenceCallingConfig.status == Status.ENABLED - return userConfigRepository.setConferenceCallingEnabled(conferenceCallingEnabled) + val result = userConfigRepository.setConferenceCallingEnabled(conferenceCallingEnabled).flatMap { + userConfigRepository.setUseSFTForOneOnOneCalls(conferenceCallingConfig.useSFTForOneOnOneCalls) + } + return result } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/event/FeatureConfigMapperTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/event/FeatureConfigMapperTest.kt index 1f326d2bb6b..344ac9c580d 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/event/FeatureConfigMapperTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/event/FeatureConfigMapperTest.kt @@ -21,10 +21,9 @@ package com.wire.kalium.logic.data.event import com.wire.kalium.logic.data.featureConfig.FeatureConfigMapper import com.wire.kalium.logic.data.featureConfig.FeatureConfigMapperImpl import com.wire.kalium.logic.data.featureConfig.Status -import com.wire.kalium.logic.data.id.PlainId -import com.wire.kalium.network.api.base.authenticated.conversation.ConvProtocol import com.wire.kalium.network.api.base.authenticated.featureConfigs.AppLockConfigDTO import com.wire.kalium.network.api.base.authenticated.featureConfigs.ClassifiedDomainsConfigDTO +import com.wire.kalium.network.api.base.authenticated.featureConfigs.ConferenceCallingConfigDTO import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureConfigData import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureConfigResponse import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureFlagStatusDTO @@ -143,7 +142,7 @@ class FeatureConfigMapperTest { ClassifiedDomainsConfigDTO(listOf("wire.com")), FeatureFlagStatusDTO.ENABLED ), - FeatureConfigData.ConferenceCalling(FeatureFlagStatusDTO.ENABLED), + FeatureConfigData.ConferenceCalling(FeatureFlagStatusDTO.ENABLED, ConferenceCallingConfigDTO(false)), FeatureConfigData.ConversationGuestLinks(FeatureFlagStatusDTO.ENABLED), FeatureConfigData.DigitalSignatures(FeatureFlagStatusDTO.ENABLED), FeatureConfigData.FileSharing(FeatureFlagStatusDTO.ENABLED), diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigRepositoryTest.kt index 186bde600b6..2feed3b2365 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigRepositoryTest.kt @@ -25,6 +25,7 @@ import com.wire.kalium.logic.util.shouldFail import com.wire.kalium.logic.util.shouldSucceed import com.wire.kalium.network.api.base.authenticated.featureConfigs.AppLockConfigDTO import com.wire.kalium.network.api.base.authenticated.featureConfigs.ClassifiedDomainsConfigDTO +import com.wire.kalium.network.api.base.authenticated.featureConfigs.ConferenceCallingConfigDTO import com.wire.kalium.network.api.base.authenticated.featureConfigs.E2EIConfigDTO import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureConfigApi import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureConfigData @@ -60,7 +61,7 @@ class FeatureConfigRepositoryTest { ClassifiedDomainsConfigModel(listOf()), Status.ENABLED ), - ConferenceCallingModel(Status.ENABLED), + ConferenceCallingModel(Status.ENABLED, false), ConfigsStatusModel(Status.ENABLED), ConfigsStatusModel(Status.ENABLED), ConfigsStatusModel(Status.ENABLED), @@ -146,7 +147,7 @@ class FeatureConfigRepositoryTest { AppLockConfigDTO(true, 0), FeatureFlagStatusDTO.ENABLED ), FeatureConfigData.ClassifiedDomains(ClassifiedDomainsConfigDTO(listOf()), FeatureFlagStatusDTO.ENABLED), - FeatureConfigData.ConferenceCalling(FeatureFlagStatusDTO.ENABLED), + FeatureConfigData.ConferenceCalling(FeatureFlagStatusDTO.ENABLED, ConferenceCallingConfigDTO(false)), FeatureConfigData.ConversationGuestLinks(FeatureFlagStatusDTO.ENABLED), FeatureConfigData.DigitalSignatures(FeatureFlagStatusDTO.ENABLED), FeatureConfigData.FileSharing(FeatureFlagStatusDTO.ENABLED), diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigTest.kt index e1aadb92060..b70e743aea6 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/featureConfig/FeatureConfigTest.kt @@ -29,7 +29,7 @@ object FeatureConfigTest { ClassifiedDomainsConfigModel(listOf()), Status.ENABLED ), - conferenceCallingModel: ConferenceCallingModel = ConferenceCallingModel(Status.ENABLED), + conferenceCallingModel: ConferenceCallingModel = ConferenceCallingModel(Status.ENABLED, false), conversationGuestLinksModel: ConfigsStatusModel = ConfigsStatusModel(Status.ENABLED), digitalSignaturesModel: ConfigsStatusModel = ConfigsStatusModel(Status.ENABLED), fileSharingModel: ConfigsStatusModel = ConfigsStatusModel(Status.ENABLED), diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCaseTest.kt index 8a454cc0e1e..b9bf805f73f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCaseTest.kt @@ -118,7 +118,7 @@ class SyncFeatureConfigsUseCaseTest { fun givenConferenceCallingIsEnabled_whenSyncing_thenItShouldBeStoredAsEnabled() = runTest { val (arrangement, syncFeatureConfigsUseCase) = Arrangement() .withRemoteFeatureConfigsSucceeding( - FeatureConfigTest.newModel(conferenceCallingModel = ConferenceCallingModel(Status.ENABLED)) + FeatureConfigTest.newModel(conferenceCallingModel = ConferenceCallingModel(Status.ENABLED, false)) ) .withGetTeamSettingsSelfDeletionStatusSuccessful() .withGetSupportedProtocolsReturning(null) @@ -135,7 +135,7 @@ class SyncFeatureConfigsUseCaseTest { fun givenConferenceCallingIsDisasbled_whenSyncing_thenItShouldBeStoredAsDisabled() = runTest { val (arrangement, syncFeatureConfigsUseCase) = Arrangement() .withRemoteFeatureConfigsSucceeding( - FeatureConfigTest.newModel(conferenceCallingModel = ConferenceCallingModel(Status.DISABLED)) + FeatureConfigTest.newModel(conferenceCallingModel = ConferenceCallingModel(Status.DISABLED, false)) ) .withGetTeamSettingsSelfDeletionStatusSuccessful() .withGetSupportedProtocolsReturning(null) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandlerTest.kt new file mode 100644 index 00000000000..20732c9142f --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandlerTest.kt @@ -0,0 +1,154 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.feature.featureConfig.handler + +import com.wire.kalium.logic.StorageFailure +import com.wire.kalium.logic.configuration.UserConfigRepository +import com.wire.kalium.logic.data.featureConfig.ConferenceCallingModel +import com.wire.kalium.logic.data.featureConfig.Status +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.functional.isLeft +import com.wire.kalium.logic.functional.isRight +import io.mockative.Mock +import io.mockative.any +import io.mockative.anything +import io.mockative.eq +import io.mockative.given +import io.mockative.mock +import io.mockative.once +import io.mockative.verify +import kotlin.test.Test +import kotlin.test.assertTrue + +class ConferenceCallingConfigHandlerTest { + + @Test + fun givenUserConfigRepositoryFailureForConferenceCallingEnabled_whenHandlingTheEvent_ThenReturnFailure() { + val conferenceCallingModel = ConferenceCallingModel(Status.ENABLED, false) + val (arrangement, conferenceCallingConfigHandler) = Arrangement() + .withSetConferenceCallingEnabledFailure() + .arrange() + + val result = conferenceCallingConfigHandler.handle(conferenceCallingModel) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setConferenceCallingEnabled) + .with(eq(conferenceCallingModel.status.toBoolean())) + .wasInvoked(once) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setUseSFTForOneOnOneCalls) + .with(any()) + .wasNotInvoked() + + assertTrue { result.isLeft() } + } + + @Test + fun givenUserConfigRepositoryFailureForUseSFTForOneOnOneCalls_whenHandlingTheEvent_ThenReturnFailure() { + val conferenceCallingModel = ConferenceCallingModel(Status.ENABLED, false) + val (arrangement, conferenceCallingConfigHandler) = Arrangement() + .withSetConferenceCallingEnabledSuccess() + .withSetUseSFTForOneOnOneCallsFailure() + .arrange() + + val result = conferenceCallingConfigHandler.handle(conferenceCallingModel) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setConferenceCallingEnabled) + .with(eq(conferenceCallingModel.status.toBoolean())) + .wasInvoked(once) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setUseSFTForOneOnOneCalls) + .with(any()) + .wasInvoked(once) + + assertTrue { result.isLeft() } + } + + @Test + fun givenUserConfigRepositorySuccess_whenHandlingTheEvent_ThenReturnUnit() { + val conferenceCallingModel = ConferenceCallingModel(Status.ENABLED, false) + val (arrangement, conferenceCallingConfigHandler) = Arrangement() + .withSetConferenceCallingEnabledSuccess() + .withSetUseSFTForOneOnOneCallsSuccess() + .arrange() + + val result = conferenceCallingConfigHandler.handle(conferenceCallingModel) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setConferenceCallingEnabled) + .with(eq(conferenceCallingModel.status.toBoolean())) + .wasInvoked(once) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setUseSFTForOneOnOneCalls) + .with(any()) + .wasInvoked(once) + + assertTrue { result.isRight() } + } + + private class Arrangement { + + @Mock + val userConfigRepository: UserConfigRepository = mock(UserConfigRepository::class) + + fun arrange() = run { + this@Arrangement to ConferenceCallingConfigHandler( + userConfigRepository = userConfigRepository + ) + } + + init { + given(userConfigRepository) + .function(userConfigRepository::setAppLockStatus) + .whenInvokedWith(anything(), anything(), anything()) + .thenReturn(Either.Right(Unit)) + } + + fun withSetConferenceCallingEnabledFailure() = apply { + given(userConfigRepository) + .function(userConfigRepository::setConferenceCallingEnabled) + .whenInvokedWith(anything()) + .thenReturn(Either.Left(StorageFailure.DataNotFound)) + } + + fun withSetConferenceCallingEnabledSuccess() = apply { + given(userConfigRepository) + .function(userConfigRepository::setConferenceCallingEnabled) + .whenInvokedWith(anything()) + .thenReturn(Either.Right(Unit)) + } + + fun withSetUseSFTForOneOnOneCallsFailure() = apply { + given(userConfigRepository) + .function(userConfigRepository::setUseSFTForOneOnOneCalls) + .whenInvokedWith(anything()) + .thenReturn(Either.Left(StorageFailure.DataNotFound)) + } + + fun withSetUseSFTForOneOnOneCallsSuccess() = apply { + given(userConfigRepository) + .function(userConfigRepository::setUseSFTForOneOnOneCalls) + .whenInvokedWith(anything()) + .thenReturn(Either.Right(Unit)) + } + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/FeatureConfigEventReceiverTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/FeatureConfigEventReceiverTest.kt index 20af00316b6..948bc356d3a 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/FeatureConfigEventReceiverTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/FeatureConfigEventReceiverTest.kt @@ -45,6 +45,7 @@ import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.util.shouldSucceed import io.mockative.Mock import io.mockative.any +import io.mockative.anything import io.mockative.classOf import io.mockative.eq import io.mockative.given @@ -60,120 +61,169 @@ import kotlin.time.toDuration class FeatureConfigEventReceiverTest { @Test - fun givenFileSharingUpdatedEventWithStatusEnabled_whenProcessingEvent_ThenSetFileSharingStatusToTrue() = runTest { - val (arrangement, featureConfigEventReceiver) = Arrangement() - .withSettingFileSharingEnabledSuccessful() - .withIsFileSharingEnabled(Either.Right(FileSharingStatus(state = FileSharingStatus.Value.Disabled, isStatusChanged = false))) - .arrange() + fun givenFileSharingUpdatedEventWithStatusEnabled_whenProcessingEvent_ThenSetFileSharingStatusToTrue() = + runTest { + val (arrangement, featureConfigEventReceiver) = Arrangement() + .withSettingFileSharingEnabledSuccessful() + .withIsFileSharingEnabled( + Either.Right( + FileSharingStatus( + state = FileSharingStatus.Value.Disabled, + isStatusChanged = false + ) + ) + ) + .arrange() - featureConfigEventReceiver.onEvent( - event = arrangement.newFileSharingUpdatedEvent(ConfigsStatusModel(Status.ENABLED)), - deliveryInfo = TestEvent.liveDeliveryInfo - ) + featureConfigEventReceiver.onEvent( + event = arrangement.newFileSharingUpdatedEvent(ConfigsStatusModel(Status.ENABLED)), + deliveryInfo = TestEvent.liveDeliveryInfo + ) - verify(arrangement.userConfigRepository) - .function(arrangement.userConfigRepository::setFileSharingStatus) - .with(eq(true), eq(true)) - .wasInvoked(once) - } + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setFileSharingStatus) + .with(eq(true), eq(true)) + .wasInvoked(once) + } @Test - fun givenFileSharingUpdatedEventWithStatusDisabled_whenProcessingEvent_ThenSetFileSharingStatusToFalse() = runTest { - val (arrangement, featureConfigEventReceiver) = Arrangement() - .withIsFileSharingEnabled(Either.Right(FileSharingStatus(state = FileSharingStatus.Value.EnabledAll, isStatusChanged = false))) - .withSettingFileSharingEnabledSuccessful() - .arrange() + fun givenFileSharingUpdatedEventWithStatusDisabled_whenProcessingEvent_ThenSetFileSharingStatusToFalse() = + runTest { + val (arrangement, featureConfigEventReceiver) = Arrangement() + .withIsFileSharingEnabled( + Either.Right( + FileSharingStatus( + state = FileSharingStatus.Value.EnabledAll, + isStatusChanged = false + ) + ) + ) + .withSettingFileSharingEnabledSuccessful() + .arrange() - featureConfigEventReceiver.onEvent( - event = arrangement.newFileSharingUpdatedEvent(ConfigsStatusModel(Status.DISABLED)), - deliveryInfo = TestEvent.liveDeliveryInfo - ) + featureConfigEventReceiver.onEvent( + event = arrangement.newFileSharingUpdatedEvent(ConfigsStatusModel(Status.DISABLED)), + deliveryInfo = TestEvent.liveDeliveryInfo + ) - verify(arrangement.userConfigRepository) - .function(arrangement.userConfigRepository::setFileSharingStatus) - .with(eq(false), eq(true)) - .wasInvoked(once) - } + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setFileSharingStatus) + .with(eq(false), eq(true)) + .wasInvoked(once) + } @Test - fun givenFileSharingUpdatedEvent_whenTheNewValueIsSameAsTHeOneStored_ThenIsChangedIsSetToFalse() = runTest { - val (arrangement, featureConfigEventReceiver) = Arrangement() - .withIsFileSharingEnabled(Either.Right(FileSharingStatus(state = FileSharingStatus.Value.Disabled, isStatusChanged = false))) - .withSettingFileSharingEnabledSuccessful() - .arrange() + fun givenFileSharingUpdatedEvent_whenTheNewValueIsSameAsTHeOneStored_ThenIsChangedIsSetToFalse() = + runTest { + val (arrangement, featureConfigEventReceiver) = Arrangement() + .withIsFileSharingEnabled( + Either.Right( + FileSharingStatus( + state = FileSharingStatus.Value.Disabled, + isStatusChanged = false + ) + ) + ) + .withSettingFileSharingEnabledSuccessful() + .arrange() - featureConfigEventReceiver.onEvent( - event = arrangement.newFileSharingUpdatedEvent(ConfigsStatusModel(Status.DISABLED)), - deliveryInfo = TestEvent.liveDeliveryInfo - ) + featureConfigEventReceiver.onEvent( + event = arrangement.newFileSharingUpdatedEvent(ConfigsStatusModel(Status.DISABLED)), + deliveryInfo = TestEvent.liveDeliveryInfo + ) - verify(arrangement.userConfigRepository) - .function(arrangement.userConfigRepository::setFileSharingStatus) - .with(eq(false), eq(false)) - .wasInvoked(once) - } + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setFileSharingStatus) + .with(eq(false), eq(false)) + .wasInvoked(once) + } @Test - fun givenConferenceCallingUpdatedEventGrantingAccess_whenProcessingEvent_ThenSetConferenceCallingEnabledToTrue() = runTest { - val (arrangement, featureConfigEventReceiver) = Arrangement() - .withSettingConferenceCallingEnabledSuccessful() - .arrange() + fun givenConferenceCallingEventEnabled_whenProcessingEvent_ThenSetConferenceCallingEnabledToTrueAndSetShouldUseSFTFlag() = + runTest { + val (arrangement, featureConfigEventReceiver) = Arrangement() + .withSetUseSFTForOneOnOneCallsSuccessful() + .withSettingConferenceCallingEnabledSuccessful() + .arrange() - featureConfigEventReceiver.onEvent( - arrangement.newConferenceCallingUpdatedEvent(ConferenceCallingModel(Status.ENABLED)), - TestEvent.liveDeliveryInfo - ) + featureConfigEventReceiver.onEvent( + arrangement.newConferenceCallingUpdatedEvent( + ConferenceCallingModel( + Status.ENABLED, + false + ) + ), + TestEvent.liveDeliveryInfo + ) - verify(arrangement.userConfigRepository) - .function(arrangement.userConfigRepository::setConferenceCallingEnabled) - .with(eq(true)) - .wasInvoked(once) - } + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setConferenceCallingEnabled) + .with(eq(true)) + .wasInvoked(once) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setUseSFTForOneOnOneCalls) + .with(eq(false)) + .wasInvoked(once) + } @Test - fun givenConferenceCallingUpdatedEventGrantingAccess_whenProcessingEvent_ThenSetConferenceCallingEnabledToFalse() = runTest { - val (arrangement, featureConfigEventReceiver) = Arrangement() - .withSettingConferenceCallingEnabledSuccessful() - .arrange() + fun givenConferenceCallingEventDisabled_whenProcessingEvent_ThenSetConferenceCallingEnabledToFalseOnly() = + runTest { + val (arrangement, featureConfigEventReceiver) = Arrangement() + .withSetUseSFTForOneOnOneCallsSuccessful() + .withSettingConferenceCallingEnabledSuccessful() + .arrange() - featureConfigEventReceiver.onEvent( - event = arrangement.newConferenceCallingUpdatedEvent(ConferenceCallingModel(Status.DISABLED)), - deliveryInfo = TestEvent.liveDeliveryInfo - ) + featureConfigEventReceiver.onEvent( + event = arrangement.newConferenceCallingUpdatedEvent( + ConferenceCallingModel( + Status.DISABLED, + false + ) + ), + deliveryInfo = TestEvent.liveDeliveryInfo + ) - verify(arrangement.userConfigRepository) - .function(arrangement.userConfigRepository::setConferenceCallingEnabled) - .with(eq(false)) - .wasInvoked(once) - } + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setConferenceCallingEnabled) + .with(eq(false)) + .wasInvoked(once) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setUseSFTForOneOnOneCalls) + .with(eq(true)) + .wasNotInvoked() + } @Test - fun givenNewTeamSettingsSelfDeletingDisablingEvent_whenProcessingEvent_ThenDisableFeatureOnUserConfigRepository() = runTest { - val currentSelfDeletingMessagesStatus = TeamSettingsSelfDeletionStatus( - enforcedSelfDeletionTimer = TeamSelfDeleteTimer.Enabled, - hasFeatureChanged = false - ) - val newSelfDeletingEventModel = SelfDeletingMessagesModel( - SelfDeletingMessagesConfigModel( - enforcedTimeoutSeconds = null - ), Status.DISABLED - ) - val (arrangement, featureConfigEventReceiver) = Arrangement() - .withSelfDeletingMessages(currentSelfDeletingMessagesStatus) - .arrange() + fun givenNewTeamSettingsSelfDeletingDisablingEvent_whenProcessingEvent_ThenDisableFeatureOnUserConfigRepository() = + runTest { + val currentSelfDeletingMessagesStatus = TeamSettingsSelfDeletionStatus( + enforcedSelfDeletionTimer = TeamSelfDeleteTimer.Enabled, + hasFeatureChanged = false + ) + val newSelfDeletingEventModel = SelfDeletingMessagesModel( + SelfDeletingMessagesConfigModel( + enforcedTimeoutSeconds = null + ), Status.DISABLED + ) + val (arrangement, featureConfigEventReceiver) = Arrangement() + .withSelfDeletingMessages(currentSelfDeletingMessagesStatus) + .arrange() - featureConfigEventReceiver.onEvent( - arrangement.newSelfDeletingMessagesUpdatedEvent(newSelfDeletingEventModel), - TestEvent.liveDeliveryInfo - ) - - verify(arrangement.userConfigRepository) - .suspendFunction(arrangement.userConfigRepository::setTeamSettingsSelfDeletionStatus) - .with(matching { - it.hasFeatureChanged == true && it.enforcedSelfDeletionTimer is TeamSelfDeleteTimer.Disabled - }) - .wasInvoked(once) - } + featureConfigEventReceiver.onEvent( + arrangement.newSelfDeletingMessagesUpdatedEvent(newSelfDeletingEventModel), + TestEvent.liveDeliveryInfo + ) + + verify(arrangement.userConfigRepository) + .suspendFunction(arrangement.userConfigRepository::setTeamSettingsSelfDeletionStatus) + .with(matching { + it.hasFeatureChanged == true && it.enforcedSelfDeletionTimer is TeamSelfDeleteTimer.Disabled + }) + .wasInvoked(once) + } @Test fun givenNewSelfDeletingMessagesEnablingEventWithNoEnforcedTimeout_whenProcessingEvent_ThenEnableFeatureOnUserConfigRepository() = @@ -211,7 +261,8 @@ class FeatureConfigEventReceiverTest { hasFeatureChanged = false ) val newSelfDeletingEventModel = SelfDeletingMessagesModel( - SelfDeletingMessagesConfigModel(enforcedTimeoutSeconds = newEnforcedDuration.inWholeSeconds), Status.ENABLED + SelfDeletingMessagesConfigModel(enforcedTimeoutSeconds = newEnforcedDuration.inWholeSeconds), + Status.ENABLED ) val (arrangement, featureConfigEventReceiver) = Arrangement() .withSelfDeletingMessages(currentSelfDeletingMessagesStatus) @@ -236,7 +287,8 @@ class FeatureConfigEventReceiverTest { runTest { val newEnforcedTimeoutSeconds = 3600L val newSelfDeletingEventModel = SelfDeletingMessagesModel( - SelfDeletingMessagesConfigModel(enforcedTimeoutSeconds = newEnforcedTimeoutSeconds), Status.ENABLED + SelfDeletingMessagesConfigModel(enforcedTimeoutSeconds = newEnforcedTimeoutSeconds), + Status.ENABLED ) val (arrangement, featureConfigEventReceiver) = Arrangement() .withStoredTeamSettingsSelfDeletionStatusError() @@ -260,7 +312,8 @@ class FeatureConfigEventReceiverTest { runTest { val newEnforcedTimeoutSeconds = 0L val newSelfDeletingEventModel = SelfDeletingMessagesModel( - SelfDeletingMessagesConfigModel(enforcedTimeoutSeconds = newEnforcedTimeoutSeconds), Status.DISABLED + SelfDeletingMessagesConfigModel(enforcedTimeoutSeconds = newEnforcedTimeoutSeconds), + Status.DISABLED ) val (arrangement, featureConfigEventReceiver) = Arrangement() .withStoredTeamSettingsSelfDeletionStatusError() @@ -280,27 +333,29 @@ class FeatureConfigEventReceiverTest { } @Test - fun givenSelfDeletingFlagDisabledInKaliumConfigs_whenProcessingEnablingEvent_ThenItDisablesFeatureOnUserConfigRepository() = runTest { - val newEnforcedTimeoutSeconds = 0L - val newSelfDeletingEventModel = SelfDeletingMessagesModel( - SelfDeletingMessagesConfigModel(enforcedTimeoutSeconds = newEnforcedTimeoutSeconds), Status.ENABLED - ) - val (arrangement, featureConfigEventReceiver) = Arrangement() - .withDisabledKaliumConfigFlag() - .arrange() + fun givenSelfDeletingFlagDisabledInKaliumConfigs_whenProcessingEnablingEvent_ThenItDisablesFeatureOnUserConfigRepository() = + runTest { + val newEnforcedTimeoutSeconds = 0L + val newSelfDeletingEventModel = SelfDeletingMessagesModel( + SelfDeletingMessagesConfigModel(enforcedTimeoutSeconds = newEnforcedTimeoutSeconds), + Status.ENABLED + ) + val (arrangement, featureConfigEventReceiver) = Arrangement() + .withDisabledKaliumConfigFlag() + .arrange() - featureConfigEventReceiver.onEvent( - event = arrangement.newSelfDeletingMessagesUpdatedEvent(newSelfDeletingEventModel), - deliveryInfo = TestEvent.liveDeliveryInfo - ) - - verify(arrangement.userConfigRepository) - .function(arrangement.userConfigRepository::setTeamSettingsSelfDeletionStatus) - .with(matching { - it.hasFeatureChanged == null && it.enforcedSelfDeletionTimer is TeamSelfDeleteTimer.Disabled - }) - .wasInvoked(once) - } + featureConfigEventReceiver.onEvent( + event = arrangement.newSelfDeletingMessagesUpdatedEvent(newSelfDeletingEventModel), + deliveryInfo = TestEvent.liveDeliveryInfo + ) + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setTeamSettingsSelfDeletionStatus) + .with(matching { + it.hasFeatureChanged == null && it.enforcedSelfDeletionTimer is TeamSelfDeleteTimer.Disabled + }) + .wasInvoked(once) + } @Test fun givenUnknownFeatureConfig_whenPrecessing_thenReturnSuccess() = runTest { @@ -319,14 +374,18 @@ class FeatureConfigEventReceiverTest { val userConfigRepository = mock(classOf()) @Mock - val updateSupportedProtocolsAndResolveOneOnOnes = mock(classOf()) + val updateSupportedProtocolsAndResolveOneOnOnes = + mock(classOf()) private val featureConfigEventReceiver: FeatureConfigEventReceiver by lazy { FeatureConfigEventReceiverImpl( GuestRoomConfigHandler(userConfigRepository, kaliumConfigs), FileSharingConfigHandler(userConfigRepository), MLSConfigHandler(userConfigRepository, updateSupportedProtocolsAndResolveOneOnOnes), - MLSMigrationConfigHandler(userConfigRepository, updateSupportedProtocolsAndResolveOneOnOnes), + MLSMigrationConfigHandler( + userConfigRepository, + updateSupportedProtocolsAndResolveOneOnOnes + ), ClassifiedDomainsConfigHandler(userConfigRepository), ConferenceCallingConfigHandler(userConfigRepository), SelfDeletingMessagesConfigHandler(userConfigRepository, kaliumConfigs), @@ -349,6 +408,13 @@ class FeatureConfigEventReceiverTest { .thenReturn(Either.Right(Unit)) } + fun withSetUseSFTForOneOnOneCallsSuccessful() = apply { + given(userConfigRepository) + .function(userConfigRepository::setUseSFTForOneOnOneCalls) + .whenInvokedWith(anything()) + .thenReturn(Either.Right(Unit)) + } + fun withIsFileSharingEnabled(result: Either) = apply { given(userConfigRepository) .function(userConfigRepository::isFileSharingEnabled) @@ -356,16 +422,17 @@ class FeatureConfigEventReceiverTest { .thenReturn(result) } - fun withSelfDeletingMessages(currentSelfDeletingMessagesStatus: TeamSettingsSelfDeletionStatus) = apply { - given(userConfigRepository) - .suspendFunction(userConfigRepository::getTeamSettingsSelfDeletionStatus) - .whenInvoked() - .thenReturn(Either.Right(currentSelfDeletingMessagesStatus)) - given(userConfigRepository) - .suspendFunction(userConfigRepository::setTeamSettingsSelfDeletionStatus) - .whenInvokedWith(any()) - .thenReturn(Either.Right(Unit)) - } + fun withSelfDeletingMessages(currentSelfDeletingMessagesStatus: TeamSettingsSelfDeletionStatus) = + apply { + given(userConfigRepository) + .suspendFunction(userConfigRepository::getTeamSettingsSelfDeletionStatus) + .whenInvoked() + .thenReturn(Either.Right(currentSelfDeletingMessagesStatus)) + given(userConfigRepository) + .suspendFunction(userConfigRepository::setTeamSettingsSelfDeletionStatus) + .whenInvokedWith(any()) + .thenReturn(Either.Right(Unit)) + } fun withStoredTeamSettingsSelfDeletionStatusError() = apply { given(userConfigRepository) diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/featureConfigs/FeatureConfigResponse.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/featureConfigs/FeatureConfigResponse.kt index d11c6feba54..0f211ab1d21 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/featureConfigs/FeatureConfigResponse.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/featureConfigs/FeatureConfigResponse.kt @@ -68,6 +68,12 @@ enum class FeatureFlagStatusDTO { DISABLED; } +@Serializable +data class ConferenceCallingConfigDTO( + @SerialName("useSFTForOneToOneCalls") + val useSFTForOneToOneCalls: Boolean +) + @Serializable data class AppLockConfigDTO( @SerialName("enforceAppLock") @@ -155,7 +161,9 @@ sealed class FeatureConfigData { @Serializable data class ConferenceCalling( @SerialName("status") - val status: FeatureFlagStatusDTO + val status: FeatureFlagStatusDTO, + @SerialName("config") + val config: ConferenceCallingConfigDTO ) : FeatureConfigData() @SerialName("conversationGuestLinks") diff --git a/network/src/commonTest/kotlin/com/wire/kalium/model/FeatureConfigJson.kt b/network/src/commonTest/kotlin/com/wire/kalium/model/FeatureConfigJson.kt index 8791d27512d..2a75d3434ee 100644 --- a/network/src/commonTest/kotlin/com/wire/kalium/model/FeatureConfigJson.kt +++ b/network/src/commonTest/kotlin/com/wire/kalium/model/FeatureConfigJson.kt @@ -19,9 +19,9 @@ package com.wire.kalium.model import com.wire.kalium.api.json.ValidJsonProvider -import com.wire.kalium.network.api.base.authenticated.conversation.ConvProtocol import com.wire.kalium.network.api.base.authenticated.featureConfigs.AppLockConfigDTO import com.wire.kalium.network.api.base.authenticated.featureConfigs.ClassifiedDomainsConfigDTO +import com.wire.kalium.network.api.base.authenticated.featureConfigs.ConferenceCallingConfigDTO import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureConfigData import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureConfigData.AppLock import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureConfigData.ClassifiedDomains @@ -66,6 +66,9 @@ object FeatureConfigJson { | "status": "enabled" | }, | "conferenceCalling": { + | "config": { + | "useSFTForOneToOneCalls": false + | }, | "status": "enabled" | }, | "conversationGuestLinks": { @@ -118,7 +121,7 @@ object FeatureConfigJson { AppLockConfigDTO(true, 0), FeatureFlagStatusDTO.ENABLED ), ClassifiedDomains(ClassifiedDomainsConfigDTO(listOf()), FeatureFlagStatusDTO.ENABLED), - ConferenceCalling(FeatureFlagStatusDTO.ENABLED), + ConferenceCalling(FeatureFlagStatusDTO.ENABLED, ConferenceCallingConfigDTO(false)), ConversationGuestLinks(FeatureFlagStatusDTO.ENABLED), DigitalSignatures(FeatureFlagStatusDTO.ENABLED), FileSharing(FeatureFlagStatusDTO.ENABLED), diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorage.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorage.kt index 57c8866264b..b3542fb10f7 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorage.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorage.kt @@ -145,6 +145,10 @@ interface UserConfigStorage { */ fun isConferenceCallingEnabled(): Boolean + fun persistUseSftForOneOnOneCalls(shouldUse: Boolean) + + fun shouldUseSftForOneOnOneCalls(): Boolean + /** * Get the saved flag to know whether user's Read Receipts are currently enabled or not */ @@ -503,6 +507,16 @@ class UserConfigStorageImpl( DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE ) + override fun persistUseSftForOneOnOneCalls(shouldUse: Boolean) { + kaliumPreferences.putBoolean(USE_SFT_FOR_ONE_ON_ONE_CALLS, shouldUse) + } + + override fun shouldUseSftForOneOnOneCalls(): Boolean = + kaliumPreferences.getBoolean( + USE_SFT_FOR_ONE_ON_ONE_CALLS, + DEFAULT_USE_SFT_FOR_ONE_ON_ONE_CALLS_VALUE + ) + override fun areReadReceiptsEnabled(): Flow = areReadReceiptsEnabledFlow .map { kaliumPreferences.getBoolean(ENABLE_READ_RECEIPTS, true) } .onStart { emit(kaliumPreferences.getBoolean(ENABLE_READ_RECEIPTS, true)) } @@ -577,8 +591,10 @@ class UserConfigStorageImpl( const val E2EI_SETTINGS = "end_to_end_identity_settings" const val E2EI_NOTIFICATION_TIME = "end_to_end_identity_notification_time" const val ENABLE_CONFERENCE_CALLING = "enable_conference_calling" + const val USE_SFT_FOR_ONE_ON_ONE_CALLS = "use_sft_for_one_on_one_calls" const val ENABLE_READ_RECEIPTS = "enable_read_receipts" const val DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE = false + const val DEFAULT_USE_SFT_FOR_ONE_ON_ONE_CALLS_VALUE = false const val REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE = "require_second_factor_password_challenge" const val ENABLE_SCREENSHOT_CENSORING = "enable_screenshot_censoring"