Skip to content

Commit

Permalink
PERA-1216 :: Create account caching database and network api services (
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsinsar authored Dec 17, 2024
1 parent 75a0d57 commit f3e3706
Show file tree
Hide file tree
Showing 63 changed files with 2,438 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 Pera Wallet, LDA
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/

package co.algorand.app.di

import com.algorand.common.foundation.network.GetIndexerInterceptorConfig
import com.algorand.common.foundation.network.IndexerInterceptorPluginConfig
import org.koin.dsl.module

val accountInformationModule = module {
factory<GetIndexerInterceptorConfig> {
GetIndexerInterceptorConfig {
IndexerInterceptorPluginConfig(
baseUrl = "-",
apiKey = "-"
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
package co.algorand.app.di

import com.algorand.common.account.local.di.localAccountsKoinModule
import com.algorand.common.di.platformKoinModule
import com.algorand.common.encryption.di.encryptionModule
import com.algorand.common.di.commonModuleKoinModules
import org.koin.dsl.KoinAppDeclaration
import org.koin.dsl.includes
import org.koin.dsl.koinConfiguration

expect fun nativeConfig() : KoinAppDeclaration
expect fun nativeConfig(): KoinAppDeclaration

val initKoinConfig = koinConfiguration {
includes(nativeConfig())
modules(appModules())
}

fun appModules() =
listOf(
provideHttpClientModules,
provideRepositoryModules,
provideViewModelModules,
localAccountsKoinModule,
encryptionModule,
platformKoinModule()
// providePlatformModules(), // Room DB & DataStore located here
// module {
// single { AppSettings(get()) }
// },
)
fun appModules() = listOf(
provideHttpClientModules,
provideRepositoryModules,
provideViewModelModules,
accountInformationModule
) + commonModuleKoinModules
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "kto
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorContentNegotiation" }
ktor-client-content-negotiation-jvm = { module = "io.ktor:ktor-client-content-negotiation-jvm", version.ref = "ktorContentNegotiation" }
ktor-kotlinx-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }

legacy-support-v4 = { module = "androidx.legacy:legacy-support-v4", version.ref = "legacySupport" }
lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "lifecycle" }
lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "lifecycleExt" }
Expand Down
3 changes: 3 additions & 0 deletions wallet-sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ kotlin {
implementation(libs.koin.compose.viewmodel)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.kotlinx.serialization)
implementation(libs.ktor.client.core)
implementation(libs.room.runtime)
implementation(libs.sqlite.bundled)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2022 Pera Wallet, LDA
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/

package com.algorand.common.account.info.data.service

import com.algorand.common.foundation.network.IndexerInterceptorPlugin
import com.algorand.common.foundation.network.PeraJsonNegotiation
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.ANDROID
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.serialization.kotlinx.json.json

internal actual fun getIndexerApiHttpClient(indexerInterceptorPlugin: IndexerInterceptorPlugin): HttpClient {
return HttpClient(OkHttp) {
install(Logging) {
logger = Logger.ANDROID
level = LogLevel.BODY
}
install(ContentNegotiation) {
json(PeraJsonNegotiation)
}
install(indexerInterceptorPlugin)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import com.algorand.common.account.local.data.database.AccountDatabase
import com.algorand.common.account.local.data.database.getAccountDatabase
import com.algorand.common.algosdk.AlgoAccountSdk
import com.algorand.common.algosdk.AlgoAccountSdkImpl
import com.algorand.common.foundation.database.PeraDatabase
import com.algorand.foundation.database.getPeraDatabase
import org.koin.core.module.Module
import org.koin.dsl.module

actual fun platformKoinModule(): Module = module {
internal actual fun platformKoinModule(): Module = module {
single<AccountDatabase> { getAccountDatabase(get()) }
single<AlgoAccountSdk> { AlgoAccountSdkImpl() }
single<PeraDatabase> { getPeraDatabase(get()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2022 Pera Wallet, LDA
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/

package com.algorand.foundation.database

import android.content.Context
import androidx.room.Room
import androidx.room.RoomDatabase
import com.algorand.common.foundation.database.PeraDatabase
import kotlinx.coroutines.Dispatchers

internal fun getPeraDatabase(context: Context): PeraDatabase {
return getPeraDatabaseBuilder(context).build()
}

internal fun getPeraDatabaseBuilder(context: Context): RoomDatabase.Builder<PeraDatabase> {
val appContext = context.applicationContext
val dbFile = appContext.getDatabasePath(PeraDatabase.DATABASE_NAME)
return Room.databaseBuilder<PeraDatabase>(
context = appContext,
name = dbFile.absolutePath
).setQueryCoroutineContext(Dispatchers.IO)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright 2022 Pera Wallet, LDA
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/

package com.algorand.common.account.info.data.mapper

import com.algorand.common.account.info.data.database.model.AccountInformationEntity
import com.algorand.common.account.info.data.model.AccountInformationResponse
import com.algorand.common.account.info.data.model.AccountInformationResponsePayloadResponse
import com.algorand.common.account.info.data.model.AppStateSchemaResponse
import com.algorand.common.encryption.AddressEncryptionManager
import com.algorand.common.testing.peraFixture
import io.mockk.every
import io.mockk.mockk
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

class AccountInformationEntityMapperImplTest {

private val addressEncryptionManager: AddressEncryptionManager = mockk {
every { encrypt(ADDRESS) } returns ENCRYPTED_ADDRESS
}
private val sut = AccountInformationEntityMapperImpl(addressEncryptionManager)

@Test
fun `EXPECT account information to be mapped successfully`() {
val result = sut(ACCOUNT_INFORMATION_RESPONSE)

assertEquals(ACCOUNT_INFORMATION_ENTITY, result)
}

@Test
fun `EXPECT null WHEN address is missing`() {
val accountInformationResponse = AccountInformationResponse(
accountInformation = ACCOUNT_INFORMATION_PAYLOAD.copy(
address = null
),
currentRound = 9
)

val result = sut(accountInformationResponse)

assertNull(result)
}

@Test
fun `EXPECT null WHEN current round is missing`() {
val accountInformationResponse = AccountInformationResponse(
accountInformation = ACCOUNT_INFORMATION_PAYLOAD.copy(
address = null
),
currentRound = 9
)

val result = sut(accountInformationResponse)

assertNull(result)
}

@Test
fun `EXPECT null WHEN algo amount is missing`() {
val accountInformationResponse = AccountInformationResponse(
accountInformation = ACCOUNT_INFORMATION_PAYLOAD.copy(
address = null
),
currentRound = 9
)

val result = sut(accountInformationResponse)

assertNull(result)
}

@Test
fun `EXPECT default values WHEN optional fields are missing`() {
val accountInformationResponse = AccountInformationResponse(
accountInformation = ACCOUNT_INFORMATION_PAYLOAD.copy(
totalAppsOptedIn = null,
totalCreatedApps = null,
totalCreatedAssets = null,
appsTotalExtraPages = null
),
currentRound = 9
)

val result = sut(accountInformationResponse)

val expected = ACCOUNT_INFORMATION_ENTITY.copy(
optedInAppsCount = 0,
totalCreatedAppsCount = 0,
totalCreatedAssetsCount = 0,
appsTotalExtraPages = 0,
)
assertEquals(expected, result)
}

companion object {
private const val ADDRESS = "address"
private const val ENCRYPTED_ADDRESS = "encrypted_address"

private val ACCOUNT_INFORMATION_PAYLOAD = peraFixture<AccountInformationResponsePayloadResponse>().copy(
address = ADDRESS,
amount = "10",
participation = null,
rekeyAdminAddress = "rekeyAddress",
allAssetHoldingList = emptyList(),
createdAtRound = 9,
appStateSchemaResponse = AppStateSchemaResponse(
numByteSlice = 21,
numUint = 12
),
appsTotalExtraPages = 2,
totalAppsOptedIn = 9,
totalAssetsOptedIn = 4,
totalCreatedAssets = 0,
totalCreatedApps = 0
)
private val ACCOUNT_INFORMATION_RESPONSE = AccountInformationResponse(
accountInformation = ACCOUNT_INFORMATION_PAYLOAD,
currentRound = 9
)

private val ACCOUNT_INFORMATION_ENTITY = AccountInformationEntity(
encryptedAddress = ENCRYPTED_ADDRESS,
algoAmount = "10",
lastFetchedRound = 9,
authAddress = "rekeyAddress",
optedInAppsCount = 9,
appsTotalExtraPages = 2,
createdAtRound = 9,
totalCreatedAssetsCount = 0,
totalCreatedAppsCount = 0,
appStateNumByteSlice = 21,
appStateSchemaUint = 12
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2022 Pera Wallet, LDA
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/

package com.algorand.common.account.info.data.mapper

import com.algorand.common.account.info.data.database.model.AccountInformationEntity
import com.algorand.common.encryption.AddressEncryptionManager
import io.mockk.every
import io.mockk.mockk
import kotlin.test.assertEquals
import org.junit.Test

class AccountInformationErrorEntityMapperImplTest {

private val addressEncryptionManager = mockk<AddressEncryptionManager> {
every { encrypt(ADDRESS) } returns ENCRYPTED_ADDRESS
}
private val sut = AccountInformationErrorEntityMapperImpl(addressEncryptionManager)

@Test
fun `EXPECT entity with default values`() {
val result = sut(ADDRESS)

val expected = AccountInformationEntity(
encryptedAddress = ENCRYPTED_ADDRESS,
algoAmount = "0",
optedInAppsCount = 0,
appsTotalExtraPages = 0,
authAddress = null,
createdAtRound = null,
lastFetchedRound = 0,
totalCreatedAppsCount = 0,
totalCreatedAssetsCount = 0,
appStateNumByteSlice = null,
appStateSchemaUint = null
)
assertEquals(expected, result)
}

private companion object {
private const val ADDRESS = "ADDRESS"
private const val ENCRYPTED_ADDRESS = "ENCRYPTED_ADDRESS"
}
}
Loading

0 comments on commit f3e3706

Please sign in to comment.