diff --git a/.githooks/pre-push b/.githooks/pre-push index edf9a7c1..7754821c 100644 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -8,9 +8,22 @@ status=$? if [ "$status" = 0 ] ; then echo "ktlint check ran successfully." - exit 0 else echo "ktlint check failed. Formatting with ktlint..." ./gradlew ktlintFormat exit 1 +fi + +echo "Running unit tests..." + +./gradlew testDebugUnitTest --daemon + +status=$? # Update the status after running unit tests + +if [ "$status" = 0 ] ; then + echo "Unit tests ran successfully." + exit 0 +else + echo "Unit tests failed." + exit 1 fi \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 90588113..5b26f28d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ -## [@next](https://github.com/virtusize/integration_android/releases/tag/@next) (2024-8-\_\_) +### Next Release +- Update the Virtusize WebView URL - General Use ### 2.6.2 diff --git a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApi.kt b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApi.kt index 68240756..d2211193 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApi.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApi.kt @@ -49,6 +49,8 @@ data class ApiRequest( * @param userId the user ID that is unique from the client system */ object VirtusizeApi { + const val DEFAULT_AOYAMA_VERSION = "3.3.1" + private var environment = VirtusizeEnvironment.GLOBAL private lateinit var apiKey: String private lateinit var userId: String @@ -84,7 +86,7 @@ object VirtusizeApi { */ fun productCheck(product: VirtusizeProduct): ApiRequest { val urlBuilder = - Uri.parse(environment.servicesApiUrl() + VirtusizeEndpoint.ProductCheck.getPath()) + Uri.parse(environment.servicesApiUrl() + VirtusizeEndpoint.ProductCheck.path) .buildUpon() .appendQueryParameter("apiKey", apiKey) .appendQueryParameter("externalId", product.externalId) @@ -93,16 +95,26 @@ object VirtusizeApi { return ApiRequest(url, HttpMethod.GET) } + fun fetchLatestAoyamaVersion(): ApiRequest { + val url = + Uri.parse(environment.virtusizeUrl() + VirtusizeEndpoint.LatestAoyamaVersion.path) + .buildUpon() + .build() + .toString() + return ApiRequest(url, HttpMethod.GET) + } + /** * Gets the Virtusize web view URL for a VirtusizeProduct + * + * @param version the version of the Virtusize web view * @return the Virtusize web view URL as String */ - fun virtusizeWebViewURL(): String { + fun virtusizeWebViewURL(version: String = DEFAULT_AOYAMA_VERSION): String { val urlBuilder = Uri.parse( - environment.virtusizeUrl() + VirtusizeEndpoint.VirtusizeWebView.getPath(environment), - ) - .buildUpon() + environment.virtusizeUrl() + VirtusizeEndpoint.VirtusizeWebView(version = version).path, + ).buildUpon() return urlBuilder.build().toString() } @@ -115,7 +127,7 @@ object VirtusizeApi { val url = Uri.parse( environment.defaultApiUrl() + - VirtusizeEndpoint.ProductMetaDataHints.getPath(), + VirtusizeEndpoint.ProductMetaDataHints.path, ) .buildUpon() .build() @@ -228,7 +240,7 @@ object VirtusizeApi { */ fun sendOrder(order: VirtusizeOrder): ApiRequest { val url = - Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.Orders.getPath()) + Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.Orders.path) .buildUpon() .build() .toString() @@ -244,7 +256,7 @@ object VirtusizeApi { val url = Uri.parse( environment.defaultApiUrl() + - VirtusizeEndpoint.StoreViewApiKey.getPath() + + VirtusizeEndpoint.StoreViewApiKey.path + apiKey, ) .buildUpon() @@ -263,7 +275,7 @@ object VirtusizeApi { val url = Uri.parse( environment.defaultApiUrl() + - VirtusizeEndpoint.StoreProducts.getPath() + + VirtusizeEndpoint.StoreProducts.path + productId, ) .buildUpon() @@ -279,7 +291,7 @@ object VirtusizeApi { */ fun getProductTypes(): ApiRequest { val url = - Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.ProductType.getPath()) + Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.ProductType.path) .buildUpon() .build() .toString() @@ -292,7 +304,7 @@ object VirtusizeApi { */ fun getI18n(language: VirtusizeLanguage): ApiRequest { val url = - Uri.parse(I18N_URL + VirtusizeEndpoint.I18N.getPath() + language.value) + Uri.parse(I18N_URL + VirtusizeEndpoint.I18N.path + language.value) .buildUpon() .build() .toString() @@ -301,7 +313,7 @@ object VirtusizeApi { fun getSessions(): ApiRequest { val url = - Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.Sessions.getPath()) + Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.Sessions.path) .buildUpon() .build() .toString() @@ -314,7 +326,7 @@ object VirtusizeApi { */ fun deleteUser(): ApiRequest { val url = - Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.User.getPath()) + Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.User.path) .buildUpon() .build() .toString() @@ -328,7 +340,7 @@ object VirtusizeApi { fun getUserProducts(): ApiRequest { val url = Uri.parse( - environment.defaultApiUrl() + VirtusizeEndpoint.UserProducts.getPath(), + environment.defaultApiUrl() + VirtusizeEndpoint.UserProducts.path, ) .buildUpon() .build() @@ -343,7 +355,7 @@ object VirtusizeApi { fun getUserBodyProfile(): ApiRequest { val url = Uri.parse( - environment.defaultApiUrl() + VirtusizeEndpoint.UserBodyMeasurements.getPath(), + environment.defaultApiUrl() + VirtusizeEndpoint.UserBodyMeasurements.path, ) .buildUpon() .build() @@ -366,7 +378,7 @@ object VirtusizeApi { val bodyProfileRecommendedSizeParams = BodyProfileRecommendedSizeParams(productTypes, storeProduct, userBodyProfile) val url = - Uri.parse("${environment.sizeRecommendationApiBaseUrl()}${VirtusizeEndpoint.GetSize.getPath()}") + Uri.parse("${environment.sizeRecommendationApiBaseUrl()}${VirtusizeEndpoint.GetSize.path}") .buildUpon() .build() .toString() diff --git a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiResponseFormat.kt b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiResponseFormat.kt new file mode 100644 index 00000000..326a5064 --- /dev/null +++ b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiResponseFormat.kt @@ -0,0 +1,6 @@ +package com.virtusize.android.network + +enum class VirtusizeApiResponseFormat { + JSON, + STRING, +} diff --git a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiTask.kt b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiTask.kt index db4ca8a9..bfa05c25 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiTask.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiTask.kt @@ -17,6 +17,7 @@ import java.net.URL import java.util.Scanner import java.util.concurrent.TimeUnit import javax.net.ssl.HttpsURLConnection +import kotlin.jvm.Throws /** * The asynchronous task to make an API request in the background thread @@ -47,6 +48,9 @@ class VirtusizeApiTask( // The Json parser interface for converting the JSON response to a given type of Java object private var jsonParser: VirtusizeJsonParser? = null + // The response format of the API request + private var responseFormat: VirtusizeApiResponseFormat = VirtusizeApiResponseFormat.JSON + /** * Sets up the JSON parser for converting the JSON response to a given type of Java object * @return the [VirtusizeApiTask] with the JSON parser set up @@ -56,6 +60,15 @@ class VirtusizeApiTask( return this } + /** + * Sets up the response format of the API request + * @return the [VirtusizeApiTask] with the response format set up + */ + fun setResponseFormat(responseFormat: VirtusizeApiResponseFormat): VirtusizeApiTask { + this.responseFormat = responseFormat + return this + } + /** * Executes the API request and returns the response * @param apiRequest [ApiRequest] @@ -93,7 +106,7 @@ class VirtusizeApiTask( setRequestProperty(HEADER_CONTENT_TYPE, "application/json") // Set up the request header for the sessions API - if (apiRequest.url.contains(VirtusizeEndpoint.Sessions.getPath())) { + if (apiRequest.url.contains(VirtusizeEndpoint.Sessions.path)) { sharedPreferencesHelper.getAuthToken()?.let { setRequestProperty(HEADER_AUTH, it) setRequestProperty(HEADER_COOKIE, "") @@ -120,8 +133,8 @@ class VirtusizeApiTask( val inputStreamString = readInputStreamAsString(inputStream) val response = parseInputStreamStringToObject( - apiRequest.url, - inputStreamString, + apiRequestUrl = apiRequest.url, + inputStreamString = inputStreamString, ) VirtusizeApiResponse.Success(response) as VirtusizeApiResponse } catch (e: JSONException) { @@ -222,35 +235,37 @@ class VirtusizeApiTask( /** * Parses the string of the input stream to a data object + * * @param apiRequestUrl the API request URL * @param inputStreamString the string of the input stream * @return either the object that contains the content of the string of the input stream or null */ - private fun parseInputStreamStringToObject( - apiRequestUrl: String? = null, + internal fun parseInputStreamStringToObject( + apiRequestUrl: String = "", inputStreamString: String? = null, - ): Any? { - var result: Any? = null + ): Any? = if (inputStreamString != null) { try { - result = parseStringToObject(apiRequestUrl, inputStreamString) + parseStringToObject(apiRequestUrl = apiRequestUrl, streamString = inputStreamString) } catch (e: JSONException) { messageHandler?.onError( VirtusizeErrorType.JsonParsingError.virtusizeError( extraMessage = e.localizedMessage, ), ) + null } + } else { + null } - return result - } /** * Parses the string of the error stream to a data object + * * @param errorStreamString the string of the error stream * @return either an object that contains the content of the string of the input stream, the string of the error stream, or null */ - private fun parseErrorStreamStringToObject(errorStreamString: String? = null): Any? { + internal fun parseErrorStreamStringToObject(errorStreamString: String? = null): Any? { var result: Any? = null if (errorStreamString != null) { result = @@ -265,44 +280,48 @@ class VirtusizeApiTask( /** * Parses the string of an input stream to an object + * * @param apiRequestUrl the API request URL * @param streamString the string of the input stream * @return either the data object that is converted from streamString or null */ - private fun parseStringToObject( - apiRequestUrl: String? = null, + @Throws(JSONException::class) + internal fun parseStringToObject( + apiRequestUrl: String = "", streamString: String, - ): Any? { - var result: Any? = null - jsonParser?.let { jsonParser -> - result = - if (apiRequestUrl != null && responseIsJsonArray(apiRequestUrl)) { - val jsonArray = JSONArray(streamString) - (0 until jsonArray.length()) - .map { idx -> jsonArray.getJSONObject(idx) } - .mapNotNull { jsonParser.parse(it) } - } else { - val jsonObject = JSONObject(streamString) - jsonParser.parse(jsonObject) - } + ): Any? = + when { + responseFormat == VirtusizeApiResponseFormat.STRING -> streamString.trimIndent() + + responseIsJsonArray(apiRequestUrl) -> { + val jsonArray = JSONArray(streamString) + (0 until jsonArray.length()) + .map { idx -> jsonArray.getJSONObject(idx) } + .mapNotNull { jsonParser?.parse(it) } + } + + else -> { + val jsonObject = JSONObject(streamString) + jsonParser?.parse(jsonObject) + } } - return result - } /** * Check if the response of the API request is a JSON array + * * @param apiRequestUrl The input stream of bytes * @return the boolean value to tell whether the response of the apiRequestUrl is a JSON array. */ private fun responseIsJsonArray(apiRequestUrl: String): Boolean { - return apiRequestUrl.contains(VirtusizeEndpoint.ProductType.getPath()) || + return apiRequestUrl.contains(VirtusizeEndpoint.ProductType.path) || apiRequestUrl.contains( - VirtusizeEndpoint.UserProducts.getPath(), - ) || apiRequestUrl.contains(VirtusizeEndpoint.GetSize.getPath()) + VirtusizeEndpoint.UserProducts.path, + ) || apiRequestUrl.contains(VirtusizeEndpoint.GetSize.path) } /** * Returns the contents of an [InputStream] as a String. + * * @param inputStream The input stream of bytes * @return the string from scanning through the inputStream */ @@ -313,6 +332,7 @@ class VirtusizeApiTask( /** * Gets the API error message based on the path part of the request url + * * @param requestPath the path part of the request URL * @param response the response from an API request * @return the message with the info of the request's path and response diff --git a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeEndpoint.kt b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeEndpoint.kt index 99d4a12c..3bf0b91f 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeEndpoint.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeEndpoint.kt @@ -1,78 +1,64 @@ package com.virtusize.android.network -import com.virtusize.android.data.local.VirtusizeEnvironment - /** - * This enum represents all available Virtusize endpoints + * This sealed interface represents all available Virtusize endpoints */ -enum class VirtusizeEndpoint { - ProductCheck, - GetSize, - VirtusizeWebView, - ProductMetaDataHints, - Orders, - StoreViewApiKey, - StoreProducts, - ProductType, - Sessions, - User, - UserProducts, - UserBodyMeasurements, - I18N, -} +sealed interface VirtusizeEndpoint { + val path: String -/** - * This method returns a URL corresponding to the Virtusize endpoint that it is called upon - * @return the Virtusize Endpoint URL - */ -fun VirtusizeEndpoint.getPath(env: VirtusizeEnvironment? = null): String { - return when (this) { - VirtusizeEndpoint.ProductCheck -> { - "/product/check" - } - /*VirtusizeEndpoint.GetSize -> { - "/ds-functions/size-rec/get-size" - }*/ - VirtusizeEndpoint.GetSize -> { - "/item" - } - VirtusizeEndpoint.VirtusizeWebView -> { - val stgPath = - when (env) { - VirtusizeEnvironment.TESTING, VirtusizeEnvironment.STAGING -> "staging" - else -> "latest" - } - "/a/aoyama/$stgPath/sdk-webview.html" - } - VirtusizeEndpoint.ProductMetaDataHints -> { - "/rest-api/v1/product-meta-data-hints" - } - VirtusizeEndpoint.Orders -> { - "/a/api/v3/orders" - } - VirtusizeEndpoint.StoreViewApiKey -> { - "/a/api/v3/stores/api-key/" - } - VirtusizeEndpoint.StoreProducts -> { - "/a/api/v3/store-products/" - } - VirtusizeEndpoint.ProductType -> { - "/a/api/v3/product-types" - } - VirtusizeEndpoint.Sessions -> { - "/a/api/v3/sessions" - } - VirtusizeEndpoint.User -> { - "/a/api/v3/users/me" - } - VirtusizeEndpoint.UserProducts -> { - "/a/api/v3/user-products" - } - VirtusizeEndpoint.UserBodyMeasurements -> { - "/a/api/v3/user-body-measurements" - } - VirtusizeEndpoint.I18N -> { - "/bundle-payloads/aoyama/" - } + data object ProductCheck : VirtusizeEndpoint { + override val path: String = "/product/check" + } + + data object GetSize : VirtusizeEndpoint { + override val path: String = "/item" + } + + data object LatestAoyamaVersion : VirtusizeEndpoint { + override val path: String = "/a/aoyama/latest.txt" + } + + data class VirtusizeWebView(val version: String) : VirtusizeEndpoint { + override val path: String = "/a/aoyama/$version/sdk-webview.html" + } + + data object ProductMetaDataHints : VirtusizeEndpoint { + override val path: String = "/rest-api/v1/product-meta-data-hints" + } + + data object Orders : VirtusizeEndpoint { + override val path: String = "/a/api/v3/orders" + } + + data object StoreViewApiKey : VirtusizeEndpoint { + override val path: String = "/a/api/v3/stores/api-key/" + } + + data object StoreProducts : VirtusizeEndpoint { + override val path: String = "/a/api/v3/store-products/" + } + + data object ProductType : VirtusizeEndpoint { + override val path: String = "/a/api/v3/product-types" + } + + data object Sessions : VirtusizeEndpoint { + override val path: String = "/a/api/v3/sessions" + } + + data object User : VirtusizeEndpoint { + override val path: String = "/a/api/v3/users/me" + } + + data object UserProducts : VirtusizeEndpoint { + override val path: String = "/a/api/v3/user-products" + } + + data object UserBodyMeasurements : VirtusizeEndpoint { + override val path: String = "/a/api/v3/user-body-measurements" + } + + data object I18N : VirtusizeEndpoint { + override val path: String = "/bundle-payloads/aoyama/" } } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParamsTests.kt b/virtusize-core/src/test/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParamsTests.kt index 8f2eece9..503bde78 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParamsTests.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParamsTests.kt @@ -8,7 +8,7 @@ import org.json.JSONObject import org.junit.Before import org.junit.Test -class BodyProfileRecommendedSizeParamsTests { +internal class BodyProfileRecommendedSizeParamsTests { private var productTypes = mutableListOf() @Before diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderItemTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderItemTest.kt index 53db1c8e..975fd40a 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderItemTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderItemTest.kt @@ -3,7 +3,7 @@ package com.virtusize.android.data.local import com.google.common.truth.Truth.assertThat import org.junit.Test -class VirtusizeOrderItemTest { +internal class VirtusizeOrderItemTest { @Test fun paramsToMap_shouldReturnExpectedMap() { val actualMap = diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderTest.kt index 53cb923c..02cc8138 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderTest.kt @@ -3,7 +3,7 @@ package com.virtusize.android.data.local import com.google.common.truth.Truth.assertThat import org.junit.Test -class VirtusizeOrderTest { +internal class VirtusizeOrderTest { @Test fun paramsToMap_withNoOrderItem_shouldReturnExpectedMap() { val actualOrder = VirtusizeOrder("TEST_ORDER") diff --git a/virtusize-core/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt b/virtusize-core/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt index 112c8ccb..17199cf4 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt @@ -12,7 +12,7 @@ import org.json.JSONArray import org.json.JSONObject internal object ProductFixtures { - internal val PRODUCT_TYPE_ID_ONE_JSON_OBJECT_STRING = + val PRODUCT_TYPE_ID_ONE_JSON_OBJECT_STRING = """ { "id": 1, diff --git a/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTaskTest.kt b/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTaskTest.kt index 24a1a59a..664832af 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTaskTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTaskTest.kt @@ -27,7 +27,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.Q]) -class VirtusizeApiTaskTest { +internal class VirtusizeApiTaskTest { private val context: Context = ApplicationProvider.getApplicationContext() private lateinit var virtusizeApiTask: VirtusizeApiTask @@ -35,186 +35,166 @@ class VirtusizeApiTaskTest { fun setup() { virtusizeApiTask = VirtusizeApiTask( - null, - SharedPreferencesHelper.getInstance(context), - null, + urlConnection = null, + sharedPreferencesHelper = SharedPreferencesHelper.getInstance(context), + messageHandler = null, ) } @Test - fun testParseInputStreamStringToUserBodyProfile_return_expectedUserBodyProfile() { - virtusizeApiTask - .setJsonParser(UserBodyProfileJsonParser()) + fun `test user-body-measurements endpoint with UserBodyProfileJsonParser should return expected UserBodyProfile`() { + virtusizeApiTask.setJsonParser(UserBodyProfileJsonParser()) - val parseInputStreamStringToObjectMethod = - VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseInputStreamStringToObject" } - parseInputStreamStringToObjectMethod?.let { method -> - method.isAccessible = true - val returnValue = - method.invoke( - virtusizeApiTask, - "", - TestFixtures.USER_BODY_JSONObject.toString(), - ) - val actualUserBodyProfile = returnValue as? UserBodyProfile - assertThat(actualUserBodyProfile?.age).isEqualTo(32) - assertThat(actualUserBodyProfile?.gender).isEqualTo("female") - assertThat(actualUserBodyProfile?.height).isEqualTo(1630) - assertThat(actualUserBodyProfile?.weight).isEqualTo("50.00") - assertThat(actualUserBodyProfile?.bodyData).isEqualTo( - mutableSetOf( - Measurement("hip", 830), - Measurement("hip", 830), - Measurement("bust", 755), - Measurement("neck", 300), - Measurement("rise", 215), - Measurement("bicep", 220), - Measurement("thigh", 480), - Measurement("waist", 630), - Measurement("inseam", 700), - Measurement("sleeve", 720), - Measurement("shoulder", 370), - Measurement("hipWidth", 300), - Measurement("bustWidth", 245), - Measurement("hipHeight", 750), - Measurement("headHeight", 215), - Measurement("kneeHeight", 395), - Measurement("waistWidth", 225), - Measurement("waistHeight", 920), - Measurement("armpitHeight", 1130), - Measurement("sleeveLength", 520), - Measurement("shoulderWidth", 340), - Measurement("shoulderHeight", 1240), - ), + val returnValue = + virtusizeApiTask.parseInputStreamStringToObject( + inputStreamString = TestFixtures.USER_BODY_JSONObject.toString(), ) - } + + val actualUserBodyProfile = returnValue as? UserBodyProfile + assertThat(actualUserBodyProfile?.age).isEqualTo(32) + assertThat(actualUserBodyProfile?.gender).isEqualTo("female") + assertThat(actualUserBodyProfile?.height).isEqualTo(1630) + assertThat(actualUserBodyProfile?.weight).isEqualTo("50.00") + assertThat(actualUserBodyProfile?.bodyData).isEqualTo( + mutableSetOf( + Measurement("hip", 830), + Measurement("hip", 830), + Measurement("bust", 755), + Measurement("neck", 300), + Measurement("rise", 215), + Measurement("bicep", 220), + Measurement("thigh", 480), + Measurement("waist", 630), + Measurement("inseam", 700), + Measurement("sleeve", 720), + Measurement("shoulder", 370), + Measurement("hipWidth", 300), + Measurement("bustWidth", 245), + Measurement("hipHeight", 750), + Measurement("headHeight", 215), + Measurement("kneeHeight", 395), + Measurement("waistWidth", 225), + Measurement("waistHeight", 920), + Measurement("armpitHeight", 1130), + Measurement("sleeveLength", 520), + Measurement("shoulderWidth", 340), + Measurement("shoulderHeight", 1240), + ), + ) } @Test fun testParseInputStreamStringToUserBodyProfile_return_null() { - virtusizeApiTask - .setJsonParser(UserBodyProfileJsonParser()) + virtusizeApiTask.setJsonParser(UserBodyProfileJsonParser()) - val parseInputStreamStringToObjectMethod = - VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseInputStreamStringToObject" } - parseInputStreamStringToObjectMethod?.let { method -> - method.isAccessible = true - val returnValue = - method.invoke( - virtusizeApiTask, - "", + val returnValue = + virtusizeApiTask.parseInputStreamStringToObject( + inputStreamString = "{\"gender\":\"\",\"age\":null,\"height\":null,\"weight\":null,\"braSize\":null," + "\"concernAreas\":null,\"bodyData\":null}", - ) - assertThat(returnValue).isNull() - } + ) + + assertThat(returnValue).isNull() } @Test - fun testParseErrorStreamStringToProductCheck_return_expectedProductCheck() { - virtusizeApiTask - .setJsonParser(ProductCheckJsonParser()) + fun `test user-body-measurements endpoint with UserBodyProfileJsonParser returning null`() { + virtusizeApiTask.setJsonParser(UserBodyProfileJsonParser()) - val parseErrorStreamStringToObjectMethod = - VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseErrorStreamStringToObject" } - parseErrorStreamStringToObjectMethod?.let { method -> - method.isAccessible = true - val pdcJsonString = - """ - { - "data": { - "productDataId": null, - "userData": {}, - "storeId": 2, - "storeName": "virtusize", - "validProduct": false, - "fetchMetaData": false - }, - "name": "backend-checked-product", - "productId": "123" - } - """.trimIndent() - val returnValue = - method.invoke( - virtusizeApiTask, - pdcJsonString, - ) - val expectedProductCheck = - ProductCheck( - Data( - validProduct = false, - fetchMetaData = false, - shouldSeePhTooltip = false, - productDataId = 0, - productTypeName = "", - storeName = "virtusize", - storeId = 2, - productTypeId = 0, - ), - productId = "123", - name = "backend-checked-product", - JSONObject(pdcJsonString).toString(), - ) - assertThat(returnValue).isEqualTo(expectedProductCheck) - } + val returnValue = + virtusizeApiTask.parseInputStreamStringToObject( + apiRequestUrl = "https://staging.virtusize.jp/a/api/v3/user-body-measurements", + inputStreamString = + """ + { + "gender":"", + "age":null, + "height":null, + "weight":null, + "braSize":null, + "concernAreas":null, + "bodyData":null + } + """.trimIndent(), + ) + + assertThat(returnValue).isNull() } @Test - fun testParseErrorStreamStringToUserProduct_return_null() { - virtusizeApiTask - .setJsonParser(UserProductJsonParser()) + fun `test product-check endpoint with ProductCheckJsonParser should return expected ProductCheck`() { + virtusizeApiTask.setJsonParser(ProductCheckJsonParser()) + + val pdcJsonString = + """ + { + "data": { + "productDataId": null, + "userData": {}, + "storeId": 2, + "storeName": "virtusize", + "validProduct": false, + "fetchMetaData": false + }, + "name": "backend-checked-product", + "productId": "123" + } + """.trimIndent() + val returnValue = + virtusizeApiTask.parseErrorStreamStringToObject(errorStreamString = pdcJsonString) + + val expectedProductCheck = + ProductCheck( + Data( + validProduct = false, + fetchMetaData = false, + shouldSeePhTooltip = false, + productDataId = 0, + productTypeName = "", + storeName = "virtusize", + storeId = 2, + productTypeId = 0, + ), + productId = "123", + name = "backend-checked-product", + JSONObject(pdcJsonString).toString(), + ) - val parseErrorStreamStringToObjectMethod = - VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseErrorStreamStringToObject" } - parseErrorStreamStringToObjectMethod?.let { method -> - method.isAccessible = true - val returnValue = - method.invoke( - virtusizeApiTask, - "{\"detail\":\"No wardrobe found\"}", - ) - assertThat(returnValue).isEqualTo("{\"detail\":\"No wardrobe found\"}") - } + assertThat(returnValue).isEqualTo(expectedProductCheck) } @Test - fun testParseStringToObjectByProductTypeJsonParser_return_ListOfProductType() { - virtusizeApiTask - .setJsonParser(ProductTypeJsonParser()) + fun `test user-product endpoint error with UserProductJsonParser`() { + virtusizeApiTask.setJsonParser(UserProductJsonParser()) + + val returnValue = + virtusizeApiTask.parseErrorStreamStringToObject( + errorStreamString = "{\"detail\":\"No wardrobe found\"}", + ) - val parseStringToObjectMethod = - VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseStringToObject" } - parseStringToObjectMethod?.let { method -> - method.isAccessible = true - val returnValue = - method.invoke( - virtusizeApiTask, - "https://staging.virtusize.jp/a/api/v3/product-types", - ProductFixtures.PRODUCT_TYPE_JSON_ARRAY.toString(), - ) - val productTypes = returnValue as List - assertThat(productTypes.size).isEqualTo(4) - } + assertThat(returnValue).isEqualTo("{\"detail\":\"No wardrobe found\"}") } @Test - fun testParseStringToObjectByUserSessionInfoJsonParser_return_expectedUserSessionInfo() { - virtusizeApiTask - .setJsonParser(UserSessionInfoJsonParser()) + fun `test product-types endpoint with ProductTypeJsonParser should return expected list Of ProductType`() { + virtusizeApiTask.setJsonParser(ProductTypeJsonParser()) - val parseStringToObjectMethod = - VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseStringToObject" } + val returnValue = + virtusizeApiTask.parseInputStreamStringToObject( + apiRequestUrl = "https://staging.virtusize.jp/a/api/v3/product-types", + inputStreamString = ProductFixtures.PRODUCT_TYPE_JSON_ARRAY.toString(), + ) + val productTypes = returnValue as List - parseStringToObjectMethod?.let { method -> - method.isAccessible = true - val streamString = - """ + assertThat(productTypes.size).isEqualTo(4) + } + + @Test + fun `test sessions endpoint with UserSessionInfoJsonParser should return expected UserSessionInfo`() { + virtusizeApiTask.setJsonParser(UserSessionInfoJsonParser()) + + val streamString = + """ { "id":"test_access_token", "expiresAt":1619062232, @@ -229,55 +209,37 @@ class VirtusizeApiTaskTest { }, "x-vs-auth":"" } - """.trimIndent().replace("\\s+|[\\n]+".toRegex(), "") + """ + .trimIndent() + .replace(regex = "\\s+|[\\n]+".toRegex(), replacement = "") + + val returnValue = + virtusizeApiTask.parseInputStreamStringToObject( + apiRequestUrl = "https://staging.virtusize.jp/a/api/v3/sessions", + inputStreamString = streamString, + ) - val returnValue = - method.invoke( - virtusizeApiTask, - "https://staging.virtusize.jp/a/api/v3/sessions", - streamString, - ) + val expectedUserSessionInfo = + UserSessionInfo( + accessToken = "test_access_token", + bid = "test_bid", + authToken = "", + userSessionResponse = streamString, + ) - val expectedUserSessionInfo = - UserSessionInfo( - accessToken = "test_access_token", - bid = "test_bid", - authToken = "", - userSessionResponse = streamString, - ) - assertThat(returnValue).isEqualTo(expectedUserSessionInfo) - } + assertThat(returnValue).isEqualTo(expectedUserSessionInfo) } @Test - fun testParseStringToObjectByUserBodyProfileJsonParser_return_null() { - virtusizeApiTask - .setJsonParser(UserBodyProfileJsonParser()) - - val parseStringToObjectMethod = - VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseStringToObject" } + fun `test latest version URL with LatestAoyamaVersionJsonParser`() { + virtusizeApiTask.setResponseFormat(VirtusizeApiResponseFormat.STRING) - parseStringToObjectMethod?.let { method -> - method.isAccessible = true - val returnValue = - method.invoke( - virtusizeApiTask, - "https://staging.virtusize.jp/a/api/v3/user-body-measurements", - """ - { - "gender":"", - "age":null, - "height":null, - "weight":null, - "braSize":null, - "concernAreas":null, - "bodyData":null - } - """.trimIndent(), - ) + val returnValue = + virtusizeApiTask.parseStringToObject( + apiRequestUrl = "https://static.api.virtusize.com/a/aoyama/latest.txt", + streamString = "${VirtusizeApi.DEFAULT_AOYAMA_VERSION}\n", + ) - assertThat(returnValue).isNull() - } + assertThat(returnValue).isEqualTo(VirtusizeApi.DEFAULT_AOYAMA_VERSION) } } diff --git a/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTest.kt b/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTest.kt index bca2c7f1..669803c1 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTest.kt @@ -16,6 +16,7 @@ import com.virtusize.android.data.local.getEventName import com.virtusize.android.data.parsers.JsonUtils import com.virtusize.android.fixtures.ProductFixtures import com.virtusize.android.fixtures.TestFixtures +import com.virtusize.android.network.VirtusizeApi.DEFAULT_AOYAMA_VERSION import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -24,7 +25,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.Q]) -class VirtusizeApiTest { +internal class VirtusizeApiTest { private val context: Context = ApplicationProvider.getApplicationContext() private val defaultDisplay = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay @@ -55,11 +56,20 @@ class VirtusizeApiTest { assertThat(actualApiRequest).isEqualTo(expectedApiRequest) } + @Test + fun fetchLatestAoyamaVersion_stagingEnv_shouldReturnExpectedUrl() { + val actualApiRequest = VirtusizeApi.fetchLatestAoyamaVersion() + + val expectedUrl = "https://static.api.virtusize.com/a/aoyama/latest.txt" + + assertThat(actualApiRequest.url).isEqualTo(expectedUrl) + } + @Test fun virtusizeWebView_stagingEnv_shouldReturnExpectedUrl() { - val actualUrl = VirtusizeApi.virtusizeWebViewURL() + val actualUrl = VirtusizeApi.virtusizeWebViewURL(DEFAULT_AOYAMA_VERSION) - val expectedUrl = "https://static.api.virtusize.com/a/aoyama/staging/sdk-webview.html" + val expectedUrl = "https://static.api.virtusize.com/a/aoyama/$DEFAULT_AOYAMA_VERSION/sdk-webview.html" assertThat(actualUrl).isEqualTo(expectedUrl) } @@ -71,8 +81,8 @@ class VirtusizeApiTest { TestFixtures.API_KEY, TestFixtures.USER_ID, ) - val actualUrl = VirtusizeApi.virtusizeWebViewURL() - val expectedUrl = "https://static.api.virtusize.jp/a/aoyama/latest/sdk-webview.html" + val actualUrl = VirtusizeApi.virtusizeWebViewURL(DEFAULT_AOYAMA_VERSION) + val expectedUrl = "https://static.api.virtusize.jp/a/aoyama/$DEFAULT_AOYAMA_VERSION/sdk-webview.html" assertThat(actualUrl).isEqualTo(expectedUrl) } diff --git a/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt b/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt index 4329e699..f7de5279 100644 --- a/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt +++ b/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt @@ -98,6 +98,16 @@ internal class VirtusizeAPIService( this.coroutineDispatcher = dispatcher } + internal suspend fun fetchLatestAoyamaVersion(): VirtusizeApiResponse = + withContext(Dispatchers.IO) { + val apiRequest = VirtusizeApi.fetchLatestAoyamaVersion() + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ).setResponseFormat(VirtusizeApiResponseFormat.STRING).execute(apiRequest) + } + /** * Executes the API task to make a network request for Product Check * @param product [VirtusizeProduct] diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeWebViewFragment.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeWebViewFragment.kt index 2898e616..9bf66f23 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeWebViewFragment.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeWebViewFragment.kt @@ -18,7 +18,9 @@ import android.webkit.WebChromeClient import android.webkit.WebView import android.webkit.WebViewClient import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope import com.virtusize.android.R import com.virtusize.android.SharedPreferencesHelper import com.virtusize.android.auth.VirtusizeAuth @@ -27,17 +29,25 @@ import com.virtusize.android.data.local.VirtusizeMessageHandler import com.virtusize.android.data.local.VirtusizeProduct import com.virtusize.android.data.parsers.VirtusizeEventJsonParser import com.virtusize.android.databinding.FragmentVirtusizeWebviewBinding +import com.virtusize.android.network.VirtusizeAPIService +import com.virtusize.android.network.VirtusizeApi import com.virtusize.android.util.Constants +import kotlinx.coroutines.launch import org.json.JSONObject class VirtusizeWebViewFragment : DialogFragment() { private var virtusizeWebAppUrl = - "https://static.api.virtusize.jp/a/aoyama/latest/sdk-webview.html" + "https://static.api.virtusize.jp/a/aoyama/${VirtusizeApi.DEFAULT_AOYAMA_VERSION}/sdk-webview.html" private var vsParamsFromSDKScript = "" private var backButtonClickEventFromSDKScript = "javascript:vsEventFromSDK({ name: 'sdk-back-button-tapped'})" private var virtusizeMessageHandler: VirtusizeMessageHandler? = null + + private val apiService: VirtusizeAPIService by lazy { + VirtusizeAPIService.getInstance(requireContext(), virtusizeMessageHandler) + } + private lateinit var clientProduct: VirtusizeProduct private lateinit var sharedPreferencesHelper: SharedPreferencesHelper @@ -80,6 +90,7 @@ class VirtusizeWebViewFragment : DialogFragment() { ) { super.onViewCreated(view, savedInstanceState) dialog?.window?.attributes?.windowAnimations = R.style.VirtusizeDialogFragmentAnimation + binding.webView.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.virtusizeWhite)) // Enable JavaScript in the web view binding.webView.settings.javaScriptEnabled = true binding.webView.settings.domStorageEnabled = true @@ -189,13 +200,6 @@ class VirtusizeWebViewFragment : DialogFragment() { true } - // Get the Virtusize URL passed in fragment arguments - arguments?.getString(Constants.URL_KEY)?.let { - virtusizeWebAppUrl = it - } ?: run { - dismiss() - } - // Get the Virtusize params script passed in fragment arguments. // If the script is not passed, we dismiss this dialog fragment. arguments?.getString(Constants.VIRTUSIZE_PARAMS_SCRIPT_KEY)?.let { @@ -210,7 +214,26 @@ class VirtusizeWebViewFragment : DialogFragment() { dismiss() } - binding.webView.loadUrl(virtusizeWebAppUrl) + viewLifecycleOwner.lifecycleScope.launch { + val fetchedVersion = + apiService.fetchLatestAoyamaVersion().successData ?: run { + dismiss() + return@launch + } + + // Get the Virtusize URL passed in fragment arguments + arguments?.getString(Constants.URL_KEY)?.let { url -> + virtusizeWebAppUrl = + url.replace( + oldValue = VirtusizeApi.DEFAULT_AOYAMA_VERSION, + newValue = fetchedVersion, + ) + } ?: run { + dismiss() + } + + binding.webView.loadUrl(virtusizeWebAppUrl) + } } override fun onStop() { diff --git a/virtusize/src/main/java/com/virtusize/android/util/VirtusizeUtils.kt b/virtusize/src/main/java/com/virtusize/android/util/VirtusizeUtils.kt index ee4e11fe..f8269d29 100644 --- a/virtusize/src/main/java/com/virtusize/android/util/VirtusizeUtils.kt +++ b/virtusize/src/main/java/com/virtusize/android/util/VirtusizeUtils.kt @@ -35,7 +35,7 @@ internal object VirtusizeUtils { private fun configureLocale( context: Context, locale: Locale?, - ): ContextWrapper? { + ): ContextWrapper { var updatedContext = context val resources = context.resources val configuration = resources.configuration @@ -60,7 +60,7 @@ internal object VirtusizeUtils { fun getConfiguredContext( context: Context, language: VirtusizeLanguage?, - ): ContextWrapper? { + ): ContextWrapper { return when (language) { VirtusizeLanguage.EN -> configureLocale(context, Locale.ENGLISH) VirtusizeLanguage.JP -> configureLocale(context, Locale.JAPAN) diff --git a/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceTest.kt b/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceTest.kt index a4bdd247..78ae41bc 100644 --- a/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceTest.kt @@ -23,6 +23,7 @@ import com.virtusize.android.fixtures.TestFixtures import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -641,6 +642,22 @@ class VirtusizeAPIServiceTest { assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) } + @Test + fun `test fetchLatestAoyamaVersion should return expected value`() = + runTest { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + URL("https://static.api.virtusize.com/a/aoyama/latest.txt"), + MockedResponse( + 200, + VirtusizeApi.DEFAULT_AOYAMA_VERSION.byteInputStream(), + ), + ), + ) + val actualApiResponse = virtusizeAPIService.fetchLatestAoyamaVersion() + assertThat(actualApiResponse.successData).isEqualTo(VirtusizeApi.DEFAULT_AOYAMA_VERSION) + } + companion object { private const val INTERNAL_SERVER_ERROR_RESPONSE = "\n" +