diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index d3e20f12..77091043 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -23,7 +23,7 @@ jobs: run: ./gradlew ktlintCheck - name: Run tests - run: ./gradlew testDebug --stacktrace + run: ./gradlew testDebugUnitTest --stacktrace android_tests: runs-on: macos-latest diff --git a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt index f73a1320..5c682869 100644 --- a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt +++ b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt @@ -36,6 +36,8 @@ object Deps { "com.google.android:flexbox:${Versions.flexBox}" const val services = "com.google.android.gms:play-services-wearable:${Versions.services}" + const val gson = + "com.google.code.gson:gson:${Versions.gson}" } object Emoji { @@ -76,6 +78,8 @@ object Deps { "org.mockito:mockito-core:${Versions.mockito}" const val mockitoInline = "org.mockito:mockito-inline:${Versions.mockito}" + const val coroutines = + "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutinesTest}" } object UiTest { diff --git a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt index b966feca..3ebc4b10 100644 --- a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt +++ b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt @@ -18,6 +18,7 @@ object Versions { const val viewpager2 = "1.0.0" const val flexBox = "2.0.1" const val services = "18.0.0" + const val gson = "2.10.1" const val cardView = "1.0.0" const val material = "1.2.0" const val emoji = "1.4.0" @@ -33,4 +34,5 @@ object Versions { const val junitUi = "1.1.4" const val espresso = "3.5.0" const val mockito = "5.2.0" + const val coroutinesTest = "1.7.1" } \ No newline at end of file diff --git a/features/feature_wear/build.gradle.kts b/features/feature_wear/build.gradle.kts index cec75964..e2e39682 100644 --- a/features/feature_wear/build.gradle.kts +++ b/features/feature_wear/build.gradle.kts @@ -19,6 +19,10 @@ dependencies { implementation(project(":core")) implementation(project(":wearrpc")) implementation(Deps.Google.services) + implementation(Deps.Google.gson) implementation(Deps.Google.dagger) kapt(Deps.Kapt.dagger) + + testImplementation(Deps.Test.junit) + testImplementation(Deps.Test.coroutines) } diff --git a/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/DomainAPI.kt b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearCommunicationInteractor.kt similarity index 89% rename from features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/DomainAPI.kt rename to features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearCommunicationInteractor.kt index 93fa3363..a47ebb0c 100644 --- a/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/DomainAPI.kt +++ b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearCommunicationInteractor.kt @@ -17,20 +17,22 @@ import com.example.util.simpletimetracker.domain.model.RunningRecord import com.example.util.simpletimetracker.wearrpc.Activity import com.example.util.simpletimetracker.wearrpc.CurrentActivity import com.example.util.simpletimetracker.wearrpc.Settings -import com.example.util.simpletimetracker.wearrpc.SimpleTimeTrackerAPI +import com.example.util.simpletimetracker.wearrpc.WearCommunicationAPI import com.example.util.simpletimetracker.wearrpc.Tag +import javax.inject.Inject -class DomainAPI( +class WearCommunicationInteractor @Inject constructor( private val prefsInteractor: PrefsInteractor, private val recordTypeInteractor: RecordTypeInteractor, private val recordTagInteractor: RecordTagInteractor, private val runningRecordInteractor: RunningRecordInteractor, private val removeRunningRecordMediator: RemoveRunningRecordMediator, private val appColorMapper: AppColorMapper, -) : SimpleTimeTrackerAPI { +) : WearCommunicationAPI { override suspend fun queryActivities(): Array { - return recordTypeInteractor.getAll().filter { recordType -> !recordType.hidden } + return recordTypeInteractor.getAll() + .filter { recordType -> !recordType.hidden } .map { recordType -> Activity( id = recordType.id, @@ -67,15 +69,18 @@ class DomainAPI( id = currentActivity.id, timeStarted = currentActivity.startedAt, comment = "", - tagIds = currentActivity.tags.map { t -> t.id }, + tagIds = currentActivity.tags.map(Tag::id), ) } override suspend fun queryTagsForActivity(activityId: Long): Array { val activityColor = recordTypeInteractor.get(activityId)?.color - return recordTagInteractor.getByTypeOrUntyped(activityId).filter { !it.archived } - .map { asTag(it, asColor(activityColor)) }.sortedBy { it.name } - .sortedBy { it.isGeneral }.toTypedArray() + return recordTagInteractor.getByTypeOrUntyped(activityId) + .filter { !it.archived } + .map { asTag(it, asColor(activityColor)) } + .sortedBy { it.name } + .sortedBy { it.isGeneral } + .toTypedArray() } private fun asTag(recordTag: RecordTag?, activityColor: Long = 0x00000000): Tag { @@ -113,5 +118,4 @@ class DomainAPI( recordTagSelectionEvenForGeneralTags = prefsInteractor.getRecordTagSelectionEvenForGeneralTags(), ) } - } \ No newline at end of file diff --git a/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearRPCServer.kt b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearRPCServer.kt new file mode 100644 index 00000000..c12d49b7 --- /dev/null +++ b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearRPCServer.kt @@ -0,0 +1,78 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package com.example.util.simpletimetracker.feature_wear + +import com.example.util.simpletimetracker.wearrpc.CurrentActivity +import com.example.util.simpletimetracker.wearrpc.Request +import com.example.util.simpletimetracker.wearrpc.WearCommunicationAPI +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import timber.log.Timber +import javax.inject.Inject + +class WearRPCServer @Inject constructor( + private val api: WearCommunicationAPI, +) { + + private val gson = Gson() + + suspend fun onRequest(path: String, request: ByteArray): ByteArray? { + return if (path.startsWith(Request.PATH)) { + when (path) { + Request.PING -> onPing(request) + Request.QUERY_ACTIVITIES -> onQueryActivities() + Request.QUERY_CURRENT_ACTIVITIES -> onQueryCurrentActivities() + Request.SET_CURRENT_ACTIVITIES -> onSetCurrentActivities(request) + Request.QUERY_TAGS_FOR_ACTIVITY -> onQueryTagsForActivity(request) + Request.QUERY_SETTINGS -> onQuerySettings() + else -> { + Timber.d("$path is an invalid RPC call") + null + } + } + } else { + null + } + } + + private suspend fun onQueryTagsForActivity(request: ByteArray): ByteArray? { + val activityId: Long = mapRequest(request) ?: return null + return mapToResponse(api.queryTagsForActivity(activityId)) + } + + private suspend fun onSetCurrentActivities(request: ByteArray): ByteArray? { + val activities: Array = mapRequest(request) ?: return null + api.setCurrentActivities(activities) + return ByteArray(0) + } + + private fun onPing(request: ByteArray): ByteArray { + return request + } + + private suspend fun onQueryActivities(): ByteArray { + return mapToResponse(api.queryActivities()) + } + + private suspend fun onQueryCurrentActivities(): ByteArray { + return mapToResponse(api.queryCurrentActivities()) + } + + private suspend fun onQuerySettings(): ByteArray { + return mapToResponse(api.querySettings()) + } + + private fun mapToResponse(data: T): ByteArray { + return gson.toJson(data).toByteArray() + } + + private inline fun mapRequest(data: ByteArray): T? { + return runCatching { + val collectionType = object : TypeToken() {}.type + gson.fromJson(String(data), collectionType) + }.getOrNull() + } +} \ No newline at end of file diff --git a/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearService.kt b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearService.kt index ac80def5..58d34e65 100644 --- a/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearService.kt +++ b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearService.kt @@ -5,13 +5,6 @@ */ package com.example.util.simpletimetracker.feature_wear -import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor -import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor -import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor -import com.example.util.simpletimetracker.domain.interactor.RemoveRunningRecordMediator -import com.example.util.simpletimetracker.domain.interactor.RunningRecordInteractor -import com.example.util.simpletimetracker.domain.mapper.AppColorMapper -import com.example.util.simpletimetracker.wearrpc.WearRPCServer import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.Tasks import com.google.android.gms.wearable.WearableListenerService @@ -29,34 +22,16 @@ import javax.inject.Inject class WearService : WearableListenerService() { @Inject - lateinit var prefsInteractor: PrefsInteractor - - @Inject - lateinit var recordTypeInteractor: RecordTypeInteractor - - @Inject - lateinit var recordTagInteractor: RecordTagInteractor - - @Inject - lateinit var runningRecordInteractor: RunningRecordInteractor - - @Inject - lateinit var removeRunningRecordMediator: RemoveRunningRecordMediator - - @Inject - lateinit var appColorMapper: AppColorMapper - - override fun onRequest(nodeId: String, path: String, request: ByteArray): Task? { - val rpc = WearRPCServer( - DomainAPI( - prefsInteractor, - recordTypeInteractor, - recordTagInteractor, - runningRecordInteractor, - removeRunningRecordMediator, - appColorMapper, - ), - ) - return runBlocking { Tasks.forResult(rpc.onRequest(path, request)) } + lateinit var wearRPCServer: WearRPCServer + + override fun onRequest( + nodeId: String, + path: String, + request: ByteArray, + ): Task? { + // Can block because service is on separate thread. + return runBlocking { + Tasks.forResult(wearRPCServer.onRequest(path, request)) + } } } diff --git a/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WidgetModule.kt b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WidgetModule.kt new file mode 100644 index 00000000..11b23e94 --- /dev/null +++ b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WidgetModule.kt @@ -0,0 +1,15 @@ +package com.example.util.simpletimetracker.feature_wear + +import com.example.util.simpletimetracker.wearrpc.WearCommunicationAPI +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface WidgetModule { + + @Binds + fun WearCommunicationInteractor.bindWearCommunicationInteractor(): WearCommunicationAPI +} \ No newline at end of file diff --git a/wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/WearRPCTest.kt b/features/feature_wear/src/test/java/com/example/util/simpletimetracker/feature_wear/WearRPCServerTest.kt similarity index 66% rename from wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/WearRPCTest.kt rename to features/feature_wear/src/test/java/com/example/util/simpletimetracker/feature_wear/WearRPCServerTest.kt index f876c6d8..686bb29a 100644 --- a/wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/WearRPCTest.kt +++ b/features/feature_wear/src/test/java/com/example/util/simpletimetracker/feature_wear/WearRPCServerTest.kt @@ -3,8 +3,15 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -package com.example.util.simpletimetracker.wearrpc - +package com.example.util.simpletimetracker.feature_wear + +import com.example.util.simpletimetracker.wearrpc.Activity +import com.example.util.simpletimetracker.wearrpc.CurrentActivity +import com.example.util.simpletimetracker.wearrpc.Messenger +import com.example.util.simpletimetracker.wearrpc.MockWearCommunicationAPI +import com.example.util.simpletimetracker.wearrpc.Settings +import com.example.util.simpletimetracker.wearrpc.Tag +import com.example.util.simpletimetracker.wearrpc.WearRPCClient import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals @@ -14,42 +21,46 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +// TODO use mockito +@Suppress("LocalVariableName") +class WearRPCServerTest { + private lateinit var api: MockWearCommunicationAPI + private lateinit var rpc: WearRPCServer + private lateinit var messenger: Messenger + private lateinit var client: WearRPCClient -open class WearRPCServerTestBase { - lateinit var api: MockSimpleTimeTrackerAPI - lateinit var rpc: WearRPCServer - lateinit var messenger: Messenger - lateinit var client: WearRPCClient + private val sampleSettings = Settings( + allowMultitasking = true, + showRecordTagSelection = false, + recordTagSelectionCloseAfterOne = false, + recordTagSelectionEvenForGeneralTags = false, + ) - val tagFriends = Tag(1, "Friends", isGeneral = false, 0xFFFD3251) - val tagFamily = Tag(2, "Family", isGeneral = true, 0xFFFD3251) - val tagShopping = Tag(3, "Shopping", isGeneral = false, 0xFFFF0000) - val tagWork = Tag(14, "Work", isGeneral = false, 0xFF00FF00) + private val tagFriends = Tag(1, "Friends", isGeneral = false, 0xFFFD3251) + private val tagFamily = Tag(2, "Family", isGeneral = true, 0xFFFD3251) + private val tagShopping = Tag(3, "Shopping", isGeneral = false, 0xFFFF0000) + private val tagWork = Tag(14, "Work", isGeneral = false, 0xFF00FF00) @Before fun setup() { - api = MockSimpleTimeTrackerAPI() + api = MockWearCommunicationAPI() rpc = WearRPCServer(api) messenger = MockMessenger(rpc) client = WearRPCClient(messenger) api.mockReset() } -} -class WearRPCServerTest : WearRPCServerTestBase() { @Test fun returns_null_for_unsupported_request() = runTest { val response = rpc.onRequest("/fake/path", "fake data".toByteArray()) assertNull(response) } - @Test(expected = WearRPCException::class) - fun raises_for_invalid_SimpleTimeTracker_request() = runTest { - rpc.onRequest("/stt//GET/fake/path", "fake data".toByteArray()) + @Test + fun returns_null_for_invalid_request() = runTest { + val response = rpc.onRequest("/stt//GET/fake/path", "fake data".toByteArray()) + assertNull(response) } -} - -class PingTest : WearRPCServerTestBase() { @Test fun responds_to_empty_request_with_empty_string() = runTest { @@ -62,9 +73,7 @@ class PingTest : WearRPCServerTestBase() { val response = client.ping("Hello World!") assertEquals("Hello World!", response) } -} -class GetActivitiesTest : WearRPCServerTestBase() { @Test fun returns_no_activities_when_none_are_available() = runTest { val response = client.queryActivities() @@ -89,9 +98,7 @@ class GetActivitiesTest : WearRPCServerTestBase() { val response = client.queryActivities() assertArrayEquals(activities, response) } -} -class QueryCurrentActivitiesTest : WearRPCServerTestBase() { @Test fun returns_no_activities_when_none_are_running() = runTest { val activities = arrayOf() @@ -100,7 +107,7 @@ class QueryCurrentActivitiesTest : WearRPCServerTestBase() { } @Test - fun returns_one_activity_when_one_exists() = runTest { + fun returns_one_current_activity_when_one_exists() = runTest { val jan_31_2024_afternoon = 1706704801L val activities = arrayOf( CurrentActivity( @@ -134,9 +141,7 @@ class QueryCurrentActivitiesTest : WearRPCServerTestBase() { val response = client.queryCurrentActivities() assertArrayEquals(activities, response) } -} -class QueryTagsForActivityTest : WearRPCServerTestBase() { @Test fun returns_no_tags_if_activity_has_none() = runTest { api.mock_queryTagsForActivity(mapOf(13L to arrayOf())) @@ -145,7 +150,7 @@ class QueryTagsForActivityTest : WearRPCServerTestBase() { } @Test - fun returns_no_tags_if_activity_doesnt_exist() = runTest { + fun returns_no_tags_if_activity_does_not_exist() = runTest { val response = client.queryTagsForActivity(42) assertArrayEquals(arrayOf(), response) } @@ -176,15 +181,7 @@ class QueryTagsForActivityTest : WearRPCServerTestBase() { val responseOther = client.queryTagsForActivity(17L) assertArrayEquals(otherTags, responseOther) } -} -class QuerySettingsTest : WearRPCServerTestBase() { - private val sampleSettings = Settings( - allowMultitasking = true, - showRecordTagSelection = false, - recordTagSelectionCloseAfterOne = false, - recordTagSelectionEvenForGeneralTags = false, - ) @Test fun returns_settings_with_multitasking_enabled() = runTest { api.mock_querySettings(sampleSettings) @@ -198,64 +195,11 @@ class QuerySettingsTest : WearRPCServerTestBase() { val response = client.querySettings() assertFalse(response.allowMultitasking) } -} - - -class MockSimpleTimeTrackerAPI : SimpleTimeTrackerAPI { - - var activities: Array = arrayOf() - var currentActivities: Array = arrayOf() - var tags: Map> = mapOf() - lateinit var settings: Settings - - override suspend fun queryActivities(): Array { - return activities - } - - fun mock_queryActivities(activities: Array) { - this.activities = activities - } - override suspend fun queryCurrentActivities(): Array { - return currentActivities - } - - fun mock_queryCurrentActivities(activities: Array) { - this.currentActivities = activities - } + class MockMessenger(private val rpc: WearRPCServer) : Messenger { - override suspend fun setCurrentActivities(activities: Array) { - TODO("Not yet implemented") + override suspend fun send(capability: String, message: ByteArray): ByteArray? { + return rpc.onRequest(capability, message) + } } - - override suspend fun queryTagsForActivity(activityId: Long): Array { - return this.tags.getOrDefault(activityId, arrayOf()) - } - - fun mock_queryTagsForActivity(tags: Map>) { - this.tags = tags - } - - override suspend fun querySettings(): Settings { - return settings - } - - fun mock_querySettings(settings: Settings) { - this.settings = settings - } - - fun mockReset() { - this.activities = arrayOf() - this.currentActivities = arrayOf() - this.tags = mapOf() - } - -} - -class MockMessenger(private val rpc: WearRPCServer) : Messenger { - - override suspend fun send(capability: String, message: ByteArray): ByteArray? { - return rpc.onRequest(capability, message) - } - } \ No newline at end of file diff --git a/wearrpc/build.gradle.kts b/wearrpc/build.gradle.kts index fa1f5e69..b7692fb5 100644 --- a/wearrpc/build.gradle.kts +++ b/wearrpc/build.gradle.kts @@ -1,4 +1,5 @@ import com.example.util.simpletimetracker.Base +import com.example.util.simpletimetracker.Deps /* * This Source Code Form is subject to the terms of the Mozilla Public @@ -40,16 +41,12 @@ android { } dependencies { - // Custom Dependencies - implementation("com.google.android.gms:play-services-wearable:18.0.0") - implementation("com.google.code.gson:gson:2.10.1") - - // Auto-generated dependencies + implementation(Deps.Google.services) + implementation(Deps.Google.gson) implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.11.0") - testImplementation("junit:junit:4.13.2") - testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + + testImplementation(Deps.Test.junit) + testImplementation(Deps.Test.coroutines) } \ No newline at end of file diff --git a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/MockWearCommunicationAPI.kt b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/MockWearCommunicationAPI.kt new file mode 100644 index 00000000..ff187c5e --- /dev/null +++ b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/MockWearCommunicationAPI.kt @@ -0,0 +1,52 @@ +package com.example.util.simpletimetracker.wearrpc + +class MockWearCommunicationAPI : WearCommunicationAPI { + + var activities: Array = arrayOf() + var currentActivities: Array = arrayOf() + var tags: Map> = mapOf() + lateinit var settings: Settings + + override suspend fun queryActivities(): Array { + return activities + } + + fun mock_queryActivities(activities: Array) { + this.activities = activities + } + + override suspend fun queryCurrentActivities(): Array { + return currentActivities + } + + fun mock_queryCurrentActivities(activities: Array) { + this.currentActivities = activities + } + + override suspend fun setCurrentActivities(activities: Array) { + TODO("Not yet implemented") + } + + override suspend fun queryTagsForActivity(activityId: Long): Array { + return this.tags[activityId] ?: arrayOf() + } + + fun mock_queryTagsForActivity(tags: Map>) { + this.tags = tags + } + + override suspend fun querySettings(): Settings { + return settings + } + + fun mock_querySettings(settings: Settings) { + this.settings = settings + } + + fun mockReset() { + this.activities = arrayOf() + this.currentActivities = arrayOf() + this.tags = mapOf() + } + +} \ No newline at end of file diff --git a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/Request.kt b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/Request.kt index 36b52cd2..9ffa8041 100644 --- a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/Request.kt +++ b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/Request.kt @@ -6,10 +6,12 @@ package com.example.util.simpletimetracker.wearrpc object Request { - val PING = "/stt//GET/ping" - val QUERY_ACTIVITIES = "/stt//GET/activities" - val QUERY_CURRENT_ACTIVITIES = "/stt//GET/activities/current" - val SET_CURRENT_ACTIVITIES = "/stt//PUT/activities/current" - val QUERY_TAGS_FOR_ACTIVITY = "/stt//GET/activities/:ID/tags" - val QUERY_SETTINGS = "/stt//GET/settings" + const val PATH = "/stt" + + const val PING = "$PATH//GET/ping" + const val QUERY_ACTIVITIES = "$PATH//GET/activities" + const val QUERY_CURRENT_ACTIVITIES = "$PATH//GET/activities/current" + const val SET_CURRENT_ACTIVITIES = "$PATH//PUT/activities/current" + const val QUERY_TAGS_FOR_ACTIVITY = "$PATH//GET/activities/:ID/tags" + const val QUERY_SETTINGS = "$PATH//GET/settings" } \ No newline at end of file diff --git a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediator.kt b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediator.kt index 9032a455..ad363ed3 100644 --- a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediator.kt +++ b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediator.kt @@ -6,7 +6,7 @@ package com.example.util.simpletimetracker.wearrpc class StartActivityMediator( - private val api: SimpleTimeTrackerAPI, + private val api: WearCommunicationAPI, private val onRequestStartActivity: suspend (activity: Activity) -> Unit, private val onRequestTagSelection: suspend (activity: Activity) -> Unit, ) { diff --git a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/SimpleTimeTrackerAPI.kt b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearCommunicationAPI.kt similarity index 78% rename from wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/SimpleTimeTrackerAPI.kt rename to wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearCommunicationAPI.kt index d8f4f632..a5bc8053 100644 --- a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/SimpleTimeTrackerAPI.kt +++ b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearCommunicationAPI.kt @@ -5,48 +5,48 @@ */ package com.example.util.simpletimetracker.wearrpc -interface SimpleTimeTrackerAPI { +interface WearCommunicationAPI { /** - * /stt//GET/ping + * [Request.PING] * * Echos the message it receives * * Primarily used to test request/response functionality */ - suspend fun ping(str: String): String { - return str + suspend fun ping(message: String): String { + return message } /** - * /stt//GET/activities + * [Request.QUERY_ACTIVITIES] * * Retrieves a list of all the time-tracking activities available for selection */ suspend fun queryActivities(): Array /** - * /stt//GET/activities/current + * [Request.QUERY_CURRENT_ACTIVITIES] * * Retrieves a list of the currently running activity/activities */ suspend fun queryCurrentActivities(): Array /** - * /stt//PUT/activities/current + * [Request.SET_CURRENT_ACTIVITIES] * * Replaces the currently running activity/activities with the given activities */ - suspend fun setCurrentActivities(activities: Array): Unit + suspend fun setCurrentActivities(activities: Array) /** - * /stt//GET/activities/:ID/tags + * [Request.QUERY_TAGS_FOR_ACTIVITY] * * Retrieves the tags available for association with the activity with the given ID */ suspend fun queryTagsForActivity(activityId: Long): Array /** - * /stt//GET/settings + * [Request.QUERY_SETTINGS] * * Retrieves the settings relevant to time tracking behavior */ diff --git a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCClient.kt b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCClient.kt index 3cbed8bb..73bc101a 100644 --- a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCClient.kt +++ b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCClient.kt @@ -9,7 +9,7 @@ import com.google.gson.Gson import com.google.gson.reflect.TypeToken -class WearRPCClient(private val messenger: Messenger) : SimpleTimeTrackerAPI { +class WearRPCClient(private val messenger: Messenger) : WearCommunicationAPI { override suspend fun ping(message: String): String { val response = messenger.send(Request.PING, message.toByteArray()) diff --git a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCServer.kt b/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCServer.kt deleted file mode 100644 index 58d58d81..00000000 --- a/wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCServer.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -package com.example.util.simpletimetracker.wearrpc - -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken - - -class WearRPCServer(private val api: SimpleTimeTrackerAPI) { - - suspend fun onRequest(path: String, request: ByteArray): ByteArray? { - if (path.startsWith("/stt")) { - when (path) { - Request.PING -> return onPing(request) - Request.QUERY_ACTIVITIES -> return onQueryActivities() - Request.QUERY_CURRENT_ACTIVITIES -> return onQueryCurrentActivities() - Request.SET_CURRENT_ACTIVITIES -> return onSetCurrentActivities(request) - Request.QUERY_TAGS_FOR_ACTIVITY -> return onQueryTagsForActivity(request) - Request.QUERY_SETTINGS -> return onQuerySettings() - else -> throw WearRPCException("$path is an invalid RPC call") - } - } else return null - } - - private suspend fun onQueryTagsForActivity(request: ByteArray): ByteArray? { - val activityId = Gson().fromJson(String(request), Long::class.java) - return Gson().toJson(api.queryTagsForActivity(activityId)).toByteArray() - } - - private suspend fun onSetCurrentActivities(request: ByteArray): ByteArray? { - val collectionType = object : TypeToken>() {}.type - val activities: Array = Gson().fromJson(String(request), collectionType) - api.setCurrentActivities(activities) - return ByteArray(0) - } - - private suspend fun onPing(request: ByteArray): ByteArray? { - return request - } - - private suspend fun onQueryActivities(): ByteArray? { - return Gson().toJson(api.queryActivities()).toByteArray() - } - - private suspend fun onQueryCurrentActivities(): ByteArray? { - return Gson().toJson(api.queryCurrentActivities()).toByteArray() - } - - private suspend fun onQuerySettings(): ByteArray? { - return Gson().toJson(api.querySettings()).toByteArray() - } -} \ No newline at end of file diff --git a/wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediatorTest.kt b/wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediatorTest.kt index d3307b1f..e80e93b6 100644 --- a/wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediatorTest.kt +++ b/wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediatorTest.kt @@ -10,39 +10,38 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test - -open class StartActivityMediatorTestBase { - - protected val api = MockSimpleTimeTrackerAPI() - protected val startCallback = MockMediatorCallback() - protected val requestTagCallback = MockMediatorCallback() - protected val mediator = StartActivityMediator( +// TODO use mockito +class StartActivityMediatorTest { + private val api = MockWearCommunicationAPI() + private val startCallback = MockMediatorCallback() + private val requestTagCallback = MockMediatorCallback() + private val mediator = StartActivityMediator( api = api, onRequestStartActivity = startCallback, onRequestTagSelection = requestTagCallback, ) - protected val sampleActivity = Activity(id = 1, name = "Sleep", icon = "🛏️", color = 0xFF123456) - protected val sampleGeneralTag = + private val sampleActivity = Activity(id = 1, name = "Sleep", icon = "🛏️", color = 0xFF123456) + private val sampleGeneralTag = Tag(id = 13, name = "Sleep", isGeneral = true, color = 0xFF654321) - protected val sampleNonGeneralTag = + private val sampleNonGeneralTag = Tag(id = 14, name = "Work", isGeneral = false, color = 0xFF654321) - protected val settings = Settings( + private val settings = Settings( allowMultitasking = false, showRecordTagSelection = false, recordTagSelectionCloseAfterOne = false, recordTagSelectionEvenForGeneralTags = false, ) + private val sampleSettings = settings.copy(showRecordTagSelection = true) + @Before fun setup() { api.mockReset() startCallback.reset() requestTagCallback.reset() } -} -class `Starts Activity When` : StartActivityMediatorTestBase() { @Test fun `tag selection disabled`() = runTest { api.mock_querySettings(settings.copy(showRecordTagSelection = false)) @@ -77,10 +76,6 @@ class `Starts Activity When` : StartActivityMediatorTestBase() { startCallback.assertCallsMade(1) requestTagCallback.assertCallsMade(0) } -} - -class `Requests tags when tag selection enabled and` : StartActivityMediatorTestBase() { - private val sampleSettings = settings.copy(showRecordTagSelection = true) @Test fun `activity has non-general tags`() = runTest { @@ -103,26 +98,26 @@ class `Requests tags when tag selection enabled and` : StartActivityMediatorTest requestTagCallback.assertCallsMade(1) startCallback.assertCallsMade(0) } -} - -class MockMediatorCallback : (Activity) -> Unit { - private var calledWith: Activity? = null - private var callCount: Int = 0 - override fun invoke(activity: Activity) { - calledWith = activity - callCount++ - } - fun assertCalledWith(activity: Activity) { - assertEquals(activity, calledWith) - } + class MockMediatorCallback : (Activity) -> Unit { + private var calledWith: Activity? = null + private var callCount: Int = 0 + override fun invoke(activity: Activity) { + calledWith = activity + callCount++ + } - fun assertCallsMade(count: Int) { - assertEquals(count, callCount) - } + fun assertCalledWith(activity: Activity) { + assertEquals(activity, calledWith) + } - fun reset() { - calledWith = null - callCount = 0 + fun assertCallsMade(count: Int) { + assertEquals(count, callCount) + } + + fun reset() { + calledWith = null + callCount = 0 + } } } \ No newline at end of file