| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.net.test; |
| |
| import android.content.Context; |
| |
| import org.chromium.net.CronetEngine; |
| import org.chromium.net.ExperimentalCronetEngine; |
| import org.chromium.net.UrlRequest; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Controller for fake Cronet implementation. Allows a test to setup responses for |
| * {@link UrlRequest}s. If multiple {@link ResponseMatcher}s match a specific request, the first |
| * {@link ResponseMatcher} added takes precedence. |
| */ |
| public final class FakeCronetController { |
| // List of FakeCronetEngines so that FakeCronetEngine can be accessed when created with |
| // the {@link FakeCronetProvider}. |
| private static final List<CronetEngine> sInstances = |
| Collections.synchronizedList(new ArrayList<>()); |
| |
| // List of ResponseMatchers to be checked for a response to a request in place of a server. |
| private final List<ResponseMatcher> mResponseMatchers = |
| Collections.synchronizedList(new ArrayList<>()); |
| |
| /** |
| * Creates a fake {@link CronetEngine.Builder} that creates {@link CronetEngine}s that return |
| * fake {@link UrlRequests}. Once built, the {@link CronetEngine}'s {@link UrlRequest}s will |
| * retrieve responses from this {@link FakeCronetController}. |
| * |
| * @param context the Android context to build the fake {@link CronetEngine} from. |
| * @return a fake CronetEngine.Builder that uses this {@link FakeCronetController} to manage |
| * responses once it is built. |
| */ |
| public CronetEngine.Builder newFakeCronetEngineBuilder(Context context) { |
| FakeCronetEngine.Builder builder = new FakeCronetEngine.Builder(context); |
| builder.setController(this); |
| // FakeCronetEngine.Builder is not actually a CronetEngine.Builder, so construct one with |
| // the child of CronetEngine.Builder: ExperimentalCronetEngine.Builder. |
| return new ExperimentalCronetEngine.Builder(builder); |
| } |
| |
| /** |
| * Adds a {@link UrlResponseMatcher} that will respond to the provided URL with the provided |
| * {@link FakeUrlResponse}. Equivalent to: |
| * addResponseMatcher(new UrlResponseMatcher(url, response)). |
| * |
| * @param response a {@link FakeUrlResponse} to respond with |
| * @param url a url for which the response should be returned |
| */ |
| public void addResponseForUrl(FakeUrlResponse response, String url) { |
| addResponseMatcher(new UrlResponseMatcher(url, response)); |
| } |
| |
| /** |
| * Adds a {@link ResponseMatcher} to the list of {@link ResponseMatcher}s. |
| * |
| * @param matcher the {@link ResponseMatcher} that should be matched against a request |
| */ |
| public void addResponseMatcher(ResponseMatcher matcher) { |
| mResponseMatchers.add(matcher); |
| } |
| |
| /** |
| * Removes a specific {@link ResponseMatcher} from the list of {@link ResponseMatcher}s. |
| * |
| * @param matcher the {@link ResponseMatcher} to remove |
| */ |
| public void removeResponseMatcher(ResponseMatcher matcher) { |
| mResponseMatchers.remove(matcher); |
| } |
| |
| /** Removes all {@link ResponseMatcher}s from the list of {@link ResponseMatcher}s. */ |
| public void clearResponseMatchers() { |
| mResponseMatchers.clear(); |
| } |
| |
| /** |
| * Adds a {@link FakeUrlResponse} to the list of responses that will redirect a |
| * {@link UrlRequest} to the specified URL. |
| * |
| * @param redirectLocation the URL to redirect the {@link UrlRequest} to |
| * @param url the URL that will trigger the redirect |
| */ |
| public void addRedirectResponse(String redirectLocation, String url) { |
| FakeUrlResponse redirectResponse = |
| new FakeUrlResponse.Builder() |
| .setHttpStatusCode(302) |
| .addHeader("location", redirectLocation) |
| .build(); |
| addResponseForUrl(redirectResponse, url); |
| } |
| |
| /** |
| * Adds an {@link FakeUrlResponse} that fails with the specified HTTP code for the specified |
| * URL. |
| * |
| * @param statusCode the code for the {@link FakeUrlResponse} |
| * @param url the URL that should trigger the error response when requested by a |
| * {@link UrlRequest} |
| * @throws IllegalArgumentException if the HTTP status code is not an error code (code >= 400) |
| */ |
| public void addHttpErrorResponse(int statusCode, String url) { |
| addResponseForUrl(getFailedResponse(statusCode), url); |
| } |
| |
| // TODO(kirchman): Create a function to add a response that takes a CronetException. |
| |
| /** |
| * Adds a successful 200 code {@link FakeUrlResponse} that will match the specified |
| * URL when requested by a {@link UrlRequest}. |
| * |
| * @param url the URL that triggers the successful response |
| * @param body the body of the response as a byte array |
| */ |
| public void addSuccessResponse(String url, byte[] body) { |
| addResponseForUrl(new FakeUrlResponse.Builder().setResponseBody(body).build(), url); |
| } |
| |
| /** |
| * Returns the {@link CronetEngineController} for a specified {@link CronetEngine}. This method |
| * should be used in conjunction with {@link FakeCronetController.getInstances}. |
| * |
| * @param engine the fake {@link CronetEngine} to get the controller for. |
| * @return the controller for the specified fake {@link CronetEngine}. |
| */ |
| public static FakeCronetController getControllerForFakeEngine(CronetEngine engine) { |
| if (engine instanceof FakeCronetEngine) { |
| FakeCronetEngine fakeEngine = (FakeCronetEngine) engine; |
| return fakeEngine.getController(); |
| } |
| throw new IllegalArgumentException("Provided CronetEngine is not a fake CronetEngine"); |
| } |
| |
| /** |
| * Returns all created fake instances of {@link CronetEngine} that have not been shut down with |
| * {@link CronetEngine.shutdown()} in order of creation. Can be used to retrieve a controller |
| * in conjunction with {@link FakeCronetController.getControllerForFakeEngine}. |
| * |
| * @return a list of all fake {@link CronetEngine}s that have been created |
| */ |
| public static List<CronetEngine> getFakeCronetEngines() { |
| synchronized (sInstances) { |
| return new ArrayList<>(sInstances); |
| } |
| } |
| |
| /** |
| * Removes a fake {@link CronetEngine} from the list of {@link CronetEngine} instances. |
| * |
| * @param cronetEngine the instance to remove |
| */ |
| static void removeFakeCronetEngine(CronetEngine cronetEngine) { |
| sInstances.remove(cronetEngine); |
| } |
| |
| /** |
| * Add a CronetEngine to the list of CronetEngines. |
| * |
| * @param engine the {@link CronetEngine} to add |
| */ |
| static void addFakeCronetEngine(FakeCronetEngine engine) { |
| sInstances.add(engine); |
| } |
| |
| /** |
| * Gets a response for specified request details if there is one, or a "404" failed response |
| * if there is no {@link ResponseMatcher} with a {@link FakeUrlResponse} for this request. |
| * |
| * @param url the URL that the {@link UrlRequest} is connecting to |
| * @param httpMethod the HTTP method that the {@link UrlRequest} is using to connect with |
| * @param headers the headers supplied by the {@link UrlRequest} |
| * @param body the body of the fake HTTP request |
| * @return a {@link FakeUrlResponse} if there is one, or a failed "404" response if none found |
| */ |
| FakeUrlResponse getResponse( |
| String url, String httpMethod, List<Map.Entry<String, String>> headers, byte[] body) { |
| synchronized (mResponseMatchers) { |
| for (ResponseMatcher responseMatcher : mResponseMatchers) { |
| FakeUrlResponse matchedResponse = |
| responseMatcher.getMatchingResponse(url, httpMethod, headers, body); |
| if (matchedResponse != null) { |
| return matchedResponse; |
| } |
| } |
| } |
| return getFailedResponse(404); |
| } |
| |
| /** |
| * Creates and returns a failed response with the specified HTTP status code. |
| * |
| * @param statusCode the HTTP code that the returned response will have |
| * @return a {@link FakeUrlResponse} with the specified code |
| */ |
| private static FakeUrlResponse getFailedResponse(int statusCode) { |
| if (statusCode < 400) { |
| throw new IllegalArgumentException( |
| "Expected HTTP error code (code >= 400), but was: " + statusCode); |
| } |
| return new FakeUrlResponse.Builder().setHttpStatusCode(statusCode).build(); |
| } |
| } |