//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.
//

package com.cloud.network.bigswitch;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.util.Collections;
import java.util.UUID;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.http.HttpStatus;
import org.junit.Before;
import org.junit.Test;

import com.google.gson.reflect.TypeToken;

public class BigSwitchApiTest {
    BigSwitchBcfApi _api;
    HttpClient _client = mock(HttpClient.class);
    HttpMethod _method;

    @Before
    public void setUp() {
        HttpClientParams hmp = mock(HttpClientParams.class);
        when(_client.getParams()).thenReturn(hmp);
        _api = new BigSwitchBcfApi(){
            @Override
            protected HttpClient createHttpClient() {
                return _client;
            }

            @Override
            protected HttpMethod createMethod(String type, String uri, int port) {
                return _method;
            }
        };
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("myname");
        _api.setControllerPassword("mypassword");
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectWithoutHostname() throws BigSwitchBcfApiException {
        _api.setControllerAddress(null);
        _api.setControllerUsername("myname");
        _api.setControllerPassword("mypassword");
        _api.executeUpdateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectWithEmptyHostname() throws BigSwitchBcfApiException {
        _api.setControllerAddress("");
        _api.setControllerUsername("myname");
        _api.setControllerPassword("mypassword");
        _api.executeUpdateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectWithoutUsername() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername(null);
        _api.setControllerPassword("mypassword");
        _api.executeUpdateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectWithEmptyUsername() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("");
        _api.setControllerPassword("mypassword");
        _api.executeUpdateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectWithoutPassword() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("myname");
        _api.setControllerPassword(null);
        _api.executeUpdateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectWithEmptyPassword() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("myname");
        _api.setControllerPassword("");
        _api.executeUpdateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectWithoutHostname() throws BigSwitchBcfApiException {
        _api.setControllerAddress(null);
        _api.setControllerUsername("myname");
        _api.setControllerPassword("mypassword");
        _api.executeCreateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectWithEmptyHostname() throws BigSwitchBcfApiException {
        _api.setControllerAddress("");
        _api.setControllerUsername("myname");
        _api.setControllerPassword("mypassword");
        _api.executeCreateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectWithoutUsername() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername(null);
        _api.setControllerPassword("mypassword");
        _api.executeCreateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectWithEmptyUsername() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("");
        _api.setControllerPassword("mypassword");
        _api.executeCreateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectWithoutPassword() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("myname");
        _api.setControllerPassword(null);
        _api.executeCreateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectWithEmptyPassword() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("myname");
        _api.setControllerPassword("");
        _api.executeCreateObject(new String(), "/", Collections.<String, String> emptyMap());
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteDeleteObjectWithoutHostname() throws BigSwitchBcfApiException {
        _api.setControllerAddress(null);
        _api.setControllerUsername("myname");
        _api.setControllerPassword("mypassword");
        _api.executeDeleteObject("/");
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteDeleteObjectWithEmptyHostname() throws BigSwitchBcfApiException {
        _api.setControllerAddress("");
        _api.setControllerUsername("myname");
        _api.setControllerPassword("mypassword");
        _api.executeDeleteObject("/");
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteDeleteObjectWithoutUsername() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername(null);
        _api.setControllerPassword("mypassword");
        _api.executeDeleteObject("/");
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteDeleteObjectWithEmptyUsername() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("");
        _api.setControllerPassword("mypassword");
        _api.executeDeleteObject("/");
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteDeleteObjectWithoutPassword() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("myname");
        _api.setControllerPassword(null);
        _api.executeDeleteObject("/");
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteDeleteObjectWithEmptyPassword() throws BigSwitchBcfApiException {
        _api.setControllerAddress("10.10.0.10");
        _api.setControllerUsername("myname");
        _api.setControllerPassword("");
        _api.executeDeleteObject("/");
    }

    @Test
    public void executeMethodTestOK() throws BigSwitchBcfApiException, HttpException, IOException {
        GetMethod gm = mock(GetMethod.class);
        when(gm.getStatusCode()).thenReturn(HttpStatus.SC_OK);
        _api.executeMethod(gm);
        verify(gm, times(1)).getStatusCode();
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void executeMethodTestUnauthorized() throws BigSwitchBcfApiException, IOException {
        GetMethod gm = mock(GetMethod.class);
        when(_client.executeMethod((HttpMethod)any())).thenThrow(new HttpException());
        when(gm.getStatusCode()).thenReturn(HttpStatus.SC_UNAUTHORIZED);
        _api.executeMethod(gm);
    }

    @Test
    public void testExecuteCreateObjectOK() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PostMethod.class);
        when(_method.getResponseHeader("X-BSN-BVS-HASH-MATCH")).thenReturn(new Header("X-BSN-BVS-HASH-MATCH", UUID.randomUUID().toString()));
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
        String hash = _api.executeCreateObject(network, "/", Collections.<String, String> emptyMap());
        verify(_method, times(1)).releaseConnection();
        verify(_client, times(1)).executeMethod(_method);assertNotEquals(hash, "");
        assertNotEquals(hash, BigSwitchBcfApi.HASH_CONFLICT);
        assertNotEquals(hash, BigSwitchBcfApi.HASH_IGNORE);
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectConflict() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PostMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_CONFLICT);
        _api.executeCreateObject(network, "/", Collections.<String, String> emptyMap());
    }

    @Test
    public void testExecuteCreateObjectSlave() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PostMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_SEE_OTHER);
        String hash = _api.executeCreateObject(network, "/", Collections.<String, String> emptyMap());
        assertEquals(hash, BigSwitchBcfApi.HASH_IGNORE);
        assertEquals(_api.getControllerData().isMaster(), false);
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectFailure() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PostMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
        Header header = mock(Header.class);
        when(header.getValue()).thenReturn("text/html");
        when(_method.getResponseHeader("Content-type")).thenReturn(header);
        when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later.");
        when(_method.isRequestSent()).thenReturn(true);
        try {
            _api.executeCreateObject(network, "/", Collections.<String, String> emptyMap());
        } finally {
            verify(_method, times(1)).releaseConnection();
        }
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteCreateObjectException() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        when(_client.executeMethod((HttpMethod)any())).thenThrow(new HttpException());
        _method = mock(PostMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
        Header header = mock(Header.class);
        when(header.getValue()).thenReturn("text/html");
        when(_method.getResponseHeader("Content-type")).thenReturn(header);
        when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later.");
        try {
            _api.executeCreateObject(network, "/", Collections.<String, String> emptyMap());
        } finally {
            verify(_method, times(1)).releaseConnection();
        }
    }

    @Test
    public void testExecuteUpdateObjectOK() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PutMethod.class);
        when(_method.getResponseHeader("X-BSN-BVS-HASH-MATCH")).thenReturn(new Header("X-BSN-BVS-HASH-MATCH", UUID.randomUUID().toString()));
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
        String hash = _api.executeUpdateObject(network, "/", Collections.<String, String> emptyMap());
        verify(_method, times(1)).releaseConnection();
        verify(_client, times(1)).executeMethod(_method);
        assertNotEquals(hash, "");
        assertNotEquals(hash, BigSwitchBcfApi.HASH_CONFLICT);
        assertNotEquals(hash, BigSwitchBcfApi.HASH_IGNORE);
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectConflict() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PutMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_CONFLICT);
        _api.executeUpdateObject(network, "/", Collections.<String, String> emptyMap());
    }

    @Test
    public void testExecuteUpdateObjectSlave() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PutMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_SEE_OTHER);
        String hash = _api.executeUpdateObject(network, "/", Collections.<String, String> emptyMap());
        assertEquals(hash, BigSwitchBcfApi.HASH_IGNORE);
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectFailure() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PutMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
        Header header = mock(Header.class);
        when(header.getValue()).thenReturn("text/html");
        when(_method.getResponseHeader("Content-type")).thenReturn(header);
        when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later.");
        when(_method.isRequestSent()).thenReturn(true);
        try {
            _api.executeUpdateObject(network, "/", Collections.<String, String> emptyMap());
        } finally {
            verify(_method, times(1)).releaseConnection();
        }
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteUpdateObjectException() throws BigSwitchBcfApiException, IOException {
        NetworkData network = new NetworkData();
        _method = mock(PutMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
        when(_client.executeMethod((HttpMethod)any())).thenThrow(new IOException());
        try {
            _api.executeUpdateObject(network, "/", Collections.<String, String> emptyMap());
        } finally {
            verify(_method, times(1)).releaseConnection();
        }
    }

    @Test
    public void testExecuteDeleteObject() throws BigSwitchBcfApiException, IOException {
        _method = mock(DeleteMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
        _api.executeDeleteObject("/");
        verify(_method, times(1)).releaseConnection();
        verify(_client, times(1)).executeMethod(_method);
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteDeleteObjectFailure() throws BigSwitchBcfApiException, IOException {
        _method = mock(DeleteMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
        Header header = mock(Header.class);
        when(header.getValue()).thenReturn("text/html");
        when(_method.getResponseHeader("Content-type")).thenReturn(header);
        when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later.");
        when(_method.isRequestSent()).thenReturn(true);
        try {
            _api.executeDeleteObject("/");
        } finally {
            verify(_method, times(1)).releaseConnection();
        }
    }

    @Test(expected = BigSwitchBcfApiException.class)
    public void testExecuteDeleteObjectException() throws BigSwitchBcfApiException, IOException {
        _method = mock(DeleteMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
        when(_client.executeMethod((HttpMethod)any())).thenThrow(new HttpException());
        try {
            _api.executeDeleteObject("/");
        } finally {
            verify(_method, times(1)).releaseConnection();
        }
    }

    @Test
    public void testExecuteRetrieveControllerMasterStatus() throws BigSwitchBcfApiException, IOException {
        _method = mock(GetMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
        when(((HttpMethodBase)_method).getResponseBodyAsString(2048)).thenReturn("{'healthy': true, 'topologySyncRequested': false}");
        _api.executeRetrieveObject(new TypeToken<ControlClusterStatus>() {
        }.getType(), "/", null);
        verify(_method, times(1)).releaseConnection();
        verify(_client, times(1)).executeMethod(_method);
        assertEquals(_api.getControllerData().isMaster(), true);
    }

    @Test
    public void testExecuteRetrieveControllerMasterStatusWithTopoConflict() throws BigSwitchBcfApiException, IOException {
        _method = mock(GetMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_CONFLICT);
        when(((HttpMethodBase)_method).getResponseBodyAsString(2048)).thenReturn("{'healthy': true, 'topologySyncRequested': true}");
        _api.executeRetrieveObject(new TypeToken<ControlClusterStatus>() {
        }.getType(), "/", null);
        verify(_method, times(1)).releaseConnection();
        verify(_client, times(1)).executeMethod(_method);
        assertEquals(_api.getControllerData().isMaster(), true);
    }

    @Test
    public void testExecuteRetrieveControllerSlaveStatus() throws BigSwitchBcfApiException, IOException {
        _method = mock(GetMethod.class);
        when(_method.getStatusCode()).thenReturn(HttpStatus.SC_SEE_OTHER);
        when(((HttpMethodBase)_method).getResponseBodyAsString(1024)).thenReturn("{'healthy': true, 'topologySyncRequested': false}");
        _api.executeRetrieveObject(new TypeToken<ControlClusterStatus>() {
        }.getType(), "/", null);
        verify(_method, times(1)).releaseConnection();
        verify(_client, times(1)).executeMethod(_method);
        assertEquals(_api.getControllerData().isMaster(), false);
    }
}
