From 67529547ff4aae6b6d675cdc9763762eeb9db1fb Mon Sep 17 00:00:00 2001 From: tomastiminskas Date: Fri, 24 Jul 2026 19:25:36 +0000 Subject: [PATCH] Generated with Hive: Add Hive authentication modules and repository orchestration method --- settings.gradle | 2 + .../SphinxRepositoryAndroid.kt | 3 + .../features/feature-repository/build.gradle | 2 + .../feature_repository/SphinxRepository.kt | 36 +++ .../HiveAuthenticationUnitTest.kt | 276 ++++++++++++++++++ .../concept-network-query-hive/build.gradle | 16 + .../NetworkQueryHive.kt | 15 + .../model/HiveAuthRequestDto.kt | 11 + .../model/HiveAuthenticationTokenDto.kt | 6 + .../feature-network-query-hive/build.gradle | 21 ++ .../NetworkQueryHiveImpl.kt | 31 ++ .../HiveAuthRequestDtoUnitTest.kt | 62 ++++ sphinx/application/sphinx/build.gradle | 1 + .../main/java/chat/sphinx/di/NetworkModule.kt | 14 + .../java/chat/sphinx/di/RepositoryModule.kt | 3 + 15 files changed, 499 insertions(+) create mode 100644 sphinx/application/data/features/feature-repository/src/test/java/chat/sphinx/feature_repository/HiveAuthenticationUnitTest.kt create mode 100644 sphinx/application/network/concepts/queries/concept-network-query-hive/build.gradle create mode 100644 sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/NetworkQueryHive.kt create mode 100644 sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/model/HiveAuthRequestDto.kt create mode 100644 sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/model/HiveAuthenticationTokenDto.kt create mode 100644 sphinx/application/network/features/queries/feature-network-query-hive/build.gradle create mode 100644 sphinx/application/network/features/queries/feature-network-query-hive/src/main/java/chat/sphinx/feature_network_query_hive/NetworkQueryHiveImpl.kt create mode 100644 sphinx/application/network/features/queries/feature-network-query-hive/src/test/java/chat/sphinx/feature_network_query_hive/HiveAuthRequestDtoUnitTest.kt diff --git a/settings.gradle b/settings.gradle index e265afae..358467a2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -99,6 +99,8 @@ include ':sphinx:application:network:concepts:queries:concept-network-query-disc include ':sphinx:application:network:features:queries:feature-network-query-discover-tribes' include ':sphinx:application:network:concepts:queries:concept-network-query-feed-status' include ':sphinx:application:network:features:queries:feature-network-query-feed-status' +include ':sphinx:application:network:concepts:queries:concept-network-query-hive' +include ':sphinx:application:network:features:queries:feature-network-query-hive' // Activity include ':sphinx:activity:hilt-qualifiers' diff --git a/sphinx/application/data/features/feature-repository-android/src/main/java/chat/sphinx/feature_repository_android/SphinxRepositoryAndroid.kt b/sphinx/application/data/features/feature-repository-android/src/main/java/chat/sphinx/feature_repository_android/SphinxRepositoryAndroid.kt index 09bdd217..e92cb6e8 100644 --- a/sphinx/application/data/features/feature-repository-android/src/main/java/chat/sphinx/feature_repository_android/SphinxRepositoryAndroid.kt +++ b/sphinx/application/data/features/feature-repository-android/src/main/java/chat/sphinx/feature_repository_android/SphinxRepositoryAndroid.kt @@ -14,6 +14,7 @@ import chat.sphinx.concept_network_query_invite.NetworkQueryInvite import chat.sphinx.concept_network_query_meme_server.NetworkQueryMemeServer import chat.sphinx.concept_network_query_feed_search.NetworkQueryFeedSearch import chat.sphinx.concept_network_query_feed_status.NetworkQueryFeedStatus +import chat.sphinx.concept_network_query_hive.NetworkQueryHive import chat.sphinx.concept_network_query_people.NetworkQueryPeople import chat.sphinx.concept_network_query_verify_external.NetworkQueryAuthorizeExternal import chat.sphinx.concept_paging.PageSourceWrapper @@ -61,6 +62,7 @@ class SphinxRepositoryAndroid( networkQueryPeople: NetworkQueryPeople, networkQueryFeedSearch: NetworkQueryFeedSearch, networkQueryFeedStatus: NetworkQueryFeedStatus, + networkQueryHive: NetworkQueryHive, connectManager: ConnectManager, dataSyncManager: DataSyncManager, walletDataHandler: WalletDataHandler, @@ -88,6 +90,7 @@ class SphinxRepositoryAndroid( networkQueryPeople, networkQueryFeedSearch, networkQueryFeedStatus, + networkQueryHive, connectManager, dataSyncManager, walletDataHandler, diff --git a/sphinx/application/data/features/feature-repository/build.gradle b/sphinx/application/data/features/feature-repository/build.gradle index 20d67407..2a8ffefc 100644 --- a/sphinx/application/data/features/feature-repository/build.gradle +++ b/sphinx/application/data/features/feature-repository/build.gradle @@ -36,6 +36,7 @@ dependencies { api project(path: ':sphinx:application:network:concepts:concept-meme-input-stream') api project(path: ':sphinx:application:network:concepts:queries:concept-network-query-meme-server') api project(path: ':sphinx:application:network:concepts:queries:concept-network-query-discover-tribes') + api project(path: ':sphinx:application:network:concepts:queries:concept-network-query-hive') api project(path: ':sphinx:application:network:concepts:queries:concept-network-query-chat') api project(path: ':sphinx:application:network:concepts:queries:concept-network-query-contact') api project(path: ':sphinx:application:network:concepts:queries:concept-network-query-invite') @@ -62,4 +63,5 @@ dependencies { testImplementation deps.square.sqlDelightJvm testImplementation project(path: ':sphinx:test:test-network-query') + testImplementation testDeps.junit } diff --git a/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt b/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt index 9ed7d795..817ea711 100644 --- a/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt +++ b/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt @@ -14,6 +14,7 @@ import chat.sphinx.concept_network_query_discover_tribes.NetworkQueryDiscoverTri import chat.sphinx.concept_network_query_feed_search.NetworkQueryFeedSearch import chat.sphinx.concept_network_query_feed_search.model.toFeedSearchResult import chat.sphinx.concept_network_query_feed_status.NetworkQueryFeedStatus +import chat.sphinx.concept_network_query_hive.NetworkQueryHive import chat.sphinx.concept_network_query_feed_status.model.ContentFeedStatusDto import chat.sphinx.concept_network_query_feed_status.model.EpisodeStatusDto import chat.sphinx.concept_network_query_invite.NetworkQueryInvite @@ -208,6 +209,7 @@ abstract class SphinxRepository( private val networkQueryPeople: NetworkQueryPeople, private val networkQueryFeedSearch: NetworkQueryFeedSearch, private val networkQueryFeedStatus: NetworkQueryFeedStatus, + private val networkQueryHive: NetworkQueryHive, private val connectManager: ConnectManager, private val dataSyncManager: DataSyncManager, private val walletDataHandler: WalletDataHandler, @@ -236,6 +238,7 @@ abstract class SphinxRepository( // PersistentStorage Keys const val REPOSITORY_LIGHTNING_BALANCE = "REPOSITORY_LIGHTNING_BALANCE" const val REPOSITORY_PUSH_KEY = "REPOSITORY_PUSH_KEY" + const val HIVE_AUTHENTICATION_TOKEN = "HIVE_AUTHENTICATION_TOKEN" const val MEDIA_KEY_SIZE = 32 @@ -9596,6 +9599,39 @@ abstract class SphinxRepository( return byteArray } + + ///////////////// + /// Hive Auth /// + ///////////////// + + suspend fun authenticateWithHive(): Boolean { + // Short-circuit: avoid redundant Rust FFI call + network round-trip if already authenticated + retrieveHiveToken()?.let { return true } + + val signedToken = connectManager.getSignedTimeStamps() ?: return false + val pubkey = accountOwner.value?.nodePubKey?.value ?: return false + val timestamp = System.currentTimeMillis().toString() + + var success = false + networkQueryHive.authenticateWithHive(signedToken, pubkey, timestamp) + .collect { response -> + when (response) { + is Response.Success -> { + val jwt = response.value.token + ?.takeIf { it.isNotBlank() } + ?: return@collect + authenticationStorage.putString(HIVE_AUTHENTICATION_TOKEN, jwt) + success = true + } + else -> { /* success remains false */ } + } + } + return success + } + + suspend fun retrieveHiveToken(): String? { + return authenticationStorage.getString(HIVE_AUTHENTICATION_TOKEN, null) + } } @Suppress("NOTHING_TO_INLINE") diff --git a/sphinx/application/data/features/feature-repository/src/test/java/chat/sphinx/feature_repository/HiveAuthenticationUnitTest.kt b/sphinx/application/data/features/feature-repository/src/test/java/chat/sphinx/feature_repository/HiveAuthenticationUnitTest.kt new file mode 100644 index 00000000..ff32b9b2 --- /dev/null +++ b/sphinx/application/data/features/feature-repository/src/test/java/chat/sphinx/feature_repository/HiveAuthenticationUnitTest.kt @@ -0,0 +1,276 @@ +package chat.sphinx.feature_repository + +import chat.sphinx.concept_network_query_hive.NetworkQueryHive +import chat.sphinx.concept_network_query_hive.model.HiveAuthenticationTokenDto +import chat.sphinx.kotlin_response.LoadResponse +import chat.sphinx.kotlin_response.Response +import chat.sphinx.kotlin_response.ResponseError +import io.matthewnelson.concept_authentication.data.AuthenticationStorage +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Unit tests for SphinxRepository.authenticateWithHive() and retrieveHiveToken(). + * + * Uses a standalone helper class that exercises only the Hive auth logic + * without instantiating the full abstract SphinxRepository. + */ +class HiveAuthenticationUnitTest { + + // ───────────────────────────────────────────────────────────────────────── + // Minimal fakes + // ───────────────────────────────────────────────────────────────────────── + + private class FakeAuthenticationStorage( + initial: Map = emptyMap() + ) : AuthenticationStorage { + val storage = mutableMapOf().also { it.putAll(initial) } + + override suspend fun getString(key: String, defaultValue: String?): String? = + if (storage.containsKey(key)) storage[key] else defaultValue + + override suspend fun putString(key: String, value: String?) { + storage[key] = value + } + + override suspend fun removeString(key: String) { + storage.remove(key) + } + } + + private class FakeNetworkQueryHive( + private val responseBuilder: (String, String, String) -> Flow> + ) : NetworkQueryHive() { + var callCount = 0 + + override fun authenticateWithHive( + token: String, + pubkey: String, + timestamp: String + ): Flow> { + callCount++ + return responseBuilder(token, pubkey, timestamp) + } + } + + // ───────────────────────────────────────────────────────────────────────── + // Extracted logic under test (mirrors SphinxRepository implementation) + // ───────────────────────────────────────────────────────────────────────── + + /** + * Encapsulates only the Hive auth logic from SphinxRepository so we can test + * it in isolation without the full dependency graph. + */ + private class HiveAuthLogic( + private val authenticationStorage: AuthenticationStorage, + private val networkQueryHive: NetworkQueryHive, + private val signedTimestampProvider: () -> String?, + private val pubkeyProvider: () -> String? + ) { + suspend fun authenticateWithHive(): Boolean { + retrieveHiveToken()?.let { return true } + + val signedToken = signedTimestampProvider() ?: return false + val pubkey = pubkeyProvider() ?: return false + val timestamp = System.currentTimeMillis().toString() + + var success = false + networkQueryHive.authenticateWithHive(signedToken, pubkey, timestamp) + .collect { response -> + when (response) { + is Response.Success -> { + val jwt = response.value.token + ?.takeIf { it.isNotBlank() } + ?: return@collect + authenticationStorage.putString( + SphinxRepository.HIVE_AUTHENTICATION_TOKEN, jwt + ) + success = true + } + else -> { /* success remains false */ } + } + } + return success + } + + suspend fun retrieveHiveToken(): String? = + authenticationStorage.getString(SphinxRepository.HIVE_AUTHENTICATION_TOKEN, null) + } + + // ───────────────────────────────────────────────────────────────────────── + // Tests — retrieveHiveToken + // ───────────────────────────────────────────────────────────────────────── + + @Test + fun `retrieveHiveToken returns null when no token stored`() = runBlocking { + val storage = FakeAuthenticationStorage() + val query = FakeNetworkQueryHive { _, _, _ -> flow {} } + val logic = HiveAuthLogic(storage, query, { "sig" }, { "pk" }) + + assertNull(logic.retrieveHiveToken()) + } + + @Test + fun `retrieveHiveToken returns stored token`() = runBlocking { + val storage = FakeAuthenticationStorage( + mapOf(SphinxRepository.HIVE_AUTHENTICATION_TOKEN to "stored_jwt") + ) + val query = FakeNetworkQueryHive { _, _, _ -> flow {} } + val logic = HiveAuthLogic(storage, query, { "sig" }, { "pk" }) + + assertEquals("stored_jwt", logic.retrieveHiveToken()) + } + + // ───────────────────────────────────────────────────────────────────────── + // Tests — authenticateWithHive + // ───────────────────────────────────────────────────────────────────────── + + @Test + fun `returns true immediately when token already cached - no network call made`() = runBlocking { + val storage = FakeAuthenticationStorage( + mapOf(SphinxRepository.HIVE_AUTHENTICATION_TOKEN to "existing_jwt") + ) + val query = FakeNetworkQueryHive { _, _, _ -> flow {} } + val logic = HiveAuthLogic( + storage, query, + signedTimestampProvider = { error("should not be called") }, + pubkeyProvider = { error("should not be called") } + ) + + val result = logic.authenticateWithHive() + + assertTrue(result) + assertEquals(0, query.callCount) + } + + @Test + fun `returns false when signed token is null - no network call made`() = runBlocking { + val storage = FakeAuthenticationStorage() + val query = FakeNetworkQueryHive { _, _, _ -> flow {} } + val logic = HiveAuthLogic(storage, query, + signedTimestampProvider = { null }, + pubkeyProvider = { "pubkey123" } + ) + + val result = logic.authenticateWithHive() + + assertFalse(result) + assertEquals(0, query.callCount) + assertNull(storage.storage[SphinxRepository.HIVE_AUTHENTICATION_TOKEN]) + } + + @Test + fun `returns false when pubkey is null - no network call made`() = runBlocking { + val storage = FakeAuthenticationStorage() + val query = FakeNetworkQueryHive { _, _, _ -> flow {} } + val logic = HiveAuthLogic(storage, query, + signedTimestampProvider = { "signed_ts" }, + pubkeyProvider = { null } + ) + + val result = logic.authenticateWithHive() + + assertFalse(result) + assertEquals(0, query.callCount) + assertNull(storage.storage[SphinxRepository.HIVE_AUTHENTICATION_TOKEN]) + } + + @Test + fun `returns true and stores token on success with valid jwt`() = runBlocking { + val storage = FakeAuthenticationStorage() + val expectedJwt = "valid.hive.jwt" + val query = FakeNetworkQueryHive { _, _, _ -> + flow { emit(Response.Success(HiveAuthenticationTokenDto(expectedJwt))) } + } + val logic = HiveAuthLogic(storage, query, + signedTimestampProvider = { "signed_ts" }, + pubkeyProvider = { "pubkey123" } + ) + + val result = logic.authenticateWithHive() + + assertTrue(result) + assertEquals(expectedJwt, storage.storage[SphinxRepository.HIVE_AUTHENTICATION_TOKEN]) + } + + @Test + fun `returns false and does not store when response token is null`() = runBlocking { + val storage = FakeAuthenticationStorage() + val query = FakeNetworkQueryHive { _, _, _ -> + flow { emit(Response.Success(HiveAuthenticationTokenDto(null))) } + } + val logic = HiveAuthLogic(storage, query, + signedTimestampProvider = { "signed_ts" }, + pubkeyProvider = { "pubkey123" } + ) + + val result = logic.authenticateWithHive() + + assertFalse(result) + assertNull(storage.storage[SphinxRepository.HIVE_AUTHENTICATION_TOKEN]) + } + + @Test + fun `returns false and does not store when response token is blank`() = runBlocking { + val storage = FakeAuthenticationStorage() + val query = FakeNetworkQueryHive { _, _, _ -> + flow { emit(Response.Success(HiveAuthenticationTokenDto(" "))) } + } + val logic = HiveAuthLogic(storage, query, + signedTimestampProvider = { "signed_ts" }, + pubkeyProvider = { "pubkey123" } + ) + + val result = logic.authenticateWithHive() + + assertFalse(result) + assertNull(storage.storage[SphinxRepository.HIVE_AUTHENTICATION_TOKEN]) + } + + @Test + fun `returns false and does not store when response token is empty string`() = runBlocking { + val storage = FakeAuthenticationStorage() + val query = FakeNetworkQueryHive { _, _, _ -> + flow { emit(Response.Success(HiveAuthenticationTokenDto(""))) } + } + val logic = HiveAuthLogic(storage, query, + signedTimestampProvider = { "signed_ts" }, + pubkeyProvider = { "pubkey123" } + ) + + val result = logic.authenticateWithHive() + + assertFalse(result) + assertNull(storage.storage[SphinxRepository.HIVE_AUTHENTICATION_TOKEN]) + } + + @Test + fun `returns false on network error and storage is untouched`() = runBlocking { + val storage = FakeAuthenticationStorage() + val query = FakeNetworkQueryHive { _, _, _ -> + flow { + emit(Response.Error(ResponseError("network failure"))) + } + } + val logic = HiveAuthLogic(storage, query, + signedTimestampProvider = { "signed_ts" }, + pubkeyProvider = { "pubkey123" } + ) + + val result = logic.authenticateWithHive() + + assertFalse(result) + assertNull(storage.storage[SphinxRepository.HIVE_AUTHENTICATION_TOKEN]) + } + + @Test + fun `HIVE_AUTHENTICATION_TOKEN constant has expected value`() { + assertEquals("HIVE_AUTHENTICATION_TOKEN", SphinxRepository.HIVE_AUTHENTICATION_TOKEN) + } +} diff --git a/sphinx/application/network/concepts/queries/concept-network-query-hive/build.gradle b/sphinx/application/network/concepts/queries/concept-network-query-hive/build.gradle new file mode 100644 index 00000000..4ac94a5f --- /dev/null +++ b/sphinx/application/network/concepts/queries/concept-network-query-hive/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java-library' + id 'kotlin' + id 'kotlin-kapt' +} + +dependencies { + api project(path: ':sphinx:application:common:kotlin-response') + + implementation deps.kotlin.coroutinesCore + + // needed to override moshi's use of 1.4.31 + implementation deps.kotlin.reflect + implementation deps.square.moshi + kapt kaptDeps.square.moshiCodegen +} diff --git a/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/NetworkQueryHive.kt b/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/NetworkQueryHive.kt new file mode 100644 index 00000000..d3ac53a7 --- /dev/null +++ b/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/NetworkQueryHive.kt @@ -0,0 +1,15 @@ +package chat.sphinx.concept_network_query_hive + +import chat.sphinx.concept_network_query_hive.model.HiveAuthenticationTokenDto +import chat.sphinx.kotlin_response.LoadResponse +import chat.sphinx.kotlin_response.ResponseError +import kotlinx.coroutines.flow.Flow + +abstract class NetworkQueryHive { + + abstract fun authenticateWithHive( + token: String, + pubkey: String, + timestamp: String + ): Flow> +} diff --git a/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/model/HiveAuthRequestDto.kt b/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/model/HiveAuthRequestDto.kt new file mode 100644 index 00000000..f4c3ddd1 --- /dev/null +++ b/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/model/HiveAuthRequestDto.kt @@ -0,0 +1,11 @@ +package chat.sphinx.concept_network_query_hive.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class HiveAuthRequestDto( + @Json(name = "token") val token: String, + @Json(name = "pubkey") val pubkey: String, + @Json(name = "timestamp") val timestamp: String +) diff --git a/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/model/HiveAuthenticationTokenDto.kt b/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/model/HiveAuthenticationTokenDto.kt new file mode 100644 index 00000000..7e0f9f98 --- /dev/null +++ b/sphinx/application/network/concepts/queries/concept-network-query-hive/src/main/java/chat/sphinx/concept_network_query_hive/model/HiveAuthenticationTokenDto.kt @@ -0,0 +1,6 @@ +package chat.sphinx.concept_network_query_hive.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class HiveAuthenticationTokenDto(val token: String?) diff --git a/sphinx/application/network/features/queries/feature-network-query-hive/build.gradle b/sphinx/application/network/features/queries/feature-network-query-hive/build.gradle new file mode 100644 index 00000000..192fca42 --- /dev/null +++ b/sphinx/application/network/features/queries/feature-network-query-hive/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java-library' + id 'kotlin' + id 'kotlin-kapt' +} + +dependencies { + api project(path: ':sphinx:application:network:concepts:queries:concept-network-query-hive') + api project(path: ':sphinx:application:network:concepts:calls:concept-network-relay-call') + + implementation deps.kotlin.coroutinesCore + + // needed to override moshi's use of 1.4.31 + implementation deps.kotlin.reflect + implementation deps.square.moshi + kapt kaptDeps.square.moshiCodegen + + testImplementation project(path: ':sphinx:test:test-network-query') + testImplementation testDeps.junit + testImplementation deps.kotlin.reflect +} diff --git a/sphinx/application/network/features/queries/feature-network-query-hive/src/main/java/chat/sphinx/feature_network_query_hive/NetworkQueryHiveImpl.kt b/sphinx/application/network/features/queries/feature-network-query-hive/src/main/java/chat/sphinx/feature_network_query_hive/NetworkQueryHiveImpl.kt new file mode 100644 index 00000000..5f6b7794 --- /dev/null +++ b/sphinx/application/network/features/queries/feature-network-query-hive/src/main/java/chat/sphinx/feature_network_query_hive/NetworkQueryHiveImpl.kt @@ -0,0 +1,31 @@ +package chat.sphinx.feature_network_query_hive + +import chat.sphinx.concept_network_call.NetworkCall +import chat.sphinx.concept_network_query_hive.NetworkQueryHive +import chat.sphinx.concept_network_query_hive.model.HiveAuthRequestDto +import chat.sphinx.concept_network_query_hive.model.HiveAuthenticationTokenDto +import chat.sphinx.kotlin_response.LoadResponse +import chat.sphinx.kotlin_response.ResponseError +import kotlinx.coroutines.flow.Flow + +class NetworkQueryHiveImpl( + private val networkCall: NetworkCall +) : NetworkQueryHive() { + + companion object { + const val HIVE_BASE_URL = "https://hive.sphinx.chat/api" + const val ENDPOINT_AUTH = "/auth/sphinx/token" + } + + override fun authenticateWithHive( + token: String, + pubkey: String, + timestamp: String + ): Flow> = + networkCall.post( + url = HIVE_BASE_URL + ENDPOINT_AUTH, + responseJsonClass = HiveAuthenticationTokenDto::class.java, + requestBodyJsonClass = HiveAuthRequestDto::class.java, + requestBody = HiveAuthRequestDto(token, pubkey, timestamp) + ) +} diff --git a/sphinx/application/network/features/queries/feature-network-query-hive/src/test/java/chat/sphinx/feature_network_query_hive/HiveAuthRequestDtoUnitTest.kt b/sphinx/application/network/features/queries/feature-network-query-hive/src/test/java/chat/sphinx/feature_network_query_hive/HiveAuthRequestDtoUnitTest.kt new file mode 100644 index 00000000..dbcfa471 --- /dev/null +++ b/sphinx/application/network/features/queries/feature-network-query-hive/src/test/java/chat/sphinx/feature_network_query_hive/HiveAuthRequestDtoUnitTest.kt @@ -0,0 +1,62 @@ +package chat.sphinx.feature_network_query_hive + +import chat.sphinx.concept_network_query_hive.model.HiveAuthRequestDto +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Test + +class HiveAuthRequestDtoUnitTest { + + private val moshi: Moshi = Moshi.Builder() + .addLast(KotlinJsonAdapterFactory()) + .build() + + private val adapter = moshi.adapter(HiveAuthRequestDto::class.java) + + @Test + fun `HiveAuthRequestDto serializes all three fields with correct json names`() { + val dto = HiveAuthRequestDto( + token = "signed_token_value", + pubkey = "03abc123pubkey", + timestamp = "1700000000000" + ) + + val json = adapter.toJson(dto) + + assert(json.contains("\"token\":\"signed_token_value\"")) { + "Expected 'token' field but got: $json" + } + assert(json.contains("\"pubkey\":\"03abc123pubkey\"")) { + "Expected 'pubkey' field but got: $json" + } + assert(json.contains("\"timestamp\":\"1700000000000\"")) { + "Expected 'timestamp' field but got: $json" + } + } + + @Test + fun `HiveAuthRequestDto serialized json contains no null values`() { + val dto = HiveAuthRequestDto( + token = "t", + pubkey = "p", + timestamp = "0" + ) + + val json = adapter.toJson(dto) + + assertFalse("Serialized json must not contain null values", json.contains("null")) + } + + @Test + fun `HiveAuthRequestDto deserializes correctly from json`() { + val json = """{"token":"my_token","pubkey":"my_pubkey","timestamp":"12345"}""" + + val dto = adapter.fromJson(json)!! + + assertEquals("my_token", dto.token) + assertEquals("my_pubkey", dto.pubkey) + assertEquals("12345", dto.timestamp) + } +} diff --git a/sphinx/application/sphinx/build.gradle b/sphinx/application/sphinx/build.gradle index d64db83c..af8f5543 100644 --- a/sphinx/application/sphinx/build.gradle +++ b/sphinx/application/sphinx/build.gradle @@ -118,6 +118,7 @@ dependencies { implementation project(path: ':sphinx:application:network:features:queries:feature-network-query-crypter') implementation project(path: ':sphinx:application:network:features:queries:feature-network-query-discover-tribes') implementation project(path: ':sphinx:application:network:features:queries:feature-network-query-feed-status') + implementation project(path: ':sphinx:application:network:features:queries:feature-network-query-hive') implementation project(path: ':sphinx:application:network:features:feature-network-relay-call') implementation project(path: ':sphinx:screens:onboard:key-restore') diff --git a/sphinx/application/sphinx/src/main/java/chat/sphinx/di/NetworkModule.kt b/sphinx/application/sphinx/src/main/java/chat/sphinx/di/NetworkModule.kt index 07b6b106..06b2c923 100644 --- a/sphinx/application/sphinx/src/main/java/chat/sphinx/di/NetworkModule.kt +++ b/sphinx/application/sphinx/src/main/java/chat/sphinx/di/NetworkModule.kt @@ -17,6 +17,7 @@ import chat.sphinx.concept_network_query_invite.NetworkQueryInvite import chat.sphinx.concept_network_query_meme_server.NetworkQueryMemeServer import chat.sphinx.concept_network_query_feed_search.NetworkQueryFeedSearch import chat.sphinx.concept_network_query_feed_status.NetworkQueryFeedStatus +import chat.sphinx.concept_network_query_hive.NetworkQueryHive import chat.sphinx.concept_network_query_verify_external.NetworkQueryAuthorizeExternal import chat.sphinx.concept_network_query_people.NetworkQueryPeople import chat.sphinx.concept_network_relay_call.NetworkRelayCall @@ -29,6 +30,7 @@ import chat.sphinx.feature_network_query_contact.NetworkQueryContactImpl import chat.sphinx.feature_network_query_crypter.NetworkQueryCrypterImpl import chat.sphinx.feature_network_query_discover_tribes.NetworkQueryDiscoverTribesImpl import chat.sphinx.feature_network_query_feed_status.NetworkQueryFeedStatusImpl +import chat.sphinx.feature_network_query_hive.NetworkQueryHiveImpl import chat.sphinx.feature_network_query_invite.NetworkQueryInviteImpl import chat.sphinx.feature_network_query_meme_server.NetworkQueryMemeServerImpl import chat.sphinx.feature_network_query_podcast_search.NetworkQueryFeedSearchImpl @@ -322,4 +324,16 @@ object NetworkModule { networkQueryFeedStatusImpl: NetworkQueryFeedStatusImpl ): NetworkQueryFeedStatus = networkQueryFeedStatusImpl + + @Provides + fun provideNetworkQueryHiveImpl( + networkCall: NetworkCall + ): NetworkQueryHiveImpl = + NetworkQueryHiveImpl(networkCall) + + @Provides + fun provideNetworkQueryHive( + networkQueryHiveImpl: NetworkQueryHiveImpl + ): NetworkQueryHive = + networkQueryHiveImpl } diff --git a/sphinx/application/sphinx/src/main/java/chat/sphinx/di/RepositoryModule.kt b/sphinx/application/sphinx/src/main/java/chat/sphinx/di/RepositoryModule.kt index aa789ded..fa99398e 100644 --- a/sphinx/application/sphinx/src/main/java/chat/sphinx/di/RepositoryModule.kt +++ b/sphinx/application/sphinx/src/main/java/chat/sphinx/di/RepositoryModule.kt @@ -12,6 +12,7 @@ import chat.sphinx.concept_network_query_invite.NetworkQueryInvite import chat.sphinx.concept_network_query_meme_server.NetworkQueryMemeServer import chat.sphinx.concept_network_query_feed_search.NetworkQueryFeedSearch import chat.sphinx.concept_network_query_feed_status.NetworkQueryFeedStatus +import chat.sphinx.concept_network_query_hive.NetworkQueryHive import chat.sphinx.concept_network_query_people.NetworkQueryPeople import chat.sphinx.concept_network_query_verify_external.NetworkQueryAuthorizeExternal import chat.sphinx.concept_relay.RelayDataHandler @@ -146,6 +147,7 @@ object RepositoryModule { networkQueryPeople: NetworkQueryPeople, networkQueryFeedSearch: NetworkQueryFeedSearch, networkQueryFeedStatus: NetworkQueryFeedStatus, + networkQueryHive: NetworkQueryHive, connectManager: ConnectManager, dataSyncManager: DataSyncManager, walletDataHandler: WalletDataHandler, @@ -174,6 +176,7 @@ object RepositoryModule { networkQueryPeople, networkQueryFeedSearch, networkQueryFeedStatus, + networkQueryHive, connectManager, dataSyncManager, walletDataHandler,