1+ /*
2+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License").
5+ * You may not use this file except in compliance with the License.
6+ * A copy of the License is located at
7+ *
8+ * http://aws.amazon.com/apache2.0
9+ *
10+ * or in the "license" file accompanying this file. This file is distributed
11+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+ * express or implied. See the License for the specific language governing
13+ * permissions and limitations under the License.
14+ */
15+
16+ package software .amazon .awssdk .services .s3 .internal .presignedurl ;
17+
18+ import static org .assertj .core .api .Assertions .assertThat ;
19+ import static org .assertj .core .api .Assertions .assertThatThrownBy ;
20+ import static org .mockito .ArgumentMatchers .any ;
21+ import static org .mockito .Mockito .mock ;
22+ import static org .mockito .Mockito .when ;
23+ import java .net .URL ;
24+ import org .junit .jupiter .api .BeforeEach ;
25+ import org .junit .jupiter .api .Test ;
26+ import org .junit .jupiter .params .ParameterizedTest ;
27+ import org .junit .jupiter .params .provider .NullAndEmptySource ;
28+ import org .junit .jupiter .params .provider .ValueSource ;
29+ import software .amazon .awssdk .core .exception .SdkClientException ;
30+ import software .amazon .awssdk .http .SdkHttpFullRequest ;
31+ import software .amazon .awssdk .http .SdkHttpMethod ;
32+ import software .amazon .awssdk .protocols .core .OperationInfo ;
33+ import software .amazon .awssdk .protocols .core .ProtocolMarshaller ;
34+ import software .amazon .awssdk .protocols .xml .AwsXmlProtocolFactory ;
35+ import software .amazon .awssdk .services .s3 .internal .presignedurl .model .PresignedUrlGetObjectRequestWrapper ;
36+
37+ class PresignedUrlGetObjectRequestMarshallerTest {
38+
39+ private PresignedUrlGetObjectRequestMarshaller marshaller ;
40+ private AwsXmlProtocolFactory mockProtocolFactory ;
41+ private ProtocolMarshaller <SdkHttpFullRequest > mockProtocolMarshaller ;
42+ private URL testUrl ;
43+
44+ @ BeforeEach
45+ void setUp () throws Exception {
46+ mockProtocolFactory = mock (AwsXmlProtocolFactory .class );
47+ mockProtocolMarshaller = mock (ProtocolMarshaller .class );
48+ when (mockProtocolFactory .createProtocolMarshaller (any (OperationInfo .class )))
49+ .thenReturn (mockProtocolMarshaller );
50+ marshaller = new PresignedUrlGetObjectRequestMarshaller (mockProtocolFactory );
51+
52+ testUrl = new URL ("https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" +
53+ "X-Amz-Date=20231215T000000Z&" +
54+ "X-Amz-Signature=example-signature&" +
55+ "X-Amz-Algorithm=AWS4-HMAC-SHA256&" +
56+ "X-Amz-SignedHeaders=host&" +
57+ "X-Amz-Security-Token=xxx&" +
58+ "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" +
59+ "X-Amz-Expires=3600" );
60+ }
61+
62+ @ Test
63+ void marshall_withBasicRequest_shouldCreateCorrectHttpRequest () throws Exception {
64+ // Setup the mock marshaller to return a properly configured request
65+ SdkHttpFullRequest baseRequest = SdkHttpFullRequest .builder ()
66+ .method (SdkHttpMethod .GET )
67+ .protocol ("https" )
68+ .host ("example.com" )
69+ .build ();
70+ when (mockProtocolMarshaller .marshall (any (PresignedUrlGetObjectRequestWrapper .class )))
71+ .thenReturn (baseRequest );
72+
73+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper .builder ()
74+ .url (testUrl )
75+ .build ();
76+ SdkHttpFullRequest result = marshaller .marshall (request );
77+
78+ // Verify HTTP method and URI components
79+ assertThat (result .method ()).isEqualTo (SdkHttpMethod .GET );
80+ assertThat (result .getUri ())
81+ .satisfies (uri -> {
82+ assertThat (uri .getScheme ()).isEqualTo ("https" );
83+ assertThat (uri .getHost ()).isEqualTo ("test-bucket.s3.us-east-1.amazonaws.com" );
84+ assertThat (uri .getPath ()).isEqualTo ("/test-key" );
85+ });
86+
87+ // Verify query parameters are preserved
88+ assertThat (result .getUri ().getQuery ())
89+ .contains ("X-Amz-Date=20231215T000000Z" )
90+ .contains ("X-Amz-Signature=example-signature" )
91+ .contains ("X-Amz-Algorithm=AWS4-HMAC-SHA256" )
92+ .contains ("X-Amz-SignedHeaders=host" )
93+ .contains ("X-Amz-Security-Token=xxx" )
94+ .contains ("X-Amz-Credential=EXAMPLE12345678901234" )
95+ .contains ("X-Amz-Expires=3600" );
96+
97+ assertThat (result .headers ()).doesNotContainKey ("Range" );
98+ }
99+
100+ @ ParameterizedTest
101+ @ ValueSource (strings = {
102+ "bytes=0-100" , // First 101 bytes
103+ "bytes=100-" , // From byte 100 to end
104+ "bytes=-100" , // Last 100 bytes
105+ "bytes=0-0" , // Single byte
106+ "bytes=100-200" // Specific range
107+ })
108+ void marshall_withValidRangeFormats_shouldAddRangeHeader (String rangeValue ) throws Exception {
109+ // Setup the mock marshaller to return a request with the Range header already set
110+ SdkHttpFullRequest baseRequest = SdkHttpFullRequest .builder ()
111+ .method (SdkHttpMethod .GET )
112+ .protocol ("https" )
113+ .host ("example.com" )
114+ .putHeader ("Range" , rangeValue ) // Add the Range header to the mock response
115+ .build ();
116+
117+ when (mockProtocolMarshaller .marshall (any (PresignedUrlGetObjectRequestWrapper .class )))
118+ .thenReturn (baseRequest );
119+
120+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper .builder ()
121+ .url (testUrl )
122+ .range (rangeValue )
123+ .build ();
124+
125+ SdkHttpFullRequest result = marshaller .marshall (request );
126+
127+ // Verify the Range header is preserved
128+ assertThat (result .headers ())
129+ .containsKey ("Range" )
130+ .satisfies (headers -> assertThat (headers .get ("Range" )).contains (rangeValue ));
131+ }
132+
133+ @ ParameterizedTest
134+ @ NullAndEmptySource
135+ void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader (String rangeValue ) throws Exception {
136+ // Setup the mock marshaller to return a properly configured request
137+ SdkHttpFullRequest baseRequest = SdkHttpFullRequest .builder ()
138+ .method (SdkHttpMethod .GET )
139+ .protocol ("https" )
140+ .host ("example.com" )
141+ .build ();
142+ when (mockProtocolMarshaller .marshall (any (PresignedUrlGetObjectRequestWrapper .class )))
143+ .thenReturn (baseRequest );
144+
145+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper .builder ()
146+ .url (testUrl )
147+ .range (rangeValue )
148+ .build ();
149+ SdkHttpFullRequest result = marshaller .marshall (request );
150+
151+ assertThat (result .headers ()).doesNotContainKey ("Range" );
152+ }
153+
154+ @ Test
155+ void marshall_withNullRequest_shouldThrowException () {
156+ assertThatThrownBy (() -> marshaller .marshall (null ))
157+ .isInstanceOf (NullPointerException .class )
158+ .hasMessageContaining ("presignedUrlGetObjectRequestWrapper must not be null" );
159+ }
160+
161+ @ Test
162+ void marshall_withMalformedUrl_shouldThrowSdkClientException () throws Exception {
163+ // Setup the mock marshaller to return a properly configured request
164+ SdkHttpFullRequest baseRequest = SdkHttpFullRequest .builder ()
165+ .method (SdkHttpMethod .GET )
166+ .protocol ("https" )
167+ .host ("example.com" )
168+ .build ();
169+ when (mockProtocolMarshaller .marshall (any (PresignedUrlGetObjectRequestWrapper .class )))
170+ .thenReturn (baseRequest );
171+
172+ URL malformedUrl = new URL ("https" , "test-bucket.s3.us-east-1.amazonaws.com" , -1 , "/test key with spaces" );
173+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper .builder ()
174+ .url (malformedUrl )
175+ .build ();
176+
177+ assertThatThrownBy (() -> marshaller .marshall (request ))
178+ .isInstanceOf (SdkClientException .class )
179+ .hasMessageContaining ("Unable to marshall pre-signed URL Request" );
180+ }
181+ }
0 commit comments