diracx-testing 0.0.1a8__py3-none-any.whl → 0.0.1a10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,11 +5,11 @@ import contextlib
5
5
  import os
6
6
  import re
7
7
  import subprocess
8
- from datetime import datetime, timedelta
8
+ from datetime import datetime, timedelta, timezone
9
9
  from html.parser import HTMLParser
10
10
  from pathlib import Path
11
11
  from typing import TYPE_CHECKING
12
- from urllib.parse import urljoin
12
+ from urllib.parse import parse_qs, urljoin, urlparse
13
13
  from uuid import uuid4
14
14
 
15
15
  import pytest
@@ -55,22 +55,25 @@ def pytest_collection_modifyitems(config, items):
55
55
  item.add_marker(skip_regen)
56
56
 
57
57
 
58
- @pytest.fixture
59
- def test_auth_settings() -> AuthSettings:
58
+ @pytest.fixture(scope="session")
59
+ def rsa_private_key_pem() -> str:
60
60
  from cryptography.hazmat.primitives import serialization
61
61
  from cryptography.hazmat.primitives.asymmetric import rsa
62
62
 
63
- from diracx.routers.auth import AuthSettings
64
-
65
63
  private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
66
- pem = private_key.private_bytes(
64
+ return private_key.private_bytes(
67
65
  encoding=serialization.Encoding.PEM,
68
66
  format=serialization.PrivateFormat.PKCS8,
69
67
  encryption_algorithm=serialization.NoEncryption(),
70
68
  ).decode()
71
69
 
70
+
71
+ @pytest.fixture(scope="session")
72
+ def test_auth_settings(rsa_private_key_pem) -> AuthSettings:
73
+ from diracx.routers.auth import AuthSettings
74
+
72
75
  yield AuthSettings(
73
- token_key=pem,
76
+ token_key=rsa_private_key_pem,
74
77
  allowed_redirects=[
75
78
  "http://diracx.test.invalid:8000/api/docs/oauth2-redirect",
76
79
  ],
@@ -78,7 +81,7 @@ def test_auth_settings() -> AuthSettings:
78
81
 
79
82
 
80
83
  @pytest.fixture(scope="session")
81
- def aio_moto():
84
+ def aio_moto(worker_id):
82
85
  """Start the moto server in a separate thread and return the base URL
83
86
 
84
87
  The mocking provided by moto doesn't play nicely with aiobotocore so we use
@@ -87,6 +90,8 @@ def aio_moto():
87
90
  from moto.server import ThreadedMotoServer
88
91
 
89
92
  port = 27132
93
+ if worker_id != "master":
94
+ port += int(worker_id.replace("gw", "")) + 1
90
95
  server = ThreadedMotoServer(port=port)
91
96
  server.start()
92
97
  yield {
@@ -97,7 +102,7 @@ def aio_moto():
97
102
  server.stop()
98
103
 
99
104
 
100
- @pytest.fixture(scope="function")
105
+ @pytest.fixture(scope="session")
101
106
  def test_sandbox_settings(aio_moto) -> SandboxStoreSettings:
102
107
  from diracx.routers.job_manager.sandboxes import SandboxStoreSettings
103
108
 
@@ -108,70 +113,232 @@ def test_sandbox_settings(aio_moto) -> SandboxStoreSettings:
108
113
  )
109
114
 
110
115
 
111
- @pytest.fixture
112
- def with_app(test_auth_settings, test_sandbox_settings, with_config_repo):
113
- """
114
- Create a DiracxApp with hard coded configuration for test
115
- """
116
- from diracx.core.config import ConfigSource
117
- from diracx.routers import create_app_inner
118
-
119
- app = create_app_inner(
120
- enabled_systems={".well-known", "auth", "config", "jobs"},
121
- all_service_settings=[
122
- test_auth_settings,
123
- test_sandbox_settings,
124
- ],
125
- database_urls={
126
- "JobDB": "sqlite+aiosqlite:///:memory:",
127
- "JobLoggingDB": "sqlite+aiosqlite:///:memory:",
128
- "TaskQueueDB": "sqlite+aiosqlite:///:memory:",
129
- "AuthDB": "sqlite+aiosqlite:///:memory:",
130
- "SandboxMetadataDB": "sqlite+aiosqlite:///:memory:",
131
- },
132
- os_database_conn_kwargs={
133
- # TODO: JobParametersDB
134
- },
135
- config_source=ConfigSource.create_from_url(
136
- backend_url=f"git+file://{with_config_repo}"
137
- ),
138
- )
116
+ class UnavailableDependency:
117
+ def __init__(self, key):
118
+ self.key = key
119
+
120
+ def __call__(self):
121
+ raise NotImplementedError(
122
+ f"{self.key} has not been made available to this test!"
123
+ )
124
+
125
+
126
+ class ClientFactory:
127
+ def __init__(
128
+ self,
129
+ tmp_path_factory,
130
+ with_config_repo,
131
+ test_auth_settings,
132
+ test_sandbox_settings,
133
+ ):
134
+ from diracx.core.config import ConfigSource
135
+ from diracx.core.extensions import select_from_extension
136
+ from diracx.core.settings import ServiceSettingsBase
137
+ from diracx.db.sql.utils import BaseSQLDB
138
+ from diracx.routers import create_app_inner
139
+
140
+ enabled_systems = {
141
+ e.name for e in select_from_extension(group="diracx.services")
142
+ }
143
+ database_urls = {
144
+ e.name: "sqlite+aiosqlite:///:memory:"
145
+ for e in select_from_extension(group="diracx.db.sql")
146
+ }
147
+ self._cache_dir = tmp_path_factory.mktemp("empty-dbs")
148
+
149
+ self.test_auth_settings = test_auth_settings
150
+
151
+ self.app = create_app_inner(
152
+ enabled_systems=enabled_systems,
153
+ all_service_settings=[
154
+ test_auth_settings,
155
+ test_sandbox_settings,
156
+ ],
157
+ database_urls=database_urls,
158
+ os_database_conn_kwargs={
159
+ # TODO: JobParametersDB
160
+ },
161
+ config_source=ConfigSource.create_from_url(
162
+ backend_url=f"git+file://{with_config_repo}"
163
+ ),
164
+ )
165
+
166
+ self.all_dependency_overrides = self.app.dependency_overrides.copy()
167
+ self.app.dependency_overrides = {}
168
+ for obj in self.all_dependency_overrides:
169
+ assert issubclass(
170
+ obj.__self__, (ServiceSettingsBase, BaseSQLDB, ConfigSource)
171
+ ), obj
172
+
173
+ self.all_lifetime_functions = self.app.lifetime_functions[:]
174
+ self.app.lifetime_functions = []
175
+ for obj in self.all_lifetime_functions:
176
+ assert isinstance(
177
+ obj.__self__, (ServiceSettingsBase, BaseSQLDB, ConfigSource)
178
+ ), obj
179
+
180
+ @contextlib.contextmanager
181
+ def configure(self, enabled_dependencies):
182
+ assert (
183
+ self.app.dependency_overrides == {} and self.app.lifetime_functions == []
184
+ ), "configure cannot be nested"
185
+
186
+ for k, v in self.all_dependency_overrides.items():
187
+ class_name = k.__self__.__name__
188
+ if class_name in enabled_dependencies:
189
+ self.app.dependency_overrides[k] = v
190
+ else:
191
+ self.app.dependency_overrides[k] = UnavailableDependency(class_name)
192
+
193
+ for obj in self.all_lifetime_functions:
194
+ if obj.__self__.__class__.__name__ in enabled_dependencies:
195
+ self.app.lifetime_functions.append(obj)
196
+
197
+ # Add create_db_schemas to the end of the lifetime_functions so that the
198
+ # other lifetime_functions (i.e. those which run db.engine_context) have
199
+ # already been ran
200
+ self.app.lifetime_functions.append(self.create_db_schemas)
201
+
202
+ yield
203
+
204
+ self.app.dependency_overrides = {}
205
+ self.app.lifetime_functions = []
139
206
 
140
207
  @contextlib.asynccontextmanager
141
- async def create_db_schemas(app=app):
208
+ async def create_db_schemas(self):
142
209
  """Create DB schema's based on the DBs available in app.dependency_overrides"""
210
+ import aiosqlite
211
+ import sqlalchemy
212
+ from sqlalchemy.util.concurrency import greenlet_spawn
213
+
143
214
  from diracx.db.sql.utils import BaseSQLDB
144
215
 
145
- for k, v in app.dependency_overrides.items():
216
+ for k, v in self.app.dependency_overrides.items():
146
217
  # Ignore dependency overrides which aren't BaseSQLDB.transaction
147
- if k.__func__ != BaseSQLDB.transaction.__func__:
218
+ if (
219
+ isinstance(v, UnavailableDependency)
220
+ or k.__func__ != BaseSQLDB.transaction.__func__
221
+ ):
148
222
  continue
149
223
  # The first argument of the overridden BaseSQLDB.transaction is the DB object
150
224
  db = v.args[0]
151
225
  assert isinstance(db, BaseSQLDB), (k, db)
152
- # Fill the DB schema
153
- async with db.engine.begin() as conn:
154
- # set PRAGMA foreign_keys=ON if sqlite
155
- if db._db_url.startswith("sqlite"):
156
- await conn.exec_driver_sql("PRAGMA foreign_keys=ON")
157
- await conn.run_sync(db.metadata.create_all)
226
+
227
+ # set PRAGMA foreign_keys=ON if sqlite
228
+ if db.engine.url.drivername.startswith("sqlite"):
229
+
230
+ def set_sqlite_pragma(dbapi_connection, connection_record):
231
+ cursor = dbapi_connection.cursor()
232
+ cursor.execute("PRAGMA foreign_keys=ON")
233
+ cursor.close()
234
+
235
+ sqlalchemy.event.listen(
236
+ db.engine.sync_engine, "connect", set_sqlite_pragma
237
+ )
238
+
239
+ # We maintain a cache of the populated DBs in empty_db_dir so that
240
+ # we don't have to recreate them for every test. This speeds up the
241
+ # tests by a considerable amount.
242
+ ref_db = self._cache_dir / f"{k.__self__.__name__}.db"
243
+ if ref_db.exists():
244
+ async with aiosqlite.connect(ref_db) as ref_conn:
245
+ conn = await db.engine.raw_connection()
246
+ await ref_conn.backup(conn.driver_connection)
247
+ await greenlet_spawn(conn.close)
248
+ else:
249
+ async with db.engine.begin() as conn:
250
+ await conn.run_sync(db.metadata.create_all)
251
+
252
+ async with aiosqlite.connect(ref_db) as ref_conn:
253
+ conn = await db.engine.raw_connection()
254
+ await conn.driver_connection.backup(ref_conn)
255
+ await greenlet_spawn(conn.close)
158
256
 
159
257
  yield
160
258
 
161
- # Add create_db_schemas to the end of the lifetime_functions so that the
162
- # other lifetime_functions (i.e. those which run db.engine_context) have
163
- # already been ran
164
- app.lifetime_functions.append(create_db_schemas)
259
+ @contextlib.contextmanager
260
+ def unauthenticated(self):
261
+ from fastapi.testclient import TestClient
262
+
263
+ with TestClient(self.app) as client:
264
+ yield client
265
+
266
+ @contextlib.contextmanager
267
+ def normal_user(self):
268
+ from diracx.core.properties import NORMAL_USER
269
+ from diracx.routers.auth import create_token
270
+
271
+ with self.unauthenticated() as client:
272
+ payload = {
273
+ "sub": "testingVO:yellow-sub",
274
+ "exp": datetime.now(tz=timezone.utc)
275
+ + timedelta(self.test_auth_settings.access_token_expire_minutes),
276
+ "aud": AUDIENCE,
277
+ "iss": ISSUER,
278
+ "dirac_properties": [NORMAL_USER],
279
+ "jti": str(uuid4()),
280
+ "preferred_username": "preferred_username",
281
+ "dirac_group": "test_group",
282
+ "vo": "lhcb",
283
+ }
284
+ token = create_token(payload, self.test_auth_settings)
285
+
286
+ client.headers["Authorization"] = f"Bearer {token}"
287
+ client.dirac_token_payload = payload
288
+ yield client
289
+
290
+ @contextlib.contextmanager
291
+ def admin_user(self):
292
+ from diracx.core.properties import JOB_ADMINISTRATOR
293
+ from diracx.routers.auth import create_token
294
+
295
+ with self.unauthenticated() as client:
296
+ payload = {
297
+ "sub": "testingVO:yellow-sub",
298
+ "aud": AUDIENCE,
299
+ "iss": ISSUER,
300
+ "dirac_properties": [JOB_ADMINISTRATOR],
301
+ "jti": str(uuid4()),
302
+ "preferred_username": "preferred_username",
303
+ "dirac_group": "test_group",
304
+ "vo": "lhcb",
305
+ }
306
+ token = create_token(payload, self.test_auth_settings)
307
+ client.headers["Authorization"] = f"Bearer {token}"
308
+ client.dirac_token_payload = payload
309
+ yield client
165
310
 
166
- yield app
311
+
312
+ @pytest.fixture(scope="session")
313
+ def session_client_factory(
314
+ test_auth_settings, test_sandbox_settings, with_config_repo, tmp_path_factory
315
+ ):
316
+ """
317
+ TODO
318
+ """
319
+ yield ClientFactory(
320
+ tmp_path_factory, with_config_repo, test_auth_settings, test_sandbox_settings
321
+ )
167
322
 
168
323
 
169
324
  @pytest.fixture
170
- def with_config_repo(tmp_path):
325
+ def client_factory(session_client_factory, request):
326
+ marker = request.node.get_closest_marker("enabled_dependencies")
327
+ if marker is None:
328
+ raise RuntimeError("This test requires the enabled_dependencies marker")
329
+ (enabled_dependencies,) = marker.args
330
+ with session_client_factory.configure(enabled_dependencies=enabled_dependencies):
331
+ yield session_client_factory
332
+
333
+
334
+ @pytest.fixture(scope="session")
335
+ def with_config_repo(tmp_path_factory):
171
336
  from git import Repo
172
337
 
173
338
  from diracx.core.config import Config
174
339
 
340
+ tmp_path = tmp_path_factory.mktemp("cs-repo")
341
+
175
342
  repo = Repo.init(tmp_path, initial_branch="master")
176
343
  cs_file = tmp_path / "default.yml"
177
344
  example_cs = Config.parse_obj(
@@ -220,58 +387,6 @@ def with_config_repo(tmp_path):
220
387
  yield tmp_path
221
388
 
222
389
 
223
- @pytest.fixture
224
- def test_client(with_app):
225
- from fastapi.testclient import TestClient
226
-
227
- with TestClient(with_app) as test_client:
228
- yield test_client
229
-
230
-
231
- @pytest.fixture
232
- def normal_user_client(test_client, test_auth_settings):
233
- from diracx.core.properties import NORMAL_USER
234
- from diracx.routers.auth import create_token
235
-
236
- payload = {
237
- "sub": "testingVO:yellow-sub",
238
- "exp": datetime.now()
239
- + timedelta(test_auth_settings.access_token_expire_minutes),
240
- "aud": AUDIENCE,
241
- "iss": ISSUER,
242
- "dirac_properties": [NORMAL_USER],
243
- "jti": str(uuid4()),
244
- "preferred_username": "preferred_username",
245
- "dirac_group": "test_group",
246
- "vo": "lhcb",
247
- }
248
- token = create_token(payload, test_auth_settings)
249
- test_client.headers["Authorization"] = f"Bearer {token}"
250
- test_client.dirac_token_payload = payload
251
- yield test_client
252
-
253
-
254
- @pytest.fixture
255
- def admin_user_client(test_client, test_auth_settings):
256
- from diracx.core.properties import JOB_ADMINISTRATOR
257
- from diracx.routers.auth import create_token
258
-
259
- payload = {
260
- "sub": "testingVO:yellow-sub",
261
- "aud": AUDIENCE,
262
- "iss": ISSUER,
263
- "dirac_properties": [JOB_ADMINISTRATOR],
264
- "jti": str(uuid4()),
265
- "preferred_username": "preferred_username",
266
- "dirac_group": "test_group",
267
- "vo": "lhcb",
268
- }
269
- token = create_token(payload, test_auth_settings)
270
- test_client.headers["Authorization"] = f"Bearer {token}"
271
- test_client.dirac_token_payload = payload
272
- yield test_client
273
-
274
-
275
390
  @pytest.fixture(scope="session")
276
391
  def demo_dir(request) -> Path:
277
392
  demo_dir = request.config.getoption("--demo-dir")
@@ -436,5 +551,13 @@ def do_device_flow_with_dex(url: str, ca_path: str) -> None:
436
551
  verify=ca_path,
437
552
  )
438
553
  r.raise_for_status()
554
+ approval_url = r.url # This is not the same as URL as we redirect to dex
555
+ # Do the actual approval
556
+ r = requests.post(
557
+ approval_url,
558
+ {"approval": "approve", "req": parse_qs(urlparse(r.url).query)["req"][0]},
559
+ verify=ca_path,
560
+ )
561
+
439
562
  # This should have redirected to the DiracX page that shows the login is complete
440
563
  assert "Please close the window" in r.text
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: diracx-testing
3
- Version: 0.0.1a8
3
+ Version: 0.0.1a10
4
4
  Summary: TODO
5
5
  License: GPL-3.0-only
6
6
  Classifier: Intended Audience :: Science/Research
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
13
13
  Requires-Dist: pytest
14
14
  Requires-Dist: pytest-asyncio
15
15
  Requires-Dist: pytest-cov
16
+ Requires-Dist: pytest-xdist
16
17
  Provides-Extra: testing
17
18
  Requires-Dist: diracx-testing ; extra == 'testing'
18
19
 
@@ -0,0 +1,6 @@
1
+ diracx/testing/__init__.py,sha256=wXrihINkyIbE2dn4XyKDeQO4cp8bKPkeUIi3ssod0qs,19026
2
+ diracx/testing/osdb.py,sha256=-EFZNyEY07Zq7HdQGZxS3H808Y94aaUhmo0x-Y8xo3Q,3592
3
+ diracx_testing-0.0.1a10.dist-info/METADATA,sha256=BE-nfEHYD7kakG38j-w0mdUUPOFGjycamahyTKEABaM,615
4
+ diracx_testing-0.0.1a10.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
5
+ diracx_testing-0.0.1a10.dist-info/top_level.txt,sha256=vJx10tdRlBX3rF2Psgk5jlwVGZNcL3m_7iQWwgPXt-U,7
6
+ diracx_testing-0.0.1a10.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- diracx/testing/__init__.py,sha256=teeGQ2G3stJ7hNyxV8SyaNJytN3cxL6u8hOWub212VU,14173
2
- diracx/testing/osdb.py,sha256=-EFZNyEY07Zq7HdQGZxS3H808Y94aaUhmo0x-Y8xo3Q,3592
3
- diracx_testing-0.0.1a8.dist-info/METADATA,sha256=H6zq2hStmuebu_DZiboFGmxwy9lMILOMPsYBXE-GBi0,586
4
- diracx_testing-0.0.1a8.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
5
- diracx_testing-0.0.1a8.dist-info/top_level.txt,sha256=vJx10tdRlBX3rF2Psgk5jlwVGZNcL3m_7iQWwgPXt-U,7
6
- diracx_testing-0.0.1a8.dist-info/RECORD,,