/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */
package org.mockitousage.verification;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.verification.NoInteractionsWanted;
import org.mockito.exceptions.verification.VerificationInOrderFailure;
import org.mockitousage.IMethods;
import org.mockitoutil.TestBase;

public class VerificationInOrderMixedWithOrdinaryVerificationTest extends TestBase {

    private IMethods mockOne;
    private IMethods mockTwo;
    private IMethods mockThree;
    private InOrder inOrder;

    @Before
    public void setUp() {
        mockOne = mock(IMethods.class);
        mockTwo = mock(IMethods.class);
        mockThree = mock(IMethods.class);

        mockOne.simpleMethod(1);
        mockOne.simpleMethod(1);
        mockTwo.simpleMethod(2);
        mockThree.simpleMethod(3);
        mockThree.simpleMethod(4);

        inOrder = inOrder(mockOne, mockThree);
    }

    @Test
    public void shouldMixVerificationInOrderAndOrdinaryVerification() {
        inOrder.verify(mockOne, atLeastOnce()).simpleMethod(1);
        inOrder.verify(mockThree).simpleMethod(3);
        inOrder.verify(mockThree).simpleMethod(4);
        verify(mockTwo).simpleMethod(2);

        verifyNoMoreInteractions(mockOne, mockTwo, mockThree);
    }

    @Test
    public void shouldAllowOrdinarilyVerifyingMockPassedToInOrderObject() {
        inOrder.verify(mockOne, atLeastOnce()).simpleMethod(1);

        verify(mockThree).simpleMethod(3);
        verify(mockThree).simpleMethod(4);
        verify(mockTwo).simpleMethod(2);

        verifyNoMoreInteractions(mockOne, mockTwo, mockThree);
    }

    @Test
    public void shouldAllowRedundantVerifications() {
        verify(mockOne, atLeastOnce()).simpleMethod(1);
        verify(mockTwo).simpleMethod(2);
        verify(mockThree).simpleMethod(3);
        verify(mockThree).simpleMethod(4);

        inOrder.verify(mockOne, atLeastOnce()).simpleMethod(1);
        inOrder.verify(mockThree).simpleMethod(3);
        inOrder.verify(mockThree).simpleMethod(4);

        verifyNoMoreInteractions(mockOne, mockTwo, mockThree);
    }

    @Test
    public void shouldFailOnNoMoreInteractions() {
        inOrder.verify(mockOne, atLeastOnce()).simpleMethod(1);
        inOrder.verify(mockThree).simpleMethod(3);
        inOrder.verify(mockThree).simpleMethod(4);

        try {
            verifyNoMoreInteractions(mockOne, mockTwo, mockThree);
            fail();
        } catch (NoInteractionsWanted e) {
        }
    }

    @Test
    public void shouldFailOnNoMoreInteractionsOnMockVerifiedInOrder() {
        inOrder.verify(mockOne, atLeastOnce()).simpleMethod(1);
        inOrder.verify(mockThree).simpleMethod(3);
        verify(mockTwo).simpleMethod(2);

        try {
            verifyNoMoreInteractions(mockOne, mockTwo, mockThree);
            fail();
        } catch (NoInteractionsWanted e) {
        }
    }

    @Test
    public void shouldAllowOneMethodVerifiedInOrder() {
        verify(mockTwo).simpleMethod(2);
        verify(mockOne, atLeastOnce()).simpleMethod(1);

        inOrder.verify(mockOne, atLeastOnce()).simpleMethod(1);
    }

    @Test
    public void shouldFailOnLastInvocationTooEarly() {
        inOrder.verify(mockThree).simpleMethod(4);

        verify(mockThree).simpleMethod(4);
        verify(mockTwo).simpleMethod(2);

        try {
            inOrder.verify(mockOne, atLeastOnce()).simpleMethod(1);
            fail();
        } catch (VerificationInOrderFailure e) {
        }
    }

    @Test
    public void shouldScreamWhenUnfamiliarMockPassedToInOrderObject() {
        assertThatThrownBy(
                        () -> {
                            inOrder.verify(mockTwo, atLeastOnce()).simpleMethod(1);
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContainingAll(
                        "InOrder can only verify mocks that were passed in during creation of InOrder.",
                        "For example:",
                        "    InOrder inOrder = inOrder(mockOne);",
                        "    inOrder.verify(mockOne).doStuff();");
    }

    @Test
    public void shouldUseEqualsToVerifyMethodArguments() {
        mockOne = mock(IMethods.class);

        String textOne = "test";
        String textTwo = new String(textOne);

        assertEquals(textOne, textTwo);
        assertNotSame(textOne, textTwo);

        mockOne.simpleMethod(textOne);
        mockOne.simpleMethod(textTwo);

        verify(mockOne, times(2)).simpleMethod(textOne);

        inOrder = inOrder(mockOne);
        inOrder.verify(mockOne, times(2)).simpleMethod(textOne);
    }

    @Test
    public void shouldUseEqualsToVerifyMethodVarargs() {
        mockOne = mock(IMethods.class);

        String textOne = "test";
        String textTwo = new String(textOne);

        assertEquals(textOne, textTwo);
        assertNotSame(textOne, textTwo);

        mockOne.varargsObject(1, textOne, textOne);
        mockOne.varargsObject(1, textTwo, textTwo);

        verify(mockOne, times(2)).varargsObject(1, textOne, textOne);

        inOrder = inOrder(mockOne);
        inOrder.verify(mockOne, times(2)).varargsObject(1, textOne, textOne);
    }
}
