/* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package retrofit2; import java.io.IOException; import java.lang.reflect.Method; import java.math.BigInteger; import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.ResponseBody; import okio.Buffer; import org.junit.Ignore; import org.junit.Test; import retrofit2.helpers.NullObjectConverterFactory; import retrofit2.helpers.ToStringConverterFactory; import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.Field; import retrofit2.http.FieldMap; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.HEAD; import retrofit2.http.HTTP; import retrofit2.http.Header; import retrofit2.http.HeaderMap; import retrofit2.http.Headers; import retrofit2.http.Multipart; import retrofit2.http.OPTIONS; import retrofit2.http.PATCH; import retrofit2.http.POST; import retrofit2.http.PUT; import retrofit2.http.Part; import retrofit2.http.PartMap; import retrofit2.http.Path; import retrofit2.http.Query; import retrofit2.http.QueryMap; import retrofit2.http.QueryName; import retrofit2.http.Url; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; @SuppressWarnings({"UnusedParameters", "unused"}) // Parameters inspected reflectively. public final class RequestBuilderTest { private static final MediaType TEXT_PLAIN = MediaType.parse("text/plain"); @Test public void customMethodNoBody() { class Example { @HTTP(method = "CUSTOM1", path = "/foo") Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("CUSTOM1"); assertThat(request.url().toString()).isEqualTo("http://example.com/foo"); assertThat(request.body()).isNull(); } @Ignore("https://github.com/square/okhttp/issues/229") @Test public void customMethodWithBody() { class Example { @HTTP(method = "CUSTOM2", path = "/foo", hasBody = true) Call<ResponseBody> method(@Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "hi"); Request request = buildRequest(Example.class, body); assertThat(request.method()).isEqualTo("CUSTOM2"); assertThat(request.url().toString()).isEqualTo("http://example.com/foo"); assertBody(request.body(), "hi"); } @Test public void onlyOneEncodingIsAllowedMultipartFirst() { class Example { @Multipart // @FormUrlEncoded // @POST("/") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Only one encoding annotation is allowed.\n for method Example.method"); } } @Test public void onlyOneEncodingIsAllowedFormEncodingFirst() { class Example { @FormUrlEncoded // @Multipart // @POST("/") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Only one encoding annotation is allowed.\n for method Example.method"); } } @Test public void invalidPathParam() throws Exception { class Example { @GET("/") // Call<ResponseBody> method(@Path("hey!") String thing) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Path parameter name must match \\{([a-zA-Z][a-zA-Z0-9_-]*)\\}." + " Found: hey! (parameter #1)\n for method Example.method"); } } @Test public void pathParamNotAllowedInQuery() throws Exception { class Example { @GET("/foo?bar={bar}") // Call<ResponseBody> method(@Path("bar") String thing) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "URL query string \"bar={bar}\" must not have replace block." + " For dynamic query parameters use @Query.\n for method Example.method"); } } @Test public void multipleParameterAnnotationsNotAllowed() throws Exception { class Example { @GET("/") // Call<ResponseBody> method(@Body @Query("nope") String o) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Multiple Retrofit annotations found, only one allowed. (parameter #1)\n for method Example.method"); } } @interface NonNull {} @Test public void multipleParameterAnnotationsOnlyOneRetrofitAllowed() throws Exception { class Example { @GET("/") // Call<ResponseBody> method(@Query("maybe") @NonNull Object o) { return null; } } Request request = buildRequest(Example.class, "yep"); assertThat(request.url().toString()).isEqualTo("http://example.com/?maybe=yep"); } @Test public void twoMethodsFail() { class Example { @PATCH("/foo") // @POST("/foo") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e.getMessage()) .isIn("Only one HTTP method is allowed. Found: PATCH and POST.\n for method Example.method", "Only one HTTP method is allowed. Found: POST and PATCH.\n for method Example.method"); } } @Test public void lackingMethod() { class Example { Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "HTTP method annotation is required (e.g., @GET, @POST, etc.).\n for method Example.method"); } } @Test public void implicitMultipartForbidden() { class Example { @POST("/") // Call<ResponseBody> method(@Part("a") int a) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Part parameters can only be used with multipart encoding. (parameter #1)\n for method Example.method"); } } @Test public void implicitMultipartWithPartMapForbidden() { class Example { @POST("/") // Call<ResponseBody> method(@PartMap Map<String, String> params) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@PartMap parameters can only be used with multipart encoding. (parameter #1)\n for method Example.method"); } } @Test public void multipartFailsOnNonBodyMethod() { class Example { @Multipart // @GET("/") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Multipart can only be specified on HTTP methods with request body (e.g., @POST).\n for method Example.method"); } } @Test public void multipartFailsWithNoParts() { class Example { @Multipart // @POST("/") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Multipart method must contain at least one @Part.\n for method Example.method"); } } @Test public void implicitFormEncodingByFieldForbidden() { class Example { @POST("/") // Call<ResponseBody> method(@Field("a") int a) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Field parameters can only be used with form encoding. (parameter #1)\n for method Example.method"); } } @Test public void implicitFormEncodingByFieldMapForbidden() { class Example { @POST("/") // Call<ResponseBody> method(@FieldMap Map<String, String> a) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@FieldMap parameters can only be used with form encoding. (parameter #1)\n for method Example.method"); } } @Test public void formEncodingFailsOnNonBodyMethod() { class Example { @FormUrlEncoded // @GET("/") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "FormUrlEncoded can only be specified on HTTP methods with request body (e.g., @POST).\n for method Example.method"); } } @Test public void formEncodingFailsWithNoParts() { class Example { @FormUrlEncoded // @POST("/") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Form-encoded method must contain at least one @Field.\n for method Example.method"); } } @Test public void headersFailWhenEmptyOnMethod() { class Example { @GET("/") // @Headers({}) // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("@Headers annotation is empty.\n for method Example.method"); } } @Test public void headersFailWhenMalformed() { class Example { @GET("/") // @Headers("Malformed") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Headers value must be in the form \"Name: Value\". Found: \"Malformed\"\n for method Example.method"); } } @Test public void pathParamNonPathParamAndTypedBytes() { class Example { @PUT("/{a}") // Call<ResponseBody> method(@Path("a") int a, @Path("b") int b, @Body int c) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "URL \"/{a}\" does not contain \"{b}\". (parameter #2)\n for method Example.method"); } } @Test public void parameterWithoutAnnotation() { class Example { @GET("/") // Call<ResponseBody> method(String a) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "No Retrofit annotation found. (parameter #1)\n for method Example.method"); } } @Test public void nonBodyHttpMethodWithSingleEntity() { class Example { @GET("/") // Call<ResponseBody> method(@Body String o) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Non-body HTTP method cannot contain @Body.\n for method Example.method"); } } @Test public void queryMapMustBeAMap() { class Example { @GET("/") // Call<ResponseBody> method(@QueryMap List<String> a) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@QueryMap parameter type must be Map. (parameter #1)\n for method Example.method"); } } @Test public void queryMapSupportsSubclasses() { class Foo extends HashMap<String, String> { } class Example { @GET("/") // Call<ResponseBody> method(@QueryMap Foo a) { return null; } } Foo foo = new Foo(); foo.put("hello", "world"); Request request = buildRequest(Example.class, foo); assertThat(request.url().toString()).isEqualTo("http://example.com/?hello=world"); } @Test public void queryMapRejectsNull() { class Example { @GET("/") // Call<ResponseBody> method(@QueryMap Map<String, String> a) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Query map was null."); } } @Test public void queryMapRejectsNullKeys() { class Example { @GET("/") // Call<ResponseBody> method(@QueryMap Map<String, String> a) { return null; } } Map<String, String> queryParams = new LinkedHashMap<>(); queryParams.put("ping", "pong"); queryParams.put(null, "kat"); try { buildRequest(Example.class, queryParams); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Query map contained null key."); } } @Test public void queryMapRejectsNullValues() { class Example { @GET("/") // Call<ResponseBody> method(@QueryMap Map<String, String> a) { return null; } } Map<String, String> queryParams = new LinkedHashMap<>(); queryParams.put("ping", "pong"); queryParams.put("kit", null); try { buildRequest(Example.class, queryParams); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Query map contained null value for key 'kit'."); } } @Test public void getWithHeaderMap() { class Example { @GET("/search") Call<ResponseBody> method(@HeaderMap Map<String, Object> headers) { return null; } } Map<String, Object> headers = new LinkedHashMap<>(); headers.put("Accept", "text/plain"); headers.put("Accept-Charset", "utf-8"); Request request = buildRequest(Example.class, headers); assertThat(request.method()).isEqualTo("GET"); assertThat(request.url().toString()).isEqualTo("http://example.com/search"); assertThat(request.body()).isNull(); assertThat(request.headers().size()).isEqualTo(2); assertThat(request.header("Accept")).isEqualTo("text/plain"); assertThat(request.header("Accept-Charset")).isEqualTo("utf-8"); } @Test public void headerMapMustBeAMap() { class Example { @GET("/") Call<ResponseBody> method(@HeaderMap List<String> headers) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@HeaderMap parameter type must be Map. (parameter #1)\n for method Example.method"); } } @Test public void headerMapSupportsSubclasses() { class Foo extends HashMap<String, String> { } class Example { @GET("/search") Call<ResponseBody> method(@HeaderMap Foo headers) { return null; } } Foo headers = new Foo(); headers.put("Accept", "text/plain"); Request request = buildRequest(Example.class, headers); assertThat(request.url().toString()).isEqualTo("http://example.com/search"); assertThat(request.headers().size()).isEqualTo(1); assertThat(request.header("Accept")).isEqualTo("text/plain"); } @Test public void headerMapRejectsNull() { class Example { @GET("/") Call<ResponseBody> method(@HeaderMap Map<String, String> headers) { return null; } } try { buildRequest(Example.class, (Map<String, String>) null); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Header map was null."); } } @Test public void headerMapRejectsNullKeys() { class Example { @GET("/") Call<ResponseBody> method(@HeaderMap Map<String, String> headers) { return null; } } Map<String, String> headers = new LinkedHashMap<>(); headers.put("Accept", "text/plain"); headers.put(null, "utf-8"); try { buildRequest(Example.class, headers); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Header map contained null key."); } } @Test public void headerMapRejectsNullValues() { class Example { @GET("/") Call<ResponseBody> method(@HeaderMap Map<String, String> headers) { return null; } } Map<String, String> headers = new LinkedHashMap<>(); headers.put("Accept", "text/plain"); headers.put("Accept-Charset", null); try { buildRequest(Example.class, headers); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Header map contained null value for key 'Accept-Charset'."); } } @Test public void twoBodies() { class Example { @PUT("/") // Call<ResponseBody> method(@Body String o1, @Body String o2) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Multiple @Body method annotations found. (parameter #2)\n for method Example.method"); } } @Test public void bodyInNonBodyRequest() { class Example { @Multipart // @PUT("/") // Call<ResponseBody> method(@Part("one") String o1, @Body String o2) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Body parameters cannot be used with form or multi-part encoding. (parameter #2)\n for method Example.method"); } } @Test public void get() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void delete() { class Example { @DELETE("/foo/bar/") // Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("DELETE"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertNull(request.body()); } @Test public void head() { class Example { @HEAD("/foo/bar/") // Call<Void> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("HEAD"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void headWithoutVoidThrows() { class Example { @HEAD("/foo/bar/") // Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "HEAD method must use Void as response type.\n for method Example.method"); } } @Test public void post() { class Example { @POST("/foo/bar/") // Call<ResponseBody> method(@Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "hi"); Request request = buildRequest(Example.class, body); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertBody(request.body(), "hi"); } @Test public void put() { class Example { @PUT("/foo/bar/") // Call<ResponseBody> method(@Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "hi"); Request request = buildRequest(Example.class, body); assertThat(request.method()).isEqualTo("PUT"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertBody(request.body(), "hi"); } @Test public void patch() { class Example { @PATCH("/foo/bar/") // Call<ResponseBody> method(@Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "hi"); Request request = buildRequest(Example.class, body); assertThat(request.method()).isEqualTo("PATCH"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertBody(request.body(), "hi"); } @Test public void options() { class Example { @OPTIONS("/foo/bar/") // Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("OPTIONS"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void getWithPathParam() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path("ping") String ping) { return null; } } Request request = buildRequest(Example.class, "po ng"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/po%20ng/"); assertThat(request.body()).isNull(); } @Test public void getWithUnusedAndInvalidNamedPathParam() { class Example { @GET("/foo/bar/{ping}/{kit,kat}/") // Call<ResponseBody> method(@Path("ping") String ping) { return null; } } Request request = buildRequest(Example.class, "pong"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/pong/%7Bkit,kat%7D/"); assertThat(request.body()).isNull(); } @Test public void getWithEncodedPathParam() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path(value = "ping", encoded = true) String ping) { return null; } } Request request = buildRequest(Example.class, "po%20ng"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/po%20ng/"); assertThat(request.body()).isNull(); } @Test public void getWithEncodedPathSegments() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path(value = "ping", encoded = true) String ping) { return null; } } Request request = buildRequest(Example.class, "baz/pong/more"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/baz/pong/more/"); assertThat(request.body()).isNull(); } @Test public void getWithUnencodedPathSegmentsPreventsRequestSplitting() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path(value = "ping", encoded = false) String ping) { return null; } } Request request = buildRequest(Example.class, "baz/\r\nheader: blue"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/baz%2F%0D%0Aheader:%20blue/"); assertThat(request.body()).isNull(); } @Test public void getWithEncodedPathStillPreventsRequestSplitting() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path(value = "ping", encoded = true) String ping) { return null; } } Request request = buildRequest(Example.class, "baz/\r\npong"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/baz/pong/"); assertThat(request.body()).isNull(); } @Test public void pathParamRequired() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path("ping") String ping) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e.getMessage()).isEqualTo("Path parameter \"ping\" value must not be null."); } } @Test public void getWithQueryParam() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Query("ping") String ping) { return null; } } Request request = buildRequest(Example.class, "pong"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?ping=pong"); assertThat(request.body()).isNull(); } @Test public void getWithEncodedQueryParam() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Query(value = "pi%20ng", encoded = true) String ping) { return null; } } Request request = buildRequest(Example.class, "p%20o%20n%20g"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?pi%20ng=p%20o%20n%20g"); assertThat(request.body()).isNull(); } @Test public void queryParamOptionalOmitsQuery() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Query("ping") String ping) { return null; } } Request request = buildRequest(Example.class, new Object[] { null }); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); } @Test public void queryParamOptional() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Query("foo") String foo, @Query("ping") String ping, @Query("kit") String kit) { return null; } } Request request = buildRequest(Example.class, "bar", null, "kat"); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?foo=bar&kit=kat"); } @Test public void getWithQueryUrlAndParam() { class Example { @GET("/foo/bar/?hi=mom") // Call<ResponseBody> method(@Query("ping") String ping) { return null; } } Request request = buildRequest(Example.class, "pong"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?hi=mom&ping=pong"); assertThat(request.body()).isNull(); } @Test public void getWithQuery() { class Example { @GET("/foo/bar/?hi=mom") // Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?hi=mom"); assertThat(request.body()).isNull(); } @Test public void getWithPathAndQueryParam() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path("ping") String ping, @Query("kit") String kit, @Query("riff") String riff) { return null; } } Request request = buildRequest(Example.class, "pong", "kat", "raff"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/pong/?kit=kat&riff=raff"); assertThat(request.body()).isNull(); } @Test public void getWithQueryThenPathThrows() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Query("kit") String kit, @Path("ping") String ping) { return null; } } try { buildRequest(Example.class, "kat", "pong"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("A @Path parameter must not come after a @Query. (parameter #2)\n" + " for method Example.method"); } } @Test public void getWithPathAndQueryQuestionMarkParam() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path("ping") String ping, @Query("kit") String kit) { return null; } } Request request = buildRequest(Example.class, "pong?", "kat?"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/pong%3F/?kit=kat?"); assertThat(request.body()).isNull(); } @Test public void getWithPathAndQueryAmpersandParam() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path("ping") String ping, @Query("kit") String kit) { return null; } } Request request = buildRequest(Example.class, "pong&", "kat&"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/pong&/?kit=kat%26"); assertThat(request.body()).isNull(); } @Test public void getWithPathAndQueryHashParam() { class Example { @GET("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path("ping") String ping, @Query("kit") String kit) { return null; } } Request request = buildRequest(Example.class, "pong#", "kat#"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/pong%23/?kit=kat%23"); assertThat(request.body()).isNull(); } @Test public void getWithQueryParamList() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Query("key") List<Object> keys) { return null; } } List<Object> values = Arrays.<Object>asList(1, 2, null, "three", "1"); Request request = buildRequest(Example.class, values); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?key=1&key=2&key=three&key=1"); assertThat(request.body()).isNull(); } @Test public void getWithQueryParamArray() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Query("key") Object[] keys) { return null; } } Object[] values = { 1, 2, null, "three", "1" }; Request request = buildRequest(Example.class, new Object[] { values }); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?key=1&key=2&key=three&key=1"); assertThat(request.body()).isNull(); } @Test public void getWithQueryParamPrimitiveArray() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Query("key") int[] keys) { return null; } } int[] values = { 1, 2, 3, 1 }; Request request = buildRequest(Example.class, new Object[] { values }); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?key=1&key=2&key=3&key=1"); assertThat(request.body()).isNull(); } @Test public void getWithQueryNameParam() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@QueryName String ping) { return null; } } Request request = buildRequest(Example.class, "pong"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?pong"); assertThat(request.body()).isNull(); } @Test public void getWithEncodedQueryNameParam() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@QueryName(encoded = true) String ping) { return null; } } Request request = buildRequest(Example.class, "p%20o%20n%20g"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?p%20o%20n%20g"); assertThat(request.body()).isNull(); } @Test public void queryNameParamOptionalOmitsQuery() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@QueryName String ping) { return null; } } Request request = buildRequest(Example.class, new Object[] { null }); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); } @Test public void getWithQueryNameParamList() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@QueryName List<Object> keys) { return null; } } List<Object> values = Arrays.<Object>asList(1, 2, null, "three", "1"); Request request = buildRequest(Example.class, values); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?1&2&three&1"); assertThat(request.body()).isNull(); } @Test public void getWithQueryNameParamArray() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@QueryName Object[] keys) { return null; } } Object[] values = { 1, 2, null, "three", "1" }; Request request = buildRequest(Example.class, new Object[] { values }); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?1&2&three&1"); assertThat(request.body()).isNull(); } @Test public void getWithQueryNameParamPrimitiveArray() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@QueryName int[] keys) { return null; } } int[] values = { 1, 2, 3, 1 }; Request request = buildRequest(Example.class, new Object[] { values }); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?1&2&3&1"); assertThat(request.body()).isNull(); } @Test public void getWithQueryParamMap() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@QueryMap Map<String, Object> query) { return null; } } Map<String, Object> params = new LinkedHashMap<>(); params.put("kit", "kat"); params.put("ping", "pong"); Request request = buildRequest(Example.class, params); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?kit=kat&ping=pong"); assertThat(request.body()).isNull(); } @Test public void getWithEncodedQueryParamMap() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@QueryMap(encoded = true) Map<String, Object> query) { return null; } } Map<String, Object> params = new LinkedHashMap<>(); params.put("kit", "k%20t"); params.put("pi%20ng", "p%20g"); Request request = buildRequest(Example.class, params); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?kit=k%20t&pi%20ng=p%20g"); assertThat(request.body()).isNull(); } @Test public void getAbsoluteUrl() { class Example { @GET("http://example2.com/foo/bar/") Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example2.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void getWithStringUrl() { class Example { @GET Call<ResponseBody> method(@Url String url) { return null; } } Request request = buildRequest(Example.class, "foo/bar/"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void getWithJavaUriUrl() { class Example { @GET Call<ResponseBody> method(@Url URI url) { return null; } } Request request = buildRequest(Example.class, URI.create("foo/bar/")); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void getWithStringUrlAbsolute() { class Example { @GET Call<ResponseBody> method(@Url String url) { return null; } } Request request = buildRequest(Example.class, "https://example2.com/foo/bar/"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("https://example2.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void getWithJavaUriUrlAbsolute() { class Example { @GET Call<ResponseBody> method(@Url URI url) { return null; } } Request request = buildRequest(Example.class, URI.create("https://example2.com/foo/bar/")); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("https://example2.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void getWithUrlAbsoluteSameHost() { class Example { @GET Call<ResponseBody> method(@Url String url) { return null; } } Request request = buildRequest(Example.class, "http://example.com/foo/bar/"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void getWithHttpUrl() { class Example { @GET Call<ResponseBody> method(@Url HttpUrl url) { return null; } } Request request = buildRequest(Example.class, HttpUrl.parse("http://example.com/foo/bar/")); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url()).isEqualTo(HttpUrl.parse("http://example.com/foo/bar/")); assertThat(request.body()).isNull(); } @Test public void getWithNullUrl() { class Example { @GET Call<ResponseBody> method(@Url HttpUrl url) { return null; } } try { buildRequest(Example.class, (HttpUrl) null); fail(); } catch (NullPointerException expected) { assertThat(expected).hasMessage("@Url parameter is null."); } } @Test public void getWithNonStringUrlThrows() { class Example { @GET Call<ResponseBody> method(@Url Object url) { return null; } } try { buildRequest(Example.class, "foo/bar"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Url must be okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri type." + " (parameter #1)\n" + " for method Example.method"); } } @Test public void getUrlAndUrlParamThrows() { class Example { @GET("foo/bar") Call<ResponseBody> method(@Url Object url) { return null; } } try { buildRequest(Example.class, "foo/bar"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("@Url cannot be used with @GET URL (parameter #1)\n" + " for method Example.method"); } } @Test public void getWithoutUrlThrows() { class Example { @GET Call<ResponseBody> method() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Missing either @GET URL or @Url parameter.\n" + " for method Example.method"); } } @Test public void getWithUrlThenPathThrows() { class Example { @GET Call<ResponseBody> method(@Url String url, @Path("hey") String hey) { return null; } } try { buildRequest(Example.class, "foo/bar"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("@Path parameters may not be used with @Url. (parameter #2)\n" + " for method Example.method"); } } @Test public void getWithPathThenUrlThrows() { class Example { @GET Call<ResponseBody> method(@Path("hey") String hey, @Url Object url) { return null; } } try { buildRequest(Example.class, "foo/bar"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("@Path can only be used with relative url on @GET (parameter #1)\n" + " for method Example.method"); } } @Test public void getWithQueryThenUrlThrows() { class Example { @GET("foo/bar") Call<ResponseBody> method(@Query("hey") String hey, @Url Object url) { return null; } } try { buildRequest(Example.class, "hey", "foo/bar/"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("A @Url parameter must not come after a @Query (parameter #2)\n" + " for method Example.method"); } } @Test public void getWithUrlThenQuery() { class Example { @GET Call<ResponseBody> method(@Url String url, @Query("hey") String hey) { return null; } } Request request = buildRequest(Example.class, "foo/bar/", "hey!"); assertThat(request.method()).isEqualTo("GET"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/?hey=hey!"); } @Test public void postWithUrl() { class Example { @POST Call<ResponseBody> method(@Url String url, @Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "hi"); Request request = buildRequest(Example.class, "http://example.com/foo/bar", body); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar"); assertBody(request.body(), "hi"); } @Test public void normalPostWithPathParam() { class Example { @POST("/foo/bar/{ping}/") // Call<ResponseBody> method(@Path("ping") String ping, @Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(TEXT_PLAIN, "Hi!"); Request request = buildRequest(Example.class, "pong", body); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/pong/"); assertBody(request.body(), "Hi!"); } @Test public void emptyBody() { class Example { @POST("/foo/bar/") // Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertBody(request.body(), ""); } @Ignore("https://github.com/square/okhttp/issues/229") @Test public void customMethodEmptyBody() { class Example { @HTTP(method = "CUSTOM", path = "/foo/bar/", hasBody = true) // Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("CUSTOM"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertBody(request.body(), ""); } @Test public void bodyRequired() { class Example { @POST("/foo/bar/") // Call<ResponseBody> method(@Body RequestBody body) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e.getMessage()).isEqualTo("Body parameter value must not be null."); } } @Test public void bodyWithPathParams() { class Example { @POST("/foo/bar/{ping}/{kit}/") // Call<ResponseBody> method(@Path("ping") String ping, @Body RequestBody body, @Path("kit") String kit) { return null; } } RequestBody body = RequestBody.create(TEXT_PLAIN, "Hi!"); Request request = buildRequest(Example.class, "pong", body, "kat"); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/pong/kat/"); assertBody(request.body(), "Hi!"); } @Test public void simpleMultipart() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part("ping") String ping, @Part("kit") RequestBody kit) { return null; } } Request request = buildRequest(Example.class, "pong", RequestBody.create( MediaType.parse("text/plain"), "kat")); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"\r\n") .contains("\r\npong\r\n--"); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"kit\"") .contains("\r\nkat\r\n--"); } @Test public void multipartArray() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part("ping") String[] ping) { return null; } } Request request = buildRequest(Example.class, new Object[] { new String[] { "pong1", "pong2" } }); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"\r\n") .contains("\r\npong1\r\n--"); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"") .contains("\r\npong2\r\n--"); } @Test public void multipartRequiresName() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part RequestBody part) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Part annotation must supply a name or use MultipartBody.Part parameter type. (parameter #1)\n" + " for method Example.method"); } } @Test public void multipartIterableRequiresName() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part List<RequestBody> part) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Part annotation must supply a name or use MultipartBody.Part parameter type. (parameter #1)\n" + " for method Example.method"); } } @Test public void multipartArrayRequiresName() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part RequestBody[] part) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Part annotation must supply a name or use MultipartBody.Part parameter type. (parameter #1)\n" + " for method Example.method"); } } @Test public void multipartOkHttpPartForbidsName() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part("name") MultipartBody.Part part) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Part parameters using the MultipartBody.Part must not include a part name in the annotation. (parameter #1)\n" + " for method Example.method"); } } @Test public void multipartOkHttpPart() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part MultipartBody.Part part) { return null; } } MultipartBody.Part part = MultipartBody.Part.createFormData("kit", "kat"); Request request = buildRequest(Example.class, part); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"kit\"\r\n") .contains("\r\nkat\r\n--"); } @Test public void multipartOkHttpIterablePart() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part List<MultipartBody.Part> part) { return null; } } MultipartBody.Part part1 = MultipartBody.Part.createFormData("foo", "bar"); MultipartBody.Part part2 = MultipartBody.Part.createFormData("kit", "kat"); Request request = buildRequest(Example.class, Arrays.asList(part1, part2)); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"foo\"\r\n") .contains("\r\nbar\r\n--"); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"kit\"\r\n") .contains("\r\nkat\r\n--"); } @Test public void multipartOkHttpArrayPart() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part MultipartBody.Part[] part) { return null; } } MultipartBody.Part part1 = MultipartBody.Part.createFormData("foo", "bar"); MultipartBody.Part part2 = MultipartBody.Part.createFormData("kit", "kat"); Request request = buildRequest(Example.class, new Object[] { new MultipartBody.Part[] { part1, part2 } }); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"foo\"\r\n") .contains("\r\nbar\r\n--"); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"kit\"\r\n") .contains("\r\nkat\r\n--"); } @Test public void multipartOkHttpPartWithFilename() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part MultipartBody.Part part) { return null; } } MultipartBody.Part part = MultipartBody.Part.createFormData("kit", "kit.txt", RequestBody.create(null, "kat")); Request request = buildRequest(Example.class, part); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"kit\"; filename=\"kit.txt\"\r\n") .contains("\r\nkat\r\n--"); } @Test public void multipartIterable() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part("ping") List<String> ping) { return null; } } Request request = buildRequest(Example.class, Arrays.asList("pong1", "pong2")); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"\r\n") .contains("\r\npong1\r\n--"); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"") .contains("\r\npong2\r\n--"); } @Test public void multipartIterableOkHttpPart() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part("ping") List<MultipartBody.Part> part) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Part parameters using the MultipartBody.Part must not include a part name in the annotation. (parameter #1)\n" + " for method Example.method"); } } @Test public void multipartArrayOkHttpPart() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part("ping") MultipartBody.Part[] part) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@Part parameters using the MultipartBody.Part must not include a part name in the annotation. (parameter #1)\n" + " for method Example.method"); } } @Test public void multipartWithEncoding() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part(value = "ping", encoding = "8-bit") String ping, @Part(value = "kit", encoding = "7-bit") RequestBody kit) { return null; } } Request request = buildRequest(Example.class, "pong", RequestBody.create( MediaType.parse("text/plain"), "kat")); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"\r\n") .contains("Content-Transfer-Encoding: 8-bit") .contains("\r\npong\r\n--"); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"kit\"") .contains("Content-Transfer-Encoding: 7-bit") .contains("\r\nkat\r\n--"); } @Test public void multipartPartMap() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap Map<String, RequestBody> parts) { return null; } } Map<String, RequestBody> params = new LinkedHashMap<>(); params.put("ping", RequestBody.create(null, "pong")); params.put("kit", RequestBody.create(null, "kat")); Request request = buildRequest(Example.class, params); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"\r\n") .contains("\r\npong\r\n--"); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"kit\"") .contains("\r\nkat\r\n--"); } @Test public void multipartPartMapWithEncoding() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap(encoding = "8-bit") Map<String, RequestBody> parts) { return null; } } Map<String, RequestBody> params = new LinkedHashMap<>(); params.put("ping", RequestBody.create(null, "pong")); params.put("kit", RequestBody.create(null, "kat")); Request request = buildRequest(Example.class, params); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"\r\n") .contains("Content-Transfer-Encoding: 8-bit") .contains("\r\npong\r\n--"); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"kit\"") .contains("Content-Transfer-Encoding: 8-bit") .contains("\r\nkat\r\n--"); } @Test public void multipartPartMapRejectsNonStringKeys() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap Map<Object, RequestBody> parts) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@PartMap keys must be of type String: class java.lang.Object (parameter #1)\n" + " for method Example.method"); } } @Test public void multipartPartMapRejectsOkHttpPartValues() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap Map<String, MultipartBody.Part> parts) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@PartMap values cannot be MultipartBody.Part. Use @Part List<Part> or a different value type instead. (parameter #1)\n" + " for method Example.method"); } } @Test public void multipartPartMapRejectsNull() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap Map<String, RequestBody> parts) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Part map was null."); } } @Test public void multipartPartMapRejectsNullKeys() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap Map<String, RequestBody> parts) { return null; } } Map<String, RequestBody> params = new LinkedHashMap<>(); params.put("ping", RequestBody.create(null, "pong")); params.put(null, RequestBody.create(null, "kat")); try { buildRequest(Example.class, params); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Part map contained null key."); } } @Test public void multipartPartMapRejectsNullValues() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap Map<String, RequestBody> parts) { return null; } } Map<String, RequestBody> params = new LinkedHashMap<>(); params.put("ping", RequestBody.create(null, "pong")); params.put("kit", null); try { buildRequest(Example.class, params); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Part map contained null value for key 'kit'."); } } @Test public void multipartPartMapMustBeMap() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap List<Object> parts) { return null; } } try { buildRequest(Example.class, Collections.emptyList()); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@PartMap parameter type must be Map. (parameter #1)\n for method Example.method"); } } @Test public void multipartPartMapSupportsSubclasses() throws IOException { class Foo extends HashMap<String, String> { } class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@PartMap Foo parts) { return null; } } Foo foo = new Foo(); foo.put("hello", "world"); Request request = buildRequest(Example.class, foo); Buffer buffer = new Buffer(); request.body().writeTo(buffer); assertThat(buffer.readUtf8()) .contains("name=\"hello\"") .contains("\r\n\r\nworld\r\n--"); } @Test public void multipartNullRemovesPart() throws IOException { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part("ping") String ping, @Part("fizz") String fizz) { return null; } } Request request = buildRequest(Example.class, "pong", null); assertThat(request.method()).isEqualTo("POST"); assertThat(request.headers().size()).isZero(); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); RequestBody body = request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String bodyString = buffer.readUtf8(); assertThat(bodyString) .contains("Content-Disposition: form-data;") .contains("name=\"ping\"") .contains("\r\npong\r\n--"); } @Test public void multipartPartOptional() { class Example { @Multipart // @POST("/foo/bar/") // Call<ResponseBody> method(@Part("ping") RequestBody ping) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage()).isEqualTo("Multipart body must have at least one part."); } } @Test public void simpleFormEncoded() { class Example { @FormUrlEncoded // @POST("/foo") // Call<ResponseBody> method(@Field("foo") String foo, @Field("ping") String ping) { return null; } } Request request = buildRequest(Example.class, "bar", "pong"); assertBody(request.body(), "foo=bar&ping=pong"); } @Test public void formEncodedWithEncodedNameFieldParam() { class Example { @FormUrlEncoded // @POST("/foo") // Call<ResponseBody> method(@Field(value = "na%20me", encoded = true) String foo) { return null; } } Request request = buildRequest(Example.class, "ba%20r"); assertBody(request.body(), "na%20me=ba%20r"); } @Test public void formEncodedFieldOptional() { class Example { @FormUrlEncoded // @POST("/foo") // Call<ResponseBody> method(@Field("foo") String foo, @Field("ping") String ping, @Field("kit") String kit) { return null; } } Request request = buildRequest(Example.class, "bar", null, "kat"); assertBody(request.body(), "foo=bar&kit=kat"); } @Test public void formEncodedFieldList() { class Example { @FormUrlEncoded // @POST("/foo") // Call<ResponseBody> method(@Field("foo") List<Object> fields, @Field("kit") String kit) { return null; } } List<Object> values = Arrays.<Object>asList("foo", "bar", null, 3); Request request = buildRequest(Example.class, values, "kat"); assertBody(request.body(), "foo=foo&foo=bar&foo=3&kit=kat"); } @Test public void formEncodedFieldArray() { class Example { @FormUrlEncoded // @POST("/foo") // Call<ResponseBody> method(@Field("foo") Object[] fields, @Field("kit") String kit) { return null; } } Object[] values = { 1, 2, null, "three" }; Request request = buildRequest(Example.class, values, "kat"); assertBody(request.body(), "foo=1&foo=2&foo=three&kit=kat"); } @Test public void formEncodedFieldPrimitiveArray() { class Example { @FormUrlEncoded // @POST("/foo") // Call<ResponseBody> method(@Field("foo") int[] fields, @Field("kit") String kit) { return null; } } int[] values = { 1, 2, 3 }; Request request = buildRequest(Example.class, values, "kat"); assertBody(request.body(), "foo=1&foo=2&foo=3&kit=kat"); } @Test public void formEncodedWithEncodedNameFieldParamMap() { class Example { @FormUrlEncoded // @POST("/foo") // Call<ResponseBody> method(@FieldMap(encoded = true) Map<String, Object> fieldMap) { return null; } } Map<String, Object> fieldMap = new LinkedHashMap<>(); fieldMap.put("k%20it", "k%20at"); fieldMap.put("pin%20g", "po%20ng"); Request request = buildRequest(Example.class, fieldMap); assertBody(request.body(), "k%20it=k%20at&pin%20g=po%20ng"); } @Test public void formEncodedFieldMap() { class Example { @FormUrlEncoded // @POST("/foo") // Call<ResponseBody> method(@FieldMap Map<String, Object> fieldMap) { return null; } } Map<String, Object> fieldMap = new LinkedHashMap<>(); fieldMap.put("kit", "kat"); fieldMap.put("ping", "pong"); Request request = buildRequest(Example.class, fieldMap); assertBody(request.body(), "kit=kat&ping=pong"); } @Test public void fieldMapRejectsNull() { class Example { @FormUrlEncoded // @POST("/") // Call<ResponseBody> method(@FieldMap Map<String, Object> a) { return null; } } try { buildRequest(Example.class, new Object[] { null }); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Field map was null."); } } @Test public void fieldMapRejectsNullKeys() { class Example { @FormUrlEncoded // @POST("/") // Call<ResponseBody> method(@FieldMap Map<String, Object> a) { return null; } } Map<String, Object> fieldMap = new LinkedHashMap<>(); fieldMap.put("kit", "kat"); fieldMap.put(null, "pong"); try { buildRequest(Example.class, fieldMap); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Field map contained null key."); } } @Test public void fieldMapRejectsNullValues() { class Example { @FormUrlEncoded // @POST("/") // Call<ResponseBody> method(@FieldMap Map<String, Object> a) { return null; } } Map<String, Object> fieldMap = new LinkedHashMap<>(); fieldMap.put("kit", "kat"); fieldMap.put("foo", null); try { buildRequest(Example.class, fieldMap); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Field map contained null value for key 'foo'."); } } @Test public void fieldMapMustBeAMap() { class Example { @FormUrlEncoded // @POST("/") // Call<ResponseBody> method(@FieldMap List<String> a) { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "@FieldMap parameter type must be Map. (parameter #1)\n for method Example.method"); } } @Test public void fieldMapSupportsSubclasses() throws IOException { class Foo extends HashMap<String, String> { } class Example { @FormUrlEncoded // @POST("/") // Call<ResponseBody> method(@FieldMap Foo a) { return null; } } Foo foo = new Foo(); foo.put("hello", "world"); Request request = buildRequest(Example.class, foo); Buffer buffer = new Buffer(); request.body().writeTo(buffer); assertThat(buffer.readUtf8()).isEqualTo("hello=world"); } @Test public void simpleHeaders() { class Example { @GET("/foo/bar/") @Headers({ "ping: pong", "kit: kat" }) Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.method()).isEqualTo("GET"); okhttp3.Headers headers = request.headers(); assertThat(headers.size()).isEqualTo(2); assertThat(headers.get("ping")).isEqualTo("pong"); assertThat(headers.get("kit")).isEqualTo("kat"); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void headerParamToString() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Header("kit") BigInteger kit) { return null; } } Request request = buildRequest(Example.class, new BigInteger("1234")); assertThat(request.method()).isEqualTo("GET"); okhttp3.Headers headers = request.headers(); assertThat(headers.size()).isEqualTo(1); assertThat(headers.get("kit")).isEqualTo("1234"); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void headerParam() { class Example { @GET("/foo/bar/") // @Headers("ping: pong") // Call<ResponseBody> method(@Header("kit") String kit) { return null; } } Request request = buildRequest(Example.class, "kat"); assertThat(request.method()).isEqualTo("GET"); okhttp3.Headers headers = request.headers(); assertThat(headers.size()).isEqualTo(2); assertThat(headers.get("ping")).isEqualTo("pong"); assertThat(headers.get("kit")).isEqualTo("kat"); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void headerParamList() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Header("foo") List<String> kit) { return null; } } Request request = buildRequest(Example.class, Arrays.asList("bar", null, "baz")); assertThat(request.method()).isEqualTo("GET"); okhttp3.Headers headers = request.headers(); assertThat(headers.size()).isEqualTo(2); assertThat(headers.values("foo")).containsExactly("bar", "baz"); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void headerParamArray() { class Example { @GET("/foo/bar/") // Call<ResponseBody> method(@Header("foo") String[] kit) { return null; } } Request request = buildRequest(Example.class, (Object) new String[] { "bar", null, "baz" }); assertThat(request.method()).isEqualTo("GET"); okhttp3.Headers headers = request.headers(); assertThat(headers.size()).isEqualTo(2); assertThat(headers.values("foo")).containsExactly("bar", "baz"); assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); assertThat(request.body()).isNull(); } @Test public void contentTypeAnnotationHeaderOverrides() { class Example { @POST("/") // @Headers("Content-Type: text/not-plain") // Call<ResponseBody> method(@Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "hi"); Request request = buildRequest(Example.class, body); assertThat(request.body().contentType().toString()).isEqualTo("text/not-plain"); } @Test public void malformedContentTypeHeaderThrows() { class Example { @POST("/") // @Headers("Content-Type: hello, world!") // Call<ResponseBody> method(@Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "hi"); try { buildRequest(Example.class, body); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Malformed content type: hello, world!\n" + " for method Example.method"); } } @Test public void contentTypeAnnotationHeaderAddsHeaderWithNoBody() { class Example { @DELETE("/") // @Headers("Content-Type: text/not-plain") // Call<ResponseBody> method() { return null; } } Request request = buildRequest(Example.class); assertThat(request.headers().get("Content-Type")).isEqualTo("text/not-plain"); } @Test public void contentTypeParameterHeaderOverrides() { class Example { @POST("/") // Call<ResponseBody> method(@Header("Content-Type") String contentType, @Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "Plain"); Request request = buildRequest(Example.class, "text/not-plain", body); assertThat(request.body().contentType().toString()).isEqualTo("text/not-plain"); } @Test public void malformedContentTypeParameterThrows() { class Example { @POST("/") // Call<ResponseBody> method(@Header("Content-Type") String contentType, @Body RequestBody body) { return null; } } RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "hi"); try { buildRequest(Example.class, "hello, world!", body); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Malformed content type: hello, world!"); } } @Test public void malformedAnnotationRelativeUrlThrows() { class Example { @GET("ftp://example.org") Call<ResponseBody> get() { return null; } } try { buildRequest(Example.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Malformed URL. Base: http://example.com/, Relative: ftp://example.org"); } } @Test public void malformedParameterRelativeUrlThrows() { class Example { @GET Call<ResponseBody> get(@Url String relativeUrl) { return null; } } try { buildRequest(Example.class, "ftp://example.org"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "Malformed URL. Base: http://example.com/, Relative: ftp://example.org"); } } @Test public void multipartPartsShouldBeInOrder() throws IOException { class Example { @Multipart @POST("/foo") Call<ResponseBody> get(@Part("first") String data, @Part("second") String dataTwo, @Part("third") String dataThree) { return null; } } Request request = buildRequest(Example.class, "firstParam", "secondParam", "thirdParam"); MultipartBody body = (MultipartBody) request.body(); Buffer buffer = new Buffer(); body.writeTo(buffer); String readBody = buffer.readUtf8(); assertThat(readBody.indexOf("firstParam")).isLessThan(readBody.indexOf("secondParam")); assertThat(readBody.indexOf("secondParam")).isLessThan(readBody.indexOf("thirdParam")); } @Test public void queryParamsSkippedIfConvertedToNull() throws Exception { class Example { @GET("/query") Call<ResponseBody> queryPath(@Query("a") Object a) { return null; } } Retrofit.Builder retrofitBuilder = new Retrofit.Builder() .baseUrl("http://example.com") .addConverterFactory(new NullObjectConverterFactory()); Request request = buildRequest(Example.class, retrofitBuilder, "Ignored"); assertThat(request.url().toString()).doesNotContain("Ignored"); } @Test public void queryParamMapsConvertedToNullShouldError() throws Exception { class Example { @GET("/query") Call<ResponseBody> queryPath(@QueryMap Map<String, String> a) { return null; } } Retrofit.Builder retrofitBuilder = new Retrofit.Builder() .baseUrl("http://example.com") .addConverterFactory(new NullObjectConverterFactory()); Map<String, String> queryMap = Collections.singletonMap("kit", "kat"); try { buildRequest(Example.class, retrofitBuilder, queryMap); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessageContaining( "Query map value 'kat' converted to null by retrofit2.helpers.NullObjectConverterFactory$1 for key 'kit'."); } } @Test public void fieldParamsSkippedIfConvertedToNull() throws Exception { class Example { @FormUrlEncoded @POST("/query") Call<ResponseBody> queryPath(@Field("a") Object a) { return null; } } Retrofit.Builder retrofitBuilder = new Retrofit.Builder() .baseUrl("http://example.com") .addConverterFactory(new NullObjectConverterFactory()); Request request = buildRequest(Example.class, retrofitBuilder, "Ignored"); assertThat(request.url().toString()).doesNotContain("Ignored"); } @Test public void fieldParamMapsConvertedToNullShouldError() throws Exception { class Example { @FormUrlEncoded @POST("/query") Call<ResponseBody> queryPath(@FieldMap Map<String, String> a) { return null; } } Retrofit.Builder retrofitBuilder = new Retrofit.Builder() .baseUrl("http://example.com") .addConverterFactory(new NullObjectConverterFactory()); Map<String, String> queryMap = Collections.singletonMap("kit", "kat"); try { buildRequest(Example.class, retrofitBuilder, queryMap); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessageContaining( "Field map value 'kat' converted to null by retrofit2.helpers.NullObjectConverterFactory$1 for key 'kit'."); } } private static void assertBody(RequestBody body, String expected) { assertThat(body).isNotNull(); Buffer buffer = new Buffer(); try { body.writeTo(buffer); assertThat(buffer.readUtf8()).isEqualTo(expected); } catch (IOException e) { throw new RuntimeException(e); } } static <T> Request buildRequest(Class<T> cls, Retrofit.Builder builder, Object... args) { final AtomicReference<Request> requestRef = new AtomicReference<>(); okhttp3.Call.Factory callFactory = new okhttp3.Call.Factory() { @Override public okhttp3.Call newCall(Request request) { requestRef.set(request); throw new UnsupportedOperationException("Not implemented"); } }; Retrofit retrofit = builder.callFactory(callFactory).build(); Method method = TestingUtils.onlyMethod(cls); //noinspection unchecked ServiceMethod<T, Call<T>> serviceMethod = (ServiceMethod<T, Call<T>>) retrofit.loadServiceMethod(method); Call<T> okHttpCall = new OkHttpCall<>(serviceMethod, args); Call<T> call = serviceMethod.callAdapter.adapt(okHttpCall); try { call.execute(); throw new AssertionError(); } catch (UnsupportedOperationException ignored) { return requestRef.get(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new AssertionError(e); } } static <T> Request buildRequest(Class<T> cls, Object... args) { Retrofit.Builder retrofitBuilder = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(new ToStringConverterFactory()); return buildRequest(cls, retrofitBuilder, args); } }