diff --git a/hcloud/exp/mockutils/mockutils.go b/hcloud/exp/mockutils/mockutils.go new file mode 100644 index 00000000..51d0774f --- /dev/null +++ b/hcloud/exp/mockutils/mockutils.go @@ -0,0 +1,72 @@ +package mockutils + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +// Request describes a http request that a [httptest.Server] should receive, and the +// corresponding response to return. +// +// Additional checks on the request (e.g. request body) may be added using the +// [Request.Want] function. +// +// The response body is populated from either a JSON struct, or a JSON string. +type Request struct { + Method string + Path string + Want func(t *testing.T, r *http.Request) + + Status int + JSON any + JSONRaw string +} + +// Handler is used with a [httptest.Server] to mock http requests provided by the user. +// +// Request matching is based on the request count, and the user provided request will be +// iterated over. +func Handler(t *testing.T, requests []Request) http.HandlerFunc { + t.Helper() + + index := 0 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if testing.Verbose() { + t.Logf("call %d: %s %s\n", index, r.Method, r.RequestURI) + } + + if index >= len(requests) { + t.Fatalf("received unknown call %d", index) + } + + expected := requests[index] + require.Equal(t, + expected.Method+" "+expected.Path, + r.Method+" "+r.RequestURI, + ) + + if expected.Want != nil { + expected.Want(t, r) + } + + w.WriteHeader(expected.Status) + switch { + case expected.JSON != nil: + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(expected.JSON); err != nil { + t.Fatal(err) + } + case expected.JSONRaw != "": + w.Header().Set("Content-Type", "application/json") + _, err := w.Write([]byte(expected.JSONRaw)) + if err != nil { + t.Fatal(err) + } + } + + index++ + }) +} diff --git a/hcloud/exp/mockutils/mockutils_test.go b/hcloud/exp/mockutils/mockutils_test.go new file mode 100644 index 00000000..feb9d142 --- /dev/null +++ b/hcloud/exp/mockutils/mockutils_test.go @@ -0,0 +1,53 @@ +package mockutils + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHandler(t *testing.T) { + server := httptest.NewServer(Handler(t, []Request{ + { + Method: "GET", Path: "/", + Status: 200, + JSON: struct { + Data string `json:"data"` + }{ + Data: "Hello", + }, + }, + { + Method: "GET", Path: "/", + Status: 400, + JSONRaw: `{"error": "failed"}`, + }, + })) + defer server.Close() + + // Request 1 + resp, err := http.Get(server.URL) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + assert.Equal(t, `{"data":"Hello"}`, readBody(t, resp)) + + // Request 2 + resp, err = http.Get(server.URL) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + assert.Equal(t, `{"error": "failed"}`, readBody(t, resp)) +} + +func readBody(t *testing.T, resp *http.Response) string { + t.Helper() + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + return strings.TrimSuffix(string(body), "\n") +}