From c51e097aadff89fbb5cbf882b8832be3516c6962 Mon Sep 17 00:00:00 2001 From: Christian Stewart Date: Wed, 10 Jul 2024 13:15:26 -0700 Subject: [PATCH] fix: handle nil opts and add webtests flag Signed-off-by: Christian Stewart --- doc/WEB_TESTS.md | 59 +++++++++++++++++++++++ http/log/fetch/fetch_js.go | 7 ++- http/log/fetch/fetch_test.go | 90 ++++++++++++++++++++++++++++++++++++ util/js-fetch/fetch.go | 5 +- 4 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 doc/WEB_TESTS.md create mode 100644 http/log/fetch/fetch_test.go diff --git a/doc/WEB_TESTS.md b/doc/WEB_TESTS.md new file mode 100644 index 00000000..fc2f4936 --- /dev/null +++ b/doc/WEB_TESTS.md @@ -0,0 +1,59 @@ +# Web Tests + +Some tests in this project require internet access to perform HTTP requests to external services. These tests are controlled by the `webtests` build tag. + +## Running Web Tests + +To run tests that include web requests, use the following command: + +``` +go test -tags webtests ./... +``` + +This will enable the `webtests` build tag and run all tests, including those that make HTTP requests to external services. + +### WebAssembly + +This package can be tested in a browser environment using [`wasmbrowsertest`](https://github.com/agnivade/wasmbrowsertest). + +1. Install `wasmbrowsertest`: + ```bash + go install github.com/agnivade/wasmbrowsertest@latest + ``` + +2. Rename the `wasmbrowsertest` binary to `go_js_wasm_exec`: + ```bash + mv $(go env GOPATH)/bin/wasmbrowsertest $(go env GOPATH)/bin/go_js_wasm_exec + ``` + +3. Run the tests with the `js` GOOS and `wasm` GOARCH: + ```bash + GOOS=js GOARCH=wasm go test -tags "webtests" -v ./... + ``` + +This will compile the tests to WebAssembly and run them in a headless browser environment. + + +## Skipping Web Tests + +By default, tests that require internet access are not run. To run all other tests without the web tests, simply run: + +``` +go test ./... +``` + +This will skip any tests that have the `webtests` build tag. + +## Writing Web Tests + +When writing tests that require internet access or make HTTP requests to external services, make sure to add the `webtests` build tag to the test file. For example: + +```go +//go:build js && webtests + +package mypackage + +// Test code here +``` + +This ensures that these tests are only run when explicitly requested with the `webtests` build tag. diff --git a/http/log/fetch/fetch_js.go b/http/log/fetch/fetch_js.go index 507c6079..52787052 100644 --- a/http/log/fetch/fetch_js.go +++ b/http/log/fetch/fetch_js.go @@ -11,6 +11,7 @@ import ( "github.com/sirupsen/logrus" ) +// logHeaders is the set of headers to attach to the logger as fields. var logHeaders = []string{"range", "content-range", "content-type", "content-length", "accept"} // Fetch uses the JS Fetch API to make requests with logging. @@ -50,7 +51,11 @@ func Fetch(le *logrus.Entry, url string, opts *fetch.Opts, verbose bool) (*fetch duration := time.Since(startTime) if le != nil { - fields := make(logrus.Fields, 2+len(resp.Headers)) + mapSize := 1 + if resp != nil { + mapSize += 1 + min(len(resp.Headers), len(logHeaders)) + } + fields := make(logrus.Fields, mapSize) fields["dur"] = duration.String() if resp != nil { fields["status"] = resp.Status diff --git a/http/log/fetch/fetch_test.go b/http/log/fetch/fetch_test.go new file mode 100644 index 00000000..454a1567 --- /dev/null +++ b/http/log/fetch/fetch_test.go @@ -0,0 +1,90 @@ +//go:build js && webtests + +package httplog_fetch + +import ( + "bytes" + "net/http" + "testing" + + fetch "github.com/aperturerobotics/bifrost/util/js-fetch" + "github.com/sirupsen/logrus" +) + +func TestFetch(t *testing.T) { + // Create a logger + logger := logrus.New() + logger.SetLevel(logrus.DebugLevel) + + // Test cases + testCases := []struct { + name string + url string + opts *fetch.Opts + verbose bool + expectError bool + }{ + { + name: "Successful GET request", + url: "https://httpbin.org/get", + verbose: true, + }, + { + name: "POST request with headers", + url: "https://httpbin.org/post", + opts: &fetch.Opts{ + Method: "POST", + Headers: map[string]string{ + "Content-Type": "application/json", + }, + Body: bytes.NewReader([]byte(`{"test": "data"}`)), + }, + verbose: true, + }, + { + name: "Non-existent URL", + url: "https://thisurldoesnotexist.example.com", + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + le := logger.WithField("test", tc.name) + + resp, err := Fetch(le, tc.url, tc.opts, tc.verbose) + + if tc.expectError { + if err == nil { + t.Errorf("Expected an error, but got nil") + } + if resp != nil { + t.Errorf("Expected nil response, but got %v", resp) + } + } else { + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if resp == nil { + t.Fatalf("Expected non-nil response, but got nil") + } + if resp.Status != http.StatusOK { + t.Errorf("Expected status %d, but got %d", http.StatusOK, resp.Status) + } + } + }) + } +} + +func TestFetchWithNilLogger(t *testing.T) { + resp, err := Fetch(nil, "https://httpbin.org/get", nil, false) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if resp == nil { + t.Fatal("Expected non-nil response, but got nil") + } + if resp.Status != http.StatusOK { + t.Errorf("Expected status %d, but got %d", http.StatusOK, resp.Status) + } +} diff --git a/util/js-fetch/fetch.go b/util/js-fetch/fetch.go index 5475865b..3ebf5b47 100644 --- a/util/js-fetch/fetch.go +++ b/util/js-fetch/fetch.go @@ -97,7 +97,7 @@ func Fetch(url string, opts *Opts) (*Response, error) { e error } ch := make(chan *fetchResponse) - if opts.Signal != nil { + if opts != nil && opts.Signal != nil { controller := js.Global().Get("AbortController").New() signal := controller.Get("signal") optsMap["signal"] = signal @@ -181,6 +181,9 @@ func Fetch(url string, opts *Opts) (*Response, error) { // oof. func mapOpts(opts *Opts) (map[string]interface{}, error) { mp := map[string]interface{}{} + if opts == nil { + return mp, nil + } if opts.Method != "" { mp["method"] = opts.Method