/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.underfs.s3a;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

import alluxio.conf.AlluxioConfiguration;
import alluxio.conf.Configuration;
import alluxio.conf.PropertyKey;

import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.transfer.TransferManager;
import com.amazonaws.services.s3.transfer.Upload;
import com.amazonaws.services.s3.transfer.model.UploadResult;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.security.DigestOutputStream;

/**
 * Unit tests for the {@link S3AOutputStream}.
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest(S3AOutputStream.class)
public class S3AOutputStreamTest {
  private static final String BUCKET_NAME = "testBucket";
  private static final String KEY = "testKey";
  private static AlluxioConfiguration sConf = Configuration.global();

  private File mFile;
  private BufferedOutputStream mLocalOutputStream;
  private S3AOutputStream mStream;
  private String mContentHash;

  /**
   * Sets the properties and configuration before each test runs.
   */
  @Before
  public void before() throws Exception {
    mFile = Mockito.mock(File.class);
    mLocalOutputStream = Mockito.mock(BufferedOutputStream.class);
    TransferManager manager = Mockito.mock(TransferManager.class);
    Upload result = Mockito.mock(Upload.class);
    UploadResult uploadResult = Mockito.mock(UploadResult.class);
    Mockito.doReturn(uploadResult).when(result).waitForUploadResult();
    mContentHash = "someHash";
    Mockito.doReturn(mContentHash).when(uploadResult).getETag();

    Mockito.when(manager.upload(Mockito.any(PutObjectRequest.class))).thenReturn(result);
    PowerMockito.whenNew(BufferedOutputStream.class)
        .withArguments(Mockito.any(DigestOutputStream.class)).thenReturn(mLocalOutputStream);
    PowerMockito.whenNew(File.class).withArguments(Mockito.anyString()).thenReturn(mFile);
    FileOutputStream outputStream = PowerMockito.mock(FileOutputStream.class);
    PowerMockito.whenNew(FileOutputStream.class).withArguments(mFile).thenReturn(outputStream);
    mStream = new S3AOutputStream(BUCKET_NAME, KEY, manager,
        sConf.getList(PropertyKey.TMP_DIRS),
        sConf.getBoolean(PropertyKey.UNDERFS_S3_SERVER_SIDE_ENCRYPTION_ENABLED));
    assertFalse(mStream.getContentHash().isPresent());
  }

  /**
   * Tests to ensure {@link S3AOutputStream#write(int)} calls the underlying output stream.
   */
  @Test
  public void writeByte() throws Exception {
    mStream.write(1);
    mStream.close();
    assertEquals(mContentHash, mStream.getContentHash().get());
    Mockito.verify(mLocalOutputStream).write(1);
  }

  /**
   * Tests to ensure {@link S3AOutputStream#write(byte[])} calls the underlying output stream.
   */
  @Test
  public void writeByteArray() throws Exception {
    byte[] b = new byte[10];
    mStream.write(b);
    mStream.close();
    assertEquals(mContentHash, mStream.getContentHash().get());
    Mockito.verify(mLocalOutputStream).write(b, 0, b.length);
  }

  /**
   * Tests to ensure {@link S3AOutputStream#write(byte[], int, int)} calls the underlying
   * output stream.
   */
  @Test
  public void writeByteArrayWithRange() throws Exception {
    byte[] b = new byte[10];
    mStream.write(b, 0, b.length);
    mStream.close();
    assertEquals(mContentHash, mStream.getContentHash().get());
    Mockito.verify(mLocalOutputStream).write(b, 0, b.length);
  }

  /**
   * Tests to ensure {@link File#delete()} is called when the stream is closed.
   */
  @Test
  public void close() throws Exception {
    mStream.close();
    assertEquals(mContentHash, mStream.getContentHash().get());
    Mockito.verify(mFile).delete();
  }

  /**
   * Tests to ensure {@link S3AOutputStream#flush()} calls the underlying output stream.
   */
  @Test
  public void flush() throws Exception {
    mStream.flush();
    mStream.close();
    assertEquals(mContentHash, mStream.getContentHash().get());
    Mockito.verify(mLocalOutputStream).flush();
  }
}
