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

import static org.mockito.BDDMockito.given;
import static org.mockitoutil.ConcurrentTesting.concurrently;
import static org.mockitoutil.JUnitResultAssert.assertThat;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.StateMaster;
import org.mockito.exceptions.misusing.PotentialStubbingProblem;
import org.mockito.exceptions.misusing.UnfinishedMockingSessionException;
import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
import org.mockito.quality.Strictness;
import org.mockitousage.IMethods;
import org.mockitousage.strictness.ProductionCode;

public class StrictStubbingEndToEndTest {

    JUnitCore junit = new JUnitCore();

    @After public void after() {
        new StateMaster().clearMockitoListeners();
    }

    @Test public void finish_mocking_exception_does_not_hide_the_exception_from_test() {
        Result result = junit.run(UnnecessaryStubbing.class);
        assertThat(result)
                //both exceptions are reported to JUnit:
                .fails("unnecessary_stubbing", IllegalStateException.class)
                .fails("unnecessary_stubbing", UnnecessaryStubbingException.class);
    }

    @Test public void does_not_report_unused_stubbing_if_mismatch_reported() {
        Result result = junit.run(ReportMismatchButNotUnusedStubbing.class);
        assertThat(result).fails(1, PotentialStubbingProblem.class);
    }

    @Test public void strict_stubbing_does_not_leak_to_other_tests() {
        Result result = junit.run(LenientStrictness1.class, StrictStubsPassing.class, LenientStrictness2.class);
        //all tests pass, lenient test cases contain incorrect stubbing
        assertThat(result).succeeds(5);
    }

    @Test public void detects_unfinished_session() {
        Result result = junit.run(UnfinishedMocking.class);
        assertThat(result)
            .fails(UnfinishedMockingSessionException.class, "\n" +
                "Unfinished mocking session detected.\n" +
                "Previous MockitoSession was not concluded with 'finishMocking()'.\n" +
                "For examples of correct usage see javadoc for MockitoSession class.");
    }

    @Test public void concurrent_sessions_in_different_threads() throws Exception {
        final Map<Class, Result> results = new ConcurrentHashMap<Class, Result>();
        concurrently(new Runnable() {
                         public void run() {
                             results.put(StrictStubsPassing.class, junit.run(StrictStubsPassing.class));
                         }
                     }, new Runnable() {
                         public void run() {
                             results.put(ReportMismatchButNotUnusedStubbing.class, junit.run(ReportMismatchButNotUnusedStubbing.class));
                         }
                     }
        );

        assertThat(results.get(StrictStubsPassing.class)).succeeds(1);
        assertThat(results.get(ReportMismatchButNotUnusedStubbing.class)).fails(1);
    }

    public static class UnnecessaryStubbing {
        @Mock IMethods mock;
        MockitoSession mockito = Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking();

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

        @Test public void unnecessary_stubbing() {
            given(mock.simpleMethod("1")).willReturn("one");
            throw new IllegalStateException();
        }
    }

    public static class ReportMismatchButNotUnusedStubbing {
        @Mock IMethods mock;
        MockitoSession mockito = Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking();

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

        @Test public void mismatch() {
            given(mock.simpleMethod(1)).willReturn("");
            ProductionCode.simpleMethod(mock, 2);
        }
    }

    public static class StrictStubsPassing {
        @Mock IMethods mock;
        MockitoSession mockito = Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking();

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

        @Test public void used() {
            given(mock.simpleMethod(1)).willReturn("");
            mock.simpleMethod(1);
        }
    }

    public static class LenientStrictness1 {
        @Mock IMethods mock = Mockito.mock(IMethods.class);

        @Test public void unused() {
            given(mock.simpleMethod(1)).willReturn("");
        }

        @Test public void mismatch() {
            given(mock.simpleMethod(2)).willReturn("");
            mock.simpleMethod(3);
        }
    }

    public static class LenientStrictness2 {
        @Mock IMethods mock = Mockito.mock(IMethods.class);

        @Test public void unused() {
            given(mock.simpleMethod(1)).willReturn("");
        }

        @Test public void mismatch() {
            given(mock.simpleMethod(2)).willReturn("");
            mock.simpleMethod(3);
        }
    }

    public static class UnfinishedMocking {
        @Mock IMethods mock;
        MockitoSession mockito = Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking();

        @Test public void unused() {
            given(mock.simpleMethod("1")).willReturn("one");
        }

        @Test public void unused2() {
            given(mock.simpleMethod("1")).willReturn("one");
        }
    }
}
