userver: /data/code/service_template/third_party/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 pytest
10import websockets
11
12from testsuite.daemons import service_client as base_service_client
13from testsuite.daemons.pytest_plugin import DaemonInstance
14from testsuite.utils import compat
15
16from pytest_userver import client
17
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, 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 'redis_store',
52 'mysql',
53 'ydb',
54 }
55
56 try:
57 fixture_lookup_error = pytest.FixtureLookupError
58 except AttributeError:
59 # support for an older version of the pytest
60 import _pytest.fixtures
61 fixture_lookup_error = _pytest.fixtures.FixtureLookupError
62
63 resolved_deps = []
64 for dep in known_deps:
65 try:
66 request.getfixturevalue(dep)
67 resolved_deps.append(dep)
68 except fixture_lookup_error:
69 pass
70
71 logger.debug(
72 'userver fixture "auto_client_deps" resolved dependencies %s',
73 resolved_deps,
74 )
75
76
77@pytest.fixture
78async def service_client(
79 ensure_daemon_started,
80 service_daemon,
81 dynamic_config,
82 mock_configs_service,
83 cleanup_userver_dumps,
84 extra_client_deps,
85 auto_client_deps,
86 _config_service_defaults_updated,
87 _testsuite_client_config: client.TestsuiteClientConfig,
88 _service_client_base,
89 _service_client_testsuite,
90) -> client.Client:
91 """
92 Main fixture that provides access to userver based service.
93
94 @snippet samples/testsuite-support/tests/test_ping.py service_client
95 @anchor service_client
96 @ingroup userver_testsuite_fixtures
97 """
98 # The service is lazily started here (not at the 'session' scope)
99 # to allow '*_client_deps' to be active during service start
100 daemon = await ensure_daemon_started(service_daemon)
101
102 if not _testsuite_client_config.testsuite_action_path:
103 return _service_client_base
104
105 service_client = _service_client_testsuite(daemon)
106 await _config_service_defaults_updated.update(
107 service_client, dynamic_config,
108 )
109 return service_client
110
111
112@pytest.fixture
113async def websocket_client(service_client, service_port):
114 """
115 Fixture that provides access to userver based websocket service.
116
117 @anchor websocket_client
118 @ingroup userver_testsuite_fixtures
119 """
120
121 class Client:
122 @compat.asynccontextmanager
123 async def get(self, path):
124 update_server_state = getattr(
125 service_client, 'update_server_state', None,
126 )
127 if update_server_state:
128 await update_server_state()
129 ws_context = websockets.connect(
130 f'ws://localhost:{service_port}/{path}',
131 )
132 async with ws_context as socket:
133 yield socket
134
135 return Client()
136
137
138@pytest.fixture
140 service_client,
141 service_client_options,
142 mockserver,
143 monitor_baseurl: str,
144 _testsuite_client_config: client.TestsuiteClientConfig,
146 """
147 Main fixture that provides access to userver monitor listener.
148
149 @snippet samples/testsuite-support/tests/test_metrics.py metrics labels
150 @ingroup userver_testsuite_fixtures
151 """
152 aiohttp_client = client.AiohttpClientMonitor(
153 monitor_baseurl,
154 config=_testsuite_client_config,
155 headers={'x-yatraceid': mockserver.trace_id},
156 **service_client_options,
157 )
158 return client.ClientMonitor(aiohttp_client)
159
160
161@pytest.fixture
162async def _service_client_base(service_baseurl, service_client_options):
163 class _ClientDiagnose(base_service_client.Client):
164 def __getattr__(self, name: str) -> None:
165 raise AttributeError(
166 f'"Client" object has no attribute "{name}". '
167 'Note that "service_client" fixture returned the basic '
168 '"testsuite.daemons.service_client.Client" client rather than '
169 'a "pytest_userver.client.Client" client with userver '
170 'extensions. That happened because the service '
171 'static configuration file contains no "tests-control" '
172 'component with "action" field.',
173 )
174
175 return _ClientDiagnose(service_baseurl, **service_client_options)
176
177
178@pytest.fixture
179def _service_client_testsuite(
180 service_baseurl,
181 service_client_options,
182 mocked_time,
183 userver_cache_control,
184 userver_log_capture,
185 testpoint,
186 testpoint_control,
187 cache_invalidation_state,
188 _testsuite_client_config: client.TestsuiteClientConfig,
189) -> typing.Callable[[DaemonInstance], client.Client]:
190 def create_client(daemon):
191 aiohttp_client = client.AiohttpClient(
192 service_baseurl,
193 config=_testsuite_client_config,
194 testpoint=testpoint,
195 testpoint_control=testpoint_control,
196 log_capture_fixture=userver_log_capture,
197 mocked_time=mocked_time,
198 cache_invalidation_state=cache_invalidation_state,
199 cache_control=userver_cache_control(daemon),
200 **service_client_options,
201 )
202 return client.Client(aiohttp_client)
203
204 return create_client
205
206
207@pytest.fixture(scope='session')
208def service_baseurl(service_port) -> str:
209 """
210 Returns the main listener URL of the service.
211
212 Override this fixture to change the main listener URL that the testsuite
213 uses for tests.
214
215 @ingroup userver_testsuite_fixtures
216 """
217 return f'http://localhost:{service_port}/'
218
219
220@pytest.fixture(scope='session')
221def monitor_baseurl(monitor_port) -> str:
222 """
223 Returns the main monitor URL of the service.
224
225 Override this fixture to change the main monitor URL that the testsuite
226 uses for tests.
227
228 @ingroup userver_testsuite_fixtures
229 """
230 return f'http://localhost:{monitor_port}/'
231
232
233@pytest.fixture(scope='session')
234def _testsuite_client_config(
235 pytestconfig, service_config,
236) -> client.TestsuiteClientConfig:
237 components = service_config['components_manager']['components']
238
239 def get_component_path(name, argname=None):
240 if name in components:
241 path = components[name]['path']
242 path = path.rstrip('*')
243
244 if argname and f'{{{argname}}}' not in path:
245 raise RuntimeError(
246 f'Component {name} must provide path argument {argname}',
247 )
248 return path
249 return None
250
252 server_monitor_path=get_component_path('handler-server-monitor'),
253 testsuite_action_path=get_component_path('tests-control', 'action'),
254 )