package com.atlassian.stash.internal.cluster;

import org.junit.Test;

import static com.atlassian.stash.internal.cluster.ClusterJoinCheckAction.CONNECT;
import static com.atlassian.stash.internal.cluster.ClusterJoinCheckAction.PASSIVATE_ANY_NODE;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class RequirementsClusterJoinCheckTest extends AbstractClusterJoinCheckTest {

    @Test
    public void testCheckFailsWhenAcceptingSideLacksRequirement() throws Exception {
        ClusterJoinRequirement requirementA = createRequirement("requirementA", 1);
        ClusterJoinRequirement requirementB = createRequirement("requirementB", "yo");

        runTest(new RequirementsClusterJoinCheck(requirementA),
                new RequirementsClusterJoinCheck(requirementA, requirementB), false);
    }

    @Test
    public void testCheckFailsWhenConnectingSideLacksRequirement() throws Exception {
        ClusterJoinRequirement requirementA = createRequirement("requirementA", 1);
        ClusterJoinRequirement requirementB = createRequirement("requirementB", "yo");

        runTest(new RequirementsClusterJoinCheck(requirementA, requirementB),
                new RequirementsClusterJoinCheck(requirementB), false);
    }

    @Test
    public void testCheckFailsWhenRequirementsDoNotMatch() throws Exception {
        RequirementsClusterJoinCheck clientCheck = createCheck("requirementA", 1);
        RequirementsClusterJoinCheck serverCheck = createCheck("requirementA", 2);

        runTest(clientCheck, serverCheck, false);
    }

    @Test
    public void testCheckSucceedsWithMatchingRequirements() throws Exception {
        RequirementsClusterJoinCheck check = createCheck("requirementA", 1);

        runTest(check, check, true);
    }

    @Test
    public void testCheckWithNulls() throws Exception {
        RequirementsClusterJoinCheck nullCheck = createCheck("requirementA", null);
        RequirementsClusterJoinCheck setCheck = createCheck("requirementA", "set");

        runTest(nullCheck, setCheck, false);  //null on connecting side
        runTest(setCheck, nullCheck, false);  //null on accepting side
        runTest(nullCheck, nullCheck, true); //null on both sides
    }

    @Test
    public void testOnUnknown() {
        ClusterJoinRequirement requirement = createRequirement("requirementA", 1);
        RequirementsClusterJoinCheck check = new RequirementsClusterJoinCheck(requirement);

        ClusterJoinCheckResult result = check.onUnknown(connectRequest());
        assertEquals(PASSIVATE_ANY_NODE, result.getAction());
    }

    private static RequirementsClusterJoinCheck createCheck(String name, Object value) {
        return new RequirementsClusterJoinCheck(createRequirement(name, value));
    }

    private static ClusterJoinRequirement createRequirement(String name, Object value) {
        ClusterJoinRequirement requirement = mock(ClusterJoinRequirement.class);
        when(requirement.getName()).thenReturn(name);
        when(requirement.getValue()).thenReturn(value == null ? null : String.valueOf(value));

        return requirement;
    }

    private void runTest(RequirementsClusterJoinCheck connectingCheck,
                         RequirementsClusterJoinCheck acceptingCheck,
                         boolean connect) throws Exception {
        CheckResult result = executeJoinCheck(connectingCheck, acceptingCheck);

        assertEquals(connect ? CONNECT : PASSIVATE_ANY_NODE, result.acceptResult.getAction());
        // accepting side validates requirements; connecting side always votes to connect
        assertEquals(CONNECT, result.connectResult.getAction());
    }
}