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.
- diracx/testing/__init__.py +228 -105
- {diracx_testing-0.0.1a8.dist-info → diracx_testing-0.0.1a10.dist-info}/METADATA +2 -1
- diracx_testing-0.0.1a10.dist-info/RECORD +6 -0
- diracx_testing-0.0.1a8.dist-info/RECORD +0 -6
- {diracx_testing-0.0.1a8.dist-info → diracx_testing-0.0.1a10.dist-info}/WHEEL +0 -0
- {diracx_testing-0.0.1a8.dist-info → diracx_testing-0.0.1a10.dist-info}/top_level.txt +0 -0
diracx/testing/__init__.py
CHANGED
@@ -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
|
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
|
-
|
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=
|
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="
|
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
|
-
|
112
|
-
def
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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(
|
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
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
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
|
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.
|
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,,
|
File without changes
|
File without changes
|