Skip to content

Commit 3c26bcf

Browse files
authored
100% test-coverage rule added to tox (cloudevents#109)
* version bump Signed-off-by: Curtis Mason <cumason@google.com> * adding tests for marshaller Signed-off-by: Curtis Mason <cumason@google.com> * marshaller 100% test-coverage Signed-off-by: Curtis Mason <cumason@bu.edu> * bricked some tests Signed-off-by: Curtis Mason <cumason@bu.edu> * additional error handling Signed-off-by: Curtis Mason <cumason@bu.edu> * 100% test-coverage Signed-off-by: Curtis Mason <cumason@bu.edu> * handles empty data and capitalized headers Signed-off-by: Curtis Mason <cumason@bu.edu> * 1.1.0 version bump Signed-off-by: Curtis Mason <cumason@bu.edu>
1 parent d95b130 commit 3c26bcf

19 files changed

+479
-21
lines changed

cloudevents/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.1"
1+
__version__ = "1.1.0"

cloudevents/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,11 @@ class CloudEventMissingRequiredFields(Exception):
1717

1818
class CloudEventTypeErrorRequiredFields(Exception):
1919
pass
20+
21+
22+
class InvalidStructuredJSON(Exception):
23+
pass
24+
25+
26+
class InvalidHeadersFormat(Exception):
27+
pass

cloudevents/http/event.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ def __init__(
5959

6060
if self._attributes["specversion"] not in _required_by_version:
6161
raise cloud_exceptions.CloudEventMissingRequiredFields(
62-
f"Invalid specversion: {self._attributes['specversion']}"
62+
f"Invalid specversion: {self._attributes['specversion']}. "
6363
)
6464
# There is no good way to default 'source' and 'type', so this
6565
# checks for those (or any new required attributes).
6666
required_set = _required_by_version[self._attributes["specversion"]]
6767
if not required_set <= self._attributes.keys():
6868
raise cloud_exceptions.CloudEventMissingRequiredFields(
69-
f"Missing required keys: {required_set - attributes.keys()}"
69+
f"Missing required keys: {required_set - self._attributes.keys()}. "
7070
)
7171

7272
def __eq__(self, other):

cloudevents/http/http_methods.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
def from_http(
13-
data: typing.Union[str, bytes],
13+
data: typing.Union[str, bytes, None],
1414
headers: typing.Dict[str, str],
1515
data_unmarshaller: types.UnmarshallerType = None,
1616
):
@@ -24,6 +24,16 @@ def from_http(
2424
e.g. lambda x: x or lambda x: json.loads(x)
2525
:type data_unmarshaller: types.UnmarshallerType
2626
"""
27+
if data is None:
28+
data = ""
29+
30+
if not isinstance(data, (str, bytes, bytearray)):
31+
raise cloud_exceptions.InvalidStructuredJSON(
32+
"Expected json of type (str, bytes, bytearray), "
33+
f"but instead found {type(data)}. "
34+
)
35+
36+
headers = {key.lower(): value for key, value in headers.items()}
2737
if data_unmarshaller is None:
2838
data_unmarshaller = _json_or_string
2939

@@ -32,19 +42,25 @@ def from_http(
3242
if is_binary(headers):
3343
specversion = headers.get("ce-specversion", None)
3444
else:
35-
raw_ce = json.loads(data)
45+
try:
46+
raw_ce = json.loads(data)
47+
except json.decoder.JSONDecodeError:
48+
raise cloud_exceptions.InvalidStructuredJSON(
49+
"Failed to read fields from structured event. "
50+
f"The following can not be parsed as json: {data}. "
51+
)
3652
specversion = raw_ce.get("specversion", None)
3753

3854
if specversion is None:
3955
raise cloud_exceptions.CloudEventMissingRequiredFields(
40-
"could not find specversion in HTTP request"
56+
"Specversion was set to None in HTTP request. "
4157
)
4258

4359
event_handler = _obj_by_version.get(specversion, None)
4460

4561
if event_handler is None:
4662
raise cloud_exceptions.CloudEventTypeErrorRequiredFields(
47-
f"found invalid specversion {specversion}"
63+
f"Found invalid specversion {specversion}. "
4864
)
4965

5066
event = marshall.FromRequest(
@@ -78,7 +94,7 @@ def _to_http(
7894

7995
if event._attributes["specversion"] not in _obj_by_version:
8096
raise cloud_exceptions.CloudEventTypeErrorRequiredFields(
81-
f"Unsupported specversion: {event._attributes['specversion']}"
97+
f"Unsupported specversion: {event._attributes['specversion']}. "
8298
)
8399

84100
event_handler = _obj_by_version[event._attributes["specversion"]]()

cloudevents/http/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def default_marshaller(content: any):
1212

1313

1414
def _json_or_string(content: typing.Union[str, bytes]):
15-
if len(content) == 0:
15+
if content is None or len(content) == 0:
1616
return None
1717
try:
1818
return json.loads(content)

cloudevents/sdk/event/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
# TODO(slinkydeveloper) is this really needed?
2323

2424

25-
class EventGetterSetter(object):
25+
class EventGetterSetter(object): # pragma: no cover
2626

2727
# ce-specversion
2828
def CloudEventVersion(self) -> str:

cloudevents/sdk/event/v03.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@ def ContentType(self) -> str:
7575
def ContentEncoding(self) -> str:
7676
return self.ce__datacontentencoding.get()
7777

78-
@property
79-
def datacontentencoding(self):
80-
return self.ContentEncoding()
81-
8278
def SetEventType(self, eventType: str) -> base.BaseEvent:
8379
self.Set("type", eventType)
8480
return self
@@ -119,6 +115,26 @@ def SetContentEncoding(self, contentEncoding: str) -> base.BaseEvent:
119115
self.Set("datacontentencoding", contentEncoding)
120116
return self
121117

118+
@property
119+
def datacontentencoding(self):
120+
return self.ContentEncoding()
121+
122122
@datacontentencoding.setter
123123
def datacontentencoding(self, value: str):
124124
self.SetContentEncoding(value)
125+
126+
@property
127+
def subject(self) -> str:
128+
return self.Subject()
129+
130+
@subject.setter
131+
def subject(self, value: str):
132+
self.SetSubject(value)
133+
134+
@property
135+
def schema_url(self) -> str:
136+
return self.SchemaURL()
137+
138+
@schema_url.setter
139+
def schema_url(self, value: str):
140+
self.SetSchemaURL(value)

cloudevents/sdk/event/v1.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,19 @@ def SetData(self, data: object) -> base.BaseEvent:
9898
def SetExtensions(self, extensions: dict) -> base.BaseEvent:
9999
self.Set("extensions", extensions)
100100
return self
101+
102+
@property
103+
def schema(self) -> str:
104+
return self.Schema()
105+
106+
@schema.setter
107+
def schema(self, value: str):
108+
self.SetSchema(value)
109+
110+
@property
111+
def subject(self) -> str:
112+
return self.Subject()
113+
114+
@subject.setter
115+
def subject(self, value: str):
116+
self.SetSubject(value)

cloudevents/tests/test_base_events.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
import pytest
15+
16+
import cloudevents.exceptions as cloud_exceptions
17+
from cloudevents.sdk.event import v1, v03
18+
19+
20+
@pytest.mark.parametrize("event_class", [v1.Event, v03.Event])
21+
def test_unmarshall_binary_missing_fields(event_class):
22+
event = event_class()
23+
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields) as e:
24+
event.UnmarshalBinary({}, "", lambda x: x)
25+
assert "Missing required attributes: " in str(e.value)
26+
27+
28+
@pytest.mark.parametrize("event_class", [v1.Event, v03.Event])
29+
def test_get_nonexistent_optional(event_class):
30+
event = event_class()
31+
event.SetExtensions({"ext1": "val"})
32+
res = event.Get("ext1")
33+
assert res[0] == "val" and res[1] == True

cloudevents/tests/test_converters.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
import pytest
15+
16+
from cloudevents.sdk import exceptions
17+
from cloudevents.sdk.converters import base, binary, structured
18+
19+
20+
def test_binary_converter_raise_unsupported():
21+
with pytest.raises(exceptions.UnsupportedEvent):
22+
cnvtr = binary.BinaryHTTPCloudEventConverter()
23+
cnvtr.read(None, {}, None, None)
24+
25+
26+
def test_base_converters_raise_exceptions():
27+
with pytest.raises(Exception):
28+
cnvtr = base.Converter()
29+
cnvtr.event_supported(None)
30+
31+
with pytest.raises(Exception):
32+
cnvtr = base.Converter()
33+
cnvtr.can_read(None)
34+
35+
with pytest.raises(Exception):
36+
cnvtr = base.Converter()
37+
cnvtr.write(None, None)
38+
39+
with pytest.raises(Exception):
40+
cnvtr = base.Converter()
41+
cnvtr.read(None, None, None, None)

cloudevents/tests/test_data_encaps_refs.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ def test_general_structured_properties(event_class):
9292
if key == "content-type":
9393
assert new_headers[key] == http_headers[key]
9494
continue
95-
assert key in copy_of_ce
9695

9796
# Test setters
9897
new_type = str(uuid4())

cloudevents/tests/test_event_to_request_converter.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,3 @@ def test_structured_event_to_request_upstream(event_class):
6161
if key == "content-type":
6262
assert new_headers[key] == http_headers[key]
6363
continue
64-
assert key in copy_of_ce

cloudevents/tests/test_http_cloudevent_overrides.py renamed to cloudevents/tests/test_http_cloudevent.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
import cloudevents.exceptions as cloud_exceptions
34
from cloudevents.http import CloudEvent
45

56

@@ -69,3 +70,47 @@ def test_http_cloudevent_mutates_equality(specversion):
6970
event3.data = '{"name":"paul"}'
7071
assert event2 == event3
7172
assert event1 != event2 and event3 != event1
73+
74+
75+
def test_cloudevent_missing_specversion():
76+
attributes = {"specversion": "0.2", "source": "s", "type": "t"}
77+
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields) as e:
78+
event = CloudEvent(attributes, None)
79+
assert "Invalid specversion: 0.2" in str(e.value)
80+
81+
82+
def test_cloudevent_missing_minimal_required_fields():
83+
attributes = {"type": "t"}
84+
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields) as e:
85+
event = CloudEvent(attributes, None)
86+
assert f"Missing required keys: {set(['source'])}" in str(e.value)
87+
88+
attributes = {"source": "s"}
89+
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields) as e:
90+
event = CloudEvent(attributes, None)
91+
assert f"Missing required keys: {set(['type'])}" in str(e.value)
92+
93+
94+
def test_cloudevent_general_overrides():
95+
event = CloudEvent(
96+
{
97+
"source": "my-source",
98+
"type": "com.test.overrides",
99+
"subject": "my-subject",
100+
},
101+
None,
102+
)
103+
expected_attributes = [
104+
"time",
105+
"source",
106+
"id",
107+
"specversion",
108+
"type",
109+
"subject",
110+
]
111+
112+
assert len(event) == 6
113+
for attribute in expected_attributes:
114+
assert attribute in event
115+
del event[attribute]
116+
assert len(event) == 0

0 commit comments

Comments
 (0)