mirror of
https://github.com/bitwarden/android.git
synced 2026-05-21 11:56:35 -05:00
Merge branch 'main' into PM-33982/build-device-screen
# Conflicts: # app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt
This commit is contained in:
@@ -24,6 +24,7 @@ import com.bitwarden.network.service.OrganizationService
|
||||
import com.bitwarden.network.service.PushService
|
||||
import com.bitwarden.network.service.SendsService
|
||||
import com.bitwarden.network.service.SyncService
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Provides access to Bitwarden services.
|
||||
@@ -176,4 +177,8 @@ interface BitwardenServiceClient {
|
||||
*/
|
||||
fun bitwardenServiceClient(
|
||||
config: BitwardenServiceClientConfig,
|
||||
): BitwardenServiceClient = BitwardenServiceClientImpl(config)
|
||||
json: Json,
|
||||
): BitwardenServiceClient = BitwardenServiceClientImpl(
|
||||
bitwardenServiceClientConfig = config,
|
||||
clientJson = json,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.bitwarden.network
|
||||
|
||||
import com.bitwarden.annotation.OmitFromCoverage
|
||||
import com.bitwarden.core.data.serializer.InstantSerializer
|
||||
import com.bitwarden.network.interceptor.AuthTokenManager
|
||||
import com.bitwarden.network.interceptor.BaseUrlInterceptors
|
||||
import com.bitwarden.network.interceptor.CookieInterceptor
|
||||
@@ -44,8 +43,6 @@ import com.bitwarden.network.service.PushServiceImpl
|
||||
import com.bitwarden.network.service.SendsServiceImpl
|
||||
import com.bitwarden.network.service.SyncServiceImpl
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.contextual
|
||||
import retrofit2.create
|
||||
|
||||
/**
|
||||
@@ -54,6 +51,7 @@ import retrofit2.create
|
||||
@OmitFromCoverage
|
||||
internal class BitwardenServiceClientImpl(
|
||||
private val bitwardenServiceClientConfig: BitwardenServiceClientConfig,
|
||||
private val clientJson: Json,
|
||||
) : BitwardenServiceClient {
|
||||
|
||||
private val authTokenManager: AuthTokenManager = AuthTokenManager(
|
||||
@@ -63,22 +61,7 @@ internal class BitwardenServiceClientImpl(
|
||||
override val tokenProvider: TokenProvider = authTokenManager
|
||||
|
||||
override val cookieProvider: CookieProvider = bitwardenServiceClientConfig.cookieProvider
|
||||
private val clientJson = Json {
|
||||
|
||||
// If there are keys returned by the server not modeled by a serializable class,
|
||||
// ignore them.
|
||||
// This makes additive server changes non-breaking.
|
||||
ignoreUnknownKeys = true
|
||||
|
||||
// We allow for nullable values to have keys missing in the JSON response.
|
||||
explicitNulls = false
|
||||
serializersModule = SerializersModule {
|
||||
contextual(InstantSerializer())
|
||||
}
|
||||
|
||||
// Respect model default property values.
|
||||
coerceInputValues = true
|
||||
}
|
||||
private val retrofits: Retrofits by lazy {
|
||||
RetrofitsImpl(
|
||||
authTokenManager = authTokenManager,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bitwarden.network.api
|
||||
|
||||
import com.bitwarden.network.model.BitwardenSubscriptionResponseJson
|
||||
import com.bitwarden.network.model.CheckoutSessionRequestJson
|
||||
import com.bitwarden.network.model.CheckoutSessionResponseJson
|
||||
import com.bitwarden.network.model.NetworkResult
|
||||
@@ -33,4 +34,10 @@ internal interface AuthenticatedBillingApi {
|
||||
*/
|
||||
@GET("/plans/premium")
|
||||
suspend fun getPremiumPlan(): NetworkResult<PremiumPlanResponseJson>
|
||||
|
||||
/**
|
||||
* Retrieves the user's premium subscription details.
|
||||
*/
|
||||
@GET("/account/billing/vnext/subscription")
|
||||
suspend fun getSubscription(): NetworkResult<BitwardenSubscriptionResponseJson>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package com.bitwarden.network.model
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Response object returned when retrieving the user's premium subscription details.
|
||||
*
|
||||
* @property status The current status of the subscription.
|
||||
* @property cart The cart details of the subscription.
|
||||
* @property storage The storage usage details, if available.
|
||||
* @property cancelAt The date the subscription is scheduled to cancel, if applicable.
|
||||
* @property canceled The date the subscription was canceled, if applicable.
|
||||
* @property nextCharge The date of the next charge, if applicable.
|
||||
* @property suspension The date the subscription was suspended, if applicable.
|
||||
* @property gracePeriod The grace period in days, if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class BitwardenSubscriptionResponseJson(
|
||||
@SerialName("status")
|
||||
val status: SubscriptionStatusJson,
|
||||
|
||||
@SerialName("cart")
|
||||
val cart: CartJson,
|
||||
|
||||
@SerialName("storage")
|
||||
val storage: StorageJson?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("cancelAt")
|
||||
val cancelAt: Instant?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("canceled")
|
||||
val canceled: Instant?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("nextCharge")
|
||||
val nextCharge: Instant?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("suspension")
|
||||
val suspension: Instant?,
|
||||
|
||||
@SerialName("gracePeriod")
|
||||
val gracePeriod: Int?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the status of a subscription.
|
||||
*/
|
||||
@Serializable
|
||||
enum class SubscriptionStatusJson {
|
||||
@SerialName("active")
|
||||
ACTIVE,
|
||||
|
||||
@SerialName("canceled")
|
||||
CANCELED,
|
||||
|
||||
@SerialName("past_due")
|
||||
PAST_DUE,
|
||||
|
||||
@SerialName("incomplete")
|
||||
INCOMPLETE,
|
||||
|
||||
@SerialName("incomplete_expired")
|
||||
INCOMPLETE_EXPIRED,
|
||||
|
||||
@SerialName("unpaid")
|
||||
UNPAID,
|
||||
|
||||
@SerialName("trialing")
|
||||
TRIALING,
|
||||
|
||||
@SerialName("paused")
|
||||
PAUSED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the cart details of a subscription.
|
||||
*
|
||||
* @property passwordManager The password manager cart items.
|
||||
* @property secretsManager The secrets manager cart items, if applicable.
|
||||
* @property cadence The billing cadence of the subscription.
|
||||
* @property discount The discount applied to the cart, if applicable.
|
||||
* @property estimatedTax The estimated tax amount.
|
||||
*/
|
||||
@Serializable
|
||||
data class CartJson(
|
||||
@SerialName("passwordManager")
|
||||
val passwordManager: PasswordManagerCartItemsJson,
|
||||
|
||||
@SerialName("secretsManager")
|
||||
val secretsManager: SecretsManagerCartItemsJson?,
|
||||
|
||||
@SerialName("cadence")
|
||||
val cadence: CadenceTypeJson,
|
||||
|
||||
@SerialName("discount")
|
||||
val discount: BitwardenDiscountJson?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("estimatedTax")
|
||||
val estimatedTax: BigDecimal,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the password manager cart items within a subscription.
|
||||
*
|
||||
* @property seats The seat pricing details.
|
||||
* @property additionalStorage The additional storage pricing details, if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class PasswordManagerCartItemsJson(
|
||||
@SerialName("seats")
|
||||
val seats: CartItemJson,
|
||||
|
||||
@SerialName("additionalStorage")
|
||||
val additionalStorage: CartItemJson?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the secrets manager cart items within a subscription.
|
||||
*
|
||||
* @property seats The seat pricing details.
|
||||
* @property additionalServiceAccounts The additional service accounts pricing details,
|
||||
* if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class SecretsManagerCartItemsJson(
|
||||
@SerialName("seats")
|
||||
val seats: CartItemJson,
|
||||
|
||||
@SerialName("additionalServiceAccounts")
|
||||
val additionalServiceAccounts: CartItemJson?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents a single cart item within a subscription.
|
||||
*
|
||||
* @property translationKey The translation key for display purposes.
|
||||
* @property quantity The quantity of this item.
|
||||
* @property cost The cost of this item.
|
||||
* @property discount The discount applied to this item, if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class CartItemJson(
|
||||
@SerialName("translationKey")
|
||||
val translationKey: String,
|
||||
|
||||
@SerialName("quantity")
|
||||
val quantity: Long,
|
||||
|
||||
@Contextual
|
||||
@SerialName("cost")
|
||||
val cost: BigDecimal,
|
||||
|
||||
@SerialName("discount")
|
||||
val discount: BitwardenDiscountJson?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents a discount applied to a subscription or cart item.
|
||||
*
|
||||
* @property type The type of discount.
|
||||
* @property value The discount value.
|
||||
*/
|
||||
@Serializable
|
||||
data class BitwardenDiscountJson(
|
||||
@SerialName("type")
|
||||
val type: DiscountTypeJson,
|
||||
|
||||
@Contextual
|
||||
@SerialName("value")
|
||||
val value: BigDecimal,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the type of discount applied to a subscription.
|
||||
*/
|
||||
@Serializable
|
||||
enum class DiscountTypeJson {
|
||||
@SerialName("amount-off")
|
||||
AMOUNT_OFF,
|
||||
|
||||
@SerialName("percent-off")
|
||||
PERCENT_OFF,
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the billing cadence of a subscription.
|
||||
*/
|
||||
@Serializable
|
||||
enum class CadenceTypeJson {
|
||||
@SerialName("annually")
|
||||
ANNUALLY,
|
||||
|
||||
@SerialName("monthly")
|
||||
MONTHLY,
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents storage usage details for a subscription.
|
||||
*
|
||||
* @property available The available storage in bytes.
|
||||
* @property used The used storage amount.
|
||||
* @property readableUsed A human-readable representation of the used storage.
|
||||
*/
|
||||
@Serializable
|
||||
data class StorageJson(
|
||||
@SerialName("available")
|
||||
val available: Int,
|
||||
|
||||
@SerialName("used")
|
||||
val used: Double,
|
||||
|
||||
@SerialName("readableUsed")
|
||||
val readableUsed: String,
|
||||
)
|
||||
@@ -36,6 +36,7 @@ data class SyncResponseJson(
|
||||
@JsonNames("Profile")
|
||||
val profile: Profile,
|
||||
|
||||
@Contextual
|
||||
@SerialName("ciphers")
|
||||
val ciphers: List<Cipher>?,
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bitwarden.network.service
|
||||
|
||||
import com.bitwarden.network.model.BitwardenSubscriptionResponseJson
|
||||
import com.bitwarden.network.model.CheckoutSessionResponseJson
|
||||
import com.bitwarden.network.model.PortalUrlResponseJson
|
||||
import com.bitwarden.network.model.PremiumPlanResponseJson
|
||||
@@ -23,4 +24,9 @@ interface BillingService {
|
||||
* Retrieves the premium plan pricing information.
|
||||
*/
|
||||
suspend fun getPremiumPlan(): Result<PremiumPlanResponseJson>
|
||||
|
||||
/**
|
||||
* Retrieves the user's premium subscription details.
|
||||
*/
|
||||
suspend fun getSubscription(): Result<BitwardenSubscriptionResponseJson>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bitwarden.network.service
|
||||
|
||||
import com.bitwarden.network.api.AuthenticatedBillingApi
|
||||
import com.bitwarden.network.model.BitwardenSubscriptionResponseJson
|
||||
import com.bitwarden.network.model.CheckoutSessionRequestJson
|
||||
import com.bitwarden.network.model.CheckoutSessionResponseJson
|
||||
import com.bitwarden.network.model.PortalUrlResponseJson
|
||||
@@ -32,4 +33,9 @@ internal class BillingServiceImpl(
|
||||
authenticatedBillingApi
|
||||
.getPremiumPlan()
|
||||
.toResult()
|
||||
|
||||
override suspend fun getSubscription(): Result<BitwardenSubscriptionResponseJson> =
|
||||
authenticatedBillingApi
|
||||
.getSubscription()
|
||||
.toResult()
|
||||
}
|
||||
|
||||
@@ -3,15 +3,27 @@ package com.bitwarden.network.service
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.network.api.AuthenticatedBillingApi
|
||||
import com.bitwarden.network.base.BaseServiceTest
|
||||
import com.bitwarden.network.model.BitwardenDiscountJson
|
||||
import com.bitwarden.network.model.BitwardenSubscriptionResponseJson
|
||||
import com.bitwarden.network.model.CadenceTypeJson
|
||||
import com.bitwarden.network.model.CartItemJson
|
||||
import com.bitwarden.network.model.CartJson
|
||||
import com.bitwarden.network.model.CheckoutSessionResponseJson
|
||||
import com.bitwarden.network.model.DiscountTypeJson
|
||||
import com.bitwarden.network.model.PasswordManagerCartItemsJson
|
||||
import com.bitwarden.network.model.PortalUrlResponseJson
|
||||
import com.bitwarden.network.model.PremiumPlanResponseJson
|
||||
import com.bitwarden.network.model.StorageJson
|
||||
import com.bitwarden.network.model.SubscriptionStatusJson
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.SerializationException
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.create
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
|
||||
class BillingServiceTest : BaseServiceTest() {
|
||||
|
||||
@@ -77,6 +89,105 @@ class BillingServiceTest : BaseServiceTest() {
|
||||
val actual = service.getPremiumPlan()
|
||||
assertEquals(PREMIUM_PLAN_RESPONSE.asSuccess(), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription when response is Failure should return Failure`() =
|
||||
runTest {
|
||||
val response = MockResponse().setResponseCode(400)
|
||||
server.enqueue(response)
|
||||
val actual = service.getSubscription()
|
||||
assertTrue(actual.isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription when response is Success should return Success`() =
|
||||
runTest {
|
||||
val response = MockResponse()
|
||||
.setBody(SUBSCRIPTION_RESPONSE_JSON)
|
||||
.setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.getSubscription()
|
||||
assertEquals(SUBSCRIPTION_RESPONSE.asSuccess(), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription with monthly cadence should parse correctly`() =
|
||||
runTest {
|
||||
val response = MockResponse()
|
||||
.setBody(SUBSCRIPTION_RESPONSE_MONTHLY_JSON)
|
||||
.setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.getSubscription()
|
||||
assertEquals(
|
||||
CadenceTypeJson.MONTHLY,
|
||||
actual.getOrNull()?.cart?.cadence,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription should parse every SubscriptionStatusJson value`() =
|
||||
runTest {
|
||||
SubscriptionStatusJson.entries.forEach { status ->
|
||||
val body = subscriptionResponseJsonForStatus(status)
|
||||
val response = MockResponse()
|
||||
.setBody(body)
|
||||
.setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.getSubscription()
|
||||
assertEquals(status, actual.getOrNull()?.status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription with null storage and discount should parse`() =
|
||||
runTest {
|
||||
val response = MockResponse()
|
||||
.setBody(SUBSCRIPTION_RESPONSE_MINIMAL_JSON)
|
||||
.setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.getSubscription()
|
||||
assertTrue(actual.isSuccess)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription with AMOUNT_OFF discount should parse`() =
|
||||
runTest {
|
||||
val response = MockResponse()
|
||||
.setBody(SUBSCRIPTION_RESPONSE_AMOUNT_OFF_JSON)
|
||||
.setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.getSubscription()
|
||||
assertEquals(
|
||||
DiscountTypeJson.AMOUNT_OFF,
|
||||
actual.getOrNull()?.cart?.discount?.type,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription with PERCENT_OFF discount should parse`() =
|
||||
runTest {
|
||||
val response = MockResponse()
|
||||
.setBody(SUBSCRIPTION_RESPONSE_PERCENT_OFF_JSON)
|
||||
.setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.getSubscription()
|
||||
assertEquals(
|
||||
DiscountTypeJson.PERCENT_OFF,
|
||||
actual.getOrNull()?.cart?.discount?.type,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription with unknown cadence should fail deserialization`() =
|
||||
runTest {
|
||||
val response = MockResponse()
|
||||
.setBody(SUBSCRIPTION_RESPONSE_UNKNOWN_CADENCE_JSON)
|
||||
.setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.getSubscription()
|
||||
assertTrue(actual.isFailure)
|
||||
assertTrue(actual.exceptionOrNull() is SerializationException)
|
||||
}
|
||||
}
|
||||
|
||||
private const val CHECKOUT_SESSION_RESPONSE_JSON = """
|
||||
@@ -132,3 +243,261 @@ private val PREMIUM_PLAN_RESPONSE = PremiumPlanResponseJson(
|
||||
provided = 5,
|
||||
),
|
||||
)
|
||||
|
||||
private const val SUBSCRIPTION_RESPONSE_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
},
|
||||
"additionalStorage": {
|
||||
"translationKey": "additionalStorage",
|
||||
"quantity": 24,
|
||||
"cost": 24.00,
|
||||
"discount": null
|
||||
}
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": {
|
||||
"type": "amount-off",
|
||||
"value": 2.10
|
||||
},
|
||||
"estimatedTax": 3.85
|
||||
},
|
||||
"storage": {
|
||||
"available": 5,
|
||||
"used": 0,
|
||||
"readableUsed": "0 Bytes"
|
||||
},
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": "2026-04-02T00:00:00Z",
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
"""
|
||||
|
||||
private val SUBSCRIPTION_RESPONSE = BitwardenSubscriptionResponseJson(
|
||||
status = SubscriptionStatusJson.ACTIVE,
|
||||
cart = CartJson(
|
||||
passwordManager = PasswordManagerCartItemsJson(
|
||||
seats = CartItemJson(
|
||||
translationKey = "premiumMembership",
|
||||
quantity = 1,
|
||||
cost = BigDecimal("19.80"),
|
||||
discount = null,
|
||||
),
|
||||
additionalStorage = CartItemJson(
|
||||
translationKey = "additionalStorage",
|
||||
quantity = 24,
|
||||
cost = BigDecimal("24.00"),
|
||||
discount = null,
|
||||
),
|
||||
),
|
||||
secretsManager = null,
|
||||
cadence = CadenceTypeJson.ANNUALLY,
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("2.10"),
|
||||
),
|
||||
estimatedTax = BigDecimal("3.85"),
|
||||
),
|
||||
storage = StorageJson(
|
||||
available = 5,
|
||||
used = 0.0,
|
||||
readableUsed = "0 Bytes",
|
||||
),
|
||||
cancelAt = null,
|
||||
canceled = null,
|
||||
nextCharge = Instant.parse("2026-04-02T00:00:00Z"),
|
||||
suspension = null,
|
||||
gracePeriod = null,
|
||||
)
|
||||
|
||||
private const val SUBSCRIPTION_RESPONSE_MONTHLY_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 1.67,
|
||||
"discount": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "monthly",
|
||||
"discount": null,
|
||||
"estimatedTax": 0
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
"""
|
||||
|
||||
private const val SUBSCRIPTION_RESPONSE_MINIMAL_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": null,
|
||||
"estimatedTax": 0
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
"""
|
||||
|
||||
private const val SUBSCRIPTION_RESPONSE_AMOUNT_OFF_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": {
|
||||
"type": "amount-off",
|
||||
"value": 5.00
|
||||
},
|
||||
"estimatedTax": 0
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
"""
|
||||
|
||||
private const val SUBSCRIPTION_RESPONSE_PERCENT_OFF_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": {
|
||||
"type": "percent-off",
|
||||
"value": 15.00
|
||||
},
|
||||
"estimatedTax": 0
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
"""
|
||||
|
||||
private const val SUBSCRIPTION_RESPONSE_UNKNOWN_CADENCE_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "weekly",
|
||||
"discount": null,
|
||||
"estimatedTax": 0
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
"""
|
||||
|
||||
private fun subscriptionResponseJsonForStatus(
|
||||
status: SubscriptionStatusJson,
|
||||
): String {
|
||||
val wireValue = when (status) {
|
||||
SubscriptionStatusJson.ACTIVE -> "active"
|
||||
SubscriptionStatusJson.CANCELED -> "canceled"
|
||||
SubscriptionStatusJson.PAST_DUE -> "past_due"
|
||||
SubscriptionStatusJson.INCOMPLETE -> "incomplete"
|
||||
SubscriptionStatusJson.INCOMPLETE_EXPIRED -> "incomplete_expired"
|
||||
SubscriptionStatusJson.UNPAID -> "unpaid"
|
||||
SubscriptionStatusJson.TRIALING -> "trialing"
|
||||
SubscriptionStatusJson.PAUSED -> "paused"
|
||||
}
|
||||
return """
|
||||
{
|
||||
"status": "$wireValue",
|
||||
"cart": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": null,
|
||||
"estimatedTax": 0
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ private const val SYNC_SUCCESS_JSON = """
|
||||
"mockCollectionId-1"
|
||||
],
|
||||
"name": "mockName-1",
|
||||
"id": "mockId-1"
|
||||
"id": "mockId-1",
|
||||
"fields": [
|
||||
{
|
||||
"linkedId": 100,
|
||||
|
||||
Reference in New Issue
Block a user