Skip to content

Commit

Permalink
[MOBL-1399] Events module rewrite in Kotlin language (#363)
Browse files Browse the repository at this point in the history
* Copied over the files from request queue project

* addressing IDE warnings

* Adjusted the code to support minSdk 23 change

* Removed unused imports

* Added unit tests for event manager

* Added unit tests for network queue manager

* Methods with db access and nw access made main safe

* Fixed an issue with setup method in test

* Updated the network repository code for better exception handling

* Bug fixes and logging improvements

* fix tests and optimise job scheduling

* wiring new event module to Blueshift class

* code clean up inside initialize method

* Bug fix - selection of datacenters is now possible using legacy code

* Added a queue to process events inserted to the local database

* Made necessary changes to maintain the order of the events inserted to the queue

* Fixed the order of events in the bulk events array to be ascending

* Adjusted the logs for better readability

* Refactored the unit test

* Logging cleanup

* committing new version check code (test pending)

* Added tests for the BlueshiftInstallationStatusHelper class

* Removed unused methods and their tests

* Added more test cases to cover getEventAttributes method

* Renamed the db files to match convention

* Removed Blueshift keyword from Log tags

* Reset batch interval from 15 min to 30 min

* Fixed the test method signature

* Removed commented code and added comments for the new code added

* Removed unused namespace declaration

* Fixed code formatting issues in the file BlueshiftNetworkRequestRepositoryImpl

* Addressed multiple review comments from Ketan

* Delete the offline events and any pending requests from the queue when tracking is disabled.

* Deleted two unused test files

* Added tests for the BlueshiftAPI class to ensure the validity of API URLs

* Reformatted the log code

* Added Android tests for BlueshiftEventRepositoryImpl

* Avoid crashes due to null event name

* Started adding headers in the db and also wrote tests for BlueshiftNetworkRequestRepositoryImpl

* Added more tests for the BlueshiftNetworkRequestRepositoryImpl class

* Added check for legacy sync status to avoid unwanted sync calls

* Added check for null values in eventName attribute to avoid crashes

* Fixed the variable name

* Added deprecation note for AppInstallReceiver

* Deprecated the legacy events module classes with a note

* Detects push permission change on app start and for each event

* Mark current push status before sending identify to avoid infinite loop

* Cache device token for faster loading on app opens

* modified handleNewToken method to accept context
  • Loading branch information
rahulrvp committed Sep 16, 2024
1 parent 5f468e7 commit e164124
Show file tree
Hide file tree
Showing 61 changed files with 2,313 additions and 450 deletions.
6 changes: 6 additions & 0 deletions android-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ dependencies {
// Test
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.6.1'
testImplementation("androidx.test:core:1.5.0")
testImplementation("androidx.arch.core:core-testing:2.2.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation("com.google.truth:truth:1.1.4")
testImplementation("io.mockk:mockk:1.13.5")
testImplementation("org.json:json:20140107")
}

tasks.register('androidJavadoc', Javadoc) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.blueshift.core.events

import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import org.junit.After
import org.junit.Before
import org.junit.Test

class BlueshiftEventRepositoryImplTest {
private lateinit var repository: BlueshiftEventRepositoryImpl

@Before
fun setUp() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
repository = BlueshiftEventRepositoryImpl(context)
}

@After
fun tearDown() = runBlocking {
repository.clear()
}

@Test
fun insertEvent_insertsEventsToTheSQLiteDatabase() = runBlocking {
val name = "test_event"
val json = "{\"key\":\"val\"}"
val timestamp = System.currentTimeMillis()
val event = BlueshiftEvent(
eventName = name, eventParams = JSONObject(json), timestamp = timestamp
)

repository.insertEvent(event)
val events = repository.readOneBatch()

assert(events.size == 1)
assert(events[0].eventName == name)
assert(events[0].eventParams.toString() == json)
assert(events[0].timestamp == timestamp)
}

@Test
fun deleteEvents_deletesEventsFromTheSQLiteDatabase() = runBlocking {
val name = "test_event"
val json = "{\"key\":\"val\"}"
val timestamp = 0L
val event = BlueshiftEvent(
eventName = name, eventParams = JSONObject(json), timestamp = timestamp
)

repository.insertEvent(event)
var events = repository.readOneBatch()

assert(events.size == 1)

repository.deleteEvents(events)
events = repository.readOneBatch()

assert(events.isEmpty())
}

@Test
fun readOneBatch_retrievesAListOfHundredEventsWhenCountIsNotSpecified() = runBlocking {
for (i in 1..200) {
val name = "test_event_$i"
val json = "{\"key\":\"val\"}"
val timestamp = 0L
val event = BlueshiftEvent(
eventName = name, eventParams = JSONObject(json), timestamp = timestamp
)
repository.insertEvent(event)
}

val events = repository.readOneBatch()
assert(events.size == 100)
}

@Test
fun readOneBatch_retrievesAListOfTenEventsWhenCountIsSetToTen() = runBlocking {
for (i in 1..200) {
val name = "test_event_$i"
val json = "{\"key\":\"val\"}"
val timestamp = 0L
val event = BlueshiftEvent(
eventName = name, eventParams = JSONObject(json), timestamp = timestamp
)
repository.insertEvent(event)
}

val events = repository.readOneBatch(batchCount = 10)
assert(events.size == 10)
}

@Test
fun readOneBatch_retrievesAListOfEventsInTheSameOrderTheyAreStoredInTheDatabase() =
runBlocking {
for (i in 1..10) {
val name = "test_event_$i"
val json = "{\"key\":\"val\"}"
val timestamp = 0L
val event = BlueshiftEvent(
eventName = name, eventParams = JSONObject(json), timestamp = timestamp
)
repository.insertEvent(event)
}

val events = repository.readOneBatch(batchCount = 10)
for (i in 1..9) {
assert((events[i].id - events[i - 1].id) == 1L)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package com.blueshift.core.network

import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import org.junit.After
import org.junit.Before
import org.junit.Test

class BlueshiftNetworkRequestRepositoryImplTest {
private lateinit var repository: BlueshiftNetworkRequestRepositoryImpl

@Before
fun setUp() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
repository = BlueshiftNetworkRequestRepositoryImpl(context)
}

@After
fun tearDown() = runBlocking {
repository.clear()
}

@Test
fun insertRequest_insertsRequestsToTheSQLiteDatabase(): Unit = runBlocking {
val url = "https://example.com"
val method = BlueshiftNetworkRequest.Method.GET
val header = JSONObject(mapOf("Content-Type" to "application/json"))
val body = JSONObject()
val authRequired = true
val retryBalance = 1
val retryTimestamp = 1234567890L
val timestamp = 1234567890L

val request = BlueshiftNetworkRequest(
url = url,
method = method,
header = header,
body = body,
authorizationRequired = authRequired,
retryAttemptBalance = retryBalance,
retryAttemptTimestamp = retryTimestamp,
timestamp = timestamp
)

repository.insertRequest(request)
val request2 = repository.readNextRequest()

assert(request2 != null)
request2?.let {
assert(it.url == url)
assert(it.method == method)
assert(it.header.toString() == header.toString())
assert(it.body.toString() == body.toString())
assert(it.authorizationRequired == authRequired)
assert(it.retryAttemptBalance == retryBalance)
assert(it.retryAttemptTimestamp == retryTimestamp)
assert(it.timestamp == timestamp)
}
}

@Test
fun updateRequest_updatesRequestsInTheSQLiteDatabase(): Unit = runBlocking {
val url = "https://example.com"
val method = BlueshiftNetworkRequest.Method.GET
val header = JSONObject(mapOf("Content-Type" to "application/json"))
val body = JSONObject()
val authRequired = true
val retryBalance = 1
val retryTimestamp = 1234567890L
val timestamp = 1234567890L

val request = BlueshiftNetworkRequest(
url = url,
method = method,
header = header,
body = body,
authorizationRequired = authRequired,
retryAttemptBalance = retryBalance,
retryAttemptTimestamp = retryTimestamp,
timestamp = timestamp
)

repository.insertRequest(request)

// the code only allows updating the following two fields.
val retryBalance2 = 2
val retryTimestamp2 = 9876543210L

val request2 = repository.readNextRequest()
request2?.let {
it.retryAttemptBalance = retryBalance2
it.retryAttemptTimestamp = retryTimestamp2

repository.updateRequest(it)
}

val request3 = repository.readNextRequest()
assert(request3 != null)
request3?.let {
assert(it.retryAttemptBalance == retryBalance2)
assert(it.retryAttemptTimestamp == retryTimestamp2)
}
}

@Test
fun deleteRequest_deletesRequestsInTheSQLiteDatabase(): Unit = runBlocking {
val url = "https://example.com"
val method = BlueshiftNetworkRequest.Method.GET
val header = JSONObject(mapOf("Content-Type" to "application/json"))
val body = JSONObject()
val authRequired = true
val retryBalance = 1
val retryTimestamp = 1234567890L
val timestamp = 1234567890L

val request = BlueshiftNetworkRequest(
url = url,
method = method,
header = header,
body = body,
authorizationRequired = authRequired,
retryAttemptBalance = retryBalance,
retryAttemptTimestamp = retryTimestamp,
timestamp = timestamp
)

repository.insertRequest(request)

val request2 = repository.readNextRequest()
request2?.let {
repository.deleteRequest(it)
}

val request3 = repository.readNextRequest()
assert(request3 == null)
}

@Test
fun readNextRequest_shouldReturnTheFirstRequestWhenAllRequestsInTheQueueRetryAttemptBalanceGreaterThanZero(): Unit =
runBlocking {
for (i in 1..3) {
val url = "https://api.com/$i"
val method = BlueshiftNetworkRequest.Method.GET
val header = JSONObject(mapOf("Content-Type" to "application/json"))
val body = JSONObject()
val authRequired = true
val retryBalance = 3
val retryTimestamp = 0L
val timestamp = System.currentTimeMillis()
val request = BlueshiftNetworkRequest(
url = url,
method = method,
header = header,
body = body,
authorizationRequired = authRequired,
retryAttemptBalance = retryBalance,
retryAttemptTimestamp = retryTimestamp,
timestamp = timestamp
)
repository.insertRequest(request)
}

val request = repository.readNextRequest()
assert(request != null)
request?.let {
assert(it.url == "https://api.com/1")
}
}

@Test
fun readNextRequest_shouldReturnNullWhenAllRequestsInTheQueueHasRetryAttemptBalanceEqualToZero(): Unit =
runBlocking {
for (i in 1..3) {
val url = "https://api.com/$i"
val method = BlueshiftNetworkRequest.Method.GET
val header = JSONObject(mapOf("Content-Type" to "application/json"))
val body = JSONObject()
val authRequired = true
val retryBalance = 0
val retryTimestamp = 0L
val timestamp = System.currentTimeMillis()
val request = BlueshiftNetworkRequest(
url = url,
method = method,
header = header,
body = body,
authorizationRequired = authRequired,
retryAttemptBalance = retryBalance,
retryAttemptTimestamp = retryTimestamp,
timestamp = timestamp
)
repository.insertRequest(request)
}

val request = repository.readNextRequest()
assert(request == null)
}

@Test
fun readNextRequest_shouldReturnTheRequestWithRetryAttemptTimestampLessThanCurrentTime(): Unit =
runBlocking {
for (i in 1..2) {
val fiveMinutes = 5 * 60 * 1000
val url = "https://api.com/$i"
val method = BlueshiftNetworkRequest.Method.GET
val header = JSONObject(mapOf("Content-Type" to "application/json"))
val body = JSONObject()
val authRequired = true
val retryBalance = 1
val retryTimestamp = if (i % 2 == 0) System.currentTimeMillis() + fiveMinutes else System.currentTimeMillis() - fiveMinutes
val timestamp = System.currentTimeMillis()
val request = BlueshiftNetworkRequest(
url = url,
method = method,
header = header,
body = body,
authorizationRequired = authRequired,
retryAttemptBalance = retryBalance,
retryAttemptTimestamp = retryTimestamp,
timestamp = timestamp
)
repository.insertRequest(request)
}

// i = 1 -> current time - 5min
// i = 2 -> current time + 5min
val request = repository.readNextRequest()
assert(request != null)
request?.let {
assert(it.url == "https://api.com/1")
}
}
}
Loading

0 comments on commit e164124

Please sign in to comment.