mirror of
https://github.com/bitwarden/android.git
synced 2026-03-11 20:54:58 -05:00
PM-29824: Add bulk share ciphers network layer implementation (#6271)
This commit is contained in:
@@ -2,7 +2,9 @@ package com.bitwarden.network.api
|
||||
|
||||
import com.bitwarden.network.model.AttachmentJsonRequest
|
||||
import com.bitwarden.network.model.AttachmentJsonResponse
|
||||
import com.bitwarden.network.model.BulkShareCiphersJsonRequest
|
||||
import com.bitwarden.network.model.CipherJsonRequest
|
||||
import com.bitwarden.network.model.CipherMiniResponseJson
|
||||
import com.bitwarden.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.bitwarden.network.model.ImportCiphersJsonRequest
|
||||
import com.bitwarden.network.model.NetworkResult
|
||||
@@ -75,6 +77,14 @@ internal interface CiphersApi {
|
||||
@Body body: ShareCipherJsonRequest,
|
||||
): NetworkResult<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Shares multiple ciphers in bulk.
|
||||
*/
|
||||
@PUT("ciphers/share")
|
||||
suspend fun bulkShareCiphers(
|
||||
@Body body: BulkShareCiphersJsonRequest,
|
||||
): NetworkResult<List<CipherMiniResponseJson>>
|
||||
|
||||
/**
|
||||
* Shares an attachment.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.bitwarden.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a bulk share ciphers request.
|
||||
*
|
||||
* @property ciphers The list of ciphers to share.
|
||||
* @property collectionIds A list of collection IDs to associate with all ciphers.
|
||||
*/
|
||||
@Serializable
|
||||
data class BulkShareCiphersJsonRequest(
|
||||
@SerialName("Ciphers")
|
||||
val ciphers: List<CipherJsonRequest>,
|
||||
|
||||
@SerialName("CollectionIds")
|
||||
val collectionIds: List<String>,
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.bitwarden.network.model
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Represents a minimal cipher response from the API, typically returned from bulk operations.
|
||||
* Contains core cipher metadata without detailed type-specific fields.
|
||||
*
|
||||
* @property id The ID of the cipher.
|
||||
* @property organizationId The organization ID (nullable).
|
||||
* @property type The type of cipher.
|
||||
* @property data Serialized cipher data (newer API format).
|
||||
* @property attachments List of attachments (nullable).
|
||||
* @property shouldOrganizationUseTotp If the organization should use TOTP.
|
||||
* @property revisionDate The revision date.
|
||||
* @property creationDate The creation date.
|
||||
* @property deletedDate The deleted date (nullable).
|
||||
* @property reprompt The reprompt type.
|
||||
* @property key The cipher key (nullable).
|
||||
* @property archivedDate The archived date (nullable).
|
||||
*/
|
||||
@Serializable
|
||||
data class CipherMiniResponseJson(
|
||||
@SerialName("id")
|
||||
val id: String,
|
||||
|
||||
@SerialName("organizationId")
|
||||
val organizationId: String?,
|
||||
|
||||
@SerialName("type")
|
||||
val type: CipherTypeJson,
|
||||
|
||||
@SerialName("data")
|
||||
val data: String?,
|
||||
|
||||
@SerialName("attachments")
|
||||
val attachments: List<SyncResponseJson.Cipher.Attachment>?,
|
||||
|
||||
@SerialName("organizationUseTotp")
|
||||
val shouldOrganizationUseTotp: Boolean,
|
||||
|
||||
@SerialName("revisionDate")
|
||||
@Contextual
|
||||
val revisionDate: ZonedDateTime,
|
||||
|
||||
@SerialName("creationDate")
|
||||
@Contextual
|
||||
val creationDate: ZonedDateTime,
|
||||
|
||||
@SerialName("deletedDate")
|
||||
@Contextual
|
||||
val deletedDate: ZonedDateTime?,
|
||||
|
||||
@SerialName("reprompt")
|
||||
val reprompt: CipherRepromptTypeJson,
|
||||
|
||||
@SerialName("key")
|
||||
val key: String?,
|
||||
|
||||
@SerialName("archivedDate")
|
||||
@Contextual
|
||||
val archivedDate: ZonedDateTime?,
|
||||
)
|
||||
@@ -3,7 +3,9 @@ package com.bitwarden.network.service
|
||||
import com.bitwarden.network.model.AttachmentInfo
|
||||
import com.bitwarden.network.model.AttachmentJsonRequest
|
||||
import com.bitwarden.network.model.AttachmentJsonResponse
|
||||
import com.bitwarden.network.model.BulkShareCiphersJsonRequest
|
||||
import com.bitwarden.network.model.CipherJsonRequest
|
||||
import com.bitwarden.network.model.CipherMiniResponseJson
|
||||
import com.bitwarden.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.bitwarden.network.model.CreateCipherResponseJson
|
||||
import com.bitwarden.network.model.ImportCiphersJsonRequest
|
||||
@@ -63,6 +65,13 @@ interface CiphersService {
|
||||
body: ShareCipherJsonRequest,
|
||||
): Result<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Attempt to share multiple ciphers in bulk.
|
||||
*/
|
||||
suspend fun bulkShareCiphers(
|
||||
body: BulkShareCiphersJsonRequest,
|
||||
): Result<List<CipherMiniResponseJson>>
|
||||
|
||||
/**
|
||||
* Attempt to share an attachment.
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,9 @@ import com.bitwarden.network.api.CiphersApi
|
||||
import com.bitwarden.network.model.AttachmentInfo
|
||||
import com.bitwarden.network.model.AttachmentJsonRequest
|
||||
import com.bitwarden.network.model.AttachmentJsonResponse
|
||||
import com.bitwarden.network.model.BulkShareCiphersJsonRequest
|
||||
import com.bitwarden.network.model.CipherJsonRequest
|
||||
import com.bitwarden.network.model.CipherMiniResponseJson
|
||||
import com.bitwarden.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.bitwarden.network.model.CreateCipherResponseJson
|
||||
import com.bitwarden.network.model.FileUploadType
|
||||
@@ -185,6 +187,13 @@ internal class CiphersServiceImpl(
|
||||
)
|
||||
.toResult()
|
||||
|
||||
override suspend fun bulkShareCiphers(
|
||||
body: BulkShareCiphersJsonRequest,
|
||||
): Result<List<CipherMiniResponseJson>> =
|
||||
ciphersApi
|
||||
.bulkShareCiphers(body = body)
|
||||
.toResult()
|
||||
|
||||
override suspend fun updateCipherCollections(
|
||||
cipherId: String,
|
||||
body: UpdateCipherCollectionsJsonRequest,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.bitwarden.network.model
|
||||
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Create a mock [CipherMiniResponseJson] for testing.
|
||||
*/
|
||||
fun createMockCipherMiniResponse(
|
||||
number: Int,
|
||||
): CipherMiniResponseJson = CipherMiniResponseJson(
|
||||
id = "mockId-$number",
|
||||
organizationId = "mockOrgId-$number",
|
||||
type = CipherTypeJson.LOGIN,
|
||||
data = "mockData-$number",
|
||||
attachments = null,
|
||||
shouldOrganizationUseTotp = false,
|
||||
revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"),
|
||||
creationDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"),
|
||||
deletedDate = null,
|
||||
reprompt = CipherRepromptTypeJson.NONE,
|
||||
key = "mockKey-$number",
|
||||
archivedDate = null,
|
||||
)
|
||||
@@ -5,6 +5,8 @@ import com.bitwarden.network.api.AzureApi
|
||||
import com.bitwarden.network.api.CiphersApi
|
||||
import com.bitwarden.network.base.BaseServiceTest
|
||||
import com.bitwarden.network.model.AttachmentJsonResponse
|
||||
import com.bitwarden.network.model.BulkShareCiphersJsonRequest
|
||||
import com.bitwarden.network.model.CipherMiniResponseJson
|
||||
import com.bitwarden.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.bitwarden.network.model.CreateCipherResponseJson
|
||||
import com.bitwarden.network.model.FileUploadType
|
||||
@@ -19,11 +21,13 @@ import com.bitwarden.network.model.createMockAttachmentJsonRequest
|
||||
import com.bitwarden.network.model.createMockAttachmentResponse
|
||||
import com.bitwarden.network.model.createMockCipher
|
||||
import com.bitwarden.network.model.createMockCipherJsonRequest
|
||||
import com.bitwarden.network.model.createMockCipherMiniResponse
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.encodeToString
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@@ -289,6 +293,49 @@ class CiphersServiceTest : BaseServiceTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bulkShareCiphers with success response should return Success`() = runTest {
|
||||
val expectedCiphers = listOf(
|
||||
createMockCipherMiniResponse(number = 1),
|
||||
createMockCipherMiniResponse(number = 2),
|
||||
)
|
||||
server.enqueue(
|
||||
MockResponse()
|
||||
.setResponseCode(200)
|
||||
.setBody(json.encodeToString<List<CipherMiniResponseJson>>(expectedCiphers)),
|
||||
)
|
||||
|
||||
val result = ciphersService.bulkShareCiphers(
|
||||
body = BulkShareCiphersJsonRequest(
|
||||
ciphers = listOf(
|
||||
createMockCipherJsonRequest(number = 1),
|
||||
createMockCipherJsonRequest(number = 2),
|
||||
),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedCiphers, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bulkShareCiphers with failure response should return Failure`() = runTest {
|
||||
server.enqueue(
|
||||
MockResponse()
|
||||
.setResponseCode(500)
|
||||
.setBody("""{"message":"Server error"}"""),
|
||||
)
|
||||
|
||||
val result = ciphersService.bulkShareCiphers(
|
||||
body = BulkShareCiphersJsonRequest(
|
||||
ciphers = listOf(createMockCipherJsonRequest(number = 1)),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
),
|
||||
)
|
||||
|
||||
assertTrue(result.isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateCipherCollections should execute the updateCipherCollections API`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(200))
|
||||
|
||||
Reference in New Issue
Block a user