diff --git a/Gopkg.lock b/Gopkg.lock index 560e2a1..b3dc47d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -19,6 +19,12 @@ revision = "583e8937c61f1af6513608ccc75c97b6abdf4ff9" version = "v1.3.0" +[[projects]] + branch = "master" + name = "github.com/bouk/monkey" + packages = ["."] + revision = "5df1f207ff77e025801505ae4d903133a0b4353f" + [[projects]] name = "github.com/cockroachdb/cmux" packages = ["."] @@ -263,6 +269,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "40303385e38e32716a606f65ba02c55615db5dbbf68100052f8265e835747293" + inputs-digest = "d6373538a2997e117949edecf1a8de3f004aa79782970ba9c3a859d2f36ab9c1" solver-name = "gps-cdcl" solver-version = 1 diff --git a/event/event.go b/event/event.go index 8755e8f..c5b8458 100644 --- a/event/event.go +++ b/event/event.go @@ -34,7 +34,7 @@ type Event struct { CloudEventsVersion string `json:"cloudEventsVersion" validate:"required"` Source string `json:"source" validate:"uri,required"` EventID string `json:"eventID" validate:"required"` - EventTime time.Time `json:"eventTime,omitempty"` + EventTime *time.Time `json:"eventTime,omitempty"` SchemaURL string `json:"schemaURL,omitempty"` Extensions zap.MapStringInterface `json:"extensions,omitempty"` ContentType string `json:"contentType,omitempty"` @@ -43,12 +43,14 @@ type Event struct { // New return new instance of Event. func New(eventType TypeName, mimeType string, payload interface{}) *Event { + now := time.Now() + event := &Event{ EventType: eventType, CloudEventsVersion: CloudEventsVersion, Source: "https://serverless.com/event-gateway/#transformationVersion=" + TransformationVersion, EventID: uuid.NewV4().String(), - EventTime: time.Now(), + EventTime: &now, ContentType: mimeType, Data: payload, Extensions: map[string]interface{}{ @@ -128,7 +130,9 @@ func (e Event) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("cloudEventsVersion", e.CloudEventsVersion) enc.AddString("source", e.Source) enc.AddString("eventID", e.EventID) - enc.AddString("eventTime", e.EventTime.String()) + if e.EventTime != nil { + enc.AddString("eventTime", e.EventTime.String()) + } if e.SchemaURL != "" { enc.AddString("schemaURL", e.SchemaURL) } @@ -181,7 +185,7 @@ func parseAsCloudEventBinary(headers http.Header, payload interface{}) (*Event, } if val, err := time.Parse(time.RFC3339, headers.Get("CE-EventTime")); err == nil { - event.EventTime = val + event.EventTime = &val } if val := headers.Get("CE-SchemaURL"); len(val) > 0 { diff --git a/event/event_test.go b/event/event_test.go index 6f47a8c..51a69dd 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -6,7 +6,9 @@ import ( "net/http" "net/url" "testing" + "time" + "github.com/bouk/monkey" eventpkg "github.com/serverless/event-gateway/event" "github.com/stretchr/testify/assert" ) @@ -36,6 +38,9 @@ func TestNew_Encoding(t *testing.T) { } func TestFromRequest(t *testing.T) { + patch := monkey.Patch(time.Now, func() time.Time { return testTime }) + defer patch.Unpatch() + for _, testCase := range fromRequestTests { t.Run(testCase.name, func(t *testing.T) { url, _ := url.Parse("http://example.com") @@ -49,6 +54,7 @@ func TestFromRequest(t *testing.T) { assert.Equal(t, testCase.expectedError, err) } else { assert.Equal(t, testCase.expectedEvent.EventType, received.EventType, "EventType is not equal") + assert.Equal(t, testCase.expectedEvent.EventTime, received.EventTime, "EventTime is not equal") assert.Equal(t, testCase.expectedEvent.Source, received.Source, "Source is not equal") assert.Equal(t, testCase.expectedEvent.CloudEventsVersion, received.CloudEventsVersion, "CloudEventsVersion is not equal") assert.Equal(t, testCase.expectedEvent.ContentType, received.ContentType, "ContentType is not equal") @@ -107,6 +113,8 @@ var newTests = []struct { }, } +var testTime = time.Date(1985, time.April, 12, 23, 20, 50, 00, time.UTC) //1985-04-12T23:20:50.00Z + var encodingTests = []struct { name string body []byte @@ -151,20 +159,37 @@ var fromRequestTests = []struct { requestHeaders: http.Header{"Content-Type": []string{"application/cloudevents+json"}}, requestBody: []byte(`{ "eventType": "user.created", + "eventTime": "1985-04-12T23:20:50.00Z", "cloudEventsVersion": "0.1", "source": "http://example.com", "eventID": "6f6ada3b-0aa2-4b3c-989a-91ffc6405f11", "contentType": "text/plain", "data": "test" }`), + expectedEvent: &eventpkg.Event{ EventType: eventpkg.TypeName("user.created"), + EventTime: &testTime, CloudEventsVersion: "0.1", Source: "http://example.com", ContentType: "text/plain", Data: "test", }, }, + { + name: "valid CloudEvent with invalid event time", + requestHeaders: http.Header{"Content-Type": []string{"application/cloudevents+json"}}, + requestBody: []byte(`{ + "eventType": "user.created", + "eventTime": "nottime", + "cloudEventsVersion": "0.1", + "source": "http://example.com", + "eventID": "6f6ada3b-0aa2-4b3c-989a-91ffc6405f11", + "contentType": "text/plain", + "data": "test" + }`), + expectedError: &time.ParseError{Layout: "\"2006-01-02T15:04:05Z07:00\"", Value: "\"nottime\"", LayoutElem: "2006", ValueElem: "nottime\"", Message: ""}, + }, { name: "error if invalid CloudEvent", requestHeaders: http.Header{"Content-Type": []string{"application/cloudevents+json"}}, @@ -197,6 +222,55 @@ var fromRequestTests = []struct { Extensions: map[string]interface{}{"myExtension": "ding"}, }, }, + { + name: "valid CloudEvent in binary mode with valid event time", + requestHeaders: http.Header{ + "Content-Type": []string{"text/plain"}, + "Ce-Eventtype": []string{"myevent"}, + "Ce-Eventtypeversion": []string{"0.1beta"}, + "Ce-Cloudeventsversion": []string{"0.1"}, + "Ce-Source": []string{"https://example.com"}, + "Ce-Eventid": []string{"778d495b-a29e-48f9-a438-a26de1e33515"}, + "Ce-Eventtime": []string{"1985-04-12T23:20:50.00Z"}, + "Ce-Schemaurl": []string{"https://example.com"}, + "Ce-X-MyExtension": []string{"ding"}, + }, + requestBody: []byte("hey there"), + expectedEvent: &eventpkg.Event{ + EventType: eventpkg.TypeName("myevent"), + EventTime: &testTime, + CloudEventsVersion: "0.1", + Source: "https://example.com", + ContentType: "text/plain", + SchemaURL: "https://example.com", + Data: []byte("hey there"), + Extensions: map[string]interface{}{"myExtension": "ding"}, + }, + }, + { + name: "valid CloudEvent in binary mode with invalid event time", + requestHeaders: http.Header{ + "Content-Type": []string{"text/plain"}, + "Ce-Eventtype": []string{"myevent"}, + "Ce-Eventtypeversion": []string{"0.1beta"}, + "Ce-Cloudeventsversion": []string{"0.1"}, + "Ce-Source": []string{"https://example.com"}, + "Ce-Eventid": []string{"778d495b-a29e-48f9-a438-a26de1e33515"}, + "Ce-Eventtime": []string{"not time"}, + "Ce-Schemaurl": []string{"https://example.com"}, + "Ce-X-MyExtension": []string{"ding"}, + }, + requestBody: []byte("hey there"), + expectedEvent: &eventpkg.Event{ + EventType: eventpkg.TypeName("myevent"), + CloudEventsVersion: "0.1", + Source: "https://example.com", + ContentType: "text/plain", + SchemaURL: "https://example.com", + Data: []byte("hey there"), + Extensions: map[string]interface{}{"myExtension": "ding"}, + }, + }, { name: "error if invalid CloudEvent in binary mode", requestHeaders: http.Header{ @@ -218,6 +292,7 @@ var fromRequestTests = []struct { requestBody: []byte("hey there"), expectedEvent: &eventpkg.Event{ EventType: eventpkg.TypeName("myevent"), + EventTime: &testTime, CloudEventsVersion: "0.1", Source: "https://serverless.com/event-gateway/#transformationVersion=0.1", ContentType: "application/octet-stream", @@ -259,6 +334,7 @@ var fromRequestTests = []struct { }`), expectedEvent: &eventpkg.Event{ EventType: eventpkg.TypeName("user.created"), + EventTime: &testTime, CloudEventsVersion: "0.1", Source: "https://serverless.com/event-gateway/#transformationVersion=0.1", ContentType: "application/json", @@ -275,6 +351,7 @@ var fromRequestTests = []struct { requestBody: []byte(`{"key": "value"}`), expectedEvent: &eventpkg.Event{ EventType: eventpkg.TypeHTTPRequest, + EventTime: &testTime, CloudEventsVersion: "0.1", Source: "https://serverless.com/event-gateway/#transformationVersion=0.1", ContentType: "application/json",