Skip to content

Commit d2f567b

Browse files
J3utterJ3utter
andauthored
Set cors headers when using fixtures as mocks (#545)
Co-authored-by: J3utter <j3utter@gmail.com>
1 parent 45d4cda commit d2f567b

File tree

2 files changed

+61
-2
lines changed

2 files changed

+61
-2
lines changed

stoobly_agent/app/proxy/mock/eval_fixtures_service.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ def eval_fixtures(request: 'MitmproxyRequest', **options: MockOptions) -> Union[
2525
from requests.structures import CaseInsensitiveDict
2626

2727
fixture_path = request.headers.get(MOCK_FIXTURE_PATH)
28-
headers = CaseInsensitiveDict()
28+
# Default CORS-style headers for all mock fixture responses
29+
headers = CaseInsensitiveDict({
30+
'Access-Control-Allow-Origin': '*',
31+
'Access-Control-Allow-Methods': 'GET, OPTIONS, POST, PATCH, PUT, DELETE',
32+
'Access-Control-Allow-Headers': '*',
33+
})
2934
status_code = 200
3035

3136
if fixture_path:
@@ -95,8 +100,12 @@ def eval_fixtures(request: 'MitmproxyRequest', **options: MockOptions) -> Union[
95100
if not os.path.isfile(fixture_path):
96101
return
97102

103+
# If fixture specifies headers, merge them into the defaults.
104+
# Fixture-provided values override the defaults on conflict.
98105
_headers = fixture.get('headers')
99-
headers = CaseInsensitiveDict(_headers if isinstance(_headers, dict) else {})
106+
if isinstance(_headers, dict):
107+
for k, v in _headers.items():
108+
headers[k] = v
100109

101110
if fixture.get('status_code'):
102111
status_code = fixture.get('status_code')

stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,48 @@ def test_it_sets_response(
151151
def test_it_sets_headers(self, fixtures_response: requests.Response):
152152
assert fixtures_response.headers['test'] == '1'
153153
assert fixtures_response.headers['Content-Type'] == 'text/html'
154+
# Default CORS headers should always be present
155+
assert fixtures_response.headers['Access-Control-Allow-Origin'] == '*'
156+
assert fixtures_response.headers['Access-Control-Allow-Methods'] == 'GET, OPTIONS, POST, PATCH, PUT, DELETE'
157+
assert fixtures_response.headers['Access-Control-Allow-Headers'] == '*'
158+
159+
def test_fixture_headers_merge_with_cors_defaults(
160+
self,
161+
mitmproxy_request: MitmproxyRequest,
162+
not_found_file_path: str,
163+
):
164+
"""Fixture headers should override defaults but preserve CORS keys."""
165+
tmp_dir_path = DataDir.instance().tmp_dir_path
166+
fixtures_file = os.path.join(tmp_dir_path, 'override_cors_headers.yml')
167+
168+
override_fixtures: Fixtures = {
169+
'POST': {
170+
'/404.html': {
171+
'headers': {
172+
'Access-Control-Allow-Origin': 'https://example.com',
173+
'X-Custom-Header': 'abc',
174+
},
175+
'path': not_found_file_path,
176+
'status_code': 200,
177+
},
178+
},
179+
}
180+
181+
with open(fixtures_file, 'w') as f:
182+
yaml.dump(override_fixtures, f, sort_keys=False)
183+
184+
res: requests.Response = eval_fixtures(
185+
mitmproxy_request, response_fixtures_path=fixtures_file
186+
)
187+
188+
assert res is not None
189+
# Overridden origin
190+
assert res.headers['Access-Control-Allow-Origin'] == 'https://example.com'
191+
# Defaults preserved
192+
assert res.headers['Access-Control-Allow-Methods'] == 'GET, OPTIONS, POST, PATCH, PUT, DELETE'
193+
assert res.headers['Access-Control-Allow-Headers'] == '*'
194+
# Custom header merged
195+
assert res.headers['X-Custom-Header'] == 'abc'
154196

155197
def test_it_sets_status_code(self, fixtures_response: requests.Response):
156198
assert fixtures_response.status_code == 404
@@ -683,6 +725,14 @@ def test_custom_fixture_path_header(self, mitmproxy_request: MitmproxyRequest, t
683725
assert res is not None
684726
assert res.raw.read() == test_file_contents
685727

728+
def test_custom_fixture_path_header_sets_cors(self, mitmproxy_request: MitmproxyRequest):
729+
"""CORS defaults should be applied even when using custom fixture path header."""
730+
res = eval_fixtures(mitmproxy_request)
731+
assert res is not None
732+
assert res.headers['Access-Control-Allow-Origin'] == '*'
733+
assert res.headers['Access-Control-Allow-Methods'] == 'GET, OPTIONS, POST, PATCH, PUT, DELETE'
734+
assert res.headers['Access-Control-Allow-Headers'] == '*'
735+
686736
def test_custom_fixture_path_header_nonexistent_file(self, mitmproxy_request: MitmproxyRequest):
687737
"""Test custom fixture path header pointing to nonexistent file."""
688738
from stoobly_agent.config.constants.custom_headers import MOCK_FIXTURE_PATH

0 commit comments

Comments
 (0)