1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.libraries.entitlement.testing; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import com.google.auto.value.AutoValue; 22 import com.google.common.collect.ImmutableList; 23 import com.google.common.collect.ImmutableMap; 24 25 import java.io.ByteArrayInputStream; 26 import java.io.ByteArrayOutputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import java.net.URL; 31 import java.net.URLConnection; 32 import java.net.URLStreamHandler; 33 import java.net.URLStreamHandlerFactory; 34 import java.security.cert.Certificate; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Map; 38 39 import javax.annotation.Nullable; 40 import javax.net.ssl.HttpsURLConnection; 41 42 /** 43 * A {@link URLStreamHandlerFactory} to return faked {@link URLConnection}, as {@link URL} is a 44 * final class and {@link URL#openConnection} cannot be mocked using mockito. 45 */ 46 public class FakeURLStreamHandler extends URLStreamHandler implements URLStreamHandlerFactory { 47 private Map<String, FakeResponse> mResponses = ImmutableMap.of(); 48 private List<FakeHttpsURLConnection> mConnections = new ArrayList<>(); 49 50 private static final String ACCESS_TOKEN = "8dGozfI6%2FEaSsE7LaTfJKwdy"; 51 private static final String LOCATION = "Location"; 52 private static final String CONTENT_TYPE = "Content-Type"; 53 private static final String RETRY_AFTER = "Retry-After"; 54 55 @Override openConnection(URL u)56 public URLConnection openConnection(URL u) { 57 FakeHttpsURLConnection connection = 58 new FakeHttpsURLConnection(u, mResponses.get(u.toString())); 59 mConnections.add(connection); 60 return connection; 61 } 62 63 @Override createURLStreamHandler(String protocol)64 public URLStreamHandler createURLStreamHandler(String protocol) { 65 return this; 66 } 67 68 /** 69 * Prepares canned responses. Must be called before using this handler to open any {@link 70 * URLConnection}. 71 */ stubResponse(Map<String, FakeResponse> response)72 public FakeURLStreamHandler stubResponse(Map<String, FakeResponse> response) { 73 mResponses = response; 74 mConnections = new ArrayList<>(); 75 return this; 76 } 77 78 /** Returns {@link URLConnection}s opened by this handler since last {@link #stubResponse}. */ getConnections()79 public ImmutableList<FakeHttpsURLConnection> getConnections() { 80 return ImmutableList.copyOf(mConnections); 81 } 82 83 /** Faked {@link HttpsURLConnection} to avoid making any network connection. */ 84 public static class FakeHttpsURLConnection extends HttpsURLConnection { 85 private final FakeResponse mResponse; 86 private final ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream(); 87 FakeHttpsURLConnection(URL url, FakeResponse response)88 public FakeHttpsURLConnection(URL url, FakeResponse response) { 89 super(url); 90 mResponse = response; 91 } 92 93 @Override getInputStream()94 public InputStream getInputStream() throws IOException { 95 if (mResponse.hasException() && mResponse.responseBody().length == 0) { 96 throw new IOException("stub exception"); 97 } 98 return new ByteArrayInputStream(mResponse.responseBody()); 99 } 100 101 @Override getOutputStream()102 public OutputStream getOutputStream() { 103 return mOutputStream; 104 } 105 106 @Override getErrorStream()107 public InputStream getErrorStream() { 108 return new ByteArrayInputStream("stub error".getBytes(UTF_8)); 109 } 110 getBytesWrittenToOutputStream()111 public byte[] getBytesWrittenToOutputStream() { 112 return mOutputStream.toByteArray(); 113 } 114 115 @Override getResponseCode()116 public int getResponseCode() throws IOException { 117 if (mResponse.hasException() && mResponse.responseCode() == 0) { 118 throw new IOException("stub exception"); 119 } 120 return mResponse.responseCode(); 121 } 122 123 @Override getHeaderFields()124 public Map<String, List<String>> getHeaderFields() { 125 List<String> locationList = ImmutableList.of("access_token=" + ACCESS_TOKEN); 126 return ImmutableMap.of("Location", locationList); 127 } 128 129 @Override getHeaderField(String name)130 public String getHeaderField(String name) { 131 switch (name) { 132 case LOCATION: 133 return "Location: " + mResponse.responseLocation(); 134 case CONTENT_TYPE: 135 return mResponse.contentType(); 136 case RETRY_AFTER: 137 return mResponse.retryAfter(); 138 default: 139 return null; 140 } 141 } 142 143 @Override connect()144 public void connect() { 145 } 146 147 @Override disconnect()148 public void disconnect() { 149 } 150 151 @Override usingProxy()152 public boolean usingProxy() { 153 return false; 154 } 155 156 @Override getCipherSuite()157 public String getCipherSuite() { 158 return null; 159 } 160 161 @Override getLocalCertificates()162 public Certificate[] getLocalCertificates() { 163 return null; 164 } 165 166 @Override getServerCertificates()167 public Certificate[] getServerCertificates() { 168 return null; 169 } 170 } 171 172 @AutoValue 173 public abstract static class FakeResponse { responseCode()174 public abstract int responseCode(); 175 176 @Nullable responseLocation()177 public abstract String responseLocation(); 178 179 @SuppressWarnings("mutable") // For test only responseBody()180 public abstract byte[] responseBody(); 181 contentType()182 public abstract String contentType(); 183 retryAfter()184 public abstract String retryAfter(); 185 hasException()186 abstract boolean hasException(); 187 builder()188 public static Builder builder() { 189 return new AutoValue_FakeURLStreamHandler_FakeResponse.Builder() 190 .setResponseBody(new byte[]{}) 191 .setContentType("") 192 .setResponseCode(0) 193 .setResponseLocation("") 194 .setRetryAfter("") 195 .setHasException(false); 196 } 197 198 @AutoValue.Builder 199 public abstract static class Builder { setResponseCode(int value)200 public abstract Builder setResponseCode(int value); 201 setResponseLocation(String value)202 public abstract Builder setResponseLocation(String value); 203 setResponseBody(byte[] value)204 public abstract Builder setResponseBody(byte[] value); 205 setContentType(String contentType)206 public abstract Builder setContentType(String contentType); 207 setRetryAfter(String retryAfter)208 public abstract Builder setRetryAfter(String retryAfter); 209 setHasException(boolean hasException)210 public abstract Builder setHasException(boolean hasException); 211 build()212 public abstract FakeResponse build(); 213 } 214 } 215 } 216