mirror of https://github.com/knative/pkg.git
291 lines
7.8 KiB
Go
291 lines
7.8 KiB
Go
/*
|
|
Copyright 2018 The Knative Authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package cloudevents_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/knative/pkg/cloudevents"
|
|
)
|
|
|
|
type FirestoreDocument struct {
|
|
Name string `json:"name"`
|
|
Fields map[string]interface{} `json:"fields"`
|
|
CreateTime time.Time `json:"createTime"`
|
|
UpdateTime time.Time `json:"updateTime"`
|
|
}
|
|
|
|
var (
|
|
webhook = "http://localhost/:sendEvent"
|
|
)
|
|
|
|
// Wrap the global functions in an HTTPMarshaller interface for table-driven testing:
|
|
type defaultMarshaller int
|
|
|
|
var Default defaultMarshaller = 0
|
|
|
|
func (defaultMarshaller) FromRequest(data interface{}, r *http.Request) (cloudevents.LoadContext, error) {
|
|
return cloudevents.FromRequest(data, r)
|
|
}
|
|
func (defaultMarshaller) NewRequest(urlString string, data interface{}, context cloudevents.SendContext) (*http.Request, error) {
|
|
return cloudevents.NewRequest(urlString, data, context)
|
|
}
|
|
|
|
type desiredVersion func(cloudevents.LoadContext) cloudevents.ContextType
|
|
|
|
var (
|
|
v01 = func(a cloudevents.LoadContext) cloudevents.ContextType { tmp := a.AsV01(); return &tmp }
|
|
v02 = func(a cloudevents.LoadContext) cloudevents.ContextType { tmp := a.AsV02(); return &tmp }
|
|
)
|
|
|
|
func TestValidRoundTrips(t *testing.T) {
|
|
doc := FirestoreDocument{
|
|
Name: "projects/demo/databases/default/documents/users/inlined",
|
|
Fields: map[string]interface{}{
|
|
"project": "eventing",
|
|
"handle": "@inlined",
|
|
},
|
|
CreateTime: time.Date(1985, 6, 5, 12, 0, 0, 0, time.UTC),
|
|
UpdateTime: time.Now().UTC(),
|
|
}
|
|
|
|
service := "firestore.googleapis.com"
|
|
|
|
encoderSets := map[string][]desiredVersion{
|
|
"v1-v1": {v01, v01},
|
|
"v1-v2": {v01, v02},
|
|
"v2-v1": {v02, v01},
|
|
"v2-v2": {v02, v02},
|
|
}
|
|
|
|
context := &cloudevents.EventContext{
|
|
CloudEventsVersion: "0.1",
|
|
EventID: "eventid-123",
|
|
EventTime: doc.UpdateTime,
|
|
EventType: "google.firestore.document.create",
|
|
EventTypeVersion: "v1beta2",
|
|
SchemaURL: "http://type.googleapis.com/google.firestore.v1beta1.Document",
|
|
ContentType: "application/json",
|
|
Source: fmt.Sprintf("//%s/%s", service, doc.Name),
|
|
Extensions: map[string]interface{}{
|
|
"purpose": "tbd",
|
|
"super": map[string]interface{}{"cali": "fragilistic", "expi": "alidocious"},
|
|
},
|
|
}
|
|
for _, test := range []struct {
|
|
name string
|
|
encoder cloudevents.HTTPMarshaller
|
|
decoder cloudevents.HTTPMarshaller
|
|
}{
|
|
{
|
|
name: "binary -> binary",
|
|
encoder: cloudevents.Binary,
|
|
decoder: cloudevents.Binary,
|
|
},
|
|
{
|
|
name: "binary -> default",
|
|
encoder: cloudevents.Binary,
|
|
decoder: Default,
|
|
},
|
|
{
|
|
name: "structured -> structured",
|
|
encoder: cloudevents.Structured,
|
|
decoder: cloudevents.Structured,
|
|
},
|
|
{
|
|
name: "structured -> default",
|
|
encoder: cloudevents.Structured,
|
|
decoder: Default,
|
|
},
|
|
} {
|
|
for encoding, convert := range encoderSets {
|
|
testName := test.name + "-" + encoding
|
|
t.Run(testName, func(t *testing.T) {
|
|
req, err := test.encoder.NewRequest(webhook, doc, convert[0](context))
|
|
if err != nil {
|
|
t.Fatalf("Failed to encode event %s", err)
|
|
}
|
|
|
|
var foundData FirestoreDocument
|
|
foundContext, err := test.decoder.FromRequest(&foundData, req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to decode event %s", err)
|
|
}
|
|
foundContext = convert[1](foundContext)
|
|
|
|
if diff := cmp.Diff(convert[0](context), convert[0](foundContext)); diff != "" {
|
|
t.Fatalf("%s: Context was transcoded lossily (-want +got): %s", testName, diff)
|
|
}
|
|
if diff := cmp.Diff(doc, foundData); diff != "" {
|
|
t.Fatalf("%s: Data was transcoded lossily (-want +got): %s", testName, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
type Address struct {
|
|
City, State string
|
|
}
|
|
type Person struct {
|
|
XMLName xml.Name `xml:"person"`
|
|
Id int `xml:"id,attr"`
|
|
FirstName string `xml:"name>first"`
|
|
LastName string `xml:"name>last"`
|
|
Age int `xml:"age"`
|
|
Height float32 `xml:"height,omitempty"`
|
|
Married bool
|
|
Address
|
|
Comment string `xml:",comment"`
|
|
}
|
|
|
|
func (Person) MarshalJSON() ([]byte, error) {
|
|
return nil, errors.New("Person cannot be JSON encoded")
|
|
}
|
|
|
|
func (*Person) UnmarshalJSON([]byte) error {
|
|
return errors.New("Person cannot be JSON decoded")
|
|
}
|
|
|
|
func TestXmlStructuredDecoding(t *testing.T) {
|
|
person := &Person{
|
|
XMLName: xml.Name{
|
|
Local: "person",
|
|
},
|
|
Id: 13,
|
|
FirstName: "John",
|
|
LastName: "Doe",
|
|
Age: 42,
|
|
Comment: " Need more details. ",
|
|
Address: Address{"Hanga Roa", "Easter Island"},
|
|
}
|
|
|
|
xmlPerson := `
|
|
<person id="13">
|
|
<name>
|
|
<first>John</first>
|
|
<last>Doe</last>
|
|
</name>
|
|
<age>42</age>
|
|
<Married>false</Married>
|
|
<City>Hanga Roa</City>
|
|
<State>Easter Island</State>
|
|
<!-- Need more details. -->
|
|
</person>`
|
|
xmlJsonSafe, err := json.Marshal(xmlPerson)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create JSON encoded XML string: %s", err)
|
|
}
|
|
|
|
eventText := `
|
|
{
|
|
"cloudEventsVersion": "0.1",
|
|
"eventID": "1234",
|
|
"eventType": "dev.eventing.test",
|
|
"source": "tests://TextXmlStructuredEncoding",
|
|
"contentType": "application/xml",
|
|
"data": ` + string(xmlJsonSafe) + `
|
|
}`
|
|
|
|
h := http.Header{}
|
|
h.Set(cloudevents.HeaderContentType, cloudevents.ContentTypeStructuredJSON)
|
|
req := &http.Request{
|
|
Header: h,
|
|
Body: ioutil.NopCloser(strings.NewReader(eventText)),
|
|
}
|
|
|
|
var foundPerson Person
|
|
_, err = cloudevents.FromRequest(&foundPerson, req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse cross-encoded request: %s", err)
|
|
}
|
|
|
|
if diff := cmp.Diff(person, &foundPerson); diff != "" {
|
|
t.Fatalf("Failed to parse xml-encoded data (-want +got): %s", diff)
|
|
}
|
|
}
|
|
|
|
func TestExtensionsAreNeverNil(t *testing.T) {
|
|
r := &http.Request{
|
|
Header: http.Header{
|
|
cloudevents.HeaderContentType: []string{cloudevents.ContentTypeStructuredJSON},
|
|
},
|
|
Body: ioutil.NopCloser(strings.NewReader(`
|
|
{
|
|
"cloudEventsVersion": "0.1",
|
|
"eventID": "1234",
|
|
"eventType": "dev.eventing.test",
|
|
"source": "tests://TextXmlStructuredEncoding",
|
|
"data": "hello, world"
|
|
}`)),
|
|
}
|
|
|
|
var data interface{}
|
|
ctx, err := cloudevents.FromRequest(&data, r)
|
|
if err != nil {
|
|
t.Fatal("Failed to parse request", err)
|
|
}
|
|
if ctx.AsV01().Extensions == nil {
|
|
t.Fatal("v0.1 Extensions should never be nil")
|
|
}
|
|
if ctx.AsV02().Extensions == nil {
|
|
t.Fatal("v0.2 Extensions should never be nil")
|
|
}
|
|
}
|
|
|
|
func TestExtensionExtraction(t *testing.T) {
|
|
h := http.Header{}
|
|
h.Set(cloudevents.HeaderCloudEventsVersion, cloudevents.V01CloudEventsVersion)
|
|
h.Set(cloudevents.HeaderContentType, cloudevents.ContentTypeBinaryJSON)
|
|
h.Set(cloudevents.HeaderEventID, "1234")
|
|
h.Set(cloudevents.HeaderEventType, "dev.eventing.test")
|
|
h.Set(cloudevents.HeaderSource, "tests://TestExtensionExtraction")
|
|
h.Set("CE-X-Prop1", "value1")
|
|
h.Set("CE-X-Prop2", `{"nestedProp":"nestedValue"}`)
|
|
b := strings.NewReader(`{"hello": "world"}`)
|
|
|
|
r := &http.Request{
|
|
Header: h,
|
|
Body: ioutil.NopCloser(b),
|
|
}
|
|
var data interface{}
|
|
ctx, err := cloudevents.FromRequest(&data, r)
|
|
if err != nil {
|
|
t.Fatal("Failed to parse request", err)
|
|
}
|
|
|
|
expectedV01Extensions := map[string]interface{}{
|
|
"Prop1": "value1",
|
|
"Prop2": map[string]interface{}{
|
|
"nestedProp": "nestedValue",
|
|
},
|
|
}
|
|
if diff := cmp.Diff(expectedV01Extensions, ctx.AsV01().Extensions); diff != "" {
|
|
t.Fatalf("Did not parse expected extensions (-want + got): %s", diff)
|
|
}
|
|
}
|