Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add TestRun test helper for testing Run integrations #23

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 39 additions & 33 deletions runner_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@ const (
type runnerHTTP struct{}

func (r *runnerHTTP) Run(ctx context.Context, logger *slog.Logger, h Handler) {
s := &http.Server{
Addr: fmt.Sprintf(":%d", port()),
Handler: newHTTPRunMux(ctx, logger, h),
MaxHeaderBytes: mb,
}
go func() {
select {
case <-ctx.Done():
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()

logger.Info("shutting down HTTP server...")
if err := s.Shutdown(shutdownCtx); err != nil {
logger.Error("failed to shutdown server", "err", err)
}
}
}()

logger.Info("serving HTTP server on port " + strconv.Itoa(port()))
if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error("unexpected shutdown of server", "err", err)
}
}

func newHTTPRunMux(ctx context.Context, logger *slog.Logger, h Handler) *http.ServeMux {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
r, err := toRequest(req)
Expand Down Expand Up @@ -61,43 +86,24 @@ func (r *runnerHTTP) Run(ctx context.Context, logger *slog.Logger, h Handler) {
}
}))

s := &http.Server{
Addr: fmt.Sprintf(":%d", port()),
Handler: mux,
MaxHeaderBytes: mb,
}
go func() {
select {
case <-ctx.Done():
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()

logger.Info("shutting down HTTP server...")
if err := s.Shutdown(shutdownCtx); err != nil {
logger.Error("failed to shutdown server", "err", err)
}
}
}()
return mux
}

logger.Info("serving HTTP server on port " + strconv.Itoa(port()))
if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error("unexpected shutdown of server", "err", err)
}
type httpPayload struct {
Body json.RawMessage `json:"body,omitempty"`
Context json.RawMessage `json:"context,omitempty"`
AccessToken string `json:"access_token"`
Method string `json:"method"`
Params struct {
Header http.Header `json:"header"`
Query url.Values `json:"query"`
} `json:"params"`
URL string `json:"url"`
TraceID string `json:"trace_id"`
}

func toRequest(req *http.Request) (Request, error) {
var r struct {
Body json.RawMessage `json:"body"`
Context json.RawMessage `json:"context"`
AccessToken string `json:"access_token"`
Method string `json:"method"`
Params struct {
Header http.Header `json:"header"`
Query url.Values `json:"query"`
} `json:"params"`
URL string `json:"url"`
TraceID string `json:"trace_id"`
}
var r httpPayload
payload, err := io.ReadAll(io.LimitReader(req.Body, 5*mb))
if err != nil {
return Request{}, fmt.Errorf("failed to read request body: %s", err)
Expand Down
64 changes: 64 additions & 0 deletions runner_http_direct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package fdk

import (
"bytes"
"context"
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"net/url"
"os"
)

// TestRun executes the handler in order to test it through the HTTP runner.
func TestRun[T Cfg](ctx context.Context, cfg T, newHandlerFn func(_ context.Context, cfg T) Handler) (testHandlerFn func(context.Context, Request) Response) {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{}))

mux := newHTTPRunMux(ctx, logger, newHandlerFn(ctx, cfg))

return func(ctx context.Context, r Request) Response {
b, err := json.Marshal(httpPayload{
Body: r.Body,
Context: r.Context,
AccessToken: r.AccessToken,
Method: r.Method,
Params: struct {
Header http.Header `json:"header"`
Query url.Values `json:"query"`
}{Header: r.Params.Header, Query: r.Params.Query},
URL: r.URL,
TraceID: r.TraceID,
})
if err != nil {
return ErrResp(APIError{Code: http.StatusInternalServerError, Message: "unable to create http request body: " + err.Error()})
}

req, err := http.NewRequestWithContext(ctx, "POST", "/", bytes.NewReader(b))
if err != nil {
return ErrResp(APIError{Code: http.StatusInternalServerError, Message: "unable to create http request: " + err.Error()})
}

rec := httptest.NewRecorder()

mux.ServeHTTP(rec, req)

var respBody struct {
Body json.RawMessage `json:"body,omitempty"`
Code int `json:"code"`
Errs []APIError `json:"errors"`
Headers http.Header `json:"headers"`
}
err = json.Unmarshal(rec.Body.Bytes(), &respBody)
if err != nil {
return ErrResp(APIError{Code: http.StatusInternalServerError, Message: "unable to unmarshal response body: " + err.Error()})
}

return Response{
Body: respBody.Body,
Code: respBody.Code,
Errors: respBody.Errs,
Header: respBody.Headers,
}
}
}
Loading