This commit is contained in:
7 files changed
+933
-46
No files matched your search
@@ -0,0 +1,431 @@
|
||||
package com.m57.hermescontrol.data.ws
|
||||
|
||||
import android.util.Log
|
||||
import com.m57.hermescontrol.data.local.AuthManager
|
||||
import com.m57.hermescontrol.data.remote.CookieManager
|
||||
import com.m57.hermescontrol.data.remote.DashboardSessionTokenRefresher
|
||||
import com.m57.hermescontrol.data.remote.OkHttpProvider
|
||||
import com.m57.hermescontrol.data.remote.buildFakePersistentCookieJar
|
||||
import com.m57.hermescontrol.data.ws.contract.EmptyParams
|
||||
import com.m57.hermescontrol.data.ws.contract.RpcMethods
|
||||
import com.m57.hermescontrol.data.ws.contract.SubscriptionChangeParams
|
||||
import com.m57.hermescontrol.data.ws.contract.VaultLockParams
|
||||
import com.m57.hermescontrol.data.ws.contract.VaultUnlockParams
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkAll
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class HermesWsClientBillingVaultTest {
|
||||
private lateinit var mockWebServer: MockWebServer
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockkStatic(Log::class)
|
||||
every { Log.d(any<String>(), any<String>()) } returns 0
|
||||
every { Log.i(any<String>(), any<String>()) } returns 0
|
||||
every { Log.w(any<String>(), any<String>()) } returns 0
|
||||
every { Log.w(any<String>(), any<String>(), any<Throwable>()) } returns 0
|
||||
every { Log.e(any<String>(), any<String>(), any()) } returns 0
|
||||
|
||||
mockWebServer = MockWebServer()
|
||||
mockWebServer.start()
|
||||
mockkObject(AuthManager)
|
||||
every { AuthManager.wsUrl() } returns mockWebServer.url("/").toString().replace("http://", "ws://")
|
||||
every { AuthManager.isAutoReconnect() } returns false
|
||||
every { AuthManager.getSessionCookie() } returns null
|
||||
every { AuthManager.serverStore } returns
|
||||
mockk<com.m57.hermescontrol.data.config.ServerStore>().also {
|
||||
every { it.getLatestState() } returns
|
||||
com.m57.hermescontrol.data.config
|
||||
.ServerStoreState()
|
||||
}
|
||||
CookieManager.setJarForTest(buildFakePersistentCookieJar())
|
||||
mockkObject(DashboardSessionTokenRefresher)
|
||||
coEvery { DashboardSessionTokenRefresher.refreshAsync() } returns null
|
||||
every { DashboardSessionTokenRefresher.refresh() } returns null
|
||||
|
||||
HermesWsClient.disconnect(clearPendingMessages = true)
|
||||
HermesWsClient.releaseExternalActivityConnectionLease()
|
||||
HermesWsClient.releaseBackgroundConnectionLease()
|
||||
HermesWsClient.setAppForeground(true)
|
||||
AuthManager.resetAuthStateForTest()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
AuthManager.resetAuthStateForTest()
|
||||
HermesWsClient.releaseExternalActivityConnectionLease()
|
||||
HermesWsClient.releaseBackgroundConnectionLease()
|
||||
HermesWsClient.disconnect(clearPendingMessages = true)
|
||||
runBlocking {
|
||||
withTimeoutOrNull(5000) {
|
||||
HermesWsClient.connectionStatus.first { it == ConnectionStatus.DISCONNECTED }
|
||||
}
|
||||
}
|
||||
runCatching { mockWebServer.shutdown() }
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
private fun connectClient() {
|
||||
HermesWsClient.connect()
|
||||
runBlocking {
|
||||
withTimeout(5000) { HermesWsClient.connectionStatus.first { it == ConnectionStatus.CONNECTED } }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun typedCallSubscriptionChangeEncodesOnlyNonNullFieldsAndReturnsRawServerJsonUntouched() {
|
||||
val opened = CountDownLatch(1)
|
||||
val captured = CountDownLatch(1)
|
||||
val frameRef = AtomicReference<JsonObject?>(null)
|
||||
|
||||
val serverResultJson =
|
||||
buildJsonObject {
|
||||
put("status", "ok")
|
||||
put("subscription_id", "sub-12345")
|
||||
put("scheduled_cancellation", false)
|
||||
}
|
||||
|
||||
mockWebServer.enqueue(
|
||||
MockResponse().withWebSocketUpgrade(
|
||||
object : WebSocketListener() {
|
||||
override fun onOpen(
|
||||
webSocket: WebSocket,
|
||||
response: Response,
|
||||
) = opened.countDown()
|
||||
|
||||
override fun onMessage(
|
||||
webSocket: WebSocket,
|
||||
text: String,
|
||||
) {
|
||||
val json = OkHttpProvider.json.parseToJsonElement(text) as? JsonObject ?: return
|
||||
val name = (json["method"] as? JsonPrimitive)?.content
|
||||
if (name == "subscription.change") {
|
||||
frameRef.set(json)
|
||||
captured.countDown()
|
||||
val id = (json["id"] as? JsonPrimitive)?.content ?: "1"
|
||||
webSocket.send(
|
||||
"""{"jsonrpc":"2.0","id":"$id","result":$serverResultJson}""",
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
connectClient()
|
||||
assertTrue(opened.await(5, TimeUnit.SECONDS))
|
||||
|
||||
val result: JsonElement =
|
||||
runBlocking {
|
||||
HermesWsClient.call(
|
||||
RpcMethods.SUBSCRIPTION_CHANGE,
|
||||
SubscriptionChangeParams(
|
||||
subscriptionTypeId = null,
|
||||
cancel = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
assertTrue("Frame must reach the server", captured.await(5, TimeUnit.SECONDS))
|
||||
val frame = requireNotNull(frameRef.get())
|
||||
|
||||
assertFalse("Outgoing frames have NO jsonrpc field", frame.containsKey("jsonrpc"))
|
||||
assertEquals("subscription.change", (frame["method"] as? JsonPrimitive)?.content)
|
||||
|
||||
val params = frame["params"] as? JsonObject
|
||||
val expectedParams =
|
||||
buildJsonObject {
|
||||
put("cancel", false)
|
||||
}
|
||||
assertEquals(expectedParams, params)
|
||||
assertEquals(false, (params?.get("cancel") as? JsonPrimitive)?.booleanOrNull)
|
||||
assertFalse(params?.containsKey("subscription_type_id") ?: true)
|
||||
assertEquals(serverResultJson, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun typedCallVaultUnlockPutsNameAndPasswordOnWireAndReturnsRawServerJsonUntouched() {
|
||||
val opened = CountDownLatch(1)
|
||||
val captured = CountDownLatch(1)
|
||||
val frameRef = AtomicReference<JsonObject?>(null)
|
||||
|
||||
val serverResultJson =
|
||||
buildJsonObject {
|
||||
put("unlocked", true)
|
||||
put("expires_in", 3600)
|
||||
}
|
||||
|
||||
mockWebServer.enqueue(
|
||||
MockResponse().withWebSocketUpgrade(
|
||||
object : WebSocketListener() {
|
||||
override fun onOpen(
|
||||
webSocket: WebSocket,
|
||||
response: Response,
|
||||
) = opened.countDown()
|
||||
|
||||
override fun onMessage(
|
||||
webSocket: WebSocket,
|
||||
text: String,
|
||||
) {
|
||||
val json = OkHttpProvider.json.parseToJsonElement(text) as? JsonObject ?: return
|
||||
val name = (json["method"] as? JsonPrimitive)?.content
|
||||
if (name == "vault.unlock") {
|
||||
frameRef.set(json)
|
||||
captured.countDown()
|
||||
val id = (json["id"] as? JsonPrimitive)?.content ?: "1"
|
||||
webSocket.send(
|
||||
"""{"jsonrpc":"2.0","id":"$id","result":$serverResultJson}""",
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
connectClient()
|
||||
assertTrue(opened.await(5, TimeUnit.SECONDS))
|
||||
|
||||
val result: JsonElement =
|
||||
runBlocking {
|
||||
HermesWsClient.call(
|
||||
RpcMethods.VAULT_UNLOCK,
|
||||
VaultUnlockParams(
|
||||
name = "op-work",
|
||||
password = "super-secret-pass",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
assertTrue("Frame must reach the server", captured.await(5, TimeUnit.SECONDS))
|
||||
val frame = requireNotNull(frameRef.get())
|
||||
|
||||
assertEquals("vault.unlock", (frame["method"] as? JsonPrimitive)?.content)
|
||||
|
||||
val params = frame["params"] as? JsonObject
|
||||
val expectedParams =
|
||||
buildJsonObject {
|
||||
put("name", "op-work")
|
||||
put("password", "super-secret-pass")
|
||||
}
|
||||
assertEquals(expectedParams, params)
|
||||
assertEquals(serverResultJson, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun typedCallVaultLockWithNamePutsNameOnWireAndReturnsRawServerJsonUntouched() {
|
||||
val opened = CountDownLatch(1)
|
||||
val captured = CountDownLatch(1)
|
||||
val frameRef = AtomicReference<JsonObject?>(null)
|
||||
|
||||
val serverResultJson =
|
||||
buildJsonObject {
|
||||
put("locked", true)
|
||||
put("vault", "op-work")
|
||||
}
|
||||
|
||||
mockWebServer.enqueue(
|
||||
MockResponse().withWebSocketUpgrade(
|
||||
object : WebSocketListener() {
|
||||
override fun onOpen(
|
||||
webSocket: WebSocket,
|
||||
response: Response,
|
||||
) = opened.countDown()
|
||||
|
||||
override fun onMessage(
|
||||
webSocket: WebSocket,
|
||||
text: String,
|
||||
) {
|
||||
val json = OkHttpProvider.json.parseToJsonElement(text) as? JsonObject ?: return
|
||||
val name = (json["method"] as? JsonPrimitive)?.content
|
||||
if (name == "vault.lock") {
|
||||
frameRef.set(json)
|
||||
captured.countDown()
|
||||
val id = (json["id"] as? JsonPrimitive)?.content ?: "1"
|
||||
webSocket.send(
|
||||
"""{"jsonrpc":"2.0","id":"$id","result":$serverResultJson}""",
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
connectClient()
|
||||
assertTrue(opened.await(5, TimeUnit.SECONDS))
|
||||
|
||||
val result: JsonElement =
|
||||
runBlocking {
|
||||
HermesWsClient.call(
|
||||
RpcMethods.VAULT_LOCK,
|
||||
VaultLockParams(name = "op-work"),
|
||||
)
|
||||
}
|
||||
|
||||
assertTrue("Frame must reach the server", captured.await(5, TimeUnit.SECONDS))
|
||||
val frame = requireNotNull(frameRef.get())
|
||||
|
||||
assertEquals("vault.lock", (frame["method"] as? JsonPrimitive)?.content)
|
||||
|
||||
val params = frame["params"] as? JsonObject
|
||||
val expectedParams =
|
||||
buildJsonObject {
|
||||
put("name", "op-work")
|
||||
}
|
||||
assertEquals(expectedParams, params)
|
||||
assertEquals(serverResultJson, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun typedCallVaultLockWithoutNameLeavesParamsAbsentOrEmptyAndReturnsRawServerJsonUntouched() {
|
||||
val opened = CountDownLatch(1)
|
||||
val captured = CountDownLatch(1)
|
||||
val frameRef = AtomicReference<JsonObject?>(null)
|
||||
|
||||
val serverResultJson =
|
||||
buildJsonObject {
|
||||
put("locked_all", true)
|
||||
}
|
||||
|
||||
mockWebServer.enqueue(
|
||||
MockResponse().withWebSocketUpgrade(
|
||||
object : WebSocketListener() {
|
||||
override fun onOpen(
|
||||
webSocket: WebSocket,
|
||||
response: Response,
|
||||
) = opened.countDown()
|
||||
|
||||
override fun onMessage(
|
||||
webSocket: WebSocket,
|
||||
text: String,
|
||||
) {
|
||||
val json = OkHttpProvider.json.parseToJsonElement(text) as? JsonObject ?: return
|
||||
val name = (json["method"] as? JsonPrimitive)?.content
|
||||
if (name == "vault.lock") {
|
||||
frameRef.set(json)
|
||||
captured.countDown()
|
||||
val id = (json["id"] as? JsonPrimitive)?.content ?: "1"
|
||||
webSocket.send(
|
||||
"""{"jsonrpc":"2.0","id":"$id","result":$serverResultJson}""",
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
connectClient()
|
||||
assertTrue(opened.await(5, TimeUnit.SECONDS))
|
||||
|
||||
val result: JsonElement =
|
||||
runBlocking {
|
||||
HermesWsClient.call(
|
||||
RpcMethods.VAULT_LOCK,
|
||||
VaultLockParams(name = null),
|
||||
)
|
||||
}
|
||||
|
||||
assertTrue("Frame must reach the server", captured.await(5, TimeUnit.SECONDS))
|
||||
val frame = requireNotNull(frameRef.get())
|
||||
|
||||
assertEquals("vault.lock", (frame["method"] as? JsonPrimitive)?.content)
|
||||
|
||||
val params = frame["params"] as? JsonObject
|
||||
assertTrue(
|
||||
"vault.lock with null name carries no params",
|
||||
params == null || params.isEmpty(),
|
||||
)
|
||||
assertEquals(serverResultJson, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun typedCallEmptyParamsMethodLeavesParamsAbsentOrEmptyAndReturnsRawServerJsonUntouched() {
|
||||
val opened = CountDownLatch(1)
|
||||
val captured = CountDownLatch(1)
|
||||
val frameRef = AtomicReference<JsonObject?>(null)
|
||||
|
||||
val serverResultJson =
|
||||
buildJsonObject {
|
||||
put("status", "active")
|
||||
put("tier", "standard")
|
||||
put("valid_until", "2026-12-31T23:59:59Z")
|
||||
}
|
||||
|
||||
mockWebServer.enqueue(
|
||||
MockResponse().withWebSocketUpgrade(
|
||||
object : WebSocketListener() {
|
||||
override fun onOpen(
|
||||
webSocket: WebSocket,
|
||||
response: Response,
|
||||
) = opened.countDown()
|
||||
|
||||
override fun onMessage(
|
||||
webSocket: WebSocket,
|
||||
text: String,
|
||||
) {
|
||||
val json = OkHttpProvider.json.parseToJsonElement(text) as? JsonObject ?: return
|
||||
val name = (json["method"] as? JsonPrimitive)?.content
|
||||
if (name == "subscription.state") {
|
||||
frameRef.set(json)
|
||||
captured.countDown()
|
||||
val id = (json["id"] as? JsonPrimitive)?.content ?: "1"
|
||||
webSocket.send(
|
||||
"""{"jsonrpc":"2.0","id":"$id","result":$serverResultJson}""",
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
connectClient()
|
||||
assertTrue(opened.await(5, TimeUnit.SECONDS))
|
||||
|
||||
val result: JsonElement =
|
||||
runBlocking {
|
||||
HermesWsClient.call(
|
||||
RpcMethods.SUBSCRIPTION_STATE,
|
||||
EmptyParams,
|
||||
)
|
||||
}
|
||||
|
||||
assertTrue("Frame must reach the server", captured.await(5, TimeUnit.SECONDS))
|
||||
val frame = requireNotNull(frameRef.get())
|
||||
|
||||
assertEquals("subscription.state", (frame["method"] as? JsonPrimitive)?.content)
|
||||
|
||||
val params = frame["params"] as? JsonObject
|
||||
assertTrue(
|
||||
"subscription.state with EmptyParams carries no params",
|
||||
params == null || params.isEmpty(),
|
||||
)
|
||||
assertEquals(serverResultJson, result)
|
||||
}
|
||||
}
|
||||
@@ -325,12 +325,6 @@ class RawRpcParamsRatchetTest {
|
||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_POLICY_GET",
|
||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_POLICY_SET",
|
||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_TOOLS",
|
||||
"data/ws/BillingRepository.kt::SUBSCRIPTION_CHANGE",
|
||||
"data/ws/BillingRepository.kt::SUBSCRIPTION_PREVIEW",
|
||||
"data/ws/BillingRepository.kt::SUBSCRIPTION_RESUME",
|
||||
"data/ws/BillingRepository.kt::SUBSCRIPTION_STATE",
|
||||
"data/ws/BillingRepository.kt::SUBSCRIPTION_UPGRADE",
|
||||
"data/ws/BillingRepository.kt::USAGE_BARS",
|
||||
"data/ws/ConnectorRepository.kt::CONNECTORS_CONNECT",
|
||||
"data/ws/ConnectorRepository.kt::CONNECTORS_LIST",
|
||||
"data/ws/HermesWsClient.kt::CLIENT_CAPABILITIES",
|
||||
@@ -341,11 +335,6 @@ class RawRpcParamsRatchetTest {
|
||||
"data/ws/ProjectsSource.kt::PROJECTS_LIST",
|
||||
"data/ws/SubagentRepository.kt::SUBAGENT_LIST",
|
||||
"data/ws/SubagentRepository.kt::SUBAGENT_TAIL",
|
||||
"data/ws/VaultRepository.kt::VAULT_LIST",
|
||||
"data/ws/VaultRepository.kt::VAULT_LOCK",
|
||||
"data/ws/VaultRepository.kt::VAULT_SOURCES",
|
||||
"data/ws/VaultRepository.kt::VAULT_SOURCE_SET",
|
||||
"data/ws/VaultRepository.kt::VAULT_UNLOCK",
|
||||
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
||||
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
||||
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
package com.m57.hermescontrol.data.ws.contract
|
||||
|
||||
import com.m57.hermescontrol.data.remote.OkHttpProvider
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import kotlinx.serialization.json.encodeToJsonElement
|
||||
import kotlinx.serialization.json.put
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class BillingVaultTypesTest {
|
||||
@Test
|
||||
fun emptyParamsEncodesToEmptyJsonObject() {
|
||||
val params = EmptyParams
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(EmptyParams.serializer(), params)
|
||||
|
||||
val expected = buildJsonObject {}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertTrue((encoded as JsonObject).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionPreviewParamsEncodesSubscriptionTypeIdWhenPresent() {
|
||||
val params = SubscriptionPreviewParams(subscriptionTypeId = "pro-monthly")
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(SubscriptionPreviewParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("subscription_type_id", "pro-monthly")
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("subscription_type_id"), (encoded as JsonObject).keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionPreviewParamsOmitsNullSubscriptionTypeId() {
|
||||
val params = SubscriptionPreviewParams(subscriptionTypeId = null)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(SubscriptionPreviewParams.serializer(), params)
|
||||
|
||||
val expected = buildJsonObject {}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertTrue((encoded as JsonObject).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionChangeParamsEncodesAllFieldsWhenPresent() {
|
||||
val params =
|
||||
SubscriptionChangeParams(
|
||||
subscriptionTypeId = "tier-2",
|
||||
cancel = true,
|
||||
)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(SubscriptionChangeParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("subscription_type_id", "tier-2")
|
||||
put("cancel", true)
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("subscription_type_id", "cancel"), (encoded as JsonObject).keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionChangeParamsEncodesExplicitFalseForCancel() {
|
||||
val params =
|
||||
SubscriptionChangeParams(
|
||||
subscriptionTypeId = "tier-1",
|
||||
cancel = false,
|
||||
)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(SubscriptionChangeParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("subscription_type_id", "tier-1")
|
||||
put("cancel", false)
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("subscription_type_id", "cancel"), (encoded as JsonObject).keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionChangeParamsOmitsOnlyNullFields() {
|
||||
val paramsOnlyCancel = SubscriptionChangeParams(subscriptionTypeId = null, cancel = false)
|
||||
val encodedOnlyCancel =
|
||||
OkHttpProvider.json.encodeToJsonElement(SubscriptionChangeParams.serializer(), paramsOnlyCancel)
|
||||
val expectedOnlyCancel =
|
||||
buildJsonObject {
|
||||
put("cancel", false)
|
||||
}
|
||||
assertEquals(expectedOnlyCancel, encodedOnlyCancel)
|
||||
assertTrue(encodedOnlyCancel is JsonObject)
|
||||
assertEquals(setOf("cancel"), (encodedOnlyCancel as JsonObject).keys)
|
||||
|
||||
val paramsOnlyType = SubscriptionChangeParams(subscriptionTypeId = "tier-3", cancel = null)
|
||||
val encodedOnlyType =
|
||||
OkHttpProvider.json.encodeToJsonElement(SubscriptionChangeParams.serializer(), paramsOnlyType)
|
||||
val expectedOnlyType =
|
||||
buildJsonObject {
|
||||
put("subscription_type_id", "tier-3")
|
||||
}
|
||||
assertEquals(expectedOnlyType, encodedOnlyType)
|
||||
assertTrue(encodedOnlyType is JsonObject)
|
||||
assertEquals(setOf("subscription_type_id"), (encodedOnlyType as JsonObject).keys)
|
||||
|
||||
val paramsEmpty = SubscriptionChangeParams(subscriptionTypeId = null, cancel = null)
|
||||
val encodedEmpty =
|
||||
OkHttpProvider.json.encodeToJsonElement(SubscriptionChangeParams.serializer(), paramsEmpty)
|
||||
val expectedEmpty = buildJsonObject {}
|
||||
assertEquals(expectedEmpty, encodedEmpty)
|
||||
assertTrue(encodedEmpty is JsonObject)
|
||||
assertTrue((encodedEmpty as JsonObject).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionUpgradeParamsEncodesSubscriptionTypeIdWhenPresent() {
|
||||
val params = SubscriptionUpgradeParams(subscriptionTypeId = "enterprise-yearly")
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(SubscriptionUpgradeParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("subscription_type_id", "enterprise-yearly")
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("subscription_type_id"), (encoded as JsonObject).keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionUpgradeParamsOmitsNullSubscriptionTypeId() {
|
||||
val params = SubscriptionUpgradeParams(subscriptionTypeId = null)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(SubscriptionUpgradeParams.serializer(), params)
|
||||
|
||||
val expected = buildJsonObject {}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertTrue((encoded as JsonObject).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun vaultSourceSetParamsEncodesAllFieldsWhenPresent() {
|
||||
val params =
|
||||
VaultSourceSetParams(
|
||||
name = "1password",
|
||||
enabled = true,
|
||||
)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(VaultSourceSetParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("name", "1password")
|
||||
put("enabled", true)
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("name", "enabled"), (encoded as JsonObject).keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun vaultSourceSetParamsEncodesExplicitFalseForEnabled() {
|
||||
val params =
|
||||
VaultSourceSetParams(
|
||||
name = "bitwarden",
|
||||
enabled = false,
|
||||
)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(VaultSourceSetParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("name", "bitwarden")
|
||||
put("enabled", false)
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("name", "enabled"), (encoded as JsonObject).keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun vaultSourceSetParamsOmitsNullFields() {
|
||||
val params = VaultSourceSetParams(name = "keepass", enabled = null)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(VaultSourceSetParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("name", "keepass")
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("name"), (encoded as JsonObject).keys)
|
||||
|
||||
val paramsEmpty = VaultSourceSetParams(name = null, enabled = null)
|
||||
val encodedEmpty =
|
||||
OkHttpProvider.json.encodeToJsonElement(VaultSourceSetParams.serializer(), paramsEmpty)
|
||||
val expectedEmpty = buildJsonObject {}
|
||||
assertEquals(expectedEmpty, encodedEmpty)
|
||||
assertTrue(encodedEmpty is JsonObject)
|
||||
assertTrue((encodedEmpty as JsonObject).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun vaultUnlockParamsEncodesAllFieldsWhenPresent() {
|
||||
val params =
|
||||
VaultUnlockParams(
|
||||
name = "local-vault",
|
||||
password = "secret-password",
|
||||
)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(VaultUnlockParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("name", "local-vault")
|
||||
put("password", "secret-password")
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("name", "password"), (encoded as JsonObject).keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun vaultUnlockParamsOmitsNullFields() {
|
||||
val params = VaultUnlockParams(name = "local-vault", password = null)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(VaultUnlockParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("name", "local-vault")
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("name"), (encoded as JsonObject).keys)
|
||||
|
||||
val paramsEmpty = VaultUnlockParams(name = null, password = null)
|
||||
val encodedEmpty =
|
||||
OkHttpProvider.json.encodeToJsonElement(VaultUnlockParams.serializer(), paramsEmpty)
|
||||
val expectedEmpty = buildJsonObject {}
|
||||
assertEquals(expectedEmpty, encodedEmpty)
|
||||
assertTrue(encodedEmpty is JsonObject)
|
||||
assertTrue((encodedEmpty as JsonObject).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun vaultLockParamsEncodesEmptyJsonObjectWhenNameNull() {
|
||||
val params = VaultLockParams(name = null)
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(VaultLockParams.serializer(), params)
|
||||
|
||||
val expected = buildJsonObject {}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertTrue((encoded as JsonObject).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun vaultLockParamsEncodesNameWhenSet() {
|
||||
val params = VaultLockParams(name = "primary-vault")
|
||||
val encoded = OkHttpProvider.json.encodeToJsonElement(VaultLockParams.serializer(), params)
|
||||
|
||||
val expected =
|
||||
buildJsonObject {
|
||||
put("name", "primary-vault")
|
||||
}
|
||||
assertEquals(expected, encoded)
|
||||
assertTrue(encoded is JsonObject)
|
||||
assertEquals(setOf("name"), (encoded as JsonObject).keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rpcMethodsRegistrationMatchesContract() {
|
||||
assertEquals("subscription.state", RpcMethods.SUBSCRIPTION_STATE.name)
|
||||
assertEquals("usage.bars", RpcMethods.USAGE_BARS.name)
|
||||
assertEquals("subscription.resume", RpcMethods.SUBSCRIPTION_RESUME.name)
|
||||
assertEquals("vault.list", RpcMethods.VAULT_LIST.name)
|
||||
assertEquals("vault.sources", RpcMethods.VAULT_SOURCES.name)
|
||||
assertEquals("subscription.preview", RpcMethods.SUBSCRIPTION_PREVIEW.name)
|
||||
assertEquals("subscription.change", RpcMethods.SUBSCRIPTION_CHANGE.name)
|
||||
assertEquals("subscription.upgrade", RpcMethods.SUBSCRIPTION_UPGRADE.name)
|
||||
assertEquals("vault.source.set", RpcMethods.VAULT_SOURCE_SET.name)
|
||||
assertEquals("vault.unlock", RpcMethods.VAULT_UNLOCK.name)
|
||||
assertEquals("vault.lock", RpcMethods.VAULT_LOCK.name)
|
||||
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.SUBSCRIPTION_STATE))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.USAGE_BARS))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.SUBSCRIPTION_RESUME))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.VAULT_LIST))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.VAULT_SOURCES))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.SUBSCRIPTION_PREVIEW))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.SUBSCRIPTION_CHANGE))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.SUBSCRIPTION_UPGRADE))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.VAULT_SOURCE_SET))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.VAULT_UNLOCK))
|
||||
assertTrue(RpcMethods.all.contains(RpcMethods.VAULT_LOCK))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun jsonElementPassthroughMethodsDecodeResultUntouched() {
|
||||
val arbitraryPayload =
|
||||
buildJsonObject {
|
||||
put("tier", "pro")
|
||||
put("credits", 4200)
|
||||
put("details", buildJsonObject { put("active", true) })
|
||||
}
|
||||
|
||||
val methods =
|
||||
listOf(
|
||||
RpcMethods.SUBSCRIPTION_STATE,
|
||||
RpcMethods.USAGE_BARS,
|
||||
RpcMethods.SUBSCRIPTION_RESUME,
|
||||
RpcMethods.VAULT_LIST,
|
||||
RpcMethods.VAULT_SOURCES,
|
||||
RpcMethods.SUBSCRIPTION_PREVIEW,
|
||||
RpcMethods.SUBSCRIPTION_CHANGE,
|
||||
RpcMethods.SUBSCRIPTION_UPGRADE,
|
||||
RpcMethods.VAULT_SOURCE_SET,
|
||||
RpcMethods.VAULT_UNLOCK,
|
||||
RpcMethods.VAULT_LOCK,
|
||||
)
|
||||
|
||||
for (method in methods) {
|
||||
val decoded: JsonElement =
|
||||
OkHttpProvider.json.decodeFromJsonElement(
|
||||
method.result,
|
||||
arbitraryPayload,
|
||||
)
|
||||
assertEquals("Method ${method.name} must pass raw result untouched", arbitraryPayload, decoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user