-
Notifications
You must be signed in to change notification settings - Fork 2
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
Feat(Site Launch): Mock calls to Amplify #704
Feat(Site Launch): Mock calls to Amplify #704
Conversation
* this directly within the Isomer CMS. | ||
* todo: push this check into a queue-like system when integrating this with cms | ||
*/ | ||
await new Promise((resolve) => setTimeout(resolve, 90000)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to block the main thread for 90s right?
Seems like "todo: push this check into a queue-like system when integrating this with cms" is crucial for integration with CMS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm correct me if I am wrong, but set setTimeout does not block the main thread right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kishore03109 hmm, you can do a simple test on console -
async function test() { if(true) { await new Promise((resolve) => setTimeout(resolve, 2000)) } console.log("DONE") }
"DONE" wont be printed until the promise is done. you might want to double check the actual behaviour on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm wait isnt that the point? I want to only resolve this after 90 seconds to give Amplify enough time to generate the DNS results
eg for the code
async function test() {
await new Promise((resolve) => setTimeout(resolve, 10000))
console.log("PROMISE DONE")
}
console.log("STARTING")
test()
console.log("ENDED")
in the console log, we would see:
STARTING
ENDED
PROMISE DONE
thus the other functions in the thread will not be blocked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
think the confusion stems from the await
- if you await
in an async function:
const x = await work()
doOtherWork()
const y = x
the main thread is free to continue on until the result of the await
-ed promise is used (ie, const y = x
). however, because we await
here, i think we'll pause execution for 90s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kishore03109 if you added await
on test:
console.log("STARTING")
await test()
console.log("ENDED")
it will wait for promise to be done first
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@harishv7, for ease of mind, I have made the main caller (this.handleSiteLaunchResults
) function non-blocking (ie, removing the await
), since we technically can run this async after returning the 200 response. However, this means that any error thrown by this will not be caught by handleSiteLaunchFormRequest
. Therefore, I am wrapping a try catch around and logging the error to any unintended effects of some unexpected error. Ideally, during an error, this would return something to the user, but I think this can be revisited when we are integrating the features with the frontend!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this is non-blocking - top-most caller is handleSiteLaunchFormRequest
in formsgSiteLaunch
and it calls this.handleSiteLaunchResults(formResponses, submissionId)
without await
(which calls this method later on)
@kishore03109 maybe do you want to have an actual dev config like "MOCK_AMPLIFY_CALLS = true or false" which gives devs more "conscious" control over this? If you feel this could extend beyond in future as the launch service develops, you might want to have a great detail of specificity apart from |
Hey thanks for the suggestion, think this is a cleaner solution, updated to use an env var. Regarding greater detail of specificity, I think we can revisit this as the service develops, I think this is ok for now! |
Sounds good, actually If there are a lot of mocking calls required, then a more generic env like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generally when we mock, we want to preserve the same function contract to ensure that calling code behaves as intended. moreover, because of how we use DI, we are able to substitute out the inner client (amplifyClient
) for a mocked one, which ensures that our calling code is never aware of the fact that it's calling a fake client.
in here, the API contract is changed, which might lead to callers not upholding the contract and test logic is intermingled w/ actual biz logic, which seems a little messy.
wdyt about creating a MOCK_AMPLIFY_CLIENT
that returns const data and passing in the appropriate client based on teh result of isTest
?
* this directly within the Isomer CMS. | ||
* todo: push this check into a queue-like system when integrating this with cms | ||
*/ | ||
await new Promise((resolve) => setTimeout(resolve, 90000)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
think the confusion stems from the await
- if you await
in an async function:
const x = await work()
doOtherWork()
const y = x
the main thread is free to continue on until the result of the await
-ed promise is used (ie, const y = x
). however, because we await
here, i think we'll pause execution for 90s
Yup, you are right to say that this is not ideal where test logic is intermingled with actual biz logic, and seems messy. We primarily have two main calls to Amplify here:
Originally, when actually calling Amplify, the Say we store this state in some static variable To be clear, changing the API contract here is a concession to give more power to the consumer to give a less ambiguous, clearer response of what the Amplify client will return if the values are not mocked. Does this reasoning make sense? |
hmm i don't understand what you're trying to say here - this only applies if we ever hit the amplify endpoint, which we're not doing (by design). so why can't we just give a constant result of the expected shape back to the caller? we don't have to store anything/compute anything since the code in |
the issue stems from the fact that the result of super high level code to demonstrate this: when actually calling Amplify:
When mocking When mocking To give more flexibility to the caller, the suggested solution proposes adding the prop |
@seaerchin as discussed offline, have modified to retain the original API contract. The state is now stored in |
mocking calls to amplify now
a1fa956
to
cd97d22
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong method signature for InfraService.getDomainAssociationRecord
call; minor comments otherwise
* this directly within the Isomer CMS. | ||
* todo: push this check into a queue-like system when integrating this with cms | ||
*/ | ||
await new Promise((resolve) => setTimeout(resolve, 90000)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this is non-blocking - top-most caller is handleSiteLaunchFormRequest
in formsgSiteLaunch
and it calls this.handleSiteLaunchResults(formResponses, submissionId)
without await
(which calls this method later on)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
Problem
During development, the CreateDomainAssociation calls are rate-limited to 10 per hour. This low rate limit can cause issues during actual site launches that may affect operations.
Solution
To avoid impeding with the actual infrastructure rate limits, this PR mocks all calls to CreateDomainAssociation and GetDomainAssociation in LaunchesService. Only staging, vapt, and prod environments should be making actual calls to Amplify. Additionally, since we are mocking Amplify calls now, we no longer need to wait for 90 seconds after creating domain associations for Amplify to generate the required values.
The mocked objects may seem verbose, but they closely resemble the actual values received when calling Amplify.
Breaking Changes
There are no breaking changes.
Reviewer Notes
I am concerned that future devs working around site launch feature might be confused why they are not able to create domain associations during development. Not too sure if there is a way to highlight clearly in code that the calls are mocks apart from the proposed solution of already naming them
mockCreateDomainAssociationOutput
andmockCreateDomainAssociationOutput
inLaunchesClient
. Open to feedback on this!Tests
To mock form responses, you can call the following code directly from server.js:
After mocking a site launch form submission, the launches and redirections in the database are populated, and emails are sent as expected.
Deploy Notes
New environment variables:
MOCK_AMPLIFY_CREATE_DOMAIN_CALLS
: mocking amplify calls so as to not impede with operationsValues in 1pw prod and staging env vars are also updated. TLDR; MOCK_AMPLIFY_CREATE_DOMAIN_CALLS is
true
in prod and staging env,false
in local dev.