From 9d95491e5ee19f987f343c653a8630a2e4de5fab Mon Sep 17 00:00:00 2001 From: tonisives Date: Wed, 31 Jan 2024 08:24:42 +0700 Subject: [PATCH 1/4] add failing tests --- .gitignore | 3 +- .../network/AccessTokenRequestsTest.kt | 43 ++- .../network/ClearanceRequestsTest.kt | 330 +++++++++++------- .../hmkitfleet/network/UtilityRequestsTest.kt | 115 +++--- 4 files changed, 290 insertions(+), 201 deletions(-) diff --git a/.gitignore b/.gitignore index 3c9e45c..23f003b 100644 --- a/.gitignore +++ b/.gitignore @@ -91,4 +91,5 @@ vehicleAccess.yaml vehicleAccess.json private-key.json credentialsPrivateKey.json -credentialsOAuth.json \ No newline at end of file +credentialsOAuth.json +hmkit-fleet/bin diff --git a/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/AccessTokenRequestsTest.kt b/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/AccessTokenRequestsTest.kt index 8984112..474f011 100644 --- a/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/AccessTokenRequestsTest.kt +++ b/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/AccessTokenRequestsTest.kt @@ -67,20 +67,22 @@ internal class AccessTokenRequestsTest : BaseTest() { @Test fun downloadsAccessTokenAndWritesToCacheIfDoesNotExistOrExpired() { - val responseAccessToken = notExpiredAccessToken() - // return null from cache at first, then on next call a new one - // if auth token is expired, the cache does not return it also - every { cache getProperty "accessToken" } returnsMany listOf(null, responseAccessToken) + for (i in 0..1) { + val responseAccessToken = notExpiredAccessToken() + // return null from cache at first, then on next call a new one + // if auth token is expired, the cache does not return it also + every { cache getProperty "accessToken" } returnsMany listOf(null, responseAccessToken) - val response = runBlocking { - mockSuccessfulRequest(responseAccessToken, 200).getAccessToken() - } + val response = runBlocking { + mockSuccessfulRequest(responseAccessToken, i == 0, 200).getAccessToken() + } - val recordedRequest: RecordedRequest = mockWebServer.takeRequest() - assertTrue(recordedRequest.path!!.endsWith("/access_tokens")) + val recordedRequest: RecordedRequest = mockWebServer.takeRequest() + assertTrue(recordedRequest.path!!.endsWith("/access_tokens")) - verifyNewAccessTokenReturned(responseAccessToken, response) - verify { cache setProperty "accessToken" value responseAccessToken } + verifyNewAccessTokenReturned(responseAccessToken, response) + verify { cache setProperty "accessToken" value responseAccessToken } + } } private fun verifyNewAccessTokenReturned(expected: AccessToken, response: Response) { @@ -93,7 +95,7 @@ internal class AccessTokenRequestsTest : BaseTest() { val responseAccessToken = notExpiredAccessToken() // return null from cache at first, then on next call a new one every { cache getProperty "accessToken" } returns responseAccessToken - val response = runBlocking { mockSuccessfulRequest(responseAccessToken, 201).getAccessToken() } + val response = runBlocking { mockSuccessfulRequest(responseAccessToken, false, 201).getAccessToken() } // this means request is not made verify(exactly = 0) { cache setProperty "accessToken" value any() } @@ -109,9 +111,9 @@ internal class AccessTokenRequestsTest : BaseTest() { .setResponseCode(HttpURLConnection.HTTP_UNAUTHORIZED) .setBody( "{\"errors\":" + - "[{\"detail\":\"Missing or invalid assertion. It must be a JWT signed with the service account key.\"," + - "\"source\":\"assertion\"," + - "\"title\":\"Not authorized\"}]}" + "[{\"detail\":\"Missing or invalid assertion. It must be a JWT signed with the service account key.\"," + + "\"source\":\"assertion\"," + + "\"title\":\"Not authorized\"}]}" ) mockWebServer.enqueue(mockResponse) val baseUrl: HttpUrl = mockWebServer.url("") @@ -163,9 +165,16 @@ internal class AccessTokenRequestsTest : BaseTest() { assertTrue(status.error!!.title == genericError.title) } - private fun mockSuccessfulRequest(responseAccessToken: AccessToken, responseCode: Int = 201): AccessTokenRequests { + private fun mockSuccessfulRequest( + responseAccessToken: AccessToken, + addUnknownKey: Boolean = false, + responseCode: Int = 201 + ): AccessTokenRequests { // return null from cache at first, then on next call a new one - val json = Json.encodeToString(responseAccessToken) + var json = Json.encodeToString(responseAccessToken) + if (addUnknownKey) { + json = json.replaceFirst("{", "{\"unknownKey\":\"unknownValue\",") + } val mockResponse = MockResponse() .setResponseCode(responseCode) diff --git a/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequestsTest.kt b/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequestsTest.kt index 9627be8..6c1fb69 100644 --- a/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequestsTest.kt +++ b/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequestsTest.kt @@ -124,50 +124,65 @@ internal class ClearanceRequestsTest : BaseTest() { // request clearance + private val requestClearanceSuccessResponses = listOf( + """ + { + "vehicles": [ + { + "vin": "C0NNECT0000000001", + "status": "pending" + } + ] + } + """.trimIndent(), + """ + { + "vehicles": [ + { + "vin": "C0NNECT0000000001", + "status": "pending", + "unknown" : "value" + } + ] + } + """.trimIndent() + ) + @Test fun requestClearanceSuccessResponse() { - val mockResponse = MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody( - """ - { - "vehicles": [ - { - "vin": "WBADT43452G296403", - "status": "pending" - } - ] - } - """.trimIndent() - ) - mockWebServer.enqueue(mockResponse) - val mockUrl = mockWebServer.url("").toString() - val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) + requestClearanceSuccessResponses.forEach { + val mockResponse = MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(it) + mockWebServer.enqueue(mockResponse) + val mockUrl = mockWebServer.url("").toString() + val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) - val status = runBlocking { - webService.requestClearance("WBADT43452G296403", Brand.MERCEDES_BENZ, controlMeasures) - } + val status = runBlocking { + webService.requestClearance("C0NNECT0000000001", Brand.MERCEDES_BENZ, controlMeasures) + } - coVerify { accessTokenRequests.getAccessToken() } + coVerify { accessTokenRequests.getAccessToken() } - val recordedRequest: RecordedRequest = mockWebServer.takeRequest() - assertTrue(recordedRequest.path!!.endsWith("/fleets/vehicles")) + val recordedRequest: RecordedRequest = mockWebServer.takeRequest() + assertTrue(recordedRequest.path!!.endsWith("/fleets/vehicles")) - // verify request - assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${accessToken.accessToken}") + // verify request + assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${accessToken.accessToken}") - val jsonBody = Json.parseToJsonElement(recordedRequest.body.readUtf8()) - val array = jsonBody.jsonObject["vehicles"] as JsonArray - val firstVehicle = array.first() as JsonObject - assertTrue(firstVehicle["vin"]?.jsonPrimitive?.contentOrNull == "WBADT43452G296403") - val controlMeasures = firstVehicle["control_measures"]?.jsonObject - val odometer = controlMeasures?.get("odometer")?.jsonObject - assertTrue(odometer?.get("value")?.jsonPrimitive?.contentOrNull == "100000") - assertTrue(odometer?.get("unit")?.jsonPrimitive?.contentOrNull == "kilometers") + val jsonBody = Json.parseToJsonElement(recordedRequest.body.readUtf8()) + val array = jsonBody.jsonObject["vehicles"] as JsonArray + val firstVehicle = array.first() as JsonObject + assertTrue(firstVehicle["vin"]?.jsonPrimitive?.contentOrNull == "C0NNECT0000000001") + val controlMeasures = firstVehicle["control_measures"]?.jsonObject + val odometer = controlMeasures?.get("odometer")?.jsonObject + assertTrue(odometer?.get("value")?.jsonPrimitive?.contentOrNull == "100000") + assertTrue(odometer?.get("unit")?.jsonPrimitive?.contentOrNull == "kilometers") - // verify response - assertTrue(status.response!!.status == ClearanceStatus.Status.PENDING) - assertTrue(status.response!!.vin == "WBADT43452G296403") + // verify response + assertTrue(status.response!!.status == ClearanceStatus.Status.PENDING) + assertTrue(status.response!!.vin == "C0NNECT0000000001") + } } @Test @@ -175,7 +190,7 @@ internal class ClearanceRequestsTest : BaseTest() { testAuthTokenErrorReturned(mockWebServer, accessTokenRequests) { mockUrl -> val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.requestClearance( - "WBADT43452G296403", + "C0NNECT0000000001", Brand.MERCEDES_BENZ, controlMeasures ) @@ -187,7 +202,7 @@ internal class ClearanceRequestsTest : BaseTest() { testErrorResponseReturned(mockWebServer) { mockUrl -> val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.requestClearance( - "WBADT43452G296403", + "C0NNECT0000000001", Brand.MERCEDES_BENZ, controlMeasures ) @@ -199,7 +214,7 @@ internal class ClearanceRequestsTest : BaseTest() { testForUnknownResponseGenericErrorReturned(mockWebServer) { mockUrl -> val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.requestClearance( - "WBADT43452G296403", + "C0NNECT0000000001", Brand.MERCEDES_BENZ, controlMeasures ) @@ -208,47 +223,66 @@ internal class ClearanceRequestsTest : BaseTest() { // get clearance statuses + private val getClearanceStatusesSuccessResponses = listOf( + """ + [ + { + "brand":"bmw", + "vin": "C0NNECT0000000001", + "status": "pending" + }, + { + "brand":"bmw", + "vin": "WBADT43452G296404", + "status": "pending" + } + ] + """.trimIndent(), + """ + [ + { + "brand":"bmw", + "vin": "C0NNECT0000000001", + "status": "pending", + "unknown" : "value" + }, + { + "brand":"bmw", + "vin": "WBADT43452G296404", + "status": "pending" + } + ] + """.trimIndent() + ) + @Test fun getClearanceStatusesSuccessResponse() { - val mockResponse = MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody( - """ - [ - { - "brand":"bmw", - "vin": "WBADT43452G296403", - "status": "pending" - }, - { - "brand":"bmw", - "vin": "WBADT43452G296404", - "status": "pending" - } - ] - """.trimIndent() - ) - mockWebServer.enqueue(mockResponse) - val mockUrl = mockWebServer.url("").toString() - val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) + getClearanceStatusesSuccessResponses.forEach { + val mockResponse = MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(it) + mockWebServer.enqueue(mockResponse) + val mockUrl = mockWebServer.url("").toString() + val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) - val status = runBlocking { - webService.getClearanceStatuses() - } + val status = runBlocking { + webService.getClearanceStatuses() + } - coVerify { accessTokenRequests.getAccessToken() } + coVerify { accessTokenRequests.getAccessToken() } - val recordedRequest: RecordedRequest = mockWebServer.takeRequest() - assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${accessToken.accessToken}") + val recordedRequest: RecordedRequest = mockWebServer.takeRequest() + assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${accessToken.accessToken}") - assertTrue(recordedRequest.path!!.endsWith("/fleets/vehicles")) - assertTrue(recordedRequest.method == "GET") + assertTrue(recordedRequest.path!!.endsWith("/fleets/vehicles")) + assertTrue(recordedRequest.method == "GET") - assertTrue(status.response!![0].status == ClearanceStatus.Status.PENDING) - assertTrue(status.response!![0].vin == "WBADT43452G296403") + assertTrue(status.response!![0].status == ClearanceStatus.Status.PENDING) + assertTrue(status.response!![0].vin == "C0NNECT0000000001") - assertTrue(status.response!![1].status == ClearanceStatus.Status.PENDING) - assertTrue(status.response!![1].vin == "WBADT43452G296404") + assertTrue(status.response!![1].status == ClearanceStatus.Status.PENDING) + assertTrue(status.response!![1].vin == "WBADT43452G296404") + } } @Test @@ -306,47 +340,66 @@ internal class ClearanceRequestsTest : BaseTest() { // get clearance status + private val getClearanceStatusSuccessResponses = listOf( + """ + { + "brand":"bmw", + "vin": "C0NNECT0000000001", + "status": "pending", + "changelog": [ + { + "timestamp": "string", + "status": "approved", + "unknown" : "value" + } + ] + } + """.trimIndent(), + """ + { + "brand":"bmw", + "vin": "C0NNECT0000000001", + "status": "pending", + "changelog": [ + { + "timestamp": "string", + "status": "approved" + } + ], + "unknown" : "value" + } + """.trimIndent(), + ) + @Test fun getClearanceStatusSuccessResponse() { - val vin = "WBADT43452G296403" - val mockResponse = MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody( - """ - { - "brand":"bmw", - "vin": "$vin", - "status": "pending", - "changelog": [ - { - "timestamp": "string", - "status": "approved" - } - ] - } - """.trimIndent() - ) - mockWebServer.enqueue(mockResponse) - val mockUrl = mockWebServer.url("").toString() - val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) + getClearanceStatusSuccessResponses.forEach { + val vin = "C0NNECT0000000001" + val mockResponse = MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(it) + mockWebServer.enqueue(mockResponse) + val mockUrl = mockWebServer.url("").toString() + val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) - val status = runBlocking { - webService.getClearanceStatus(vin) - } + val status = runBlocking { + webService.getClearanceStatus(vin) + } - coVerify { accessTokenRequests.getAccessToken() } + coVerify { accessTokenRequests.getAccessToken() } - val recordedRequest: RecordedRequest = mockWebServer.takeRequest() - assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${accessToken.accessToken}") + val recordedRequest: RecordedRequest = mockWebServer.takeRequest() + assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${accessToken.accessToken}") - assertTrue(recordedRequest.path!!.endsWith("/fleets/vehicles/$vin")) - assertTrue(recordedRequest.method == "GET") + assertTrue(recordedRequest.path!!.endsWith("/fleets/vehicles/$vin")) + assertTrue(recordedRequest.method == "GET") - assertTrue(status.response!!.status == ClearanceStatus.Status.PENDING) - assertTrue(status.response?.vin == vin) - assertTrue(status.response?.brand == Brand.BMW) + assertTrue(status.response!!.status == ClearanceStatus.Status.PENDING) + assertTrue(status.response?.vin == vin) + assertTrue(status.response?.brand == Brand.BMW) - assertTrue(status.response?.changelog?.get(0)?.status == ClearanceStatus.Status.APPROVED) + assertTrue(status.response?.changelog?.get(0)?.status == ClearanceStatus.Status.APPROVED) + } } @Test @@ -375,38 +428,49 @@ internal class ClearanceRequestsTest : BaseTest() { // delete clearance + private val deleteClearanceSuccessResponses = listOf( + """ + { + "vin": "C0NNECT0000000001", + "status": "revoking" + } + """.trimIndent(), + """ + { + "vin": "C0NNECT0000000001", + "status": "revoking", + "unknown" : "value" + } + """.trimIndent() + ) + @Test fun deleteClearanceSuccessResponse() { - val vin = "WBADT43452G296403" - val mockResponse = MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody( - """ - { - "vin": "$vin", - "status": "revoking" - } - """.trimIndent() - ) - mockWebServer.enqueue(mockResponse) - val mockUrl = mockWebServer.url("").toString() - val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) + val vin = "C0NNECT0000000001" + deleteClearanceSuccessResponses.forEach { + val mockResponse = MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(it) + mockWebServer.enqueue(mockResponse) + val mockUrl = mockWebServer.url("").toString() + val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) - val status = runBlocking { - webService.deleteClearance(vin) - } + val status = runBlocking { + webService.deleteClearance(vin) + } - coVerify { accessTokenRequests.getAccessToken() } + coVerify { accessTokenRequests.getAccessToken() } - val recordedRequest: RecordedRequest = mockWebServer.takeRequest() - assertTrue(recordedRequest.path!!.endsWith("/fleets/vehicles/$vin")) + val recordedRequest: RecordedRequest = mockWebServer.takeRequest() + assertTrue(recordedRequest.path!!.endsWith("/fleets/vehicles/$vin")) - // verify request - assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${accessToken.accessToken}") + // verify request + assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${accessToken.accessToken}") - // verify response - assertTrue(status.response!!.status == ClearanceStatus.Status.REVOKING) - assertTrue(status.response!!.vin == vin) + // verify response + assertTrue(status.response!!.status == ClearanceStatus.Status.REVOKING) + assertTrue(status.response!!.vin == vin) + } } @Test @@ -414,7 +478,7 @@ internal class ClearanceRequestsTest : BaseTest() { testAuthTokenErrorReturned(mockWebServer, accessTokenRequests) { mockUrl -> val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.deleteClearance( - "WBADT43452G296403" + "C0NNECT0000000001" ) } } @@ -424,7 +488,7 @@ internal class ClearanceRequestsTest : BaseTest() { testErrorResponseReturned(mockWebServer) { mockUrl -> val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.deleteClearance( - "WBADT43452G296403" + "C0NNECT0000000001" ) } } @@ -434,7 +498,7 @@ internal class ClearanceRequestsTest : BaseTest() { testForUnknownResponseGenericErrorReturned(mockWebServer) { mockUrl -> val webService = ClearanceRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.deleteClearance( - "WBADT43452G296403" + "C0NNECT0000000001" ) } } diff --git a/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/UtilityRequestsTest.kt b/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/UtilityRequestsTest.kt index c9237fe..122a14c 100644 --- a/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/UtilityRequestsTest.kt +++ b/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/UtilityRequestsTest.kt @@ -64,50 +64,65 @@ internal class UtilityRequestsTest : BaseTest() { // get eligibility + private val getEligibilitySuccessResponses = listOf( + """ + { + "vin": "C0NNECT0000000001", + "eligible": true, + "data_delivery": [ + "pull", + "push" + ] + } + """.trimIndent(), + """ + { + "vin": "C0NNECT0000000001", + "eligible": true, + "data_delivery": [ + "pull", + "push" + ], + "unknown": true + } + """.trimIndent(), + ) + @Test fun getEligibilitySuccess() { - val mockResponse = MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody( - """ - { - "vin": "WBADT43452G296403", - "eligible": true, - "data_delivery": [ - "pull", - "push" - ] - } - """.trimIndent() - ) - mockWebServer.enqueue(mockResponse) - val mockUrl = mockWebServer.url("").toString() - val webService = UtilityRequests(client, mockLogger, mockUrl, accessTokenRequests) + getEligibilitySuccessResponses.forEach { + val mockResponse = MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(it) + mockWebServer.enqueue(mockResponse) + val mockUrl = mockWebServer.url("").toString() + val webService = UtilityRequests(client, mockLogger, mockUrl, accessTokenRequests) - val response = runBlocking { - webService.getEligibility("WBADT43452G296403", Brand.BMW) + val response = runBlocking { + webService.getEligibility("C0NNECT0000000001", Brand.BMW) + } + + coVerify { accessTokenRequests.getAccessToken() } + + val recordedRequest: RecordedRequest = mockWebServer.takeRequest() + assertTrue(recordedRequest.path!!.endsWith("/eligibility")) + + // verify request + assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${authToken.accessToken}") + val jsonBody = Json.parseToJsonElement(recordedRequest.body.readUtf8()) + assertTrue(jsonBody.jsonObject["vin"]?.jsonPrimitive?.contentOrNull == "C0NNECT0000000001") + assertTrue(jsonBody.jsonObject["brand"]?.jsonPrimitive?.contentOrNull == "bmw") + + // verify response + val status = response.response!! + assertTrue(status.vin == "C0NNECT0000000001") + assertTrue(status.eligible) + assertTrue(status.dataDelivery.size == 2) + assertTrue(status.dataDelivery.find { it == EligibilityStatus.DataDelivery.PULL } != null) + assertTrue(status.dataDelivery.find { it == EligibilityStatus.DataDelivery.PUSH } != null) + assertTrue(status.connectivityStatus == null) + assertTrue(status.primaryUserAssigned == null) } - - coVerify { accessTokenRequests.getAccessToken() } - - val recordedRequest: RecordedRequest = mockWebServer.takeRequest() - assertTrue(recordedRequest.path!!.endsWith("/eligibility")) - - // verify request - assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${authToken.accessToken}") - val jsonBody = Json.parseToJsonElement(recordedRequest.body.readUtf8()) - assertTrue(jsonBody.jsonObject["vin"]?.jsonPrimitive?.contentOrNull == "WBADT43452G296403") - assertTrue(jsonBody.jsonObject["brand"]?.jsonPrimitive?.contentOrNull == "bmw") - - // verify response - val status = response.response!! - assertTrue(status.vin == "WBADT43452G296403") - assertTrue(status.eligible) - assertTrue(status.dataDelivery.size == 2) - assertTrue(status.dataDelivery.find { it == EligibilityStatus.DataDelivery.PULL } != null) - assertTrue(status.dataDelivery.find { it == EligibilityStatus.DataDelivery.PUSH } != null) - assertTrue(status.connectivityStatus == null) - assertTrue(status.primaryUserAssigned == null) } @Test @@ -117,7 +132,7 @@ internal class UtilityRequestsTest : BaseTest() { .setBody( """ { - "vin": "WBADT43452G296403", + "vin": "C0NNECT0000000001", "eligible": true, "data_delivery": [ "pull", @@ -134,7 +149,7 @@ internal class UtilityRequestsTest : BaseTest() { val webService = UtilityRequests(client, mockLogger, mockUrl, accessTokenRequests) val status = runBlocking { - webService.getEligibility("WBADT43452G296403", Brand.BMW) + webService.getEligibility("C0NNECT0000000001", Brand.BMW) }.response!! coVerify { accessTokenRequests.getAccessToken() } @@ -145,11 +160,11 @@ internal class UtilityRequestsTest : BaseTest() { // verify request assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${authToken.accessToken}") val jsonBody = Json.parseToJsonElement(recordedRequest.body.readUtf8()) - assertTrue(jsonBody.jsonObject["vin"]?.jsonPrimitive?.contentOrNull == "WBADT43452G296403") + assertTrue(jsonBody.jsonObject["vin"]?.jsonPrimitive?.contentOrNull == "C0NNECT0000000001") assertTrue(jsonBody.jsonObject["brand"]?.jsonPrimitive?.contentOrNull == "bmw") // verify response - assertTrue(status.vin == "WBADT43452G296403") + assertTrue(status.vin == "C0NNECT0000000001") assertTrue(status.eligible) assertTrue(status.dataDelivery.size == 2) assertTrue(status.dataDelivery.find { it == EligibilityStatus.DataDelivery.PULL } != null) @@ -160,33 +175,33 @@ internal class UtilityRequestsTest : BaseTest() { } @Test - fun requestClearanceAuthTokenError() = runBlocking { + fun getEligibilityAuthTokenError() = runBlocking { testAuthTokenErrorReturned(mockWebServer, accessTokenRequests) { mockUrl -> val webService = UtilityRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.getEligibility( - "WBADT43452G296403", + "C0NNECT0000000001", Brand.BMW, ) } } @Test - fun requestClearanceErrorResponse() = runBlocking { + fun getEligibilityErrorResponse() = runBlocking { testErrorResponseReturned(mockWebServer) { mockUrl -> val webService = UtilityRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.getEligibility( - "WBADT43452G296403", + "C0NNECT0000000001", Brand.BMW, ) } } @Test - fun requestClearanceUnknownResponse() = runBlocking { + fun getEligibilityUnknownResponse() = runBlocking { testForUnknownResponseGenericErrorReturned(mockWebServer) { mockUrl -> val webService = UtilityRequests(client, mockLogger, mockUrl, accessTokenRequests) webService.getEligibility( - "WBADT43452G296403", + "C0NNECT0000000001", Brand.BMW, ) } From 930b966ca9bec067e3504c817d3fdccb22d6787a Mon Sep 17 00:00:00 2001 From: tonisives Date: Wed, 31 Jan 2024 08:49:18 +0700 Subject: [PATCH 2/4] ignore unknown keys for all --- .../hmkitfleet/network/AccessTokenRequests.kt | 5 +- .../hmkitfleet/network/ClearanceRequests.kt | 8 +-- .../hmkitfleet/network/Requests.kt | 23 ++++++--- .../hmkitfleet/network/UtilityRequests.kt | 2 +- ...Requests.kt => VehicleDataRequestsTest.kt} | 49 ++++++++++++------- 5 files changed, 52 insertions(+), 35 deletions(-) rename hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/{VehicleDataRequests.kt => VehicleDataRequestsTest.kt} (75%) diff --git a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/AccessTokenRequests.kt b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/AccessTokenRequests.kt index 96d2797..27c490e 100644 --- a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/AccessTokenRequests.kt +++ b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/AccessTokenRequests.kt @@ -26,7 +26,6 @@ package com.highmobility.hmkitfleet.network import com.highmobility.hmkitfleet.HMKitCredentials import com.highmobility.hmkitfleet.model.AccessToken import com.highmobility.hmkitfleet.utils.await -import kotlinx.serialization.json.Json import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient @@ -70,8 +69,6 @@ internal class AccessTokenRequests( return request } - private val json = Json { ignoreUnknownKeys = true } - suspend fun getAccessToken(): Response { val cachedToken = cache.accessToken if (cachedToken != null) return Response(cachedToken) @@ -86,7 +83,7 @@ internal class AccessTokenRequests( return try { if (response.code == HttpURLConnection.HTTP_CREATED || response.code == HttpURLConnection.HTTP_OK) { - cache.accessToken = json.decodeFromString(responseBody) + cache.accessToken = jsonIg.decodeFromString(responseBody) Response(cache.accessToken) } else { parseError(responseBody) diff --git a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequests.kt b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequests.kt index 4229f90..7121b2a 100644 --- a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequests.kt +++ b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequests.kt @@ -81,7 +81,7 @@ internal class ClearanceRequests( val statuses = jsonElement["vehicles"] as JsonArray for (statusElement in statuses) { val status = - Json.decodeFromJsonElement(statusElement) + jsonIg.decodeFromJsonElement(statusElement) if (status.vin == vin) { return@tryParseResponse Response(status, null) } @@ -112,7 +112,7 @@ internal class ClearanceRequests( val builder = Array(statuses.size) { val statusElement = statuses[it] - val status = Json.decodeFromJsonElement(statusElement) + val status = jsonIg.decodeFromJsonElement(statusElement) status } @@ -138,7 +138,7 @@ internal class ClearanceRequests( val response = call.await() return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody -> - val status = Json.decodeFromString(responseBody) + val status = jsonIg.decodeFromString(responseBody) Response(status) } } @@ -162,7 +162,7 @@ internal class ClearanceRequests( val response = call.await() return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody -> - val status = Json.decodeFromString(responseBody) + val status = jsonIg.decodeFromString(responseBody) Response(status) } } diff --git a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/Requests.kt b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/Requests.kt index de700bf..192c223 100644 --- a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/Requests.kt +++ b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/Requests.kt @@ -44,6 +44,15 @@ internal open class Requests( ) { val mediaType = "application/json; charset=utf-8".toMediaType() + protected val jsonIg = Json { + ignoreUnknownKeys = true + } + + private val jsonIgPr = Json { + ignoreUnknownKeys = true + prettyPrint = true + } + // note: inline functions are not shown in coverage @Suppress("TooGenericExceptionCaught") inline fun tryParseResponse( @@ -66,20 +75,18 @@ internal open class Requests( } fun printRequest(request: Request) { - val format = Json { prettyPrint = true } - // parse into json, so can log it out with pretty print val body = request.bodyAsString() var bodyInPrettyPrint = "" if (!body.isNullOrBlank()) { - val jsonElement = format.decodeFromString(body) - bodyInPrettyPrint = format.encodeToString(jsonElement) + val jsonElement = jsonIgPr.decodeFromString(body) + bodyInPrettyPrint = jsonIgPr.encodeToString(jsonElement) } logger.debug( "sending ${request.method} ${request.url}:" + - "\nheaders: ${request.headers}" + - "body: $bodyInPrettyPrint" + "\nheaders: ${request.headers}" + + "body: $bodyInPrettyPrint" ) } @@ -90,14 +97,14 @@ internal open class Requests( } fun parseError(responseBody: String): com.highmobility.hmkitfleet.network.Response { - val json = Json.parseToJsonElement(responseBody) + val json = jsonIg.parseToJsonElement(responseBody) return if (json is JsonObject) { // there are 3 error formats val errors = json["errors"] as? JsonArray parseErrorsArray(errors, json) } else if (json is JsonArray && json.size > 0) { - val error = Json.decodeFromJsonElement(json.first()) + val error = jsonIg.decodeFromJsonElement(json.first()) Response(null, error) } else { Response(null, genericError("Unknown server response")) diff --git a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/UtilityRequests.kt b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/UtilityRequests.kt index 86dd4a8..ff45245 100644 --- a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/UtilityRequests.kt +++ b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/UtilityRequests.kt @@ -70,7 +70,7 @@ internal class UtilityRequests( val response = call.await() return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody -> - val eligibilityStatus = Json.decodeFromString(responseBody) + val eligibilityStatus = jsonIg.decodeFromString(responseBody) if (eligibilityStatus.vin != vin) logger.warn("VIN in response does not match VIN in request") Response(eligibilityStatus, null) } diff --git a/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/VehicleDataRequests.kt b/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/VehicleDataRequestsTest.kt similarity index 75% rename from hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/VehicleDataRequests.kt rename to hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/VehicleDataRequestsTest.kt index c1ee556..e830852 100644 --- a/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/VehicleDataRequests.kt +++ b/hmkit-fleet/src/test/kotlin/com/highmobility/hmkitfleet/network/VehicleDataRequestsTest.kt @@ -57,36 +57,49 @@ internal class VehicleDataRequestsTest : BaseTest() { mockWebServer.shutdown() } - @Test - fun getSuccess() { - val responseJson = """ + private val successResponses = listOf( + """ { "vin": $testVin, "status": "vehicle_running" } + """.trimIndent(), + """ + { + "vin": $testVin, + "status": "vehicle_running", + "unknown": "value" + } """.trimIndent() + ) - val mockResponse = MockResponse().setResponseCode(HttpURLConnection.HTTP_OK).setBody(responseJson) + @Test + fun getSuccess() { + successResponses.forEach { + val responseJson = it - mockWebServer.enqueue(mockResponse) - val mockUrl = mockWebServer.url("").toString() - val webService = VehicleDataRequests(client, mockLogger, mockUrl, accessTokenRequests) + val mockResponse = MockResponse().setResponseCode(HttpURLConnection.HTTP_OK).setBody(responseJson) - val response = runBlocking { - webService.getVehicleStatus(testVin) - } + mockWebServer.enqueue(mockResponse) + val mockUrl = mockWebServer.url("").toString() + val webService = VehicleDataRequests(client, mockLogger, mockUrl, accessTokenRequests) - coVerify { accessTokenRequests.getAccessToken() } + val response = runBlocking { + webService.getVehicleStatus(testVin) + } - val recordedRequest: RecordedRequest = mockWebServer.takeRequest() - assertTrue(recordedRequest.path!!.endsWith("/vehicle-data/autoapi-13/$testVin")) + coVerify { accessTokenRequests.getAccessToken() } - // verify request - assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${authToken.accessToken}") + val recordedRequest: RecordedRequest = mockWebServer.takeRequest() + assertTrue(recordedRequest.path!!.endsWith("/vehicle-data/autoapi-13/$testVin")) - // verify response - val status = response.response!! - assertTrue(status == responseJson) + // verify request + assertTrue(recordedRequest.headers["Authorization"] == "Bearer ${authToken.accessToken}") + + // verify response + val status = response.response!! + assertTrue(status == responseJson) + } } @Test From cbe183b68938b367cc975626b6e3d34e1dacff70 Mon Sep 17 00:00:00 2001 From: tonisives Date: Wed, 31 Jan 2024 14:10:51 +0700 Subject: [PATCH 3/4] update more json parsers --- .../hmkitfleet/network/ClearanceRequests.kt | 10 +++++----- .../com/highmobility/hmkitfleet/network/Requests.kt | 2 +- .../highmobility/hmkitfleet/network/UtilityRequests.kt | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequests.kt b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequests.kt index 7121b2a..fe64201 100644 --- a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequests.kt +++ b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/ClearanceRequests.kt @@ -77,7 +77,7 @@ internal class ClearanceRequests( val response = call.await() return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody -> - val jsonElement = Json.parseToJsonElement(responseBody) as JsonObject + val jsonElement = jsonIg.parseToJsonElement(responseBody) as JsonObject val statuses = jsonElement["vehicles"] as JsonArray for (statusElement in statuses) { val status = @@ -108,7 +108,7 @@ internal class ClearanceRequests( val response = call.await() return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody -> - val statuses = Json.parseToJsonElement(responseBody) as JsonArray + val statuses = jsonIg.parseToJsonElement(responseBody) as JsonArray val builder = Array(statuses.size) { val statusElement = statuses[it] @@ -174,14 +174,14 @@ internal class ClearanceRequests( ): RequestBody { val vehicle = buildJsonObject { put("vin", vin) - put("brand", Json.encodeToJsonElement(brand)) + put("brand", jsonIg.encodeToJsonElement(brand)) if (controlMeasures != null) { putJsonObject("control_measures") { for (controlMeasure in controlMeasures) { // polymorphism adds type key to child controlmeasure classes. remove with filter - val json = Json.encodeToJsonElement(controlMeasure) + val json = jsonIg.encodeToJsonElement(controlMeasure) val valuesWithoutType = json.jsonObject.filterNot { it.key == "type" } - val jsonTrimmed = Json.encodeToJsonElement(valuesWithoutType) + val jsonTrimmed = jsonIg.encodeToJsonElement(valuesWithoutType) put("odometer", jsonTrimmed) } } diff --git a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/Requests.kt b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/Requests.kt index 192c223..1305120 100644 --- a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/Requests.kt +++ b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/Requests.kt @@ -116,7 +116,7 @@ internal open class Requests( json: JsonObject ): com.highmobility.hmkitfleet.network.Response = if (errors != null && errors.size > 0) { val error = - Json.decodeFromJsonElement(errors.first()) + jsonIg.decodeFromJsonElement(errors.first()) Response(null, error) } else { val error = Error( diff --git a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/UtilityRequests.kt b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/UtilityRequests.kt index ff45245..04e7119 100644 --- a/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/UtilityRequests.kt +++ b/hmkit-fleet/src/main/kotlin/com/highmobility/hmkitfleet/network/UtilityRequests.kt @@ -82,10 +82,10 @@ internal class UtilityRequests( ): RequestBody { val vehicle = buildJsonObject { put("vin", vin) - put("brand", Json.encodeToJsonElement(brand)) + put("brand", jsonIg.encodeToJsonElement(brand)) } - val body = Json.encodeToString(vehicle).toRequestBody(mediaType) + val body = jsonIg.encodeToString(vehicle).toRequestBody(mediaType) return body } } From 83f6e578bee36f1224f9d7c77e1c3ea66c5906ac Mon Sep 17 00:00:00 2001 From: tonisives Date: Wed, 31 Jan 2024 15:01:39 +0700 Subject: [PATCH 4/4] update version --- CHANGELOG.md | 6 +++++- gradle.properties | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da3050a..91ec7dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ This is the changelog for v2 releases. See v0/v1 releases in appropriate branches. -## [2.2.2] - 2023-12-7 +## [2.0.3] - 2024-1-31 + +- Fix `Json {} ignoreUnknownKeys` error + +## [2.0.2] - 2023-12-7 - Update javadoc diff --git a/gradle.properties b/gradle.properties index d676cdb..239bf45 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=2.0.2 +version=2.0.3 kotlin.code.style=official \ No newline at end of file