Skip to content

Commit

Permalink
Merge pull request #34 from CrowdStrike/fix/add_missing_fn_info
Browse files Browse the repository at this point in the history
fix: add missing FnID and FnVersion to request handlers
  • Loading branch information
jsteenb2 committed Sep 18, 2024
2 parents 7c93a95 + 4d09f95 commit 10a014e
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 6 deletions.
17 changes: 15 additions & 2 deletions file_internal_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fdk

import (
"fmt"
"testing"
"time"
)
Expand Down Expand Up @@ -246,12 +247,24 @@ func TestNormalizeFile(t *testing.T) {
}
}

func EqualVals[T comparable](t testing.TB, want, got T) bool {
func EqualVals[T comparable](t testing.TB, want, got T, args ...any) bool {
t.Helper()

var errMsg string
if len(args) > 0 {
format, ok := args[0].(string)
if ok {
errMsg = fmt.Sprintf(format, args[1:]...)
}
}

match := want == got
if !match {
t.Errorf("values not equal:\n\t\twant:\t%#v\n\t\tgot:\t%#v", want, got)
msg := "values not equal:\n\twant:\t%#v\n\tgot:\t%#v"
if errMsg != "" {
msg += "\n\n\t" + errMsg
}
t.Errorf(msg, want, got)
}
return match
}
10 changes: 6 additions & 4 deletions handler_fns.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ func (h HandlerFn) Handle(ctx context.Context, r Request) Response {
// HandleFnOf provides a means to translate the incoming requests to the destination body type.
// This normalizes the sad path and provides the caller with a zero fuss request to work with. Reducing
// json boilerplate for what is essentially the same operation on different types.
func HandleFnOf[T any](fn func(context.Context, RequestOf[T]) Response) Handler {
func HandleFnOf[T any](fn func(ctx context.Context, r RequestOf[T]) Response) Handler {
return HandlerFn(func(ctx context.Context, r Request) Response {
var v T
if err := json.NewDecoder(r.Body).Decode(&v); err != nil {
return Response{Errors: []APIError{{Code: http.StatusBadRequest, Message: "failed to unmarshal payload: " + err.Error()}}}
}

return fn(ctx, RequestOf[T]{
FnID: r.FnID,
FnVersion: r.FnVersion,
Body: v,
Context: r.Context,
Params: r.Params,
Expand All @@ -39,7 +41,7 @@ func HandleFnOf[T any](fn func(context.Context, RequestOf[T]) Response) Handler
// HandlerFnOfOK provides a means to translate the incoming requests to the destination body type
// and execute validation on that type. This normalizes the sad path for both the unmarshalling of
// the request body and the validation of that request type using its OK() method.
func HandlerFnOfOK[T interface{ OK() []APIError }](fn func(context.Context, RequestOf[T]) Response) Handler {
func HandlerFnOfOK[T interface{ OK() []APIError }](fn func(ctx context.Context, r RequestOf[T]) Response) Handler {
return HandleFnOf(func(ctx context.Context, r RequestOf[T]) Response {
if errs := r.Body.OK(); len(errs) > 0 {
return ErrResp(errs...)
Expand Down Expand Up @@ -71,7 +73,7 @@ type WorkflowCtx struct {
// HandleWorkflow provides a means to create a handler with workflow integration. This function
// does not have an opinion on the request body but does expect a workflow integration. Typically,
// this is useful for DELETE/GET handlers.
func HandleWorkflow(fn func(context.Context, Request, WorkflowCtx) Response) Handler {
func HandleWorkflow(fn func(ctx context.Context, r Request, wrkCtx WorkflowCtx) Response) Handler {
return HandlerFn(func(ctx context.Context, r Request) Response {
var w WorkflowCtx
if err := json.Unmarshal(r.Context, &w); err != nil {
Expand All @@ -85,7 +87,7 @@ func HandleWorkflow(fn func(context.Context, Request, WorkflowCtx) Response) Han
// HandleWorkflowOf provides a means to create a handler with Workflow integration. This
// function is useful when you expect a request body and have workflow integrations. Typically, this
// is with PATCH/POST/PUT handlers.
func HandleWorkflowOf[T any](fn func(context.Context, RequestOf[T], WorkflowCtx) Response) Handler {
func HandleWorkflowOf[T any](fn func(ctx context.Context, r RequestOf[T], wrkCtx WorkflowCtx) Response) Handler {
return HandleWorkflow(func(ctx context.Context, r Request, workflowCtx WorkflowCtx) Response {
next := HandleFnOf(func(ctx context.Context, r RequestOf[T]) Response {
return fn(ctx, r, workflowCtx)
Expand Down
217 changes: 217 additions & 0 deletions handler_fns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package fdk_test

import (
"context"
"encoding/json"
"net/http"
"net/url"
"strings"
"testing"

fdk "github.com/CrowdStrike/foundry-fn-go"
)

type testBody struct {
Name string `json:"name"`
}

func (t testBody) OK() []fdk.APIError {
if t.Name == "fail" {
return []fdk.APIError{{Code: http.StatusBadRequest, Message: "got a fail"}}
}
return nil
}

func TestHandlers(t *testing.T) {
mux := fdk.NewMux()
mux.Post("/handler-fn-of", fdk.HandleFnOf(func(ctx context.Context, r fdk.RequestOf[testBody]) fdk.Response {
return fdk.Response{Code: 200, Body: fdk.JSON(r)}
}))
mux.Post("/handle-fn-of-ok", fdk.HandlerFnOfOK(func(ctx context.Context, r fdk.RequestOf[testBody]) fdk.Response {
return fdk.Response{Code: 200, Body: fdk.JSON(r)}
}))
mux.Get("/handle-workflow", fdk.HandleWorkflow(func(ctx context.Context, r fdk.Request, wrkCtx fdk.WorkflowCtx) fdk.Response {
return fdk.Response{Code: 200, Body: fdk.JSON(r)}
}))
mux.Put("/handle-workflow-of", fdk.HandleWorkflowOf(func(ctx context.Context, r fdk.RequestOf[testBody], wrkCtx fdk.WorkflowCtx) fdk.Response {
return fdk.Response{Code: 200, Body: fdk.JSON(r)}
}))

params := struct {
Header http.Header
Query url.Values
}{
Header: http.Header{"X-Cs-Foo": []string{"header"}},
Query: url.Values{"key": []string{"value"}},
}

wrkCtxVal, err := json.Marshal(fdk.WorkflowCtx{
ActivityExecID: "act-exec-id",
AppID: "app-id",
CID: "cid",
OwnerCID: "owner-cid",
DefinitionID: "def-id",
DefinitionVersion: 9000,
ExecutionID: "exec-id",
})
mustNoErr(t, err)

t.Run("HandleFnOf", func(t *testing.T) {
resp := mux.Handle(context.TODO(), fdk.Request{
FnID: "id1",
FnVersion: 1,
Body: strings.NewReader(`{"name":"frodo"}`),
Context: json.RawMessage(`{"some":"ctx"}`),
URL: "/handler-fn-of",
Params: params,
Method: "POST",
AccessToken: "access",
TraceID: "trace-id",
})
gotStatusOK(t, resp)

b, err := resp.Body.MarshalJSON()
mustNoErr(t, err)

var got fdk.RequestOf[testBody]
mustNoErr(t, json.Unmarshal(b, &got))

fdk.EqualVals(t, "id1", got.FnID)
fdk.EqualVals(t, 1, got.FnVersion)
fdk.EqualVals(t, "/handler-fn-of", got.URL)
fdk.EqualVals(t, "POST", got.Method)
fdk.EqualVals(t, `{"some":"ctx"}`, string(got.Context))
fdk.EqualVals(t, "header", got.Params.Header.Get("X-Cs-Foo"))
fdk.EqualVals(t, "value", got.Params.Query.Get("key"))
fdk.EqualVals(t, "access", got.AccessToken)
fdk.EqualVals(t, "trace-id", got.TraceID)

wantFoo := testBody{Name: "frodo"}
fdk.EqualVals(t, wantFoo, got.Body)
})

t.Run("HandlerFnOfOK", func(t *testing.T) {
t.Run("with valid name", func(t *testing.T) {
resp := mux.Handle(context.TODO(), fdk.Request{
FnID: "id1",
FnVersion: 1,
Body: strings.NewReader(`{"name":"frodo"}`),
Context: json.RawMessage(`{"some":"ctx"}`),
URL: "/handle-fn-of-ok",
Params: params,
Method: "POST",
AccessToken: "access",
TraceID: "trace-id",
})
gotStatusOK(t, resp)

b, err := resp.Body.MarshalJSON()
mustNoErr(t, err)

var got fdk.RequestOf[testBody]
mustNoErr(t, json.Unmarshal(b, &got))

fdk.EqualVals(t, "id1", got.FnID)
fdk.EqualVals(t, 1, got.FnVersion)
fdk.EqualVals(t, "/handle-fn-of-ok", got.URL)
fdk.EqualVals(t, "POST", got.Method)
fdk.EqualVals(t, `{"some":"ctx"}`, string(got.Context))
fdk.EqualVals(t, "header", got.Params.Header.Get("X-Cs-Foo"))
fdk.EqualVals(t, "value", got.Params.Query.Get("key"))
fdk.EqualVals(t, "access", got.AccessToken)
fdk.EqualVals(t, "trace-id", got.TraceID)

wantFoo := testBody{Name: "frodo"}
fdk.EqualVals(t, wantFoo, got.Body)
})

t.Run("with invalid name", func(t *testing.T) {
resp := mux.Handle(context.TODO(), fdk.Request{
FnID: "id1",
FnVersion: 1,
Body: strings.NewReader(`{"name":"fail"}`),
URL: "/handle-fn-of-ok",
Method: "POST",
})
fdk.EqualVals(t, http.StatusBadRequest, resp.Code)
fdk.EqualVals(t, 1, len(resp.Errors), "got invalid errors: %s", resp.Errors)
fdk.EqualVals(t, fdk.APIError{Code: http.StatusBadRequest, Message: "got a fail"}, resp.Errors[0])
})
})

t.Run("HandleWorkflow", func(t *testing.T) {
resp := mux.Handle(context.TODO(), fdk.Request{
FnID: "id1",
FnVersion: 1,
Context: wrkCtxVal,
URL: "/handle-workflow",
Params: params,
Method: "GET",
AccessToken: "access",
TraceID: "trace-id",
})
gotStatusOK(t, resp)

b, err := resp.Body.MarshalJSON()
mustNoErr(t, err)

var got fdk.Request
mustNoErr(t, json.Unmarshal(b, &got))

fdk.EqualVals(t, "id1", got.FnID)
fdk.EqualVals(t, 1, got.FnVersion)
fdk.EqualVals(t, "/handle-workflow", got.URL)
fdk.EqualVals(t, "GET", got.Method)
fdk.EqualVals(t, string(wrkCtxVal), string(got.Context))
fdk.EqualVals(t, "header", got.Params.Header.Get("X-Cs-Foo"))
fdk.EqualVals(t, "value", got.Params.Query.Get("key"))
fdk.EqualVals(t, "access", got.AccessToken)
fdk.EqualVals(t, "trace-id", got.TraceID)
})

t.Run("HandleWorkflowOf", func(t *testing.T) {
resp := mux.Handle(context.TODO(), fdk.Request{
FnID: "id1",
FnVersion: 1,
Body: strings.NewReader(`{"name":"frodo"}`),
Context: wrkCtxVal,
URL: "/handle-workflow-of",
Params: params,
Method: "PUT",
AccessToken: "access",
TraceID: "trace-id",
})
gotStatusOK(t, resp)

b, err := resp.Body.MarshalJSON()
mustNoErr(t, err)

var got fdk.RequestOf[testBody]
mustNoErr(t, json.Unmarshal(b, &got))

fdk.EqualVals(t, "id1", got.FnID)
fdk.EqualVals(t, 1, got.FnVersion)
fdk.EqualVals(t, "/handle-workflow-of", got.URL)
fdk.EqualVals(t, "PUT", got.Method)
fdk.EqualVals(t, string(wrkCtxVal), string(got.Context))
fdk.EqualVals(t, "header", got.Params.Header.Get("X-Cs-Foo"))
fdk.EqualVals(t, "value", got.Params.Query.Get("key"))
fdk.EqualVals(t, "access", got.AccessToken)
fdk.EqualVals(t, "trace-id", got.TraceID)

wantFoo := testBody{Name: "frodo"}
fdk.EqualVals(t, wantFoo, got.Body)
})
}

func gotStatusOK(t *testing.T, resp fdk.Response) {
t.Helper()

b, _ := json.MarshalIndent(resp.Errors, "", " ")
equals := fdk.EqualVals(t, 0, len(resp.Errors), "errors encountered:\n"+string(b))
equals = fdk.EqualVals(t, http.StatusOK, resp.Code, "status code is invalid") && equals

if !equals {
t.FailNow()
}
}

0 comments on commit 10a014e

Please sign in to comment.