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
515import os
6- import random
7- import time
816import uuid
917
10- import hawkauthlib
1118import 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+ )
1725from 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" )
9929def 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