package net.sf.expectit.interact;

/*
 * #%L
 * ExpectIt
 * %%
 * Copyright (C) 2014 - 2015 Alexey Gavrilov and contributors
 * %%
 * 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.
 * #L%
 */

import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static net.sf.expectit.TestUtils.SMALL_TIMEOUT;
import static net.sf.expectit.TestUtils.mockInputStream;
import static net.sf.expectit.matcher.Matchers.allOf;
import static net.sf.expectit.matcher.Matchers.anyOf;
import static net.sf.expectit.matcher.Matchers.contains;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import net.sf.expectit.Expect;
import net.sf.expectit.ExpectBuilder;
import net.sf.expectit.MockInputStream;
import net.sf.expectit.MultiResult;
import net.sf.expectit.Result;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

public class InteractTest {

    private Expect expect;

    @After
    public void cleanup() throws IOException {
        if (expect != null) {
            expect.close();
        }
    }

    @Test
    public void simpleTest() throws Exception {
        final MockInputStream input = mockInputStream("AaBbAaBbcdefF");
        expect = new ExpectBuilder()
                .withExceptionOnFailure()
                .withTimeout(SMALL_TIMEOUT, TimeUnit.MILLISECONDS)
                .withInputs(input.getStream())
                .build();
        input.waitUntilReady();
        final Action<Result> action1 = mock(Action.class);
        final Action<MultiResult> action2 = mock(Action.class);
        final Action<Result> action3 = mock(Action.class);
        final ArgumentCaptor<Result> result1 = ArgumentCaptor.forClass(Result.class);
        final ArgumentCaptor<MultiResult> result2 = ArgumentCaptor.forClass(MultiResult.class);
        final ArgumentCaptor<Result> result3 = ArgumentCaptor.forClass(Result.class);
        doNothing().when(action1).apply(result1.capture());
        doNothing().when(action2).apply(result2.capture());
        doNothing().when(action3).apply(result3.capture());
        final Result result = expect.interact()
                .when(contains("a")).then(action1)
                .when(allOf(contains("b"))).then(action2)
                .when(contains("XXX")).then(action3)
                .until(contains("ef"));
        assertEquals(result.getBefore(), "cd");
        assertEquals(result1.getValue().getBefore(), "A");
        assertEquals(result2.getValue().getBefore(), "AaB");
        verify(action1, Mockito.times(2)).apply(any(Result.class));
        verify(action2, Mockito.times(2)).apply(any(MultiResult.class));
        verifyNoMoreInteractions(action3);
    }

    @Test (timeout = 1000)
    public void negativeTest() throws Exception {
        final MockInputStream input = mockInputStream("abcd");
        expect = new ExpectBuilder()
                .withInfiniteTimeout()
                .withInputs(input.getStream())
                .build();
        input.waitUntilReady();
        final Action<Result> action1 = mock(Action.class);
        final Result result = expect
                .withTimeout(SMALL_TIMEOUT, TimeUnit.MILLISECONDS)
                .interact()
                .when(contains("X")).then(action1)
                .until(contains("Y"));
        assertFalse(result.isSuccessful());
        assertEquals(result.getInput(), "abcd");
        verifyNoMoreInteractions(action1);
    }

    @Test
    public void testException() throws Exception {
        expect = new ExpectBuilder()
                .withInputs(mock(InputStream.class))
                .build();
        try {
            expect.interactWith(1);
            Assert.fail();
        } catch (IllegalArgumentException ignore) {
        }
        try {
            expect.interactWith(-1);
            Assert.fail();
        } catch (IllegalArgumentException ignore) {
        }
        try {
            expect.interact().when(contains("X")).then(null);
            fail();
        } catch (NullPointerException ignore) {
        }
    }

    @Test
    public void testCornerCases() throws Exception {
        final MockInputStream input = mockInputStream("xyz");
        final MockInputStream input2 = mockInputStream("abcd");
        expect = new ExpectBuilder()
                .withTimeout(SMALL_TIMEOUT, TimeUnit.MILLISECONDS)
                .withExceptionOnFailure()
                .withInputs(input.getStream(), input2.getStream())
                .build();
        input.waitUntilReady();
        input2.waitUntilReady();
        final Result result = expect.interactWith(1).until(contains("ab"));
        assertTrue(result.isSuccessful());
        final Action<Result> action = mock(Action.class);
        final Result r = expect.interact().when(contains("y")).then(action).until(contains("x"));
        assertTrue(r.isSuccessful());
        verifyNoMoreInteractions(action);
    }

    @Test
    public void testRealWorld() throws Exception {
        final MockInputStream input = mockInputStream("password");
        expect = new ExpectBuilder()
                .withTimeout(SMALL_TIMEOUT, TimeUnit.MILLISECONDS)
                .withExceptionOnFailure()
                .withInputs(input.getStream())
                .build();
        input.waitUntilReady();
        final Action<Result> action1 = new Action<Result>() {
            @Override
            public void apply(final Result result) {
                try {
                    input.push("exit");
                } catch (final InterruptedException e) {
                    throw new IllegalStateException(e);
                }
            }
        };
        final Action<Result> action2 = new Action<Result>() {
            @Override
            public void apply(final Result result) {
                try {
                    input.push("#");
                } catch (final InterruptedException e) {
                    throw new IllegalStateException(e);
                }
            }
        };
        expect.interact()
                .when(contains("#")).then(action1)
                .when(contains("assword")).then(action2)
                .until(contains("exit"));
    }
}

@@ -3660,17 +3660,17 @@
 meout = 
-1
+5
 000)%0A   

Increase test timeout