Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workaround bug in android 4 for JSON objects with List<String> #942

Merged
merged 14 commits into from
May 19, 2023

Conversation

aboedo
Copy link
Member

@aboedo aboedo commented Apr 4, 2023

Fixes https://linear.app/revenuecat/issue/SDK-3052/400-from-post-receipts-because-product-did-not-have-quotes
https://revenuecats.atlassian.net/browse/CSDK-681

Works around the following bug in android 4:
https://stackoverflow.com/q/37317669
http://fupeg.blogspot.com/2011/07/android-json-bug.html

Essentially, this bug means that when you get a list of product_ids, instead of sending a list of strings, it sends a list with a string in it.

Before After
"product_ids": "[item1, item2]" "product_ids": ["item1, "item2"]

@aboedo aboedo self-assigned this Apr 4, 2023
@aboedo aboedo added the pr:fix A bug fix label Apr 4, 2023
@codecov
Copy link

codecov bot commented Apr 5, 2023

Codecov Report

Merging #942 (1344e6a) into main (82e7c20) will decrease coverage by 0.63%.
The diff coverage is 33.33%.

❗ Current head 1344e6a differs from pull request most recent head 5eea593. Consider uploading reports for the commit 5eea593 to get more accurate results

@@            Coverage Diff             @@
##             main     #942      +/-   ##
==========================================
- Coverage   85.07%   84.45%   -0.63%     
==========================================
  Files         165      160       -5     
  Lines        5824     5520     -304     
  Branches      801      774      -27     
==========================================
- Hits         4955     4662     -293     
- Misses        550      561      +11     
+ Partials      319      297      -22     
Impacted Files Coverage Δ
...java/com/revenuecat/purchases/common/HTTPClient.kt 85.50% <33.33%> (-2.38%) ⬇️

... and 25 files with indirect coverage changes

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@vegaro
Copy link
Contributor

vegaro commented Apr 5, 2023

Btw, I found the similar issue we had in the past #144

@@ -239,6 +240,16 @@ class HTTPClient(
private fun Map<String, Any?>.convert(): JSONObject {
val mapWithoutInnerMaps = mapValues { (_, value) ->
value.tryCast<Map<String, Any?>>(ifSuccess = { convert() })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need to be removed I think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you clarify? this was added to fix a separate but similar issue (the one that César posted, #144)

@@ -239,6 +240,16 @@ class HTTPClient(
private fun Map<String, Any?>.convert(): JSONObject {
val mapWithoutInnerMaps = mapValues { (_, value) ->
value.tryCast<Map<String, Any?>>(ifSuccess = { convert() })
when (value) {
is List<*> -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a comment explaining this.

@aboedo aboedo force-pushed the andy/sdk-3052-400-from-post-receipts-because-product branch 2 times, most recently from 2a95f56 to dd17b2b Compare April 6, 2023 14:49
@@ -236,30 +239,6 @@ class HTTPClient(
.filterNotNullValues()
}

private fun Map<String, Any?>.convert(): JSONObject {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to its own class to make testing easier and more focused

return JSONObject(inputMap)
}

/** To avoid Java type erasure, we use a Kotlin inline function with a reified parameter
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this bit was moved from HTTPClient

Comment on lines 21 to 23
if (inputMap == null) {
return JSONObject()
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: make sure that this matches the previous behavior exactly.

Comment on lines 128 to 131
val jsonBody = body?.convert()
val jsonBody = body?.let { mapConverter.convertToJSON(body) }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just double-checking, android folks: this should be equivalent, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do

        val jsonBody = body?.let { mapConverter.convertToJSON(it) }

But yes. The difference is that with it, you are not using smart casting from kotlin. But it's the same.

@@ -239,6 +240,16 @@ class HTTPClient(
private fun Map<String, Any?>.convert(): JSONObject {
val mapWithoutInnerMaps = mapValues { (_, value) ->
value.tryCast<Map<String, Any?>>(ifSuccess = { convert() })
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you clarify? this was added to fix a separate but similar issue (the one that César posted, #144)

@aboedo aboedo marked this pull request as ready for review April 6, 2023 15:50
@aboedo aboedo requested review from tonidero and vegaro April 6, 2023 15:50
@aboedo
Copy link
Member Author

aboedo commented Apr 6, 2023

I won't merge until I've managed to reproduce with an instrumented test, but it should be ready to review in the meantime.

* (i.e.: "[\"value1\", \"value2\"]" instead of "[value1, value2]")
*/
@Test
fun `test map conversion fixes wrong treatment of arrays of strings in JSON library`() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the old code fails this test

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

*/
internal fun convertToJSON(inputMap: Map<String, Any?>): JSONObject {
val mapWithoutInnerMaps = inputMap.mapValues { (_, value) ->
value.tryCast<Map<String, Any?>>(ifSuccess = { convertToJSON(this) })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm reading this wrong, this line doesn't do anything. It will convert to JSON but we are ignoring the return value I think? We moved this line to the else inside the when, so I think this line can be removed if I'm not wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I was thinking the same

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, leftover from refactor, thanks

.put("key1", "value1")
.put("key2", JSONArray(listOf("value2", "value3")))
.put("key3", JSONObject().put("nestedKey", "nestedValue"))
.put("key4", JSONArray(listOf("value4", "value5")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm should this be a JSONObject with a JSONArray value?

Copy link
Member Author

@aboedo aboedo Apr 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh oops this isn't nested 😅 Great catch!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, lmk what you think!

*/
@Test
fun `test map conversion fixes wrong treatment of arrays of strings in JSON library`() {
val mapConverterMock = spyk<MapConverter>()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to mapConverterSpy? Since it's not technically a mock

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh spyk nice, I've never used this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not technically using it as a spy either, more like a partial mock, since I do modify the return value for some circumstances. Perhaps renaming it to that would be clearer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trying it this way, let me know what you think

*/
@Test
fun `test map conversion fixes wrong treatment of arrays of strings in JSON library`() {
val mapConverterMock = spyk<MapConverter>()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh spyk nice, I've never used this

*/
internal fun convertToJSON(inputMap: Map<String, Any?>): JSONObject {
val mapWithoutInnerMaps = inputMap.mapValues { (_, value) ->
value.tryCast<Map<String, Any?>>(ifSuccess = { convertToJSON(this) })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I was thinking the same

Comment on lines 128 to 131
val jsonBody = body?.convert()
val jsonBody = body?.let { mapConverter.convertToJSON(body) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do

        val jsonBody = body?.let { mapConverter.convertToJSON(it) }

But yes. The difference is that with it, you are not using smart casting from kotlin. But it's the same.

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@tonidero
Copy link
Contributor

Ah linter failed.

@aboedo aboedo force-pushed the andy/sdk-3052-400-from-post-receipts-because-product branch from 57b8d2f to c2a7709 Compare April 12, 2023 13:03
@aboedo
Copy link
Member Author

aboedo commented Apr 12, 2023

We might wanna separate that linter step so it's clearer that it's not a test failure but rather linting, very different priorities there.
The linter actually failed on an entirely separate part of the codebase, though - I suppose it was failing on master on the commit this was based upon. I'm rebasing to see if that makes a difference.

@vegaro
Copy link
Contributor

vegaro commented Apr 12, 2023

@aboedo I extracted detektAll into its own job in #965

@aboedo
Copy link
Member Author

aboedo commented Apr 12, 2023

@vegaro awesome thanks!! 🙌🙌🙌

@NachoSoto
Copy link
Contributor

Is this ready to be merged?

@aboedo
Copy link
Member Author

aboedo commented May 19, 2023

oops forgot about this, yeah, it's ready to go

@aboedo aboedo merged commit c21e0c7 into main May 19, 2023
@aboedo aboedo deleted the andy/sdk-3052-400-from-post-receipts-because-product branch May 19, 2023 20:42
This was referenced May 24, 2023
tonidero added a commit that referenced this pull request May 25, 2023
**This is an automatic release.**

### New Features
* Support DEFERRED mode (#985) via swehner (@swehner)
* Add completion callback to syncPurchases API (#1002) via Toni Rico
(@tonidero)
### Bugfixes
* Workaround bug in android 4 for JSON objects with List<String> (#942)
via Andy Boedo (@aboedo)
### Dependency Updates
* Bump fastlane-plugin-revenuecat_internal from `fe45299` to `13773d2`
(#1015) via dependabot[bot] (@dependabot[bot])
### Other Changes
* Bump dokka to 1.8.10 to support Gradle 8 (#1009) via Toni Rico
(@tonidero)
* Disable offline entitlements temporarily (#1023) via Toni Rico
(@tonidero)
* Fix integration tests in CI (#1019) via Toni Rico (@tonidero)
* Add offline entitlements integration tests (#1006) via Toni Rico
(@tonidero)
* Disable offline entitlements in observer mode (#1014) via Toni Rico
(@tonidero)
* Extracts setup and teardown to BasePurchasesTest (#1011) via Cesar de
la Vega (@vegaro)
* Support forcing server errors for tests (#1008) via Toni Rico
(@tonidero)

---------

Co-authored-by: revenuecat-ops <ops@revenuecat.com>
Co-authored-by: Toni Rico <antonio.rico.diez@revenuecat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr:fix A bug fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants