/*
 * Copyright 2002-2016 the original author or authors.
 *
 * 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 org.springframework.web.client.reactive;

import static org.junit.Assert.*;
import static org.springframework.web.client.reactive.support.RxJava1ClientWebRequestBuilders.*;
import static org.springframework.web.client.reactive.support.RxJava1ResponseExtractors.*;

import java.util.concurrent.TimeUnit;

import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import rx.Observable;
import rx.Single;
import rx.observers.TestSubscriber;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.Pojo;

/**
 * {@link WebClient} integration tests with the {@code Observable} and {@code Single} API.
 *
 * @author Brian Clozel
 */
public class RxJava1WebClientIntegrationTests {

	private MockWebServer server;

	private WebClient webClient;

	@Before
	public void setup() {
		this.server = new MockWebServer();
		this.webClient = new WebClient(new ReactorClientHttpConnector());
	}

	@Test
	public void shouldGetHeaders() throws Exception {

		HttpUrl baseUrl = server.url("/greeting?name=Spring");
		this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));

		Single<HttpHeaders> result = this.webClient
				.perform(get(baseUrl.toString()))
				.extract(headers());

		TestSubscriber<HttpHeaders> ts = new TestSubscriber<HttpHeaders>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		HttpHeaders httpHeaders = ts.getOnNextEvents().get(0);
		assertEquals(MediaType.TEXT_PLAIN, httpHeaders.getContentType());
		assertEquals(13L, httpHeaders.getContentLength());
		ts.assertValueCount(1);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
		assertEquals("/greeting?name=Spring", request.getPath());
	}

	@Test
	public void shouldGetPlainTextResponseAsObject() throws Exception {

		HttpUrl baseUrl = server.url("/greeting?name=Spring");
		this.server.enqueue(new MockResponse().setBody("Hello Spring!"));

		Single<String> result = this.webClient
				.perform(get(baseUrl.toString())
						.header("X-Test-Header", "testvalue"))
				.extract(body(String.class));

		TestSubscriber<String> ts = new TestSubscriber<String>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		String response = ts.getOnNextEvents().get(0);
		assertEquals("Hello Spring!", response);
		ts.assertValueCount(1);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("testvalue", request.getHeader("X-Test-Header"));
		assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
		assertEquals("/greeting?name=Spring", request.getPath());
	}

	@Test
	public void shouldGetPlainTextResponse() throws Exception {

		HttpUrl baseUrl = server.url("/greeting?name=Spring");
		this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));

		Single<ResponseEntity<String>> result = this.webClient
				.perform(get(baseUrl.toString())
						.accept(MediaType.TEXT_PLAIN))
				.extract(response(String.class));

		TestSubscriber<ResponseEntity<String>> ts = new TestSubscriber<ResponseEntity<String>>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		ResponseEntity<String> response = ts.getOnNextEvents().get(0);
		assertEquals(200, response.getStatusCode().value());
		assertEquals(MediaType.TEXT_PLAIN, response.getHeaders().getContentType());
		assertEquals("Hello Spring!", response.getBody());
		ts.assertValueCount(1);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("/greeting?name=Spring", request.getPath());
		assertEquals("text/plain", request.getHeader(HttpHeaders.ACCEPT));
	}

	@Test
	public void shouldGetJsonAsMonoOfString() throws Exception {

		HttpUrl baseUrl = server.url("/json");
		String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
		this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
				.setBody(content));

		Single<String> result = this.webClient
				.perform(get(baseUrl.toString())
						.accept(MediaType.APPLICATION_JSON))
				.extract(body(String.class));

		TestSubscriber<String> ts = new TestSubscriber<String>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		String response = ts.getOnNextEvents().get(0);
		assertEquals(content, response);
		ts.assertValueCount(1);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("/json", request.getPath());
		assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
	}

	@Test
	public void shouldGetJsonAsMonoOfPojo() throws Exception {

		HttpUrl baseUrl = server.url("/pojo");
		this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
				.setBody("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}"));

		Single<Pojo> result = this.webClient
				.perform(get(baseUrl.toString())
						.accept(MediaType.APPLICATION_JSON))
				.extract(body(Pojo.class));

		TestSubscriber<Pojo> ts = new TestSubscriber<Pojo>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		Pojo response = ts.getOnNextEvents().get(0);
		assertEquals("barbar", response.getBar());
		ts.assertValueCount(1);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("/pojo", request.getPath());
		assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
	}

	@Test
	public void shouldGetJsonAsFluxOfPojos() throws Exception {

		HttpUrl baseUrl = server.url("/pojos");
		this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
				.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));

		Observable<Pojo> result = this.webClient
				.perform(get(baseUrl.toString())
						.accept(MediaType.APPLICATION_JSON))
				.extract(bodyStream(Pojo.class));

		TestSubscriber<Pojo> ts = new TestSubscriber<Pojo>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		assertThat(ts.getOnNextEvents().get(0).getBar(), Matchers.is("bar1"));
		assertThat(ts.getOnNextEvents().get(1).getBar(), Matchers.is("bar2"));
		ts.assertValueCount(2);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("/pojos", request.getPath());
		assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
	}

	@Test
	public void shouldGetJsonAsResponseOfPojosStream() throws Exception {

		HttpUrl baseUrl = server.url("/pojos");
		this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
				.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));

		Single<ResponseEntity<Observable<Pojo>>> result = this.webClient
				.perform(get(baseUrl.toString())
						.accept(MediaType.APPLICATION_JSON))
				.extract(responseStream(Pojo.class));

		TestSubscriber<ResponseEntity<Observable<Pojo>>> ts = new TestSubscriber<ResponseEntity<Observable<Pojo>>>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		ResponseEntity<Observable<Pojo>> response = ts.getOnNextEvents().get(0);
		assertEquals(200, response.getStatusCode().value());
		assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
		ts.assertValueCount(1);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("/pojos", request.getPath());
		assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
	}

	@Test
	public void shouldPostPojoAsJson() throws Exception {

		HttpUrl baseUrl = server.url("/pojo/capitalize");
		this.server.enqueue(new MockResponse()
				.setHeader("Content-Type", "application/json")
				.setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));

		Pojo spring = new Pojo("foofoo", "barbar");
		Single<Pojo> result = this.webClient
				.perform(post(baseUrl.toString())
						.body(spring)
						.contentType(MediaType.APPLICATION_JSON)
						.accept(MediaType.APPLICATION_JSON))
				.extract(body(Pojo.class));

		TestSubscriber<Pojo> ts = new TestSubscriber<Pojo>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		assertThat(ts.getOnNextEvents().get(0).getBar(), Matchers.is("BARBAR"));
		ts.assertValueCount(1);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("/pojo/capitalize", request.getPath());
		assertEquals("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}", request.getBody().readUtf8());
		assertEquals("chunked", request.getHeader(HttpHeaders.TRANSFER_ENCODING));
		assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
		assertEquals("application/json", request.getHeader(HttpHeaders.CONTENT_TYPE));
	}

	@Test
	public void shouldSendCookieHeader() throws Exception {
		HttpUrl baseUrl = server.url("/test");
		this.server.enqueue(new MockResponse()
				.setHeader("Content-Type", "text/plain").setBody("test"));

		Single<String> result = this.webClient
				.perform(get(baseUrl.toString())
						.cookie("testkey", "testvalue"))
				.extract(body(String.class));

		TestSubscriber<String> ts = new TestSubscriber<String>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		String response = ts.getOnNextEvents().get(0);
		assertEquals("test", response);
		ts.assertValueCount(1);
		ts.assertCompleted();

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("/test", request.getPath());
		assertEquals("testkey=testvalue", request.getHeader(HttpHeaders.COOKIE));
	}

	@Test
	public void shouldGetErrorWhen404() throws Exception {

		HttpUrl baseUrl = server.url("/greeting?name=Spring");
		this.server.enqueue(new MockResponse().setResponseCode(404)
				.setHeader("Content-Type", "text/plain").setBody("Not Found"));

		Single<String> result = this.webClient
				.perform(get(baseUrl.toString()))
				.extract(body(String.class));

		TestSubscriber<String> ts = new TestSubscriber<String>();
		result.subscribe(ts);
		ts.awaitTerminalEvent(2, TimeUnit.SECONDS);

		ts.assertError(WebClientErrorException.class);
		WebClientErrorException exc = (WebClientErrorException) ts.getOnErrorEvents().get(0);
		assertEquals(404, exc.getStatus().value());
		assertEquals(MediaType.TEXT_PLAIN, exc.getResponseHeaders().getContentType());

		RecordedRequest request = server.takeRequest();
		assertEquals(1, server.getRequestCount());
		assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
		assertEquals("/greeting?name=Spring", request.getPath());
	}

	@After
	public void tearDown() throws Exception {
		this.server.shutdown();
	}

}
