mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Add underlyng SendsService to make sends API requests. (#511)
This commit is contained in:
parent
3200f44611
commit
6ef7be296e
8 changed files with 353 additions and 0 deletions
|
@ -0,0 +1,36 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /send API with authentication applied.
|
||||
*/
|
||||
interface SendsApi {
|
||||
|
||||
/**
|
||||
* Create a send.
|
||||
*/
|
||||
@POST("sends")
|
||||
suspend fun createSend(@Body body: SendJsonRequest): Result<SyncResponseJson.Send>
|
||||
|
||||
/**
|
||||
* Updates a send.
|
||||
*/
|
||||
@PUT("sends/{sendId}")
|
||||
suspend fun updateSend(
|
||||
@Path("sendId") sendId: String,
|
||||
@Body body: SendJsonRequest,
|
||||
): Result<SyncResponseJson.Send>
|
||||
|
||||
/**
|
||||
* Deletes a send.
|
||||
*/
|
||||
@DELETE("sends/{sendId}")
|
||||
suspend fun deleteSend(@Path("sendId") sendId: String): Result<Unit>
|
||||
}
|
|
@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.vault.datasource.network.di
|
|||
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersServiceImpl
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsServiceImpl
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncServiceImpl
|
||||
import dagger.Module
|
||||
|
@ -30,6 +32,16 @@ object VaultNetworkModule {
|
|||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendsService(
|
||||
retrofits: Retrofits,
|
||||
json: Json,
|
||||
): SendsService = SendsServiceImpl(
|
||||
sendsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSyncService(
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Represents a send request.
|
||||
*
|
||||
* @property type The type of send.
|
||||
* @property name The name of the send (nullable).
|
||||
* @property notes The notes of the send (nullable).
|
||||
* @property key The send key.
|
||||
* @property maxAccessCount The maximum number of people who can access this send (nullable).
|
||||
* @property expirationDate The date in which the send will expire (nullable).
|
||||
* @property deletionDate The date in which the send will be deleted.
|
||||
* @property file The file associated with this send (nullable).
|
||||
* @property text The text associated with this send (nullable).
|
||||
* @property password The password protecting this send (nullable).
|
||||
* @property isDisabled Indicate if this send is disabled.
|
||||
* @property shouldHideEmail Should the email address of the sender be hidden (nullable).
|
||||
*/
|
||||
@Serializable
|
||||
data class SendJsonRequest(
|
||||
@SerialName("type")
|
||||
val type: SendTypeJson,
|
||||
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
@SerialName("notes")
|
||||
val notes: String?,
|
||||
|
||||
@SerialName("key")
|
||||
val key: String,
|
||||
|
||||
@SerialName("maxAccessCount")
|
||||
val maxAccessCount: Int?,
|
||||
|
||||
@SerialName("expirationDate")
|
||||
@Contextual
|
||||
val expirationDate: ZonedDateTime?,
|
||||
|
||||
@SerialName("deletionDate")
|
||||
@Contextual
|
||||
val deletionDate: ZonedDateTime,
|
||||
|
||||
@SerialName("file")
|
||||
val file: SyncResponseJson.Send.File?,
|
||||
|
||||
@SerialName("text")
|
||||
val text: SyncResponseJson.Send.Text?,
|
||||
|
||||
@SerialName("password")
|
||||
val password: String?,
|
||||
|
||||
@SerialName("disabled")
|
||||
val isDisabled: Boolean,
|
||||
|
||||
@SerialName("hideEmail")
|
||||
val shouldHideEmail: Boolean?,
|
||||
)
|
|
@ -0,0 +1,33 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Models the response from the update send request.
|
||||
*/
|
||||
sealed class UpdateSendResponseJson {
|
||||
/**
|
||||
* The request completed successfully and returned the updated [send].
|
||||
*/
|
||||
data class Success(
|
||||
val send: SyncResponseJson.Send,
|
||||
) : UpdateSendResponseJson()
|
||||
|
||||
/**
|
||||
* Represents the json body of an invalid update request.
|
||||
*
|
||||
* @param message A general, user-displayable error message.
|
||||
* @param validationErrors a map where each value is a list of error messages for each key.
|
||||
* The values in the array should be used for display to the user, since the keys tend to come
|
||||
* back as nonsense. (eg: empty string key)
|
||||
*/
|
||||
@Serializable
|
||||
data class Invalid(
|
||||
@SerialName("message")
|
||||
val message: String?,
|
||||
|
||||
@SerialName("validationErrors")
|
||||
val validationErrors: Map<String, List<String>>?,
|
||||
) : UpdateSendResponseJson()
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
|
||||
|
||||
/**
|
||||
* Provides an API for querying sends endpoints.
|
||||
*/
|
||||
interface SendsService {
|
||||
/**
|
||||
* Attempt to create a send.
|
||||
*/
|
||||
suspend fun createSend(
|
||||
body: SendJsonRequest,
|
||||
): Result<SyncResponseJson.Send>
|
||||
|
||||
/**
|
||||
* Attempt to update a send.
|
||||
*/
|
||||
suspend fun updateSend(
|
||||
sendId: String,
|
||||
body: SendJsonRequest,
|
||||
): Result<UpdateSendResponseJson>
|
||||
|
||||
/**
|
||||
* Attempt to delete a cipher.
|
||||
*/
|
||||
suspend fun deleteSend(
|
||||
sendId: String,
|
||||
): Result<Unit>
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Default implementation of the [SendsService].
|
||||
*/
|
||||
class SendsServiceImpl(
|
||||
private val sendsApi: SendsApi,
|
||||
private val json: Json,
|
||||
) : SendsService {
|
||||
override suspend fun createSend(body: SendJsonRequest): Result<SyncResponseJson.Send> =
|
||||
sendsApi.createSend(body = body)
|
||||
|
||||
override suspend fun updateSend(
|
||||
sendId: String,
|
||||
body: SendJsonRequest,
|
||||
): Result<UpdateSendResponseJson> =
|
||||
sendsApi
|
||||
.updateSend(
|
||||
sendId = sendId,
|
||||
body = body,
|
||||
)
|
||||
.map { UpdateSendResponseJson.Success(send = it) }
|
||||
.recoverCatching { throwable ->
|
||||
throwable
|
||||
.toBitwardenError()
|
||||
.parseErrorBodyOrNull<UpdateSendResponseJson.Invalid>(
|
||||
code = 400,
|
||||
json = json,
|
||||
)
|
||||
?: throw throwable
|
||||
}
|
||||
|
||||
override suspend fun deleteSend(sendId: String): Result<Unit> =
|
||||
sendsApi.deleteSend(sendId = sendId)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Create a mock [SendJsonRequest] with a given [number].
|
||||
*/
|
||||
fun createMockSendJsonRequest(
|
||||
number: Int,
|
||||
type: SendTypeJson = SendTypeJson.TEXT,
|
||||
): SendJsonRequest =
|
||||
SendJsonRequest(
|
||||
name = "mockName-$number",
|
||||
notes = "mockNotes-$number",
|
||||
type = type,
|
||||
key = "mockKey-$number",
|
||||
maxAccessCount = 1,
|
||||
expirationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
deletionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
file = createMockFile(number),
|
||||
text = createMockText(number),
|
||||
password = "mockPassword-$number",
|
||||
isDisabled = false,
|
||||
shouldHideEmail = false,
|
||||
)
|
|
@ -0,0 +1,109 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSendJsonRequest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.create
|
||||
|
||||
class SendsServiceTest : BaseServiceTest() {
|
||||
private val sendsApi: SendsApi = retrofit.create()
|
||||
|
||||
private val sendsService: SendsService = SendsServiceImpl(
|
||||
sendsApi = sendsApi,
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `createSend should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(CREATE_UPDATE_SEND_SUCCESS_JSON))
|
||||
val result = sendsService.createSend(
|
||||
body = createMockSendJsonRequest(number = 1),
|
||||
)
|
||||
assertEquals(
|
||||
createMockSend(number = 1),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateSend with success response should return a Success with the correct send`() =
|
||||
runTest {
|
||||
server.enqueue(MockResponse().setBody(CREATE_UPDATE_SEND_SUCCESS_JSON))
|
||||
val result = sendsService.updateSend(
|
||||
sendId = "send-id-1",
|
||||
body = createMockSendJsonRequest(number = 1),
|
||||
)
|
||||
assertEquals(
|
||||
UpdateSendResponseJson.Success(
|
||||
send = createMockSend(number = 1),
|
||||
),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateSend with an invalid response should return an Invalid with the correct data`() =
|
||||
runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(400).setBody(UPDATE_SEND_INVALID_JSON))
|
||||
val result = sendsService.updateSend(
|
||||
sendId = "send-id-1",
|
||||
body = createMockSendJsonRequest(number = 1),
|
||||
)
|
||||
assertEquals(
|
||||
UpdateSendResponseJson.Invalid(
|
||||
message = "You do not have permission to edit this.",
|
||||
validationErrors = null,
|
||||
),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deleteSend should return a Success with the correct data`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(200))
|
||||
val result = sendsService.deleteSend(sendId = "send-id-1")
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
private const val CREATE_UPDATE_SEND_SUCCESS_JSON = """
|
||||
{
|
||||
"id": "mockId-1",
|
||||
"accessId": "mockAccessId-1",
|
||||
"type": 1,
|
||||
"name": "mockName-1",
|
||||
"notes": "mockNotes-1",
|
||||
"file": {
|
||||
"id": "mockId-1",
|
||||
"fileName": "mockFileName-1",
|
||||
"size": 1,
|
||||
"sizeName": "mockSizeName-1"
|
||||
},
|
||||
"text": {
|
||||
"text": "mockText-1",
|
||||
"hidden": false
|
||||
},
|
||||
"key": "mockKey-1",
|
||||
"maxAccessCount": 1,
|
||||
"accessCount": 1,
|
||||
"password": "mockPassword-1",
|
||||
"disabled": false,
|
||||
"revisionDate": "2023-10-27T12:00:00.00Z",
|
||||
"expirationDate": "2023-10-27T12:00:00.00Z",
|
||||
"deletionDate": "2023-10-27T12:00:00.00Z",
|
||||
"hideEmail": false
|
||||
}
|
||||
"""
|
||||
|
||||
private const val UPDATE_SEND_INVALID_JSON = """
|
||||
{
|
||||
"message": "You do not have permission to edit this.",
|
||||
"validationErrors": null
|
||||
}
|
||||
"""
|
Loading…
Reference in a new issue