Skip to content

Commit 4a7732c

Browse files
committed
add unit tests for in memory storage
1 parent aa5b288 commit 4a7732c

File tree

1 file changed

+312
-0
lines changed

1 file changed

+312
-0
lines changed
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
package com.segment.analytics.kotlin.core.utilities
2+
3+
import com.segment.analytics.kotlin.core.Configuration
4+
import com.segment.analytics.kotlin.core.Settings
5+
import com.segment.analytics.kotlin.core.Storage
6+
import com.segment.analytics.kotlin.core.System
7+
import com.segment.analytics.kotlin.core.TrackEvent
8+
import com.segment.analytics.kotlin.core.UserInfo
9+
import com.segment.analytics.kotlin.core.emptyJsonObject
10+
import com.segment.analytics.kotlin.core.utils.testAnalytics
11+
import kotlinx.coroutines.test.TestScope
12+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
13+
import kotlinx.coroutines.test.runTest
14+
import kotlinx.serialization.encodeToString
15+
import kotlinx.serialization.json.Json
16+
import kotlinx.serialization.json.JsonObject
17+
import kotlinx.serialization.json.buildJsonObject
18+
import kotlinx.serialization.json.jsonArray
19+
import kotlinx.serialization.json.put
20+
import org.junit.jupiter.api.Assertions.assertEquals
21+
import org.junit.jupiter.api.Assertions.assertNotNull
22+
import org.junit.jupiter.api.Assertions.assertNull
23+
import org.junit.jupiter.api.Assertions.assertTrue
24+
import org.junit.jupiter.api.BeforeEach
25+
import org.junit.jupiter.api.Nested
26+
import org.junit.jupiter.api.Test
27+
import sovran.kotlin.Action
28+
import sovran.kotlin.Store
29+
import java.util.Date
30+
31+
internal class InMemoryStorageTest {
32+
33+
private val epochTimestamp = Date(0).toInstant().toString()
34+
35+
private val testDispatcher = UnconfinedTestDispatcher()
36+
37+
private val testScope = TestScope(testDispatcher)
38+
39+
private lateinit var store: Store
40+
41+
private lateinit var storage: StorageImpl
42+
43+
@BeforeEach
44+
fun setup() = runTest {
45+
val config = Configuration(
46+
writeKey = "123",
47+
application = "Test",
48+
apiHost = "local",
49+
)
50+
val analytics = testAnalytics(config, testScope, testDispatcher)
51+
store = analytics.store
52+
storage = InMemoryStorageProvider.createStorage(analytics) as StorageImpl
53+
storage.initialize()
54+
}
55+
56+
57+
@Test
58+
fun `userInfo update calls write`() = runTest {
59+
val action = object : Action<UserInfo> {
60+
override fun reduce(state: UserInfo): UserInfo {
61+
return UserInfo(
62+
anonymousId = "newAnonId",
63+
userId = "newUserId",
64+
traits = emptyJsonObject
65+
)
66+
}
67+
}
68+
store.dispatch(action, UserInfo::class)
69+
val userId = storage.read(Storage.Constants.UserId)
70+
val anonId = storage.read(Storage.Constants.AnonymousId)
71+
val traits = storage.read(Storage.Constants.Traits)
72+
73+
assertEquals("newAnonId", anonId)
74+
assertEquals("newUserId", userId)
75+
assertEquals("{}", traits)
76+
}
77+
78+
@Test
79+
fun `userInfo reset action removes userInfo`() = runTest {
80+
store.dispatch(UserInfo.ResetAction(), UserInfo::class)
81+
82+
val userId = storage.read(Storage.Constants.UserId)
83+
val anonId = storage.read(Storage.Constants.AnonymousId)
84+
val traits = storage.read(Storage.Constants.Traits)
85+
86+
assertNotNull(anonId)
87+
assertEquals(null, userId)
88+
assertEquals(null, traits)
89+
}
90+
91+
@Test
92+
fun `system update calls write for settings`() = runTest {
93+
val action = object : Action<System> {
94+
override fun reduce(state: System): System {
95+
return System(
96+
configuration = state.configuration,
97+
settings = Settings(
98+
integrations = buildJsonObject {
99+
put(
100+
"Segment.io",
101+
buildJsonObject {
102+
put(
103+
"apiKey",
104+
"1vNgUqwJeCHmqgI9S1sOm9UHCyfYqbaQ"
105+
)
106+
})
107+
},
108+
plan = emptyJsonObject,
109+
edgeFunction = emptyJsonObject,
110+
middlewareSettings = emptyJsonObject
111+
),
112+
running = false,
113+
initializedPlugins = setOf(),
114+
enabled = true
115+
)
116+
}
117+
}
118+
store.dispatch(action, System::class)
119+
val settings = storage.read(Storage.Constants.Settings) ?: ""
120+
121+
assertEquals(
122+
Settings(
123+
integrations = buildJsonObject {
124+
put(
125+
"Segment.io",
126+
buildJsonObject { put("apiKey", "1vNgUqwJeCHmqgI9S1sOm9UHCyfYqbaQ") })
127+
},
128+
plan = emptyJsonObject,
129+
edgeFunction = emptyJsonObject,
130+
middlewareSettings = emptyJsonObject
131+
), Json.decodeFromString(Settings.serializer(), settings)
132+
)
133+
}
134+
135+
@Test
136+
fun `system reset action removes system`() = runTest {
137+
val action = object : Action<System> {
138+
override fun reduce(state: System): System {
139+
return System(state.configuration, null, state.running, state.initializedPlugins, state.enabled)
140+
}
141+
}
142+
store.dispatch(action, System::class)
143+
144+
val settings = storage.read(Storage.Constants.Settings)
145+
146+
assertEquals(null, settings)
147+
}
148+
149+
@Nested
150+
inner class EventsStorage() {
151+
152+
@Test
153+
fun `writing events writes to eventsFile`() = runTest {
154+
val event = TrackEvent(
155+
event = "clicked",
156+
properties = buildJsonObject { put("behaviour", "good") })
157+
.apply {
158+
messageId = "qwerty-1234"
159+
anonymousId = "anonId"
160+
integrations = emptyJsonObject
161+
context = emptyJsonObject
162+
timestamp = epochTimestamp
163+
}
164+
val stringified: String = Json.encodeToString(event)
165+
storage.write(Storage.Constants.Events, stringified)
166+
storage.rollover()
167+
val storagePath = storage.eventStream.read()[0]
168+
val storageContents = (storage.eventStream as InMemoryEventStream).readAsStream(storagePath)
169+
assertNotNull(storageContents)
170+
val jsonFormat = Json.decodeFromString(JsonObject.serializer(), storageContents!!.bufferedReader().use { it.readText() })
171+
assertEquals(1, jsonFormat["batch"]!!.jsonArray.size)
172+
}
173+
174+
@Test
175+
fun `cannot write more than 32kb as event`() = runTest {
176+
val stringified: String = "A".repeat(32002)
177+
val exception = try {
178+
storage.write(
179+
Storage.Constants.Events,
180+
stringified
181+
)
182+
null
183+
}
184+
catch (e : Exception) {
185+
e
186+
}
187+
assertNotNull(exception)
188+
assertTrue(storage.eventStream.read().isEmpty())
189+
}
190+
191+
@Test
192+
fun `reading events returns a non-null file handle with correct events`() = runTest {
193+
val event = TrackEvent(
194+
event = "clicked",
195+
properties = buildJsonObject { put("behaviour", "good") })
196+
.apply {
197+
messageId = "qwerty-1234"
198+
anonymousId = "anonId"
199+
integrations = emptyJsonObject
200+
context = emptyJsonObject
201+
timestamp = epochTimestamp
202+
}
203+
val stringified: String = Json.encodeToString(event)
204+
storage.write(Storage.Constants.Events, stringified)
205+
206+
storage.rollover()
207+
val fileUrl = storage.read(Storage.Constants.Events)
208+
assertNotNull(fileUrl)
209+
fileUrl!!.let {
210+
val storageContents = (storage.eventStream as InMemoryEventStream).readAsStream(it)
211+
assertNotNull(storageContents)
212+
val contentsStr = storageContents!!.bufferedReader().use { it.readText() }
213+
val contentsJson: JsonObject = Json.decodeFromString(contentsStr)
214+
assertEquals(3, contentsJson.size) // batch, sentAt, writeKey
215+
assertTrue(contentsJson.containsKey("batch"))
216+
assertTrue(contentsJson.containsKey("sentAt"))
217+
assertTrue(contentsJson.containsKey("writeKey"))
218+
assertEquals(1, contentsJson["batch"]?.jsonArray?.size)
219+
val eventInFile = contentsJson["batch"]?.jsonArray?.get(0)
220+
val eventInFile2 = Json.decodeFromString(
221+
TrackEvent.serializer(),
222+
Json.encodeToString(eventInFile)
223+
)
224+
assertEquals(event, eventInFile2)
225+
}
226+
}
227+
228+
@Test
229+
fun `reading events with empty storage return empty list`() {
230+
val fileUrls = storage.read(Storage.Constants.Events)
231+
assertTrue(fileUrls!!.isEmpty())
232+
}
233+
234+
@Test
235+
fun `can write and read multiple events`() = runTest {
236+
val event1 = TrackEvent(
237+
event = "clicked",
238+
properties = buildJsonObject { put("behaviour", "good") })
239+
.apply {
240+
messageId = "qwerty-1234"
241+
anonymousId = "anonId"
242+
integrations = emptyJsonObject
243+
context = emptyJsonObject
244+
timestamp = epochTimestamp
245+
}
246+
val event2 = TrackEvent(
247+
event = "clicked2",
248+
properties = buildJsonObject { put("behaviour", "bad") })
249+
.apply {
250+
messageId = "qwerty-12345"
251+
anonymousId = "anonId"
252+
integrations = emptyJsonObject
253+
context = emptyJsonObject
254+
timestamp = epochTimestamp
255+
}
256+
val stringified1: String = Json.encodeToString(event1)
257+
val stringified2: String = Json.encodeToString(event2)
258+
storage.write(Storage.Constants.Events, stringified1)
259+
storage.write(Storage.Constants.Events, stringified2)
260+
261+
storage.rollover()
262+
val fileUrl = storage.read(Storage.Constants.Events)
263+
assertNotNull(fileUrl)
264+
fileUrl!!.let {
265+
val storageContents = (storage.eventStream as InMemoryEventStream).readAsStream(it)
266+
assertNotNull(storageContents)
267+
val contentsStr = storageContents!!.bufferedReader().use { it.readText() }
268+
val contentsJson: JsonObject = Json.decodeFromString(contentsStr)
269+
assertEquals(3, contentsJson.size) // batch, sentAt, writeKey
270+
assertTrue(contentsJson.containsKey("batch"))
271+
assertTrue(contentsJson.containsKey("sentAt"))
272+
assertTrue(contentsJson.containsKey("writeKey"))
273+
assertEquals(2, contentsJson["batch"]?.jsonArray?.size)
274+
val eventInFile = contentsJson["batch"]?.jsonArray?.get(0)
275+
val eventInFile2 = Json.decodeFromString(
276+
TrackEvent.serializer(),
277+
Json.encodeToString(eventInFile)
278+
)
279+
assertEquals(event1, eventInFile2)
280+
281+
val event2InFile = contentsJson["batch"]?.jsonArray?.get(1)
282+
val event2InFile2 = Json.decodeFromString(
283+
TrackEvent.serializer(),
284+
Json.encodeToString(event2InFile)
285+
)
286+
assertEquals(event2, event2InFile2)
287+
}
288+
}
289+
290+
@Test
291+
fun remove() = runTest {
292+
val action = object : Action<UserInfo> {
293+
override fun reduce(state: UserInfo): UserInfo {
294+
return UserInfo(
295+
anonymousId = "newAnonId",
296+
userId = "newUserId",
297+
traits = emptyJsonObject
298+
)
299+
}
300+
}
301+
store.dispatch(action, UserInfo::class)
302+
303+
val userId = storage.read(Storage.Constants.UserId)
304+
assertEquals("newUserId", userId)
305+
306+
storage.remove(Storage.Constants.UserId)
307+
assertNull(storage.read(Storage.Constants.UserId))
308+
assertTrue(storage.remove(Storage.Constants.Events))
309+
}
310+
}
311+
312+
}

0 commit comments

Comments
 (0)