Skip to content

Commit 102733f

Browse files
authored
test: segment away util helpers conftest (#2274)
* moved to helper modules * update conftest to remove non fixture functions * update imports * format and helper import
1 parent 4880e31 commit 102733f

9 files changed

Lines changed: 585 additions & 521 deletions

File tree

Lines changed: 22 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,29 @@
1-
"""Pytest configuration and fixtures for integration tests."""
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
3+
# You can obtain one at http://mozilla.org/MPL/2.0/.
4+
"""Pytest fixtures for storage integration tests.
5+
6+
Fixture hierarchy
7+
─────────────────
8+
st_ctx — function-scoped composite: sets up Pyramid configurator, creates a
9+
hawk-signed TestApp, seeds a random user, clears that user's data,
10+
and yields a plain dict consumed by test functions.
11+
12+
Helper functions and constants live in helpers.py.
13+
"""
214

3-
import contextlib
4-
import logging
515
import os
6-
import random
7-
import time
816
import uuid
917

10-
import hawkauthlib
1118
import pytest
12-
import webtest
13-
from pyramid.interfaces import IAuthenticationPolicy
14-
from pyramid.request import Request
15-
from webtest import TestApp
1619

20+
from tools.integration_tests.helpers import (
21+
make_auth_state,
22+
make_test_app,
23+
retry_delete,
24+
)
1725
from tools.integration_tests.test_support import get_test_configurator
1826

19-
# max number of attempts to check server heartbeat
20-
SYNC_SERVER_STARTUP_MAX_ATTEMPTS = 35
21-
SYNC_SERVER_URL = os.environ.get("SYNC_SERVER_URL", "http://localhost:8000")
22-
23-
logger = logging.getLogger("tools.integration-tests")
24-
25-
if os.environ.get("SYNC_TEST_LOG_HTTP"):
26-
_orig_do_request = webtest.TestApp.do_request
27-
28-
def _logged_do_request(self, req, *args, **kwargs):
29-
"""Wrap request and response logging around original do_request."""
30-
logger.info(">> %s %s", req.method, req.url)
31-
if req.body:
32-
logger.info(">> BODY: %s", req.body)
33-
resp = _orig_do_request(self, req, *args, **kwargs)
34-
logger.info("<< %s", resp.status)
35-
logger.info("<< BODY: %s", resp.body)
36-
return resp
37-
38-
webtest.TestApp.do_request = _logged_do_request
39-
40-
41-
def _retry_send(func, *args, **kwargs):
42-
"""Call a webtest method, retrying once on 409/503."""
43-
try:
44-
return func(*args, **kwargs)
45-
except webtest.AppError as ex:
46-
if "409 " not in ex.args[0] and "503 " not in ex.args[0]:
47-
raise
48-
time.sleep(0.01)
49-
return func(*args, **kwargs)
50-
51-
52-
def retry_post_json(app, *args, **kwargs):
53-
"""POST JSON with retry on transient errors."""
54-
return _retry_send(app.post_json, *args, **kwargs)
55-
56-
57-
def retry_put_json(app, *args, **kwargs):
58-
"""PUT JSON with retry on transient errors."""
59-
return _retry_send(app.put_json, *args, **kwargs)
60-
61-
62-
def retry_delete(app, *args, **kwargs):
63-
"""DELETE with retry on transient errors."""
64-
return _retry_send(app.delete, *args, **kwargs)
65-
66-
67-
def _make_auth_state(config, host_url):
68-
"""Generate hawk credentials for a new random user."""
69-
global_secret = os.environ.get("SYNC_MASTER_SECRET")
70-
policy = config.registry.getUtility(IAuthenticationPolicy)
71-
if global_secret is not None:
72-
policy.secrets._secrets = [global_secret]
73-
user_id = random.randint(1, 100000)
74-
fxa_uid = "DECAFBAD" + str(uuid.uuid4().hex)[8:]
75-
hashed_fxa_uid = str(uuid.uuid4().hex)
76-
fxa_kid = "0000000000000-DECAFBAD" + str(uuid.uuid4().hex)[8:]
77-
req = Request.blank(host_url)
78-
creds = policy.encode_hawk_id(
79-
req,
80-
user_id,
81-
extra={
82-
"hashed_fxa_uid": hashed_fxa_uid,
83-
"fxa_uid": fxa_uid,
84-
"fxa_kid": fxa_kid,
85-
},
86-
)
87-
auth_token, auth_secret = creds
88-
return {
89-
"user_id": user_id,
90-
"fxa_uid": fxa_uid,
91-
"hashed_fxa_uid": hashed_fxa_uid,
92-
"fxa_kid": fxa_kid,
93-
"auth_token": auth_token,
94-
"auth_secret": auth_secret,
95-
}
96-
9727

9828
@pytest.fixture(scope="function")
9929
def st_ctx():
@@ -113,49 +43,26 @@ def st_ctx():
11343
ondisk = "sqlite:////tmp/tests-sync-%s.db" % os.environ["MOZSVC_UUID"]
11444
os.environ["MOZSVC_ONDISK_SQLURI"] = ondisk
11545

116-
# Locate tests.ini relative to test_storage.py
46+
# Locate tests.ini relative to this file
11747
this_dir = os.path.dirname(os.path.abspath(__file__))
11848
config = get_test_configurator(this_dir, ini_file)
11949
config.commit()
12050
config.make_wsgi_app()
12151

12252
host_url = os.environ.get("SYNC_SERVER_URL", "http://localhost:8000")
123-
import urllib.parse as urlparse
124-
125-
host_parts = urlparse.urlparse(host_url)
126-
app = TestApp(
127-
host_url,
128-
extra_environ={
129-
"HTTP_HOST": host_parts.netloc,
130-
"wsgi.url_scheme": host_parts.scheme or "http",
131-
"SERVER_NAME": host_parts.hostname,
132-
"REMOTE_ADDR": "127.0.0.1",
133-
"SCRIPT_NAME": host_parts.path,
134-
},
135-
)
13653

137-
# Mutable auth state — shared with the do_request closure so that
138-
# switch_user() and the expired-token test can swap credentials at runtime.
139-
auth = _make_auth_state(config, host_url)
54+
auth = make_auth_state(config, host_url)
14055
auth_state = {
14156
"auth_token": auth["auth_token"],
14257
"auth_secret": auth["auth_secret"],
14358
}
14459

145-
orig_do_request = app.do_request
146-
147-
def new_do_request(req, *args, **kwds):
148-
hawkauthlib.sign_request(
149-
req, auth_state["auth_token"], auth_state["auth_secret"]
150-
)
151-
return orig_do_request(req, *args, **kwds)
152-
153-
app.do_request = new_do_request
60+
app = make_test_app(host_url, auth_state)
15461

15562
root = "/1.5/%d" % auth["user_id"]
15663
retry_delete(app, root)
15764

158-
ctx = {
65+
yield {
15966
"app": app,
16067
"root": root,
16168
"user_id": auth["user_id"],
@@ -167,56 +74,5 @@ def new_do_request(req, *args, **kwds):
16774
"host_url": host_url,
16875
}
16976

170-
yield ctx
171-
17277
config.end()
17378
del os.environ["MOZSVC_UUID"]
174-
175-
176-
@contextlib.contextmanager
177-
def switch_user(st_ctx):
178-
"""Context manager: temporarily switch to a fresh random user.
179-
180-
Updates both st_ctx and the auth_state dict (shared with the
181-
do_request closure) for the duration of the block, then restores
182-
the original user on exit.
183-
"""
184-
orig_root = st_ctx["root"]
185-
orig_user_id = st_ctx["user_id"]
186-
orig_fxa_uid = st_ctx["fxa_uid"]
187-
orig_hashed_fxa_uid = st_ctx["hashed_fxa_uid"]
188-
orig_fxa_kid = st_ctx["fxa_kid"]
189-
orig_auth_token = st_ctx["auth_state"]["auth_token"]
190-
orig_auth_secret = st_ctx["auth_state"]["auth_secret"]
191-
192-
config = st_ctx["config"]
193-
host_url = st_ctx["host_url"]
194-
app = st_ctx["app"]
195-
196-
for _ in range(10):
197-
new_auth = _make_auth_state(config, host_url)
198-
if new_auth["user_id"] != orig_user_id:
199-
break
200-
else:
201-
raise RuntimeError("Failed to switch to new user id")
202-
203-
st_ctx["auth_state"]["auth_token"] = new_auth["auth_token"]
204-
st_ctx["auth_state"]["auth_secret"] = new_auth["auth_secret"]
205-
st_ctx["user_id"] = new_auth["user_id"]
206-
st_ctx["fxa_uid"] = new_auth["fxa_uid"]
207-
st_ctx["hashed_fxa_uid"] = new_auth["hashed_fxa_uid"]
208-
st_ctx["fxa_kid"] = new_auth["fxa_kid"]
209-
new_root = "/1.5/%d" % new_auth["user_id"]
210-
st_ctx["root"] = new_root
211-
retry_delete(app, new_root)
212-
213-
try:
214-
yield
215-
finally:
216-
st_ctx["auth_state"]["auth_token"] = orig_auth_token
217-
st_ctx["auth_state"]["auth_secret"] = orig_auth_secret
218-
st_ctx["user_id"] = orig_user_id
219-
st_ctx["fxa_uid"] = orig_fxa_uid
220-
st_ctx["hashed_fxa_uid"] = orig_hashed_fxa_uid
221-
st_ctx["fxa_kid"] = orig_fxa_kid
222-
st_ctx["root"] = orig_root

0 commit comments

Comments
 (0)