diff --git a/hcloud/client_generic.go b/hcloud/client_generic.go new file mode 100644 index 00000000..072ce98a --- /dev/null +++ b/hcloud/client_generic.go @@ -0,0 +1,86 @@ +package hcloud + +import ( + "bytes" + "context" + "encoding/json" +) + +func getRequest[Schema any](ctx context.Context, client *Client, url string) (*Schema, *Response, error) { + req, err := client.NewRequest(ctx, "GET", url, nil) + if err != nil { + return nil, nil, err + } + + var respBody Schema + resp, err := client.Do(req, &respBody) + if err != nil { + return nil, resp, err + } + + return &respBody, resp, nil +} + +func postRequest[Schema any](ctx context.Context, client *Client, url string, reqBody any) (*Schema, *Response, error) { + reqBodyBytes, err := json.Marshal(reqBody) + if err != nil { + return nil, nil, err + } + + req, err := client.NewRequest(ctx, "POST", url, bytes.NewReader(reqBodyBytes)) + if err != nil { + return nil, nil, err + } + + var respBody Schema + resp, err := client.Do(req, &respBody) + if err != nil { + return nil, resp, err + } + + return &respBody, resp, nil +} + +func putRequest[Schema any](ctx context.Context, client *Client, url string, reqBody any) (*Schema, *Response, error) { + reqBodyBytes, err := json.Marshal(reqBody) + if err != nil { + return nil, nil, err + } + + req, err := client.NewRequest(ctx, "PUT", url, bytes.NewReader(reqBodyBytes)) + if err != nil { + return nil, nil, err + } + + var respBody Schema + resp, err := client.Do(req, &respBody) + if err != nil { + return nil, resp, err + } + + return &respBody, resp, nil +} + +func deleteRequest[Schema any](ctx context.Context, client *Client, url string) (*Schema, *Response, error) { + req, err := client.NewRequest(ctx, "DELETE", url, nil) + if err != nil { + return nil, nil, err + } + + var respBody Schema + resp, err := client.Do(req, &respBody) + if err != nil { + return nil, resp, err + } + + return &respBody, resp, nil +} + +func deleteRequestNoResult(ctx context.Context, client *Client, url string) (*Response, error) { + req, err := client.NewRequest(ctx, "DELETE", url, nil) + if err != nil { + return nil, err + } + + return client.Do(req, nil) +} diff --git a/hcloud/client_generic_test.go b/hcloud/client_generic_test.go new file mode 100644 index 00000000..095575b2 --- /dev/null +++ b/hcloud/client_generic_test.go @@ -0,0 +1,115 @@ +package hcloud + +import ( + "io" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/hetznercloud/hcloud-go/v2/hcloud/exp/mockutil" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" +) + +func TestGenericRequest(t *testing.T) { + t.Run("get", func(t *testing.T) { + ctx, server, client := makeTestUtils(t) + + server.Expect([]mockutil.Request{ + { + Method: "GET", Path: "/resource", + Status: 200, + JSON: schema.ActionGetResponse{Action: schema.Action{ID: 1234}}, + }, + }) + + respBody, resp, err := getRequest[schema.ActionGetResponse](ctx, client, "/resource") + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, respBody) + + require.Equal(t, int64(1234), respBody.Action.ID) + }) + + t.Run("post", func(t *testing.T) { + ctx, server, client := makeTestUtils(t) + + server.Expect([]mockutil.Request{ + { + Method: "POST", Path: "/resource", + Want: func(t *testing.T, r *http.Request) { + bodyBytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.JSONEq(t, `{"hello": "world"}`, string(bodyBytes)) + }, + Status: 200, + JSON: schema.ActionGetResponse{Action: schema.Action{ID: 1234}}, + }, + }) + + respBody, resp, err := postRequest[schema.ActionGetResponse](ctx, client, "/resource", map[string]string{"hello": "world"}) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, respBody) + + require.Equal(t, int64(1234), respBody.Action.ID) + }) + + t.Run("put", func(t *testing.T) { + ctx, server, client := makeTestUtils(t) + + server.Expect([]mockutil.Request{ + { + Method: "PUT", Path: "/resource", + Want: func(t *testing.T, r *http.Request) { + bodyBytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.JSONEq(t, `{"hello": "world"}`, string(bodyBytes)) + }, + Status: 200, + JSON: schema.ActionGetResponse{Action: schema.Action{ID: 1234}}, + }, + }) + + respBody, resp, err := putRequest[schema.ActionGetResponse](ctx, client, "/resource", map[string]string{"hello": "world"}) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, respBody) + + require.Equal(t, int64(1234), respBody.Action.ID) + }) + + t.Run("delete", func(t *testing.T) { + ctx, server, client := makeTestUtils(t) + + server.Expect([]mockutil.Request{ + { + Method: "DELETE", Path: "/resource", + Status: 200, + JSON: schema.ActionGetResponse{Action: schema.Action{ID: 1234}}, + }, + }) + + respBody, resp, err := deleteRequest[schema.ActionGetResponse](ctx, client, "/resource") + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, respBody) + + require.Equal(t, int64(1234), respBody.Action.ID) + }) + + t.Run("delete no result", func(t *testing.T) { + ctx, server, client := makeTestUtils(t) + + server.Expect([]mockutil.Request{ + { + Method: "DELETE", Path: "/resource", + Status: 204, + }, + }) + + resp, err := deleteRequestNoResult(ctx, client, "/resource") + require.NoError(t, err) + require.NotNil(t, resp) + }) +} diff --git a/hcloud/client_test.go b/hcloud/client_test.go index 6b44f484..743699ee 100644 --- a/hcloud/client_test.go +++ b/hcloud/client_test.go @@ -15,9 +15,24 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/hetznercloud/hcloud-go/v2/hcloud/exp/mockutil" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) +func makeTestUtils(t *testing.T) (context.Context, *mockutil.Server, *Client) { + ctx := context.Background() + + server := mockutil.NewServer(t, nil) + + client := NewClient( + WithEndpoint(server.URL), + WithRetryOpts(RetryOpts{BackoffFunc: ConstantBackoff(0), MaxRetries: 5}), + WithPollOpts(PollOpts{BackoffFunc: ConstantBackoff(0)}), + ) + + return ctx, server, client +} + type testEnv struct { Server *httptest.Server Mux *http.ServeMux