def test_put_overwrite_and_get(self): service_manager = ServiceManager() service_manager.get_states = mock.MagicMock( return_value={"foo": ServiceState.AVAILABLE}) resource = HealthResource(service_manager) resource.on_put( Request( "PUT", "/", body= b'{"features:initScripts": "initializing","features:persistence": "disabled"}', )) resource.on_put( Request("PUT", "/", body=b'{"features:initScripts": "initialized"}')) state = resource.on_get(Request("GET", "/", body=b"None")) assert state == { "features": { "initScripts": "initialized", "persistence": "disabled", }, "services": { "foo": "available", }, "version": VERSION, }
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_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_s3_head_request(): router = RestServiceOperationRouter(load_service("s3")) op, _ = router.match(Request("GET", "/my-bucket/my-key/")) assert op.name == "GetObject" op, _ = router.match(Request("HEAD", "/my-bucket/my-key/")) assert op.name == "HeadObject"
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_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_endpoint_prefix_based_routing(): # TODO could be generalized using endpoint resolvers and replacing "amazonaws.com" with "localhost.localstack.cloud" detected_service_name = determine_aws_service_name( Request(method="GET", path="/", headers={"Host": "sqs.localhost.localstack.cloud"}) ) assert detected_service_name == "sqs" detected_service_name = determine_aws_service_name( Request( method="POST", path="/app-instances", headers={"Host": "identity-chime.localhost.localstack.cloud"}, ) ) assert detected_service_name == "chime-sdk-identity"
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 try_call_sqs(request: Request, region: str) -> Tuple[Dict, OperationModel]: action = request.values.get("Action") if not action: raise UnknownOperationException() if action in ["ListQueues", "CreateQueue"]: raise InvalidAction(action) # prepare aws request for the SQS query protocol (POST request with action url-encoded in the body) params = {"QueueUrl": request.base_url} # if a QueueUrl is already set in the body, it should overwrite the one in the URL. this behavior is validated # against AWS (see TestSqsQueryApi) params.update(request.values) body = urlencode(params) try: headers = Headers(request.headers) headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8" operation, service_request = parser.parse(Request("POST", "/", headers=headers, body=body)) validate_request(operation, service_request).raise_first() except OperationNotFoundParserError: raise InvalidAction(action) except MissingRequiredField as e: raise MissingParameter(f"The request must contain the parameter {e.required_name}.") # TODO: permissions encoded in URL as AUTHPARAMS cannot be accounted for in this method, which is not a big # problem yet since we generally don't enforce permissions. client = aws_stack.connect_to_service("sqs", region_name=region) try: # using the layer below boto3.client("sqs").<operation>(...) to make the call boto_response = client._make_api_call(operation.name, service_request) except ClientError as e: raise BotoException(e.response) from e return boto_response, operation
def test_trailing_slashes_are_not_strict(): # this is tested against AWS. AWS is not strict about trailing slashes when routing operations. router = RestServiceOperationRouter(load_service("lambda")) op, _ = router.match(Request("GET", "/2015-03-31/functions")) assert op.name == "ListFunctions" op, _ = router.match(Request("GET", "/2015-03-31/functions/")) assert op.name == "ListFunctions" op, _ = router.match(Request("POST", "/2015-03-31/functions")) assert op.name == "CreateFunction" op, _ = router.match(Request("POST", "/2015-03-31/functions/")) assert op.name == "CreateFunction"
def test_create_op_router_works_for_every_service(service): router = RestServiceOperationRouter(load_service(service)) try: router.match(Request("GET", "/")) except NotFound: pass
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_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_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_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_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_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_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 on_post(self, request: Request): data = request.get_json(True, True) if not data: return Response("invalid request", 400) # backdoor API to support restarting the instance if data.get("action") in ["kill", "restart"]: terminate_all_processes_in_docker() SHUTDOWN_INFRA.set() return Response("ok", 200)
def test_diagnose_resource(): # simple smoke test diagnose resource resource = DiagnoseResource() result = resource.on_get(Request(path="/_localstack/diagnose")) assert "/tmp" in result["file-tree"] assert "/var/lib/localstack" in result["file-tree"] assert result["config"]["EDGE_PORT"] == config.EDGE_PORT assert result["config"]["DATA_DIR"] == config.DATA_DIR assert result["important-endpoints"][ "localhost.localstack.cloud"].startswith("127.0.") assert result["logs"]["docker"]
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_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"))
def _botocore_request_to_localstack_request(request_object: AWSRequest) -> Request: """Converts a botocore request (AWSRequest) to our HTTP framework's Request object based on Werkzeug.""" split_url = urlsplit(request_object.url) path = split_url.path query_string = split_url.query body = request_object.body headers = request_object.headers return Request( method=request_object.method or "GET", path=path, query_string=to_str(query_string), headers=dict(headers), body=body, raw_path=path, )
def on_put(self, request: Request): data = request.get_json(True, True) or {} # keys like "features:initScripts" should be interpreted as ['features']['initScripts'] state = defaultdict(dict) for k, v in data.items(): if ":" in k: path = k.split(":") else: path = [k] d = state for p in path[:-1]: d = state[p] d[path[-1]] = v self.state = merge_recursive(state, self.state, overwrite=True) return {"status": "OK"}
def test_dispatch_raises_not_found(self): router = Router() router.add("/foobar", noop) with pytest.raises(NotFound): assert router.dispatch(Request("GET", "/foo"))
def invoke(path, server, port): return router.dispatch(Request("GET", path, server=(server, port))).json