def test_format_title_simple(): problem_type = ProblemType("https://example.com/foo", extension={"bar": { "type": "string" }}) assert problem_type.format("test {foo}", {"foo": "bar baz bam"}) == "test bar baz bam"
class ProblemDetails: def __init__( self, *, problem_type: Optional[ProblemType] = None, title: Optional[str] = None, detail: Optional[str] = None, status: Optional[Union[int, HTTPStatus]] = None, **kwargs, ): self.type = ProblemType() if problem_type is None else problem_type self.data = self.validate_data(kwargs) if title is None: title = self.type.title self.title = self.type.format(title, self.data) if detail is None: detail = self.type.detail self.detail = self.type.format(detail, self.data) self.status = None if status is None else HTTPStatus(status) def __iter__(self): yield "type", self.type.identifier yield "title", self.title yield "detail", self.detail yield "status", self.status yield from self.data.items() def __getattr__(self, item): try: return self.data[item] except KeyError: raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'") def validate_data(self, data): return {field: self.type.validate(field, value) for field, value in data.items()}
def test_format_title_raises_error_on_incorrect_nested_key(): nested_schema = { "foo": { "type": "object", "items": { "bar": { "type": "string" } } } } problem_type = ProblemType("https://example.com/foo", extension=nested_schema) with pytest.raises(AttributeError) as ex: problem_type.format("test {foo.baa}", {"foo": {"bar": "bar baz bam"}}) assert "object has no attribute 'baa'" in str(ex.value)
def test_convert_blank_type_to_dict(): problem_type = ProblemType() assert dict(problem_type) == { "identifier": "about:blank", "title": "", "detail": "", "extension": {}, }
def test_format_title_nested(): nested_schema = { "foo": { "type": "object", "items": { "bar": { "type": "string" } } } } problem_type = ProblemType("https://example.com/foo", extension=nested_schema) assert (problem_type.format("test {foo.bar}", {"foo": { "bar": "bar baz bam" }}) == "test bar baz bam")
def __init__( self, *, problem_type: Optional[ProblemType] = None, title: Optional[str] = None, detail: Optional[str] = None, status: Optional[Union[int, HTTPStatus]] = None, **kwargs, ): self.type = ProblemType() if problem_type is None else problem_type self.data = self.validate_data(kwargs) if title is None: title = self.type.title self.title = self.type.format(title, self.data) if detail is None: detail = self.type.detail self.detail = self.type.format(detail, self.data) self.status = None if status is None else HTTPStatus(status)
def test_construct_typed_details_with_incorrect_data_wrong_value(): problem_type = ProblemType(extension={ "foo": { "type": "string" }, "baz": { "type": "integer" } }) with pytest.raises(ValidationError) as ex: ProblemDetails(problem_type=problem_type, foo="bar", baz="123") assert "'123' is not of type 'integer'" in str(ex.value)
def test_construct_typed_details_with_incorrect_data_wrong_field(): problem_type = ProblemType(extension={ "foo": { "type": "string" }, "baz": { "type": "integer" } }) with pytest.raises(ValueError) as ex: ProblemDetails(problem_type=problem_type, foo="bar", baa=123) assert "'baa' is not a valid extension member for problem type 'ProblemType'." in str( ex.value)
def test_construct_typed_details_with_data(): problem_type = ProblemType(extension={ "foo": { "type": "string" }, "baz": { "type": "integer" } }) details = ProblemDetails(problem_type=problem_type, foo="bar", baz=123) assert hasattr(details, "foo") assert details.foo == "bar" assert details.baz == 123 assert not hasattr(details, "bar")
def base_type(): return ProblemType( identifier="//example.com/base", title="Base example type", detail="This is a base example type, imagine that!", extension={ "foo": { "type": "string" }, "bar": { "type": "integer" } }, )
def test_convert_typed_details_with_data_to_dict(): problem_type = ProblemType(extension={ "foo": { "type": "string" }, "baz": { "type": "integer" } }) details = ProblemDetails(problem_type=problem_type, foo="bar", baz=123) result = dict(details) assert result == { "type": "about:blank", "title": "", "detail": "", "status": None, "foo": "bar", "baz": 123, }
def test_convert_type_to_dict(): problem_type = ProblemType( "https://example.com/foo", "Foo problem", "Foo fighters attack", extension={ "bar": {}, "baz": {} }, ) assert dict(problem_type) == { "identifier": "https://example.com/foo", "title": "Foo problem", "detail": "Foo fighters attack", "extension": { "bar": {}, "baz": {} }, }
def test_extension_is_rejected_if_not_valid_json_schema(): with pytest.raises(TypeError) as ex: ProblemType(extension={"foo": []}) assert str(ex).endswith( "Extension for field 'foo' needs to be a valid JSON schema.")
def test_extension_is_rejected_if_includes_class_attribute_names(input): with pytest.raises(ValueError) as ex: ProblemType(extension={input: {}}) assert str(ex).endswith( f"Extension member name {input} is not allowed.")
def test_construct_blank_type(): problem_type = ProblemType() assert problem_type.identifier == "about:blank" assert problem_type.title == "" assert problem_type.detail == "" assert problem_type.extension == {}
def test_rejects_identifier_without_path(): with pytest.raises(ValueError) as ex: ProblemType("https://example.com") assert str(ex).endswith("path was required but missing")
def test_rejects_unallowed_hostname(): with pytest.raises(ValueError) as ex: ProblemType("https://foo.bar/baz") assert str(ex).endswith( "Host was required to be one of ['example.com'] but was 'foo.bar'")
def test_identifier_inserts_default_scheme_and_host(input, expected): problem_type = ProblemType(input) assert problem_type.identifier == expected
def test_identifier_must_not_be_empty(): with pytest.raises(ValueError): ProblemType(identifier="")
def test_convert_blank_type_to_string(): problem_type = ProblemType() assert str(problem_type) == "about:blank"
def test_identifier_inserts_default_scheme(): problem_type = ProblemType("//example.com/baz") assert problem_type.identifier == "https://example.com/baz"