Adding web app tests (#13)
* Adding web app tests Signed-off-by: Denis Makogon <denys.makogon@oracle.com> * addressing review comments Signed-off-by: Denis Makogon <denys.makogon@oracle.com>
This commit is contained in:
parent
5cd67e4122
commit
7070e5124a
|
@ -21,8 +21,13 @@ class Converter(object):
|
||||||
|
|
||||||
TYPE = None
|
TYPE = None
|
||||||
|
|
||||||
def read(self, event, headers: dict, body: typing.IO,
|
def read(
|
||||||
data_unmarshaller: typing.Callable) -> base.BaseEvent:
|
self,
|
||||||
|
event,
|
||||||
|
headers: dict,
|
||||||
|
body: typing.IO,
|
||||||
|
data_unmarshaller: typing.Callable
|
||||||
|
) -> base.BaseEvent:
|
||||||
raise Exception("not implemented")
|
raise Exception("not implemented")
|
||||||
|
|
||||||
def event_supported(self, event: object) -> bool:
|
def event_supported(self, event: object) -> bool:
|
||||||
|
@ -31,6 +36,9 @@ class Converter(object):
|
||||||
def can_read(self, content_type: str) -> bool:
|
def can_read(self, content_type: str) -> bool:
|
||||||
raise Exception("not implemented")
|
raise Exception("not implemented")
|
||||||
|
|
||||||
def write(self, event: base.BaseEvent,
|
def write(
|
||||||
data_marshaller: typing.Callable) -> (dict, typing.IO):
|
self,
|
||||||
|
event: base.BaseEvent,
|
||||||
|
data_marshaller: typing.Callable
|
||||||
|
) -> (dict, object):
|
||||||
raise Exception("not implemented")
|
raise Exception("not implemented")
|
||||||
|
|
|
@ -23,7 +23,7 @@ from cloudevents.sdk.event import v02
|
||||||
class BinaryHTTPCloudEventConverter(base.Converter):
|
class BinaryHTTPCloudEventConverter(base.Converter):
|
||||||
|
|
||||||
TYPE = "binary"
|
TYPE = "binary"
|
||||||
SUPPORTED_VERSIONS = [v02.Event, ]
|
SUPPORTED_VERSIONS = [v02.Event]
|
||||||
|
|
||||||
def can_read(self, content_type: str) -> bool:
|
def can_read(self, content_type: str) -> bool:
|
||||||
return True
|
return True
|
||||||
|
@ -31,17 +31,21 @@ class BinaryHTTPCloudEventConverter(base.Converter):
|
||||||
def event_supported(self, event: object) -> bool:
|
def event_supported(self, event: object) -> bool:
|
||||||
return type(event) in self.SUPPORTED_VERSIONS
|
return type(event) in self.SUPPORTED_VERSIONS
|
||||||
|
|
||||||
def read(self,
|
def read(
|
||||||
event: event_base.BaseEvent,
|
self,
|
||||||
headers: dict, body: typing.IO,
|
event: event_base.BaseEvent,
|
||||||
data_unmarshaller: typing.Callable) -> event_base.BaseEvent:
|
headers: dict,
|
||||||
|
body: typing.IO,
|
||||||
|
data_unmarshaller: typing.Callable,
|
||||||
|
) -> event_base.BaseEvent:
|
||||||
if type(event) not in self.SUPPORTED_VERSIONS:
|
if type(event) not in self.SUPPORTED_VERSIONS:
|
||||||
raise exceptions.UnsupportedEvent(type(event))
|
raise exceptions.UnsupportedEvent(type(event))
|
||||||
event.UnmarshalBinary(headers, body, data_unmarshaller)
|
event.UnmarshalBinary(headers, body, data_unmarshaller)
|
||||||
return event
|
return event
|
||||||
|
|
||||||
def write(self, event: event_base.BaseEvent,
|
def write(
|
||||||
data_marshaller: typing.Callable) -> (dict, typing.IO):
|
self, event: event_base.BaseEvent, data_marshaller: typing.Callable
|
||||||
|
) -> (dict, typing.IO):
|
||||||
return event.MarshalBinary(data_marshaller)
|
return event.MarshalBinary(data_marshaller)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,17 +30,20 @@ class JSONHTTPCloudEventConverter(base.Converter):
|
||||||
# structured format supported by both spec 0.1 and 0.2
|
# structured format supported by both spec 0.1 and 0.2
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def read(self, event: event_base.BaseEvent,
|
def read(
|
||||||
headers: dict,
|
self,
|
||||||
body: typing.IO,
|
event: event_base.BaseEvent,
|
||||||
data_unmarshaller: typing.Callable) -> event_base.BaseEvent:
|
headers: dict,
|
||||||
|
body: typing.IO,
|
||||||
|
data_unmarshaller: typing.Callable,
|
||||||
|
) -> event_base.BaseEvent:
|
||||||
event.UnmarshalJSON(body, data_unmarshaller)
|
event.UnmarshalJSON(body, data_unmarshaller)
|
||||||
return event
|
return event
|
||||||
|
|
||||||
def write(self,
|
def write(
|
||||||
event: event_base.BaseEvent,
|
self, event: event_base.BaseEvent, data_marshaller: typing.Callable
|
||||||
data_marshaller: typing.Callable) -> (dict, typing.IO):
|
) -> (dict, typing.IO):
|
||||||
http_headers = {'content-type': self.MIME_TYPE}
|
http_headers = {"content-type": self.MIME_TYPE}
|
||||||
return http_headers, event.MarshalJSON(data_marshaller)
|
return http_headers, event.MarshalJSON(data_marshaller)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import typing
|
||||||
|
|
||||||
|
|
||||||
class EventGetterSetter(object):
|
class EventGetterSetter(object):
|
||||||
|
|
||||||
def CloudEventVersion(self) -> str:
|
def CloudEventVersion(self) -> str:
|
||||||
raise Exception("not implemented")
|
raise Exception("not implemented")
|
||||||
|
|
||||||
|
@ -76,18 +75,13 @@ class EventGetterSetter(object):
|
||||||
|
|
||||||
|
|
||||||
class BaseEvent(EventGetterSetter):
|
class BaseEvent(EventGetterSetter):
|
||||||
|
|
||||||
def Properties(self, with_nullable=False) -> dict:
|
def Properties(self, with_nullable=False) -> dict:
|
||||||
props = dict()
|
props = dict()
|
||||||
for name, value in self.__dict__.items():
|
for name, value in self.__dict__.items():
|
||||||
if str(name).startswith("ce__"):
|
if str(name).startswith("ce__"):
|
||||||
v = value.get()
|
v = value.get()
|
||||||
if v is not None or with_nullable:
|
if v is not None or with_nullable:
|
||||||
props.update(
|
props.update({str(name).replace("ce__", ""): value.get()})
|
||||||
{
|
|
||||||
str(name).replace("ce__", ""): value.get()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return props
|
return props
|
||||||
|
|
||||||
|
@ -119,33 +113,38 @@ class BaseEvent(EventGetterSetter):
|
||||||
props["data"] = data_marshaller(props.get("data"))
|
props["data"] = data_marshaller(props.get("data"))
|
||||||
return io.BytesIO(json.dumps(props).encode("utf-8"))
|
return io.BytesIO(json.dumps(props).encode("utf-8"))
|
||||||
|
|
||||||
def UnmarshalJSON(self, b: typing.IO,
|
def UnmarshalJSON(self, b: typing.IO, data_unmarshaller: typing.Callable):
|
||||||
data_unmarshaller: typing.Callable):
|
|
||||||
raw_ce = json.load(b)
|
raw_ce = json.load(b)
|
||||||
for name, value in raw_ce.items():
|
for name, value in raw_ce.items():
|
||||||
if name == "data":
|
if name == "data":
|
||||||
value = data_unmarshaller(value)
|
value = data_unmarshaller(value)
|
||||||
self.Set(name, value)
|
self.Set(name, value)
|
||||||
|
|
||||||
def UnmarshalBinary(self, headers: dict, body: typing.IO,
|
def UnmarshalBinary(
|
||||||
data_unmarshaller: typing.Callable):
|
self,
|
||||||
BINARY_MAPPING = {
|
headers: dict,
|
||||||
'content-type': 'contenttype',
|
body: typing.IO,
|
||||||
|
data_unmarshaller: typing.Callable
|
||||||
|
):
|
||||||
|
binary_mapping = {
|
||||||
|
"content-type": "contenttype",
|
||||||
# TODO(someone): add Distributed Tracing. It's not clear
|
# TODO(someone): add Distributed Tracing. It's not clear
|
||||||
# if this is one extension or two.
|
# if this is one extension or two.
|
||||||
# https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md
|
# https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md
|
||||||
}
|
}
|
||||||
for header, value in headers.items():
|
for header, value in headers.items():
|
||||||
header = header.lower()
|
header = header.lower()
|
||||||
if header in BINARY_MAPPING:
|
if header in binary_mapping:
|
||||||
self.Set(BINARY_MAPPING[header], value)
|
self.Set(binary_mapping[header], value)
|
||||||
elif header.startswith("ce-"):
|
elif header.startswith("ce-"):
|
||||||
self.Set(header[3:], value)
|
self.Set(header[3:], value)
|
||||||
|
|
||||||
self.Set("data", data_unmarshaller(body))
|
self.Set("data", data_unmarshaller(body))
|
||||||
|
|
||||||
def MarshalBinary(
|
def MarshalBinary(
|
||||||
self, data_marshaller: typing.Callable) -> (dict, object):
|
self,
|
||||||
|
data_marshaller: typing.Callable
|
||||||
|
) -> (dict, object):
|
||||||
headers = {}
|
headers = {}
|
||||||
if self.ContentType():
|
if self.ContentType():
|
||||||
headers["content-type"] = self.ContentType()
|
headers["content-type"] = self.ContentType()
|
||||||
|
@ -159,5 +158,4 @@ class BaseEvent(EventGetterSetter):
|
||||||
headers["ce-{0}".format(key)] = value
|
headers["ce-{0}".format(key)] = value
|
||||||
|
|
||||||
data, _ = self.Get("data")
|
data, _ = self.Get("data")
|
||||||
return headers, io.BytesIO(
|
return headers, data_marshaller(data)
|
||||||
str(data_marshaller(data)).encode("utf-8"))
|
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
|
|
||||||
class Option(object):
|
class Option(object):
|
||||||
|
|
||||||
def __init__(self, name, value, is_required):
|
def __init__(self, name, value, is_required):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.value = value
|
self.value = value
|
||||||
|
@ -25,7 +24,9 @@ class Option(object):
|
||||||
if self.is_required and is_none:
|
if self.is_required and is_none:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Attribute value error: '{0}', "
|
"Attribute value error: '{0}', "
|
||||||
"invalid new value.".format(self.name))
|
"" "invalid new value."
|
||||||
|
.format(self.name)
|
||||||
|
)
|
||||||
|
|
||||||
self.value = new_value
|
self.value = new_value
|
||||||
|
|
||||||
|
|
|
@ -12,25 +12,62 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from cloudevents.sdk.event import opt
|
|
||||||
from cloudevents.sdk.event import base
|
from cloudevents.sdk.event import base
|
||||||
|
from cloudevents.sdk.event import opt
|
||||||
|
|
||||||
|
|
||||||
class Event(base.BaseEvent):
|
class Event(base.BaseEvent):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ce__cloudEventsVersion = opt.Option(
|
self.ce__cloudEventsVersion = opt.Option(
|
||||||
"cloudEventsVersion", "0.1", True)
|
"cloudEventsVersion",
|
||||||
self.ce__eventType = opt.Option("eventType", None, True)
|
"0.1",
|
||||||
|
True
|
||||||
|
)
|
||||||
|
self.ce__eventType = opt.Option(
|
||||||
|
"eventType",
|
||||||
|
None,
|
||||||
|
True
|
||||||
|
)
|
||||||
self.ce__eventTypeVersion = opt.Option(
|
self.ce__eventTypeVersion = opt.Option(
|
||||||
"eventTypeVersion", None, False)
|
"eventTypeVersion",
|
||||||
self.ce__source = opt.Option("source", None, True)
|
None,
|
||||||
self.ce__eventID = opt.Option("eventID", None, True)
|
False
|
||||||
self.ce__eventTime = opt.Option("eventTime", None, True)
|
)
|
||||||
self.ce__schemaURL = opt.Option("schemaURL", None, False)
|
self.ce__source = opt.Option(
|
||||||
self.ce__contentType = opt.Option("contentType", None, False)
|
"source",
|
||||||
self.ce__data = opt.Option("data", None, False)
|
None,
|
||||||
self.ce__extensions = opt.Option("extensions", dict(), False)
|
True
|
||||||
|
)
|
||||||
|
self.ce__eventID = opt.Option(
|
||||||
|
"eventID",
|
||||||
|
None,
|
||||||
|
True
|
||||||
|
)
|
||||||
|
self.ce__eventTime = opt.Option(
|
||||||
|
"eventTime",
|
||||||
|
None,
|
||||||
|
True
|
||||||
|
)
|
||||||
|
self.ce__schemaURL = opt.Option(
|
||||||
|
"schemaURL",
|
||||||
|
None,
|
||||||
|
False
|
||||||
|
)
|
||||||
|
self.ce__contentType = opt.Option(
|
||||||
|
"contentType",
|
||||||
|
None,
|
||||||
|
False
|
||||||
|
)
|
||||||
|
self.ce__data = opt.Option(
|
||||||
|
"data",
|
||||||
|
None,
|
||||||
|
False
|
||||||
|
)
|
||||||
|
self.ce__extensions = opt.Option(
|
||||||
|
"extensions",
|
||||||
|
dict(),
|
||||||
|
False
|
||||||
|
)
|
||||||
|
|
||||||
def CloudEventVersion(self) -> str:
|
def CloudEventVersion(self) -> str:
|
||||||
return self.ce__cloudEventsVersion.get()
|
return self.ce__cloudEventsVersion.get()
|
||||||
|
|
|
@ -12,12 +12,11 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from cloudevents.sdk.event import opt
|
|
||||||
from cloudevents.sdk.event import base
|
from cloudevents.sdk.event import base
|
||||||
|
from cloudevents.sdk.event import opt
|
||||||
|
|
||||||
|
|
||||||
class Event(base.BaseEvent):
|
class Event(base.BaseEvent):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ce__specversion = opt.Option("specversion", "0.2", True)
|
self.ce__specversion = opt.Option("specversion", "0.2", True)
|
||||||
self.ce__type = opt.Option("type", None, True)
|
self.ce__type = opt.Option("type", None, True)
|
||||||
|
|
|
@ -14,34 +14,34 @@
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedEvent(Exception):
|
class UnsupportedEvent(Exception):
|
||||||
|
|
||||||
def __init__(self, event_class):
|
def __init__(self, event_class):
|
||||||
super().__init__("Invalid CloudEvent class: "
|
super().__init__(
|
||||||
"'{0}'".format(event_class))
|
"Invalid CloudEvent class: '{0}'".format(event_class)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvalidDataUnmarshaller(Exception):
|
class InvalidDataUnmarshaller(Exception):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__("Invalid data unmarshaller, is not a callable")
|
||||||
"Invalid data unmarshaller, is not a callable")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidDataMarshaller(Exception):
|
class InvalidDataMarshaller(Exception):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Invalid data marshaller, is not a callable")
|
"Invalid data marshaller, is not a callable"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NoSuchConverter(Exception):
|
class NoSuchConverter(Exception):
|
||||||
def __init__(self, converter_type):
|
def __init__(self, converter_type):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"No such converter {0}".format(converter_type))
|
"No such converter {0}".format(converter_type)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedEventConverter(Exception):
|
class UnsupportedEventConverter(Exception):
|
||||||
def __init__(self, content_type):
|
def __init__(self, content_type):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Unable to identify valid event converter "
|
"Unable to identify valid event converter "
|
||||||
"for content-type: '{0}'".format(content_type))
|
"for content-type: '{0}'".format(content_type)
|
||||||
|
)
|
||||||
|
|
|
@ -35,14 +35,16 @@ class HTTPMarshaller(object):
|
||||||
:param converters: a list of HTTP-to-CloudEvent-to-HTTP constructors
|
:param converters: a list of HTTP-to-CloudEvent-to-HTTP constructors
|
||||||
:type converters: typing.List[base.Converter]
|
:type converters: typing.List[base.Converter]
|
||||||
"""
|
"""
|
||||||
self.__converters = (c for c in converters)
|
self.__converters = [c for c in converters]
|
||||||
self.__converters_by_type = {c.TYPE: c for c in converters}
|
self.__converters_by_type = {c.TYPE: c for c in converters}
|
||||||
|
|
||||||
def FromRequest(self, event: event_base.BaseEvent,
|
def FromRequest(
|
||||||
headers: dict,
|
self,
|
||||||
body: typing.IO,
|
event: event_base.BaseEvent,
|
||||||
data_unmarshaller:
|
headers: dict,
|
||||||
typing.Callable) -> event_base.BaseEvent:
|
body: typing.IO,
|
||||||
|
data_unmarshaller: typing.Callable,
|
||||||
|
) -> event_base.BaseEvent:
|
||||||
"""
|
"""
|
||||||
Reads a CloudEvent from an HTTP headers and request body
|
Reads a CloudEvent from an HTTP headers and request body
|
||||||
:param event: CloudEvent placeholder
|
:param event: CloudEvent placeholder
|
||||||
|
@ -59,8 +61,7 @@ class HTTPMarshaller(object):
|
||||||
if not isinstance(data_unmarshaller, typing.Callable):
|
if not isinstance(data_unmarshaller, typing.Callable):
|
||||||
raise exceptions.InvalidDataUnmarshaller()
|
raise exceptions.InvalidDataUnmarshaller()
|
||||||
|
|
||||||
content_type = headers.get(
|
content_type = headers.get("content-type", headers.get("Content-Type"))
|
||||||
"content-type", headers.get("Content-Type"))
|
|
||||||
|
|
||||||
for cnvrtr in self.__converters:
|
for cnvrtr in self.__converters:
|
||||||
if cnvrtr.can_read(content_type) and cnvrtr.event_supported(event):
|
if cnvrtr.can_read(content_type) and cnvrtr.event_supported(event):
|
||||||
|
@ -68,11 +69,16 @@ class HTTPMarshaller(object):
|
||||||
|
|
||||||
raise exceptions.UnsupportedEventConverter(
|
raise exceptions.UnsupportedEventConverter(
|
||||||
"No registered marshaller for {0} in {1}".format(
|
"No registered marshaller for {0} in {1}".format(
|
||||||
content_type, self.__converters))
|
content_type, self.__converters
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def ToRequest(self, event: event_base.BaseEvent,
|
def ToRequest(
|
||||||
converter_type: str,
|
self,
|
||||||
data_marshaller: typing.Callable) -> (dict, typing.IO):
|
event: event_base.BaseEvent,
|
||||||
|
converter_type: str,
|
||||||
|
data_marshaller: typing.Callable,
|
||||||
|
) -> (dict, typing.IO):
|
||||||
"""
|
"""
|
||||||
Writes a CloudEvent into a HTTP-ready form of headers and request body
|
Writes a CloudEvent into a HTTP-ready form of headers and request body
|
||||||
:param event: CloudEvent
|
:param event: CloudEvent
|
||||||
|
@ -101,14 +107,17 @@ def NewDefaultHTTPMarshaller() -> HTTPMarshaller:
|
||||||
:return: an instance of HTTP marshaller
|
:return: an instance of HTTP marshaller
|
||||||
:rtype: cloudevents.sdk.marshaller.HTTPMarshaller
|
:rtype: cloudevents.sdk.marshaller.HTTPMarshaller
|
||||||
"""
|
"""
|
||||||
return HTTPMarshaller([
|
return HTTPMarshaller(
|
||||||
structured.NewJSONHTTPCloudEventConverter(),
|
[
|
||||||
binary.NewBinaryHTTPCloudEventConverter(),
|
structured.NewJSONHTTPCloudEventConverter(),
|
||||||
])
|
binary.NewBinaryHTTPCloudEventConverter(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def NewHTTPMarshaller(
|
def NewHTTPMarshaller(
|
||||||
converters: typing.List[base.Converter]) -> HTTPMarshaller:
|
converters: typing.List[base.Converter]
|
||||||
|
) -> HTTPMarshaller:
|
||||||
"""
|
"""
|
||||||
Creates the default HTTP marshaller with both
|
Creates the default HTTP marshaller with both
|
||||||
structured and binary converters
|
structured and binary converters
|
||||||
|
|
|
@ -25,7 +25,7 @@ headers = {
|
||||||
"ce-id": ce_id,
|
"ce-id": ce_id,
|
||||||
"ce-time": eventTime,
|
"ce-time": eventTime,
|
||||||
"ce-source": source,
|
"ce-source": source,
|
||||||
"Content-Type": contentType
|
"Content-Type": contentType,
|
||||||
}
|
}
|
||||||
ce = {
|
ce = {
|
||||||
"specversion": specversion,
|
"specversion": specversion,
|
||||||
|
@ -33,5 +33,5 @@ ce = {
|
||||||
"id": ce_id,
|
"id": ce_id,
|
||||||
"time": eventTime,
|
"time": eventTime,
|
||||||
"source": source,
|
"source": source,
|
||||||
"contenttype": contentType
|
"contenttype": contentType,
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,7 @@ from cloudevents.tests import data
|
||||||
|
|
||||||
def test_binary_converter_upstream():
|
def test_binary_converter_upstream():
|
||||||
m = marshaller.NewHTTPMarshaller(
|
m = marshaller.NewHTTPMarshaller(
|
||||||
[
|
[binary.NewBinaryHTTPCloudEventConverter()])
|
||||||
binary.NewBinaryHTTPCloudEventConverter()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
event = m.FromRequest(v02.Event(), data.headers, None, lambda x: x)
|
event = m.FromRequest(v02.Event(), data.headers, None, lambda x: x)
|
||||||
assert event is not None
|
assert event is not None
|
||||||
assert event.Get("type") == (data.ce_type, True)
|
assert event.Get("type") == (data.ce_type, True)
|
||||||
|
@ -42,15 +39,12 @@ def test_binary_converter_upstream():
|
||||||
|
|
||||||
def test_structured_converter_upstream():
|
def test_structured_converter_upstream():
|
||||||
m = marshaller.NewHTTPMarshaller(
|
m = marshaller.NewHTTPMarshaller(
|
||||||
[
|
[structured.NewJSONHTTPCloudEventConverter()])
|
||||||
structured.NewJSONHTTPCloudEventConverter()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
event = m.FromRequest(
|
event = m.FromRequest(
|
||||||
v02.Event(),
|
v02.Event(),
|
||||||
{"Content-Type": "application/cloudevents+json"},
|
{"Content-Type": "application/cloudevents+json"},
|
||||||
io.StringIO(json.dumps(data.ce)),
|
io.StringIO(json.dumps(data.ce)),
|
||||||
lambda x: x.read()
|
lambda x: x.read(),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert event is not None
|
assert event is not None
|
||||||
|
@ -60,41 +54,40 @@ def test_structured_converter_upstream():
|
||||||
|
|
||||||
def test_binary_converter_v01():
|
def test_binary_converter_v01():
|
||||||
m = marshaller.NewHTTPMarshaller(
|
m = marshaller.NewHTTPMarshaller(
|
||||||
[
|
[binary.NewBinaryHTTPCloudEventConverter()])
|
||||||
binary.NewBinaryHTTPCloudEventConverter()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
exceptions.UnsupportedEventConverter,
|
exceptions.UnsupportedEventConverter,
|
||||||
m.FromRequest,
|
m.FromRequest,
|
||||||
v01.Event, {}, None, lambda x: x)
|
v01.Event,
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
lambda x: x,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_unsupported_converter_v01():
|
def test_unsupported_converter_v01():
|
||||||
m = marshaller.NewHTTPMarshaller(
|
m = marshaller.NewHTTPMarshaller(
|
||||||
[
|
[structured.NewJSONHTTPCloudEventConverter()])
|
||||||
structured.NewJSONHTTPCloudEventConverter()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
exceptions.UnsupportedEventConverter,
|
exceptions.UnsupportedEventConverter,
|
||||||
m.FromRequest,
|
m.FromRequest,
|
||||||
v01.Event, {}, None, lambda x: x)
|
v01.Event,
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
lambda x: x,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_structured_converter_v01():
|
def test_structured_converter_v01():
|
||||||
m = marshaller.NewHTTPMarshaller(
|
m = marshaller.NewHTTPMarshaller(
|
||||||
[
|
[structured.NewJSONHTTPCloudEventConverter()])
|
||||||
structured.NewJSONHTTPCloudEventConverter()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
event = m.FromRequest(
|
event = m.FromRequest(
|
||||||
v01.Event(),
|
v01.Event(),
|
||||||
{"Content-Type": "application/cloudevents+json"},
|
{"Content-Type": "application/cloudevents+json"},
|
||||||
io.StringIO(json.dumps(data.ce)),
|
io.StringIO(json.dumps(data.ce)),
|
||||||
lambda x: x.read()
|
lambda x: x.read(),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert event is not None
|
assert event is not None
|
||||||
|
@ -109,7 +102,7 @@ def test_default_http_marshaller_with_structured():
|
||||||
v02.Event(),
|
v02.Event(),
|
||||||
{"Content-Type": "application/cloudevents+json"},
|
{"Content-Type": "application/cloudevents+json"},
|
||||||
io.StringIO(json.dumps(data.ce)),
|
io.StringIO(json.dumps(data.ce)),
|
||||||
lambda x: x.read()
|
lambda x: x.read(),
|
||||||
)
|
)
|
||||||
assert event is not None
|
assert event is not None
|
||||||
assert event.Get("type") == (data.ce_type, True)
|
assert event.Get("type") == (data.ce_type, True)
|
||||||
|
@ -120,8 +113,7 @@ def test_default_http_marshaller_with_binary():
|
||||||
m = marshaller.NewDefaultHTTPMarshaller()
|
m = marshaller.NewDefaultHTTPMarshaller()
|
||||||
|
|
||||||
event = m.FromRequest(
|
event = m.FromRequest(
|
||||||
v02.Event(),
|
v02.Event(), data.headers,
|
||||||
data.headers,
|
|
||||||
io.StringIO(json.dumps(data.body)),
|
io.StringIO(json.dumps(data.body)),
|
||||||
json.load
|
json.load
|
||||||
)
|
)
|
||||||
|
@ -133,17 +125,14 @@ def test_default_http_marshaller_with_binary():
|
||||||
|
|
||||||
def test_unsupported_event_configuration():
|
def test_unsupported_event_configuration():
|
||||||
m = marshaller.NewHTTPMarshaller(
|
m = marshaller.NewHTTPMarshaller(
|
||||||
[
|
[binary.NewBinaryHTTPCloudEventConverter()])
|
||||||
binary.NewBinaryHTTPCloudEventConverter()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
exceptions.UnsupportedEventConverter,
|
exceptions.UnsupportedEventConverter,
|
||||||
m.FromRequest,
|
m.FromRequest,
|
||||||
v01.Event(),
|
v01.Event(),
|
||||||
{"Content-Type": "application/cloudevents+json"},
|
{"Content-Type": "application/cloudevents+json"},
|
||||||
io.StringIO(json.dumps(data.ce)),
|
io.StringIO(json.dumps(data.ce)),
|
||||||
lambda x: x.read()
|
lambda x: x.read(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,12 +141,12 @@ def test_invalid_data_unmarshaller():
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
exceptions.InvalidDataUnmarshaller,
|
exceptions.InvalidDataUnmarshaller,
|
||||||
m.FromRequest,
|
m.FromRequest,
|
||||||
v01.Event(), {}, None, None)
|
v01.Event(), {}, None, None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_data_marshaller():
|
def test_invalid_data_marshaller():
|
||||||
m = marshaller.NewDefaultHTTPMarshaller()
|
m = marshaller.NewDefaultHTTPMarshaller()
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
exceptions.InvalidDataMarshaller,
|
exceptions.InvalidDataMarshaller, m.ToRequest, v01.Event(), "blah", None
|
||||||
m.ToRequest,
|
)
|
||||||
v01.Event(), "blah", None)
|
|
||||||
|
|
|
@ -27,13 +27,13 @@ from cloudevents.tests import data
|
||||||
|
|
||||||
def test_event_pipeline_upstream():
|
def test_event_pipeline_upstream():
|
||||||
event = (
|
event = (
|
||||||
v02.Event().
|
v02.Event()
|
||||||
SetContentType(data.contentType).
|
.SetContentType(data.contentType)
|
||||||
SetData(data.body).
|
.SetData(data.body)
|
||||||
SetEventID(data.ce_id).
|
.SetEventID(data.ce_id)
|
||||||
SetSource(data.source).
|
.SetSource(data.source)
|
||||||
SetEventTime(data.eventTime).
|
.SetEventTime(data.eventTime)
|
||||||
SetEventType(data.ce_type)
|
.SetEventType(data.ce_type)
|
||||||
)
|
)
|
||||||
m = marshaller.NewDefaultHTTPMarshaller()
|
m = marshaller.NewDefaultHTTPMarshaller()
|
||||||
new_headers, body = m.ToRequest(event, converters.TypeBinary, lambda x: x)
|
new_headers, body = m.ToRequest(event, converters.TypeBinary, lambda x: x)
|
||||||
|
@ -44,29 +44,25 @@ def test_event_pipeline_upstream():
|
||||||
assert "ce-id" in new_headers
|
assert "ce-id" in new_headers
|
||||||
assert "ce-time" in new_headers
|
assert "ce-time" in new_headers
|
||||||
assert "content-type" in new_headers
|
assert "content-type" in new_headers
|
||||||
assert isinstance(body, io.BytesIO)
|
assert isinstance(body, str)
|
||||||
assert data.body == body.read().decode("utf-8")
|
assert data.body == body
|
||||||
|
|
||||||
|
|
||||||
def test_event_pipeline_v01():
|
def test_event_pipeline_v01():
|
||||||
event = (
|
event = (
|
||||||
v01.Event().
|
v01.Event()
|
||||||
SetContentType(data.contentType).
|
.SetContentType(data.contentType)
|
||||||
SetData(data.body).
|
.SetData(data.body)
|
||||||
SetEventID(data.ce_id).
|
.SetEventID(data.ce_id)
|
||||||
SetSource(data.source).
|
.SetSource(data.source)
|
||||||
SetEventTime(data.eventTime).
|
.SetEventTime(data.eventTime)
|
||||||
SetEventType(data.ce_type)
|
.SetEventType(data.ce_type)
|
||||||
)
|
|
||||||
m = marshaller.NewHTTPMarshaller(
|
|
||||||
[
|
|
||||||
structured.NewJSONHTTPCloudEventConverter()
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
m = marshaller.NewHTTPMarshaller([structured.NewJSONHTTPCloudEventConverter()])
|
||||||
|
|
||||||
_, body = m.ToRequest(event, converters.TypeStructured, lambda x: x)
|
_, body = m.ToRequest(event, converters.TypeStructured, lambda x: x)
|
||||||
assert isinstance(body, io.BytesIO)
|
assert isinstance(body, io.BytesIO)
|
||||||
new_headers = json.load(io.TextIOWrapper(body, encoding='utf-8'))
|
new_headers = json.load(io.TextIOWrapper(body, encoding="utf-8"))
|
||||||
assert new_headers is not None
|
assert new_headers is not None
|
||||||
assert "cloudEventsVersion" in new_headers
|
assert "cloudEventsVersion" in new_headers
|
||||||
assert "eventType" in new_headers
|
assert "eventType" in new_headers
|
||||||
|
|
|
@ -33,7 +33,7 @@ def test_binary_event_to_request_upstream():
|
||||||
v02.Event(),
|
v02.Event(),
|
||||||
{"Content-Type": "application/cloudevents+json"},
|
{"Content-Type": "application/cloudevents+json"},
|
||||||
io.StringIO(json.dumps(data.ce)),
|
io.StringIO(json.dumps(data.ce)),
|
||||||
lambda x: x.read()
|
lambda x: x.read(),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert event is not None
|
assert event is not None
|
||||||
|
@ -50,10 +50,7 @@ def test_structured_event_to_request_upstream():
|
||||||
m = marshaller.NewDefaultHTTPMarshaller()
|
m = marshaller.NewDefaultHTTPMarshaller()
|
||||||
http_headers = {"content-type": "application/cloudevents+json"}
|
http_headers = {"content-type": "application/cloudevents+json"}
|
||||||
event = m.FromRequest(
|
event = m.FromRequest(
|
||||||
v02.Event(),
|
v02.Event(), http_headers, io.StringIO(json.dumps(data.ce)), lambda x: x.read()
|
||||||
http_headers,
|
|
||||||
io.StringIO(json.dumps(data.ce)),
|
|
||||||
lambda x: x.read()
|
|
||||||
)
|
)
|
||||||
assert event is not None
|
assert event is not None
|
||||||
assert event.Get("type") == (data.ce_type, True)
|
assert event.Get("type") == (data.ce_type, True)
|
||||||
|
@ -69,17 +66,10 @@ def test_structured_event_to_request_upstream():
|
||||||
|
|
||||||
def test_structured_event_to_request_v01():
|
def test_structured_event_to_request_v01():
|
||||||
copy_of_ce = copy.deepcopy(data.ce)
|
copy_of_ce = copy.deepcopy(data.ce)
|
||||||
m = marshaller.NewHTTPMarshaller(
|
m = marshaller.NewHTTPMarshaller([structured.NewJSONHTTPCloudEventConverter()])
|
||||||
[
|
|
||||||
structured.NewJSONHTTPCloudEventConverter()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
http_headers = {"content-type": "application/cloudevents+json"}
|
http_headers = {"content-type": "application/cloudevents+json"}
|
||||||
event = m.FromRequest(
|
event = m.FromRequest(
|
||||||
v01.Event(),
|
v01.Event(), http_headers, io.StringIO(json.dumps(data.ce)), lambda x: x.read()
|
||||||
http_headers,
|
|
||||||
io.StringIO(json.dumps(data.ce)),
|
|
||||||
lambda x: x.read()
|
|
||||||
)
|
)
|
||||||
assert event is not None
|
assert event is not None
|
||||||
assert event.Get("type") == (data.ce_type, True)
|
assert event.Get("type") == (data.ce_type, True)
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from cloudevents.sdk import marshaller
|
||||||
|
from cloudevents.sdk import converters
|
||||||
|
from cloudevents.sdk.event import v02
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic import response
|
||||||
|
|
||||||
|
from cloudevents.tests import data as test_data
|
||||||
|
|
||||||
|
|
||||||
|
m = marshaller.NewDefaultHTTPMarshaller()
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/is-ok", ["POST"])
|
||||||
|
async def is_ok(request):
|
||||||
|
m.FromRequest(
|
||||||
|
v02.Event(),
|
||||||
|
dict(request.headers),
|
||||||
|
request.body,
|
||||||
|
lambda x: x
|
||||||
|
)
|
||||||
|
return response.text("OK")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/echo", ["POST"])
|
||||||
|
async def echo(request):
|
||||||
|
event = m.FromRequest(
|
||||||
|
v02.Event(),
|
||||||
|
dict(request.headers),
|
||||||
|
request.body,
|
||||||
|
lambda x: x
|
||||||
|
)
|
||||||
|
hs, body = m.ToRequest(event, converters.TypeBinary, lambda x: x)
|
||||||
|
return response.text(body, headers=hs)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reusable_marshaller():
|
||||||
|
for i in range(10):
|
||||||
|
_, r = app.test_client.post(
|
||||||
|
"/is-ok", headers=test_data.headers, data=test_data.body
|
||||||
|
)
|
||||||
|
assert r.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_web_app_integration():
|
||||||
|
_, r = app.test_client.post(
|
||||||
|
"/is-ok", headers=test_data.headers, data=test_data.body
|
||||||
|
)
|
||||||
|
assert r.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_web_app_echo():
|
||||||
|
_, r = app.test_client.post("/echo", headers=test_data.headers, data=test_data.body)
|
||||||
|
assert r.status == 200
|
||||||
|
event = m.FromRequest(v02.Event(), dict(r.headers), r.body, lambda x: x)
|
||||||
|
assert event is not None
|
||||||
|
props = event.Properties()
|
||||||
|
for key in test_data.headers.keys():
|
||||||
|
if key == "Content-Type":
|
||||||
|
assert "contenttype" in props
|
||||||
|
else:
|
||||||
|
assert key.lstrip("ce-") in props
|
|
@ -1,4 +1,10 @@
|
||||||
flake8<2.7.0,>=2.6.0
|
flake8
|
||||||
hacking==1.1.0
|
pep8-naming==0.5.0
|
||||||
|
flake8-import-order
|
||||||
|
flake8-print
|
||||||
|
flake8-strict
|
||||||
pytest==4.0.0
|
pytest==4.0.0
|
||||||
pytest-cov==2.4.0
|
pytest-cov==2.4.0
|
||||||
|
# web app tests
|
||||||
|
sanic
|
||||||
|
aiohttp
|
7
tox.ini
7
tox.ini
|
@ -18,7 +18,8 @@ whitelist_externals = find
|
||||||
go
|
go
|
||||||
docker
|
docker
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands = flake8
|
commands =
|
||||||
|
flake8
|
||||||
|
|
||||||
[testenv:venv]
|
[testenv:venv]
|
||||||
commands = {posargs}
|
commands = {posargs}
|
||||||
|
@ -30,6 +31,6 @@ commands = pytest -v -s --tb=long --cov=cloudevents {toxinidir}/cloudevents/test
|
||||||
commands = pytest -v -s --tb=long --cov=cloudevents {toxinidir}/cloudevents/tests
|
commands = pytest -v -s --tb=long --cov=cloudevents {toxinidir}/cloudevents/tests
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore = H405,H404,H403,H401,H306
|
ignore = H405,H404,H403,H401,H306,S101,N802,N803,N806,I202,I201
|
||||||
show-source = True
|
show-source = True
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,docs,venv,.venv,docs,etc
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,docs,venv,.venv,docs,etc,samples,tests
|
||||||
|
|
Loading…
Reference in New Issue