/*
 * Copyright 2002-2013 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.context.request.async;


import javax.servlet.AsyncEvent;

import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.test.MockAsyncContext;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;

/**
 * A test fixture with a {@link StandardServletAsyncWebRequest}.
 *
 * @author Rossen Stoyanchev
 */
public class StandardServletAsyncWebRequestTests {

	private StandardServletAsyncWebRequest asyncRequest;

	private MockHttpServletRequest request;

	private MockHttpServletResponse response;

	@Before
	public void setup() {
		this.request = new MockHttpServletRequest();
		this.request.setAsyncSupported(true);
		this.response = new MockHttpServletResponse();
		this.asyncRequest = new StandardServletAsyncWebRequest(this.request, this.response);
		this.asyncRequest.setTimeout(44*1000L);
	}

	@Test
	public void isAsyncStarted() throws Exception {
		assertFalse(this.asyncRequest.isAsyncStarted());

		this.asyncRequest.startAsync();
		assertTrue(this.asyncRequest.isAsyncStarted());
	}

	@Test
	public void startAsync() throws Exception {
		this.asyncRequest.startAsync();

		MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext();

		assertNotNull(asyncContext);
		assertEquals("Timeout value not set", 44 * 1000, asyncContext.getTimeout());
		assertEquals(1, asyncContext.getListeners().size());
		assertSame(this.asyncRequest, asyncContext.getListeners().get(0));
	}

	@Test
	public void startAsyncMultipleTimes() throws Exception {
		this.asyncRequest.startAsync();
		this.asyncRequest.startAsync();
		this.asyncRequest.startAsync();
		this.asyncRequest.startAsync();	// idempotent

		MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext();

		assertNotNull(asyncContext);
		assertEquals(1, asyncContext.getListeners().size());
	}

	@Test
	public void startAsyncNotSupported() throws Exception {
		this.request.setAsyncSupported(false);
		try {
			this.asyncRequest.startAsync();
			fail("expected exception");
		}
		catch (IllegalStateException ex) {
			assertThat(ex.getMessage(), containsString("Async support must be enabled"));
		}
	}

	@Test
	public void startAsyncAfterCompleted() throws Exception {
		this.asyncRequest.onComplete(new AsyncEvent(null));
		try {
			this.asyncRequest.startAsync();
			fail("expected exception");
		}
		catch (IllegalStateException ex) {
			assertEquals("Async processing has already completed", ex.getMessage());
		}
	}

	@Test
	public void onTimeoutDefaultBehavior() throws Exception {
		this.asyncRequest.onTimeout(new AsyncEvent(null));
		assertEquals(200, this.response.getStatus());
	}

	@Test
	public void onTimeoutTimeoutHandler() throws Exception {
		Runnable timeoutHandler = mock(Runnable.class);

		this.asyncRequest.addTimeoutHandler(timeoutHandler);
		this.asyncRequest.onTimeout(new AsyncEvent(null));

		verify(timeoutHandler).run();
	}

	@Test(expected=IllegalStateException.class)
	public void setTimeoutDuringConcurrentHandling() {
		this.asyncRequest.startAsync();
		this.asyncRequest.setTimeout(25L);
	}

}
