diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6122ac18cb..a08152824f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -158,6 +158,7 @@ hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "dag hilt-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "dagger" } javapoet = "com.squareup:javapoet:1.13.0" json = "org.json:json:20230618" +jsonUnit-assertj = "net.javacrumbs.json-unit:json-unit-assertj:3.2.2" jsoup = "org.jsoup:jsoup:1.16.1" junit = "junit:junit:4.13.2" kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutines" } @@ -175,6 +176,7 @@ lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = " materialComponents = "com.google.android.material:material:1.9.0" mockk = "io.mockk:mockk:1.13.8" okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" } +okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp3" } onesky-gradlePlugin = "co.brainly:plugin:1.6.0" picasso = "com.squareup.picasso:picasso:2.8" picasso-transformations = "jp.wasabeef:picasso-transformations:2.4.0" diff --git a/library/api/build.gradle.kts b/library/api/build.gradle.kts index 3a04e12da5..3867cc1fc1 100644 --- a/library/api/build.gradle.kts +++ b/library/api/build.gradle.kts @@ -43,4 +43,7 @@ dependencies { kapt(libs.hilt.compiler) testImplementation(libs.json) + testImplementation(libs.jsonUnit.assertj) + testImplementation(libs.kotlin.coroutines.test) + testImplementation(libs.okhttp3.mockwebserver) } diff --git a/library/api/src/main/kotlin/org/cru/godtools/api/ApiModule.kt b/library/api/src/main/kotlin/org/cru/godtools/api/ApiModule.kt index 188e59a635..7ec8a0f9b1 100644 --- a/library/api/src/main/kotlin/org/cru/godtools/api/ApiModule.kt +++ b/library/api/src/main/kotlin/org/cru/godtools/api/ApiModule.kt @@ -148,6 +148,12 @@ object ApiModule { @Named(MOBILE_CONTENT_API_AUTHENTICATED) retrofit: Retrofit, ): UserCountersApi = retrofit.create() + @Provides + @Reusable + fun userFavoriteToolsApi( + @Named(MOBILE_CONTENT_API_AUTHENTICATED) retrofit: Retrofit, + ): UserFavoriteToolsApi = retrofit.create() + @Provides @Reusable fun viewsApi(@Named(MOBILE_CONTENT_API) retrofit: Retrofit): ViewsApi = retrofit.create() diff --git a/library/api/src/main/kotlin/org/cru/godtools/api/UserFavoriteToolsApi.kt b/library/api/src/main/kotlin/org/cru/godtools/api/UserFavoriteToolsApi.kt new file mode 100644 index 0000000000..ecb11b89ba --- /dev/null +++ b/library/api/src/main/kotlin/org/cru/godtools/api/UserFavoriteToolsApi.kt @@ -0,0 +1,33 @@ +package org.cru.godtools.api + +import org.ccci.gto.android.common.jsonapi.model.JsonApiObject +import org.ccci.gto.android.common.jsonapi.retrofit2.JsonApiParams +import org.ccci.gto.android.common.jsonapi.retrofit2.annotation.JsonApiFields +import org.cru.godtools.model.Tool +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.HTTP +import retrofit2.http.POST +import retrofit2.http.QueryMap + +interface UserFavoriteToolsApi { + companion object { + internal const val PATH_FAVORITE_TOOLS = "$PATH_USER/relationships/favorite-tools" + } + + @POST(PATH_FAVORITE_TOOLS) + suspend fun addFavoriteTools( + @QueryMap params: JsonApiParams = JsonApiParams(), + @Body + @JsonApiFields(Tool.JSONAPI_TYPE) + tools: List, + ): Response> + + @HTTP(method = "DELETE", path = PATH_FAVORITE_TOOLS, hasBody = true) + suspend fun removeFavoriteTools( + @QueryMap params: JsonApiParams = JsonApiParams(), + @Body + @JsonApiFields(Tool.JSONAPI_TYPE) + tools: List, + ): Response> +} diff --git a/library/api/src/test/kotlin/org/cru/godtools/api/UserFavoriteToolsApiTest.kt b/library/api/src/test/kotlin/org/cru/godtools/api/UserFavoriteToolsApiTest.kt new file mode 100644 index 0000000000..aba767550a --- /dev/null +++ b/library/api/src/test/kotlin/org/cru/godtools/api/UserFavoriteToolsApiTest.kt @@ -0,0 +1,46 @@ +package org.cru.godtools.api + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlinx.coroutines.test.runTest +import net.javacrumbs.jsonunit.assertj.assertThatJson +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.ccci.gto.android.common.jsonapi.retrofit2.JsonApiConverterFactory +import org.cru.godtools.api.UserFavoriteToolsApi.Companion.PATH_FAVORITE_TOOLS +import org.cru.godtools.model.Tool +import org.junit.Rule +import retrofit2.Retrofit + +private const val JSON_RESPONSE_FAVORITES = "{data:[{id:5,type:\"resource\"}]}" + +class UserFavoriteToolsApiTest { + @get:Rule + val server = MockWebServer() + + private var api = Retrofit.Builder() + .baseUrl(server.url("/")) + .addConverterFactory(JsonApiConverterFactory(Tool::class.java)) + .build() + .create(UserFavoriteToolsApi::class.java) + + @Test + fun `removeFavoriteTools()`() = runTest { + server.enqueue(MockResponse().setBody(JSON_RESPONSE_FAVORITES)) + + val resp = api.removeFavoriteTools(tools = listOf(Tool("en") { id = 1 })).body()!!.data.single() + assertNotNull(resp) { assertEquals(5, it.id) } + + val request = server.takeRequest() + assertEquals("DELETE", request.method) + assertEquals("/$PATH_FAVORITE_TOOLS", request.path) + assertThatJson(request.body.readUtf8()) { + isObject + node("data").isArray.hasSize(1) + node("data[0].id").isEqualTo(1) + node("data[0].attributes").isAbsent() + node("data[0].relationships").isAbsent() + } + } +}