10 files changed
+1286
-149
No files matched your search
@@ -7,6 +7,19 @@ import com.m57.hermescontrol.data.model.ConnectorCatalogEntry
|
|||||||
import com.m57.hermescontrol.data.model.ConnectorError
|
import com.m57.hermescontrol.data.model.ConnectorError
|
||||||
import com.m57.hermescontrol.data.model.ConnectorPolicy
|
import com.m57.hermescontrol.data.model.ConnectorPolicy
|
||||||
import com.m57.hermescontrol.data.model.ConnectorTool
|
import com.m57.hermescontrol.data.model.ConnectorTool
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorOwner
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsAccountsParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsAccountsRemoveParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsCatalogParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsConnectParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsListParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsOperationStatusParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsPolicyGetParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsPolicySetParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsToolsParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.HermesRpcCaller
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.RpcMethods
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.TypedRpcCaller
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@@ -74,6 +87,7 @@ class HermesAccountConnectorRepository(
|
|||||||
if (!deferred.isCompleted) deferred.cancel()
|
if (!deferred.isCompleted) deferred.cancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
private val caller: TypedRpcCaller = HermesRpcCaller,
|
||||||
) : AccountConnectorRepository {
|
) : AccountConnectorRepository {
|
||||||
private suspend fun request(
|
private suspend fun request(
|
||||||
method: String,
|
method: String,
|
||||||
@@ -116,10 +130,11 @@ class HermesAccountConnectorRepository(
|
|||||||
override suspend fun listConnectors(): com.m57.hermescontrol.data.model.ConnectorListResult =
|
override suspend fun listConnectors(): com.m57.hermescontrol.data.model.ConnectorListResult =
|
||||||
try {
|
try {
|
||||||
ConnectorParser.parseListResult(
|
ConnectorParser.parseListResult(
|
||||||
rpc(
|
caller.call(
|
||||||
WsMethods.CONNECTORS_LIST,
|
RpcMethods.CONNECTORS_LIST,
|
||||||
accountParams(
|
ConnectorsListParams(
|
||||||
mapOf("owner" to mapOf("type" to "account")),
|
owner = ConnectorOwner.account(),
|
||||||
|
profile = AuthManager.activeProfileId.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -146,14 +161,13 @@ class HermesAccountConnectorRepository(
|
|||||||
) = guarded {
|
) = guarded {
|
||||||
require(slugs.isNotEmpty() && slugs.all(ConnectorRepository::isValidSlug))
|
require(slugs.isNotEmpty() && slugs.all(ConnectorRepository::isValidSlug))
|
||||||
parseOperation(
|
parseOperation(
|
||||||
request(
|
caller.call(
|
||||||
WsMethods.CONNECTORS_CONNECT,
|
RpcMethods.CONNECTORS_CONNECT,
|
||||||
accountParams(
|
ConnectorsConnectParams(
|
||||||
mapOf(
|
owner = ConnectorOwner.account(),
|
||||||
"owner" to mapOf("type" to "account"),
|
connectors = slugs,
|
||||||
"connectors" to slugs,
|
reconnect = reconnect,
|
||||||
"reconnect" to reconnect,
|
profile = AuthManager.activeProfileId.value,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -166,7 +180,16 @@ class HermesAccountConnectorRepository(
|
|||||||
|
|
||||||
override suspend fun operationStatus(opId: String) =
|
override suspend fun operationStatus(opId: String) =
|
||||||
guarded {
|
guarded {
|
||||||
parseOperation(operationRequest(WsMethods.CONNECTORS_OPERATION_STATUS, mapOf("op_id" to opId)))
|
parseOperation(
|
||||||
|
caller.call(
|
||||||
|
RpcMethods.CONNECTORS_OPERATION_STATUS,
|
||||||
|
ConnectorsOperationStatusParams(
|
||||||
|
owner = ConnectorOwner.account(),
|
||||||
|
opId = opId,
|
||||||
|
profile = AuthManager.activeProfileId.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -180,7 +203,14 @@ class HermesAccountConnectorRepository(
|
|||||||
|
|
||||||
override suspend fun catalog() =
|
override suspend fun catalog() =
|
||||||
guarded {
|
guarded {
|
||||||
obj(request(WsMethods.CONNECTORS_CATALOG, accountParams()))["connectors"]!!.jsonArray.map {
|
val response =
|
||||||
|
caller.call(
|
||||||
|
RpcMethods.CONNECTORS_CATALOG,
|
||||||
|
ConnectorsCatalogParams(
|
||||||
|
profile = AuthManager.activeProfileId.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
obj(response)["connectors"]!!.jsonArray.map {
|
||||||
val row = it.jsonObject
|
val row = it.jsonObject
|
||||||
ConnectorCatalogEntry(row.str("slug"), row.str("name"), row.str("description"), row.str("category"))
|
ConnectorCatalogEntry(row.str("slug"), row.str("name"), row.str("description"), row.str("category"))
|
||||||
}
|
}
|
||||||
@@ -188,7 +218,14 @@ class HermesAccountConnectorRepository(
|
|||||||
|
|
||||||
override suspend fun accounts() =
|
override suspend fun accounts() =
|
||||||
guarded {
|
guarded {
|
||||||
obj(request(WsMethods.CONNECTORS_ACCOUNTS, accountParams()))["accounts"]!!.jsonArray.map { e ->
|
val response =
|
||||||
|
caller.call(
|
||||||
|
RpcMethods.CONNECTORS_ACCOUNTS,
|
||||||
|
ConnectorsAccountsParams(
|
||||||
|
profile = AuthManager.activeProfileId.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
obj(response)["accounts"]!!.jsonArray.map { e ->
|
||||||
val r = e.jsonObject
|
val r = e.jsonObject
|
||||||
ConnectorAccount(
|
ConnectorAccount(
|
||||||
r.str("connection_id"),
|
r.str("connection_id"),
|
||||||
@@ -209,10 +246,11 @@ class HermesAccountConnectorRepository(
|
|||||||
require(connectionId.isNotBlank())
|
require(connectionId.isNotBlank())
|
||||||
val response =
|
val response =
|
||||||
obj(
|
obj(
|
||||||
request(
|
caller.call(
|
||||||
WsMethods.CONNECTORS_ACCOUNTS_REMOVE,
|
RpcMethods.CONNECTORS_ACCOUNTS_REMOVE,
|
||||||
accountParams(
|
ConnectorsAccountsRemoveParams(
|
||||||
mapOf("connection_id" to connectionId),
|
connectionId = connectionId,
|
||||||
|
profile = AuthManager.activeProfileId.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -225,12 +263,16 @@ class HermesAccountConnectorRepository(
|
|||||||
refresh: Boolean,
|
refresh: Boolean,
|
||||||
) = guarded {
|
) = guarded {
|
||||||
require(ConnectorRepository.isValidSlug(slug))
|
require(ConnectorRepository.isValidSlug(slug))
|
||||||
val params =
|
val response =
|
||||||
buildMap<String, Any> {
|
caller.call(
|
||||||
put("slug", slug)
|
RpcMethods.CONNECTORS_TOOLS,
|
||||||
if (refresh) put("refresh", true)
|
ConnectorsToolsParams(
|
||||||
}
|
slug = slug,
|
||||||
obj(request(WsMethods.CONNECTORS_TOOLS, accountParams(params)))["tools"]!!.jsonArray.map { e ->
|
refresh = if (refresh) true else null,
|
||||||
|
profile = AuthManager.activeProfileId.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
obj(response)["tools"]!!.jsonArray.map { e ->
|
||||||
val r = e.jsonObject
|
val r = e.jsonObject
|
||||||
ConnectorTool(
|
ConnectorTool(
|
||||||
r.str("slug"),
|
r.str("slug"),
|
||||||
@@ -245,7 +287,15 @@ class HermesAccountConnectorRepository(
|
|||||||
|
|
||||||
override suspend fun policy() =
|
override suspend fun policy() =
|
||||||
guarded {
|
guarded {
|
||||||
val root = obj(request(WsMethods.CONNECTORS_POLICY_GET, accountParams()))
|
val root =
|
||||||
|
obj(
|
||||||
|
caller.call(
|
||||||
|
RpcMethods.CONNECTORS_POLICY_GET,
|
||||||
|
ConnectorsPolicyGetParams(
|
||||||
|
profile = AuthManager.activeProfileId.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
val layers = root.array("layers").map { it.jsonObject }
|
val layers = root.array("layers").map { it.jsonObject }
|
||||||
val member = layers.firstOrNull { it.str("kind") == "member" }
|
val member = layers.firstOrNull { it.str("kind") == "member" }
|
||||||
parsePolicy(root["effective"]!!.jsonObject).copy(
|
parsePolicy(root["effective"]!!.jsonObject).copy(
|
||||||
@@ -293,13 +343,12 @@ class HermesAccountConnectorRepository(
|
|||||||
check(scope == AuthManager.currentDataScope()) { "Account scope changed" }
|
check(scope == AuthManager.currentDataScope()) { "Account scope changed" }
|
||||||
val result =
|
val result =
|
||||||
obj(
|
obj(
|
||||||
request(
|
caller.call(
|
||||||
WsMethods.CONNECTORS_POLICY_SET,
|
RpcMethods.CONNECTORS_POLICY_SET,
|
||||||
accountParams(
|
ConnectorsPolicySetParams(
|
||||||
mapOf(
|
change = ConnectorParser.toJsonElement(change) as JsonObject,
|
||||||
"change" to change,
|
expectedRevision = expectedRevision,
|
||||||
"expected_revision" to expectedRevision,
|
profile = AuthManager.activeProfileId.value,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ package com.m57.hermescontrol.data.ws
|
|||||||
import com.m57.hermescontrol.data.model.ConnectorConnectResult
|
import com.m57.hermescontrol.data.model.ConnectorConnectResult
|
||||||
import com.m57.hermescontrol.data.model.ConnectorError
|
import com.m57.hermescontrol.data.model.ConnectorError
|
||||||
import com.m57.hermescontrol.data.model.ConnectorListResult
|
import com.m57.hermescontrol.data.model.ConnectorListResult
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorOwner
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsConnectParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsListParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.HermesRpcCaller
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.RpcMethods
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.TypedRpcCaller
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,19 +48,7 @@ interface ConnectorRepository {
|
|||||||
* Default implementation of [ConnectorRepository] backed by [HermesWsClient].
|
* Default implementation of [ConnectorRepository] backed by [HermesWsClient].
|
||||||
*/
|
*/
|
||||||
class HermesConnectorRepository(
|
class HermesConnectorRepository(
|
||||||
private val rpcRequest: suspend (method: String, params: Map<String, Any>) -> Any? = { method, params ->
|
private val caller: TypedRpcCaller = HermesRpcCaller,
|
||||||
val deferred = HermesWsClient.request(method, params)
|
|
||||||
try {
|
|
||||||
deferred.await()
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
deferred.cancel(e)
|
|
||||||
throw e
|
|
||||||
} finally {
|
|
||||||
if (!deferred.isCompleted) {
|
|
||||||
deferred.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) : ConnectorRepository {
|
) : ConnectorRepository {
|
||||||
override suspend fun listConnectors(sessionId: String): ConnectorListResult {
|
override suspend fun listConnectors(sessionId: String): ConnectorListResult {
|
||||||
if (sessionId.isBlank()) {
|
if (sessionId.isBlank()) {
|
||||||
@@ -64,8 +58,8 @@ class HermesConnectorRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val params = mapOf("owner" to ConnectorRepository.sessionOwner(sessionId))
|
val params = ConnectorsListParams(owner = ConnectorOwner.session(sessionId))
|
||||||
val result = rpcRequest(WsMethods.CONNECTORS_LIST, params)
|
val result = caller.call(RpcMethods.CONNECTORS_LIST, params)
|
||||||
ConnectorParser.parseListResult(result)
|
ConnectorParser.parseListResult(result)
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
@@ -107,12 +101,12 @@ class HermesConnectorRepository(
|
|||||||
|
|
||||||
return try {
|
return try {
|
||||||
val params =
|
val params =
|
||||||
mapOf(
|
ConnectorsConnectParams(
|
||||||
"owner" to ConnectorRepository.sessionOwner(sessionId),
|
owner = ConnectorOwner.session(sessionId),
|
||||||
"connectors" to connectors,
|
connectors = connectors,
|
||||||
"reconnect" to reconnect,
|
reconnect = reconnect,
|
||||||
)
|
)
|
||||||
val result = rpcRequest(WsMethods.CONNECTORS_CONNECT, params)
|
val result = caller.call(RpcMethods.CONNECTORS_CONNECT, params)
|
||||||
ConnectorParser.parseConnectResult(result)
|
ConnectorParser.parseConnectResult(result)
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.m57.hermescontrol.data.ws.contract
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorOwner(
|
||||||
|
@SerialName("type") val type: String,
|
||||||
|
@SerialName("session_id") val sessionId: String? = null,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun account() = ConnectorOwner("account")
|
||||||
|
|
||||||
|
fun session(sessionId: String) = ConnectorOwner("session", sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsListParams(
|
||||||
|
@SerialName("owner") val owner: ConnectorOwner,
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsConnectParams(
|
||||||
|
@SerialName("owner") val owner: ConnectorOwner,
|
||||||
|
@SerialName("connectors") val connectors: List<String>,
|
||||||
|
@SerialName("reconnect") val reconnect: Boolean? = null,
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsOperationStatusParams(
|
||||||
|
@SerialName("owner") val owner: ConnectorOwner,
|
||||||
|
@SerialName("op_id") val opId: String,
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsCatalogParams(
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsAccountsParams(
|
||||||
|
@SerialName("connector") val connector: String? = null,
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsAccountsRemoveParams(
|
||||||
|
@SerialName("connection_id") val connectionId: String,
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsPolicyGetParams(
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsPolicySetParams(
|
||||||
|
@SerialName("change") val change: JsonObject,
|
||||||
|
@SerialName("expected_revision") val expectedRevision: String,
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnectorsToolsParams(
|
||||||
|
@SerialName("slug") val slug: String,
|
||||||
|
@SerialName("refresh") val refresh: Boolean? = null,
|
||||||
|
@SerialName("profile") val profile: String? = null,
|
||||||
|
)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.m57.hermescontrol.data.ws.contract
|
package com.m57.hermescontrol.data.ws.contract
|
||||||
|
|
||||||
|
import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||||
import com.m57.hermescontrol.data.ws.WsMethods
|
import com.m57.hermescontrol.data.ws.WsMethods
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
@@ -18,6 +19,20 @@ interface TypedRpcSender {
|
|||||||
): String
|
): String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TypedRpcCaller {
|
||||||
|
suspend fun <P, R> call(
|
||||||
|
method: RpcMethod<P, R>,
|
||||||
|
params: P,
|
||||||
|
): R
|
||||||
|
}
|
||||||
|
|
||||||
|
object HermesRpcCaller : TypedRpcCaller {
|
||||||
|
override suspend fun <P, R> call(
|
||||||
|
method: RpcMethod<P, R>,
|
||||||
|
params: P,
|
||||||
|
): R = HermesWsClient.call(method, params)
|
||||||
|
}
|
||||||
|
|
||||||
object RpcMethods {
|
object RpcMethods {
|
||||||
val SESSION_EVENTS_SINCE: RpcMethod<SessionEventsSinceParams, SessionEventsSinceResult> =
|
val SESSION_EVENTS_SINCE: RpcMethod<SessionEventsSinceParams, SessionEventsSinceResult> =
|
||||||
RpcMethod(
|
RpcMethod(
|
||||||
@@ -228,6 +243,78 @@ object RpcMethods {
|
|||||||
JsonElement.serializer(),
|
JsonElement.serializer(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_LIST: RpcMethod<ConnectorsListParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_LIST,
|
||||||
|
ConnectorsListParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_CONNECT: RpcMethod<ConnectorsConnectParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_CONNECT,
|
||||||
|
ConnectorsConnectParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_OPERATION_STATUS: RpcMethod<ConnectorsOperationStatusParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_OPERATION_STATUS,
|
||||||
|
ConnectorsOperationStatusParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_CATALOG: RpcMethod<ConnectorsCatalogParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_CATALOG,
|
||||||
|
ConnectorsCatalogParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_ACCOUNTS: RpcMethod<ConnectorsAccountsParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_ACCOUNTS,
|
||||||
|
ConnectorsAccountsParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_ACCOUNTS_REMOVE: RpcMethod<ConnectorsAccountsRemoveParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_ACCOUNTS_REMOVE,
|
||||||
|
ConnectorsAccountsRemoveParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_POLICY_GET: RpcMethod<ConnectorsPolicyGetParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_POLICY_GET,
|
||||||
|
ConnectorsPolicyGetParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_POLICY_SET: RpcMethod<ConnectorsPolicySetParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_POLICY_SET,
|
||||||
|
ConnectorsPolicySetParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy parsers.
|
||||||
|
val CONNECTORS_TOOLS: RpcMethod<ConnectorsToolsParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.CONNECTORS_TOOLS,
|
||||||
|
ConnectorsToolsParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
/** Every typed method. GatewayContractTest iterates this; future migrations append here. */
|
/** Every typed method. GatewayContractTest iterates this; future migrations append here. */
|
||||||
val all: List<RpcMethod<*, *>> =
|
val all: List<RpcMethod<*, *>> =
|
||||||
listOf(
|
listOf(
|
||||||
@@ -260,5 +347,14 @@ object RpcMethods {
|
|||||||
VAULT_SOURCE_SET,
|
VAULT_SOURCE_SET,
|
||||||
VAULT_UNLOCK,
|
VAULT_UNLOCK,
|
||||||
VAULT_LOCK,
|
VAULT_LOCK,
|
||||||
|
CONNECTORS_LIST,
|
||||||
|
CONNECTORS_CONNECT,
|
||||||
|
CONNECTORS_OPERATION_STATUS,
|
||||||
|
CONNECTORS_CATALOG,
|
||||||
|
CONNECTORS_ACCOUNTS,
|
||||||
|
CONNECTORS_ACCOUNTS_REMOVE,
|
||||||
|
CONNECTORS_POLICY_GET,
|
||||||
|
CONNECTORS_POLICY_SET,
|
||||||
|
CONNECTORS_TOOLS,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ class AccountConnectorRepositoryTest {
|
|||||||
runTest {
|
runTest {
|
||||||
val calls = mutableListOf<Pair<String, Map<String, Any>>>()
|
val calls = mutableListOf<Pair<String, Map<String, Any>>>()
|
||||||
val repo =
|
val repo =
|
||||||
HermesAccountConnectorRepository { method, params ->
|
accountRepo { method, params ->
|
||||||
calls += method to params
|
calls += method to params
|
||||||
operationFixture()
|
operationFixture()
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ class AccountConnectorRepositoryTest {
|
|||||||
runTest {
|
runTest {
|
||||||
val calls = mutableListOf<Pair<String, Map<String, Any>>>()
|
val calls = mutableListOf<Pair<String, Map<String, Any>>>()
|
||||||
val repo =
|
val repo =
|
||||||
HermesAccountConnectorRepository { method, params ->
|
accountRepo { method, params ->
|
||||||
calls += method to params
|
calls += method to params
|
||||||
operationFixture()
|
operationFixture()
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ class AccountConnectorRepositoryTest {
|
|||||||
runTest {
|
runTest {
|
||||||
val calls = mutableListOf<Pair<String, Map<String, Any>>>()
|
val calls = mutableListOf<Pair<String, Map<String, Any>>>()
|
||||||
val repo =
|
val repo =
|
||||||
HermesAccountConnectorRepository { method, params ->
|
accountRepo { method, params ->
|
||||||
calls += method to params
|
calls += method to params
|
||||||
when (method) {
|
when (method) {
|
||||||
WsMethods.CONNECTORS_CATALOG -> {
|
WsMethods.CONNECTORS_CATALOG -> {
|
||||||
@@ -165,7 +165,7 @@ class AccountConnectorRepositoryTest {
|
|||||||
|
|
||||||
@Test fun `policy keeps member revision separate from effective and respects inherited rules`() =
|
@Test fun `policy keeps member revision separate from effective and respects inherited rules`() =
|
||||||
runTest {
|
runTest {
|
||||||
val repo = HermesAccountConnectorRepository { _, _ -> policyFixture() }
|
val repo = accountRepo { _, _ -> policyFixture() }
|
||||||
val policy = (repo.policy() as AccountConnectorResult.Success).value
|
val policy = (repo.policy() as AccountConnectorResult.Success).value
|
||||||
assertEquals("effective-revision", policy.revision)
|
assertEquals("effective-revision", policy.revision)
|
||||||
assertEquals("01ARZ3NDEKTSV4RRFFQ69G5FAV", policy.member?.revision)
|
assertEquals("01ARZ3NDEKTSV4RRFFQ69G5FAV", policy.member?.revision)
|
||||||
@@ -180,7 +180,7 @@ class AccountConnectorRepositoryTest {
|
|||||||
runTest {
|
runTest {
|
||||||
val calls = mutableListOf<Pair<String, Map<String, Any>>>()
|
val calls = mutableListOf<Pair<String, Map<String, Any>>>()
|
||||||
val repo =
|
val repo =
|
||||||
HermesAccountConnectorRepository { method, params ->
|
accountRepo { method, params ->
|
||||||
calls += method to params
|
calls += method to params
|
||||||
if (method == WsMethods.CONNECTORS_POLICY_GET) {
|
if (method == WsMethods.CONNECTORS_POLICY_GET) {
|
||||||
policyFixture()
|
policyFixture()
|
||||||
@@ -208,10 +208,13 @@ class AccountConnectorRepositoryTest {
|
|||||||
|
|
||||||
@Test fun `malformed authorization does not look like a successful connect`() =
|
@Test fun `malformed authorization does not look like a successful connect`() =
|
||||||
runTest {
|
runTest {
|
||||||
val repo = HermesAccountConnectorRepository { _, _ -> mapOf("status" to "initiated") }
|
val repo = accountRepo { _, _ -> mapOf("status" to "initiated") }
|
||||||
assertTrue(repo.connect(listOf("drive")) is AccountConnectorResult.Failure)
|
assertTrue(repo.connect(listOf("drive")) is AccountConnectorResult.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun accountRepo(handler: suspend (String, Map<String, Any>) -> Any?) =
|
||||||
|
HermesAccountConnectorRepository(rpc = handler, caller = fakeCaller(handler))
|
||||||
|
|
||||||
private fun operationFixture() =
|
private fun operationFixture() =
|
||||||
mapOf(
|
mapOf(
|
||||||
"op_id" to "op-a",
|
"op_id" to "op-a",
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ class ConnectorRepositoryTest {
|
|||||||
|
|
||||||
val repo =
|
val repo =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { method, params ->
|
caller =
|
||||||
recordedMethod = method
|
fakeCaller { method, params ->
|
||||||
recordedParams = params
|
recordedMethod = method
|
||||||
mapOf("available" to true, "connectors" to emptyList<Map<String, Any>>())
|
recordedParams = params
|
||||||
},
|
mapOf("available" to true, "connectors" to emptyList<Map<String, Any>>())
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = repo.listConnectors("sess-abc-123")
|
val result = repo.listConnectors("sess-abc-123")
|
||||||
@@ -61,17 +62,18 @@ class ConnectorRepositoryTest {
|
|||||||
|
|
||||||
val repo =
|
val repo =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { method, params ->
|
caller =
|
||||||
recordedMethod = method
|
fakeCaller { method, params ->
|
||||||
recordedParams = params
|
recordedMethod = method
|
||||||
mapOf(
|
recordedParams = params
|
||||||
"results" to
|
mapOf(
|
||||||
listOf(
|
"results" to
|
||||||
mapOf("connector" to "linear", "status" to "initiated"),
|
listOf(
|
||||||
),
|
mapOf("connector" to "linear", "status" to "initiated"),
|
||||||
"summary" to mapOf("total" to 1, "initiated" to 1),
|
),
|
||||||
)
|
"summary" to mapOf("total" to 1, "initiated" to 1),
|
||||||
},
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val result =
|
val result =
|
||||||
@@ -103,16 +105,17 @@ class ConnectorRepositoryTest {
|
|||||||
|
|
||||||
val repo =
|
val repo =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, params ->
|
caller =
|
||||||
recordedParams = params
|
fakeCaller { _, params ->
|
||||||
mapOf(
|
recordedParams = params
|
||||||
"results" to
|
mapOf(
|
||||||
listOf(
|
"results" to
|
||||||
mapOf("connector" to "slack", "status" to "active"),
|
listOf(
|
||||||
),
|
mapOf("connector" to "slack", "status" to "active"),
|
||||||
"summary" to mapOf("total" to 1, "active" to 1),
|
),
|
||||||
)
|
"summary" to mapOf("total" to 1, "active" to 1),
|
||||||
},
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
repo.connect(sessionId = "sess-789", connectors = listOf("slack"))
|
repo.connect(sessionId = "sess-789", connectors = listOf("slack"))
|
||||||
@@ -125,10 +128,11 @@ class ConnectorRepositoryTest {
|
|||||||
var rpcCalled = false
|
var rpcCalled = false
|
||||||
val repo =
|
val repo =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
rpcCalled = true
|
fakeCaller { _, _ ->
|
||||||
null
|
rpcCalled = true
|
||||||
},
|
null
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val r1 = repo.listConnectors("")
|
val r1 = repo.listConnectors("")
|
||||||
@@ -147,10 +151,11 @@ class ConnectorRepositoryTest {
|
|||||||
var rpcCalled = false
|
var rpcCalled = false
|
||||||
val repo =
|
val repo =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
rpcCalled = true
|
fakeCaller { _, _ ->
|
||||||
null
|
rpcCalled = true
|
||||||
},
|
null
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Blank session_id
|
// Blank session_id
|
||||||
@@ -172,10 +177,11 @@ class ConnectorRepositoryTest {
|
|||||||
var rpcCalled = false
|
var rpcCalled = false
|
||||||
val repo =
|
val repo =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
rpcCalled = true
|
fakeCaller { _, _ ->
|
||||||
null
|
rpcCalled = true
|
||||||
},
|
null
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Invalid slugs: uppercase, starting with hyphen/underscore, containing spaces/symbols
|
// Invalid slugs: uppercase, starting with hyphen/underscore, containing spaces/symbols
|
||||||
@@ -210,9 +216,10 @@ class ConnectorRepositoryTest {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
val repo =
|
val repo =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
throw CancellationException("coroutine cancelled")
|
fakeCaller { _, _ ->
|
||||||
},
|
throw CancellationException("coroutine cancelled")
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -236,13 +243,14 @@ class ConnectorRepositoryTest {
|
|||||||
// 4031 CONNECTORS_UNAVAILABLE
|
// 4031 CONNECTORS_UNAVAILABLE
|
||||||
val repo4031 =
|
val repo4031 =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
throw HermesWsClient.HermesRpcException(
|
fakeCaller { _, _ ->
|
||||||
message = "Connectors unavailable",
|
throw HermesWsClient.HermesRpcException(
|
||||||
code = 4031,
|
message = "Connectors unavailable",
|
||||||
data = JsonObject(mapOf("reason" to JsonPrimitive("CONNECTORS_UNAVAILABLE"))),
|
code = 4031,
|
||||||
)
|
data = JsonObject(mapOf("reason" to JsonPrimitive("CONNECTORS_UNAVAILABLE"))),
|
||||||
},
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val r4031 = repo4031.listConnectors("sess-1")
|
val r4031 = repo4031.listConnectors("sess-1")
|
||||||
assertTrue(r4031 is ConnectorListResult.Error)
|
assertTrue(r4031 is ConnectorListResult.Error)
|
||||||
@@ -251,13 +259,14 @@ class ConnectorRepositoryTest {
|
|||||||
// 4001 NOT_OWNER
|
// 4001 NOT_OWNER
|
||||||
val repo4001 =
|
val repo4001 =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
throw HermesWsClient.HermesRpcException(
|
fakeCaller { _, _ ->
|
||||||
message = "Session not owned",
|
throw HermesWsClient.HermesRpcException(
|
||||||
code = 4001,
|
message = "Session not owned",
|
||||||
data = JsonObject(mapOf("reason" to JsonPrimitive("NOT_OWNER"))),
|
code = 4001,
|
||||||
)
|
data = JsonObject(mapOf("reason" to JsonPrimitive("NOT_OWNER"))),
|
||||||
},
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val r4001 = repo4001.connect("sess-1", listOf("linear"))
|
val r4001 = repo4001.connect("sess-1", listOf("linear"))
|
||||||
assertTrue(r4001 is ConnectorConnectResult.Error)
|
assertTrue(r4001 is ConnectorConnectResult.Error)
|
||||||
@@ -266,13 +275,14 @@ class ConnectorRepositoryTest {
|
|||||||
// 5033 UNSUPPORTED_RUNTIME
|
// 5033 UNSUPPORTED_RUNTIME
|
||||||
val repo5033 =
|
val repo5033 =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
throw HermesWsClient.HermesRpcException(
|
fakeCaller { _, _ ->
|
||||||
message = "Compute host required",
|
throw HermesWsClient.HermesRpcException(
|
||||||
code = 5033,
|
message = "Compute host required",
|
||||||
data = JsonObject(mapOf("reason" to JsonPrimitive("UNSUPPORTED_RUNTIME"))),
|
code = 5033,
|
||||||
)
|
data = JsonObject(mapOf("reason" to JsonPrimitive("UNSUPPORTED_RUNTIME"))),
|
||||||
},
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val r5033 = repo5033.connect("sess-1", listOf("linear"))
|
val r5033 = repo5033.connect("sess-1", listOf("linear"))
|
||||||
assertTrue(r5033 is ConnectorConnectResult.Error)
|
assertTrue(r5033 is ConnectorConnectResult.Error)
|
||||||
@@ -281,13 +291,14 @@ class ConnectorRepositoryTest {
|
|||||||
// 5034 INVALID_CONNECTOR_RESPONSE
|
// 5034 INVALID_CONNECTOR_RESPONSE
|
||||||
val repo5034 =
|
val repo5034 =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
throw HermesWsClient.HermesRpcException(
|
fakeCaller { _, _ ->
|
||||||
message = "Invalid connector response",
|
throw HermesWsClient.HermesRpcException(
|
||||||
code = 5034,
|
message = "Invalid connector response",
|
||||||
data = JsonObject(mapOf("reason" to JsonPrimitive("INVALID_CONNECTOR_RESPONSE"))),
|
code = 5034,
|
||||||
)
|
data = JsonObject(mapOf("reason" to JsonPrimitive("INVALID_CONNECTOR_RESPONSE"))),
|
||||||
},
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val r5034 = repo5034.connect("sess-1", listOf("linear"))
|
val r5034 = repo5034.connect("sess-1", listOf("linear"))
|
||||||
assertTrue(r5034 is ConnectorConnectResult.Error)
|
assertTrue(r5034 is ConnectorConnectResult.Error)
|
||||||
@@ -296,13 +307,14 @@ class ConnectorRepositoryTest {
|
|||||||
// -32601 Unsupported backend
|
// -32601 Unsupported backend
|
||||||
val repo32601 =
|
val repo32601 =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
throw HermesWsClient.HermesRpcException(
|
fakeCaller { _, _ ->
|
||||||
message = "Method not found",
|
throw HermesWsClient.HermesRpcException(
|
||||||
code = -32601,
|
message = "Method not found",
|
||||||
data = null,
|
code = -32601,
|
||||||
)
|
data = null,
|
||||||
},
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val r32601 = repo32601.listConnectors("sess-1")
|
val r32601 = repo32601.listConnectors("sess-1")
|
||||||
assertTrue(r32601 is ConnectorListResult.Error)
|
assertTrue(r32601 is ConnectorListResult.Error)
|
||||||
@@ -314,9 +326,10 @@ class ConnectorRepositoryTest {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
val repo =
|
val repo =
|
||||||
HermesConnectorRepository(
|
HermesConnectorRepository(
|
||||||
rpcRequest = { _, _ ->
|
caller =
|
||||||
throw java.io.IOException("Connection reset by peer")
|
fakeCaller { _, _ ->
|
||||||
},
|
throw java.io.IOException("Connection reset by peer")
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val rList = repo.listConnectors("sess-1")
|
val rList = repo.listConnectors("sess-1")
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.m57.hermescontrol.data.ws
|
||||||
|
|
||||||
|
import com.m57.hermescontrol.data.remote.OkHttpProvider
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.RpcMethod
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.TypedRpcCaller
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test double for [TypedRpcCaller]: encodes the typed params exactly as the wire would, hands the
|
||||||
|
* resulting map to [handler] together with the method name, and returns its value as a JsonElement.
|
||||||
|
*/
|
||||||
|
internal fun fakeCaller(handler: suspend (String, Map<String, Any>) -> Any?): TypedRpcCaller =
|
||||||
|
object : TypedRpcCaller {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override suspend fun <P, R> call(
|
||||||
|
method: RpcMethod<P, R>,
|
||||||
|
params: P,
|
||||||
|
): R {
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(method.params, params)
|
||||||
|
val map = (encoded.toAny() as? Map<String, Any>).orEmpty()
|
||||||
|
return ConnectorParser.toJsonElement(handler(method.name, map)) as R
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,358 @@
|
|||||||
|
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.ConnectorOwner
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsConnectParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsListParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.ConnectorsPolicySetParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.HermesRpcCaller
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.RpcMethods
|
||||||
|
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.buildJsonArray
|
||||||
|
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 HermesWsClientConnectorsTest {
|
||||||
|
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 typedCallConnectorsConnectEncodesOwnerAccountReconnectTrueAndReturnsRawServerJsonUntouched() {
|
||||||
|
val opened = CountDownLatch(1)
|
||||||
|
val captured = CountDownLatch(1)
|
||||||
|
val frameRef = AtomicReference<JsonObject?>(null)
|
||||||
|
|
||||||
|
val serverResultJson =
|
||||||
|
buildJsonObject {
|
||||||
|
put("op_id", "op-connect-123")
|
||||||
|
put("status", "running")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == "connectors.connect") {
|
||||||
|
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.CONNECTORS_CONNECT,
|
||||||
|
ConnectorsConnectParams(
|
||||||
|
owner = ConnectorOwner.account(),
|
||||||
|
connectors = listOf("slack", "github"),
|
||||||
|
reconnect = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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("connectors.connect", (frame["method"] as? JsonPrimitive)?.content)
|
||||||
|
|
||||||
|
val params = frame["params"] as? JsonObject
|
||||||
|
val expectedParams =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "account")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
"connectors",
|
||||||
|
buildJsonArray {
|
||||||
|
add(JsonPrimitive("slack"))
|
||||||
|
add(JsonPrimitive("github"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put("reconnect", true)
|
||||||
|
}
|
||||||
|
assertEquals(expectedParams, params)
|
||||||
|
assertEquals(true, (params?.get("reconnect") as? JsonPrimitive)?.booleanOrNull)
|
||||||
|
assertFalse(params?.containsKey("profile") ?: true)
|
||||||
|
assertEquals(serverResultJson, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun typedCallConnectorsPolicySetEncodesNestedChangeWithArrayAndReturnsRawServerJsonUntouched() {
|
||||||
|
val opened = CountDownLatch(1)
|
||||||
|
val captured = CountDownLatch(1)
|
||||||
|
val frameRef = AtomicReference<JsonObject?>(null)
|
||||||
|
|
||||||
|
val serverResultJson =
|
||||||
|
buildJsonObject {
|
||||||
|
put("revision", 5)
|
||||||
|
put("applied", 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 == "connectors.policy.set") {
|
||||||
|
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 nestedChange =
|
||||||
|
buildJsonObject {
|
||||||
|
put("policy", "whitelist")
|
||||||
|
put(
|
||||||
|
"allowed_connectors",
|
||||||
|
buildJsonArray {
|
||||||
|
add(JsonPrimitive("notion"))
|
||||||
|
add(JsonPrimitive("linear"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
"rules",
|
||||||
|
buildJsonObject {
|
||||||
|
put("require_auth", true)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result: JsonElement =
|
||||||
|
runBlocking {
|
||||||
|
HermesWsClient.call(
|
||||||
|
RpcMethods.CONNECTORS_POLICY_SET,
|
||||||
|
ConnectorsPolicySetParams(
|
||||||
|
change = nestedChange,
|
||||||
|
expectedRevision = "4",
|
||||||
|
profile = "team-profile",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("Frame must reach the server", captured.await(5, TimeUnit.SECONDS))
|
||||||
|
val frame = requireNotNull(frameRef.get())
|
||||||
|
|
||||||
|
assertEquals("connectors.policy.set", (frame["method"] as? JsonPrimitive)?.content)
|
||||||
|
|
||||||
|
val params = frame["params"] as? JsonObject
|
||||||
|
val expectedParams =
|
||||||
|
buildJsonObject {
|
||||||
|
put("change", nestedChange)
|
||||||
|
put("expected_revision", "4")
|
||||||
|
put("profile", "team-profile")
|
||||||
|
}
|
||||||
|
assertEquals(expectedParams, params)
|
||||||
|
assertEquals(nestedChange, params?.get("change"))
|
||||||
|
assertEquals(serverResultJson, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun hermesRpcCallerCallDelegatesToHermesWsClientCallAndEncodesFrameParamsUntouched() {
|
||||||
|
val opened = CountDownLatch(1)
|
||||||
|
val captured = CountDownLatch(1)
|
||||||
|
val frameRef = AtomicReference<JsonObject?>(null)
|
||||||
|
|
||||||
|
val serverResultJson =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"connectors",
|
||||||
|
buildJsonArray {
|
||||||
|
add(
|
||||||
|
buildJsonObject {
|
||||||
|
put("id", "c1")
|
||||||
|
put("status", "connected")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == "connectors.list") {
|
||||||
|
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 {
|
||||||
|
HermesRpcCaller.call(
|
||||||
|
RpcMethods.CONNECTORS_LIST,
|
||||||
|
ConnectorsListParams(
|
||||||
|
owner = ConnectorOwner.session("s1"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("Frame must reach the server", captured.await(5, TimeUnit.SECONDS))
|
||||||
|
val frame = requireNotNull(frameRef.get())
|
||||||
|
|
||||||
|
assertEquals("connectors.list", (frame["method"] as? JsonPrimitive)?.content)
|
||||||
|
|
||||||
|
val params = frame["params"] as? JsonObject
|
||||||
|
val expectedParams =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "session")
|
||||||
|
put("session_id", "s1")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assertEquals(expectedParams, params)
|
||||||
|
assertEquals(serverResultJson, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -316,17 +316,6 @@ class RawRpcParamsRatchetTest {
|
|||||||
*/
|
*/
|
||||||
val BASELINE: List<String> =
|
val BASELINE: List<String> =
|
||||||
listOf(
|
listOf(
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_ACCOUNTS",
|
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_ACCOUNTS_REMOVE",
|
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_CATALOG",
|
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_CONNECT",
|
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_LIST",
|
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_OPERATION_STATUS",
|
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_POLICY_GET",
|
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_POLICY_SET",
|
|
||||||
"data/ws/AccountConnectorRepository.kt::CONNECTORS_TOOLS",
|
|
||||||
"data/ws/ConnectorRepository.kt::CONNECTORS_CONNECT",
|
|
||||||
"data/ws/ConnectorRepository.kt::CONNECTORS_LIST",
|
|
||||||
"data/ws/HermesWsClient.kt::CLIENT_CAPABILITIES",
|
"data/ws/HermesWsClient.kt::CLIENT_CAPABILITIES",
|
||||||
"data/ws/ModelOptionsRepository.kt::MODEL_OPTIONS",
|
"data/ws/ModelOptionsRepository.kt::MODEL_OPTIONS",
|
||||||
"data/ws/PluginManageRepository.kt::PLUGINS_MANAGE",
|
"data/ws/PluginManageRepository.kt::PLUGINS_MANAGE",
|
||||||
|
|||||||
@@ -0,0 +1,539 @@
|
|||||||
|
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.JsonPrimitive
|
||||||
|
import kotlinx.serialization.json.buildJsonArray
|
||||||
|
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.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ConnectorsTypesTest {
|
||||||
|
@Test
|
||||||
|
fun connectorOwnerAccountEncodesToTypeAccountWithoutSessionId() {
|
||||||
|
val owner = ConnectorOwner.account()
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorOwner.serializer(), owner)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "account")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("type"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorOwnerSessionEncodesToTypeSessionAndSessionId() {
|
||||||
|
val owner = ConnectorOwner.session("s1")
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorOwner.serializer(), owner)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "session")
|
||||||
|
put("session_id", "s1")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("type", "session_id"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsListParamsEncodesOwnerAndProfileWhenProfileProvided() {
|
||||||
|
val params = ConnectorsListParams(owner = ConnectorOwner.account(), profile = "work")
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsListParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "account")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put("profile", "work")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("owner", "profile"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsListParamsOmitsProfileWhenNull() {
|
||||||
|
val params = ConnectorsListParams(owner = ConnectorOwner.session("s1"), profile = null)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsListParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "session")
|
||||||
|
put("session_id", "s1")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("owner"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsConnectParamsEncodesAllFieldsWhenPresent() {
|
||||||
|
val params =
|
||||||
|
ConnectorsConnectParams(
|
||||||
|
owner = ConnectorOwner.account(),
|
||||||
|
connectors = listOf("slack", "github"),
|
||||||
|
reconnect = true,
|
||||||
|
profile = "personal",
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsConnectParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "account")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
"connectors",
|
||||||
|
buildJsonArray {
|
||||||
|
add(JsonPrimitive("slack"))
|
||||||
|
add(JsonPrimitive("github"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put("reconnect", true)
|
||||||
|
put("profile", "personal")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("owner", "connectors", "reconnect", "profile"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsConnectParamsEncodesExplicitFalseForReconnect() {
|
||||||
|
val params =
|
||||||
|
ConnectorsConnectParams(
|
||||||
|
owner = ConnectorOwner.session("s2"),
|
||||||
|
connectors = listOf("jira"),
|
||||||
|
reconnect = false,
|
||||||
|
profile = null,
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsConnectParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "session")
|
||||||
|
put("session_id", "s2")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
"connectors",
|
||||||
|
buildJsonArray {
|
||||||
|
add(JsonPrimitive("jira"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put("reconnect", false)
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("owner", "connectors", "reconnect"), (encoded as JsonObject).keys)
|
||||||
|
assertFalse((encoded as JsonObject).containsKey("profile"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsConnectParamsOmitsNullReconnectAndProfile() {
|
||||||
|
val params =
|
||||||
|
ConnectorsConnectParams(
|
||||||
|
owner = ConnectorOwner.account(),
|
||||||
|
connectors = emptyList(),
|
||||||
|
reconnect = null,
|
||||||
|
profile = null,
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsConnectParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "account")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put("connectors", buildJsonArray {})
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("owner", "connectors"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsOperationStatusParamsEncodesAllFieldsWhenPresent() {
|
||||||
|
val params =
|
||||||
|
ConnectorsOperationStatusParams(
|
||||||
|
owner = ConnectorOwner.session("sess-1"),
|
||||||
|
opId = "op-99",
|
||||||
|
profile = "work",
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsOperationStatusParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "session")
|
||||||
|
put("session_id", "sess-1")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put("op_id", "op-99")
|
||||||
|
put("profile", "work")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("owner", "op_id", "profile"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsOperationStatusParamsOmitsNullProfile() {
|
||||||
|
val params =
|
||||||
|
ConnectorsOperationStatusParams(
|
||||||
|
owner = ConnectorOwner.account(),
|
||||||
|
opId = "op-100",
|
||||||
|
profile = null,
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsOperationStatusParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put(
|
||||||
|
"owner",
|
||||||
|
buildJsonObject {
|
||||||
|
put("type", "account")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put("op_id", "op-100")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("owner", "op_id"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsCatalogParamsEncodesProfileWhenPresent() {
|
||||||
|
val params = ConnectorsCatalogParams(profile = "default")
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsCatalogParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("profile", "default")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("profile"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsCatalogParamsOmitsNullProfileResultingInEmptyJsonObject() {
|
||||||
|
val params = ConnectorsCatalogParams(profile = null)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsCatalogParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected = buildJsonObject {}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertTrue((encoded as JsonObject).isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsAccountsParamsEncodesAllFieldsWhenPresent() {
|
||||||
|
val params = ConnectorsAccountsParams(connector = "google", profile = "work")
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsAccountsParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("connector", "google")
|
||||||
|
put("profile", "work")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("connector", "profile"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsAccountsParamsOmitsNullFieldsResultingInEmptyJsonObject() {
|
||||||
|
val params = ConnectorsAccountsParams(connector = null, profile = null)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsAccountsParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected = buildJsonObject {}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertTrue((encoded as JsonObject).isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsAccountsParamsEncodesConnectorOnlyWhenProfileNull() {
|
||||||
|
val params = ConnectorsAccountsParams(connector = "github", profile = null)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsAccountsParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("connector", "github")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("connector"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsAccountsRemoveParamsEncodesConnectionIdAndProfile() {
|
||||||
|
val params = ConnectorsAccountsRemoveParams(connectionId = "conn-123", profile = "work")
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsAccountsRemoveParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("connection_id", "conn-123")
|
||||||
|
put("profile", "work")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("connection_id", "profile"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsAccountsRemoveParamsOmitsNullProfile() {
|
||||||
|
val params = ConnectorsAccountsRemoveParams(connectionId = "conn-456", profile = null)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsAccountsRemoveParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("connection_id", "conn-456")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("connection_id"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsPolicyGetParamsEncodesProfileWhenPresent() {
|
||||||
|
val params = ConnectorsPolicyGetParams(profile = "custom-profile")
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsPolicyGetParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("profile", "custom-profile")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("profile"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsPolicyGetParamsOmitsNullProfileResultingInEmptyJsonObject() {
|
||||||
|
val params = ConnectorsPolicyGetParams(profile = null)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsPolicyGetParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected = buildJsonObject {}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertTrue((encoded as JsonObject).isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsPolicySetParamsEncodesNestedChangeWithArraysUntouched() {
|
||||||
|
val nestedChange =
|
||||||
|
buildJsonObject {
|
||||||
|
put("action", "allow")
|
||||||
|
put(
|
||||||
|
"tools",
|
||||||
|
buildJsonArray {
|
||||||
|
add(JsonPrimitive("gmail.send"))
|
||||||
|
add(JsonPrimitive("gmail.list"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
"scope",
|
||||||
|
buildJsonObject {
|
||||||
|
put("level", "read-write")
|
||||||
|
put(
|
||||||
|
"allowed_users",
|
||||||
|
buildJsonArray {
|
||||||
|
add(JsonPrimitive("alice"))
|
||||||
|
add(JsonPrimitive("bob"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val params =
|
||||||
|
ConnectorsPolicySetParams(
|
||||||
|
change = nestedChange,
|
||||||
|
expectedRevision = "4",
|
||||||
|
profile = "work",
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsPolicySetParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("change", nestedChange)
|
||||||
|
put("expected_revision", "4")
|
||||||
|
put("profile", "work")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("change", "expected_revision", "profile"), (encoded as JsonObject).keys)
|
||||||
|
assertEquals(nestedChange, (encoded as JsonObject)["change"])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsPolicySetParamsOmitsNullProfile() {
|
||||||
|
val change = buildJsonObject { put("enabled", true) }
|
||||||
|
val params =
|
||||||
|
ConnectorsPolicySetParams(
|
||||||
|
change = change,
|
||||||
|
expectedRevision = "1",
|
||||||
|
profile = null,
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsPolicySetParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("change", change)
|
||||||
|
put("expected_revision", "1")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("change", "expected_revision"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsToolsParamsEncodesAllFieldsWhenPresent() {
|
||||||
|
val params =
|
||||||
|
ConnectorsToolsParams(
|
||||||
|
slug = "gmail",
|
||||||
|
refresh = true,
|
||||||
|
profile = "work",
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsToolsParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("slug", "gmail")
|
||||||
|
put("refresh", true)
|
||||||
|
put("profile", "work")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("slug", "refresh", "profile"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsToolsParamsEncodesExplicitFalseForRefresh() {
|
||||||
|
val params =
|
||||||
|
ConnectorsToolsParams(
|
||||||
|
slug = "slack",
|
||||||
|
refresh = false,
|
||||||
|
profile = null,
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsToolsParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("slug", "slack")
|
||||||
|
put("refresh", false)
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("slug", "refresh"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connectorsToolsParamsOmitsNullRefreshAndProfile() {
|
||||||
|
val params =
|
||||||
|
ConnectorsToolsParams(
|
||||||
|
slug = "github",
|
||||||
|
refresh = null,
|
||||||
|
profile = null,
|
||||||
|
)
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(ConnectorsToolsParams.serializer(), params)
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
buildJsonObject {
|
||||||
|
put("slug", "github")
|
||||||
|
}
|
||||||
|
assertEquals(expected, encoded)
|
||||||
|
assertTrue(encoded is JsonObject)
|
||||||
|
assertEquals(setOf("slug"), (encoded as JsonObject).keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun rpcMethodsRegistrationMatchesContract() {
|
||||||
|
assertEquals("connectors.list", RpcMethods.CONNECTORS_LIST.name)
|
||||||
|
assertEquals("connectors.connect", RpcMethods.CONNECTORS_CONNECT.name)
|
||||||
|
assertEquals("connectors.operation.status", RpcMethods.CONNECTORS_OPERATION_STATUS.name)
|
||||||
|
assertEquals("connectors.catalog", RpcMethods.CONNECTORS_CATALOG.name)
|
||||||
|
assertEquals("connectors.accounts", RpcMethods.CONNECTORS_ACCOUNTS.name)
|
||||||
|
assertEquals("connectors.accounts.remove", RpcMethods.CONNECTORS_ACCOUNTS_REMOVE.name)
|
||||||
|
assertEquals("connectors.policy.get", RpcMethods.CONNECTORS_POLICY_GET.name)
|
||||||
|
assertEquals("connectors.policy.set", RpcMethods.CONNECTORS_POLICY_SET.name)
|
||||||
|
assertEquals("connectors.tools", RpcMethods.CONNECTORS_TOOLS.name)
|
||||||
|
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_LIST))
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_CONNECT))
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_OPERATION_STATUS))
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_CATALOG))
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_ACCOUNTS))
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_ACCOUNTS_REMOVE))
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_POLICY_GET))
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_POLICY_SET))
|
||||||
|
assertTrue(RpcMethods.all.contains(RpcMethods.CONNECTORS_TOOLS))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun jsonElementPassthroughMethodsDecodeResultUntouched() {
|
||||||
|
val arbitraryPayload =
|
||||||
|
buildJsonObject {
|
||||||
|
put("status", "ok")
|
||||||
|
put(
|
||||||
|
"items",
|
||||||
|
buildJsonArray {
|
||||||
|
add(JsonPrimitive("elem1"))
|
||||||
|
add(JsonPrimitive("elem2"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
put("nested", buildJsonObject { put("revision", 10) })
|
||||||
|
}
|
||||||
|
|
||||||
|
val methods =
|
||||||
|
listOf(
|
||||||
|
RpcMethods.CONNECTORS_LIST,
|
||||||
|
RpcMethods.CONNECTORS_CONNECT,
|
||||||
|
RpcMethods.CONNECTORS_OPERATION_STATUS,
|
||||||
|
RpcMethods.CONNECTORS_CATALOG,
|
||||||
|
RpcMethods.CONNECTORS_ACCOUNTS,
|
||||||
|
RpcMethods.CONNECTORS_ACCOUNTS_REMOVE,
|
||||||
|
RpcMethods.CONNECTORS_POLICY_GET,
|
||||||
|
RpcMethods.CONNECTORS_POLICY_SET,
|
||||||
|
RpcMethods.CONNECTORS_TOOLS,
|
||||||
|
)
|
||||||
|
|
||||||
|
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