-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathtest_thing_client.py
More file actions
176 lines (133 loc) · 5.86 KB
/
test_thing_client.py
File metadata and controls
176 lines (133 loc) · 5.86 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""Test that Thing Client's can call actions and read properties."""
import re
import pytest
import labthings_fastapi as lt
from fastapi.testclient import TestClient
class ThingToTest(lt.Thing):
"""A thing to be tested by using a ThingClient."""
int_prop: int = lt.property(default=1)
float_prop: float = lt.property(default=0.1)
str_prop: str = lt.property(default="foo")
int_prop_read_only: int = lt.property(default=1, readonly=True)
float_prop_read_only: float = lt.property(default=0.1, readonly=True)
str_prop_read_only: str = lt.property(default="foo", readonly=True)
@lt.action
def increment(self) -> None:
"""Increment the counter.
An action with no arguments or return.
"""
self.int_prop += 1
@lt.action
def increment_and_return(self) -> int:
"""Increment the counter and return value.
An action with no arguments, but with a return value
"""
self.int_prop += 1
return self.int_prop
@lt.action
def increment_by_input(self, value: int) -> None:
"""Increment the counter by input value.
An action with an argument but no return.
"""
self.int_prop += value
@lt.action
def increment_by_input_and_return(self, value: int) -> int:
"""Increment the counter by input value and return the new value.
An action with and argument and a return value.
"""
self.int_prop += value
return self.int_prop
@lt.action
def throw_value_error(self) -> None:
"""Throw a value error."""
raise ValueError("This never works!")
@pytest.fixture
def thing_client():
"""Yield a test client connected to a ThingServer."""
server = lt.ThingServer({"test_thing": ThingToTest}, api_prefix="/api/v1")
with TestClient(server.app) as client:
yield lt.ThingClient.from_url("/api/v1/test_thing/", client=client)
def test_reading_and_setting_properties(thing_client):
"""Test reading and setting properties."""
assert thing_client.int_prop == 1
assert thing_client.float_prop == 0.1
assert thing_client.str_prop == "foo"
thing_client.int_prop = 2
thing_client.float_prop = 0.2
thing_client.str_prop = "foo2"
assert thing_client.int_prop == 2
assert thing_client.float_prop == 0.2
assert thing_client.str_prop == "foo2"
# Set a property that doesn't exist.
err = "Failed to get property foobar: Not Found"
with pytest.raises(lt.exceptions.ClientPropertyError, match=err):
thing_client.get_property("foobar")
# Set a property with bad data type.
err = (
"Failed to get property int_prop: Input should be a valid integer, unable to "
"parse string as an integer"
)
with pytest.raises(lt.exceptions.ClientPropertyError, match=err):
thing_client.int_prop = "Bad value!"
def test_reading_and_not_setting_read_only_properties(thing_client):
"""Test reading read_only properties, but failing to set."""
assert thing_client.int_prop_read_only == 1
assert thing_client.float_prop_read_only == 0.1
assert thing_client.str_prop_read_only == "foo"
with pytest.raises(lt.exceptions.ClientPropertyError, match="Method Not Allowed"):
thing_client.int_prop_read_only = 2
with pytest.raises(lt.exceptions.ClientPropertyError, match="Method Not Allowed"):
thing_client.float_prop_read_only = 0.2
with pytest.raises(lt.exceptions.ClientPropertyError, match="Method Not Allowed"):
thing_client.str_prop_read_only = "foo2"
assert thing_client.int_prop_read_only == 1
assert thing_client.float_prop_read_only == 0.1
assert thing_client.str_prop_read_only == "foo"
def test_call_action(thing_client):
"""Test calling an action."""
assert thing_client.int_prop == 1
thing_client.increment()
assert thing_client.int_prop == 2
def test_call_action_with_return(thing_client):
"""Test calling an action with a return."""
assert thing_client.int_prop == 1
new_value = thing_client.increment_and_return()
assert new_value == 2
assert thing_client.int_prop == 2
def test_call_action_with_args(thing_client):
"""Test calling an action."""
assert thing_client.int_prop == 1
thing_client.increment_by_input(value=5)
assert thing_client.int_prop == 6
def test_call_action_with_args_and_return(thing_client):
"""Test calling an action with a return."""
assert thing_client.int_prop == 1
new_value = thing_client.increment_by_input_and_return(value=5)
assert new_value == 6
assert thing_client.int_prop == 6
def test_call_action_wrong_arg(thing_client):
"""Test calling an action with wrong argument."""
err = "Error when invoking action increment_by_input: 'value' - Field required"
with pytest.raises(lt.exceptions.FailedToInvokeActionError, match=err):
thing_client.increment_by_input(input=5)
def test_call_action_wrong_type(thing_client):
"""Test calling an action with wrong argument."""
err = (
"Error when invoking action increment_by_input: 'value' - Input should be a "
"valid integer, unable to parse string as an integer"
)
with pytest.raises(lt.exceptions.FailedToInvokeActionError, match=err):
thing_client.increment_by_input(value="foo")
def test_call_that_errors(thing_client):
"""Test calling an action with wrong argument."""
regex = r"Action throw_value_error \(ID: [0-9a-f\-]*\) failed with error:"
with pytest.raises(lt.exceptions.ServerActionError, match=regex) as exc_info:
thing_client.throw_value_error()
full_message = str(exc_info.value)
assert "[ValueError]: This never works!" in full_message
assert "SERVER TRACEBACK START:" in full_message
assert "SERVER TRACEBACK END" in full_message
assert re.search(
r'File ".*test_thing_client\.py", line \d+, in throw_value_error',
full_message,
)