userver: /home/antonyzhilin/arcadia/taxi/uservices/userver/testsuite/pytest_plugins/pytest_userver/plugins/service_client.py Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
service_client.py
1"""
2Service main and monitor clients.
3"""
4
5# pylint: disable=redefined-outer-name
6import typing
7
8import aiohttp.client_exceptions
9import pytest
10import websockets
11
12from testsuite.daemons import service_client as base_service_client
13from testsuite.utils import compat
14
15from pytest_userver import client
16
17
18@pytest.fixture
19async def service_client(
20 service_daemon_instance,
21 service_baseurl,
22 service_client_options,
23 userver_service_client_options,
24 userver_client_cleanup,
25 _testsuite_client_config: client.TestsuiteClientConfig,
26) -> client.Client:
27 """
28 Main fixture that provides access to userver based service.
29
30 @snippet samples/testsuite-support/tests/test_ping.py service_client
31 @anchor service_client
32 @ingroup userver_testsuite_fixtures
33 """
34 if not _testsuite_client_config.testsuite_action_path:
35 yield _ClientDiagnose(service_baseurl, **service_client_options)
36 return
37
38 aiohttp_client = client.AiohttpClient(
39 service_baseurl,
40 **userver_service_client_options,
41 )
42 userver_client = client.Client(aiohttp_client)
43 async with userver_client_cleanup(userver_client):
44 yield userver_client
45
46
47@pytest.fixture
49 request,
50 service_logs_update_position,
51 servicelogs_register_flusher,
52 _dynamic_config_defaults_storage,
53 _check_config_marks,
54 dynamic_config,
55) -> typing.Callable[[client.Client], typing.AsyncGenerator]:
56 """
57 Contains the pre-test and post-test setup that depends
58 on @ref service_client.
59
60 Feel free to override, but in that case make sure to call the original
61 `userver_client_cleanup` fixture instance.
62
63 @ingroup userver_testsuite_fixtures
64 """
65 marker = request.node.get_closest_marker('suspend_periodic_tasks')
66 if marker:
67 tasks_to_suspend = marker.args
68 else:
69 tasks_to_suspend = ()
70
71 @compat.asynccontextmanager
72 async def cleanup_manager(client: client.Client):
73 @servicelogs_register_flusher
74 async def do_flush():
75 try:
76 await client.log_flush()
77 except aiohttp.client_exceptions.ClientError:
78 pass
79 except RuntimeError:
80 # TODO: find a better way to handle closed aiohttp session
81 pass
82
83 # Service is already started we don't want startup logs to be shown
84 service_logs_update_position()
85
86 await _dynamic_config_defaults_storage.update(client, dynamic_config)
87 _check_config_marks()
88
89 await client.suspend_periodic_tasks(tasks_to_suspend)
90 try:
91 yield
92 finally:
93 await client.resume_all_periodic_tasks()
94
95 return cleanup_manager
96
97
98@pytest.fixture
99async def websocket_client(service_client, service_port):
100 """
101 Fixture that provides access to userver based websocket service.
102
103 @anchor websocket_client
104 @ingroup userver_testsuite_fixtures
105 """
106
107 class Client:
108 @compat.asynccontextmanager
109 async def get(self, path):
110 update_server_state = getattr(
111 service_client,
112 'update_server_state',
113 None,
114 )
115 if update_server_state:
116 await update_server_state()
117 ws_context = websockets.connect(
118 f'ws://localhost:{service_port}/{path}',
119 )
120 async with ws_context as socket:
121 yield socket
122
123 return Client()
124
125
126@pytest.fixture
128 service_client, # For daemon setup and userver_client_cleanup
129 userver_monitor_client_options,
130 monitor_baseurl,
131) -> client.ClientMonitor:
132 """
133 Main fixture that provides access to userver monitor listener.
134
135 @snippet samples/testsuite-support/tests/test_metrics.py metrics labels
136 @ingroup userver_testsuite_fixtures
137 """
138 aiohttp_client = client.AiohttpClientMonitor(
139 monitor_baseurl,
140 **userver_monitor_client_options,
141 )
142 return client.ClientMonitor(aiohttp_client)
143
144
145# @cond
146
147
148class _ClientDiagnose(base_service_client.Client):
149 def __getattr__(self, name: str) -> None:
150 raise AttributeError(
151 f'"Client" object has no attribute "{name}". '
152 'Note that "service_client" fixture returned the basic '
153 '"testsuite.daemons.service_client.Client" client rather than '
154 'a "pytest_userver.client.Client" client with userver '
155 'extensions. That happened because the service '
156 'static configuration file contains no "tests-control" '
157 'component with "action" field.',
158 )
159
160
161# Overriding testsuite fixture
162@pytest.fixture(name='service_client_options')
163def _service_client_options(
164 service_client_options,
165 service_client_default_headers,
166 mockserver,
167):
168 # Should only use options for the base testsuite client here,
169 # not for the userver client.
170 return dict(
171 service_client_options,
172 headers={
173 **service_client_default_headers,
174 mockserver.trace_id_header: mockserver.trace_id,
175 },
176 )
177
178
179@pytest.fixture
180def userver_service_client_options(
181 service_client_options,
182 _testsuite_client_config: client.TestsuiteClientConfig,
183 testpoint,
184 testpoint_control,
185 service_periodic_tasks_state,
186 userver_log_capture,
187 mocked_time,
188 cache_invalidation_state,
189 userver_cache_control,
190 asyncexc_check,
191 userver_allow_all_caches_invalidation,
192):
193 return dict(
194 **service_client_options,
195 config=_testsuite_client_config,
196 testpoint=testpoint,
197 testpoint_control=testpoint_control,
198 periodic_tasks_state=service_periodic_tasks_state,
199 log_capture_fixture=userver_log_capture,
200 mocked_time=mocked_time,
201 cache_invalidation_state=cache_invalidation_state,
202 cache_control=userver_cache_control,
203 asyncexc_check=asyncexc_check,
204 allow_all_caches_invalidation=userver_allow_all_caches_invalidation,
205 )
206
207
208@pytest.fixture
209def userver_monitor_client_options(
210 service_client_options,
211 _testsuite_client_config: client.TestsuiteClientConfig,
212):
213 return dict(**service_client_options, config=_testsuite_client_config)
214
215
216# @endcond
217
218
219@pytest.fixture(scope='session')
220def service_baseurl(service_port) -> str:
221 """
222 Returns the main listener URL of the service.
223
224 Override this fixture to change the main listener URL that the testsuite
225 uses for tests.
226
227 @ingroup userver_testsuite_fixtures
228 """
229 return f'http://localhost:{service_port}/'
230
231
232@pytest.fixture(scope='session')
233def monitor_baseurl(monitor_port) -> str:
234 """
235 Returns the main monitor URL of the service.
236
237 Override this fixture to change the main monitor URL that the testsuite
238 uses for tests.
239
240 @ingroup userver_testsuite_fixtures
241 """
242 return f'http://localhost:{monitor_port}/'
243
244
245@pytest.fixture(scope='session')
246def service_periodic_tasks_state() -> client.PeriodicTasksState:
248
249
250@pytest.fixture(scope='session')
251def _testsuite_client_config(service_config) -> client.TestsuiteClientConfig:
252 components = service_config['components_manager']['components']
253
254 def get_component_path(name, argname=None):
255 if name in components:
256 path = components[name]['path']
257 path = path.rstrip('*')
258
259 if argname and f'{{{argname}}}' not in path:
260 raise RuntimeError(
261 f'Component {name} must provide path argument {argname}',
262 )
263 return path
264 return None
265
267 server_monitor_path=get_component_path('handler-server-monitor'),
268 testsuite_action_path=get_component_path('tests-control', 'action'),
269 )