From 8bc47d43b22ea3d6dd1e274b2a53d65c1501b55e Mon Sep 17 00:00:00 2001 From: Tim Julien Date: Thu, 2 Mar 2023 12:26:43 -0500 Subject: [PATCH] 3.5.12 release (#271) * publish SNAPSHOT releases from pre-releases not from pushing to develop (#259) * don't publish pre-releases to maven as a release * publish SNAPSHOTs from pre-releases not from pushing to develop * MOB-57: update the README with more detail on example app (#260) Co-authored-by: Jason R Tibbetts <708510+jrtibbetts@users.noreply.github.com> Co-authored-by: Nick Patrick Co-authored-by: Tim Julien * Surface center, radius, and location for geofences & places (#267) * Surface center, radius, and location for geofences & places * rely on geofence's geometry for center and radius, add test * RAD-5747 Anonymous config call in `trackOnce` (#266) * add config call for anonymous before track * add boolean for track usage * usage enum not bool * resume usage * move config track usage call later in the call stack * move config track usage call later in the call stack * use anon val * bump version * 3.5.11 version bump --------- Co-authored-by: Jason R Tibbetts <708510+jrtibbetts@users.noreply.github.com> Co-authored-by: Nick Patrick Co-authored-by: Tim Julien * Include approachingThreshold in tripOptions (#261) * Include approachingThreshold in trip options helper functions * Only include in dictionary if > 0, add test * Offline replay (#270) * First part of offline replay * Fix kotlin errors * Fix params for replays * Extend timeout * Fix unallocated val bug * Fix nowMS bug * Fix connectTimeout numbers * Bump version to 3.5.12-beta.2 to cut a prerelease * Bump nexus client timeout * Fix bugs in buffer logic * bump version to 3.5.11-beta.6 * dont fire event listeners for custom events (#263) * publish beta's the same way we publish final releases (#273) * publish beta's the same way we publish final releases * Delete radar-snapshot-action.yml * Update build.gradle * Always clear last location if successful track (#272) * Always clear last location if succesful track * Always use System.currentTimeMillis() * Always clear ReplayBuffer upon successful request --------- Co-authored-by: Tim Julien * version bump * set replay = NONE for continuous * not beta anymore --------- Co-authored-by: Travis Derouin Co-authored-by: Jason R Tibbetts <708510+jrtibbetts@users.noreply.github.com> Co-authored-by: Nick Patrick Co-authored-by: Liam Meier Co-authored-by: David Goodfellow <61531269+david-goodfellow@users.noreply.github.com> Co-authored-by: jsani-radar --- .github/workflows/radar-release-actions.yml | 5 +- .github/workflows/radar-snapshot-action.yml | 31 ---- README.md | 12 +- .../radar/mvnpublish/RadarNexusClient.groovy | 4 + sdk/build.gradle | 2 +- sdk/src/main/java/io/radar/sdk/Radar.kt | 31 +++- .../main/java/io/radar/sdk/RadarApiClient.kt | 37 ++++- .../main/java/io/radar/sdk/RadarApiHelper.kt | 7 +- .../java/io/radar/sdk/RadarTrackingOptions.kt | 5 + .../java/io/radar/sdk/RadarTripOptions.kt | 8 +- .../java/io/radar/sdk/model/RadarReplay.kt | 45 ++++++ .../java/io/radar/sdk/util/RadarLogBuffer.kt | 2 +- .../io/radar/sdk/util/RadarReplayBuffer.kt | 22 +++ .../radar/sdk/util/RadarSimpleReplayBuffer.kt | 44 ++++++ .../java/io/radar/sdk/RadarApiHelperMock.kt | 1 + .../radar/sdk/model/RadarTripOptionsTest.kt | 144 ++++++++++++++++++ 16 files changed, 350 insertions(+), 50 deletions(-) delete mode 100644 .github/workflows/radar-snapshot-action.yml create mode 100644 sdk/src/main/java/io/radar/sdk/model/RadarReplay.kt create mode 100644 sdk/src/main/java/io/radar/sdk/util/RadarReplayBuffer.kt create mode 100644 sdk/src/main/java/io/radar/sdk/util/RadarSimpleReplayBuffer.kt create mode 100644 sdk/src/test/java/io/radar/sdk/model/RadarTripOptionsTest.kt diff --git a/.github/workflows/radar-release-actions.yml b/.github/workflows/radar-release-actions.yml index d7063a7d..490a28e1 100644 --- a/.github/workflows/radar-release-actions.yml +++ b/.github/workflows/radar-release-actions.yml @@ -2,7 +2,8 @@ name: Radar Release Action on: release: - types: [ published ] + # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release + types: [ prereleased, released ] jobs: Build-And-Publish: runs-on: ubuntu-latest @@ -25,4 +26,4 @@ jobs: ORG_GRADLE_PROJECT_SIGNINGKEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} ORG_GRADLE_PROJECT_SIGNINGPASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} SNAPSHOT: false - run: ./gradlew build publish \ No newline at end of file + run: ./gradlew build publish diff --git a/.github/workflows/radar-snapshot-action.yml b/.github/workflows/radar-snapshot-action.yml deleted file mode 100644 index a44378f6..00000000 --- a/.github/workflows/radar-snapshot-action.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Trigger an SDK publication to SNAPSHOTS repo on Maven Central when a commit is pushed to develop. -name: Radar Snapshot Action -on: - push: - branches: - - develop -jobs: - Build-And-Publish: - runs-on: ubuntu-latest - steps: - # Prepare Environment - - name: Check out repository code - uses: actions/checkout@v2 - - name: Configure Java - uses: actions/setup-java@v2 - with: - java-version: '11' - distribution: 'adopt' - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b - # Gradle Build & Publish - - name: Build SDK, publish to Maven, and dispatch snapshot event - env: - NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} - NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} - ORG_GRADLE_PROJECT_SIGNINGKEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} - ORG_GRADLE_PROJECT_SIGNINGPASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} - PAT_USERNAME: radar-circleci - PAT_TOKEN: ${{ secrets.RADAR_CIRCLECI_PAT }} - SNAPSHOT: true - run: ./gradlew build publish dispatchSnapshot \ No newline at end of file diff --git a/README.md b/README.md index 8f014975..24a19aed 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,17 @@ See migration guides in `MIGRATION.md`. See an example app in `example/`. -To run the example app, clone this repository, add your publishable API key in `MainActivity.java`, and build the app. +* Download and install Android Studio +* Open Android Studio, accept the terms, install the components +* In Android Studio, open the radar-sdk-android folder +* Open `example/src/main/java/io/radar/example/MainActivity.kt` +* Copy your production publishable key to the `Radar.initialize` call +* Run the emulator (press the green play button) +* Type "Settings" into the primary search bar, and then Location into "Search Settings" and click "Location (permission manager)" +* Click "example" and change "Allow all of the time" +* Use your mouse to swipe up from the bottom to get to the home screen +* Type "example" into the main search bar again to run the app +* Click "Run Demo" ## Support diff --git a/buildSrc/src/main/groovy/io/radar/mvnpublish/RadarNexusClient.groovy b/buildSrc/src/main/groovy/io/radar/mvnpublish/RadarNexusClient.groovy index bb5d9879..e24cee97 100644 --- a/buildSrc/src/main/groovy/io/radar/mvnpublish/RadarNexusClient.groovy +++ b/buildSrc/src/main/groovy/io/radar/mvnpublish/RadarNexusClient.groovy @@ -14,6 +14,7 @@ import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Headers import retrofit2.http.POST +import java.util.concurrent.TimeUnit import static io.github.gradlenexus.publishplugin.internal.NexusClient.* @@ -35,6 +36,9 @@ class RadarNexusClient extends NexusClient { RadarNexusClient(String username, String password) { super(BASE_URL, username, password, null, null) OkHttpClient.Builder httpClient = new OkHttpClient.Builder() + .writeTimeout(5, TimeUnit.MINUTES) + .readTimeout(5, TimeUnit.MINUTES) + .connectTimeout(5, TimeUnit.MINUTES) .addInterceptor { chain -> String version = NexusClient.package.implementationVersion ?: 'dev' chain.proceed(chain.request().newBuilder() diff --git a/sdk/build.gradle b/sdk/build.gradle index 880bdfd9..5bd048d0 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -10,7 +10,7 @@ apply plugin: "org.jetbrains.dokka" apply plugin: 'io.radar.mvnpublish' ext { - radarVersion = '3.5.11' + radarVersion = '3.5.12' } String buildNumber = ".${System.currentTimeMillis()}" diff --git a/sdk/src/main/java/io/radar/sdk/Radar.kt b/sdk/src/main/java/io/radar/sdk/Radar.kt index 23623236..bc9818cc 100644 --- a/sdk/src/main/java/io/radar/sdk/Radar.kt +++ b/sdk/src/main/java/io/radar/sdk/Radar.kt @@ -11,6 +11,8 @@ import io.radar.sdk.model.* import io.radar.sdk.model.RadarEvent.RadarEventVerification import io.radar.sdk.util.RadarLogBuffer import io.radar.sdk.util.RadarSimpleLogBuffer +import io.radar.sdk.util.RadarReplayBuffer +import io.radar.sdk.util.RadarSimpleReplayBuffer import org.json.JSONObject import java.util.* @@ -372,6 +374,7 @@ object Radar { internal lateinit var locationManager: RadarLocationManager internal lateinit var beaconManager: RadarBeaconManager private lateinit var logBuffer: RadarLogBuffer + private lateinit var replayBuffer: RadarReplayBuffer internal lateinit var batteryManager: RadarBatteryManager /** @@ -414,6 +417,10 @@ object Radar { this.logBuffer = RadarSimpleLogBuffer() } + if (!this::replayBuffer.isInitialized) { + this.replayBuffer = RadarSimpleReplayBuffer() + } + if (!this::logger.isInitialized) { this.logger = RadarLogger(this.context) } @@ -2596,12 +2603,6 @@ object Radar { return } - if (events != null) { - // The events are returned in the completion handler, but they're also - // sent back via the RadarReceiver. - receiver?.onEventsReceived(context, events, user) - } - handler.post { callback.onComplete(status, location, events, user) } @@ -2642,6 +2643,24 @@ object Radar { } } + @JvmStatic + internal fun getReplays(): List { + val flushable = replayBuffer.getFlushableReplaysStash() + flushable.onFlush(false) + return flushable.get() + } + + @JvmStatic + internal fun clearReplays() { + val flushable = replayBuffer.getFlushableReplaysStash() + flushable.onFlush(true) + } + + @JvmStatic + internal fun addReplay(replayParams: JSONObject) { + replayBuffer.write(replayParams) + } + @JvmStatic internal fun isTestKey(): Boolean { val key = RadarSettings.getPublishableKey(this.context) diff --git a/sdk/src/main/java/io/radar/sdk/RadarApiClient.kt b/sdk/src/main/java/io/radar/sdk/RadarApiClient.kt index 85089ed0..12b5e78a 100644 --- a/sdk/src/main/java/io/radar/sdk/RadarApiClient.kt +++ b/sdk/src/main/java/io/radar/sdk/RadarApiClient.kt @@ -159,6 +159,7 @@ internal class RadarApiClient( callback?.onComplete(status, res) } }, + extendedTimeout = false, stream = true, // Do not log the saved log events. If the logs themselves were logged it would create a redundancy and // eventually lead to a crash when creating a downstream log request, since these will log to memory as a @@ -284,14 +285,37 @@ internal class RadarApiClient( val uri = Uri.parse(host).buildUpon() .appendEncodedPath("v1/track") .build() - val url = URL(uri.toString()) - + var url = URL(uri.toString()) val headers = headers(publishableKey) - apiHelper.request(context, "POST", url, headers, params, true, object : RadarApiHelper.RadarApiCallback { + var requestParams = params + var replays = Radar.getReplays() + val replayCount = replays.size + var nowMS = System.currentTimeMillis() + val replaying = replayCount > 0 && options.replay == RadarTrackingOptions.RadarTrackingOptionsReplay.ALL + if (replaying) { + val replayList = mutableListOf() + for (replay in replays) { + replayList.add(replay.replayParams) + } + replayList.add(params) + requestParams = JSONObject() + requestParams.putOpt("replays", JSONArray(replayList)) + val replayUri = Uri.parse(host).buildUpon() + .appendEncodedPath("v1/track/replay") + .build() + url = URL(replayUri.toString()) + } + + apiHelper.request(context, "POST", url, headers, requestParams, true, object : RadarApiHelper.RadarApiCallback { override fun onComplete(status: RadarStatus, res: JSONObject?) { if (status != RadarStatus.SUCCESS || res == null) { - if (options.replay == RadarTrackingOptions.RadarTrackingOptionsReplay.STOPS && stopped && !(source == RadarLocationSource.FOREGROUND_LOCATION || source == RadarLocationSource.BACKGROUND_LOCATION)) { + if (options.replay == RadarTrackingOptions.RadarTrackingOptionsReplay.ALL) { + params.putOpt("replayed", true) + params.putOpt("updatedAtMs", nowMS) + params.remove("updatedAtMsDiff") + Radar.addReplay(params) + } else if (options.replay == RadarTrackingOptions.RadarTrackingOptionsReplay.STOPS && stopped && !(source == RadarLocationSource.FOREGROUND_LOCATION || source == RadarLocationSource.BACKGROUND_LOCATION)) { RadarState.setLastFailedStoppedLocation(context, location) } @@ -301,9 +325,10 @@ internal class RadarApiClient( return } - Radar.flushLogs() + Radar.clearReplays() RadarState.setLastFailedStoppedLocation(context, null) + Radar.flushLogs() val config = RadarConfig.fromJson(res) @@ -369,7 +394,7 @@ internal class RadarApiClient( callback?.onComplete(RadarStatus.ERROR_SERVER) } - }) + }, replaying, false, !replaying) } internal fun verifyEvent(eventId: String, verification: RadarEventVerification, verifiedPlaceId: String? = null) { diff --git a/sdk/src/main/java/io/radar/sdk/RadarApiHelper.kt b/sdk/src/main/java/io/radar/sdk/RadarApiHelper.kt index e7713c56..94e1569c 100644 --- a/sdk/src/main/java/io/radar/sdk/RadarApiHelper.kt +++ b/sdk/src/main/java/io/radar/sdk/RadarApiHelper.kt @@ -31,6 +31,7 @@ internal open class RadarApiHelper( params: JSONObject?, sleep: Boolean, callback: RadarApiCallback? = null, + extendedTimeout: Boolean = false, stream: Boolean = false, logPayload: Boolean = true) { if (logPayload) { @@ -53,7 +54,11 @@ internal open class RadarApiHelper( } urlConnection.requestMethod = method urlConnection.connectTimeout = 10000 - urlConnection.readTimeout = 10000 + if (extendedTimeout) { + urlConnection.readTimeout = 25000 + } else { + urlConnection.readTimeout = 10000 + } if (stream) { urlConnection.setChunkedStreamingMode(1024) } diff --git a/sdk/src/main/java/io/radar/sdk/RadarTrackingOptions.kt b/sdk/src/main/java/io/radar/sdk/RadarTrackingOptions.kt index b8f9eb4f..7d1ca90f 100644 --- a/sdk/src/main/java/io/radar/sdk/RadarTrackingOptions.kt +++ b/sdk/src/main/java/io/radar/sdk/RadarTrackingOptions.kt @@ -167,6 +167,8 @@ data class RadarTrackingOptions( * The replay options for failed location updates. */ enum class RadarTrackingOptionsReplay(internal val replay: Int) { + /** Replays all failed location updates. */ + ALL(2), /** Replays failed stops. */ STOPS(1), /** Replays no location updates. */ @@ -175,6 +177,7 @@ data class RadarTrackingOptions( internal companion object { internal const val STOPS_STR = "stops" internal const val NONE_STR = "none" + internal const val ALL_STR = "all" fun fromInt(replay: Int?): RadarTrackingOptionsReplay { for (value in values()) { @@ -189,6 +192,7 @@ data class RadarTrackingOptions( return when(replay) { STOPS_STR -> STOPS NONE_STR -> NONE + ALL_STR -> ALL else -> NONE } } @@ -198,6 +202,7 @@ data class RadarTrackingOptions( return when(this) { STOPS -> STOPS_STR NONE -> NONE_STR + ALL -> ALL_STR } } } diff --git a/sdk/src/main/java/io/radar/sdk/RadarTripOptions.kt b/sdk/src/main/java/io/radar/sdk/RadarTripOptions.kt index 8a0b82a7..48fe8977 100644 --- a/sdk/src/main/java/io/radar/sdk/RadarTripOptions.kt +++ b/sdk/src/main/java/io/radar/sdk/RadarTripOptions.kt @@ -50,6 +50,7 @@ data class RadarTripOptions( internal const val KEY_DESTINATION_GEOFENCE_EXTERNAL_ID = "destinationGeofenceExternalId" internal const val KEY_MODE = "mode" internal const val KEY_SCHEDULED_ARRIVAL_AT = "scheduledArrivalAt" + internal const val KEY_APPROACHING_THRESHOLD = "approachingThreshold" @JvmStatic fun fromJson(obj: JSONObject): RadarTripOptions { @@ -75,6 +76,7 @@ data class RadarTripOptions( RadarUtils.isoStringToDate(obj.optString(KEY_SCHEDULED_ARRIVAL_AT)) } } else null, + approachingThreshold = obj.optInt(KEY_APPROACHING_THRESHOLD) ) } @@ -88,6 +90,9 @@ data class RadarTripOptions( obj.put(KEY_DESTINATION_GEOFENCE_EXTERNAL_ID, destinationGeofenceExternalId) obj.put(KEY_MODE, Radar.stringForMode(mode)) obj.put(KEY_SCHEDULED_ARRIVAL_AT, scheduledArrivalAt?.time) + if (approachingThreshold > 0) { + obj.put(KEY_APPROACHING_THRESHOLD, approachingThreshold) + } return obj } @@ -107,7 +112,8 @@ data class RadarTripOptions( this.destinationGeofenceTag == other.destinationGeofenceTag && this.destinationGeofenceExternalId == other.destinationGeofenceExternalId && this.mode == other.mode && - this.scheduledArrivalAt?.time == other.scheduledArrivalAt?.time + this.scheduledArrivalAt?.time == other.scheduledArrivalAt?.time && + this.approachingThreshold == other.approachingThreshold } } \ No newline at end of file diff --git a/sdk/src/main/java/io/radar/sdk/model/RadarReplay.kt b/sdk/src/main/java/io/radar/sdk/model/RadarReplay.kt new file mode 100644 index 00000000..c66e47b0 --- /dev/null +++ b/sdk/src/main/java/io/radar/sdk/model/RadarReplay.kt @@ -0,0 +1,45 @@ +package io.radar.sdk.model + +import io.radar.sdk.Radar +import org.json.JSONObject + +/** + * Represents a replay. + */ + + internal data class RadarReplay( + val replayParams: JSONObject +) : Comparable { + + companion object { + private const val REPLAY_PARAMS = "replayParams" + + // does this make sense? + @JvmStatic + fun fromJson(json: JSONObject): RadarReplay { + return RadarReplay( + replayParams = json.optJSONObject(REPLAY_PARAMS) + ) + } + } + + // does this make sense as well? + fun toJson(): JSONObject { + return JSONObject().apply { + putOpt(REPLAY_PARAMS, replayParams) + } + } + + fun toListofJson(replays: List): List { + val replayList = mutableListOf() + for (replay in replays) { + replayList.add(replay.toJson()) + } + return replayList + + } + + override fun compareTo(other: RadarReplay): Int { + return replayParams.toString().compareTo(other.replayParams.toString()) + } +} \ No newline at end of file diff --git a/sdk/src/main/java/io/radar/sdk/util/RadarLogBuffer.kt b/sdk/src/main/java/io/radar/sdk/util/RadarLogBuffer.kt index c791b04a..e4c81645 100644 --- a/sdk/src/main/java/io/radar/sdk/util/RadarLogBuffer.kt +++ b/sdk/src/main/java/io/radar/sdk/util/RadarLogBuffer.kt @@ -11,7 +11,7 @@ internal interface RadarLogBuffer { * @param[level] log level * @param[message] log message */ - fun write(level: Radar.RadarLogLevel, message: String) + fun write(level: Radar.RadarLogLevel , message: String) /** * Creates a stash of the logs currently in the buffer and returns them as a [Flushable] so that a successful diff --git a/sdk/src/main/java/io/radar/sdk/util/RadarReplayBuffer.kt b/sdk/src/main/java/io/radar/sdk/util/RadarReplayBuffer.kt new file mode 100644 index 00000000..af9342ac --- /dev/null +++ b/sdk/src/main/java/io/radar/sdk/util/RadarReplayBuffer.kt @@ -0,0 +1,22 @@ +package io.radar.sdk.util + +import io.radar.sdk.Radar +import io.radar.sdk.model.RadarReplay +import org.json.JSONObject + +internal interface RadarReplayBuffer { + + /** + * Write an element to the buffer + * + */ + fun write(replayParams: JSONObject) + + /** + * Creates a stash of the logs currently in the buffer and returns them as a [Flushable] so that a successful + * callback can cleanup this log buffer by deleting old log files. + * + * @return a [Flushable] containing all stored logs + */ + fun getFlushableReplaysStash(): Flushable +} \ No newline at end of file diff --git a/sdk/src/main/java/io/radar/sdk/util/RadarSimpleReplayBuffer.kt b/sdk/src/main/java/io/radar/sdk/util/RadarSimpleReplayBuffer.kt new file mode 100644 index 00000000..ec9813a2 --- /dev/null +++ b/sdk/src/main/java/io/radar/sdk/util/RadarSimpleReplayBuffer.kt @@ -0,0 +1,44 @@ +package io.radar.sdk.util + +import io.radar.sdk.Radar +import io.radar.sdk.model.RadarReplay +// TODO: determine if we need the above and below +import java.util.concurrent.LinkedBlockingDeque +import org.json.JSONObject + +/** + * A buffer for replay events. + */ + +internal class RadarSimpleReplayBuffer : RadarReplayBuffer { + + private companion object { + const val MAXIMUM_CAPACITY = 120 + } + + private val buffer = LinkedBlockingDeque(MAXIMUM_CAPACITY) + + override fun write(replayParams: JSONObject) { + if (buffer.size >= MAXIMUM_CAPACITY) { + buffer.removeFirst() + } + buffer.offer(RadarReplay(replayParams)) + } + + override fun getFlushableReplaysStash(): Flushable { + val replays = buffer.toList() + + return object : Flushable { + + override fun get(): List { + return replays + } + + override fun onFlush(success: Boolean) { + if (success) { + buffer.clear() + } + } + } + } +} \ No newline at end of file diff --git a/sdk/src/test/java/io/radar/sdk/RadarApiHelperMock.kt b/sdk/src/test/java/io/radar/sdk/RadarApiHelperMock.kt index 3e9aad07..6de6665e 100644 --- a/sdk/src/test/java/io/radar/sdk/RadarApiHelperMock.kt +++ b/sdk/src/test/java/io/radar/sdk/RadarApiHelperMock.kt @@ -26,6 +26,7 @@ internal class RadarApiHelperMock : RadarApiHelper() { params: JSONObject?, sleep: Boolean, callback: RadarApiCallback?, + extendedTimeout: Boolean, stream: Boolean, logPayload: Boolean) { // Use the entry in the mockResponses map, if any. diff --git a/sdk/src/test/java/io/radar/sdk/model/RadarTripOptionsTest.kt b/sdk/src/test/java/io/radar/sdk/model/RadarTripOptionsTest.kt new file mode 100644 index 00000000..abd60c9c --- /dev/null +++ b/sdk/src/test/java/io/radar/sdk/model/RadarTripOptionsTest.kt @@ -0,0 +1,144 @@ +package io.radar.sdk.model + +import android.os.Build +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.radar.sdk.RadarTrackingOptions +import io.radar.sdk.RadarTripOptions +import junit.framework.Assert.assertNull +import org.json.JSONObject +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import java.util.* + +@RunWith(AndroidJUnit4::class) +@Config(sdk = [Build.VERSION_CODES.P]) +class RadarTripOptionsTest { + + @Test + fun testToFromJsonWithNonZeroApproachingThreshold() { + val tripOptions = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + approachingThreshold = 5 + ) + val jsonObject = tripOptions.toJson() + + assertEquals("externalId", jsonObject["externalId"]) + assertEquals("destinationGeofenceTag", jsonObject["destinationGeofenceTag"]) + assertEquals("destinationGeofenceExternalId", jsonObject["destinationGeofenceExternalId"]) + assertEquals(5, jsonObject["approachingThreshold"]) + + // Deserialize it and compare them again + val deserializedTripOptions = RadarTripOptions.fromJson(jsonObject) + assertEquals(tripOptions.externalId, deserializedTripOptions.externalId) + assertEquals(tripOptions.destinationGeofenceTag, deserializedTripOptions.destinationGeofenceTag) + assertEquals(tripOptions.destinationGeofenceExternalId, deserializedTripOptions.destinationGeofenceExternalId) + assertEquals(tripOptions.approachingThreshold, deserializedTripOptions.approachingThreshold) + } + + @Test + fun testToFromJsonWithZeroApproachingThresholdIgnoresThreshold() { + val tripOptions = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + approachingThreshold = 0 // values less than 1 aren't serialized + ) + val jsonObject = tripOptions.toJson() + + assertEquals("externalId", jsonObject["externalId"]) + assertEquals("destinationGeofenceTag", jsonObject["destinationGeofenceTag"]) + assertEquals("destinationGeofenceExternalId", jsonObject["destinationGeofenceExternalId"]) + assertFalse(jsonObject.has("approachingThreshold")) + + // Deserialize it and compare them again + val deserializedTripOptions = RadarTripOptions.fromJson(jsonObject) + assertEquals(tripOptions.externalId, deserializedTripOptions.externalId) + assertEquals(tripOptions.destinationGeofenceTag, deserializedTripOptions.destinationGeofenceTag) + assertEquals(tripOptions.destinationGeofenceExternalId, deserializedTripOptions.destinationGeofenceExternalId) + assertEquals(tripOptions.approachingThreshold, deserializedTripOptions.approachingThreshold) // both are 0 + } + + @Test + fun testToFromJsonWithNegativeApproachingThresholdIgnoresThreshold() { + val tripOptions = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + approachingThreshold = -5 // negative values aren't serialized + ) + val jsonObject = tripOptions.toJson() + + assertEquals("externalId", jsonObject["externalId"]) + assertEquals("destinationGeofenceTag", jsonObject["destinationGeofenceTag"]) + assertEquals("destinationGeofenceExternalId", jsonObject["destinationGeofenceExternalId"]) + assertFalse(jsonObject.has("approachingThreshold")) + + // Deserialize it and compare them again + val deserializedTripOptions = RadarTripOptions.fromJson(jsonObject) + assertEquals(tripOptions.externalId, deserializedTripOptions.externalId) + assertEquals(tripOptions.destinationGeofenceTag, deserializedTripOptions.destinationGeofenceTag) + assertEquals(tripOptions.destinationGeofenceExternalId, deserializedTripOptions.destinationGeofenceExternalId) + assertNotEquals(tripOptions.approachingThreshold, deserializedTripOptions.approachingThreshold) + } + + @Test + fun testIsEqualsWithDifferentApproachingThresholdsReturnsFalse() { + val tripOptions1 = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + approachingThreshold = -5 + ) + + val tripOptions2 = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + approachingThreshold = 11 + ) + + assertNotEquals(tripOptions1, tripOptions2) + } + + @Test + fun testIsEqualsWithUnsetApproachingThresholdsReturnsFalse() { + val tripOptions1 = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + ) + + val tripOptions2 = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + approachingThreshold = 11 + ) + + assertNotEquals(tripOptions1, tripOptions2) + } + + @Test + fun testIsEqualsWithSameApproachingThresholdsReturnsTrue() { + val tripOptions1 = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + approachingThreshold = 11 + ) + + val tripOptions2 = RadarTripOptions( + externalId = "externalId", + destinationGeofenceTag = "destinationGeofenceTag", + destinationGeofenceExternalId = "destinationGeofenceExternalId", + approachingThreshold = 11 + ) + + assertEquals(tripOptions1, tripOptions2) + } + +} \ No newline at end of file