From 3da778d188d2a842332ddb80e7447d3dd2cfc8da Mon Sep 17 00:00:00 2001 From: Mark Mandel Date: Mon, 9 Jan 2023 14:19:23 -0800 Subject: [PATCH] Review updates * Expanded test for NewEventsFromHTTPRequest * Implemented NewHTTPRequestFromEvent as well --- v2/protocol/http/utility.go | 20 ++++++- v2/protocol/http/utility_test.go | 95 +++++++++++++++++++++++++++++--- 2 files changed, 107 insertions(+), 8 deletions(-) diff --git a/v2/protocol/http/utility.go b/v2/protocol/http/utility.go index c1f16a7dd..4c6ef5f45 100644 --- a/v2/protocol/http/utility.go +++ b/v2/protocol/http/utility.go @@ -39,7 +39,25 @@ func NewEventsFromHTTPResponse(resp *nethttp.Response) ([]event.Event, error) { return binding.ToEvents(context.Background(), msg, msg.BodyReader) } -// NewHTTPRequestFromEvents creates a http.Request object that can be used with any http.Client. +// NewHTTPRequestFromEvent creates a http.Request object that can be used with any http.Client for a singular event. +func NewHTTPRequestFromEvent(ctx context.Context, url string, event event.Event) (*nethttp.Request, error) { + if err := event.Validate(); err != nil { + return nil, err + } + + req, err := nethttp.NewRequestWithContext(ctx, nethttp.MethodPost, url, nil) + if err != nil { + return nil, err + } + if err := WriteRequest(ctx, (*binding.EventMessage)(&event), req); err != nil { + return nil, err + } + + return req, nil +} + +// NewHTTPRequestFromEvents creates a http.Request object that can be used with any http.Client for sending +// a batched set of events. func NewHTTPRequestFromEvents(ctx context.Context, url string, events []event.Event) (*nethttp.Request, error) { // Sending batch events is quite straightforward, as there is only JSON format, so a simple implementation. for _, e := range events { diff --git a/v2/protocol/http/utility_test.go b/v2/protocol/http/utility_test.go index 131f517b2..37b27e00f 100644 --- a/v2/protocol/http/utility_test.go +++ b/v2/protocol/http/utility_test.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "strings" "testing" "github.com/cloudevents/sdk-go/v2/binding" @@ -92,13 +93,58 @@ func TestNewEventFromHttpResponse(t *testing.T) { } func TestNewEventsFromHTTPRequest(t *testing.T) { - req := httptest.NewRequest("POST", "http://localhost", bytes.NewReader([]byte(`[{"data":"foo","datacontenttype":"application/json","id":"id","source":"source","specversion":"1.0","type":"type"}]`))) - req.Header.Set(ContentType, event.ApplicationCloudEventsBatchJSON) + type expected struct { + len int + ids []string + } - events, err := NewEventsFromHTTPRequest(req) - require.NoError(t, err) - require.Len(t, events, 1) - test.AssertEvent(t, events[0], test.IsValid()) + fixtures := map[string]struct { + jsn string + expected expected + }{ + "single": { + jsn: `[{"data":"foo","datacontenttype":"application/json","id":"id","source":"source","specversion":"1.0","type":"type"}]`, + expected: expected{ + len: 1, + ids: []string{"id"}, + }, + }, + "triple": { + jsn: `[{"data":"foo","datacontenttype":"application/json","id":"id1","source":"source","specversion":"1.0","type":"type"},{"data":"foo","datacontenttype":"application/json","id":"id2","source":"source","specversion":"1.0","type":"type"},{"data":"foo","datacontenttype":"application/json","id":"id3","source":"source","specversion":"1.0","type":"type"}]`, + expected: expected{ + len: 3, + ids: []string{"id1", "id2", "id3"}, + }, + }, + } + + for k, v := range fixtures { + t.Run(k, func(t *testing.T) { + req := httptest.NewRequest("POST", "http://localhost", bytes.NewReader([]byte(v.jsn))) + req.Header.Set(ContentType, event.ApplicationCloudEventsBatchJSON) + + events, err := NewEventsFromHTTPRequest(req) + require.NoError(t, err) + require.Len(t, events, v.expected.len) + for i, e := range events { + test.AssertEvent(t, e, test.IsValid()) + require.Equal(t, v.expected.ids[i], events[i].ID()) + } + }) + } + + t.Run("bad request", func(t *testing.T) { + e := event.New() + e.SetID(uuid.New().String()) + e.SetSource("example/uri") + e.SetType("example.type") + require.NoError(t, e.SetData(event.ApplicationJSON, map[string]string{"hello": "world"})) + req, err := NewHTTPRequestFromEvent(context.Background(), "http://localhost", e) + require.NoError(t, err) + + _, err = NewEventsFromHTTPRequest(req) + require.ErrorContainsf(t, err, "cannot convert message to batched events", "error should include message") + }) } func TestNewEventsFromHTTPResponse(t *testing.T) { @@ -116,6 +162,42 @@ func TestNewEventsFromHTTPResponse(t *testing.T) { test.AssertEvent(t, events[0], test.IsValid()) } +func TestNewHTTPRequestFromEvent(t *testing.T) { + e := event.New() + e.SetID(uuid.New().String()) + e.SetSource("example/uri") + e.SetType("example.type") + require.NoError(t, e.SetData(event.ApplicationJSON, map[string]string{"hello": "world"})) + + // echo back what we get, so we can compare events at either side. + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(ContentType, r.Header.Get(ContentType)) + // copy across structured headers + for k, v := range r.Header { + if strings.HasPrefix(k, "Ce-") { + w.Header()[k] = v + } + } + + b, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.NoError(t, r.Body.Close()) + _, err = w.Write(b) + require.NoError(t, err) + })) + defer ts.Close() + + req, err := NewHTTPRequestFromEvent(context.Background(), ts.URL, e) + require.NoError(t, err) + + resp, err := ts.Client().Do(req) + require.NoError(t, err) + + result, err := NewEventFromHTTPResponse(resp) + require.NoError(t, err) + require.Equal(t, &e, result) +} + func TestNewHTTPRequestFromEvents(t *testing.T) { var events []event.Event e := event.New() @@ -151,6 +233,5 @@ func TestNewHTTPRequestFromEvents(t *testing.T) { result, err := NewEventsFromHTTPResponse(resp) require.NoError(t, err) - require.Equal(t, events, result) }