TestBroadExceptionHandler.test_pydantic_validation_error()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 22
nop 1
dl 0
loc 34
rs 9.352
c 0
b 0
f 0
1
from dataclasses import dataclass
2
from enum import Enum
3
from typing import Union, Callable, Type
4
5
from flask import Flask
6
from pydantic import ValidationError, NoneIsNotAllowedError, BoolError, BaseModel
7
from pydantic.error_wrappers import ErrorWrapper
8
from werkzeug.exceptions import HTTPException, PreconditionFailed
9
from werkzeug.routing import RequestRedirect
10
11
from app.hooks.error import broad_exception_handler
12
from tests import BaseTestCase
13
14
15
@dataclass
16
class ExceptionHandlingSpec:
17
    error_handler_exception_or_code: Union[Type[Exception], int]
18
    error_handler_func: Callable
19
    exception_instance_to_handler_raises: Exception
20
21
22
class TestError(BaseTestCase):
23
    def setUp(self):
24
        super(TestError, self).setUp()
25
26
        self.path = "/foo"
27
28
    def add_route_raises_exception(
29
        self, exception_handling_spec: ExceptionHandlingSpec
30
    ):
31
        self.app = Flask(__name__)
32
        self.app.register_error_handler(
33
            exception_handling_spec.error_handler_exception_or_code,
34
            exception_handling_spec.error_handler_func,
35
        )
36
        self.client = self.app.test_client()
37
38
        @self.app.route(self.path)
39
        def handler():
40
            raise exception_handling_spec.exception_instance_to_handler_raises
41
42
43
class TestBroadExceptionHandler(TestError):
44
    def test_http_exception(self):
45
        for exception_cls in HTTPException.__subclasses__():
46
            if exception_cls in (RequestRedirect, PreconditionFailed):
47
                continue
48
49
            exception_instance = exception_cls()
50
51
            self.add_route_raises_exception(
52
                ExceptionHandlingSpec(
53
                    Exception, broad_exception_handler, exception_instance
54
                )
55
            )
56
57
            resp = self.request()
58
59
            self.assertEqual(exception_instance.code, resp.status_code)
60
            self.assertTrue(resp.is_json)
61
            self.assertDictEqual({"error": exception_instance.description}, resp.json)
62
63
    def test_pydantic_validation_error(self):
64
        self.add_route_raises_exception(
65
            ExceptionHandlingSpec(
66
                Exception,
67
                broad_exception_handler,
68
                ValidationError(
69
                    [
70
                        ErrorWrapper(NoneIsNotAllowedError(), "foo"),
71
                        ErrorWrapper(BoolError(), "bar"),
72
                    ],
73
                    BaseModel,
74
                ),
75
            )
76
        )
77
78
        resp = self.request()
79
80
        self.assertEqual(400, resp.status_code)
81
        self.assertDictEqual(
82
            {
83
                "error": [
84
                    {
85
                        "loc": ["foo"],
86
                        "msg": "none is not an allowed value",
87
                        "type": "type_error.none.not_allowed",
88
                    },
89
                    {
90
                        "loc": ["bar"],
91
                        "msg": "value could not be parsed to a boolean",
92
                        "type": "type_error.bool",
93
                    },
94
                ]
95
            },
96
            resp.json,
97
        )
98
99
    def test_pydantic_validation_error_with_enum(self):
100
        class Example(BaseModel):
101
            class Choices(Enum):
102
                A = 1
103
                B = 2
104
                C = 3
105
106
            c: Choices
107
108
        try:
109
            Example(c=4)
110
        except ValidationError as e:
111
            self.add_route_raises_exception(
112
                ExceptionHandlingSpec(Exception, broad_exception_handler, e,)
113
            )
114
115
        resp = self.request()
116
117
        self.assertEqual(400, resp.status_code)
118
        self.assertDictEqual(
119
            {
120
                "error": [
121
                    {
122
                        "ctx": {"enum_values": [1, 2, 3]},
123
                        "loc": ["c"],
124
                        "msg": "value is not a valid enumeration member; permitted: 1, 2, 3",
125
                        "type": "type_error.enum",
126
                    }
127
                ]
128
            },
129
            resp.json,
130
        )
131
132
    def test_500(self):
133
        self.add_route_raises_exception(
134
            ExceptionHandlingSpec(Exception, broad_exception_handler, KeyError())
135
        )
136
137
        resp = self.request()
138
139
        self.assertEqual(500, resp.status_code)
140
        self.assertDictEqual({"error": ""}, resp.json)
141