Implemented plain fields for UserBridgeMapper.kt

Affected: none

MAILAND-722
This commit is contained in:
Davide Farella 2020-07-07 13:47:04 +02:00 committed by Davide Giuseppe Farella
parent fc1dc71928
commit 1920352afc
9 changed files with 215 additions and 21 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ config/detekt/reports/
# Gradle files
.gradle/
build/
scripts/extract_dependencies/raw_dependencies.txt
# Local configuration file (sdk path, etc)
local.properties

View File

@ -75,6 +75,7 @@ import static ch.protonmail.android.core.Constants.Prefs.PREF_USED_SPACE;
import static ch.protonmail.android.core.Constants.Prefs.PREF_USER_CREDIT;
import static ch.protonmail.android.core.Constants.Prefs.PREF_USER_CURRENCY;
import static ch.protonmail.android.core.Constants.Prefs.PREF_USER_ID;
import static ch.protonmail.android.core.Constants.Prefs.PREF_USER_NAME;
import static ch.protonmail.android.core.Constants.Prefs.PREF_USER_ORG_PRIVATE_KEY;
import static ch.protonmail.android.core.Constants.Prefs.PREF_USER_PRIVATE;
import static ch.protonmail.android.core.Constants.Prefs.PREF_USER_SERVICES;
@ -162,6 +163,7 @@ public class User {
public static User load(String username) {
final SharedPreferences securePrefs = ProtonMailApplication.getApplication().getSecureSharedPreferences(username);
final User user = new User();
user.name = securePrefs.getString(PREF_USER_NAME, "");
if (!TextUtils.isEmpty(username)) {
user.username = username;
}
@ -476,14 +478,46 @@ public class User {
return allowMobileSignatureEdit || role > 0;
}
public int getPrivate() {
return isPrivate;
}
public int getSubscribed() {
return subscribed;
}
public int getServices() {
return services;
}
public boolean isPaidUser() {
return subscribed > 0;
}
public int getCredit() {
return credit;
}
public String getCurrency() {
return currency;
}
public String getOrganizationPrivateKey() {
return organizationPrivateKey;
}
public int getDelinquentValue() {
return delinquent;
}
public boolean getDelinquent() {
return delinquent >= 3;
}
public int getMaxUpload() {
return maxUpload;
}
public void setAndSaveUsedSpace(long usedSpace) {
if (this.usedSpace != usedSpace) {
this.usedSpace = usedSpace;
@ -728,6 +762,10 @@ public class User {
return AddressId;
}
public String getId() {
return id;
}
public String getName() {
return name;
}

View File

@ -18,16 +18,19 @@
*/
package ch.protonmail.android.mapper.bridge
import ch.protonmail.android.domain.entity.Bytes
import ch.protonmail.android.domain.entity.Id
import ch.protonmail.android.domain.entity.Name
import ch.protonmail.android.domain.entity.NotBlankString
import ch.protonmail.android.domain.entity.bytes
import ch.protonmail.android.domain.entity.user.Addresses
import ch.protonmail.android.domain.entity.user.Delinquent
import ch.protonmail.android.domain.entity.user.Plan
import ch.protonmail.android.domain.entity.user.Role
import ch.protonmail.android.domain.entity.user.User
import ch.protonmail.android.domain.entity.user.UserKeys
import ch.protonmail.android.domain.entity.user.UserSpace
import me.proton.core.util.kotlin.takeIfNotBlank
import me.proton.core.util.kotlin.toBoolean
import ch.protonmail.android.api.models.User as OldUser
/**
@ -39,19 +42,49 @@ class UserBridgeMapper : BridgeMapper<OldUser, User> {
override fun OldUser.toNewModel(): User {
return User(
id = Id("id"), // TODO
id = Id(id),
name = Name(name),
addresses = Addresses(emptyMap()), // TODO
keys = UserKeys(null, emptyList()), // TODO
plans = emptySet(), // TODO
private = false, // TODO
role = Role.values().first { it.i == role },
organizationPrivateKey = null, // TODO
currency = NotBlankString("eur"), // TODO
credits = 0, // TODO
delinquent = Delinquent.None, //TODO
totalUploadLimit = Bytes(0u), //TODO
dedicatedSpace = UserSpace(Bytes(0u), Bytes(0u)) // TODO
plans = getPlans(services, subscribed),
private = private.toBoolean(),
role = getRole(role),
organizationPrivateKey = getOrganizationKey(organizationPrivateKey),
currency = NotBlankString(currency),
credits = credit,
delinquent = getDelinquent(delinquentValue),
totalUploadLimit = maxUpload.bytes,
dedicatedSpace = UserSpace(usedSpace.bytes, maxSpace.bytes)
)
}
@OptIn(ExperimentalStdlibApi::class)
private fun getPlans(services: Int, subscribed: Int) = buildSet {
fun Int.hasMail() = MAIL_PLAN_VALUE and this == MAIL_PLAN_VALUE
fun Int.hasVpn() = VPN_PLAN_VALUE and this == VPN_PLAN_VALUE
if (subscribed.hasMail()) add(Plan.Mail.Paid)
else if (services.hasMail()) add(Plan.Mail.Free)
if (subscribed.hasVpn()) add(Plan.Vpn.Paid)
else if (services.hasVpn()) add(Plan.Vpn.Free)
}
private fun getRole(value: Int) = Role.values().first { it.i == value }
private fun getOrganizationKey(key: String?) = key?.takeIfNotBlank()?.let(::NotBlankString)
private fun getDelinquent(value: Int) = when (value.toUInt()) {
Delinquent.None.i -> Delinquent.None
Delinquent.InvoiceAvailable.i -> Delinquent.InvoiceAvailable
Delinquent.InvoiceOverdue.i -> Delinquent.InvoiceOverdue
Delinquent.InvoiceDelinquent.i -> Delinquent.InvoiceDelinquent
Delinquent.IncomingMailDisabled.i -> Delinquent.IncomingMailDisabled
else -> throw IllegalArgumentException("Cannot get Delinquent for value $value")
}
private companion object {
const val MAIL_PLAN_VALUE = 1 // 001
const val VPN_PLAN_VALUE = 4 // 100
}
}

View File

@ -18,10 +18,29 @@
*/
package ch.protonmail.android.mapper.bridge
import android.os.SystemClock
import android.text.TextUtils
import assert4k.*
import ch.protonmail.android.core.Constants.Prefs.PREF_DELINQUENT
import ch.protonmail.android.core.Constants.Prefs.PREF_MAX_SPACE
import ch.protonmail.android.core.Constants.Prefs.PREF_MAX_UPLOAD_FILE_SIZE
import ch.protonmail.android.core.Constants.Prefs.PREF_ROLE
import ch.protonmail.android.core.Constants.Prefs.PREF_SUBSCRIBED
import ch.protonmail.android.core.Constants.Prefs.PREF_USED_SPACE
import ch.protonmail.android.core.Constants.Prefs.PREF_USER_CREDIT
import ch.protonmail.android.core.Constants.Prefs.PREF_USER_CURRENCY
import ch.protonmail.android.core.Constants.Prefs.PREF_USER_ID
import ch.protonmail.android.core.Constants.Prefs.PREF_USER_NAME
import ch.protonmail.android.core.Constants.Prefs.PREF_USER_ORG_PRIVATE_KEY
import ch.protonmail.android.core.Constants.Prefs.PREF_USER_PRIVATE
import ch.protonmail.android.core.ProtonMailApplication
import ch.protonmail.android.domain.entity.user.Delinquent
import ch.protonmail.android.domain.entity.user.Plan
import ch.protonmail.android.domain.entity.user.Role
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import me.proton.core.util.kotlin.invoke
import kotlin.test.Test
import ch.protonmail.android.api.models.User as OldUser
@ -34,12 +53,23 @@ internal class UserBridgeMapperTest {
private val mapper = UserBridgeMapper()
@Test
fun `verify it transform correctly`() {
fun `transform from api`() {
// GIVEN
val oldUser = mockk<OldUser>(relaxed = true) {
every { id } returns "id"
every { name } returns "name"
every { role } returns 1
every { services } returns 4 // TODO: use 5 when addresses are handled
every { subscribed } returns 4
every { private } returns 1
every { role } returns 2
every { organizationPrivateKey } returns "orgKey"
every { currency } returns "eur"
every { credit } returns 10
every { delinquentValue } returns 3
every { maxUpload } returns 12_345
every { usedSpace } returns 15_000
every { maxSpace } returns 30_000
}
// WHEN
@ -47,8 +77,90 @@ internal class UserBridgeMapperTest {
// THEN
assert that newUser * {
+id.s equals "id"
+name.s equals "name"
+role equals Role.ORGANIZATION_MEMBER
+(plans * {
+size() equals 1 // TODO use 2 when addresses are handled
// it contains Plan.Mail.Free // TODO uncomment when addresses are handled
it contains Plan.Vpn.Paid
})
+private equals true
+role equals Role.ORGANIZATION_ADMIN
+organizationPrivateKey?.s equals "orgKey"
+currency.s equals "eur"
+credits() equals 10
+delinquent equals Delinquent.InvoiceDelinquent
+totalUploadLimit.l equals 12_345uL
+(dedicatedSpace * {
+used.l() equals 15_000uL
+total.l() equals 30_000uL
})
}
}
@Test
fun `transform from preferences`() {
mockkStatic(ProtonMailApplication::class, TextUtils::class, SystemClock::class)
every { ProtonMailApplication.getApplication() } returns mockk {
every { getString(any()) } returns ""
every { resources } returns mockk(relaxed = true)
every { getSecureSharedPreferences("username") } returns mockk() {
// Defaults
every { getBoolean(any(), any()) } returns false
every { getInt(any(), any()) } returns 0
every { getLong(any(), any()) } returns 0
every { getString(any(), any()) } returns ""
// Meaningful User data
every { getString(PREF_USER_ID, any()) } returns "id"
every { getString(PREF_USER_NAME, any()) } returns "username"
every { getInt(PREF_SUBSCRIBED, any()) } returns 4 // TODO: use 5 when addresses are handled
every { getInt(PREF_USER_PRIVATE, any()) } returns 1
every { getInt(PREF_ROLE, any()) } returns 2
every { getString(PREF_USER_ORG_PRIVATE_KEY, any()) } returns "orgKey"
every { getString(PREF_USER_CURRENCY, any()) } returns "eur"
every { getInt(PREF_USER_CREDIT, any()) } returns 10
every { getInt(PREF_DELINQUENT, any()) } returns 3
every { getInt(PREF_MAX_UPLOAD_FILE_SIZE, any()) } returns 12_345
every { getLong(PREF_USED_SPACE, any()) } returns 15_000
every { getLong(PREF_MAX_SPACE, any()) } returns 30_000
every { edit() } returns mockk(relaxed = true)
}
every { getSharedPreferences(any(), any()) } returns mockk(relaxed = true)
}
every { TextUtils.isEmpty(any()) } answers { firstArg<String?>().isNullOrEmpty() }
every { SystemClock.elapsedRealtime() } returns 0
// GIVEN
val oldUser = OldUser.load("username")
// WHEN
val newUser = mapper { oldUser.toNewModel() }
// THEN
assert that newUser * {
+id.s equals "id"
+name.s equals "username"
+(plans * {
+size() equals 1 // TODO use 2 when addresses are handled
// it contains Plan.Mail.Free // TODO uncomment when addresses are handled
it contains Plan.Vpn.Paid
})
+private equals true
+role equals Role.ORGANIZATION_ADMIN
+organizationPrivateKey?.s equals "orgKey"
+currency.s equals "eur"
+credits() equals 10
+delinquent equals Delinquent.InvoiceDelinquent
+totalUploadLimit.l equals 12_345uL
+(dedicatedSpace * {
+used.l() equals 15_000uL
+total.l() equals 30_000uL
})
}
unmockkStatic(ProtonMailApplication::class, TextUtils::class, SystemClock::class)
}
}

View File

@ -29,5 +29,6 @@ val repos: RepositoryHandler.() -> Unit get() = {
jcenter()
// Proton Core libraries
maven("https://dl.bintray.com/proton/Core-publishing")
// Assert4k
maven("https://dl.bintray.com/4face/4face")
}

View File

@ -70,7 +70,7 @@ private fun <V : Validable> Validator<V>.wrap() = invoke()
* [Validator] that accepts only strings that are not blank
*/
fun NotBlankStringValidator(field: String) = { _: Any ->
require(field.isNotBlank())
require(field.isNotBlank()) { "String is blank" }
}.wrap()
/**

View File

@ -38,6 +38,7 @@ package ch.protonmail.android.domain.entity
* Represent a given number of bytes
*/
inline class Bytes(val l: ULong)
val Number.bytes get() = Bytes(toLong().toULong())
/**
* Entity representing an email address

View File

@ -71,15 +71,23 @@ data class User( // TODO: consider naming UserInfo or simialar
val dedicatedSpace: UserSpace
) : Validable by Validator<User>({
// Addresses
require(addresses.hasAddresses || plans.none { it is Plan.Mail }) { "Mail plan but no addresses" }
require(keys.hasKeys || !addresses.hasAddresses) { "Has addresses but not key" }
// Keys
require(keys.hasKeys || !addresses.hasAddresses) { "Has addresses but no keys" }
require(role == Role.ORGANIZATION_ADMIN && organizationPrivateKey != null ||
role != Role.ORGANIZATION_ADMIN && organizationPrivateKey == null) {
"Has organization but not organization key"
// Organization
val isAdmin = role == Role.ORGANIZATION_ADMIN
require(!isAdmin || isAdmin && organizationPrivateKey != null) {
"Is organization admin but doesn't have organization key"
}
require(isAdmin || !isAdmin && organizationPrivateKey == null) {
"Is not organization admin, but has organization key"
}
// Plans
require(plans.count { it is Plan.Mail } <= 1 &&
plans.count { it is Plan.Vpn } <= 1) {
"Has 2 or more plans of the same type"
@ -104,4 +112,4 @@ enum class Role(val i: Int) {
}
// TODO can this entity be used on other spaces under a different name?
data class UserSpace(val total: Bytes, val used: Bytes)
data class UserSpace(val used: Bytes, val total: Bytes)

View File

@ -147,7 +147,7 @@ internal class UserTest {
0,
Delinquent.None,
Bytes(5_000u),
UserSpace(Bytes(5_000_000u), Bytes(25_000u))
UserSpace(Bytes(25_000u), Bytes(5_000_000u))
)
private val notEmptyAddresses = Addresses(