@@ -112,6 +112,117 @@ def test_redirect_to_presign_url_exception(self):
112112 result = self .mixin .redirect_to_presign_url ('fileuri' , self .task , 'Task' )
113113 assert result .status_code == status .HTTP_404_NOT_FOUND
114114
115+ def test_proxy_data_from_storage_content_type_fallback_for_octet_stream (self ):
116+ """Test that proxy detects correct content type from URI when storage returns octet-stream.
117+
118+ S3 objects uploaded without explicit Content-Type often have binary/octet-stream.
119+ The proxy should detect the correct type from the URI file extension.
120+ """
121+ mock_storage = MagicMock ()
122+ mock_stream = MagicMock ()
123+ mock_metadata = {
124+ 'StatusCode' : 200 ,
125+ 'ContentLength' : 1000 ,
126+ 'LastModified' : datetime .now (),
127+ 'ETag' : '"abcdef123456"' ,
128+ }
129+ # Storage returns binary/octet-stream (common S3 default for missing Content-Type)
130+ mock_storage .get_bytes_stream .return_value = (mock_stream , 'binary/octet-stream' , mock_metadata )
131+ mock_project = MagicMock ()
132+
133+ with patch ('io_storages.proxy_api.StreamingHttpResponse' ) as mock_response_class , patch (
134+ 'io_storages.proxy_api.settings'
135+ ) as mock_settings :
136+ mock_settings .RESOLVER_PROXY_MAX_RANGE_SIZE = 1024 * 1024
137+ mock_settings .RESOLVER_PROXY_BUFFER_SIZE = 8192
138+ mock_settings .RESOLVER_PROXY_CACHE_TIMEOUT = 3600
139+ mock_settings .RESOLVER_PROXY_TIMEOUT = 20
140+ mock_settings .RESOLVER_PROXY_ENABLE_ETAG_CACHE = False
141+
142+ mock_response = MagicMock ()
143+ mock_response .headers = {}
144+ mock_response_class .return_value = mock_response
145+ self .request .headers = {}
146+
147+ # URI with .jpg extension - should be detected as image/jpeg
148+ self .mixin .proxy_data_from_storage (self .request , 's3://bucket/photo.jpg' , mock_project , mock_storage )
149+
150+ # Verify StreamingHttpResponse was called with image/jpeg, not binary/octet-stream
151+ call_args , call_kwargs = mock_response_class .call_args
152+ assert call_kwargs .get ('content_type' ) == 'image/jpeg' or (
153+ len (call_args ) > 1 and call_args [1 ] == 'image/jpeg'
154+ ), f'Expected content_type=image/jpeg, got: args={ call_args } , kwargs={ call_kwargs } '
155+
156+ def test_proxy_data_from_storage_content_type_fallback_for_application_octet_stream (self ):
157+ """Test fallback for application/octet-stream (another generic type)."""
158+ mock_storage = MagicMock ()
159+ mock_stream = MagicMock ()
160+ mock_metadata = {
161+ 'StatusCode' : 200 ,
162+ 'ContentLength' : 5000 ,
163+ 'LastModified' : datetime .now (),
164+ 'ETag' : '"xyz789"' ,
165+ }
166+ mock_storage .get_bytes_stream .return_value = (mock_stream , 'application/octet-stream' , mock_metadata )
167+ mock_project = MagicMock ()
168+
169+ with patch ('io_storages.proxy_api.StreamingHttpResponse' ) as mock_response_class , patch (
170+ 'io_storages.proxy_api.settings'
171+ ) as mock_settings :
172+ mock_settings .RESOLVER_PROXY_MAX_RANGE_SIZE = 1024 * 1024
173+ mock_settings .RESOLVER_PROXY_BUFFER_SIZE = 8192
174+ mock_settings .RESOLVER_PROXY_CACHE_TIMEOUT = 3600
175+ mock_settings .RESOLVER_PROXY_TIMEOUT = 20
176+ mock_settings .RESOLVER_PROXY_ENABLE_ETAG_CACHE = False
177+
178+ mock_response = MagicMock ()
179+ mock_response .headers = {}
180+ mock_response_class .return_value = mock_response
181+ self .request .headers = {}
182+
183+ self .mixin .proxy_data_from_storage (self .request , 's3://bucket/video.mp4' , mock_project , mock_storage )
184+
185+ call_args , call_kwargs = mock_response_class .call_args
186+ assert call_kwargs .get ('content_type' ) == 'video/mp4' or (
187+ len (call_args ) > 1 and call_args [1 ] == 'video/mp4'
188+ ), f'Expected content_type=video/mp4, got: args={ call_args } , kwargs={ call_kwargs } '
189+
190+ def test_proxy_data_from_storage_preserves_correct_content_type (self ):
191+ """When storage returns a proper content type, it should not be overridden."""
192+ mock_storage = MagicMock ()
193+ mock_stream = MagicMock ()
194+ mock_metadata = {
195+ 'StatusCode' : 200 ,
196+ 'ContentLength' : 1000 ,
197+ 'LastModified' : datetime .now (),
198+ 'ETag' : '"test"' ,
199+ }
200+ # Storage returns correct content type
201+ mock_storage .get_bytes_stream .return_value = (mock_stream , 'image/webp' , mock_metadata )
202+ mock_project = MagicMock ()
203+
204+ with patch ('io_storages.proxy_api.StreamingHttpResponse' ) as mock_response_class , patch (
205+ 'io_storages.proxy_api.settings'
206+ ) as mock_settings :
207+ mock_settings .RESOLVER_PROXY_MAX_RANGE_SIZE = 1024 * 1024
208+ mock_settings .RESOLVER_PROXY_BUFFER_SIZE = 8192
209+ mock_settings .RESOLVER_PROXY_CACHE_TIMEOUT = 3600
210+ mock_settings .RESOLVER_PROXY_TIMEOUT = 20
211+ mock_settings .RESOLVER_PROXY_ENABLE_ETAG_CACHE = False
212+
213+ mock_response = MagicMock ()
214+ mock_response .headers = {}
215+ mock_response_class .return_value = mock_response
216+ self .request .headers = {}
217+
218+ self .mixin .proxy_data_from_storage (self .request , 's3://bucket/photo.jpg' , mock_project , mock_storage )
219+
220+ # Should preserve the original image/webp, not override with image/jpeg
221+ call_args , call_kwargs = mock_response_class .call_args
222+ assert call_kwargs .get ('content_type' ) == 'image/webp' or (
223+ len (call_args ) > 1 and call_args [1 ] == 'image/webp'
224+ ), f'Expected content_type=image/webp, got: args={ call_args } , kwargs={ call_kwargs } '
225+
115226 def test_proxy_data_from_storage_success (self ):
116227 mock_storage = MagicMock ()
117228 # Ensure get_bytes_stream returns a three-tuple, metadata can be empty initially
0 commit comments