def test_remove_rule(self): router = Router() def index(_: Request, args) -> Response: return Response(b"index") def users(_: Request, args) -> Response: return Response(b"users") rule0 = router.add("/", index) rule1 = router.add("/users/<int:user_id>", users) assert router.dispatch(Request("GET", "/")).data == b"index" assert router.dispatch(Request("GET", "/users/12")).data == b"users" router.remove_rule(rule1) assert router.dispatch(Request("GET", "/")).data == b"index" with pytest.raises(NotFound): assert router.dispatch(Request("GET", "/users/12")) router.remove_rule(rule0) with pytest.raises(NotFound): assert router.dispatch(Request("GET", "/")) with pytest.raises(NotFound): assert router.dispatch(Request("GET", "/users/12"))
def test_dispatch_with_host_matching(self): router = Router() def ep_all(_: Request, args) -> Response: response = Response() response.set_json(dict(method="all", **args)) return response def ep_index1(_: Request, args) -> Response: response = Response() response.set_json(dict(method="1", **args)) return response def ep_index2(_: Request, args) -> Response: response = Response() response.set_json(dict(method="2", **args)) return response router.add("/", ep_index1, host="localhost:<port>") router.add("/", ep_index2, host="localhost:12345") router.add("/all", ep_all, host="<host>") def invoke(path, server, port): return router.dispatch(Request("GET", path, server=(server, port))).json assert invoke("/", "localhost", 4566) == {"method": "1", "port": "4566"} assert invoke("/", "localhost", 12345) == {"method": "2"} assert invoke("/all", "127.0.0.1", None) == {"method": "all", "host": "127.0.0.1"} assert invoke("/all", "127.0.0.1", 12345) == {"method": "all", "host": "127.0.0.1:12345"} with pytest.raises(NotFound): invoke("/", "localstack.cloud", None)
def test_dispatch_to_correct_function(self): router = Router(dispatcher=resource_dispatcher(pass_response=False)) requests = [] class TestResource: def on_get(self, req): requests.append(req) return "GET/OK" def on_post(self, req): requests.append(req) return {"ok": "POST"} router.add("/health", TestResource()) request1 = Request("GET", "/health") request2 = Request("POST", "/health") assert router.dispatch(request1).get_data(True) == "GET/OK" assert router.dispatch(request1).get_data(True) == "GET/OK" assert router.dispatch(request2).json == {"ok": "POST"} assert len(requests) == 3 assert requests[0] is request1 assert requests[1] is request1 assert requests[2] is request2
def test_add_route_endpoint_with_object(self): class MySuperApi: @route("/users") def user(self, _: Request, args): # should be inherited assert not args return Response("user") class MyApi(MySuperApi): @route("/users/<int:user_id>") def user_id(self, _: Request, args): assert args return Response(f"{args['user_id']}") def foo(self, _: Request, args): # should be ignored raise NotImplementedError api = MyApi() router = Router() rules = router.add_route_endpoints(api) assert len(rules) == 2 assert router.dispatch(Request("GET", "/users")).data == b"user" assert router.dispatch(Request("GET", "/users/123")).data == b"123"
def test_with_werkzeug(self): # setup up router router = Router() def index(_: Request, args) -> Response: return Response(b"index") def echo_json(request: Request, args) -> Response: response = Response() response.set_json(request.json) return response def users(_: Request, args) -> Response: response = Response() response.set_json(args) return response router.add("/", index) router.add("/users/<int:user_id>", users, host="<host>:<port>") router.add("/echo/", echo_json, methods=["POST"]) # serve router through werkzeug @werkzeug.Request.application def app(request: werkzeug.Request) -> werkzeug.Response: return router.dispatch(request) host = "localhost" port = get_free_tcp_port() url = f"http://{host}:{port}" server = werkzeug.serving.make_server(host, port, app=app, threaded=True) t = threading.Thread(target=server.serve_forever) t.start() try: resp = requests.get(f"{url}/") assert resp.ok assert resp.content == b"index" resp = requests.get(f"{url}/users/123") assert resp.ok assert resp.json() == { "user_id": 123, "host": host, "port": str(port) } resp = requests.get(f"{url}/users") assert not resp.ok resp = requests.post(f"{url}/echo", json={"foo": "bar", "a": 420}) assert resp.ok assert resp.json() == {"foo": "bar", "a": 420} finally: server.shutdown() t.join(timeout=10)
def test_handler_dispatcher_with_none_return(self): router = Router(dispatcher=handler_dispatcher()) def handler(_request: Request): return None router.add("/", handler) assert router.dispatch(Request("GET", "/")).status_code == 200
def test_handler_dispatcher_with_text_return(self): router = Router(dispatcher=handler_dispatcher()) def handler(_request: Request, arg1) -> str: return f"hello: {arg1}" router.add("/<arg1>", handler) assert router.dispatch(Request("GET", "/world")).data == b"hello: world"
def test_regex_path_dispatcher(self): router = Router() router.url_map.converters["regex"] = RegexConverter rgx = r"([^.]+)endpoint(.*)" regex = f"/<regex('{rgx}'):dist>/" router.add(path=regex, endpoint=noop) assert router.dispatch(Request(method="GET", path="/test-endpoint")) with pytest.raises(NotFound): router.dispatch(Request(method="GET", path="/test-not-point"))
def test_handler_dispatcher_invalid_signature(self): router = Router(dispatcher=handler_dispatcher()) def handler(_request: Request, arg1) -> Response: # invalid signature return Response("ok") router.add("/foo/<arg1>/<arg2>", handler) with pytest.raises(TypeError): router.dispatch(Request("GET", "/foo/a/b"))
def test_handler_dispatcher_with_dict_return(self): router = Router(dispatcher=handler_dispatcher()) def handler(_request: Request, arg1) -> Dict[str, Any]: return {"arg1": arg1, "hello": "there"} router.add("/foo/<arg1>", handler) assert router.dispatch(Request("GET", "/foo/a")).json == { "arg1": "a", "hello": "there" }
def test_remove_non_existing_rule(self): router = Router() def index(_: Request, args) -> Response: return Response(b"index") rule = router.add("/", index) router.remove_rule(rule) with pytest.raises(KeyError) as e: router.remove_rule(rule) e.match("no such rule")
def test_dispatch_to_non_existing_method_raises_exception(self): router = Router(dispatcher=resource_dispatcher(pass_response=False)) class TestResource: def on_post(self, request): return "POST/OK" router.add("/health", TestResource()) with pytest.raises(MethodNotAllowed): assert router.dispatch(Request("GET", "/health")) assert router.dispatch(Request("POST", "/health")).get_data(True) == "POST/OK"
def test_router_route_decorator(self): router = Router() @router.route("/users") def user(_: Request, args): assert not args return Response("user") @router.route("/users/<int:user_id>") def user_id(_: Request, args): assert args return Response(f"{args['user_id']}") assert router.dispatch(Request("GET", "/users")).data == b"user" assert router.dispatch(Request("GET", "/users/123")).data == b"123"
def test_dispatcher_with_pass_response(self): router = Router(dispatcher=resource_dispatcher(pass_response=True)) class TestResource: def on_get(self, req, resp: Response): resp.set_json({"message": "GET/OK"}) def on_post(self, req, resp): resp.set_data("POST/OK") router.add("/health", TestResource()) assert router.dispatch(Request("GET", "/health")).json == { "message": "GET/OK" } assert router.dispatch(Request("POST", "/health")).get_data(True) == "POST/OK"
def test_custom_dispatcher(self): collector = RequestCollector() router = Router(dispatcher=collector) router.add("/", "index") router.add("/users/<int:id>", "users") router.dispatch(Request("GET", "/")) router.dispatch(Request("GET", "/users/12")) _, endpoint, args = collector.requests[0] assert endpoint == "index" assert args == {} _, endpoint, args = collector.requests[1] assert endpoint == "users" assert args == {"id": 12}
def test_regex_host_dispatcher(self): router = Router() router.url_map.converters["regex"] = RegexConverter rgx = r"\.cloudfront.(net|localhost\.localstack\.cloud)" router.add(path="/", endpoint=noop, host=f"<dist_id><regex('{rgx}'):host>:<port>") assert router.dispatch( Request( method="GET", headers={"Host": "ad91f538.cloudfront.localhost.localstack.cloud:5446"}, ) ) with pytest.raises(NotFound): router.dispatch( Request( method="GET", headers={"Host": "ad91f538.cloudfront.amazon.aws.com:5446"}, ) )
def test_default_dispatcher_invokes_correct_endpoint(self): router = Router() def index(_: Request, args) -> Response: response = Response() response.set_json(args) return response def users(_: Request, args) -> Response: response = Response() response.set_json(args) return response router.add("/", index) router.add("/users/<int:user_id>", users) assert router.dispatch(Request("GET", "/")).json == {} assert router.dispatch(Request("GET", "/users/12")).json == {"user_id": 12}
def test_handler_dispatcher(self): router = Router(dispatcher=handler_dispatcher()) def handler_foo(_request: Request) -> Response: return Response("ok") def handler_bar(_request: Request, bar, baz) -> Dict[str, any]: response = Response() response.set_json({"bar": bar, "baz": baz}) return response router.add("/foo", handler_foo) router.add("/bar/<int:bar>/<baz>", handler_bar) assert router.dispatch(Request("GET", "/foo")).data == b"ok" assert router.dispatch(Request("GET", "/bar/420/ed")).json == { "bar": 420, "baz": "ed" } with pytest.raises(NotFound): assert router.dispatch(Request("GET", "/bar/asfg/ed"))
# certain EC2 requests from Java SDK contain no Auth headers (issue #3805) if b"Version=2016-11-15" in data_bytes: return "ec2", config.service_port("ec2") def get_service_port_for_account(service, headers): # assume we're only using a single account, hence return the static port mapping from config.py return config.service_port(service) PROXY_LISTENER_EDGE = ProxyListenerEdge() # the ROUTER is part of the edge proxy. use the router to inject custom handlers that are handled before actual # service calls ROUTER: Router[Handler] = Router( dispatcher=handler_dispatcher(), converters={"regex": RegexConverter} ) def is_trace_logging_enabled(headers): if not config.LS_LOG: return False if config.LS_LOG == LS_LOG_TRACE_INTERNAL: return True auth_header = headers.get("Authorization") or "" return INTERNAL_AWS_ACCESS_KEY_ID not in auth_header def do_start_edge(bind_address, port, use_ssl, asynchronous=False): from localstack.http.adapters import RouterListener from localstack.services.internal import LocalstackResourceHandler
def test_dispatch_raises_not_found(self): router = Router() router.add("/foobar", noop) with pytest.raises(NotFound): assert router.dispatch(Request("GET", "/foo"))