Skip to content

Commit bff8f08

Browse files
SNOW-2211783: detect in tests s3 redirects throwing 403 (#2428)
1 parent 0ee0b08 commit bff8f08

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed

test/unit/test_s3_redirect_403.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import pathlib
2+
from unittest.mock import MagicMock
3+
4+
import pytest
5+
6+
try:
7+
from snowflake.connector import SnowflakeConnection
8+
from snowflake.connector.file_transfer_agent import (
9+
SnowflakeFileMeta,
10+
StorageCredential,
11+
)
12+
from snowflake.connector.s3_storage_client import SnowflakeS3RestClient
13+
except ImportError: # pragma: no cover
14+
pytest.skip("Snowflake connector not available", allow_module_level=True)
15+
16+
17+
MEGABYTE = 1024 * 1024
18+
19+
20+
@pytest.fixture(scope="session")
21+
def wiremock_password_auth_dir(wiremock_auth_dir) -> pathlib.Path:
22+
return wiremock_auth_dir / "password"
23+
24+
25+
@pytest.mark.parametrize("object_name", ["MANIFEST.yml"])
26+
def test_s3_redirect_do_not_raise(
27+
wiremock_client,
28+
wiremock_generic_mappings_dir,
29+
wiremock_password_auth_dir,
30+
object_name,
31+
):
32+
"""Reproduce the 307→403 pattern that causes PUT to fail.
33+
34+
The Wiremock server acts as a forward proxy. We stub two responses:
35+
1. Initial HEAD request returns **307 Temporary Redirect** with a Location
36+
pointing to a new path and the x-amz-bucket-region header.
37+
2. Follow-up HEAD request on the redirected URL returns **403 Forbidden**.
38+
39+
The SnowflakeS3RestClient should surface this as an HTTPError coming from
40+
`requests` (raised by ``response.raise_for_status()`` inside
41+
``get_file_header``).
42+
"""
43+
44+
wiremock_client.import_mapping(wiremock_password_auth_dir / "successful_flow.json")
45+
wiremock_client.add_mapping(wiremock_generic_mappings_dir / "telemetry.json")
46+
wiremock_client.add_mapping(
47+
wiremock_generic_mappings_dir / "s3_head_redirect_307.json"
48+
)
49+
wiremock_client.add_mapping(
50+
wiremock_generic_mappings_dir / "s3_head_forbidden_403.json"
51+
)
52+
wiremock_client.add_mapping(
53+
wiremock_generic_mappings_dir / "s3_accelerate_successful.json"
54+
)
55+
56+
# Minimal file-meta & credentials setup for the storage client
57+
creds_dict = {"AWS_SECRET_KEY": "dummy", "AWS_KEY_ID": "dummy", "AWS_TOKEN": ""}
58+
59+
meta_info = {
60+
"name": object_name,
61+
"src_file_name": "/tmp/nonexistent", # Not used in HEAD path
62+
"stage_location_type": "S3",
63+
}
64+
meta = SnowflakeFileMeta(**meta_info)
65+
66+
# Build the storage client. We purposefully override its endpoint so that
67+
# it points at Wiremock instead of a real S3 bucket.
68+
stage_info = {
69+
"locationType": "AWS",
70+
"location": "bucket/path/", # yields "/path/<object_name>" in the URL
71+
"creds": creds_dict,
72+
"region": "us-west-2",
73+
"endPoint": None,
74+
}
75+
76+
client = SnowflakeS3RestClient(
77+
meta,
78+
StorageCredential(creds_dict, MagicMock(spec=SnowflakeConnection), "PUT"),
79+
stage_info,
80+
8 * MEGABYTE,
81+
# use_s3_regional_url=True,
82+
)
83+
84+
# Direct all calls to Wiremock (HTTP for simplicity)
85+
client.endpoint = wiremock_client.http_host_with_port
86+
87+
# The first HEAD will be redirected, the second will receive 403 → HTTPError
88+
# with pytest.raises(requests.exceptions.HTTPError):
89+
# client.get_file_header(object_name)
90+
file_header = client.get_file_header(object_name)
91+
assert file_header is None
92+
93+
94+
# tODO: uzyj cursor.execute do puta
95+
# TODO: upewnij sie ze nie mam zlego podejscia do wiremocka i nie powinienm ustawiac proxy
96+
97+
98+
@pytest.mark.parametrize("object_name", ["MANIFEST.yml"])
99+
def test_put_via_cursor_handles_307_403(
100+
wiremock_client,
101+
wiremock_generic_mappings_dir,
102+
wiremock_password_auth_dir,
103+
wiremock_queries_dir,
104+
conn_cnx_wiremock,
105+
tmp_path,
106+
object_name,
107+
):
108+
"""Execute a real `PUT` through the connection and ensure the driver copes with
109+
307 redirect followed by 403 on HEAD requests to S3 (it should raise
110+
OperationalError 253003).
111+
"""
112+
113+
# --- Prepare WireMock mappings ------------------------------------------------
114+
wiremock_client.import_mapping(wiremock_password_auth_dir / "successful_flow.json")
115+
wiremock_client.add_mapping(wiremock_generic_mappings_dir / "telemetry.json")
116+
wiremock_client.add_mapping(
117+
wiremock_generic_mappings_dir / "s3_head_redirect_307.json"
118+
)
119+
wiremock_client.add_mapping(
120+
wiremock_generic_mappings_dir / "s3_head_forbidden_403.json"
121+
)
122+
wiremock_client.add_mapping(
123+
wiremock_generic_mappings_dir / "s3_accelerate_successful.json"
124+
)
125+
wiremock_client.add_mapping(
126+
wiremock_generic_mappings_dir / "snowflake_disconnect_successful.json"
127+
)
128+
# Mapping that makes Snowflake respond successfully to the PUT command
129+
130+
# -----------------------------------------------------------------------------
131+
test_file = tmp_path / object_name
132+
test_file.write_text("dummy data")
133+
wiremock_client.add_mapping(
134+
wiremock_queries_dir / "put_file_successful.json",
135+
placeholders={"{{SRC_FILE}}": test_file.as_uri()},
136+
)
137+
138+
conn = conn_cnx_wiremock()
139+
140+
with conn as cxn:
141+
cur = cxn.cursor()
142+
# No need to create stage; use @~ (user stage)
143+
put_sql = f"PUT file://{test_file} @~/{object_name} AUTO_COMPRESS=FALSE"
144+
# with pytest.raises(requests.exceptions.HTTPError):
145+
# cur.execute(put_sql)
146+
147+
cur.execute(put_sql)

0 commit comments

Comments
 (0)