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