[PM-28468] Added service methods to migration to MyItems validation (#6248)

This commit is contained in:
aj-rosado 2025-12-09 15:58:23 +00:00 committed by GitHub
parent cd27fe339d
commit 4a874668f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 322 additions and 0 deletions

View File

@ -25,4 +25,10 @@ interface PolicyManager {
userId: String, userId: String,
type: PolicyTypeJson, type: PolicyTypeJson,
): List<SyncResponseJson.Policy> ): List<SyncResponseJson.Policy>
/**
* Get the organization id of the personal ownership policy.
* If multiple organizations enforce the policy, return the first to set it.
*/
fun getPersonalOwnershipPolicyOrganizationId(): String?
} }

View File

@ -66,6 +66,13 @@ class PolicyManagerImpl(
) )
.orEmpty() .orEmpty()
override fun getPersonalOwnershipPolicyOrganizationId(): String? =
this
.getActivePolicies(PolicyTypeJson.PERSONAL_OWNERSHIP)
.sortedBy { it.revisionDate }
.firstOrNull()
?.organizationId
/** /**
* A helper method to filter policies. * A helper method to filter policies.
*/ */

View File

@ -166,4 +166,11 @@ interface VaultRepository :
* `null` if the item cannot be found. * `null` if the item cannot be found.
*/ */
fun getVaultListItemStateFlow(itemId: String): StateFlow<DataState<CipherListView?>> fun getVaultListItemStateFlow(itemId: String): StateFlow<DataState<CipherListView?>>
/**
* Checks if there are any personal vault items (items without an organization ID) in the vault.
*
* @return `true` if there are personal vault items, `false` otherwise.
*/
fun hasPersonalVaultItems(): Boolean
} }

View File

@ -550,4 +550,9 @@ class VaultRepositoryImpl(
organizationKeys = organizationKeys, organizationKeys = organizationKeys,
) )
} }
override fun hasPersonalVaultItems(): Boolean {
val vaultData = vaultSyncManager.vaultDataStateFlow.value.data ?: return false
return vaultData.decryptCipherListResult.successes.any { it.organizationId.isNullOrEmpty() }
}
} }

View File

@ -13,9 +13,11 @@ import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.time.ZonedDateTime
class PolicyManagerTest { class PolicyManagerTest {
private val mutableUserStateFlow = MutableStateFlow<UserStateJson?>(null) private val mutableUserStateFlow = MutableStateFlow<UserStateJson?>(null)
@ -277,6 +279,207 @@ class PolicyManagerTest {
), ),
) )
} }
@Test
fun `getPersonalOwnershipPolicyOrganizationId returns null when no active user`() {
every { authDiskSource.userState } returns null
assertNull(policyManager.getPersonalOwnershipPolicyOrganizationId())
}
@Test
fun `getPersonalOwnershipPolicyOrganizationId returns null when no policies exist`() {
val userState: UserStateJson = mockk {
every { activeUserId } returns USER_ID
}
every { authDiskSource.userState } returns userState
every { authDiskSource.getOrganizations(USER_ID) } returns emptyList()
every { authDiskSource.getPolicies(USER_ID) } returns emptyList()
assertNull(policyManager.getPersonalOwnershipPolicyOrganizationId())
}
@Test
fun `getPersonalOwnershipPolicyOrganizationId returns null when policy is disabled`() {
val userState: UserStateJson = mockk {
every { activeUserId } returns USER_ID
}
every { authDiskSource.userState } returns userState
every {
authDiskSource.getOrganizations(USER_ID)
} returns listOf(
createMockOrganization(
number = 1,
isEnabled = true,
shouldUsePolicies = true,
type = OrganizationType.USER,
),
)
every {
authDiskSource.getPolicies(USER_ID)
} returns listOf(
createMockPolicy(
organizationId = "mockId-1",
isEnabled = false,
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
),
)
assertNull(policyManager.getPersonalOwnershipPolicyOrganizationId())
}
@Test
fun `getPersonalOwnershipPolicyOrganizationId returns organization id for single policy`() {
val userState: UserStateJson = mockk {
every { activeUserId } returns USER_ID
}
val expectedOrganizationId = "mockId-1"
every { authDiskSource.userState } returns userState
every {
authDiskSource.getOrganizations(USER_ID)
} returns listOf(
createMockOrganization(
number = 1,
isEnabled = true,
shouldUsePolicies = true,
type = OrganizationType.USER,
),
)
every {
authDiskSource.getPolicies(USER_ID)
} returns listOf(
createMockPolicy(
organizationId = expectedOrganizationId,
isEnabled = true,
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
),
)
assertEquals(
expectedOrganizationId,
policyManager.getPersonalOwnershipPolicyOrganizationId(),
)
}
@Suppress("MaxLineLength")
@Test
fun `getPersonalOwnershipPolicyOrganizationId returns earliest policy when multiple orgs have policy`() {
val userState: UserStateJson = mockk {
every { activeUserId } returns USER_ID
}
val earliestRevisionDate = ZonedDateTime.parse("2024-01-01T00:00:00Z")
val middleRevisionDate = ZonedDateTime.parse("2024-06-01T00:00:00Z")
val latestRevisionDate = ZonedDateTime.parse("2024-12-01T00:00:00Z")
val expectedOrganizationId = "mockId-1"
every { authDiskSource.userState } returns userState
every {
authDiskSource.getOrganizations(USER_ID)
} returns listOf(
createMockOrganization(
number = 1,
isEnabled = true,
shouldUsePolicies = true,
type = OrganizationType.USER,
),
createMockOrganization(
number = 2,
isEnabled = true,
shouldUsePolicies = true,
type = OrganizationType.USER,
),
createMockOrganization(
number = 3,
isEnabled = true,
shouldUsePolicies = true,
type = OrganizationType.USER,
),
)
every {
authDiskSource.getPolicies(USER_ID)
} returns listOf(
createMockPolicy(
number = 3,
organizationId = "mockId-3",
isEnabled = true,
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
revisionDate = latestRevisionDate,
),
createMockPolicy(
number = 1,
organizationId = expectedOrganizationId,
isEnabled = true,
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
revisionDate = earliestRevisionDate,
),
createMockPolicy(
number = 2,
organizationId = "mockId-2",
isEnabled = true,
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
revisionDate = middleRevisionDate,
),
)
assertEquals(
expectedOrganizationId,
policyManager.getPersonalOwnershipPolicyOrganizationId(),
)
}
@Suppress("MaxLineLength")
@Test
fun `getPersonalOwnershipPolicyOrganizationId filters out policies from organizations not using policies`() {
val userState: UserStateJson = mockk {
every { activeUserId } returns USER_ID
}
val earlierRevisionDate = ZonedDateTime.parse("2024-01-01T00:00:00Z")
val laterRevisionDate = ZonedDateTime.parse("2024-06-01T00:00:00Z")
val expectedOrganizationId = "mockId-2"
every { authDiskSource.userState } returns userState
every {
authDiskSource.getOrganizations(USER_ID)
} returns listOf(
createMockOrganization(
number = 1,
isEnabled = true,
shouldUsePolicies = false, // This org does NOT use policies
type = OrganizationType.USER,
),
createMockOrganization(
number = 2,
isEnabled = true,
shouldUsePolicies = true, // This org uses policies
type = OrganizationType.USER,
),
)
every {
authDiskSource.getPolicies(USER_ID)
} returns listOf(
createMockPolicy(
number = 1,
organizationId = "mockId-1",
isEnabled = true,
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
revisionDate = earlierRevisionDate, // Earlier but org doesn't enforce
),
createMockPolicy(
number = 2,
organizationId = expectedOrganizationId,
isEnabled = true,
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
revisionDate = laterRevisionDate,
),
)
// Should return mockId-2 because mockId-1's organization doesn't enforce policies
assertEquals(
expectedOrganizationId,
policyManager.getPersonalOwnershipPolicyOrganizationId(),
)
}
} }
private const val USER_ID = "userId" private const val USER_ID = "userId"

View File

@ -1390,6 +1390,100 @@ class VaultRepositoryTest {
} }
} }
@Test
fun `hasPersonalVaultItems returns false when vault data is loading`() {
mutableVaultDataStateFlow.value = DataState.Loading
val result = vaultRepository.hasPersonalVaultItems()
assertEquals(false, result)
}
@Test
fun `hasPersonalVaultItems returns false when all items belong to organizations`() {
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
decryptCipherListResult = DecryptCipherListResult(
successes = listOf(
createMockCipherListView(number = 1, organizationId = "org-1"),
createMockCipherListView(number = 2, organizationId = "org-2"),
),
failures = emptyList(),
),
collectionViewList = emptyList(),
folderViewList = emptyList(),
sendViewList = emptyList(),
),
)
val result = vaultRepository.hasPersonalVaultItems()
assertEquals(false, result)
}
@Test
fun `hasPersonalVaultItems returns true when there are items without organization ID`() {
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
decryptCipherListResult = DecryptCipherListResult(
successes = listOf(
createMockCipherListView(number = 1, organizationId = null),
createMockCipherListView(number = 2, organizationId = "org-2"),
),
failures = emptyList(),
),
collectionViewList = emptyList(),
folderViewList = emptyList(),
sendViewList = emptyList(),
),
)
val result = vaultRepository.hasPersonalVaultItems()
assertEquals(true, result)
}
@Test
fun `hasPersonalVaultItems returns true when there are items with empty organization ID`() {
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
decryptCipherListResult = DecryptCipherListResult(
successes = listOf(
createMockCipherListView(number = 1, organizationId = ""),
createMockCipherListView(number = 2, organizationId = "org-2"),
),
failures = emptyList(),
),
collectionViewList = emptyList(),
folderViewList = emptyList(),
sendViewList = emptyList(),
),
)
val result = vaultRepository.hasPersonalVaultItems()
assertEquals(true, result)
}
@Test
fun `hasPersonalVaultItems returns false when successes list is empty`() {
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
decryptCipherListResult = DecryptCipherListResult(
successes = emptyList(),
failures = emptyList(),
),
collectionViewList = emptyList(),
folderViewList = emptyList(),
sendViewList = emptyList(),
),
)
val result = vaultRepository.hasPersonalVaultItems()
assertEquals(false, result)
}
//region Helper functions //region Helper functions
/** /**