Skip to content

Commit b37f1d7

Browse files
gazreesematthewelwellzeienko-vitalii
authored
Feat(typing)/allow non string trait types (#43)
* Update the tests to reproduce the initialisation error * Fix the initialisation error * Remove TODO comment * Allow non-string trait values * Allow non-string trait types * Basic tests * Make Trait's value to be any type and add getters * Update Trait model * Add more variations of mocked responses * Update TraitEntity tests * Add getters to TraitWithIdentity * Pass traitValue to TraitWithIdentity in setTrait() method * Add tests to different Trait value's type when setting Trait * Use traitValue in TraitsEndpoint * Update value deprecation message * Remove unnecessary constructors from Trait model * Use stringValue instead of value in tests * Add constructors that accept strict-typed value * Check the value type to be one of supported in Trait's init * Update to make the convenience constructors more explicit and also verify the original behaviour * Remove unneeded constructor keyword * Make sure we're using the old 'value' parameter * Updated the TraitWithIdentity in-line with the Trait class and added unit tests --------- Co-authored-by: Matthew Elwell <[email protected]> Co-authored-by: Vitaly Zeenko <[email protected]>
1 parent c747fb0 commit b37f1d7

File tree

6 files changed

+495
-16
lines changed

6 files changed

+495
-16
lines changed

FlagsmithClient/src/main/java/com/flagsmith/Flagsmith.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class Flagsmith constructor(
148148
}.also { lastUsedIdentity = identity }
149149

150150
fun setTrait(trait: Trait, identity: String, result: (Result<TraitWithIdentity>) -> Unit) =
151-
retrofit.postTraits(TraitWithIdentity(trait.key, trait.value, Identity(identity))).enqueueWithResult(result = result)
151+
retrofit.postTraits(TraitWithIdentity(trait.key, trait.traitValue, Identity(identity))).enqueueWithResult(result = result)
152152

153153
fun getIdentity(identity: String, result: (Result<IdentityFlagsAndTraits>) -> Unit) =
154154
retrofit.getIdentityFlagsAndTraits(identity).enqueueWithResult(defaults = null, result = result)

FlagsmithClient/src/main/java/com/flagsmith/entities/Trait.kt

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,84 @@ package com.flagsmith.entities
33

44
import com.google.gson.annotations.SerializedName
55

6-
data class Trait(
6+
data class Trait (
77
val identifier: String? = null,
88
@SerializedName(value = "trait_key") val key: String,
9-
@SerializedName(value = "trait_value") val value: String
10-
)
9+
@SerializedName(value = "trait_value") val traitValue: Any
10+
) {
1111

12-
data class TraitWithIdentity(
12+
constructor(key: String, value: String)
13+
: this(key = key, traitValue = value)
14+
15+
constructor(key: String, value: Int)
16+
: this(key = key, traitValue = value)
17+
18+
constructor(key: String, value: Double)
19+
: this(key = key, traitValue = value)
20+
21+
constructor(key: String, value: Boolean)
22+
: this(key = key, traitValue = value)
23+
24+
@Deprecated("Use traitValue instead or one of the type-safe getters", ReplaceWith("traitValue"))
25+
val value: String
26+
get() { return traitValue as? String ?: traitValue.toString() }
27+
28+
val stringValue: String?
29+
get() = traitValue as? String
30+
31+
val intValue: Int?
32+
get() {
33+
return when (traitValue) {
34+
is Int -> traitValue
35+
is Double -> traitValue.toInt()
36+
else -> null
37+
}
38+
}
39+
40+
val doubleValue: Double?
41+
get() = traitValue as? Double
42+
43+
val booleanValue: Boolean?
44+
get() = traitValue as? Boolean
45+
46+
}
47+
48+
data class TraitWithIdentity (
1349
@SerializedName(value = "trait_key") val key: String,
14-
@SerializedName(value = "trait_value") val value: String,
15-
val identity: Identity
16-
)
50+
@SerializedName(value = "trait_value") val traitValue: Any,
51+
val identity: Identity,
52+
) {
53+
constructor(key: String, value: String, identity: Identity)
54+
: this(key = key, traitValue = value, identity = identity)
55+
56+
constructor(key: String, value: Int, identity: Identity)
57+
: this(key = key, traitValue = value, identity = identity)
58+
59+
constructor(key: String, value: Double, identity: Identity)
60+
: this(key = key, traitValue = value, identity = identity)
61+
62+
constructor(key: String, value: Boolean, identity: Identity)
63+
: this(key = key, traitValue = value, identity = identity)
64+
65+
@Deprecated("Use traitValue instead or one of the type-safe getters", ReplaceWith("traitValue"))
66+
val value: String
67+
get() { return traitValue as? String ?: traitValue.toString() }
68+
69+
val stringValue: String?
70+
get() = traitValue as? String
71+
72+
val intValue: Int?
73+
get() {
74+
return when (traitValue) {
75+
is Int -> traitValue
76+
is Double -> traitValue.toInt()
77+
else -> null
78+
}
79+
}
80+
81+
val doubleValue: Double?
82+
get() = traitValue as? Double
83+
84+
val booleanValue: Boolean?
85+
get() = traitValue as? Boolean
86+
}

FlagsmithClient/src/test/java/com/flagsmith/TraitsTests.kt

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class TraitsTests {
4242
assertTrue(result.getOrThrow().isNotEmpty())
4343
assertEquals(
4444
"electric pink",
45-
result.getOrThrow().find { trait -> trait.key == "favourite-colour" }?.value
45+
result.getOrThrow().find { trait -> trait.key == "favourite-colour" }?.stringValue
4646
)
4747
}
4848
}
@@ -54,7 +54,7 @@ class TraitsTests {
5454
val result = flagsmith.getTraitsSync("person")
5555
assertTrue(result.isSuccess)
5656
assertTrue(result.getOrThrow().isNotEmpty())
57-
assertNull(result.getOrThrow().find { trait -> trait.key == "fake-trait" }?.value)
57+
assertNull(result.getOrThrow().find { trait -> trait.key == "fake-trait" }?.stringValue)
5858
}
5959
}
6060

@@ -64,7 +64,7 @@ class TraitsTests {
6464
runBlocking {
6565
val result = flagsmith.getTraitSync("favourite-colour", "person")
6666
assertTrue(result.isSuccess)
67-
assertEquals("electric pink", result.getOrThrow()?.value)
67+
assertEquals("electric pink", result.getOrThrow()?.stringValue)
6868
}
6969
}
7070

@@ -86,7 +86,46 @@ class TraitsTests {
8686
flagsmith.setTraitSync(Trait(key = "set-from-client", value = "12345"), "person")
8787
assertTrue(result.isSuccess)
8888
assertEquals("set-from-client", result.getOrThrow().key)
89-
assertEquals("12345", result.getOrThrow().value)
89+
assertEquals("12345", result.getOrThrow().stringValue)
90+
assertEquals("person", result.getOrThrow().identity.identifier)
91+
}
92+
}
93+
94+
@Test
95+
fun testSetTraitInteger() {
96+
mockServer.mockResponseFor(MockEndpoint.SET_TRAIT_INTEGER)
97+
runBlocking {
98+
val result =
99+
flagsmith.setTraitSync(Trait(key = "set-from-client", value = 5), "person")
100+
assertTrue(result.isSuccess)
101+
assertEquals("set-from-client", result.getOrThrow().key)
102+
assertEquals(5, result.getOrThrow().intValue)
103+
assertEquals("person", result.getOrThrow().identity.identifier)
104+
}
105+
}
106+
107+
@Test
108+
fun testSetTraitDouble() {
109+
mockServer.mockResponseFor(MockEndpoint.SET_TRAIT_DOUBLE)
110+
runBlocking {
111+
val result =
112+
flagsmith.setTraitSync(Trait(key = "set-from-client", value = 0.5), "person")
113+
assertTrue(result.isSuccess)
114+
assertEquals("set-from-client", result.getOrThrow().key)
115+
assertEquals(0.5, result.getOrThrow().doubleValue)
116+
assertEquals("person", result.getOrThrow().identity.identifier)
117+
}
118+
}
119+
120+
@Test
121+
fun testSetTraitBoolean() {
122+
mockServer.mockResponseFor(MockEndpoint.SET_TRAIT_BOOLEAN)
123+
runBlocking {
124+
val result =
125+
flagsmith.setTraitSync(Trait(key = "set-from-client", value = true), "person")
126+
assertTrue(result.isSuccess)
127+
assertEquals("set-from-client", result.getOrThrow().key)
128+
assertEquals(true, result.getOrThrow().booleanValue)
90129
assertEquals("person", result.getOrThrow().identity.identifier)
91130
}
92131
}
@@ -101,7 +140,7 @@ class TraitsTests {
101140
assertTrue(result.getOrThrow().flags.isNotEmpty())
102141
assertEquals(
103142
"electric pink",
104-
result.getOrThrow().traits.find { trait -> trait.key == "favourite-colour" }?.value
143+
result.getOrThrow().traits.find { trait -> trait.key == "favourite-colour" }?.stringValue
105144
)
106145
}
107146
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package com.flagsmith.entities
2+
3+
import com.flagsmith.Flagsmith
4+
import com.flagsmith.FlagsmithCacheConfig
5+
import com.flagsmith.getTraitSync
6+
import com.flagsmith.mockResponses.MockEndpoint
7+
import com.flagsmith.mockResponses.mockResponseFor
8+
import kotlinx.coroutines.runBlocking
9+
import org.junit.After
10+
import org.junit.Assert
11+
import org.junit.Before
12+
import org.junit.Test
13+
import org.mockserver.integration.ClientAndServer
14+
15+
class TraitEntityTests {
16+
17+
private lateinit var mockServer: ClientAndServer
18+
private lateinit var flagsmith: Flagsmith
19+
20+
@Before
21+
fun setup() {
22+
mockServer = ClientAndServer.startClientAndServer()
23+
flagsmith = Flagsmith(
24+
environmentKey = "",
25+
baseUrl = "http://localhost:${mockServer.localPort}",
26+
enableAnalytics = false,
27+
cacheConfig = FlagsmithCacheConfig(enableCache = false)
28+
)
29+
}
30+
31+
@After
32+
fun tearDown() {
33+
mockServer.stop()
34+
}
35+
36+
@Test
37+
fun testTraitValueStringType() {
38+
mockServer.mockResponseFor(MockEndpoint.GET_IDENTITIES_TRAIT_STRING)
39+
runBlocking {
40+
val result = flagsmith.getTraitSync("client-key", "person")
41+
Assert.assertTrue(result.isSuccess)
42+
Assert.assertEquals("12345", result.getOrThrow()?.stringValue)
43+
}
44+
}
45+
46+
@Test
47+
fun testTraitValueIntType() {
48+
mockServer.mockResponseFor(MockEndpoint.GET_IDENTITIES_TRAIT_INTEGER)
49+
runBlocking {
50+
val result = flagsmith.getTraitSync("client-key", "person")
51+
Assert.assertTrue(result.isSuccess)
52+
Assert.assertEquals(5, result.getOrThrow()?.intValue)
53+
Assert.assertTrue("Integers in the JSON actually get decoded as Double",
54+
(result.getOrThrow()?.traitValue) is Double)
55+
}
56+
}
57+
58+
@Test
59+
fun testTraitValueDoubleType() {
60+
mockServer.mockResponseFor(MockEndpoint.GET_IDENTITIES_TRAIT_DOUBLE)
61+
runBlocking {
62+
val result = flagsmith.getTraitSync("client-key", "person")
63+
Assert.assertTrue(result.isSuccess)
64+
Assert.assertEquals(0.5, result.getOrThrow()?.doubleValue)
65+
}
66+
}
67+
68+
@Test
69+
fun testTraitValueBooleanType() {
70+
mockServer.mockResponseFor(MockEndpoint.GET_IDENTITIES_TRAIT_BOOLEAN)
71+
runBlocking {
72+
val result = flagsmith.getTraitSync("client-key", "person")
73+
Assert.assertTrue(result.isSuccess)
74+
Assert.assertEquals(true, result.getOrThrow()?.booleanValue)
75+
}
76+
}
77+
78+
@Test
79+
fun testTraitConstructorStringType() {
80+
val trait = Trait( "string-key", "string-value")
81+
Assert.assertEquals("string-value", trait.traitValue)
82+
Assert.assertEquals("string-value", trait.stringValue)
83+
Assert.assertNull(trait.intValue)
84+
85+
val traitWithIdentity = TraitWithIdentity("string-key", "string-value", Identity("person"))
86+
Assert.assertEquals("string-value", traitWithIdentity.traitValue)
87+
Assert.assertEquals("string-value", traitWithIdentity.stringValue)
88+
Assert.assertNull(traitWithIdentity.intValue)
89+
}
90+
91+
@Test
92+
fun testTraitConstructorIntType() {
93+
val trait = Trait("string-key", 1)
94+
Assert.assertEquals(1, trait.traitValue)
95+
Assert.assertEquals(1, trait.intValue)
96+
Assert.assertNull("Can't convert an int to a double", trait.doubleValue)
97+
Assert.assertNull(trait.stringValue)
98+
Assert.assertEquals("We should maintain the original functionality for the String .value",
99+
"1", trait.value)
100+
101+
val traitWithIdentity = TraitWithIdentity("string-key", 1, Identity("person"))
102+
Assert.assertEquals(1, traitWithIdentity.traitValue)
103+
Assert.assertEquals(1, traitWithIdentity.intValue)
104+
Assert.assertNull("Can't convert an int to a double", traitWithIdentity.doubleValue)
105+
Assert.assertNull(traitWithIdentity.stringValue)
106+
Assert.assertEquals("We should maintain the original functionality for the String .value",
107+
"1", traitWithIdentity.value)
108+
}
109+
110+
@Test
111+
fun testTraitConstructorDoubleType() {
112+
val trait = Trait("string-key", 1.0)
113+
Assert.assertEquals(1.0, trait.traitValue)
114+
Assert.assertEquals(1.0, trait.doubleValue)
115+
Assert.assertEquals("JS ints are actually doubles so we should handle this",
116+
1, trait.intValue)
117+
Assert.assertNull(trait.stringValue)
118+
Assert.assertEquals("We should maintain the original functionality for the String .value",
119+
"1.0", trait.value)
120+
121+
val traitWithIdentity = TraitWithIdentity("string-key", 1.0, Identity("person"))
122+
Assert.assertEquals(1.0, traitWithIdentity.traitValue)
123+
Assert.assertEquals(1.0, traitWithIdentity.doubleValue)
124+
Assert.assertEquals("JS ints are actually doubles so we should handle this",
125+
1, traitWithIdentity.intValue)
126+
Assert.assertNull(traitWithIdentity.stringValue)
127+
Assert.assertEquals("We should maintain the original functionality for the String .value",
128+
"1.0", traitWithIdentity.value)
129+
}
130+
131+
@Test
132+
fun testTraitConstructorBooleanType() {
133+
val trait = Trait("string-key", true)
134+
Assert.assertEquals(true, trait.traitValue)
135+
Assert.assertEquals(true, trait.booleanValue)
136+
Assert.assertNull(trait.intValue)
137+
Assert.assertNull(trait.doubleValue)
138+
Assert.assertNull(trait.stringValue)
139+
Assert.assertEquals("We should maintain the original functionality for the String .value",
140+
"true", trait.value)
141+
}
142+
}

0 commit comments

Comments
 (0)