userver: /data/code/userver/testsuite/pytest_plugins/pytest_userver/plugins/service_client.py Source File
Loading...
Searching...
No Matches
service_client.py
1"""
2Service main and monitor clients.
3"""
4
5# pylint: disable=redefined-outer-name
6import logging
7import typing
8
9import aiohttp.client_exceptions
10import pytest
11import websockets
12
13from testsuite.daemons import service_client as base_service_client
14from testsuite.daemons.pytest_plugin import DaemonInstance
15from testsuite.utils import compat
16
17from pytest_userver import client
18
19logger = logging.getLogger(__name__)
20
21
22@pytest.fixture
23def extra_client_deps() -> None:
24 """
25 Service client dependencies hook. Feel free to override, e.g.:
26
27 @code
28 @pytest.fixture
29 def extra_client_deps(some_fixtures_to_wait_before_service_start):
30 pass
31 @endcode
32 @ingroup userver_testsuite_fixtures
33 """
34
35
36@pytest.fixture
37def auto_client_deps(request) -> None:
38 """
39 Service client dependencies hook that knows about pgsql, mongodb,
40 clickhouse, rabbitmq, kafka, redis_store, ydb, and mysql dependencies.
41 To add some other dependencies prefer overriding the
42 extra_client_deps() fixture.
43
44 @ingroup userver_testsuite_fixtures
45 """
46 known_deps = {
47 'pgsql',
48 'mongodb',
49 'clickhouse',
50 'rabbitmq',
51 'kafka_producer',
52 'kafka_consumer',
53 'redis_store',
54 'mysql',
55 'ydb',
56 }
57
58 try:
59 fixture_lookup_error = pytest.FixtureLookupError
60 except AttributeError:
61 # support for an older version of the pytest
62 import _pytest.fixtures
63
64 fixture_lookup_error = _pytest.fixtures.FixtureLookupError
65
66 resolved_deps = []
67 for dep in known_deps:
68 try:
69 request.getfixturevalue(dep)
70 resolved_deps.append(dep)
71 except fixture_lookup_error:
72 pass
73
74 logger.debug(
75 'userver fixture "auto_client_deps" resolved dependencies %s',
76 resolved_deps,
77 )
78
79
80@pytest.fixture
81async def service_client(
82 ensure_daemon_started,
83 service_daemon,
84 dynamic_config,
85 mock_configs_service,
86 cleanup_userver_dumps,
87 userver_client_cleanup,
88 _testsuite_client_config: client.TestsuiteClientConfig,
89 _service_client_base,
90 _service_client_testsuite,
91 # User defined client deps must be last in order to use
92 # fixtures defined above.
93 extra_client_deps,
94 auto_client_deps,
95) -> client.Client:
96 """
97 Main fixture that provides access to userver based service.
98
99 @snippet samples/testsuite-support/tests/test_ping.py service_client
100 @anchor service_client
101 @ingroup userver_testsuite_fixtures
102 """
103 # The service is lazily started here (not at the 'session' scope)
104 # to allow '*_client_deps' to be active during service start
105 daemon = await ensure_daemon_started(service_daemon)
106
107 if not _testsuite_client_config.testsuite_action_path:
108 yield _service_client_base
109 else:
110 service_client = _service_client_testsuite(daemon)
111 async with userver_client_cleanup(service_client):
112 yield service_client
113
114
115@pytest.fixture
117 request,
118 _userver_logging_plugin,
119 _dynamic_config_defaults_storage,
120 _check_config_marks,
121 dynamic_config,
122) -> typing.Callable[[client.Client], typing.AsyncGenerator]:
123 """
124 Contains the pre-test and post-test setup that depends
125 on @ref service_client.
126
127 Feel free to override, but in that case make sure to call the original
128 `userver_client_cleanup` fixture instance.
129
130 @ingroup userver_testsuite_fixtures
131 """
132 marker = request.node.get_closest_marker('suspend_periodic_tasks')
133 if marker:
134 tasks_to_suspend = marker.args
135 else:
136 tasks_to_suspend = ()
137
138 @compat.asynccontextmanager
139 async def cleanup_manager(client: client.Client):
140 @_userver_logging_plugin.register_flusher
141 async def do_flush():
142 try:
143 await client.log_flush()
144 except aiohttp.client_exceptions.ClientError:
145 pass
146 except RuntimeError:
147 # TODO: find a better way to handle closed aiohttp session
148 pass
149
150 # Service is already started we don't want startup logs to be shown
151 _userver_logging_plugin.update_position()
152
153 await _dynamic_config_defaults_storage.update(client, dynamic_config)
154 _check_config_marks()
155
156 await client.suspend_periodic_tasks(tasks_to_suspend)
157 try:
158 yield
159 finally:
160 await client.resume_all_periodic_tasks()
161
162 return cleanup_manager
163
164
165@pytest.fixture
166async def websocket_client(service_client, service_port):
167 """
168 Fixture that provides access to userver based websocket service.
169
170 @anchor websocket_client
171 @ingroup userver_testsuite_fixtures
172 """
173
174 class Client:
175 @compat.asynccontextmanager
176 async def get(self, path):
177 update_server_state = getattr(
178 service_client, 'update_server_state', None,
179 )
180 if update_server_state:
181 await update_server_state()
182 ws_context = websockets.connect(
183 f'ws://localhost:{service_port}/{path}',
184 )
185 async with ws_context as socket:
186 yield socket
187
188 return Client()
189
190
191@pytest.fixture
193 service_client,
194 service_client_options,
195 mockserver,
196 monitor_baseurl: str,
197 _testsuite_client_config: client.TestsuiteClientConfig,
199 """
200 Main fixture that provides access to userver monitor listener.
201
202 @snippet samples/testsuite-support/tests/test_metrics.py metrics labels
203 @ingroup userver_testsuite_fixtures
204 """
205 aiohttp_client = client.AiohttpClientMonitor(
206 monitor_baseurl,
207 config=_testsuite_client_config,
208 headers={'x-yatraceid': mockserver.trace_id},
209 **service_client_options,
210 )
211 return client.ClientMonitor(aiohttp_client)
212
213
214@pytest.fixture
215async def _service_client_base(service_baseurl, service_client_options):
216 class _ClientDiagnose(base_service_client.Client):
217 def __getattr__(self, name: str) -> None:
218 raise AttributeError(
219 f'"Client" object has no attribute "{name}". '
220 'Note that "service_client" fixture returned the basic '
221 '"testsuite.daemons.service_client.Client" client rather than '
222 'a "pytest_userver.client.Client" client with userver '
223 'extensions. That happened because the service '
224 'static configuration file contains no "tests-control" '
225 'component with "action" field.',
226 )
227
228 return _ClientDiagnose(service_baseurl, **service_client_options)
229
230
231@pytest.fixture
232def _service_client_testsuite(
233 service_baseurl,
234 service_client_options,
235 mocked_time,
236 userver_cache_control,
237 userver_log_capture,
238 testpoint,
239 testpoint_control,
240 cache_invalidation_state,
241 service_periodic_tasks_state,
242 _testsuite_client_config: client.TestsuiteClientConfig,
243 asyncexc_check,
244) -> typing.Callable[[DaemonInstance], client.Client]:
245 def create_client(daemon):
246 aiohttp_client = client.AiohttpClient(
247 service_baseurl,
248 config=_testsuite_client_config,
249 testpoint=testpoint,
250 testpoint_control=testpoint_control,
251 periodic_tasks_state=service_periodic_tasks_state,
252 log_capture_fixture=userver_log_capture,
253 mocked_time=mocked_time,
254 cache_invalidation_state=cache_invalidation_state,
255 cache_control=userver_cache_control(daemon),
256 asyncexc_check=asyncexc_check,
257 **service_client_options,
258 )
259 return client.Client(aiohttp_client)
260
261 return create_client
262
263
264@pytest.fixture(scope='session')
265def service_baseurl(service_port) -> str:
266 """
267 Returns the main listener URL of the service.
268
269 Override this fixture to change the main listener URL that the testsuite
270 uses for tests.
271
272 @ingroup userver_testsuite_fixtures
273 """
274 return f'http://localhost:{service_port}/'
275
276
277@pytest.fixture(scope='session')
278def monitor_baseurl(monitor_port) -> str:
279 """
280 Returns the main monitor URL of the service.
281
282 Override this fixture to change the main monitor URL that the testsuite
283 uses for tests.
284
285 @ingroup userver_testsuite_fixtures
286 """
287 return f'http://localhost:{monitor_port}/'
288
289
290@pytest.fixture(scope='session')
291def service_periodic_tasks_state() -> client.PeriodicTasksState:
293
294
295@pytest.fixture(scope='session')
296def _testsuite_client_config(
297 pytestconfig, service_config,
298) -> client.TestsuiteClientConfig:
299 components = service_config['components_manager']['components']
300
301 def get_component_path(name, argname=None):
302 if name in components:
303 path = components[name]['path']
304 path = path.rstrip('*')
305
306 if argname and f'{{{argname}}}' not in path:
307 raise RuntimeError(
308 f'Component {name} must provide path argument {argname}',
309 )
310 return path
311 return None
312
314 server_monitor_path=get_component_path('handler-server-monitor'),
315 testsuite_action_path=get_component_path('tests-control', 'action'),
316 )