/**
 * 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 org.apache.hive.service.cli;

import java.util.List;

import junit.framework.Assert;

import org.apache.commons.lang.StringUtils;
import org.apache.hive.service.rpc.thrift.TStatus;
import org.apache.hive.service.rpc.thrift.TStatusCode;
import org.junit.Test;

public class TestHiveSQLException {

  /**
   * Tests the conversion from a regular exception to the TStatus object
   */
  @Test
  public void testExceptionToTStatus() {
    Exception ex1 = createException();
    ex1.initCause(createSimpleCause());
    TStatus status = HiveSQLException.toTStatus(ex1);

    Assert.assertEquals(TStatusCode.ERROR_STATUS, status.getStatusCode());
    Assert.assertEquals(ex1.getMessage(), status.getErrorMessage());
    Assert.assertEquals(HiveSQLException.toString(ex1), status.getInfoMessages());
  }

  /**
   * Tests the conversion from a HiveSQLException exception to the TStatus object
   */
  @Test
  public void testHiveSQLExceptionToTStatus() {
    String expectedMessage = "reason";
    String expectedSqlState = "sqlState";
    int expectedVendorCode = 10;

    Exception ex1 = new HiveSQLException(expectedMessage, expectedSqlState, expectedVendorCode, createSimpleCause());
    TStatus status = HiveSQLException.toTStatus(ex1);

    Assert.assertEquals(TStatusCode.ERROR_STATUS, status.getStatusCode());
    Assert.assertEquals(expectedSqlState, status.getSqlState());
    Assert.assertEquals(expectedMessage, status.getErrorMessage());
    Assert.assertEquals(HiveSQLException.toString(ex1), status.getInfoMessages());
  }

  /**
   * Tests the conversion between the exception text with the simple cause and the
   * Throwable object
   */
  @Test
  public void testExceptionMarshalling() throws Exception {
    Exception ex1 = createException();
    ex1.initCause(createSimpleCause());
    Throwable ex = HiveSQLException.toCause(HiveSQLException.toString(ex1));

    Assert.assertSame(RuntimeException.class, ex.getClass());
    Assert.assertEquals("exception1", ex.getMessage());
    Assert.assertSame(UnsupportedOperationException.class, ex.getCause().getClass());
    Assert.assertEquals("exception2", ex.getCause().getMessage());
  }

  /**
   * Tests the conversion between the exception text with nested cause and
   * the Throwable object
   */
  @Test
  public void testNestedException() {
    Exception ex1 = createException();
    ex1.initCause(createNestedCause());
    Throwable ex = HiveSQLException.toCause(HiveSQLException.toString(ex1));

    Assert.assertSame(RuntimeException.class, ex.getClass());
    Assert.assertEquals("exception1", ex.getMessage());

    Assert.assertSame(UnsupportedOperationException.class, ex.getCause().getClass());
    Assert.assertEquals("exception2", ex.getCause().getMessage());

    Assert.assertSame(Exception.class, ex.getCause().getCause().getClass());
    Assert.assertEquals("exception3", ex.getCause().getCause().getMessage());
  }

  /**
   * Tests the conversion of the exception with unknown source
   */
  @Test
  public void testExceptionWithUnknownSource() {
    Exception ex1 = createException();
    ex1.initCause(createSimpleCause());
    List<String> details = HiveSQLException.toString(ex1);

    // Simulate the unknown source
    String[] tokens = details.get(1).split(":");
    tokens[2] = null;
    tokens[3] = "-1";
    details.set(1, StringUtils.join(tokens, ":"));

    Throwable ex = HiveSQLException.toCause(details);

    Assert.assertSame(RuntimeException.class, ex.getClass());
    Assert.assertEquals("exception1", ex.getMessage());
    Assert.assertSame(UnsupportedOperationException.class, ex.getCause().getClass());
    Assert.assertEquals("exception2", ex.getCause().getMessage());
  }

  /**
   * Tests the conversion of the exception that the class type of one of the causes
   * doesn't exist. The stack trace text is generated on the server and passed to JDBC
   * client. It's possible that some cause types don't exist on the client and HiveSQLException
   * can't convert them and use RunTimeException instead.
   */
  @Test
  public void testExceptionWithMissingTypeOnClient() {
    Exception ex1 = new UnsupportedOperationException();
    ex1.initCause(createSimpleCause());
    List<String> details = HiveSQLException.toString(ex1);

    // Simulate an unknown type
    String[] tokens = details.get(0).split(":");
    tokens[0] = "*DummyException";
    details.set(0, StringUtils.join(tokens, ":"));

    Throwable ex = HiveSQLException.toCause(details);
    Assert.assertEquals(RuntimeException.class, ex.getClass());
  }

  /**
   * Tests the conversion of the exception from anonymous class
   */
  @Test
  public void testExceptionFromAnonymousClass() {
    Dummy d = new Dummy() {

      public void testExceptionConversion() {
        Exception ex1 = createException();
        ex1.initCause(createSimpleCause());
        Throwable ex = HiveSQLException.toCause(HiveSQLException.toString(ex1));

        Assert.assertSame(RuntimeException.class, ex.getClass());
        Assert.assertEquals("exception1", ex.getMessage());
        Assert.assertSame(UnsupportedOperationException.class, ex.getCause().getClass());
        Assert.assertEquals("exception2", ex.getCause().getMessage());
      }
    };

    d.testExceptionConversion();
  }

  interface Dummy {
    void testExceptionConversion();
  }

  private static Exception createException() {
    return new RuntimeException("exception1");
  }

  private static Exception createSimpleCause() {
    return new UnsupportedOperationException("exception2");
  }

  private static Exception createNestedCause() {
    return new UnsupportedOperationException("exception2", new Exception("exception3"));
  }
}
