def test_chain_validators(): request = { "num": "42", "num2": "-10", "num3": "3.14", "power": "16", "power2": "25", "new_param": "2.79", } # fmt: off factories = { "num": int, "num2": int, "num3": float, "power": int, "power2": lambda x: float(x) ** 0.5 } # fmt: on qval = ( QueryParamValidator(make_request(request), factories) .check("power", lambda x: (x ** 0.5).is_integer()) .check("power2", float.is_integer) .eq("num", 42) .gt("num", 41) .lt("num2", 0) .nonzero("num3") .positive("power") ) qval.add_predicate( "new_param", lambda x: float(x) not in (float("inf"), float("-inf")) ) for r in builder.iterbuild(request): params = qval.apply_to_request(r) with params as p: assert p.num == 42 assert p.num2 == -10 assert p.num3 == 3.14 assert p.power == 16 assert p.power2 == 5 assert p.new_param == "2.79" bad_examples = { "num": "0", # is not equal to 42 "num2": "1", # is greater than zero "num3": "0", # is equal to zero "power": "-64", # is not positive "power2": "24", # is not a perfect square "new_param": "inf", # is an infinity } for r in builder.iterbuild(bad_examples): params = qval.apply_to_request(r) with pytest.raises(InvalidQueryParamException) as e, params: pass assert e.type is InvalidQueryParamException
def test_validator_factory(): currency2f = lambda x: float(x[:-1]) qparams = { "price": "43.5$", "n_items": "1", "meta": "info", "num": "10", "num2": "5", "token": "0123456789", } for r in builder.iterbuild(qparams): params = ( validate(r, price=currency2f, n_items=int, num=int, num2=int).positive( # `n_items` must be greater than zero "n_items") # `num` must be equal to 10 .eq("num", 10) # `num2` must be less than 10 .lt("num2", 10) # Len of `token` must be equal to 10 .check("token", lambda x: len(x) == 10) # The same check as above, but using `transform` .eq("token", 10, transform=len)) with params as p: assert {43.5, 1, "info", 10, 5, "0123456789"} == set(p.__dct__.values())
def test_validation_fails(): currency2f = lambda x: float(x[:-1]) qparams = { "price": "43.5$", "n_items": "0", "meta": "info", "num": "-10", "num2": "20", "token": "012345678", } for r in builder.iterbuild(qparams): params = ( validate(r, price=currency2f, n_items=int, num=int, num2=int).positive( # `n_items` must be greater than zero "n_items") # `num` must be equal to 10 .eq("num", 10) # `num2` must be less than 10 .lt("num2", 10) # Len of `token` must be equal to 10 .check("token", lambda x: len(x) == 10) # The same check as above, but using `transform` .eq("token", 10, transform=len)) with pytest.raises(InvalidQueryParamException) as e, params: pass assert e.value.status_code == HTTP_400_BAD_REQUEST
def test_missing_param_throws_error(): dct = {"param1": "whatever", "param2": "6.66"} for request in builder.iterbuild(dct): params = validate(request, param1=None, param2=float, param3=int) with pytest.raises(InvalidQueryParamException) as e, params: pass assert e.value.status_code == HTTP_400_BAD_REQUEST
def test_params_processed(): dct = {"num": "42", "double": "2.79", "string": "some string"} for request in builder.iterbuild(dct): with validate(request, num=int, double=float) as p: assert p.num == 42 assert p.double == 2.79 assert p.string == "some string"
def test_params_omitted(): dct = {"num": "42", "string": "some string"} for request in builder.iterbuild(dct): # Disable auto-detection of parameters (box_all) params = validate(request, num=int, box_all=False) with pytest.raises(APIException) as e, params as p: assert p.num == 42 assert p.string == "some string" assert e.value.status_code == HTTP_500_INTERNAL_SERVER_ERROR
def test_unsupported_errors_handled(): supported_exceptions = (TypeError, ValueError, KeyError) random_exceptions = (IOError, BrokenPipeError, ConnectionError, BufferError) params = {"param": "value"} for r in builder.iterbuild(params): for exc in supported_exceptions + random_exceptions: with pytest.raises(APIException) as e, validate(r): raise exc assert e.value.status_code == HTTP_500_INTERNAL_SERVER_ERROR
def test_exception_handled_in_outside_context(): """ See `QueryParamValidator._validate()` and `test_supported_errors_handled()` for more info. """ # Random exception. def f(_): raise IOError params = {"param": "value"} for r in builder.iterbuild(params): with pytest.raises(APIException) as e, validate(r, param=f): pass assert e.value.status_code == HTTP_500_INTERNAL_SERVER_ERROR
def test_params_validated(): params = { "double": "3.14", "num": "10", "price": "0", "hashme": "s$cret_t0k3n", "observable": "important metric", } for request in builder.iterbuild(params): with pytest.raises(InvalidQueryParamException) as e: simple_view(request) assert stats[-1] == params["observable"] assert e.value.status_code == HTTP_400_BAD_REQUEST with pytest.raises(InvalidQueryParamException) as e: ViewClass().complex_view(request, 1, 2) assert stats[-1] == params["observable"] assert e.value.status_code == HTTP_400_BAD_REQUEST
def test_params_provided(): params = { "double": "3.14", "num": "10", "price": "2.79", "hashme": "s$cret_t0k3n", "observable": "important metric", } for request in builder.iterbuild(params): # Test simple view r, box = simple_view(request) assert stats[-1] == params["observable"] assert set(r.query_params.keys()) == set(box.__dct__.keys()) # Test complex view r, p1, p2, box = ViewClass().complex_view(request, 1, 2) assert (p1, p2) == (1, 2) assert stats[-1] == params["observable"] assert set(r.query_params.keys()) == set(box.__dct__.keys())
def test_supported_errors_handled(): """ Only TypeError, ValueError and KeyError occurred during the validation are handled as expected. Any error thrown inside of the context will raise APIError. See `test_unsupported_errors_handled()`. """ def exc_factory(exc): def f(_): raise exc return f params = {"param": "value"} for r in builder.iterbuild(params): for exc in (TypeError, ValueError, KeyError): with pytest.raises(InvalidQueryParamException) as e, validate( r, param=exc_factory(exc)): pass assert e.value.status_code == HTTP_400_BAD_REQUEST