/*
 * Copyright 2018 Google LLC.
 *
 * 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.
 */

package com.google.cloud.tools.jib.registry;

import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpResponseException;
import com.google.cloud.tools.jib.http.BlobHttpContent;
import com.google.cloud.tools.jib.http.Response;
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
import com.google.cloud.tools.jib.json.JsonTemplateMapper;
import com.google.common.io.Resources;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.http.HttpStatus;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

/** Tests for {@link ManifestPusher}. */
@RunWith(MockitoJUnitRunner.class)
public class ManifestPusherTest {

  private Path v22manifestJsonFile;
  private V22ManifestTemplate fakeManifestTemplate;
  private ManifestPusher testManifestPusher;

  @Before
  public void setUp() throws URISyntaxException, IOException {
    v22manifestJsonFile = Paths.get(Resources.getResource("json/v22manifest.json").toURI());
    fakeManifestTemplate =
        JsonTemplateMapper.readJsonFromFile(v22manifestJsonFile, V22ManifestTemplate.class);

    testManifestPusher =
        new ManifestPusher(
            new RegistryEndpointRequestProperties("someServerUrl", "someImageName"),
            fakeManifestTemplate,
            "test-image-tag");
  }

  @Test
  public void testGetContent() throws IOException {
    BlobHttpContent body = testManifestPusher.getContent();

    Assert.assertNotNull(body);
    Assert.assertEquals(V22ManifestTemplate.MANIFEST_MEDIA_TYPE, body.getType());

    ByteArrayOutputStream bodyCaptureStream = new ByteArrayOutputStream();
    body.writeTo(bodyCaptureStream);
    String v22manifestJson =
        new String(Files.readAllBytes(v22manifestJsonFile), StandardCharsets.UTF_8);
    Assert.assertEquals(
        v22manifestJson, new String(bodyCaptureStream.toByteArray(), StandardCharsets.UTF_8));
  }

  @Test
  public void testHandleResponse() {
    Assert.assertNull(testManifestPusher.handleResponse(Mockito.mock(Response.class)));
  }

  @Test
  public void testApiRoute() throws MalformedURLException {
    Assert.assertEquals(
        new URL("http://someApiBase/someImageName/manifests/test-image-tag"),
        testManifestPusher.getApiRoute("http://someApiBase/"));
  }

  @Test
  public void testGetHttpMethod() {
    Assert.assertEquals("PUT", testManifestPusher.getHttpMethod());
  }

  @Test
  public void testGetActionDescription() {
    Assert.assertEquals(
        "push image manifest for someServerUrl/someImageName:test-image-tag",
        testManifestPusher.getActionDescription());
  }

  @Test
  public void testGetAccept() {
    Assert.assertEquals(0, testManifestPusher.getAccept().size());
  }

  /** Docker Registry 2.0 and 2.1 return 400 / TAG_INVALID. */
  @Test
  public void testHandleHttpResponseException_dockerRegistry_tagInvalid() {
    HttpResponseException exception =
        new HttpResponseException.Builder(
                HttpStatus.SC_BAD_REQUEST, "Bad Request", new HttpHeaders())
            .setContent(
                "{\"errors\":[{\"code\":\"TAG_INVALID\",\"message\":\"manifest tag did not match URI\"}]}")
            .build();
    try {
      testManifestPusher.handleHttpResponseException(exception);
      Assert.fail();

    } catch (RegistryErrorException ex) {
      Assert.assertThat(
          ex.getMessage(),
          CoreMatchers.containsString(
              "Registry may not support Image Manifest Version 2, Schema 2"));

    } catch (HttpResponseException ex) {
      Assert.fail("should have been a RegistryErrorException");
    }
  }

  /** Docker Registry 2.2 returns a 400 / MANIFEST_INVALID. */
  @Test
  public void testHandleHttpResponseException_dockerRegistry_manifestInvalid() {
    HttpResponseException exception =
        new HttpResponseException.Builder(
                HttpStatus.SC_BAD_REQUEST, "Bad Request", new HttpHeaders())
            .setContent(
                "{\"errors\":[{\"code\":\"MANIFEST_INVALID\",\"message\":\"manifest invalid\",\"detail\":{}}]}")
            .build();
    try {
      testManifestPusher.handleHttpResponseException(exception);
      Assert.fail();

    } catch (RegistryErrorException ex) {
      Assert.assertThat(
          ex.getMessage(),
          CoreMatchers.containsString(
              "Registry may not support Image Manifest Version 2, Schema 2"));

    } catch (HttpResponseException ex) {
      Assert.fail("should have been a RegistryErrorException");
    }
  }

  /** Quay.io returns an undocumented 415 / MANIFEST_INVALID. */
  @Test
  public void testHandleHttpResponseException_quayIo() {
    HttpResponseException exception =
        new HttpResponseException.Builder(
                HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, "UNSUPPORTED MEDIA TYPE", new HttpHeaders())
            .setContent(
                "{\"errors\":[{\"code\":\"MANIFEST_INVALID\",\"detail\":"
                    + "{\"message\":\"manifest schema version not supported\"},\"message\":\"manifest invalid\"}]}")
            .build();
    try {
      testManifestPusher.handleHttpResponseException(exception);
      Assert.fail();

    } catch (RegistryErrorException ex) {
      Assert.assertThat(
          ex.getMessage(),
          CoreMatchers.containsString(
              "Registry may not support Image Manifest Version 2, Schema 2"));

    } catch (HttpResponseException ex) {
      Assert.fail("should have been a RegistryErrorException");
    }
  }

  @Test
  public void testHandleHttpResponseException_otherError() {
    HttpResponseException exception =
        new HttpResponseException.Builder(
                HttpStatus.SC_UNAUTHORIZED, "Unauthorized", new HttpHeaders())
            .setContent("{\"errors\":[{\"code\":\"UNAUTHORIZED\",\"message\":\"Unauthorized\"]}}")
            .build();
    try {
      testManifestPusher.handleHttpResponseException(exception);
      Assert.fail();

    } catch (RegistryErrorException ex) {
      Assert.fail("should have been a HttpResponseException");

    } catch (HttpResponseException ex) {
      Assert.assertSame(exception, ex);
    }
  }
}

@@ -3826,32 +3826,67 @@
 try_tagInvalid()
+%0A      throws HttpResponseException
  %7B%0A    HttpRespo
@@ -4497,114 +4497,8 @@
 %22));
-%0A%0A    %7D catch (HttpResponseException ex) %7B%0A      Assert.fail(%22should have been a RegistryErrorException%22);
 %0A   
@@ -4654,16 +4654,51 @@
 nvalid()
+%0A      throws HttpResponseException
  %7B%0A    H
@@ -5322,114 +5322,8 @@
 %22));
-%0A%0A    %7D catch (HttpResponseException ex) %7B%0A      Assert.fail(%22should have been a RegistryErrorException%22);
 %0A   
@@ -5457,16 +5457,45 @@
 quayIo()
+ throws HttpResponseException
  %7B%0A    H
@@ -6219,114 +6219,8 @@
 %22));
-%0A%0A    %7D catch (HttpResponseException ex) %7B%0A      Assert.fail(%22should have been a RegistryErrorException%22);
 %0A   
@@ -6293,16 +6293,46 @@
 rError()
+ throws RegistryErrorException
  %7B%0A    H
@@ -6708,114 +6708,8 @@
 );%0A%0A
-    %7D catch (RegistryErrorException ex) %7B%0A      Assert.fail(%22should have been a HttpResponseException%22);%0A%0A
     

Fix ManifestPusherTest warnings (#941)