-
Notifications
You must be signed in to change notification settings - Fork 24
test: fix tests to run in airplane mode (offline) #102
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
Conversation
mwbrooks
left a comment
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.
Sharing a few learnings and decisions that I made along this flight path
|
|
||
| // URLChecker returns url if its status code is 200, otherwise returns empty string | ||
| func URLChecker(httpClient slackdeps.HTTPClient, url string) string { | ||
| resp, err := httpClient.Get(url) |
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.
note: A future PR should update this to .Head. This will require adding Head to our HTTPClient interface as well.
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.
@mwbrooks Great thinking! For templates that vendor dependencies I imagine this being an expensive operation and I fear rate limits might've been blocking downloads in CI sometimes as well π
It's so good to know of this follow up! π§ β¨
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 fear rate limits might've been blocking downloads in CI sometimes as well π
π§ Hopefully this reduces some flakiness!
| func Test_URLChecker(t *testing.T) { | ||
| url := URLChecker("https://github.com/slack-samples/deno-starter-template") | ||
| assert.Equal(t, "https://github.com/slack-samples/deno-starter-template", url, "should return url when url is valid") | ||
| tests := map[string]struct { |
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.
note: Reimplemented the tests as a table test and extended the cover. This function now has 100% test coverage. π―
internal/pkg/create/create.go
Outdated
| httpClient := slackdeps.NewHTTPClient(slackdeps.HTTPClientOptions{}) | ||
| zipFileURL := generateGitZipFileURL(httpClient, template.path, gitBranch) |
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.
note: This is the main code change. We now inject a httpClient and mock it in tests.
We could take this further by defining a clients.HTTPClient instance, but I wanted to keep these changes scoped smaller to allow us to discuss those trade-offs later.
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.
@mwbrooks So disciplined but a solid call for now!
I think it makes sense to use a shared http client that might allow specific options in calls, but I agree that discussion can wait.
FWIW this will be so fast to find as reference I hope π β¨
| zipURL = deputil.URLChecker(httpClient, masterURL) | ||
| } | ||
| } else { | ||
| zipURL = zipURL + gitBranch + ".zip" |
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.
note: Kinda funny that we never apply URLChecker to custom branches. Work for the future!
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.
@mwbrooks I was wondering of possible reason for this, but the points around how we can improve error messages for missing URLs outweigh the missing checks here I hope πͺ
|
|
||
| url = generateGitZipFileURL("https://github.com/slack-samples/deno-starter-template", "") | ||
| assert.Equal(t, "https://github.com/slack-samples/deno-starter-template/archive/refs/heads/main.zip", url, "should return zip download link with main") | ||
| tests := map[string]struct { |
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.
note: Reimplemented the tests as a table test and extended the cover. This function now has 100% test coverage. π―
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.
@mwbrooks Huge unlock for test stabilities all around here π β¨
While it'd be so surprising to me, I can imagine missing branches might cause some of the past tests to fail π
| // HTTPClient interface for http.Client. | ||
| type HTTPClient interface { | ||
| Do(req *http.Request) (*http.Response, error) | ||
| Get(url string) (*http.Response, error) | ||
| } |
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.
note: I only added what we use today, but this will need to extend to Post and Head as we use it in other areas of the code base.
| // NewHTTPClient returns an http.Client configured with HTTPClientOptions. | ||
| func NewHTTPClient(opts HTTPClientOptions) *http.Client { |
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.
note: This is taken from internal/api/httpclient.go so it's been well used and tested. I only updated the comments and style for declaring the pointer client := &http.Client{}.
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'm excited for the future refactors that bring the http logic out of api π€ β¨
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.
Same same same same same!
| // MockHTTPResponse is a helper that returns a mocked http.Response with the provided httpStatus and body. | ||
| func MockHTTPResponse(httpStatus int, body string) *http.Response { |
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.
note: This little helper makes me really happy π
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.
"net/http/httptest"
This is such an impressive package IMO! I'm glad we're making tests at the right level here π
| resWriter := httptest.NewRecorder() | ||
|
|
||
| resWriter.WriteHeader(httpStatus) | ||
| _, _ = io.WriteString(resWriter, body) |
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.
tip: After some searching, I learned that fmt.Printf(resWriter, body) and io.WriteString(resWriter, body) are both acceptable. WriteString is preferred because it's more readable and some packages such as http have optimized implementations of it.
Right now, we use both throughout our code base.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #102 +/- ##
==========================================
+ Coverage 63.37% 63.42% +0.04%
==========================================
Files 211 212 +1
Lines 22284 22308 +24
==========================================
+ Hits 14122 14148 +26
Misses 7071 7071
+ Partials 1091 1089 -2 β View full report in Codecov by Sentry. π New features to boost your workflow:
|
zimeg
left a comment
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.
@mwbrooks Hacking in the cloud can callout such interesting problems! π€ βοΈ
All of these changes move the code to such a good place for enhanced tests and all related improvements to follow. I'm glad to find tests running a bit faster and more stable too, and the cases provided are working well during review at this moment π
|
|
||
| // URLChecker returns url if its status code is 200, otherwise returns empty string | ||
| func URLChecker(httpClient slackdeps.HTTPClient, url string) string { | ||
| resp, err := httpClient.Get(url) |
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.
@mwbrooks Great thinking! For templates that vendor dependencies I imagine this being an expensive operation and I fear rate limits might've been blocking downloads in CI sometimes as well π
It's so good to know of this follow up! π§ β¨
| // Create mocks | ||
| httpClientMock := &slackdeps.HTTPClientMock{} | ||
| tt.setupHTTPClientMock(httpClientMock) | ||
|
|
||
| url = URLChecker("fake_url") | ||
| assert.Equal(t, "", url, "should return empty string when url is invalid") | ||
| // Execute | ||
| url := URLChecker(httpClientMock, tt.url) |
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.
The table tests, mock setup, and injected dependencies are so good to see here! π€©
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.
The trio dream team!
internal/deputil/url.go
Outdated
| // URLChecker returns url if it's status code is 200, otherwise returns empty string | ||
| func URLChecker(url string) string { | ||
| resp, err := http.Get(url) | ||
| "github.com/slackapi/slack-cli/internal/slackdeps" |
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.
π No blocker but I can imagine we move custom HTTP setups - including this file - and interface implementations into a separate internal package?
| "github.com/slackapi/slack-cli/internal/slackdeps" | |
| "github.com/slackapi/slack-cli/internal/slackhttp" |
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 was thinking the same thing. The use of *util or *deps usually smells of opportunity for improvement. This seems like a good step forward.
| "github.com/slackapi/slack-cli/internal/slackdeps" | ||
| ) | ||
|
|
||
| // URLChecker returns url if its status code is 200, otherwise returns empty string |
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'm not sure if this was included in the followup plans, but we might also want to return an error here for continued improvements to error messages?
It's not clear to me if .Get returns separate errors for the specific error cases:
- Internet broke
- URL is missing
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 had the same thought but let it slide. I agree, let's follow-up with returning an error from URLChecker ππ»
internal/pkg/create/create.go
Outdated
| httpClient := slackdeps.NewHTTPClient(slackdeps.HTTPClientOptions{}) | ||
| zipFileURL := generateGitZipFileURL(httpClient, template.path, gitBranch) |
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.
@mwbrooks So disciplined but a solid call for now!
I think it makes sense to use a shared http client that might allow specific options in calls, but I agree that discussion can wait.
FWIW this will be so fast to find as reference I hope π β¨
|
|
||
| url = generateGitZipFileURL("https://github.com/slack-samples/deno-starter-template", "") | ||
| assert.Equal(t, "https://github.com/slack-samples/deno-starter-template/archive/refs/heads/main.zip", url, "should return zip download link with main") | ||
| tests := map[string]struct { |
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.
@mwbrooks Huge unlock for test stabilities all around here π β¨
While it'd be so surprising to me, I can imagine missing branches might cause some of the past tests to fail π
internal/pkg/create/create_test.go
Outdated
| res := slackdeps.MockHTTPResponse(http.StatusOK, "OK") | ||
| httpClientMock.On("Get", "https://github.com/slack-samples/deno-starter-template/archive/refs/heads/main.zip").Return(nil, fmt.Errorf("HttpClient error")) | ||
| httpClientMock.On("Get", "https://github.com/slack-samples/deno-starter-template/archive/refs/heads/master.zip").Return(res, nil) |
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.
π§ͺ β¨
| // NewHTTPClient returns an http.Client configured with HTTPClientOptions. | ||
| func NewHTTPClient(opts HTTPClientOptions) *http.Client { |
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'm excited for the future refactors that bring the http logic out of api π€ β¨
| // MockHTTPResponse is a helper that returns a mocked http.Response with the provided httpStatus and body. | ||
| func MockHTTPResponse(httpStatus int, body string) *http.Response { |
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.
"net/http/httptest"
This is such an impressive package IMO! I'm glad we're making tests at the right level here π
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.
π Edit to an above comment!
I didn't realize these files were new, but we might take this PR as a chance to start a separate package:
internal/slackhttp/http.go
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.
Good point! I was going to hold off for a new PR, but since these files are new, we might as well do it now. I'll start the refactor now...
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.
Commit 96adc92 refactors this PR to use a separate packaged:
internal/slackhttp/http.go
internal/slackhttp/http_mock.go
internal/slackhttp/http_test.go
|
Thanks for the great review @zimeg! ππ» And for suggesting that we refactor the |
Summary
This pull request updates our tests to not require a network connection. Now our tests run β‘ slightly faster and π work offline.
A note about the
HTTPClient:HTTPClientOptionsdefines a wide range of options available.package api.HTTPClientwith the unsupported options.A few opportunities for follow-up pull requests:
URLChecker(...)should be updated to usehttpClient.Headinstead ofhttpClient.GetURLChecker(...)and prevent a double-download of a template from happeninggenerateGitZipFileURL(...)should be updated to callURLChecker(...)for custom branches as wellpackage updateshould be updated to use the newHTTPClient interfaceHTTPClient interfacewas borrowed from thepackage apiimplementationpackage api,package update, andpackage projectto use the newHTTPClient interfaceHTTPClient interfacein other areas of the code base that use a customhttp.ClientHTTPClientimplementation throughout the code baseReviewers
Test Offline Mode
Test Updates to
generateGitZipFileURLand ``URLChecker`Requirements