From d53109f4dc76a783ae7537a205609282155109ce Mon Sep 17 00:00:00 2001 From: frag223 Date: Sat, 29 Nov 2025 21:48:15 +1100 Subject: [PATCH 01/15] update deps + tools --- .gitignore | 3 +++ .golangci.yaml | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ scripts/lints.mk | 1 - scripts/tools.mk | 3 +-- 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 .golangci.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbd6a33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +.DS_Store +coverage* \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..e1cb1ca --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,49 @@ +version: "2" + +linters: + enable: + - cyclop + - nestif + - bodyclose + - iface + - gosec + - errcheck + - errchkjson + - errname + - errorlint + - exptostd + - fatcontext + - forbidigo + - forcetypeassert + - govet + - importas + - ireturn + - perfsprint + - recvcheck + - sloglint + - staticcheck + - unparam + - unused + - wastedassign + exclusions: + generated: lax + + rules: + # Exclude some linters from running on tests files. + - path: _test\.go + + linters: + - gocyclo + - cyclop + - dupl + - gosec + - forbidigo + - exhaustruct + + settings: + depguard: + rules: + main: + list-mode: lax + + diff --git a/scripts/lints.mk b/scripts/lints.mk index 8673a95..1e73a33 100644 --- a/scripts/lints.mk +++ b/scripts/lints.mk @@ -7,7 +7,6 @@ lint: ## Lint tools golangci-lint run ./... scan: ## run golang security scan - gosec ./... govulncheck ./... trivy: ## run trivy scan diff --git a/scripts/tools.mk b/scripts/tools.mk index aa9ffd6..f2554b2 100644 --- a/scripts/tools.mk +++ b/scripts/tools.mk @@ -6,8 +6,7 @@ tools-all: tools-dev tools-scan ## Get all tools for development tools-scan: ## get all the tools required go install golang.org/x/vuln/cmd/govulncheck@latest - go install github.com/securego/gosec/v2/cmd/gosec@latest - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.2.2 tools-dev: ## Dev specific tooling From 3258434f7824c2dce923933535af222070875738 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sat, 29 Nov 2025 22:06:12 +1100 Subject: [PATCH 02/15] update --- .golangci.yaml | 6 ++++++ fetch.go | 28 ++++++++++------------------ go.mod | 2 +- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index e1cb1ca..b8e9474 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -38,8 +38,14 @@ linters: - dupl - gosec - forbidigo + - bodyclose - exhaustruct + - path: dad_jokes.go + linters: + - forbidigo + - errchkjson + settings: depguard: rules: diff --git a/fetch.go b/fetch.go index 02db9e6..361e08f 100644 --- a/fetch.go +++ b/fetch.go @@ -2,10 +2,9 @@ package fetch import ( "errors" - "fmt" "io" + "log" "net/http" - "sync" "time" ) @@ -55,7 +54,7 @@ func (a *Client) Patch(url string, body io.Reader, headers map[string]string) (* return a.do(url, http.MethodPatch, body, headers) } -// do - make http call with provided configuration +// do - make http call with the provided configuration func (a *Client) do(url string, method string, body io.Reader, headers map[string]string) (*http.Response, error) { if a.RetryStrategy == nil { return call(url, method, body, a.Client, headers, a.DefaultHeaders) @@ -73,23 +72,16 @@ func callWithRetry(url string, method string, body io.Reader, client httpClient, return resp, ErrNoValidRetryStrategy } - waitGroup := sync.WaitGroup{} - waitGroup.Add(1) - - go func() { - for _, retryWait := range retryStrategy { - resp, err = call(url, method, body, client, headers...) - if err == nil || !isRecoverable(err) { - break - } - - fmt.Printf("%s: http %s request error [%s], will retry in [%s]", logPrefix, method, err, retryWait) - time.Sleep(retryWait) + for _, retryWait := range retryStrategy { + resp, err = call(url, method, body, client, headers...) + if err == nil || !isRecoverable(err) { + break } - waitGroup.Done() - }() - waitGroup.Wait() + log.Printf("%s: http %s request error [%s], will retry in [%s]", logPrefix, method, err, retryWait) + time.Sleep(retryWait) + } + return resp, err } diff --git a/go.mod b/go.mod index e51e99c..5200d2b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/code-gorilla-au/fetch -go 1.22.4 +go 1.25.3 require github.com/code-gorilla-au/odize v1.3.4 From 0196a28fa4eee0480590109aae9f4f72250b2259 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sat, 29 Nov 2025 22:12:50 +1100 Subject: [PATCH 03/15] adding watch --- scripts/tests.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/tests.mk b/scripts/tests.mk index 4f6c1c7..a3b3f4e 100644 --- a/scripts/tests.mk +++ b/scripts/tests.mk @@ -10,6 +10,9 @@ test: test-unit ## Run all tests test-unit: ## Run unit tests go test -coverprofile $(COVER_OUTPUT_RAW) --short -cover -failfast ./... +test-watch: ## Run tests in watch mode + gow test -coverprofile $(COVER_OUTPUT_RAW) --short -cover -failfast ./... + test-cover: ## generate html coverage report + open go tool cover -html=$(COVER_OUTPUT_RAW) -o $(COVER_OUTPUT_HTML) open coverage.html From ee1f2993aa3667962357156b66231fcdf72460ee Mon Sep 17 00:00:00 2001 From: frag223 Date: Sat, 29 Nov 2025 22:34:18 +1100 Subject: [PATCH 04/15] fixing tests --- .covignore | 1 + fetch.go | 12 +- fetch_test.go | 269 --------------------------------------- mocks.go => mocks.gen.go | 0 scripts/tests.mk | 12 +- 5 files changed, 13 insertions(+), 281 deletions(-) create mode 100644 .covignore rename mocks.go => mocks.gen.go (100%) diff --git a/.covignore b/.covignore new file mode 100644 index 0000000..87c6170 --- /dev/null +++ b/.covignore @@ -0,0 +1 @@ +.gen.go \ No newline at end of file diff --git a/fetch.go b/fetch.go index 361e08f..38bd3a6 100644 --- a/fetch.go +++ b/fetch.go @@ -57,13 +57,13 @@ func (a *Client) Patch(url string, body io.Reader, headers map[string]string) (* // do - make http call with the provided configuration func (a *Client) do(url string, method string, body io.Reader, headers map[string]string) (*http.Response, error) { if a.RetryStrategy == nil { - return call(url, method, body, a.Client, headers, a.DefaultHeaders) + return a.call(url, method, body, headers, a.DefaultHeaders) } - return callWithRetry(url, method, body, a.Client, a.RetryStrategy, headers, a.DefaultHeaders) + return a.callWithRetry(url, method, body, a.RetryStrategy, headers, a.DefaultHeaders) } // callWithRetry - wrap the call method with the retry strategy -func callWithRetry(url string, method string, body io.Reader, client httpClient, retryStrategy []time.Duration, headers ...map[string]string) (*http.Response, error) { +func (a *Client) callWithRetry(url string, method string, body io.Reader, retryStrategy []time.Duration, headers ...map[string]string) (*http.Response, error) { logPrefix := "fetch: callWithRetry" var resp *http.Response var err error @@ -73,7 +73,7 @@ func callWithRetry(url string, method string, body io.Reader, client httpClient, } for _, retryWait := range retryStrategy { - resp, err = call(url, method, body, client, headers...) + resp, err = a.call(url, method, body, headers...) if err == nil || !isRecoverable(err) { break } @@ -86,7 +86,7 @@ func callWithRetry(url string, method string, body io.Reader, client httpClient, } // call - creates a new HTTP request and returns an HTTP response -func call(url string, method string, body io.Reader, client httpClient, headers ...map[string]string) (*http.Response, error) { +func (a *Client) call(url string, method string, body io.Reader, headers ...map[string]string) (*http.Response, error) { req, err := http.NewRequest(method, url, body) if err != nil { return &http.Response{}, err @@ -97,7 +97,7 @@ func call(url string, method string, body io.Reader, client httpClient, headers req.Header.Add(key, value) } - resp, err := client.Do(req) + resp, err := a.Client.Do(req) if err != nil { return resp, err } diff --git a/fetch_test.go b/fetch_test.go index ba4a586..3c38790 100644 --- a/fetch_test.go +++ b/fetch_test.go @@ -2,8 +2,6 @@ package fetch import ( "bytes" - "encoding/json" - "errors" "net/http" "testing" "time" @@ -11,273 +9,6 @@ import ( "github.com/code-gorilla-au/odize" ) -func Test_call_POST_should_not_return_error_and_match_req(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusOK, - }, - } - - expectedHeaders := map[string]string{ - "Auth": "/app/json", - } - - url := "foo" - - _, err := call(url, http.MethodPost, nil, &m, expectedHeaders) - odize.AssertNoError(t, err) - for key, value := range expectedHeaders { - odize.AssertEqual(t, m.Req.Header.Get(key), value) - } - odize.AssertEqual(t, m.Req.URL.String(), url) - odize.AssertEqual(t, m.Req.Method, http.MethodPost) - -} - -func Test_call_POST_4xx_should_return_error(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusBadRequest, - }, - } - - expectedHeaders := map[string]string{ - "Auth": "/app/json", - } - - url := "foo" - - var expectedErr *APIError - - _, err := call(url, http.MethodPost, nil, &m, expectedHeaders) - if err == nil { - t.Error("expected error, got none") - return - } - odize.AssertTrue(t, errors.As(err, &expectedErr)) -} - -func Test_call_POST_5xx_should_return_error(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusInternalServerError, - }, - } - - expectedHeaders := map[string]string{ - "Auth": "/app/json", - } - - url := "foo" - - var expectedErr *APIError - - _, err := call(url, http.MethodPost, nil, &m, expectedHeaders) - if err == nil { - t.Error("expected error, got none") - return - } - odize.AssertTrue(t, errors.As(err, &expectedErr)) -} - -func Test_call_GET_should_not_return_error_and_match_req(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusOK, - }, - } - - expectedHeaders := map[string]string{ - "Auth": "/app/json", - } - - url := "foo" - - _, err := call(url, http.MethodGet, nil, &m, expectedHeaders) - odize.AssertNoError(t, err) - for key, value := range expectedHeaders { - odize.AssertEqual(t, m.Req.Header.Get(key), value) - } - odize.AssertEqual(t, m.Req.URL.String(), url) - odize.AssertEqual(t, m.Req.Method, http.MethodGet) - -} -func Test_call_PUT_should_not_return_error_and_match_req(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusOK, - }, - } - - expectedHeaders := map[string]string{ - "Auth": "/app/json", - } - - url := "foo" - - _, err := call(url, http.MethodPut, nil, &m, expectedHeaders) - odize.AssertNoError(t, err) - for key, value := range expectedHeaders { - odize.AssertEqual(t, m.Req.Header.Get(key), value) - } - odize.AssertEqual(t, m.Req.URL.String(), url) - odize.AssertEqual(t, m.Req.Method, http.MethodPut) - -} - -func Test_call_PATCH_should_not_return_error_and_match_req(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusOK, - }, - } - - expectedHeaders := map[string]string{ - "Auth": "/app/json", - } - - url := "foo" - - _, err := call(url, http.MethodPatch, nil, &m, expectedHeaders) - odize.AssertNoError(t, err) - for key, value := range expectedHeaders { - odize.AssertEqual(t, m.Req.Header.Get(key), value) - } - odize.AssertEqual(t, m.Req.URL.String(), url) - odize.AssertEqual(t, m.Req.Method, http.MethodPatch) - -} - -func Test_call_DELETE_should_not_return_error_and_match_req(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusOK, - }, - } - - expectedHeaders := map[string]string{ - "Auth": "/app/json", - } - - url := "foo" - - _, err := call(url, http.MethodDelete, nil, &m, expectedHeaders) - odize.AssertNoError(t, err) - for key, value := range expectedHeaders { - odize.AssertEqual(t, m.Req.Header.Get(key), value) - } - odize.AssertEqual(t, m.Req.URL.String(), url) - odize.AssertEqual(t, m.Req.Method, http.MethodDelete) - -} - -func Test_call_body_should_match(t *testing.T) { - m := MockHTTPClient{} - - body := map[string]string{ - "slap": "foo", - } - - data, err := json.Marshal(&body) - odize.AssertNoError(t, err) - - url := "foo" - - _, err = call(url, http.MethodPost, bytes.NewReader(data), &m, body) - odize.AssertNoError(t, err) - - test := map[string]string{} - err = json.NewDecoder(m.Req.Body).Decode(&test) - odize.AssertNoError(t, err) - odize.AssertEqual(t, test, body) - -} - -func Test_call_should_should_return_error(t *testing.T) { - m := MockHTTPClient{ - ErrDo: true, - Err: errors.New("expected error"), - } - - expectedHeaders := map[string]string{ - "Auth": "/app/json", - } - - url := "foo" - - _, err := call(url, http.MethodPost, nil, &m, expectedHeaders) - odize.AssertTrue(t, errors.Is(err, m.Err)) -} - -func Test_callWithRetry_client_error_should_return_error(t *testing.T) { - m := MockHTTPClient{ - ErrDo: true, - Err: errors.New("expected error"), - } - - _, err := callWithRetry("", http.MethodPost, nil, &m, []time.Duration{1 * time.Nanosecond}) - odize.AssertTrue(t, errors.Is(err, m.Err)) -} - -func Test_callWithRetry_4xx_client_error_should_return_error(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusBadRequest, - }, - } - var apiErr *APIError - _, err := callWithRetry("", http.MethodPost, nil, &m, []time.Duration{1 * time.Nanosecond}) - odize.AssertTrue(t, errors.As(err, &apiErr)) - odize.AssertEqual(t, 1, m.Retries) -} - -func Test_callWithRetry_5xx_client_error_retry_and_should_return_error(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - StatusCode: http.StatusInternalServerError, - }, - } - var apiErr *APIError - _, err := callWithRetry("", http.MethodPost, nil, &m, []time.Duration{1 * time.Nanosecond, 1 * time.Nanosecond}) - - odize.AssertTrue(t, errors.As(err, &apiErr)) - odize.AssertEqual(t, 2, m.Retries) -} - -func Test_callWithRetry_no_retries_should_return_error(t *testing.T) { - m := MockHTTPClient{ - ErrDo: true, - Err: errors.New("not expected error"), - } - - _, err := callWithRetry("", http.MethodPost, nil, &m, []time.Duration{}) - odize.AssertTrue(t, errors.Is(err, ErrNoValidRetryStrategy)) -} - -func Test_callWithRetry_nill_retries_should_return_error(t *testing.T) { - m := MockHTTPClient{ - ErrDo: true, - Err: errors.New("not expected error"), - } - - _, err := callWithRetry("", http.MethodPost, nil, &m, nil) - odize.AssertTrue(t, errors.Is(err, ErrNoValidRetryStrategy)) -} - -func Test_callWithRetry_should_return_response(t *testing.T) { - m := MockHTTPClient{ - Resp: &http.Response{ - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - } - - resp, err := callWithRetry("", http.MethodGet, nil, &m, []time.Duration{1 * time.Nanosecond}) - odize.AssertNoError(t, err) - odize.AssertEqual(t, resp, m.Resp) - odize.AssertEqual(t, m.Req.Method, http.MethodGet) -} - func TestAxios_Patch_no_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ diff --git a/mocks.go b/mocks.gen.go similarity index 100% rename from mocks.go rename to mocks.gen.go diff --git a/scripts/tests.mk b/scripts/tests.mk index a3b3f4e..92159aa 100644 --- a/scripts/tests.mk +++ b/scripts/tests.mk @@ -13,12 +13,12 @@ test-unit: ## Run unit tests test-watch: ## Run tests in watch mode gow test -coverprofile $(COVER_OUTPUT_RAW) --short -cover -failfast ./... -test-cover: ## generate html coverage report + open + +test-gen-coverage: + grep -v -E -f ${PWD}/.covignore $(COVER_OUTPUT_RAW) > coverage.filtered.out + mv coverage.filtered.out $(COVER_OUTPUT_RAW) + +test-cover: test-gen-coverage ## generate html coverage report + open go tool cover -html=$(COVER_OUTPUT_RAW) -o $(COVER_OUTPUT_HTML) open coverage.html -test-purge: build ## Run purge integration tests - ./goety purge -e http://localhost:8000 -t "dev-main-adjacent" -p "inventoryId" -s "relationshipId" - -test-dump: build ## Run dump integration tests - ./goety dump -e http://localhost:8000 -t "dev-main-adjacent" -p "test.json" \ No newline at end of file From c78eb619d1592bfeae9e709e50dce7d6c9045a98 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sat, 29 Nov 2025 22:37:38 +1100 Subject: [PATCH 05/15] adding --- .covignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.covignore b/.covignore index 87c6170..069acce 100644 --- a/.covignore +++ b/.covignore @@ -1 +1,2 @@ -.gen.go \ No newline at end of file +.gen.go +dad_jokes.go \ No newline at end of file From d695037eb99f2f46ac9c84afe30db76e8aa71f85 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 06:51:25 +1100 Subject: [PATCH 06/15] adding tests, fixing format --- errors.go | 2 +- errors_test.go | 19 +++++++++++++-- fetch.go | 5 ++-- fetch_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 6 deletions(-) diff --git a/errors.go b/errors.go index 4e94b24..eb7e2f9 100644 --- a/errors.go +++ b/errors.go @@ -16,7 +16,7 @@ type APIError struct { } func (e *APIError) Error() string { - return fmt.Sprintf("%s [%d]: %s", e.StatusText, e.StatusCode, e.Message) + return fmt.Sprintf("%s: [%d]: %s", e.StatusText, e.StatusCode, e.Message) } func (e *APIError) Unwrap() error { diff --git a/errors_test.go b/errors_test.go index 656c4b5..61f5a47 100644 --- a/errors_test.go +++ b/errors_test.go @@ -26,7 +26,7 @@ func TestAPIError_Error(t *testing.T) { StatusText: http.StatusText(http.StatusBadRequest), Message: "there was an issue with the request", }, - want: "Bad Request [400]: there was an issue with the request", + want: "Bad Request: [400]: there was an issue with the request", }, { name: "should return 5xx error", @@ -35,7 +35,7 @@ func TestAPIError_Error(t *testing.T) { StatusText: http.StatusText(http.StatusInternalServerError), Message: "there was an issue with the request", }, - want: "Internal Server Error [500]: there was an issue with the request", + want: "Internal Server Error: [500]: there was an issue with the request", }, } for _, tt := range tests { @@ -79,3 +79,18 @@ func TestAPIError_errors_is(t *testing.T) { err := fn() odize.AssertEqual(t, err.Error(), expected.Error()) } + +func TestAPIError_Unwrap(t *testing.T) { + var err *APIError + expectedErr := APIError{ + StatusCode: http.StatusBadRequest, + StatusText: http.StatusText(http.StatusBadRequest), + Message: "some issue", + } + var testErr error = &expectedErr + if errors.As(testErr, &err) { + odize.AssertEqual(t, err.Unwrap().Error(), expectedErr.Error()) + } else { + t.Error("non matching error") + } +} diff --git a/fetch.go b/fetch.go index 38bd3a6..de1d9b4 100644 --- a/fetch.go +++ b/fetch.go @@ -2,8 +2,8 @@ package fetch import ( "errors" + "fmt" "io" - "log" "net/http" "time" ) @@ -74,11 +74,12 @@ func (a *Client) callWithRetry(url string, method string, body io.Reader, retryS for _, retryWait := range retryStrategy { resp, err = a.call(url, method, body, headers...) + if err == nil || !isRecoverable(err) { break } - log.Printf("%s: http %s request error [%s], will retry in [%s]", logPrefix, method, err, retryWait) + fmt.Printf("%s: http %s request error [%s], will retry in [%s]", logPrefix, method, err, retryWait) time.Sleep(retryWait) } diff --git a/fetch_test.go b/fetch_test.go index 3c38790..16cd5b1 100644 --- a/fetch_test.go +++ b/fetch_test.go @@ -321,7 +321,7 @@ func TestAxios_Get_no_retry_with_default_and_normal_headers(t *testing.T) { } } -func TestAxios_Post_with_retry(t *testing.T) { +func TestAxios_Post_with_retry_response_status_ok(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -343,6 +343,48 @@ func TestAxios_Post_with_retry(t *testing.T) { odize.AssertEqual(t, resp, m.Resp) } +func TestAxios_Post_with_retry_response_should_try_once(t *testing.T) { + m := MockHTTPClient{ + Resp: &http.Response{ + Status: http.StatusText(http.StatusOK), + StatusCode: http.StatusOK, + }, + } + + headers := map[string]string{ + "Content-Type": "application/json", + } + + axios := &Client{ + RetryStrategy: []time.Duration{1 * time.Nanosecond}, + Client: &m, + } + + _, _ = axios.Post("", bytes.NewReader(nil), headers) + odize.AssertEqual(t, m.Retries, 1) +} + +func TestAxios_Post_with_retry_response_should_try_twice(t *testing.T) { + m := MockHTTPClient{ + Resp: &http.Response{ + Status: http.StatusText(http.StatusGatewayTimeout), + StatusCode: http.StatusGatewayTimeout, + }, + } + + headers := map[string]string{ + "Content-Type": "application/json", + } + + axios := &Client{ + RetryStrategy: []time.Duration{1 * time.Nanosecond, 1 * time.Nanosecond}, + Client: &m, + } + + _, _ = axios.Post("", bytes.NewReader(nil), headers) + odize.AssertEqual(t, m.Retries, 2) +} + func TestAxios_Post_no_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ @@ -365,6 +407,27 @@ func TestAxios_Post_no_retry(t *testing.T) { odize.AssertEqual(t, resp, m.Resp) } +func TestAxios_Post_empty_retry_list(t *testing.T) { + m := MockHTTPClient{ + Resp: &http.Response{ + Status: http.StatusText(http.StatusOK), + StatusCode: http.StatusOK, + }, + } + + headers := map[string]string{ + "Content-Type": "application/json", + } + + axios := &Client{ + RetryStrategy: []time.Duration{}, + Client: &m, + } + + _, err := axios.Post("", bytes.NewReader(nil), headers) + odize.AssertError(t, err) +} + func TestAxios_Post_no_retry_with_default_and_normal_headers(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ From e3facc1a8afa0b79bd69075b7579b0871c6d2736 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 07:01:59 +1100 Subject: [PATCH 07/15] adding more tests to options --- fetch_test.go | 132 +++++++++++++++++++++++------------------------ options.go | 2 +- options_test.go | 38 ++++++++++++++ scripts/tests.mk | 2 +- 4 files changed, 106 insertions(+), 68 deletions(-) diff --git a/fetch_test.go b/fetch_test.go index 16cd5b1..9b223af 100644 --- a/fetch_test.go +++ b/fetch_test.go @@ -9,7 +9,7 @@ import ( "github.com/code-gorilla-au/odize" ) -func TestAxios_Patch_no_retry(t *testing.T) { +func TestClient_Patch_no_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -25,18 +25,18 @@ func TestAxios_Patch_no_retry(t *testing.T) { "default-header": "bar", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := axios.Patch("", bytes.NewReader(nil), headers) + resp, err := client.Patch("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Patch_no_retry_with_default_and_normal_headers(t *testing.T) { +func TestClient_Patch_no_retry_with_default_and_normal_headers(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -52,13 +52,13 @@ func TestAxios_Patch_no_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := axios.Patch("", bytes.NewReader(nil), headers) + resp, err := client.Patch("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -69,7 +69,7 @@ func TestAxios_Patch_no_retry_with_default_and_normal_headers(t *testing.T) { } } -func TestAxios_Patch_with_retry(t *testing.T) { +func TestClient_Patch_with_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -81,17 +81,17 @@ func TestAxios_Patch_with_retry(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := axios.Patch("", bytes.NewReader(nil), headers) + resp, err := client.Patch("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Delete_no_retry(t *testing.T) { +func TestClient_Delete_no_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -103,17 +103,17 @@ func TestAxios_Delete_no_retry(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, } - resp, err := axios.Delete("", bytes.NewReader(nil), headers) + resp, err := client.Delete("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Delete_with_retry(t *testing.T) { +func TestClient_Delete_with_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -125,17 +125,17 @@ func TestAxios_Delete_with_retry(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := axios.Delete("", bytes.NewReader(nil), headers) + resp, err := client.Delete("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Delete_with_retry_with_default_and_normal_headers(t *testing.T) { +func TestClient_Delete_with_retry_with_default_and_normal_headers(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -151,13 +151,13 @@ func TestAxios_Delete_with_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := axios.Delete("", bytes.NewReader(nil), headers) + resp, err := client.Delete("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -168,7 +168,7 @@ func TestAxios_Delete_with_retry_with_default_and_normal_headers(t *testing.T) { } } -func TestAxios_Put_no_retry(t *testing.T) { +func TestClient_Put_no_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -180,17 +180,17 @@ func TestAxios_Put_no_retry(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, } - resp, err := axios.Put("", bytes.NewReader(nil), headers) + resp, err := client.Put("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Put_with_retry(t *testing.T) { +func TestClient_Put_with_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -202,17 +202,17 @@ func TestAxios_Put_with_retry(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := axios.Put("", bytes.NewReader(nil), headers) + resp, err := client.Put("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Put_with_retry_with_default_and_normal_headers(t *testing.T) { +func TestClient_Put_with_retry_with_default_and_normal_headers(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -228,13 +228,13 @@ func TestAxios_Put_with_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := axios.Put("", bytes.NewReader(nil), headers) + resp, err := client.Put("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -245,7 +245,7 @@ func TestAxios_Put_with_retry_with_default_and_normal_headers(t *testing.T) { } } -func TestAxios_Get_with_retry(t *testing.T) { +func TestClient_Get_with_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -257,16 +257,16 @@ func TestAxios_Get_with_retry(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := axios.Get("", headers) + resp, err := client.Get("", headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Get_no_retry(t *testing.T) { +func TestClient_Get_no_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -278,17 +278,17 @@ func TestAxios_Get_no_retry(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, } - resp, err := axios.Get("", headers) + resp, err := client.Get("", headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Get_no_retry_with_default_and_normal_headers(t *testing.T) { +func TestClient_Get_no_retry_with_default_and_normal_headers(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -304,13 +304,13 @@ func TestAxios_Get_no_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := axios.Get("", headers) + resp, err := client.Get("", headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -321,7 +321,7 @@ func TestAxios_Get_no_retry_with_default_and_normal_headers(t *testing.T) { } } -func TestAxios_Post_with_retry_response_status_ok(t *testing.T) { +func TestClient_Post_with_retry_response_status_ok(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -333,17 +333,17 @@ func TestAxios_Post_with_retry_response_status_ok(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := axios.Post("", bytes.NewReader(nil), headers) + resp, err := client.Post("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Post_with_retry_response_should_try_once(t *testing.T) { +func TestClient_Post_with_retry_response_should_try_once(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -355,16 +355,16 @@ func TestAxios_Post_with_retry_response_should_try_once(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - _, _ = axios.Post("", bytes.NewReader(nil), headers) + _, _ = client.Post("", bytes.NewReader(nil), headers) odize.AssertEqual(t, m.Retries, 1) } -func TestAxios_Post_with_retry_response_should_try_twice(t *testing.T) { +func TestClient_Post_with_retry_response_should_try_twice(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusGatewayTimeout), @@ -376,16 +376,16 @@ func TestAxios_Post_with_retry_response_should_try_twice(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond, 1 * time.Nanosecond}, Client: &m, } - _, _ = axios.Post("", bytes.NewReader(nil), headers) + _, _ = client.Post("", bytes.NewReader(nil), headers) odize.AssertEqual(t, m.Retries, 2) } -func TestAxios_Post_no_retry(t *testing.T) { +func TestClient_Post_no_retry(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -397,17 +397,17 @@ func TestAxios_Post_no_retry(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, } - resp, err := axios.Post("", bytes.NewReader(nil), headers) + resp, err := client.Post("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } -func TestAxios_Post_empty_retry_list(t *testing.T) { +func TestClient_Post_empty_retry_list(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -419,16 +419,16 @@ func TestAxios_Post_empty_retry_list(t *testing.T) { "Content-Type": "application/json", } - axios := &Client{ + client := &Client{ RetryStrategy: []time.Duration{}, Client: &m, } - _, err := axios.Post("", bytes.NewReader(nil), headers) + _, err := client.Post("", bytes.NewReader(nil), headers) odize.AssertError(t, err) } -func TestAxios_Post_no_retry_with_default_and_normal_headers(t *testing.T) { +func TestClient_Post_no_retry_with_default_and_normal_headers(t *testing.T) { m := MockHTTPClient{ Resp: &http.Response{ Status: http.StatusText(http.StatusOK), @@ -444,13 +444,13 @@ func TestAxios_Post_no_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - axios := &Client{ + client := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := axios.Post("", bytes.NewReader(nil), headers) + resp, err := client.Post("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -462,21 +462,21 @@ func TestAxios_Post_no_retry_with_default_and_normal_headers(t *testing.T) { } func TestNew_with_default_retry(t *testing.T) { - axios := New(nil) - odize.AssertEqual(t, axios.RetryStrategy, setDefaultFetch().RetryStrategy) + client := New(nil) + odize.AssertEqual(t, client.RetryStrategy, setDefaultFetch().RetryStrategy) } func TestNew_with_default_header(t *testing.T) { - axios := New(nil) - odize.AssertEqual(t, axios.DefaultHeaders, setDefaultFetch().DefaultHeaders) + client := New(nil) + odize.AssertEqual(t, client.DefaultHeaders, setDefaultFetch().DefaultHeaders) } func TestNew_with_functional_options(t *testing.T) { expected := []time.Duration{1, 2} - axios := New(WithOpts( + client := New(WithOpts( WithRetryStrategy(&expected), )) - odize.AssertEqual(t, axios.RetryStrategy, expected) + odize.AssertEqual(t, client.RetryStrategy, expected) } func TestNew_with_options_headers(t *testing.T) { @@ -486,8 +486,8 @@ func TestNew_with_options_headers(t *testing.T) { "foo": "bar", }, } - axios := New(&options) - odize.AssertEqual(t, axios.DefaultHeaders, options.DefaultHeaders) + client := New(&options) + odize.AssertEqual(t, client.DefaultHeaders, options.DefaultHeaders) } func TestNew_with_options_no_retry(t *testing.T) { @@ -497,15 +497,15 @@ func TestNew_with_options_no_retry(t *testing.T) { "foo": "bar", }, } - axios := New(&options) - odize.AssertEqual(t, axios.RetryStrategy, []time.Duration(nil)) + client := New(&options) + odize.AssertEqual(t, client.RetryStrategy, []time.Duration(nil)) } func TestNew_with_options_with_retry(t *testing.T) { options := Options{ WithRetry: true, } - axios := New(&options) - odize.AssertEqual(t, axios.RetryStrategy, setDefaultRetryStrategy()) + client := New(&options) + odize.AssertEqual(t, client.RetryStrategy, setDefaultRetryStrategy()) } func Test_mergeHeaders_should_merge_correctly(t *testing.T) { diff --git a/options.go b/options.go index bcd6a82..40db27d 100644 --- a/options.go +++ b/options.go @@ -36,7 +36,7 @@ func WithOpts(opts ...FnOpts) *Options { return &o } -// WithDefaultRetry - use client default retry strategy +// WithDefaultRetryStrategy - use client default retry strategy func WithDefaultRetryStrategy() FnOpts { return func(o *Options) error { o.WithRetry = true diff --git a/options_test.go b/options_test.go index 2970ce1..e0464d8 100644 --- a/options_test.go +++ b/options_test.go @@ -65,3 +65,41 @@ func TestWithOpts_with_custom_client(t *testing.T) { odize.AssertEqual(t, &cl, options.HTTPClient) odize.AssertNil(t, options.RetryStrategy) } + +func TestWithOpts_with_custom_retry_and_client(t *testing.T) { + st := []time.Duration{1, 2} + cl := http.Client{} + options := WithOpts(WithHTTPClient(&cl), WithRetryStrategy(&[]time.Duration{1, 2})) + odize.AssertEqual(t, &st, options.RetryStrategy) +} + +func TestWithOpts_with_multiple_options(t *testing.T) { + st := []time.Duration{1, 2} + cl := http.Client{} + options := WithOpts(WithHTTPClient(&cl), WithRetryStrategy(&[]time.Duration{1, 2})) + odize.AssertEqual(t, &st, options.RetryStrategy) +} + +func TestWithOpts_with_nil_options(t *testing.T) { + options := WithOpts(nil) + odize.AssertNil(t, options.HTTPClient) + odize.AssertNil(t, options.RetryStrategy) +} + +func TestWithOpts_with_empty_options(t *testing.T) { + options := WithOpts() + odize.AssertNil(t, options.HTTPClient) +} + +func TestWithOpts_with_default_retry_strategy(t *testing.T) { + options := WithOpts(WithDefaultRetryStrategy()) + odize.AssertTrue(t, options.WithRetry) +} + +func TestWithOpts_with_headers(t *testing.T) { + headers := map[string]string{ + "foo": "bar", + } + options := WithOpts(WithHeaders(headers)) + odize.AssertEqual(t, headers, options.DefaultHeaders) +} diff --git a/scripts/tests.mk b/scripts/tests.mk index 92159aa..09abbe1 100644 --- a/scripts/tests.mk +++ b/scripts/tests.mk @@ -18,7 +18,7 @@ test-gen-coverage: grep -v -E -f ${PWD}/.covignore $(COVER_OUTPUT_RAW) > coverage.filtered.out mv coverage.filtered.out $(COVER_OUTPUT_RAW) -test-cover: test-gen-coverage ## generate html coverage report + open +test-cover: test-unit test-gen-coverage ## generate html coverage report + open go tool cover -html=$(COVER_OUTPUT_RAW) -o $(COVER_OUTPUT_HTML) open coverage.html From 050a9bf29b93561cbc281c4f6df39c331eaa37b7 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 09:37:47 +1100 Subject: [PATCH 08/15] small lint fixes --- fetch.go | 18 ++++++---- fetch_test.go | 96 +++++++++++++++++++++++++-------------------------- 2 files changed, 60 insertions(+), 54 deletions(-) diff --git a/fetch.go b/fetch.go index de1d9b4..d7168df 100644 --- a/fetch.go +++ b/fetch.go @@ -1,9 +1,10 @@ package fetch import ( + "context" "errors" - "fmt" "io" + "log" "net/http" "time" ) @@ -59,27 +60,32 @@ func (a *Client) do(url string, method string, body io.Reader, headers map[strin if a.RetryStrategy == nil { return a.call(url, method, body, headers, a.DefaultHeaders) } - return a.callWithRetry(url, method, body, a.RetryStrategy, headers, a.DefaultHeaders) + + return a.callWithRetry(url, method, body, headers, a.DefaultHeaders) } // callWithRetry - wrap the call method with the retry strategy -func (a *Client) callWithRetry(url string, method string, body io.Reader, retryStrategy []time.Duration, headers ...map[string]string) (*http.Response, error) { +func (a *Client) callWithRetry(url string, method string, body io.Reader, headers ...map[string]string) (*http.Response, error) { logPrefix := "fetch: callWithRetry" var resp *http.Response var err error - if len(retryStrategy) == 0 { + if len(a.RetryStrategy) == 0 { return resp, ErrNoValidRetryStrategy } - for _, retryWait := range retryStrategy { + for _, retryWait := range a.RetryStrategy { resp, err = a.call(url, method, body, headers...) if err == nil || !isRecoverable(err) { + if errors.Is(err, context.Canceled) { + log.Printf("%s: http %s request canceled", logPrefix, method) + } + break } - fmt.Printf("%s: http %s request error [%s], will retry in [%s]", logPrefix, method, err, retryWait) + log.Printf("%s: http %s request error [%s], will retry in [%s]", logPrefix, method, err, retryWait) time.Sleep(retryWait) } diff --git a/fetch_test.go b/fetch_test.go index 9b223af..c3967af 100644 --- a/fetch_test.go +++ b/fetch_test.go @@ -25,13 +25,13 @@ func TestClient_Patch_no_retry(t *testing.T) { "default-header": "bar", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := client.Patch("", bytes.NewReader(nil), headers) + resp, err := c.Patch("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -52,13 +52,13 @@ func TestClient_Patch_no_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := client.Patch("", bytes.NewReader(nil), headers) + resp, err := c.Patch("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -81,12 +81,12 @@ func TestClient_Patch_with_retry(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := client.Patch("", bytes.NewReader(nil), headers) + resp, err := c.Patch("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -103,12 +103,12 @@ func TestClient_Delete_no_retry(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, } - resp, err := client.Delete("", bytes.NewReader(nil), headers) + resp, err := c.Delete("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -125,12 +125,12 @@ func TestClient_Delete_with_retry(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := client.Delete("", bytes.NewReader(nil), headers) + resp, err := c.Delete("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -151,13 +151,13 @@ func TestClient_Delete_with_retry_with_default_and_normal_headers(t *testing.T) "default-header": "bar", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := client.Delete("", bytes.NewReader(nil), headers) + resp, err := c.Delete("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -180,12 +180,12 @@ func TestClient_Put_no_retry(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, } - resp, err := client.Put("", bytes.NewReader(nil), headers) + resp, err := c.Put("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -202,12 +202,12 @@ func TestClient_Put_with_retry(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := client.Put("", bytes.NewReader(nil), headers) + resp, err := c.Put("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -228,13 +228,13 @@ func TestClient_Put_with_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := client.Put("", bytes.NewReader(nil), headers) + resp, err := c.Put("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -257,12 +257,12 @@ func TestClient_Get_with_retry(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := client.Get("", headers) + resp, err := c.Get("", headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -278,12 +278,12 @@ func TestClient_Get_no_retry(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, } - resp, err := client.Get("", headers) + resp, err := c.Get("", headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -304,13 +304,13 @@ func TestClient_Get_no_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := client.Get("", headers) + resp, err := c.Get("", headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -333,12 +333,12 @@ func TestClient_Post_with_retry_response_status_ok(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - resp, err := client.Post("", bytes.NewReader(nil), headers) + resp, err := c.Post("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -355,12 +355,12 @@ func TestClient_Post_with_retry_response_should_try_once(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond}, Client: &m, } - _, _ = client.Post("", bytes.NewReader(nil), headers) + _, _ = c.Post("", bytes.NewReader(nil), headers) odize.AssertEqual(t, m.Retries, 1) } @@ -376,12 +376,12 @@ func TestClient_Post_with_retry_response_should_try_twice(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: []time.Duration{1 * time.Nanosecond, 1 * time.Nanosecond}, Client: &m, } - _, _ = client.Post("", bytes.NewReader(nil), headers) + _, _ = c.Post("", bytes.NewReader(nil), headers) odize.AssertEqual(t, m.Retries, 2) } @@ -397,12 +397,12 @@ func TestClient_Post_no_retry(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, } - resp, err := client.Post("", bytes.NewReader(nil), headers) + resp, err := c.Post("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) } @@ -419,12 +419,12 @@ func TestClient_Post_empty_retry_list(t *testing.T) { "Content-Type": "application/json", } - client := &Client{ + c := &Client{ RetryStrategy: []time.Duration{}, Client: &m, } - _, err := client.Post("", bytes.NewReader(nil), headers) + _, err := c.Post("", bytes.NewReader(nil), headers) odize.AssertError(t, err) } @@ -444,13 +444,13 @@ func TestClient_Post_no_retry_with_default_and_normal_headers(t *testing.T) { "default-header": "bar", } - client := &Client{ + c := &Client{ RetryStrategy: nil, Client: &m, DefaultHeaders: defaultHeaders, } - resp, err := client.Post("", bytes.NewReader(nil), headers) + resp, err := c.Post("", bytes.NewReader(nil), headers) odize.AssertNoError(t, err) odize.AssertEqual(t, resp, m.Resp) for key, value := range defaultHeaders { @@ -462,21 +462,21 @@ func TestClient_Post_no_retry_with_default_and_normal_headers(t *testing.T) { } func TestNew_with_default_retry(t *testing.T) { - client := New(nil) - odize.AssertEqual(t, client.RetryStrategy, setDefaultFetch().RetryStrategy) + c := New(nil) + odize.AssertEqual(t, c.RetryStrategy, setDefaultFetch().RetryStrategy) } func TestNew_with_default_header(t *testing.T) { - client := New(nil) - odize.AssertEqual(t, client.DefaultHeaders, setDefaultFetch().DefaultHeaders) + c := New(nil) + odize.AssertEqual(t, c.DefaultHeaders, setDefaultFetch().DefaultHeaders) } func TestNew_with_functional_options(t *testing.T) { expected := []time.Duration{1, 2} - client := New(WithOpts( + c := New(WithOpts( WithRetryStrategy(&expected), )) - odize.AssertEqual(t, client.RetryStrategy, expected) + odize.AssertEqual(t, c.RetryStrategy, expected) } func TestNew_with_options_headers(t *testing.T) { @@ -486,8 +486,8 @@ func TestNew_with_options_headers(t *testing.T) { "foo": "bar", }, } - client := New(&options) - odize.AssertEqual(t, client.DefaultHeaders, options.DefaultHeaders) + c := New(&options) + odize.AssertEqual(t, c.DefaultHeaders, options.DefaultHeaders) } func TestNew_with_options_no_retry(t *testing.T) { @@ -497,15 +497,15 @@ func TestNew_with_options_no_retry(t *testing.T) { "foo": "bar", }, } - client := New(&options) - odize.AssertEqual(t, client.RetryStrategy, []time.Duration(nil)) + c := New(&options) + odize.AssertEqual(t, c.RetryStrategy, []time.Duration(nil)) } func TestNew_with_options_with_retry(t *testing.T) { options := Options{ WithRetry: true, } - client := New(&options) - odize.AssertEqual(t, client.RetryStrategy, setDefaultRetryStrategy()) + c := New(&options) + odize.AssertEqual(t, c.RetryStrategy, setDefaultRetryStrategy()) } func Test_mergeHeaders_should_merge_correctly(t *testing.T) { From d3974f04942a25a75c3d2e68e247bd1300316f78 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 10:02:07 +1100 Subject: [PATCH 09/15] adding documentation --- fetch.go | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 188 insertions(+), 12 deletions(-) diff --git a/fetch.go b/fetch.go index d7168df..f52a589 100644 --- a/fetch.go +++ b/fetch.go @@ -1,3 +1,4 @@ +// Package fetch provides a simple fetch client with built in backup / retry strategy. package fetch import ( @@ -9,6 +10,7 @@ import ( "time" ) +// New initialises and returns a new Client instance with the provided options or default configurations if nil. func New(options *Options) *Client { if options == nil { @@ -35,37 +37,211 @@ func New(options *Options) *Client { return &fetch } +// Get sends an HTTP GET request to the specified URL with optional headers and returns the HTTP response or an error. +// +// Example: +// +// var apiErr *fetch.APIError +// +// resp, err := client.Get(url, nil) +// if err != nil { +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } func (a *Client) Get(url string, headers map[string]string) (*http.Response, error) { - return a.do(url, http.MethodGet, nil, headers) + ctx := context.Background() + return a.do(ctx, url, http.MethodGet, nil, headers) } +// Post sends an HTTP POST request to the specified URL with a body and optional headers, returning the HTTP response or an error. +// +// Example: +// +// var apiErr *fetch.APIError +// +// resp, err := client.Post(url, bytes.NewReader(`{"hello": "world"}`) +// if err != nil { +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } func (a *Client) Post(url string, body io.Reader, headers map[string]string) (*http.Response, error) { - return a.do(url, http.MethodPost, body, headers) + ctx := context.Background() + return a.do(ctx, url, http.MethodPost, body, headers) } +// Put sends an HTTP PUT request to the specified URL with a body and optional headers, returning the HTTP response or an error. +// +// Example: +// +// var apiErr *fetch.APIError +// +// resp, err := client.Put(url, bytes.NewReader(`{"hello": "world"}`) +// if err != nil { +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } func (a *Client) Put(url string, body io.Reader, headers map[string]string) (*http.Response, error) { - return a.do(url, http.MethodPut, body, headers) + ctx := context.Background() + return a.do(ctx, url, http.MethodPut, body, headers) } +// Delete sends an HTTP DELETE request to the specified URL with a body and optional headers, returning the response or an error. +// +// Example: +// +// var apiErr *fetch.APIError +// +// resp, err := client.Delete(url, bytes.NewReader(`{"hello": "world"}`) +// if err != nil { +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } func (a *Client) Delete(url string, body io.Reader, headers map[string]string) (*http.Response, error) { - return a.do(url, http.MethodDelete, body, headers) + ctx := context.Background() + return a.do(ctx, url, http.MethodDelete, body, headers) } +// Patch sends an HTTP PATCH request to the specified URL with a body and optional headers, returning the response or an error. +// Example: +// +// var apiErr *fetch.APIError +// +// resp, err := client.Patch(url, bytes.NewReader(`{"hello": "world"}`) +// if err != nil { +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } func (a *Client) Patch(url string, body io.Reader, headers map[string]string) (*http.Response, error) { - return a.do(url, http.MethodPatch, body, headers) + ctx := context.Background() + return a.do(ctx, url, http.MethodPatch, body, headers) +} + +// GetCtx sends a cancelable HTTP GET request to the specified URL with context and optional headers, returning the response or an error. +// +// Example: +// +// var apiErr *fetch.APIError +// ctx, cancel := context.WithCancel(context.Background()) +// +// resp, err := client.GetCtx(ctx,url, nil) +// if err != nil { +// if errors.is(err, context.Canceled) { +// // Handle context cancelled +// } +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } +func (a *Client) GetCtx(ctx context.Context, url string, headers map[string]string) (*http.Response, error) { + return a.do(ctx, url, http.MethodGet, nil, headers) +} + +// PostCtx sends a cancelable HTTP POST request to the specified URL with context, body, and headers, returning a response or error. +// +// Example: +// +// var apiErr *fetch.APIError +// ctx, cancel := context.WithCancel(context.Background()) +// +// resp, err := client.PostCtx(ctx,url, bytes.NewReader(`{"hello": "world"}`) +// if err != nil { +// if errors.is(err, context.Canceled) { +// // Handle context cancelled +// } +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } +func (a *Client) PostCtx(ctx context.Context, url string, body io.Reader, headers map[string]string) (*http.Response, error) { + return a.do(ctx, url, http.MethodPost, body, headers) +} + +// PutCtx sends a cancelable HTTP PUT request to the specified URL with context, body, and headers, returning a response or error. +// +// Example: +// +// var apiErr *fetch.APIError +// ctx, cancel := context.WithCancel(context.Background()) +// +// resp, err := client.PutCtx(ctx,url, bytes.NewReader(`{"hello": "world"}`) +// if err != nil { +// if errors.is(err, context.Canceled) { +// // Handle context cancelled +// } +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } +func (a *Client) PutCtx(ctx context.Context, url string, body io.Reader, headers map[string]string) (*http.Response, error) { + return a.do(ctx, url, http.MethodPut, body, headers) +} + +// DeleteCtx sends a cancelable HTTP DELETE request to the specified URL with context, body, and headers, returning a response or error. +// +// Example: +// +// var apiErr *fetch.APIError +// ctx, cancel := context.WithCancel(context.Background()) +// +// resp, err := client.DeleteCtx(ctx,url, bytes.NewReader(`{"hello": "world"}`) +// if err != nil { +// if errors.is(err, context.Canceled) { +// // Handle context cancelled +// } +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } +func (a *Client) DeleteCtx(ctx context.Context, url string, body io.Reader, headers map[string]string) (*http.Response, error) { + return a.do(ctx, url, http.MethodDelete, body, headers) +} + +// PatchCtx sends an HTTP PATCH request to the specified URL with the provided context, body, and headers. +// +// Example: +// +// var apiErr *fetch.APIError +// ctx, cancel := context.WithCancel(context.Background()) +// +// resp, err := client.PatchCtx(ctx,url, bytes.NewReader(`{"hello": "world"}`) +// if err != nil { +// if errors.is(err, context.Canceled) { +// // Handle context cancelled +// } +// if errors.As(err, &apiErr) { +// fmt.Println("API Response error", apiErr) +// } +// // Handle non-API Error +// } +func (a *Client) PatchCtx(ctx context.Context, url string, body io.Reader, headers map[string]string) (*http.Response, error) { + return a.do(ctx, url, http.MethodPatch, body, headers) } // do - make http call with the provided configuration -func (a *Client) do(url string, method string, body io.Reader, headers map[string]string) (*http.Response, error) { +func (a *Client) do(ctx context.Context, url string, method string, body io.Reader, headers map[string]string) (*http.Response, error) { if a.RetryStrategy == nil { - return a.call(url, method, body, headers, a.DefaultHeaders) + return a.call(ctx, url, method, body, headers, a.DefaultHeaders) } - return a.callWithRetry(url, method, body, headers, a.DefaultHeaders) + return a.callWithRetry(ctx, url, method, body, headers, a.DefaultHeaders) } // callWithRetry - wrap the call method with the retry strategy -func (a *Client) callWithRetry(url string, method string, body io.Reader, headers ...map[string]string) (*http.Response, error) { +func (a *Client) callWithRetry(ctx context.Context, url string, method string, body io.Reader, headers ...map[string]string) (*http.Response, error) { logPrefix := "fetch: callWithRetry" var resp *http.Response var err error @@ -75,7 +251,7 @@ func (a *Client) callWithRetry(url string, method string, body io.Reader, header } for _, retryWait := range a.RetryStrategy { - resp, err = a.call(url, method, body, headers...) + resp, err = a.call(ctx, url, method, body, headers...) if err == nil || !isRecoverable(err) { if errors.Is(err, context.Canceled) { @@ -93,8 +269,8 @@ func (a *Client) callWithRetry(url string, method string, body io.Reader, header } // call - creates a new HTTP request and returns an HTTP response -func (a *Client) call(url string, method string, body io.Reader, headers ...map[string]string) (*http.Response, error) { - req, err := http.NewRequest(method, url, body) +func (a *Client) call(ctx context.Context, url string, method string, body io.Reader, headers ...map[string]string) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, method, url, body) if err != nil { return &http.Response{}, err } From e5841bdd22552e4555594376e0df70fd0d7193de Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 10:32:04 +1100 Subject: [PATCH 10/15] adding ability to cancel API calls --- fetch.go | 18 ++++++----- fetch_test.go | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/fetch.go b/fetch.go index f52a589..919fff3 100644 --- a/fetch.go +++ b/fetch.go @@ -61,7 +61,7 @@ func (a *Client) Get(url string, headers map[string]string) (*http.Response, err // // var apiErr *fetch.APIError // -// resp, err := client.Post(url, bytes.NewReader(`{"hello": "world"}`) +// resp, err := client.Post(url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) // if err != nil { // if errors.As(err, &apiErr) { // fmt.Println("API Response error", apiErr) @@ -79,7 +79,7 @@ func (a *Client) Post(url string, body io.Reader, headers map[string]string) (*h // // var apiErr *fetch.APIError // -// resp, err := client.Put(url, bytes.NewReader(`{"hello": "world"}`) +// resp, err := client.Put(url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) // if err != nil { // if errors.As(err, &apiErr) { // fmt.Println("API Response error", apiErr) @@ -97,7 +97,7 @@ func (a *Client) Put(url string, body io.Reader, headers map[string]string) (*ht // // var apiErr *fetch.APIError // -// resp, err := client.Delete(url, bytes.NewReader(`{"hello": "world"}`) +// resp, err := client.Delete(url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) // if err != nil { // if errors.As(err, &apiErr) { // fmt.Println("API Response error", apiErr) @@ -114,7 +114,7 @@ func (a *Client) Delete(url string, body io.Reader, headers map[string]string) ( // // var apiErr *fetch.APIError // -// resp, err := client.Patch(url, bytes.NewReader(`{"hello": "world"}`) +// resp, err := client.Patch(url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) // if err != nil { // if errors.As(err, &apiErr) { // fmt.Println("API Response error", apiErr) @@ -154,7 +154,7 @@ func (a *Client) GetCtx(ctx context.Context, url string, headers map[string]stri // var apiErr *fetch.APIError // ctx, cancel := context.WithCancel(context.Background()) // -// resp, err := client.PostCtx(ctx,url, bytes.NewReader(`{"hello": "world"}`) +// resp, err := client.PostCtx(ctx,url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) // if err != nil { // if errors.is(err, context.Canceled) { // // Handle context cancelled @@ -175,7 +175,7 @@ func (a *Client) PostCtx(ctx context.Context, url string, body io.Reader, header // var apiErr *fetch.APIError // ctx, cancel := context.WithCancel(context.Background()) // -// resp, err := client.PutCtx(ctx,url, bytes.NewReader(`{"hello": "world"}`) +// resp, err := client.PutCtx(ctx,url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) // if err != nil { // if errors.is(err, context.Canceled) { // // Handle context cancelled @@ -196,7 +196,7 @@ func (a *Client) PutCtx(ctx context.Context, url string, body io.Reader, headers // var apiErr *fetch.APIError // ctx, cancel := context.WithCancel(context.Background()) // -// resp, err := client.DeleteCtx(ctx,url, bytes.NewReader(`{"hello": "world"}`) +// resp, err := client.DeleteCtx(ctx,url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) // if err != nil { // if errors.is(err, context.Canceled) { // // Handle context cancelled @@ -217,7 +217,7 @@ func (a *Client) DeleteCtx(ctx context.Context, url string, body io.Reader, head // var apiErr *fetch.APIError // ctx, cancel := context.WithCancel(context.Background()) // -// resp, err := client.PatchCtx(ctx,url, bytes.NewReader(`{"hello": "world"}`) +// resp, err := client.PatchCtx(ctx,url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) // if err != nil { // if errors.is(err, context.Canceled) { // // Handle context cancelled @@ -280,6 +280,8 @@ func (a *Client) call(ctx context.Context, url string, method string, body io.Re req.Header.Add(key, value) } + log.Println("request", req == nil) + resp, err := a.Client.Do(req) if err != nil { return resp, err diff --git a/fetch_test.go b/fetch_test.go index c3967af..09e23f8 100644 --- a/fetch_test.go +++ b/fetch_test.go @@ -2,6 +2,8 @@ package fetch import ( "bytes" + "context" + "errors" "net/http" "testing" "time" @@ -526,3 +528,91 @@ func Test_mergeHeaders_empty_should_work(t *testing.T) { odize.AssertEqual(t, expected, test) } + +func TestClient_context_methods(t *testing.T) { + group := odize.NewGroup(t, nil) + + var c Client + var mock *MockHTTPClient + var ctx context.Context + var cancelFunc context.CancelFunc + + group.BeforeEach(func() { + ctx, cancelFunc = context.WithTimeout(context.Background(), 1*time.Nanosecond) + + mock = &MockHTTPClient{ + Err: context.Canceled, + ErrDo: true, + Resp: &http.Response{ + Status: http.StatusText(http.StatusOK), + StatusCode: http.StatusOK, + }, + } + + c = Client{Client: mock} + }) + + err := group. + Test("GetCtx cancel() should return error", func(t *testing.T) { + + c = Client{ + Client: mock, + } + + cancelFunc() + + _, err := c.GetCtx(ctx, "/https://google.com", nil) + odize.AssertTrue(t, errors.Is(err, context.Canceled)) + + }). + Test("PostCtx cancel() should return error", func(t *testing.T) { + + c = Client{ + Client: mock, + } + + cancelFunc() + + _, err := c.PostCtx(ctx, "/https://google.com", bytes.NewReader([]byte(`{"hello": "world"}`)), nil) + odize.AssertTrue(t, errors.Is(err, context.Canceled)) + + }). + Test("PutCtx cancel() should return error", func(t *testing.T) { + + c = Client{ + Client: mock, + } + + cancelFunc() + + _, err := c.PutCtx(ctx, "/https://google.com", bytes.NewReader([]byte(`{"hello": "world"}`)), nil) + odize.AssertTrue(t, errors.Is(err, context.Canceled)) + + }). + Test("DeleteCtx cancel() should return error", func(t *testing.T) { + + c = Client{ + Client: mock, + } + + cancelFunc() + + _, err := c.DeleteCtx(ctx, "/https://google.com", bytes.NewReader([]byte(`{"hello": "world"}`)), nil) + odize.AssertTrue(t, errors.Is(err, context.Canceled)) + + }). + Test("PatchCtx cancel() should return error", func(t *testing.T) { + + c = Client{ + Client: mock, + } + + cancelFunc() + + _, err := c.PatchCtx(ctx, "/https://google.com", bytes.NewReader([]byte(`{"hello": "world"}`)), nil) + odize.AssertTrue(t, errors.Is(err, context.Canceled)) + + }). + Run() + odize.AssertNoError(t, err) +} From 3835cbb50681c4ea4a66e80a86b68a62305657fd Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 10:36:32 +1100 Subject: [PATCH 11/15] adding docs --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index e4fd985..e0d5253 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,28 @@ if err != nil { ``` + +### Cancelable HTTP calls +Use Ctx if you want more granular control and the ability to cancel. + +```go + var apiErr *fetch.APIError + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + resp, err := client.PutCtx(ctx,url, bytes.NewReader([]byte(`{"hello": "world"}`)), nil) + if err != nil { + if errors.is(err, context.Canceled) { + // Handle context cancelled + } + if errors.As(err, &apiErr) { + fmt.Println("API Response error", apiErr) + } + // Handle non-API Error + } +``` + +

From aa92ad66aa529a2ee761fba62706d17ceeea740f Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 10:39:27 +1100 Subject: [PATCH 12/15] removing lint --- .golangci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yaml b/.golangci.yaml index b8e9474..9c6e948 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -40,6 +40,7 @@ linters: - forbidigo - bodyclose - exhaustruct + - fatcontext - path: dad_jokes.go linters: From e284a5d007e5994756f7f034d5a8754933c17d59 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 10:47:29 +1100 Subject: [PATCH 13/15] adding fix --- .github/workflows/pull-request.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 31b8bab..d18076b 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -40,6 +40,9 @@ jobs: with: go-version: ${{ env.GOLANG_VERSION }} + - name: Fix "no such tool covdata" error + run: go env -w GOTOOLCHAIN=go1.25.0+auto + - name: install tools run: make install-ci From 8eaa4b3f016188e0a9cc2a3527dddf1a59bb7914 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 10:51:32 +1100 Subject: [PATCH 14/15] fixing go ver --- .github/workflows/pull-request.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index d18076b..c508404 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -9,8 +9,6 @@ on: types: [opened, synchronize, reopened] -env: - GOLANG_VERSION: 1.22 jobs: @@ -36,12 +34,10 @@ jobs: fetch-depth: 0 - name: Setup golang ${{ env.GOLANG_VERSION }} - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: - go-version: ${{ env.GOLANG_VERSION }} - - - name: Fix "no such tool covdata" error - run: go env -w GOTOOLCHAIN=go1.25.0+auto + go-version-file: go.mod + cache-dependency-path: go.sum - name: install tools run: make install-ci From ce3abcaac4ca69177ace9c812872c8266cd17428 Mon Sep 17 00:00:00 2001 From: frag223 Date: Sun, 30 Nov 2025 10:53:04 +1100 Subject: [PATCH 15/15] fixing dependabot --- .github/dependabot.yaml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 36125f9..9df3b0f 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -5,7 +5,16 @@ updates: directory: / schedule: interval: monthly - - directory: github - package-ecosystem: github-actions + groups: + prod: + patterns: + - "*" + + - package-ecosystem: "github-actions" + directory: "/" schedule: - interval: monthly \ No newline at end of file + interval: "monthly" + groups: + prod: + patterns: + - "*"