@@ -33,7 +33,14 @@ def _mock_catalog(self, principals=None):
3333 catalog ._listAllowedRolesAndUsers .return_value = principals
3434 return catalog
3535
36- def _patch_dependencies (self , service , catalog , row ):
36+ def _patch_dependencies (self , service , catalog , row , storage_conn = True ):
37+ """Patch REST service dependencies.
38+
39+ By default (``storage_conn=True``) the storage connection is used
40+ — matching the production path where ZODB holds a request-scoped
41+ connection. Set ``storage_conn=False`` to simulate the pool
42+ fallback (tests, scripts, non-ZODB contexts).
43+ """
3744 mock_conn = MagicMock ()
3845 mock_conn .execute .return_value .fetchone .return_value = row
3946 mock_pool = MagicMock ()
@@ -44,6 +51,10 @@ def _patch_dependencies(self, service, catalog, row):
4451 patch (
4552 "plone.pgthumbor.restapi.get_request_connection" , return_value = mock_conn
4653 ),
54+ patch (
55+ "plone.pgthumbor.restapi.get_storage_connection" ,
56+ return_value = mock_conn if storage_conn else None ,
57+ ),
4758 ]
4859 return patches
4960
@@ -53,7 +64,7 @@ def test_allowed_user_returns_200(self):
5364 catalog = self ._mock_catalog (["user:john" , "Authenticated" , "Anonymous" ])
5465 patches = self ._patch_dependencies (service , catalog , {"allowed" : True })
5566
56- with patches [0 ], patches [1 ] as mock_sm , patches [2 ], patches [3 ]:
67+ with patches [0 ], patches [1 ] as mock_sm , patches [2 ], patches [3 ], patches [ 4 ] :
5768 mock_sm .return_value .getUser .return_value = MagicMock ()
5869 result = service .render ()
5970
@@ -66,7 +77,7 @@ def test_denied_user_returns_401(self):
6677 catalog = self ._mock_catalog (["user:john" , "Authenticated" ])
6778 patches = self ._patch_dependencies (service , catalog , {"allowed" : False })
6879
69- with patches [0 ], patches [1 ] as mock_sm , patches [2 ], patches [3 ]:
80+ with patches [0 ], patches [1 ] as mock_sm , patches [2 ], patches [3 ], patches [ 4 ] :
7081 mock_sm .return_value .getUser .return_value = MagicMock ()
7182 result = service .render ()
7283
@@ -95,7 +106,7 @@ def test_zoid_not_in_catalog_returns_404(self):
95106 catalog = self ._mock_catalog (["user:john" , "Authenticated" ])
96107 patches = self ._patch_dependencies (service , catalog , None )
97108
98- with patches [0 ], patches [1 ] as mock_sm , patches [2 ], patches [3 ]:
109+ with patches [0 ], patches [1 ] as mock_sm , patches [2 ], patches [3 ], patches [ 4 ] :
99110 mock_sm .return_value .getUser .return_value = MagicMock ()
100111 result = service .render ()
101112
@@ -110,10 +121,68 @@ def test_db_error_returns_503(self):
110121 with (
111122 patch ("plone.pgthumbor.restapi.getToolByName" , return_value = catalog ),
112123 patch ("plone.pgthumbor.restapi.getSecurityManager" ) as mock_sm ,
113- patch ("plone.pgthumbor.restapi.get_pool" , side_effect = Exception ("DB down" )),
124+ patch (
125+ "plone.pgthumbor.restapi.get_storage_connection" ,
126+ side_effect = Exception ("DB down" ),
127+ ),
114128 ):
115129 mock_sm .return_value .getUser .return_value = MagicMock ()
116130 result = service .render ()
117131
118132 assert service .request .response .status == 503
119133 assert "error" in json .loads (result )
134+
135+ def test_prefers_storage_connection_over_pool (self ):
136+ """Storage conn is used when available; pool is not touched (regression for #8)."""
137+ service = self ._make_service ("000000000000001a" )
138+ catalog = self ._mock_catalog (["user:john" , "Authenticated" , "Anonymous" ])
139+
140+ storage_conn = MagicMock ()
141+ storage_conn .execute .return_value .fetchone .return_value = {"allowed" : True }
142+
143+ with (
144+ patch ("plone.pgthumbor.restapi.getToolByName" , return_value = catalog ),
145+ patch ("plone.pgthumbor.restapi.getSecurityManager" ) as mock_sm ,
146+ patch (
147+ "plone.pgthumbor.restapi.get_storage_connection" ,
148+ return_value = storage_conn ,
149+ ) as mock_storage ,
150+ patch ("plone.pgthumbor.restapi.get_pool" ) as mock_pool ,
151+ patch ("plone.pgthumbor.restapi.get_request_connection" ) as mock_req_conn ,
152+ ):
153+ mock_sm .return_value .getUser .return_value = MagicMock ()
154+ service .render ()
155+
156+ mock_storage .assert_called_once_with (service .context )
157+ # Pool fallback must not be entered when storage conn is available.
158+ mock_pool .assert_not_called ()
159+ mock_req_conn .assert_not_called ()
160+ storage_conn .execute .assert_called_once ()
161+
162+ def test_falls_back_to_pool_when_no_storage_connection (self ):
163+ """When storage conn is unavailable (tests, scripts), fall back to pool."""
164+ service = self ._make_service ("000000000000001a" )
165+ catalog = self ._mock_catalog (["user:john" , "Authenticated" , "Anonymous" ])
166+
167+ pool_conn = MagicMock ()
168+ pool_conn .execute .return_value .fetchone .return_value = {"allowed" : True }
169+
170+ with (
171+ patch ("plone.pgthumbor.restapi.getToolByName" , return_value = catalog ),
172+ patch ("plone.pgthumbor.restapi.getSecurityManager" ) as mock_sm ,
173+ patch (
174+ "plone.pgthumbor.restapi.get_storage_connection" ,
175+ return_value = None ,
176+ ),
177+ patch ("plone.pgthumbor.restapi.get_pool" ) as mock_pool ,
178+ patch (
179+ "plone.pgthumbor.restapi.get_request_connection" ,
180+ return_value = pool_conn ,
181+ ) as mock_req_conn ,
182+ ):
183+ mock_sm .return_value .getUser .return_value = MagicMock ()
184+ service .render ()
185+
186+ mock_pool .assert_called_once_with (service .context )
187+ mock_req_conn .assert_called_once ()
188+ pool_conn .execute .assert_called_once ()
0 commit comments