mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 23:25:45 +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.platform.datasource.network.retrofit.Retrofits
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
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.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.SyncService
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncServiceImpl
|
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncServiceImpl
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
@ -30,6 +32,16 @@ object VaultNetworkModule {
|
||||||
json = json,
|
json = json,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSendsService(
|
||||||
|
retrofits: Retrofits,
|
||||||
|
json: Json,
|
||||||
|
): SendsService = SendsServiceImpl(
|
||||||
|
sendsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||||
|
json = json,
|
||||||
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSyncService(
|
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