HTTPClient.perform: changed response to Result<[String: Any], Error>#1387
Conversation
|
I'm seeing these failures locally too, but only sometimes 🤔 not when I run each test individually. It's failing to find the active I have an idea for how to improve this for tests by I don't understand what in this PR would make these fail all of a sudden? |
bf3c5c3 to
7b3b9f1
Compare
|
Tests fixed in #1390 |
There was a problem hiding this comment.
There's so much logic here that's going to go away and move to HTTPClient.
There was a problem hiding this comment.
This line is basically repeated in all operations, so it'll be moved to HTTPClient too.
There was a problem hiding this comment.
Obviously the public APIs still have the Obj-C style API. This is how the Result is converted back.
There was a problem hiding this comment.
I'd love to add Result alternatives down the line for Swift, although tbh I imagine a lot of folks will just migrate to async / await
There was a problem hiding this comment.
Yeah exactly, but we could easily offer them too
There was a problem hiding this comment.
One example of several where tests were using mocks that returned no value and no error by default, which many tests didn't override.
There was a problem hiding this comment.
These are no longer using the test-only .init(testData:). The actual method in the code .init(data:) already has a throws initializer, so don't need to use XCTUnwrap. I think using this we can probably get rid of init(testData:).
There was a problem hiding this comment.
This was testing something that couldn't happen.
There was a problem hiding this comment.
Same here, why have an Optional around when we can make sure at initialization that the data is correct. Th (Value?, Error?) APIs enabled optionals all over the place that shouldn't have been.
There was a problem hiding this comment.
Same here, this could have been setting nil as the mock without knowing 🤷🏻♂️
There was a problem hiding this comment.
Again testing something that wasn't possible.
aboedo
left a comment
There was a problem hiding this comment.
awesome!!! I love this refactor. I'd love to see pretty much everything in our codebase that requires completion blocks use Result, and this gets us like 90% of the way there 👏
There was a problem hiding this comment.
I've been dreaming of this refactor since we first started migrating code to swift
There was a problem hiding this comment.
tbh I still feel like the needsToRefresh variable made this slightly more readable, i.e.: it told you why you're going on this path - you need to refresh.
maybe we could have this be guard !needsToRefresh else {
There was a problem hiding this comment.
The reason I changed it is so that I could do let customerInfo = instead of cachedCustomerInfo != nil and then force-unwrapping :/
There was a problem hiding this comment.
this is such a nice win - now if the completion was called, there's no way that receivedResult could be nil, so need for an extra var
7b3b9f1 to
af3f089
Compare
Currently pretty much all closures in the codebase use `(Value?, Error?)`, which is a leftover from Objective-C days. These can represent 4 different cases: - **Value / no-error** - **No-value / error** - _Value / error_ - _No-value / no-error_ Out of those 4, only 2 are really valid, which meant that a lot of code was spent handling the other 2, either creating errors (like `UnexpectedBackendResponseSubErrorCode.loginMissingResponse`) that weren't possible, or hard to follow code because both a value and an error were possible. In tests, too, the mocks were initially set up to produce no value and no error, which were in many cases triggering code paths that weren't legal with real code. This change removes this possibility all together by ensuring **at compile time**, that one of the two is always present.
af3f089 to
b812108
Compare
These have began failing on #1387, even though that doesn't change anything that should affect the `UIWindowScene` or anything related. This could have been a regression from Xcode 13.3. When the test fails (which isn't always), the scene isn't yet `.foregroundActive` . To workaround that, this defaults to any scene, *though only when running tests, on debug, and on the simulator*. So nothing is changed in release or device builds. This also has a small refactor on `BeginRefundRequestHelperTests` to move the test data below and make the tests more readable. Additionally, the `MockSK2BeginRefundRequestHelper` is recreated for every test. I thought initially that this is what was making the test fail, as the mock expectations and state was being shared across tests. It's not, but now that should remove any potential issues with shared state.
These have began failing on #1387, even though that doesn't change anything that should affect the `UIWindowScene` or anything related. This could have been a regression from Xcode 13.3. When the test fails (which isn't always), the scene isn't yet `.foregroundActive` . To workaround that, this defaults to any scene, *though only when running tests, on debug, and on the simulator*. So nothing is changed in release or device builds. This also has a small refactor on `BeginRefundRequestHelperTests` to move the test data below and make the tests more readable. Additionally, the `MockSK2BeginRefundRequestHelper` is recreated for every test. I thought initially that this is what was making the test fail, as the mock expectations and state was being shared across tests. It's not, but now that should remove any potential issues with shared state.
… if the response was successful Fixes [SDKONCALL-62]. The behavior in version 3.x was that we would return a `CustomerInfo` AND the attribute errors. [The test in that version](https://github.com/RevenueCat/purchases-ios/blob/3.14.2/PurchasesTests/SubscriberAttributes/BackendSubscriberAttributesTests.swift#L370-L404) however only verified that the error was returned. When we converted `(Value, Error)` callbacks to using `Result` (#1387), I assumed this was an error. Because of that incomplete test, we didn't notice this wasn't the desired behavior. If the backend returns a 200, it's assumed that the errors aren't more important than the correct `CustomerInfo`. The new implementation simply logs the attribute errors (which is now tested), and returns the `CustomerInfo`.
… if the response was successful (#1778) Fixes [SDKONCALL-62]. The behavior in version 3.x was that we would return a `CustomerInfo` AND the attribute errors. [The test in that version](https://github.com/RevenueCat/purchases-ios/blob/3.14.2/PurchasesTests/SubscriberAttributes/BackendSubscriberAttributesTests.swift#L370-L404) however only verified that the error was returned. When we converted `(Value, Error)` callbacks to using `Result` (#1387), I assumed this was an error. Because of that incomplete test, we didn't notice this wasn't the desired behavior. If the backend returns a 200, it's assumed that the errors aren't more important than the correct `CustomerInfo`. The new implementation simply logs the attribute errors (which is now tested), and returns the `CustomerInfo`: ``` 😿‼️ One or more of the attributes sent could not be saved. ["$email": "email is not in valid format"] ``` [SDKONCALL-62]: https://revenuecats.atlassian.net/browse/SDKONCALL-62?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Currently pretty much all closures in the codebase use
(Value?, Error?), which is a leftover from Objective-C days. These can represent 4 different cases:Out of those 4, only 2 are really valid, which meant that a lot of code was spent handling the other 2, either creating errors (like
UnexpectedBackendResponseSubErrorCode.loginMissingResponse) that weren't possible, or hard to follow code because both a value and an error were possible.In tests, too, the mocks were initially set up to produce no value and no error, which were in many cases triggering code paths that weren't legal with real code.
This change removes this possibility all together by ensuring at compile time, that one of the two is always present.
This is step 1 in a series of refactors.
HTTPClientand out of the operations, making the operations a lot simpler.HTTPClientto return aResponse: Decodableinstead of[String: Any], moving more of the response serialization logic toHTTPClient, and leaving the operations to only focus on the "happy path".