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

[Hack Week] ApiFaker: introduce new module and logic #13052

Open
wants to merge 20 commits into
base: trunk
Choose a base branch
from

Conversation

hichamboushaba
Copy link
Member

@hichamboushaba hichamboushaba commented Dec 3, 2024

Description

As part of my Hack Week, I'm bringing back the ApiFaker project paqN3M-SI-p2, and I'm splitting the feature into multiple PRs.

This PR adds the apifaker gradle module, and with it the following changes:

  1. A Room database to hold the list of faked endpoints.
  2. The EndpointProcessor class is responsible for matching and finding the faked endpoints.
  3. The OkHttp interceptor that replaces the response when needed.

Steps to reproduce

Code review and green CI should be enough.

But if you prefer to do some tests, you can use the following PR #13070

  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

Reviewer (or Author, in the case of optional code reviews):

Please make sure these conditions are met before approving the PR, or request changes if the PR needs improvement:

  • The PR is small and has a clear, single focus, or a valid explanation is provided in the description. If needed, please request to split it into smaller PRs.
  • Ensure Adequate Unit Test Coverage: The changes are reasonably covered by unit tests or an explanation is provided in the PR description.
  • Manual Testing: The author listed all the tests they ran, including smoke tests when needed (e.g., for refactorings). The reviewer confirmed that the PR works as expected on big (tablet) and small (phone) in case of UI changes, and no regressions are added.

@hichamboushaba hichamboushaba added type: task An internally driven task. milestone-not-required Prevents the Milestone check to fail the build labels Dec 3, 2024
@dangermattic
Copy link
Collaborator

dangermattic commented Dec 3, 2024

1 Error
🚫 This PR is tagged with status: do not merge label(s).
4 Warnings
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
⚠️ Class ApiFakerInterceptor is missing tests, but unit-tests-exemption label was set to ignore this.
⚠️ Class ApiFakerDatabase is missing tests, but unit-tests-exemption label was set to ignore this.
⚠️ Class EndpointTypeConverter is missing tests, but unit-tests-exemption label was set to ignore this.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Dec 3, 2024

📲 You can test the changes from this Pull Request in WooCommerce-Wear Android by scanning the QR code below to install the corresponding build.
App Name WooCommerce-Wear Android
Platform⌚️ Wear OS
FlavorJalapeno
Build TypeDebug
Commit3d1c8c9
Direct Downloadwoocommerce-wear-prototype-build-pr13052-3d1c8c9.apk

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Dec 3, 2024

📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App Name WooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Commit3d1c8c9
Direct Downloadwoocommerce-prototype-build-pr13052-3d1c8c9.apk

@codecov-commenter
Copy link

codecov-commenter commented Dec 3, 2024

Codecov Report

Attention: Patch coverage is 0% with 126 lines in your changes missing coverage. Please review.

Project coverage is 40.26%. Comparing base (c944700) to head (3d1c8c9).
Report is 2 commits behind head on task/api-faker-prep.

Files with missing lines Patch % Lines
.../woocommerce/android/apifaker/EndpointProcessor.kt 0.00% 63 Missing ⚠️
...oocommerce/android/apifaker/ApiFakerInterceptor.kt 0.00% 19 Missing ⚠️
...merce/android/apifaker/db/EndpointTypeConverter.kt 0.00% 10 Missing ⚠️
...oocommerce/android/apifaker/db/ApiFakerDatabase.kt 0.00% 8 Missing ⚠️
...com/woocommerce/android/apifaker/models/Request.kt 0.00% 7 Missing ⚠️
...com/woocommerce/android/apifaker/db/EndpointDao.kt 0.00% 5 Missing ⚠️
...om/woocommerce/android/apifaker/models/Response.kt 0.00% 5 Missing ⚠️
...commerce/android/apifaker/models/MockedEndpoint.kt 0.00% 3 Missing ⚠️
...com/woocommerce/android/apifaker/models/ApiType.kt 0.00% 2 Missing ⚠️
.../woocommerce/android/apifaker/models/HttpMethod.kt 0.00% 2 Missing ⚠️
... and 1 more
Additional details and impacted files
@@                    Coverage Diff                    @@
##             task/api-faker-prep   #13052      +/-   ##
=========================================================
- Coverage                  40.33%   40.26%   -0.07%     
  Complexity                  6154     6154              
=========================================================
  Files                       1287     1298      +11     
  Lines                      74121    74247     +126     
  Branches                   10140    10157      +17     
=========================================================
  Hits                       29895    29895              
- Misses                     41634    41760     +126     
  Partials                    2592     2592              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hichamboushaba hichamboushaba force-pushed the task/api-faker-1 branch 2 times, most recently from 185c0cb to c5117e3 Compare December 4, 2024 13:43
This commit adds logic for extracting the method from the path, and also the logic to wrap the body using `data` property when needed
@hichamboushaba hichamboushaba changed the title [Hack Week] Api Faker: introduce new module and logic [Hack Week] ApiFaker: introduce new module and logic Dec 4, 2024
@hichamboushaba hichamboushaba removed the milestone-not-required Prevents the Milestone check to fail the build label Dec 5, 2024
@hichamboushaba hichamboushaba added this to the 21.3 milestone Dec 5, 2024
private val jsonObjectProvider: JSONObjectProvider
) {
fun fakeRequestIfNeeded(request: Request): Response? {
// TODO match against method and query parameters too
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 TODO here is not accurate, as part of this PR we are already matching against the HTTP method, the comment is fixed in next PRs, to avoid issues with merge conflicts, let's ignore it here.

val (path, method, body) = if (method == "GET") {
Triple(
url.queryParameter("path")!!.substringBefore("&"),
url.queryParameter("_method") ?: "GET",
Copy link
Member Author

Choose a reason for hiding this comment

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

For Jetpack Tunneled GET requests, the HTTP method is sent as a query parameter _method, see https://github.com/wordpress-mobile/WordPress-FluxC-Android/blob/64479b5648444c945dccae776f596daf192f4b87/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/jetpacktunnel/JetpackTunnelGsonRequest.kt#L249

This will generally be always GET, but to avoid hardcoding it, I'm reading it from the parameter when it exists.

val jsonObject = jsonObjectProvider.parseString(originalBody)
val pathParts = jsonObject.getString("path").split("&")
val path = pathParts[0]
val method = pathParts.firstOrNull { it.startsWith("_method") }?.split("=")?.getOrNull(1) ?: "POST"
Copy link
Member Author

Choose a reason for hiding this comment

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

url.encodedPath.trimEnd('/').matches(Regex(JETPACK_TUNNEL_REGEX)) &&
!startsWith("{\"data\":")
) {
"{\"data\": $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.

Jetpack Tunnel sends responses wrapped in a data property, this logic makes it easier for faking those without the wrap, which should work for both site credentials login and Jetpack login.

Copy link
Contributor

Choose a reason for hiding this comment

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

This brings up the same question from above. What if we don't want to have JSON formatted body with data { } as the root of if? Like the example mentioned above when simulating an error that returns html?

Copy link
Member Author

Choose a reason for hiding this comment

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

As said in the other thread, I would love to keep this as is right now, changing it would require more testing.

Depending on the developer's needs, we can revisit this in the future.

@@ -2,7 +2,7 @@

echo "--- 🧪 Testing"
set +e
./gradlew testJalapenoDebugUnitTest lib:cardreader:testDebugUnitTest lib:iap:testDebugUnitTest
./gradlew testJalapenoDebugUnitTest testDebugUnitTest
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 allows running unit tests for all modules instead of having to list all of them

@hichamboushaba hichamboushaba added the status: do not merge Dependent on another PR, ready for review but not ready for merge. label Dec 5, 2024
Zulu-1540

This comment was marked as spam.

Zulu-1540

This comment was marked as spam.

@hichamboushaba hichamboushaba marked this pull request as ready for review December 5, 2024 11:16
@JorgeMucientes JorgeMucientes self-assigned this Dec 6, 2024
Base automatically changed from task/api-faker-prep to trunk December 6, 2024 16:47
Copy link
Contributor

@JorgeMucientes JorgeMucientes left a comment

Choose a reason for hiding this comment

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

Great job @hichamboushaba, looking forward to see this in action in subsequent PRs. Code looks clean and well organised. I just left a couple minor questions, but nothing blocking.

Comment on lines +13 to +19
includeGroup "org.wordpress"
includeGroup "org.wordpress.fluxc"
includeGroup "org.wordpress.fluxc.plugins"
includeGroup "org.wordpress.wellsql"
includeGroup "org.wordpress.mediapicker"
includeGroup "com.automattic"
includeGroup "com.automattic.tracks"
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need all these? I see you only add FluxC as dependency (which also makes sense since it's where all the requests happen). But why include tracks, mediapicker, wellsql, wordpress ?

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 don't like this either, but I tried to just keep the same setup as what we have in the other modules, personally I think this code should be in the root build.gradle file, or better in a convention plugin, but since this is not the setup we have yet, I prefer to keep the same setup as the other modules here, as the includeGroup statements won't have any impact here, they are just used for defining the repo itself.

.protocol(Protocol.HTTP_1_1)
.message("Fake Response")
.code(fakeResponse.statusCode)
// TODO check if it's safe to always use JSON as the content type
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the body of the faked request is anything the user inputs from the app, wouldn't it make more sense to leave MediaType null? For example sometimes we get and html body for API errors. Haven't tested yet, but what happens if we keep this application/json content type and enter non json 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.

This is a good point; but for now I'll keep it like this, my rationale for hardcording application/json here is that all of the API requests we have in the app use JSON responses, the HTML errors are an exception that can be caused by a specific site setup that returns an error before the site handles the error.

what happens if we keep this application/json content type and enter non json body?

It should work as you expect from the app, the upper layer will try to deserialize the content, and would fail, then return a JSON error, this is what happens when we receive HTML instead of JSON (for example this case: p1723565504566789/1720009283.216679-slack-C6H8C3G23 API org.json.JSONException: Value <!DOCTYPE of type java.lang.String cannot be converted to JSONObject)

url.encodedPath.trimEnd('/').matches(Regex(JETPACK_TUNNEL_REGEX)) &&
!startsWith("{\"data\":")
) {
"{\"data\": $this}"
Copy link
Contributor

Choose a reason for hiding this comment

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

This brings up the same question from above. What if we don't want to have JSON formatted body with data { } as the root of if? Like the example mentioned above when simulating an error that returns html?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: do not merge Dependent on another PR, ready for review but not ready for merge. type: task An internally driven task. unit-tests-exemption
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants