content: package com.amazon.SellingPartnerAPIAA;
  
  import com.google.gson.JsonObject;
  import com.google.gson.JsonParser;
  import okhttp3.Call;
  import okhttp3.MediaType;
  import okhttp3.OkHttpClient;
  import okhttp3.Protocol;
  import okhttp3.Request;
  import okhttp3.Response;
  import okhttp3.ResponseBody;
  import okio.Buffer;
  import org.junit.Before;
  import org.junit.Test;
  import org.junit.jupiter.api.Assertions;
  import org.junit.jupiter.api.BeforeEach;
  import org.junit.jupiter.params.ParameterizedTest;
  import org.junit.jupiter.params.provider.Arguments;
  import org.junit.jupiter.params.provider.MethodSource;
  import org.junit.runner.RunWith;
  import org.mockito.ArgumentCaptor;
  import org.mockito.Mock;
  import org.mockito.MockitoAnnotations;
  import org.mockito.junit.MockitoJUnitRunner;
  
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.Set;
  import java.util.stream.Stream;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertNotNull;
  import static org.mockito.ArgumentMatchers.any;
  import static org.mockito.Mockito.when;
  
  import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API;
  import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API;
  
  @RunWith(MockitoJUnitRunner.class)
  public class LWAClientTest {
      private static final String TEST_ENDPOINT = "https://www.amazon.com/api";
      private static final MediaType EXPECTED_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
      private static LWAAccessTokenRequestMeta lwaAccessTokenRequestMetaSeller;
      private static LWAAccessTokenRequestMeta lwaAccessTokenRequestMetaSellerless;
  
      private static final String TEST_SCOPE_1 = SCOPE_NOTIFICATIONS_API;
      private static final String TEST_SCOPE_2 = SCOPE_MIGRATION_API;
  
      private static final Set<String> scopesTestSellerless = new HashSet<String>(Arrays.asList(TEST_SCOPE_1,
              TEST_SCOPE_2));
  
      private static final String SELLER_TYPE_SELLER = "seller";
      private static final String SELLER_TYPE_SELLERLESS = "sellerless";
  
      private static final String LWA_EXCEPTION_MSG ="Error getting LWA Token";
      private static final String LWA_CLIENT_AUTH_MSG ="Client_authentication_failed";
      private static final String LWA_ACCESS_DENIED_MSG ="Authorization_server_denied_the_request";
      @Mock
      private OkHttpClient mockOkHttpClient;
  
      @Mock
      private Call mockCall;
      
      private LWAClient underTest;
  
      static {
          lwaAccessTokenRequestMetaSeller = LWAAccessTokenRequestMeta.builder()
                  .refreshToken("rToken")
                  .clientId("cId")
                  .clientSecret("cSecret")
                  .grantType("rToken")
                  .build();
  
          lwaAccessTokenRequestMetaSellerless = LWAAccessTokenRequestMeta.builder()
                  .clientId("cId")
                  .clientSecret("cSecret")
                  .grantType("cCredentials")
                  .scopes(new LWAClientScopes(scopesTestSellerless))
                  .build(); 
      }
  
      @Before @BeforeEach
      public void init() {
          MockitoAnnotations.initMocks(this);
  
          underTest = new LWAClient(TEST_ENDPOINT);
          underTest.setOkHttpClient(mockOkHttpClient);
      }
  
      public static Stream<Arguments> lwaClient(){
  
          return Stream.of(
                  Arguments.of(SELLER_TYPE_SELLER, lwaAccessTokenRequestMetaSeller),
                  Arguments.of(SELLER_TYPE_SELLERLESS, lwaAccessTokenRequestMetaSellerless)
          );
      }
  
      @Test
      public void initializeEndpoint() {
          assertEquals(TEST_ENDPOINT, underTest.getEndpoint());
      }
  
      @ParameterizedTest
      @MethodSource("lwaClient")
      public void makeRequestFromMeta (String sellerType, LWAAccessTokenRequestMeta testLwaAccessTokenRequestMeta) throws LWAException,IOException {
          ArgumentCaptor<Request> requestArgumentCaptor = ArgumentCaptor.forClass(Request.class);
          when(mockOkHttpClient.newCall(requestArgumentCaptor.capture()))
                  .thenReturn(mockCall);
          when(mockCall.execute())
                  .thenReturn(buildResponse(200, "foo"));
  
          underTest.getAccessToken(testLwaAccessTokenRequestMeta);
  
          Request actualRequest = requestArgumentCaptor.getValue();
          assertEquals(TEST_ENDPOINT, actualRequest.url().toString());
          assertEquals("POST", actualRequest.method().toUpperCase());
  
          Buffer bodyBuffer = new Buffer();
          actualRequest.body().writeTo(bodyBuffer);
          JsonObject bodyJson = new JsonParser()
                  .parse(new InputStreamReader(bodyBuffer.inputStream()))
                  .getAsJsonObject();
  
          if (sellerType == SELLER_TYPE_SELLER) {
              assertEquals("rToken", bodyJson.get("refresh_token")
                      .getAsString());
          }
          else if (sellerType == SELLER_TYPE_SELLERLESS){
              assertNotNull(bodyJson.get("scope"));
          }
          assertEquals("cId", bodyJson.get("client_id").getAsString());
          assertEquals("cSecret", bodyJson.get("client_secret").getAsString());
          assertEquals(EXPECTED_MEDIA_TYPE, actualRequest.body().contentType());
      }
  
      @ParameterizedTest
      @MethodSource("lwaClient")
      public void returnAccessTokenFromResponse(String sellerType, LWAAccessTokenRequestMeta testLwaAccessTokenRequestMeta) throws IOException, LWAException {
          when(mockOkHttpClient.newCall(any(Request.class)))
                  .thenReturn(mockCall);
          when(mockCall.execute())
                  .thenReturn(buildResponse(200, "Azta|foo"));
  
          assertEquals("Azta|foo", underTest.getAccessToken(testLwaAccessTokenRequestMeta));
      }
  
      @ParameterizedTest
      @MethodSource("lwaClient")
      public void unsuccessfulPostThrowsLwaExceptionUnknownErrorCode(String sellerType, LWAAccessTokenRequestMeta testLwaAccessTokenRequestMeta) throws IOException, LWAException {
          when(mockOkHttpClient.newCall(any(Request.class)))
                  .thenReturn(mockCall);
          when(mockCall.execute())
                  .thenReturn(buildResponse(400, "Azta|foo"));
  
          Throwable exception = Assertions.assertThrows(LWAException.class, () -> {
              underTest.getAccessToken(testLwaAccessTokenRequestMeta);
          });
          assertEquals(LWAExceptionErrorCode.other.toString(),((LWAException)exception).getErrorCode());
          assertEquals(LWA_EXCEPTION_MSG, exception.getMessage());
      }
  
      @ParameterizedTest
      @MethodSource("lwaClient")
      public void unsuccessfulPostThrowsLwaExceptionKnownErrorCode1(String sellerType, LWAAccessTokenRequestMeta testLwaAccessTokenRequestMeta) throws IOException, LWAException {
          when(mockOkHttpClient.newCall(any(Request.class)))
                  .thenReturn(mockCall);
          when(mockCall.execute())
                  .thenReturn(buildResponse(401, "Azta|foo","invalid_client","Client_authentication_failed"));
  
          Throwable exception = Assertions.assertThrows(LWAException.class, () -> {
              underTest.getAccessToken(testLwaAccessTokenRequestMeta);
          });
          assertEquals(LWAExceptionErrorCode.invalid_client.toString(),((LWAException)exception).getErrorCode());
          assertEquals(LWA_EXCEPTION_MSG, exception.getMessage());
          assertEquals(LWA_CLIENT_AUTH_MSG,((LWAException)exception).getErrorMessage());
      }
  
      @ParameterizedTest
      @MethodSource("lwaClient")
      public void unsuccessfulPostThrowsLwaExceptionKnownErrorCode2(String sellerType, LWAAccessTokenRequestMeta testLwaAccessTokenRequestMeta) throws IOException, LWAException {
          when(mockOkHttpClient.newCall(any(Request.class)))
                  .thenReturn(mockCall);
          when(mockCall.execute())
                  .thenReturn(buildResponse(401, "Azta|foo","access_denied","Authorization_server_denied_the_request"));
  
          Throwable exception = Assertions.assertThrows(LWAException.class, () -> {
              underTest.getAccessToken(testLwaAccessTokenRequestMeta);
          });
          assertEquals(LWAExceptionErrorCode.access_denied.toString(),((LWAException)exception).getErrorCode());
          assertEquals(LWA_EXCEPTION_MSG, exception.getMessage());
          assertEquals(LWA_ACCESS_DENIED_MSG,((LWAException)exception).getErrorMessage());
      }
  
      @ParameterizedTest
      @MethodSource("lwaClient")
      public void missingAccessTokenInResponseThrowsException(String sellerType, LWAAccessTokenRequestMeta testLwaAccessTokenRequestMeta) throws IOException, LWAException {
          when(mockOkHttpClient.newCall(any(Request.class)))
                  .thenReturn(mockCall);
          when(mockCall.execute())
                  .thenReturn(buildResponse(200, ""));
  
          Assertions.assertThrows(RuntimeException.class, () -> {
              underTest.getAccessToken(testLwaAccessTokenRequestMeta);
                  });
      }
  
      @ParameterizedTest
      @MethodSource("lwaClient")
      public void missingResponseBodyThrowsLwaException(String sellerType, LWAAccessTokenRequestMeta testLwaAccessTokenRequestMeta) throws IOException {
          when(mockOkHttpClient.newCall(any(Request.class)))
                  .thenReturn(mockCall);
          when(mockCall.execute())
                  .thenReturn(buildResponse(200));
  
          Assertions.assertThrows(LWAException.class, () -> {
              underTest.getAccessToken(testLwaAccessTokenRequestMeta);
          });
      }
      
      //Test for Access Token getting from cache
      @Test
      public void returnAccessTokenFromCache() throws IOException, InterruptedException, LWAException {
          
          when(mockOkHttpClient.newCall(any(Request.class)))
          .thenReturn(mockCall);
          when(mockCall.execute())     
          .thenReturn(buildResponse(200, "Azta|foo", "120"))
          .thenThrow(IllegalStateException.class);
          underTest.setLWAAccessTokenCache(new LWAAccessTokenCacheImpl());
          
          //First call should get from Endpoint
          assertEquals("Azta|foo", underTest.getAccessToken(lwaAccessTokenRequestMetaSeller));
          //Second call when the cache is still valid, if it goes to end point it will return IllegalStateException.
          assertEquals("Azta|foo", underTest.getAccessToken(lwaAccessTokenRequestMetaSeller));
      }
      
      @Test
      public void returnAccessTokenFromCacheWithExpiry() throws IOException, InterruptedException, LWAException {
          LWAClient client = new LWAClient(TEST_ENDPOINT);
          client.setOkHttpClient(mockOkHttpClient);
          when(mockOkHttpClient.newCall(any(Request.class)))
          .thenReturn(mockCall);
          when(mockCall.execute())     
          .thenReturn(buildResponse(200, "Azta|foo", "1"))
          .thenReturn(buildResponse(200, "Azta|foo1", "1"));
         
          //First call should get from Endpoint
          assertEquals("Azta|foo", client.getAccessToken(lwaAccessTokenRequestMetaSeller));
          //Second call should again go to the endpoint because accesstoken is expired after expiry adjustment.
          assertEquals("Azta|foo1", client.getAccessToken(lwaAccessTokenRequestMetaSeller));
      }
      
      private static Response buildResponse(int code, String accessToken, String expiryInSeconds, String errorCode, String errorMessage) {
          ResponseBody responseBody = ResponseBody.create(EXPECTED_MEDIA_TYPE,
                      String.format("{%s:%s,%s:%s,%s:%s,%s:%s}", "access_token", accessToken, "expires_in", expiryInSeconds, "error", errorCode, "error_description", errorMessage));
  
          return new Response.Builder()
                  .request(new Request.Builder().url(TEST_ENDPOINT).build())
                  .code(code)
                  .body(responseBody)
                  .protocol(Protocol.HTTP_1_1)
                  .message("OK")
                  .build();
      }
      
      private static Response buildResponse(int code, String accessToken,String errorCode, String errorMessage) {
          return buildResponse(code, accessToken, "3600",errorCode,errorMessage);
      }
  
      private static Response buildResponse(int code, String accessToken) {
          return buildResponse(code, accessToken, "3600");
      }
  
      private static Response buildResponse(int code, String accessToken,String expiryInSeconds) {
          ResponseBody  responseBody = ResponseBody.create(EXPECTED_MEDIA_TYPE,
                      String.format("{%s:%s,%s:%s}", "access_token", accessToken, "expires_in", expiryInSeconds));
  
          return new Response.Builder()
                  .request(new Request.Builder().url(TEST_ENDPOINT).build())
                  .code(code)
                  .body(responseBody)
                  .protocol(Protocol.HTTP_1_1)
                  .message("OK")
                  .build();
      }
  
      private static Response buildResponse(int code) {
          return new Response.Builder()
                  .request(new Request.Builder().url(TEST_ENDPOINT).build())
                  .code(code)
                  .body(null)
                  .protocol(Protocol.HTTP_1_1)
                  .message("OK")
                  .build();
      }
  }
  