/* * Copyright (C) 2015 Mesosphere, 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 com.mesosphere.mesos.rx.java; import com.google.common.collect.Maps; import com.mesosphere.mesos.rx.java.test.StringMessageCodec; import com.mesosphere.mesos.rx.java.util.UserAgent; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.reactivex.netty.protocol.http.UnicastContentSubject; import io.reactivex.netty.protocol.http.client.HttpClientRequest; import io.reactivex.netty.protocol.http.client.HttpClientResponse; import io.reactivex.netty.protocol.http.client.HttpRequestHeaders; import org.jetbrains.annotations.NotNull; import org.junit.Test; import rx.Observable; import rx.functions.Func1; import java.net.URI; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static com.mesosphere.mesos.rx.java.util.UserAgentEntries.literal; import static org.assertj.core.api.Assertions.assertThat; public final class MesosClientTest { @Test public void testUserAgentContains_MesosRxJavaCore_RxNetty() throws Exception { final String clientName = "unit-tests"; final MesosClient<String, String> client = MesosClientBuilder.<String, String>newBuilder() .sendCodec(StringMessageCodec.UTF8_STRING) .receiveCodec(StringMessageCodec.UTF8_STRING) .mesosUri(URI.create("http://localhost:12345")) .applicationUserAgentEntry(literal(clientName, "latest")) .subscribe("subscribe") .processStream(events -> events.map(e -> Optional.<SinkOperation<String>>empty())) .build(); final HttpClientRequest<ByteBuf> request = client.createPost .call("ACK") .toBlocking() .first(); final Map<String, String> headers = headersToMap(request.getHeaders()); assertThat(headers).containsKeys("User-Agent"); final String ua = headers.get("User-Agent"); assertThat(ua).startsWith(String.format("%s/%s", clientName, "latest")); assertThat(ua).contains("mesos-rxjava-client/"); } @Test public void testRequestUriFromPassedUri() throws Exception { final Func1<String, Observable<HttpClientRequest<ByteBuf>>> createPost = MesosClient.curryCreatePost( URI.create("http://localhost:12345/glavin/api/v1/scheduler"), StringMessageCodec.UTF8_STRING, StringMessageCodec.UTF8_STRING, new UserAgent( literal("testing", "latest") ), new AtomicReference<>(null) ); final HttpClientRequest<ByteBuf> request = createPost.call("something") .toBlocking() .first(); assertThat(request.getUri()).isEqualTo("/glavin/api/v1/scheduler"); } @Test public void testBasicAuthHeaderAddedToRequestWhenUserInfoPresentInUri() throws Exception { final Func1<String, Observable<HttpClientRequest<ByteBuf>>> createPost = MesosClient.curryCreatePost( URI.create("http://testuser:[email protected]:12345/api/v1/scheduler"), StringMessageCodec.UTF8_STRING, StringMessageCodec.UTF8_STRING, new UserAgent( literal("testing", "latest") ), new AtomicReference<>(null) ); final HttpClientRequest<ByteBuf> request = createPost.call("something") .toBlocking() .first(); final String authHeaderName = HttpHeaderNames.AUTHORIZATION.toString(); final Map<String, String> headers = headersToMap(request.getHeaders()); assertThat(headers).containsKeys(authHeaderName); final String authorization = headers.get(authHeaderName); assertThat(authorization).isEqualTo("Basic dGVzdHVzZXI6dGVzdHBhc3N3b3Jk"); final String base64UserPass = authorization.substring("Basic ".length()); final String userPass = new String(Base64.getDecoder().decode(base64UserPass)); assertThat(userPass).isEqualTo("testuser:testpassword"); } @Test public void testMesosStreamIdAddedToRequestWhenNonNull() throws Exception { final Func1<String, Observable<HttpClientRequest<ByteBuf>>> createPost = MesosClient.curryCreatePost( URI.create("http://localhost:12345/api/v1/scheduler"), StringMessageCodec.UTF8_STRING, StringMessageCodec.UTF8_STRING, new UserAgent( literal("testing", "latest") ), new AtomicReference<>("streamId") ); final HttpClientRequest<ByteBuf> request = createPost.call("something") .toBlocking() .first(); final Map<String, String> headers = headersToMap(request.getHeaders()); assertThat(headers).containsKeys("Mesos-Stream-Id"); assertThat(headers.get("Mesos-Stream-Id")).isEqualTo("streamId"); } @Test public void testMesosStreamIdNotPresentWhenNull() throws Exception { final Func1<String, Observable<HttpClientRequest<ByteBuf>>> createPost = MesosClient.curryCreatePost( URI.create("http://localhost:12345/api/v1/scheduler"), StringMessageCodec.UTF8_STRING, StringMessageCodec.UTF8_STRING, new UserAgent( literal("testing", "latest") ), new AtomicReference<>(null) ); final HttpClientRequest<ByteBuf> request = createPost.call("something") .toBlocking() .first(); final Map<String, String> headers = headersToMap(request.getHeaders()); assertThat(headers).doesNotContainKeys("Mesos-Stream-Id"); } @Test public void testMesosStreamIdIsSavedForSuccessfulSubscribeCall() throws Exception { final AtomicReference<String> mesosStreamId = new AtomicReference<>(null); final Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>> f = MesosClient.verifyResponseOk( "Subscribe", mesosStreamId, StringMessageCodec.UTF8_STRING.mediaType() ); final DefaultHttpResponse nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); nettyResponse.headers().add("Mesos-Stream-Id", "streamId"); nettyResponse.headers().add("Content-Type", StringMessageCodec.UTF8_STRING.mediaType()); final HttpClientResponse<ByteBuf> response = new HttpClientResponse<>( nettyResponse, UnicastContentSubject.create(1000, TimeUnit.MILLISECONDS) ); f.call(response); assertThat(mesosStreamId.get()).isEqualTo("streamId"); } @Test public void testMesosStreamIdIsNotSavedForUnsuccessfulSubscribeCall() throws Exception { final AtomicReference<String> mesosStreamId = new AtomicReference<>(null); final Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>> f = MesosClient.verifyResponseOk( "Subscribe", mesosStreamId, StringMessageCodec.UTF8_STRING.mediaType() ); final DefaultHttpResponse nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST); nettyResponse.headers().add("Mesos-Stream-Id", "streamId"); nettyResponse.headers().add("Content-Type", StringMessageCodec.UTF8_STRING.mediaType()); final HttpClientResponse<ByteBuf> response = new HttpClientResponse<>( nettyResponse, UnicastContentSubject.create(1000, TimeUnit.MILLISECONDS) ); try { f.call(response); } catch (Mesos4xxException e) { // expected } assertThat(mesosStreamId.get()).isEqualTo(null); } @Test public void testGetPort_returnsSpecifiedPort() throws Exception { assertThat(MesosClient.getPort(URI.create("http://glavin:500/path"))).isEqualTo(500); } @Test public void testGetPort_returns80ForHttp() throws Exception { assertThat(MesosClient.getPort(URI.create("http://glavin/path"))).isEqualTo(80); } @Test public void testGetPort_returns443ForHttps() throws Exception { assertThat(MesosClient.getPort(URI.create("https://glavin/path"))).isEqualTo(443); } @Test(expected = IllegalArgumentException.class) public void testGetPort_throwsExceptionWhenNoPortIsSpecifiedAndSchemeIsNotHttpOrHttps() throws Exception { MesosClient.getPort(URI.create("ftp://glavin/path")); } @Test public void testVerifyResponseOk_ensuresContentTypeOfResponseMatchesReceiveCodec() throws Exception { final Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>> f = MesosClient.verifyResponseOk( "Subscribe", new AtomicReference<>(), StringMessageCodec.UTF8_STRING.mediaType() ); final DefaultHttpResponse nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); nettyResponse.headers().add("Content-Type", "text/html"); final HttpClientResponse<ByteBuf> response = new HttpClientResponse<>( nettyResponse, UnicastContentSubject.create(1000, TimeUnit.MILLISECONDS) ); try { f.call(response); } catch (MesosException e) { final String expected = String.format( "Response had Content-Type \"%s\" expected \"%s\"", "text/html", StringMessageCodec.UTF8_STRING.mediaType() ); assertThat(e.getContext().getMessage()).isEqualTo(expected); } } @Test public void testResolveRelativeUri_usesSchemeHostAndPortFromLocation() throws Exception { final URI mesosUri = URI.create("http://127.1.0.1:5050/api/v1/scheduler"); final String location = "//127.1.0.2:5050"; final URI actual = MesosClient.resolveRelativeUri(mesosUri, location); assertThat(actual).isEqualTo(URI.create("http://127.1.0.2:5050/api/v1/scheduler")); } @Test public void testResolveRelativeUri_usesSchemeHostAndPortFromLocation_defaultPort() throws Exception { final URI mesosUri = URI.create("http://127.1.0.1:5050/api/v1/scheduler"); final String location = "//127.1.0.2"; final URI actual = MesosClient.resolveRelativeUri(mesosUri, location); assertThat(actual).isEqualTo(URI.create("http://127.1.0.2/api/v1/scheduler")); } @Test public void testCreateRedirectUri() throws Exception { final URI mesosUri = URI.create("http://127.1.0.1:5050/api/v1/scheduler"); final String actual = MesosClient.createRedirectUri(mesosUri); assertThat(actual).isEqualTo("http://127.1.0.1:5050/redirect"); } @Test public void testCreateRedirectUri_nestedPath() throws Exception { final URI mesosUri = URI.create("http://127.1.0.1:5050/nested/api/v1/scheduler"); final String actual = MesosClient.createRedirectUri(mesosUri); assertThat(actual).isEqualTo("http://127.1.0.1:5050/nested/redirect"); } @Test public void testGetUriFromRedirectResponse() throws Exception { final URI mesosUri = URI.create("http://127.1.0.1:5050/api/v1/scheduler"); final DefaultHttpResponse nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.TEMPORARY_REDIRECT); nettyResponse.headers().add("Location", "//127.1.0.2:5050"); final HttpClientResponse<ByteBuf> response = new HttpClientResponse<>( nettyResponse, UnicastContentSubject.create(1000, TimeUnit.MILLISECONDS) ); final URI uri = MesosClient.getUriFromRedirectResponse(mesosUri, response); assertThat(uri).isEqualTo(URI.create("http://127.1.0.2:5050/api/v1/scheduler")); } @Test public void testGetUriFromRedirectResponse_404() throws Exception { final URI mesosUri = URI.create("http://127.1.0.1:5050/api/v1/scheduler"); final DefaultHttpResponse nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND); final HttpClientResponse<ByteBuf> response = new HttpClientResponse<>( nettyResponse, UnicastContentSubject.create(1000, TimeUnit.MILLISECONDS) ); final URI uri = MesosClient.getUriFromRedirectResponse(mesosUri, response); assertThat(uri).isEqualTo(URI.create("http://127.1.0.1:5050/api/v1/scheduler")); } @Test public void testGetUriFromRedirectResponse_200() throws Exception { final URI mesosUri = URI.create("http://127.1.0.1:5050/api/v1/scheduler"); final DefaultHttpResponse nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); final HttpClientResponse<ByteBuf> response = new HttpClientResponse<>( nettyResponse, UnicastContentSubject.create(1000, TimeUnit.MILLISECONDS) ); final URI uri = MesosClient.getUriFromRedirectResponse(mesosUri, response); assertThat(uri).isEqualTo(URI.create("http://127.1.0.1:5050/api/v1/scheduler")); } @NotNull private static Map<String, String> headersToMap(@NotNull final HttpRequestHeaders headers) { final HashMap<String, String> map = Maps.newHashMap(); for (Map.Entry<String, String> entry : headers.entries()) { map.put(entry.getKey(), entry.getValue()); } return Collections.unmodifiableMap(map); } }