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

import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertNull;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.withSettings;

import org.assertj.core.api.Assertions;
import org.assertj.core.api.ThrowableAssert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.exceptions.misusing.PotentialStubbingProblem;
import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
import org.mockito.exceptions.verification.NoInteractionsWanted;
import org.mockito.quality.Strictness;
import org.mockitousage.IMethods;
import org.mockitoutil.TestBase;

// TODO 792 also move other Strictness tests to this package (unless they already have good package)
public class StrictnessPerMockTest {

    MockitoSession mockito;
    @Mock IMethods strictStubsMock;
    IMethods lenientMock;

    @Before
    public void before() {
        mockito =
                Mockito.mockitoSession()
                        .initMocks(this)
                        .strictness(Strictness.STRICT_STUBS)
                        .startMocking();
        assertNull(lenientMock);
        lenientMock = mock(IMethods.class, withSettings().lenient());
    }

    @Test
    public void knows_if_mock_is_lenient() {
        assertTrue(mockingDetails(lenientMock).getMockCreationSettings().isLenient());
        assertFalse(mockingDetails(strictStubsMock).getMockCreationSettings().isLenient());
    }

    @Test
    public void potential_stubbing_problem() {
        // when
        given(lenientMock.simpleMethod(100)).willReturn("100");
        given(strictStubsMock.simpleMethod(100)).willReturn("100");

        // then on lenient mock (created by hand), we can call the stubbed method with different
        // arg:
        lenientMock.simpleMethod(200);

        // and on strict stub mock (created by session), we cannot call stubbed method with
        // different arg:
        Assertions.assertThatThrownBy(
                        new ThrowableAssert.ThrowingCallable() {
                            public void call() throws Throwable {
                                ProductionCode.simpleMethod(strictStubsMock, 200);
                            }
                        })
                .isInstanceOf(PotentialStubbingProblem.class);
    }

    @Test
    public void unnecessary_stubbing() {
        // when
        given(lenientMock.simpleMethod(100)).willReturn("100");
        given(strictStubsMock.simpleMethod(100)).willReturn("100");

        // then unnecessary stubbing flags method only on the strict stub mock:
        Assertions.assertThatThrownBy(
                        new ThrowableAssert.ThrowingCallable() {
                            @Override
                            public void call() throws Throwable {
                                mockito.finishMocking();
                            }
                        })
                .isInstanceOf(UnnecessaryStubbingException.class)
                .hasMessageContaining("1. -> ")
                // good enough to prove that we're flagging just one unnecessary stubbing:
                // TODO 792: let's make UnnecessaryStubbingException exception contain the
                // Invocation instance
                // so that we can write clean assertion rather than depending on string
                .isNot(TestBase.hasMessageContaining("2. ->"));
    }

    @Test
    public void verify_no_more_invocations() {
        // when
        given(lenientMock.simpleMethod(100)).willReturn("100");
        given(strictStubsMock.simpleMethod(100)).willReturn("100");

        // and:
        strictStubsMock.simpleMethod(100);
        lenientMock.simpleMethod(100);

        // then 'verifyNoMoreInteractions' ignores strict stub (implicitly verified) but flags the
        // lenient mock
        Assertions.assertThatThrownBy(
                        new ThrowableAssert.ThrowingCallable() {
                            @Override
                            public void call() throws Throwable {
                                verifyNoMoreInteractions(strictStubsMock, lenientMock);
                            }
                        })
                .isInstanceOf(NoInteractionsWanted.class)
                .hasMessageContaining("But found this interaction on mock 'iMethods'")
                // TODO 792: let's make NoInteractionsWanted exception contain the Invocation
                // instances
                // so that we can write clean assertion rather than depending on string
                .hasMessageContaining("Actually, above is the only interaction with this mock");
    }

    @After
    public void after() {
        mockito.finishMocking();
    }
}
