-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathtest_server_config_model.py
More file actions
147 lines (121 loc) · 4.65 KB
/
test_server_config_model.py
File metadata and controls
147 lines (121 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
r"""Test code for `.server.config_model`\ ."""
from pydantic import ValidationError
import pytest
from labthings_fastapi.server.config_model import (
ThingConfig,
ThingServerConfig,
ThingImportFailure,
)
import labthings_fastapi.example_things
from labthings_fastapi.example_things import MyThing
def test_ThingConfig():
"""Test the ThingConfig model loads classes as expected."""
# We should be able to create a valid config with just a class
direct = ThingConfig(cls=labthings_fastapi.example_things.MyThing)
# Equivalently, we should be able to pass a string
fromstr = ThingConfig(cls="labthings_fastapi.example_things:MyThing")
assert direct.cls is MyThing
assert fromstr.cls is MyThing
# In the absence of supplied arguments, default factories should be used
assert len(direct.args) == 0
assert direct.kwargs == {}
assert direct.thing_slots == {}
with pytest.raises(ThingImportFailure, match="No module named 'missing.module'"):
ThingConfig(cls="missing.module")
VALID_THING_CONFIGS = {
"direct": MyThing,
"string": "labthings_fastapi.example_things:MyThing",
"model_d": ThingConfig(cls=MyThing),
"model_s": ThingConfig(cls="labthings_fastapi.example_things:MyThing"),
"dict_d": {"cls": MyThing},
"dict_da": {"class": MyThing},
"dict_s": {"cls": "labthings_fastapi.example_things:MyThing"},
"dict_sa": {"class": "labthings_fastapi.example_things:MyThing"},
}
INVALID_THING_CONFIGS = [
{},
{"foo": "bar"},
{"class": MyThing, "kwargs": 1},
4,
None,
False,
]
VALID_THING_NAMES = [
"my_thing",
"MyThing",
"Something",
"f90785342",
"1",
]
INVALID_THING_NAMES = [
"",
"spaces in name",
"special * chars",
False,
1,
"/",
"thing/with/slashes",
"trailingslash/",
"/leadingslash",
]
def test_ThingServerConfig():
"""Check validation of the whole server config."""
# Things should be able to be specified as a string, a class, or a ThingConfig
config = ThingServerConfig(things=VALID_THING_CONFIGS)
assert len(config.thing_configs) == 8
for v in config.thing_configs.values():
assert v.cls is MyThing
# When we validate from a dict, the same options work
config = ThingServerConfig.model_validate({"things": VALID_THING_CONFIGS})
assert len(config.thing_configs) == 8
for v in config.thing_configs.values():
assert v.cls is MyThing
# Check invalid configs are picked up
for spec in INVALID_THING_CONFIGS:
with pytest.raises(ValidationError):
ThingServerConfig(things={"thing": spec})
# Check valid names are allowed
for name in VALID_THING_NAMES:
sc = ThingServerConfig(things={name: MyThing})
assert sc.thing_configs[name].cls is MyThing
# Check bad names raise errors
for name in INVALID_THING_NAMES:
with pytest.raises(ValidationError):
ThingServerConfig(things={name: MyThing})
# Check some good prefixes
for prefix in ["", "/api", "/api/v2", "/api-v2"]:
config = ThingServerConfig(things={}, api_prefix=prefix)
assert config.api_prefix == prefix
# Check some bad prefixes
for prefix in ["api", "/api/", "api/v2", "/badchars!"]:
with pytest.raises(ValidationError):
ThingServerConfig(things={}, api_prefix=prefix)
def test_unimportable_modules():
"""Test that unimportable modules raise errors as expected."""
with pytest.raises(ThingImportFailure, match="No module named 'missing.module'"):
ThingConfig(cls="missing.module:object")
with pytest.raises(
ThingImportFailure,
match=r"\[RuntimeError\] This module should not be importable!",
):
# This checks RuntimErrors get reported with a single error
ThingConfig(cls="tests.unimportable.runtimeerror:SomeClass")
with pytest.raises(
ThingImportFailure,
match=r"\[ValueError\] This module should not be importable due to ValueError!",
):
# This checks ValueErrors get reported with a single error
# rather than getting swallowed by a ValidationError
ThingConfig(cls="tests.unimportable.valueerror:SomeClass")
with pytest.raises(
ThingImportFailure,
match="No module named 'tests.unimportable.missing'",
):
# This checks normal ImportErrors get reported as usual
ThingConfig(cls="tests.unimportable.missing:SomeClass")
with pytest.raises(
ThingImportFailure,
match=("cannot import name 'MissingClass' from 'tests.unimportable'"),
):
# This checks normal ImportErrors get reported as usual
ThingConfig(cls="tests.unimportable:MissingClass")