Skip to content

Commit 98fc7ad

Browse files
committed
core integration test conftest
1 parent 030288e commit 98fc7ad

1 file changed

Lines changed: 202 additions & 0 deletions

File tree

tools/integration_tests/conftest.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,205 @@ def _logged_do_request(self, req, *args, **kwargs):
2525
return resp
2626

2727
webtest.TestApp.do_request = _logged_do_request
28+
29+
30+
import contextlib
31+
import random
32+
import time
33+
import uuid
34+
35+
import hawkauthlib
36+
import pytest
37+
import webtest
38+
from pyramid.authorization import ACLAuthorizationPolicy
39+
from pyramid.interfaces import IAuthenticationPolicy
40+
from pyramid.request import Request
41+
from webtest import TestApp
42+
43+
from tools.integration_tests.test_support import get_test_configurator
44+
45+
46+
def _retry_send(func, *args, **kwargs):
47+
"""Call a webtest method, retrying once on 409/503."""
48+
try:
49+
return func(*args, **kwargs)
50+
except webtest.AppError as ex:
51+
if "409 " not in ex.args[0] and "503 " not in ex.args[0]:
52+
raise
53+
time.sleep(0.01)
54+
return func(*args, **kwargs)
55+
56+
57+
def retry_post_json(app, *args, **kwargs):
58+
"""POST JSON with retry on transient errors."""
59+
return _retry_send(app.post_json, *args, **kwargs)
60+
61+
62+
def retry_put_json(app, *args, **kwargs):
63+
"""PUT JSON with retry on transient errors."""
64+
return _retry_send(app.put_json, *args, **kwargs)
65+
66+
67+
def retry_delete(app, *args, **kwargs):
68+
"""DELETE with retry on transient errors."""
69+
return _retry_send(app.delete, *args, **kwargs)
70+
71+
72+
def _make_auth_state(config, host_url):
73+
"""Generate hawk credentials for a new random user."""
74+
global_secret = os.environ.get("SYNC_MASTER_SECRET")
75+
policy = config.registry.getUtility(IAuthenticationPolicy)
76+
if global_secret is not None:
77+
policy.secrets._secrets = [global_secret]
78+
user_id = random.randint(1, 100000)
79+
fxa_uid = "DECAFBAD" + str(uuid.uuid4().hex)[8:]
80+
hashed_fxa_uid = str(uuid.uuid4().hex)
81+
fxa_kid = "0000000000000-DECAFBAD" + str(uuid.uuid4().hex)[8:]
82+
req = Request.blank(host_url)
83+
creds = policy.encode_hawk_id(
84+
req,
85+
user_id,
86+
extra={
87+
"hashed_fxa_uid": hashed_fxa_uid,
88+
"fxa_uid": fxa_uid,
89+
"fxa_kid": fxa_kid,
90+
},
91+
)
92+
auth_token, auth_secret = creds
93+
return {
94+
"user_id": user_id,
95+
"fxa_uid": fxa_uid,
96+
"hashed_fxa_uid": hashed_fxa_uid,
97+
"fxa_kid": fxa_kid,
98+
"auth_token": auth_token,
99+
"auth_secret": auth_secret,
100+
}
101+
102+
103+
@pytest.fixture(scope="function")
104+
def st_ctx():
105+
"""Functional test context for storage API tests.
106+
107+
Sets up a Pyramid configurator, creates a TestApp with hawk signing,
108+
authenticates a random user, clears that user's data, and yields a
109+
context dict. Tears down configurator on exit.
110+
"""
111+
ini_file = os.environ.get("MOZSVC_TEST_INI_FILE", "tests.ini")
112+
os.environ["MOZSVC_UUID"] = str(uuid.uuid4())
113+
if "MOZSVC_SQLURI" not in os.environ:
114+
os.environ["MOZSVC_SQLURI"] = "sqlite:///:memory:"
115+
if "MOZSVC_ONDISK_SQLURI" not in os.environ:
116+
ondisk = os.environ["MOZSVC_SQLURI"]
117+
if ":memory:" in ondisk:
118+
ondisk = "sqlite:////tmp/tests-sync-%s.db" % os.environ["MOZSVC_UUID"]
119+
os.environ["MOZSVC_ONDISK_SQLURI"] = ondisk
120+
121+
# Locate tests.ini relative to test_storage.py
122+
this_dir = os.path.dirname(os.path.abspath(__file__))
123+
config = get_test_configurator(this_dir, ini_file)
124+
authz_policy = ACLAuthorizationPolicy()
125+
config.set_authorization_policy(authz_policy)
126+
config.commit()
127+
config.make_wsgi_app()
128+
129+
host_url = os.environ.get("SYNC_SERVER_URL", "http://localhost:8000")
130+
import urllib.parse as urlparse
131+
132+
host_parts = urlparse.urlparse(host_url)
133+
app = TestApp(
134+
host_url,
135+
extra_environ={
136+
"HTTP_HOST": host_parts.netloc,
137+
"wsgi.url_scheme": host_parts.scheme or "http",
138+
"SERVER_NAME": host_parts.hostname,
139+
"REMOTE_ADDR": "127.0.0.1",
140+
"SCRIPT_NAME": host_parts.path,
141+
},
142+
)
143+
144+
# Mutable auth state — shared with the do_request closure so that
145+
# switch_user() and the expired-token test can swap credentials at runtime.
146+
auth = _make_auth_state(config, host_url)
147+
auth_state = {
148+
"auth_token": auth["auth_token"],
149+
"auth_secret": auth["auth_secret"],
150+
}
151+
152+
orig_do_request = app.do_request
153+
154+
def new_do_request(req, *args, **kwds):
155+
hawkauthlib.sign_request(
156+
req, auth_state["auth_token"], auth_state["auth_secret"]
157+
)
158+
return orig_do_request(req, *args, **kwds)
159+
160+
app.do_request = new_do_request
161+
162+
root = "/1.5/%d" % auth["user_id"]
163+
retry_delete(app, root)
164+
165+
ctx = {
166+
"app": app,
167+
"root": root,
168+
"user_id": auth["user_id"],
169+
"fxa_uid": auth["fxa_uid"],
170+
"hashed_fxa_uid": auth["hashed_fxa_uid"],
171+
"fxa_kid": auth["fxa_kid"],
172+
"auth_state": auth_state,
173+
"config": config,
174+
"host_url": host_url,
175+
}
176+
177+
yield ctx
178+
179+
config.end()
180+
del os.environ["MOZSVC_UUID"]
181+
182+
183+
@contextlib.contextmanager
184+
def switch_user(st_ctx):
185+
"""Context manager: temporarily switch to a fresh random user.
186+
187+
Updates both st_ctx and the auth_state dict (shared with the
188+
do_request closure) for the duration of the block, then restores
189+
the original user on exit.
190+
"""
191+
orig_root = st_ctx["root"]
192+
orig_user_id = st_ctx["user_id"]
193+
orig_fxa_uid = st_ctx["fxa_uid"]
194+
orig_hashed_fxa_uid = st_ctx["hashed_fxa_uid"]
195+
orig_fxa_kid = st_ctx["fxa_kid"]
196+
orig_auth_token = st_ctx["auth_state"]["auth_token"]
197+
orig_auth_secret = st_ctx["auth_state"]["auth_secret"]
198+
199+
config = st_ctx["config"]
200+
host_url = st_ctx["host_url"]
201+
app = st_ctx["app"]
202+
203+
for _ in range(10):
204+
new_auth = _make_auth_state(config, host_url)
205+
if new_auth["user_id"] != orig_user_id:
206+
break
207+
else:
208+
raise RuntimeError("Failed to switch to new user id")
209+
210+
st_ctx["auth_state"]["auth_token"] = new_auth["auth_token"]
211+
st_ctx["auth_state"]["auth_secret"] = new_auth["auth_secret"]
212+
st_ctx["user_id"] = new_auth["user_id"]
213+
st_ctx["fxa_uid"] = new_auth["fxa_uid"]
214+
st_ctx["hashed_fxa_uid"] = new_auth["hashed_fxa_uid"]
215+
st_ctx["fxa_kid"] = new_auth["fxa_kid"]
216+
new_root = "/1.5/%d" % new_auth["user_id"]
217+
st_ctx["root"] = new_root
218+
retry_delete(app, new_root)
219+
220+
try:
221+
yield
222+
finally:
223+
st_ctx["auth_state"]["auth_token"] = orig_auth_token
224+
st_ctx["auth_state"]["auth_secret"] = orig_auth_secret
225+
st_ctx["user_id"] = orig_user_id
226+
st_ctx["fxa_uid"] = orig_fxa_uid
227+
st_ctx["hashed_fxa_uid"] = orig_hashed_fxa_uid
228+
st_ctx["fxa_kid"] = orig_fxa_kid
229+
st_ctx["root"] = orig_root

0 commit comments

Comments
 (0)