def test_publish_needs_topic(self): """ Test that attempted publishes without a topic will be rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{}') self.assertEqual(len(session._published_messages), 0) self.assertEqual(request.code, 400) errors = l.get_category("AR455") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400) self.assertEqual( json.loads(native_string(request.get_written_data())), { "error": log_categories["AR455"].format(key="topic"), "args": [], "kwargs": {} })
def test_undecodable_UTF8(self): """ Undecodable UTF-8 will return an error. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf( renderResource( resource, b"/", method=b"POST", headers={ b"Content-Type": [b"application/json;charset=utf-8"] }, body= b'{"topic": "com.test.messages", "args": ["\x61\x62\x63\xe9"]}' )) self.assertEqual(request.code, 400) errors = l.get_category("AR451") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_basic(self): """ A message, when a request has gone through to it, publishes a WAMP message on the configured topic. """ session = MockPublisherSession(self) resource = WebhookResource({u"topic": u"com.test.webhook"}, session) request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": []}, body=b'{"foo": "has happened"}') self.assertEqual(len(session._published_messages), 1) self.assertEqual( { u"body": u'{"foo": "has happened"}', u"headers": { u"Content-Type": [], u'Date': [u'Sun, 1 Jan 2013 15:21:01 GMT'], u'Host': [u'localhost:8000'] } }, session._published_messages[0]["args"][0]) self.assertEqual(request.code, 202) self.assertEqual(native_string(request.get_written_data()), "OK")
def test_outdated_delta(self): """ If the delta between now and the timestamp in the request is larger than C{timestamp_delta_limit}, the request is rejected. """ custOpts = {"timestamp_delta_limit": 1} custOpts.update(resourceOptions) session = MockPublisherSession(self) resource = PublisherResource(custOpts, session) signedParams = makeSignedArguments({}, "bazapp", "foobar", publishBody) signedParams[b'timestamp'] = [b"2011-10-14T16:59:51.123Z"] with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, params=signedParams) self.assertEqual(request.code, 400) errors = l.get_category("AR464") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_broken_contenttype(self): """ Crossbar rejects broken content-types. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json;charset=blarg;charset=boo"]}, body=b'{"foo": "\xe2\x98\x83"}')) errors = l.get_category("AR450") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400) del l with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"charset=blarg;application/json"]}, body=b'{"foo": "\xe2\x98\x83"}')) self.assertEqual(request.code, 400) errors = l.get_category("AR452") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_basic_publish(self): """ Test a very basic publish to a topic. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"topic": "com.test.messages", "args": [1]}') self.assertEqual(len(session._published_messages), 1) self.assertEqual(session._published_messages[0]["args"], (1,)) self.assertEqual(request.code, 200) logs = l.get_category("AR200") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 200) self.assertEqual(json.loads(native_string(request.get_written_data())), {"id": session._published_messages[0]["id"]})
def test_broken_contenttype(self): """ Crossbar rejects broken content-types. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json;charset=blarg;charset=boo"]}, body=b'{"foo": "\xe2\x98\x83"}')) self.assertEqual(request.code, 400) self.assertEqual( b"mangled Content-Type header\n", request.getWrittenData()) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"charset=blarg;application/json"]}, body=b'{"foo": "\xe2\x98\x83"}')) self.assertEqual(request.code, 400) self.assertEqual( b"bad or missing content type, should be 'application/json'\n", request.getWrittenData())
def test_basic_publish(self): """ Test a very basic publish to a topic. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"topic": "com.test.messages", "args": [1]}') self.assertEqual(len(session._published_messages), 1) self.assertEqual(session._published_messages[0]["args"], (1, )) self.assertEqual(request.code, 200) logs = l.get_category("AR200") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 200) self.assertEqual(json.loads(native_string(request.get_written_data())), {"id": session._published_messages[0]["id"]}) # ensure we have all the format-keys AR200 asks for (can we # extract these from the _log_categories string instead?) self.assertIn('code', logs[0]) self.assertIn('reason', logs[0])
def test_allowed_IP_range(self): """ The client having an IP in an allowed address range allows the request. """ session = MockPublisherSession(self) resource = PublisherResource({"require_ip": ["127.0.0.0/8"]}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 202)
def test_required_tls_with_tls(self): """ Required TLS, plus a request over TLS, will allow the request. """ session = MockPublisherSession(self) resource = PublisherResource({"require_tls": True}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, isSecure=True)) self.assertEqual(request.code, 202)
def test_invalid_JSON_body(self): """ A body that is not valid JSON will be rejected by the server. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b"sometext")) self.assertEqual(request.code, 400) self.assertIn(b"invalid request event - HTTP/POST body must be valid JSON:", request.getWrittenData())
def test_too_large_body(self): """ A too large body will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource({"post_body_limit": 1}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 400) self.assertIn("HTTP/POST body length ({}) exceeds maximum ({})".format(len(publishBody), 1), native_string(request.getWrittenData()))
def test_bad_method(self): """ An incorrect method will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"PUT", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 405) self.assertIn(b"HTTP/PUT not allowed", request.getWrittenData())
def test_bad_content_type(self): """ An incorrect content type will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/text"]}, body=publishBody)) self.assertEqual(request.code, 400) self.assertIn(b"bad or missing content type", request.getWrittenData())
def test_allowed_IP(self): """ The client having an allowed IP address allows the request. """ session = MockPublisherSession(self) resource = PublisherResource({"require_ip": ["127.0.0.1"]}, session) request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody) self.assertEqual(request.code, 202)
def test_allow_caps_in_content_type(self): """ Differently-capitalised content-type headers will be allowed. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"CONTENT-TYPE": [b"APPLICATION/JSON"]}, body=publishBody)) self.assertEqual(request.code, 202) self.assertIn(b'{"id":', request.get_written_data())
def test_disallowed_IP_range(self): """ The client having an IP not in allowed address range denies the request. """ session = MockPublisherSession(self) resource = PublisherResource({"require_ip": ["192.168.0.0/16", "10.0.0.0/8"]}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 400) self.assertIn(b"Request denied based on IP address", request.get_written_data())
def test_UTF8_assumption(self): """ A body, when the Content-Type has no charset, is assumed to be UTF-8. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"topic": "com.test.messages", "args": ["\xe2\x98\x83"]}')) self.assertEqual(request.code, 202) self.assertIn(b'{"id":', request.get_written_data())
def test_allow_charset_in_content_type(self): """ A charset in the content-type will be allowed. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json; charset=utf-8"]}, body=publishBody)) self.assertEqual(request.code, 202) self.assertIn(b'{"id":', request.get_written_data())
def test_empty_content_type(self): """ A request lacking a content-type header will be rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={}, body=publishBody)) self.assertEqual(request.code, 400) self.assertEqual((b"bad or missing content type, " b"should be 'application/json'\n"), request.getWrittenData())
def test_not_required_tls_with_tls(self): """ A request over TLS even when not required, will allow the request. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, isSecure=True) self.assertEqual(request.code, 202)
def test_multiple_content_length(self): """ Requests with multiple Content-Length headers will be rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"], b"Content-Length": ["1", "10"]}, body=publishBody)) self.assertEqual(request.code, 400) self.assertIn("Multiple Content-Length headers are not allowed", native_string(request.getWrittenData()))
def test_ASCII_denied(self): """ A body with an ASCII charset is denied, it must be UTF-8. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json; charset=ascii"]}, body=b'')) self.assertEqual(request.code, 400) self.assertIn((b"'ascii' is not an accepted charset encoding, must be " b"utf-8"), request.getWrittenData())
def test_decodes_UTF8(self): """ A body, when the Content-Type has been set to be charset=utf-8, will decode it as UTF8. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json;charset=utf-8"]}, body=b'{"topic": "com.test.messages", "args": ["\xe2\x98\x83"]}')) self.assertEqual(request.code, 202) self.assertIn(b'{"id":', request.getWrittenData())
def test_undecodable_UTF8(self): """ Undecodable UTF-8 will return an error. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json;charset=utf-8"]}, body=b'{"topic": "com.test.messages", "args": ["\x61\x62\x63\xe9"]}')) self.assertEqual(request.code, 400) self.assertEqual( (b"invalid request event - HTTP/POST body was invalid UTF-8\n"), request.getWrittenData())
def test_required_tls_without_tls(self): """ Required TLS, plus a request NOT over TLS, will deny the request. """ session = MockPublisherSession(self) resource = PublisherResource({"require_tls": True}, session) request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, isSecure=False) self.assertEqual(request.code, 400)
def test_not_matching_bodylength(self): """ A body length that is different than the Content-Length header will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource({"post_body_limit": 1}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"], b"Content-Length": [1]}, body=publishBody)) self.assertEqual(request.code, 400) self.assertIn("HTTP/POST body length ({}) is different to Content-Length ({})".format(len(publishBody), 1), native_string(request.getWrittenData()))
def test_bad_method(self): """ An incorrect method will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"BLBLBLB", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 405) errors = l.get_category("AR405") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 405)
def test_unknown_encoding(self): """ A body, when the Content-Type has been set to something other than charset=utf-8, will error out. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json;charset=blarg"]}, body=b'{"args": ["\x61\x62\x63\xe9"]}')) self.assertEqual(request.code, 400) self.assertEqual( (b"'blarg' is not an accepted charset encoding, must be utf-8\n"), request.getWrittenData())
def test_ASCII_denied(self): """ A body with an ASCII charset is denied, it must be UTF-8. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json; charset=ascii"]}, body=b'')) self.assertEqual(request.code, 400) errors = l.get_category("AR450") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)