From 987b99be2e3634c1bf1a177766d8ab4e6d6fd78b Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Tue, 18 Aug 2026 10:05:35 +0300 Subject: [PATCH 1/2] test: add unit tests for config, repoapi, and workflowapi clients Covers config.Load()'s precedence (defaults -> file -> env, including EDS_CONFIG tilde-expansion) and Save()/Load() round-tripping, plus each API client's APIError mapping (error/message/errors response shapes, empty body, request-id header capture) exercised through Do() against httptest.Server. REP-514 --- internal/config/config_test.go | 189 ++++++++++++++++++++++++++++ internal/repoapi/client_test.go | 163 ++++++++++++++++++++++++ internal/workflowapi/client_test.go | 148 ++++++++++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 internal/config/config_test.go create mode 100644 internal/repoapi/client_test.go create mode 100644 internal/workflowapi/client_test.go diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..4ee7d44 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,189 @@ +package config_test + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/cloud-ru/evolution-devservices-cli/internal/config" +) + +// withTempConfig points EDS_CONFIG at a fresh, non-existent file inside a +// per-test temp dir so tests never touch the developer's real config. +func withTempConfig(t *testing.T) string { + t.Helper() + path := filepath.Join(t.TempDir(), "config.json") + t.Setenv("EDS_CONFIG", path) + return path +} + +// clearEnv resets every env var Load() consults so tests aren't affected by +// whatever happens to be exported in the shell running `go test`. +func clearEnv(t *testing.T) { + t.Helper() + for _, k := range []string{ + "EDS_PROJECT_ID", "EDS_REPO_API_URL", "EDS_API_KEY", + "EDS_REPO_GIT_HOST", "EDS_WF_API_URL", + } { + t.Setenv(k, "") + } +} + +func TestLoad_Defaults(t *testing.T) { + withTempConfig(t) + clearEnv(t) + + cfg, err := config.Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + if cfg.APIURL != config.DefaultAPIURL { + t.Errorf("APIURL = %q, want %q", cfg.APIURL, config.DefaultAPIURL) + } + if cfg.GitHost != config.DefaultGitHost { + t.Errorf("GitHost = %q, want %q", cfg.GitHost, config.DefaultGitHost) + } + if cfg.WorkflowAPIURL != config.DefaultWorkflowAPIURL { + t.Errorf("WorkflowAPIURL = %q, want %q", cfg.WorkflowAPIURL, config.DefaultWorkflowAPIURL) + } + if cfg.APIKey != "" { + t.Errorf("APIKey = %q, want empty", cfg.APIKey) + } +} + +func TestLoad_FileOverridesDefaults(t *testing.T) { + path := withTempConfig(t) + clearEnv(t) + + writeConfig(t, path, config.Config{ + APIURL: "https://file.example/api", + ProjectID: "proj-from-file", + APIKey: "key-from-file", + }) + + cfg, err := config.Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + if cfg.APIURL != "https://file.example/api" { + t.Errorf("APIURL = %q, want file value", cfg.APIURL) + } + if cfg.ProjectID != "proj-from-file" { + t.Errorf("ProjectID = %q, want file value", cfg.ProjectID) + } + if cfg.APIKey != "key-from-file" { + t.Errorf("APIKey = %q, want file value", cfg.APIKey) + } + // GitHost was left empty in the file, so it should fall back to the default. + if cfg.GitHost != config.DefaultGitHost { + t.Errorf("GitHost = %q, want default %q when unset in file", cfg.GitHost, config.DefaultGitHost) + } +} + +func TestLoad_EnvOverridesFile(t *testing.T) { + path := withTempConfig(t) + clearEnv(t) + + writeConfig(t, path, config.Config{ + APIURL: "https://file.example/api", + ProjectID: "proj-from-file", + APIKey: "key-from-file", + }) + + t.Setenv("EDS_REPO_API_URL", "https://env.example/api") + t.Setenv("EDS_API_KEY", "key-from-env") + t.Setenv("EDS_PROJECT_ID", "proj-from-env") + + cfg, err := config.Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + if cfg.APIURL != "https://env.example/api" { + t.Errorf("APIURL = %q, want env value", cfg.APIURL) + } + if cfg.APIKey != "key-from-env" { + t.Errorf("APIKey = %q, want env value", cfg.APIKey) + } + if cfg.ProjectID != "proj-from-env" { + t.Errorf("ProjectID = %q, want env value", cfg.ProjectID) + } +} + +func TestLoad_MissingFileIsNotAnError(t *testing.T) { + withTempConfig(t) // path deliberately left non-existent + clearEnv(t) + + if _, err := config.Load(); err != nil { + t.Fatalf("Load() with no config file on disk should not error, got %v", err) + } +} + +func TestSaveThenLoad_RoundTrips(t *testing.T) { + path := withTempConfig(t) + clearEnv(t) + + cfg := &config.Config{ + APIURL: "https://saved.example/api", + ProjectID: "saved-project", + APIKey: "saved-key", + GitHost: "https://saved.example/git", + } + if err := cfg.Save(); err != nil { + t.Fatalf("Save() error = %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatalf("stat saved config: %v", err) + } + if perm := info.Mode().Perm(); perm != 0o600 { + t.Errorf("config file mode = %o, want 0600", perm) + } + + got, err := config.Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + if got.APIURL != cfg.APIURL || got.ProjectID != cfg.ProjectID || + got.APIKey != cfg.APIKey || got.GitHost != cfg.GitHost { + t.Errorf("round-tripped config = %+v, want %+v", got, cfg) + } +} + +func TestLoad_TildeInEDSConfigExpandsToHome(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) // os.UserHomeDir() honors $HOME on Unix + t.Setenv("USERPROFILE", home) // ... and on Windows + t.Setenv("EDS_CONFIG", "~/eds-test/config.json") + clearEnv(t) + + cfg := &config.Config{ProjectID: "tilde-project"} + if err := cfg.Save(); err != nil { + t.Fatalf("Save() error = %v", err) + } + + wantPath := filepath.Join(home, "eds-test", "config.json") + if _, err := os.Stat(wantPath); err != nil { + t.Fatalf("expected config at %s, stat error = %v", wantPath, err) + } + + got, err := config.Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + if got.ProjectID != "tilde-project" { + t.Errorf("ProjectID = %q, want %q", got.ProjectID, "tilde-project") + } +} + +func writeConfig(t *testing.T, path string, cfg config.Config) { + t.Helper() + data, err := json.Marshal(cfg) + if err != nil { + t.Fatalf("marshal config: %v", err) + } + if err := os.WriteFile(path, data, 0o600); err != nil { + t.Fatalf("write config: %v", err) + } +} diff --git a/internal/repoapi/client_test.go b/internal/repoapi/client_test.go new file mode 100644 index 0000000..24f70c5 --- /dev/null +++ b/internal/repoapi/client_test.go @@ -0,0 +1,163 @@ +package repoapi_test + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cloud-ru/evolution-devservices-cli/internal/repoapi" +) + +func TestDo_SuccessDecodesJSONAndSendsAPIKey(t *testing.T) { + var gotAPIKey, gotPath string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAPIKey = r.Header.Get("X-API-KEY") + gotPath = r.URL.Path + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"id":"abc123","name":"demo"}`)) + })) + defer srv.Close() + + c := repoapi.New(srv.URL, "test-api-key", "proj-1") + + var out struct { + ID string `json:"id"` + Name string `json:"name"` + } + err := c.Do(context.Background(), http.MethodGet, "/repository/abc123", nil, nil, &out) + if err != nil { + t.Fatalf("Do() error = %v", err) + } + if out.ID != "abc123" || out.Name != "demo" { + t.Errorf("decoded = %+v, want id=abc123 name=demo", out) + } + if gotAPIKey != "test-api-key" { + t.Errorf("X-API-KEY header = %q, want %q", gotAPIKey, "test-api-key") + } + if gotPath != "/repository/abc123" { + t.Errorf("request path = %q, want %q", gotPath, "/repository/abc123") + } +} + +func TestDo_ErrorShape_ErrorField(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":"invalid name"}`)) + })) + defer srv.Close() + + c := repoapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodPost, "/repository", nil, map[string]string{"name": ""}, nil) + + var apiErr *repoapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *repoapi.APIError", err) + } + if apiErr.StatusCode != http.StatusBadRequest { + t.Errorf("StatusCode = %d, want %d", apiErr.StatusCode, http.StatusBadRequest) + } + if apiErr.Message != "invalid name" { + t.Errorf("Message = %q, want %q", apiErr.Message, "invalid name") + } +} + +func TestDo_ErrorShape_MessageField(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"message":"something broke"}`)) + })) + defer srv.Close() + + c := repoapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodGet, "/repository/x", nil, nil, nil) + + var apiErr *repoapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *repoapi.APIError", err) + } + if apiErr.Message != "something broke" { + t.Errorf("Message = %q, want %q", apiErr.Message, "something broke") + } +} + +func TestDo_ErrorShape_ValidationErrors(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"errors":{"name":["is required"]}}`)) + })) + defer srv.Close() + + c := repoapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodPost, "/repository", nil, map[string]string{}, nil) + + var apiErr *repoapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *repoapi.APIError", err) + } + if apiErr.Message != "name: is required" { + t.Errorf("Message = %q, want %q", apiErr.Message, "name: is required") + } +} + +func TestDo_ErrorShape_EmptyBody(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer srv.Close() + + c := repoapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodGet, "/repository/x", nil, nil, nil) + + var apiErr *repoapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *repoapi.APIError", err) + } + if got := apiErr.Error(); got != "api error 500: (empty response body)" { + t.Errorf("Error() = %q, want %q", got, "api error 500: (empty response body)") + } +} + +func TestDo_RequestIDIsCapturedFromHeaderAndIncludedInError(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Request-Id", "req-42") + w.WriteHeader(http.StatusBadGateway) + _, _ = w.Write([]byte(`{"error":"upstream down"}`)) + })) + defer srv.Close() + + c := repoapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodGet, "/repository/x", nil, nil, nil) + + var apiErr *repoapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *repoapi.APIError", err) + } + if apiErr.RequestID != "req-42" { + t.Errorf("RequestID = %q, want %q", apiErr.RequestID, "req-42") + } + want := "api error 502: upstream down (request-id: req-42)" + if got := apiErr.Error(); got != want { + t.Errorf("Error() = %q, want %q", got, want) + } +} + +func TestDo_2xxWithNoOutTargetDoesNotError(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + + c := repoapi.New(srv.URL, "key", "proj-1") + if err := c.Do(context.Background(), http.MethodDelete, "/repository/x", nil, nil, nil); err != nil { + t.Fatalf("Do() error = %v", err) + } +} + +func TestNew_ExposesProjectID(t *testing.T) { + c := repoapi.New("https://example.invalid", "key", "proj-42") + if got := c.ProjectID(); got != "proj-42" { + t.Errorf("ProjectID() = %q, want %q", got, "proj-42") + } +} diff --git a/internal/workflowapi/client_test.go b/internal/workflowapi/client_test.go new file mode 100644 index 0000000..2f2f05f --- /dev/null +++ b/internal/workflowapi/client_test.go @@ -0,0 +1,148 @@ +package workflowapi_test + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cloud-ru/evolution-devservices-cli/internal/workflowapi" +) + +func TestDo_SuccessDecodesJSONAndSendsAPIKey(t *testing.T) { + var gotAPIKey, gotPath string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAPIKey = r.Header.Get("X-API-KEY") + gotPath = r.URL.Path + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"id":"run-1","status":"running"}`)) + })) + defer srv.Close() + + c := workflowapi.New(srv.URL, "test-api-key", "proj-1") + + var out struct { + ID string `json:"id"` + Status string `json:"status"` + } + err := c.Do(context.Background(), http.MethodGet, "/run/run-1", nil, nil, &out) + if err != nil { + t.Fatalf("Do() error = %v", err) + } + if out.ID != "run-1" || out.Status != "running" { + t.Errorf("decoded = %+v, want id=run-1 status=running", out) + } + if gotAPIKey != "test-api-key" { + t.Errorf("X-API-KEY header = %q, want %q", gotAPIKey, "test-api-key") + } + if gotPath != "/run/run-1" { + t.Errorf("request path = %q, want %q", gotPath, "/run/run-1") + } +} + +func TestDo_SetTokenChangesSubsequentRequests(t *testing.T) { + var gotAPIKey string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAPIKey = r.Header.Get("X-API-KEY") + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + + c := workflowapi.New(srv.URL, "old-key", "proj-1") + c.SetToken("new-key") + + if err := c.Do(context.Background(), http.MethodGet, "/run/x", nil, nil, nil); err != nil { + t.Fatalf("Do() error = %v", err) + } + if gotAPIKey != "new-key" { + t.Errorf("X-API-KEY header = %q, want %q (after SetToken)", gotAPIKey, "new-key") + } +} + +func TestDo_ErrorShape_ErrorField(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":"invalid pipeline"}`)) + })) + defer srv.Close() + + c := workflowapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodPost, "/pipeline/x/run", nil, map[string]string{}, nil) + + var apiErr *workflowapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *workflowapi.APIError", err) + } + if apiErr.Message != "invalid pipeline" { + t.Errorf("Message = %q, want %q", apiErr.Message, "invalid pipeline") + } +} + +func TestDo_ErrorShape_ValidationErrors(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"errors":{"branch":["is required"]}}`)) + })) + defer srv.Close() + + c := workflowapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodPost, "/application", nil, map[string]string{}, nil) + + var apiErr *workflowapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *workflowapi.APIError", err) + } + if apiErr.Message != "branch: is required" { + t.Errorf("Message = %q, want %q", apiErr.Message, "branch: is required") + } +} + +func TestDo_ErrorShape_EmptyBody(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer srv.Close() + + c := workflowapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodGet, "/run/x", nil, nil, nil) + + var apiErr *workflowapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *workflowapi.APIError", err) + } + if got := apiErr.Error(); got != "workflow api error 500: (empty response body)" { + t.Errorf("Error() = %q, want %q", got, "workflow api error 500: (empty response body)") + } +} + +func TestDo_RequestIDIsCapturedFromHeaderAndIncludedInError(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Request-Id", "wf-req-7") + w.WriteHeader(http.StatusBadGateway) + _, _ = w.Write([]byte(`{"error":"upstream down"}`)) + })) + defer srv.Close() + + c := workflowapi.New(srv.URL, "key", "proj-1") + err := c.Do(context.Background(), http.MethodGet, "/run/x", nil, nil, nil) + + var apiErr *workflowapi.APIError + if !errors.As(err, &apiErr) { + t.Fatalf("error = %v, want *workflowapi.APIError", err) + } + if apiErr.RequestID != "wf-req-7" { + t.Errorf("RequestID = %q, want %q", apiErr.RequestID, "wf-req-7") + } + want := "workflow api error 502: upstream down (request-id: wf-req-7)" + if got := apiErr.Error(); got != want { + t.Errorf("Error() = %q, want %q", got, want) + } +} + +func TestNew_ExposesProjectID(t *testing.T) { + c := workflowapi.New("https://example.invalid", "key", "proj-42") + if got := c.ProjectID(); got != "proj-42" { + t.Errorf("ProjectID() = %q, want %q", got, "proj-42") + } +} From c8035e7bcc566749a4308a7c94c3ca3886033686 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Tue, 18 Aug 2026 13:59:54 +0300 Subject: [PATCH 2/2] fix: rename unused handler request params to _ in new tests golangci-lint's revive (unused-parameter) flagged the httptest handler closures that don't reference their *http.Request argument. --- internal/repoapi/client_test.go | 12 ++++++------ internal/workflowapi/client_test.go | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/repoapi/client_test.go b/internal/repoapi/client_test.go index 24f70c5..4ebfd06 100644 --- a/internal/repoapi/client_test.go +++ b/internal/repoapi/client_test.go @@ -42,7 +42,7 @@ func TestDo_SuccessDecodesJSONAndSendsAPIKey(t *testing.T) { } func TestDo_ErrorShape_ErrorField(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte(`{"error":"invalid name"}`)) })) @@ -64,7 +64,7 @@ func TestDo_ErrorShape_ErrorField(t *testing.T) { } func TestDo_ErrorShape_MessageField(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte(`{"message":"something broke"}`)) })) @@ -83,7 +83,7 @@ func TestDo_ErrorShape_MessageField(t *testing.T) { } func TestDo_ErrorShape_ValidationErrors(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusUnprocessableEntity) _, _ = w.Write([]byte(`{"errors":{"name":["is required"]}}`)) })) @@ -102,7 +102,7 @@ func TestDo_ErrorShape_ValidationErrors(t *testing.T) { } func TestDo_ErrorShape_EmptyBody(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() @@ -120,7 +120,7 @@ func TestDo_ErrorShape_EmptyBody(t *testing.T) { } func TestDo_RequestIDIsCapturedFromHeaderAndIncludedInError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("X-Request-Id", "req-42") w.WriteHeader(http.StatusBadGateway) _, _ = w.Write([]byte(`{"error":"upstream down"}`)) @@ -144,7 +144,7 @@ func TestDo_RequestIDIsCapturedFromHeaderAndIncludedInError(t *testing.T) { } func TestDo_2xxWithNoOutTargetDoesNotError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) })) defer srv.Close() diff --git a/internal/workflowapi/client_test.go b/internal/workflowapi/client_test.go index 2f2f05f..497fea5 100644 --- a/internal/workflowapi/client_test.go +++ b/internal/workflowapi/client_test.go @@ -61,7 +61,7 @@ func TestDo_SetTokenChangesSubsequentRequests(t *testing.T) { } func TestDo_ErrorShape_ErrorField(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte(`{"error":"invalid pipeline"}`)) })) @@ -80,7 +80,7 @@ func TestDo_ErrorShape_ErrorField(t *testing.T) { } func TestDo_ErrorShape_ValidationErrors(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusUnprocessableEntity) _, _ = w.Write([]byte(`{"errors":{"branch":["is required"]}}`)) })) @@ -99,7 +99,7 @@ func TestDo_ErrorShape_ValidationErrors(t *testing.T) { } func TestDo_ErrorShape_EmptyBody(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() @@ -117,7 +117,7 @@ func TestDo_ErrorShape_EmptyBody(t *testing.T) { } func TestDo_RequestIDIsCapturedFromHeaderAndIncludedInError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("X-Request-Id", "wf-req-7") w.WriteHeader(http.StatusBadGateway) _, _ = w.Write([]byte(`{"error":"upstream down"}`))