-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add http adaptor for pact tests (GO-7710) (#13)
- Loading branch information
Showing
5 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package g8 | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/aws/aws-lambda-go/events" | ||
"github.com/go-chi/chi/v5" | ||
"io" | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
const ( | ||
WELCOME_MESSAGE = "G8 HTTP server is running on port" | ||
UNHANDLED_ERR_MESSAGE = "unhandled error: " | ||
) | ||
|
||
type LambdaHandlerEndpoints []LambdaHandler | ||
|
||
type LambdaHandler struct { | ||
Handler interface{} | ||
Method string | ||
Path string | ||
PathParams []string | ||
} | ||
|
||
// NewHTTPHandler creates a new HTTP server that listens on the given port. | ||
func NewHTTPHandler(lambdaEndpoints LambdaHandlerEndpoints, portNumber int) { | ||
fmt.Printf("\n%s %d\n\n", WELCOME_MESSAGE, portNumber) | ||
r := chi.NewRouter() | ||
for _, l := range lambdaEndpoints { | ||
r.MethodFunc(l.Method, l.Path, LambdaAdapter(l)) | ||
} | ||
if err := http.ListenAndServe(fmt.Sprintf(":%d", portNumber), r); err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
// LambdaAdapter converts a LambdaHandler into a http.HandlerFunc. | ||
func LambdaAdapter(l LambdaHandler) func(http.ResponseWriter, *http.Request) { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
switch eventHandler := l.Handler.(type) { | ||
// APIGatewayProxyHandler | ||
case func(context.Context, events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error): | ||
fmt.Printf("%s %s: %+v \n", r.Method, r.URL.Path, l.PathParams) | ||
request := NewAPIGatewayRequestBuilder(r, l.PathParams) | ||
resp, eErr := eventHandler(context.Background(), request.Request()) | ||
if eErr != nil { | ||
fmt.Printf("%s %s\n", UNHANDLED_ERR_MESSAGE, eErr.Error()) | ||
resp, eErr = unhandledError(eErr) | ||
if eErr != nil { | ||
panic(eErr) | ||
} | ||
} | ||
|
||
w.WriteHeader(resp.StatusCode) | ||
for k, v := range resp.Headers { | ||
w.Header().Set(k, v) | ||
} | ||
for k, v := range resp.MultiValueHeaders { | ||
w.Header().Set(k, strings.Join(v, ",")) | ||
} | ||
|
||
if _, wErr := w.Write([]byte(resp.Body)); wErr != nil { | ||
panic(wErr) | ||
} | ||
default: | ||
panic(fmt.Sprintf("unknown type: %T", l.Handler)) | ||
} | ||
} | ||
} | ||
|
||
// APIGatewayRequestBuilder is a builder for APIGatewayProxyRequest. | ||
type APIGatewayRequestBuilder struct { | ||
pathParams []string | ||
request *http.Request | ||
} | ||
|
||
// Headers returns the headers of the request. | ||
func (b *APIGatewayRequestBuilder) Headers() map[string]string { | ||
headers := make(map[string]string, len(b.request.Header)) | ||
for k, v := range b.request.Header { | ||
headers[k] = strings.Join(v, ",") | ||
} | ||
|
||
return headers | ||
} | ||
|
||
// QueryStrings returns the query strings of the request. | ||
func (b *APIGatewayRequestBuilder) QueryStrings() (map[string]string, map[string][]string) { | ||
query := b.request.URL.Query() | ||
queryParams := make(map[string]string, len(query)) | ||
MultiQueryParams := make(map[string][]string, len(query)) | ||
for k, v := range query { | ||
queryParams[k] = strings.Join(v, ",") | ||
MultiQueryParams[k] = v | ||
} | ||
|
||
return queryParams, MultiQueryParams | ||
} | ||
|
||
// PathParams returns the path parameters of the request. | ||
func (b *APIGatewayRequestBuilder) PathParams() map[string]string { | ||
pathParams := make(map[string]string, len(b.pathParams)) | ||
for _, v := range b.pathParams { | ||
pathParams[v] = chi.URLParam(b.request, v) | ||
} | ||
|
||
return pathParams | ||
} | ||
|
||
// Body returns the body of the request. | ||
func (b *APIGatewayRequestBuilder) Body() string { | ||
if body, err := io.ReadAll(b.request.Body); err == nil { | ||
return string(body) | ||
} | ||
return "" | ||
} | ||
|
||
// Request returns the APIGatewayProxyRequest. | ||
func (b *APIGatewayRequestBuilder) Request() events.APIGatewayProxyRequest { | ||
query, multiQuery := b.QueryStrings() | ||
return events.APIGatewayProxyRequest{ | ||
Path: b.request.URL.Path, | ||
HTTPMethod: b.request.Method, | ||
Headers: b.Headers(), | ||
MultiValueHeaders: b.request.Header, | ||
QueryStringParameters: query, | ||
MultiValueQueryStringParameters: multiQuery, | ||
PathParameters: b.PathParams(), | ||
Body: b.Body(), | ||
} | ||
} | ||
|
||
// NewAPIGatewayRequestBuilder creates a new APIGatewayRequestBuilder. | ||
func NewAPIGatewayRequestBuilder(request *http.Request, pathParams []string) *APIGatewayRequestBuilder { | ||
return &APIGatewayRequestBuilder{ | ||
request: request, | ||
pathParams: pathParams, | ||
} | ||
} | ||
|
||
// unhandledError returns an APIGatewayProxyResponse with the given error. | ||
func unhandledError(err error) (*events.APIGatewayProxyResponse, error) { | ||
var newErr Err | ||
switch err := err.(type) { | ||
case Err: | ||
newErr = err | ||
default: | ||
newErr = ErrInternalServer | ||
} | ||
|
||
b, err := json.Marshal(newErr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
r := new(events.APIGatewayProxyResponse) | ||
r.Headers = make(map[string]string) | ||
r.Headers["Content-Type"] = "application/json" | ||
r.StatusCode = newErr.Status | ||
r.Body = string(b) | ||
|
||
return r, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package g8_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/aws/aws-lambda-go/events" | ||
"github.com/stretchr/testify/assert" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/JSainsburyPLC/g8" | ||
) | ||
|
||
func TestLambdaAdapter(t *testing.T) { | ||
l := g8.LambdaHandler{ | ||
Handler: func(ctx context.Context, r events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) { | ||
return &events.APIGatewayProxyResponse{ | ||
StatusCode: http.StatusOK, | ||
Headers: map[string]string{"Content-Type": "text/plain"}, | ||
MultiValueHeaders: map[string][]string{"Set-Cookie": {"cookie1", "cookie2"}}, | ||
Body: "success", | ||
}, nil | ||
}, | ||
Method: http.MethodGet, | ||
Path: "/test/url/path/{var1}/{var2}", | ||
PathParams: []string{"var1", "var2"}, | ||
} | ||
|
||
w := httptest.NewRecorder() | ||
r := httptest.NewRequest(http.MethodGet, "/test/url/path/var1/var2", nil) | ||
r.Header.Set("Content-Type", "application/json") | ||
g8.LambdaAdapter(l)(w, r) | ||
|
||
assert.Equal(t, http.StatusOK, w.Code) | ||
assert.Equal(t, "text/plain", w.Header().Get("Content-Type")) | ||
assert.Equal(t, "cookie1,cookie2", w.Header().Get("Set-Cookie")) | ||
assert.Equal(t, "success", w.Body.String()) | ||
} | ||
|
||
func TestLambdaAdapter_g8_error(t *testing.T) { | ||
l := g8.LambdaHandler{ | ||
Handler: func(ctx context.Context, r events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) { | ||
return nil, g8.ErrInternalServer | ||
}, | ||
Method: http.MethodGet, | ||
Path: "/test/url/path", | ||
} | ||
|
||
w := httptest.NewRecorder() | ||
r := httptest.NewRequest(http.MethodGet, "/test/url/path/var1/var2", nil) | ||
r.Header.Set("Content-Type", "application/json") | ||
g8.LambdaAdapter(l)(w, r) | ||
|
||
assert.Equal(t, http.StatusInternalServerError, w.Code) | ||
assert.Equal(t, "application/json", w.Header().Get("Content-Type")) | ||
assert.Equal(t, `{"code":"INTERNAL_SERVER_ERROR","detail":"Internal server error"}`, w.Body.String()) | ||
} | ||
|
||
func TestLambdaAdapter_generic_error(t *testing.T) { | ||
l := g8.LambdaHandler{ | ||
Handler: func(ctx context.Context, r events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) { | ||
return nil, fmt.Errorf("generic error") | ||
}, | ||
Method: http.MethodGet, | ||
Path: "/test/url/path", | ||
} | ||
|
||
w := httptest.NewRecorder() | ||
r := httptest.NewRequest(http.MethodGet, "/test/url/path/var1/var2", nil) | ||
r.Header.Set("Content-Type", "application/json") | ||
g8.LambdaAdapter(l)(w, r) | ||
|
||
assert.Equal(t, http.StatusInternalServerError, w.Code) | ||
assert.Equal(t, "application/json", w.Header().Get("Content-Type")) | ||
assert.Equal(t, `{"code":"INTERNAL_SERVER_ERROR","detail":"Internal server error"}`, w.Body.String()) | ||
} |