/*
 * Created on 14-Apr-2003
 */
package test.mockobjects.dynamic;

import com.mockobjects.dynamic.*;
import com.mockobjects.dynamic.C;
import com.mockobjects.dynamic.CallSignature;
import com.mockobjects.dynamic.CallSequence;
import com.mockobjects.dynamic.Callable;
import com.mockobjects.dynamic.CallOnceExpectation;
import com.mockobjects.dynamic.Mock;
import com.mockobjects.dynamic.ReturnStub;

import com.mockobjects.util.AssertMo;

import junit.framework.AssertionFailedError;
import junit.framework.TestCase;


public class CallSequenceTest extends TestCase {
	final String METHOD_A_NAME = "methodA";
	final String METHOD_A_RESULT = "resultA";
	final String METHOD_B_NAME = "methodB";
	final String METHOD_B_RESULT = "resultB";
	final Throwable METHOD_A_EXCEPTION = new DummyThrowable("Configured test throwable");
	final String[] METHOD_A_ARGS = new String[] { "a1", "a2" };
	final ConstraintMatcher METHOD_A_CONSTRAINTS = C.args(C.eq("a1"), C.eq("a2"));
	final String[] METHOD_B_ARGS = new String[] { "b1", "b2" };
	private CallSequence callSequence = new CallSequence();
	private Mock unusedMock = null;
	private MockCallable methodA = new MockCallable();
	private MockCallable methodB = new MockCallable();
	private MockCallable mockCallable = new MockCallable();

	public CallSequenceTest(String name) {
		super(name);
	}

	public void testCallableThrowableThrown() throws Throwable {
		final Throwable throwable = new DummyThrowable();

		mockCallable.setupMatchesReturn(true);
		mockCallable.setupCallThrow(throwable);

		callSequence.addExpect(mockCallable);

		try {
			callSequence.call(unusedMock, "hello", new String[0]);
		} catch (Throwable ex) {
			assertSame("exception is caught by mock", throwable, ex);
		}
	}

	public void testCallFailsOnEmptyList() throws Throwable {
		try {
			callSequence.call(unusedMock, "missingMethod", new Object[0]);
		} catch (AssertionFailedError ex) {
			AssertMo.assertIncludes("reports empty set in error message", "no methods", ex.getMessage());

			return;
		}

		fail("Should fail for a missing item");
	}

	public void testCallFailsWithTooManyCalls() throws Throwable {
		mockCallable.setupMatchesReturn(true);
		mockCallable.setupCallReturn(METHOD_A_RESULT);
		callSequence.addExpect(mockCallable);
		callSequence.call(unusedMock, "willdefinitelyMatch", new Object[0]);

		try {
			callSequence.call(unusedMock, "oneMethodTooMany", new Object[0]);
		} catch (AssertionFailedError ex) {
			AssertMo.assertIncludes("reports one method call too many", "too many", ex.getMessage());

			return;
		}

		fail("Should fail for calling too many times");
	}

	public void testCallPassedToContainedElements() throws Throwable {
		methodA.setExpectedMatches(METHOD_A_NAME, METHOD_A_ARGS);
		methodA.setupMatchesReturn(true);
		methodA.setExpectedCall(unusedMock, METHOD_A_NAME, METHOD_A_ARGS);
		methodA.setupCallReturn(METHOD_A_RESULT);

		methodB.setExpectedCallCount(0);

		callSequence.addExpect(methodA);
		callSequence.addExpect(methodB);

		assertSame("expected result from method A", METHOD_A_RESULT, callSequence.call(unusedMock, METHOD_A_NAME, METHOD_A_ARGS));

		methodA.verifyExpectations();
		methodB.verifyExpectations();
	}

	public void testCallPassedToContainedElementsOtherOrderShouldFail()
		throws Throwable {
		methodA.setupMatchesReturn(false);

		methodA.setupGetDescription("***methodA-description****");
		methodB.setupGetDescription("***methodB-description****");

		callSequence.addExpect(methodA);
		callSequence.addExpect(methodB);

		try {
			assertSame("expected result from method B", METHOD_B_RESULT, callSequence.call(unusedMock, METHOD_B_NAME, METHOD_B_ARGS));
		} catch (AssertionFailedError ex) {
			String message = ex.getMessage();
			AssertMo.assertIncludes("Should have expected error message", "Unexpected call: methodB", message);
			AssertMo.assertIncludes("Should have arguments in error message (1)", METHOD_B_ARGS[0], message);
			AssertMo.assertIncludes("Should have arguments in error message (2)", METHOD_B_ARGS[1], message);
			AssertMo.assertIncludes("Should have Index pointer next to correct item", methodA.getDescription() + " <<<", message);

			AssertMo.assertIncludes("shows set contents (A)", methodA.getDescription(), message);
			AssertMo.assertIncludes("shows set contents (B)", methodB.getDescription(), message);
			AssertMo.assertTrue("Method A should be before method B", message.indexOf(methodA.getDescription()) < message.indexOf(methodB.getDescription()));

			return;
		}

		fail("Should fail due to wrong order");
	}

	public void testConfiguredResultReturned() throws Throwable {
		final String result = "result";

		mockCallable.setupCallReturn(result);
		mockCallable.setupMatchesReturn(true);

		callSequence.addExpect(mockCallable);

		assertSame("result is returned by mock", result, callSequence.call(unusedMock, "method", new Object[0]));
	}

	public void testEmptySetVerifies() throws Exception {
		callSequence.verify();
	}

	public void testFailureIfNoElementMatches() throws Throwable {
		final String methodCName = "methodC";
		final String[] methodCArgs = { "c1", "c2" };

		methodA.setExpectedMatches(methodCName, methodCArgs);
		methodA.setupMatchesReturn(false);
		methodA.setExpectedCallCount(0);
		methodA.setupGetDescription("***methodA-description****");
		methodB.setExpectedCall(unusedMock, methodCName, methodCArgs);
		methodB.setupMatchesReturn(false);
		methodB.setExpectedCallCount(0);
		methodB.setupGetDescription("***methodB-description****");

		callSequence.addExpect(methodA);
		callSequence.addExpect(methodB);

		try {
			callSequence.call(unusedMock, methodCName, methodCArgs);
		} catch (AssertionFailedError ex) {
			AssertMo.assertIncludes("method name is in error message", "methodC", ex.getMessage());
			AssertMo.assertIncludes("argument is in error message (1)", methodCArgs[0], ex.getMessage());
			AssertMo.assertIncludes("argument is in error message (2)", methodCArgs[1], ex.getMessage());

			AssertMo.assertIncludes("shows set contents (A)", methodA.getDescription(), ex.getMessage());
			AssertMo.assertIncludes("shows set contents (B)", methodB.getDescription(), ex.getMessage());

			return;
		}

		fail("Should fail for a missing item");
	}

	public void testVerifiesIfAllContainedElementsVerify()
		throws Throwable {
		methodA.setExpectedVerifyCalls(1);
		methodB.setExpectedVerifyCalls(1);

		callSequence.addExpect(methodA);
		callSequence.addExpect(methodB);
		callSequence.verify();

		methodA.verifyExpectations();
		methodB.verifyExpectations();
	}

	public void testVerifyFailsIfContainedElementDoesNotVerify()
		throws Exception {
		methodA.setExpectedVerifyCalls(1);
		methodA.setupVerifyThrow(new AssertionFailedError("verify failed"));
		callSequence.addExpect(methodA);

		try {
			callSequence.verify();
		} catch (AssertionFailedError ex) {
			methodA.verifyExpectations();

			return;
		}

		fail("Should have got a failure for contained element failing");
	}
	
	public void testExpectOverridesMatch() throws Throwable {
		
		Callable methodASignature = new CallSignature(METHOD_A_NAME,METHOD_A_CONSTRAINTS, new ReturnStub("result1"));
		Callable anotherMethodASignature = new CallSignature(METHOD_A_NAME,METHOD_A_CONSTRAINTS, new ReturnStub("result2"));
	
		callSequence.addMatch(methodASignature);
		callSequence.addExpect(new CallOnceExpectation(anotherMethodASignature));

		assertSame("expected result from method B, as  expect has precendence over match", "result2",
		callSequence.call(unusedMock, METHOD_A_NAME, METHOD_A_ARGS));
	}
}
