xref: /aosp_15_r20/external/cronet/components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.test;
6 
7 import android.content.Context;
8 
9 import org.chromium.net.CronetEngine;
10 import org.chromium.net.ExperimentalCronetEngine;
11 import org.chromium.net.UrlRequest;
12 
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.Map;
17 
18 /**
19  * Controller for fake Cronet implementation. Allows a test to setup responses for
20  * {@link UrlRequest}s. If multiple {@link ResponseMatcher}s match a specific request, the first
21  * {@link ResponseMatcher} added takes precedence.
22  */
23 public final class FakeCronetController {
24     // List of FakeCronetEngines so that FakeCronetEngine can be accessed when created with
25     // the {@link FakeCronetProvider}.
26     private static final List<CronetEngine> sInstances =
27             Collections.synchronizedList(new ArrayList<>());
28 
29     // List of ResponseMatchers to be checked for a response to a request in place of a server.
30     private final List<ResponseMatcher> mResponseMatchers =
31             Collections.synchronizedList(new ArrayList<>());
32 
33     /**
34      * Creates a fake {@link CronetEngine.Builder} that creates {@link CronetEngine}s that return
35      * fake {@link UrlRequests}. Once built, the {@link CronetEngine}'s {@link UrlRequest}s will
36      * retrieve responses from this {@link FakeCronetController}.
37      *
38      * @param context the Android context to build the fake {@link CronetEngine} from.
39      * @return a fake CronetEngine.Builder that uses this {@link FakeCronetController} to manage
40      * responses once it is built.
41      */
newFakeCronetEngineBuilder(Context context)42     public CronetEngine.Builder newFakeCronetEngineBuilder(Context context) {
43         FakeCronetEngine.Builder builder = new FakeCronetEngine.Builder(context);
44         builder.setController(this);
45         // FakeCronetEngine.Builder is not actually a CronetEngine.Builder, so construct one with
46         // the child of CronetEngine.Builder: ExperimentalCronetEngine.Builder.
47         return new ExperimentalCronetEngine.Builder(builder);
48     }
49 
50     /**
51      * Adds a {@link UrlResponseMatcher} that will respond to the provided URL with the provided
52      * {@link FakeUrlResponse}. Equivalent to:
53      * addResponseMatcher(new UrlResponseMatcher(url, response)).
54      *
55      * @param response a {@link FakeUrlResponse} to respond with
56      * @param url      a url for which the response should be returned
57      */
addResponseForUrl(FakeUrlResponse response, String url)58     public void addResponseForUrl(FakeUrlResponse response, String url) {
59         addResponseMatcher(new UrlResponseMatcher(url, response));
60     }
61 
62     /**
63      * Adds a {@link ResponseMatcher} to the list of {@link ResponseMatcher}s.
64      *
65      * @param matcher the {@link ResponseMatcher} that should be matched against a request
66      */
addResponseMatcher(ResponseMatcher matcher)67     public void addResponseMatcher(ResponseMatcher matcher) {
68         mResponseMatchers.add(matcher);
69     }
70 
71     /**
72      * Removes a specific {@link ResponseMatcher} from the list of {@link ResponseMatcher}s.
73      *
74      * @param matcher the {@link ResponseMatcher} to remove
75      */
removeResponseMatcher(ResponseMatcher matcher)76     public void removeResponseMatcher(ResponseMatcher matcher) {
77         mResponseMatchers.remove(matcher);
78     }
79 
80     /** Removes all {@link ResponseMatcher}s from the list of {@link ResponseMatcher}s. */
clearResponseMatchers()81     public void clearResponseMatchers() {
82         mResponseMatchers.clear();
83     }
84 
85     /**
86      * Adds a {@link FakeUrlResponse} to the list of responses that will redirect a
87      * {@link UrlRequest} to the specified URL.
88      *
89      * @param redirectLocation the URL to redirect the {@link UrlRequest} to
90      * @param url              the URL that will trigger the redirect
91      */
addRedirectResponse(String redirectLocation, String url)92     public void addRedirectResponse(String redirectLocation, String url) {
93         FakeUrlResponse redirectResponse =
94                 new FakeUrlResponse.Builder()
95                         .setHttpStatusCode(302)
96                         .addHeader("location", redirectLocation)
97                         .build();
98         addResponseForUrl(redirectResponse, url);
99     }
100 
101     /**
102      * Adds an {@link FakeUrlResponse} that fails with the specified HTTP code for the specified
103      * URL.
104      *
105      * @param statusCode the code for the {@link FakeUrlResponse}
106      * @param url        the URL that should trigger the error response when requested by a
107      *                   {@link UrlRequest}
108      * @throws IllegalArgumentException if the HTTP status code is not an error code (code >= 400)
109      */
addHttpErrorResponse(int statusCode, String url)110     public void addHttpErrorResponse(int statusCode, String url) {
111         addResponseForUrl(getFailedResponse(statusCode), url);
112     }
113 
114     // TODO(kirchman): Create a function to add a response that takes a CronetException.
115 
116     /**
117      * Adds a successful 200 code {@link FakeUrlResponse} that will match the specified
118      * URL when requested by a {@link UrlRequest}.
119      *
120      * @param url the URL that triggers the successful response
121      * @param body the body of the response as a byte array
122      */
addSuccessResponse(String url, byte[] body)123     public void addSuccessResponse(String url, byte[] body) {
124         addResponseForUrl(new FakeUrlResponse.Builder().setResponseBody(body).build(), url);
125     }
126 
127     /**
128      * Returns the {@link CronetEngineController} for a specified {@link CronetEngine}. This method
129      * should be used in conjunction with {@link FakeCronetController.getInstances}.
130      *
131      * @param engine the fake {@link CronetEngine} to get the controller for.
132      * @return the controller for the specified fake {@link CronetEngine}.
133      */
getControllerForFakeEngine(CronetEngine engine)134     public static FakeCronetController getControllerForFakeEngine(CronetEngine engine) {
135         if (engine instanceof FakeCronetEngine) {
136             FakeCronetEngine fakeEngine = (FakeCronetEngine) engine;
137             return fakeEngine.getController();
138         }
139         throw new IllegalArgumentException("Provided CronetEngine is not a fake CronetEngine");
140     }
141 
142     /**
143      * Returns all created fake instances of {@link CronetEngine} that have not been shut down with
144      * {@link CronetEngine.shutdown()} in order of creation. Can be used to retrieve a controller
145      * in conjunction with {@link FakeCronetController.getControllerForFakeEngine}.
146      *
147      * @return a list of all fake {@link CronetEngine}s that have been created
148      */
getFakeCronetEngines()149     public static List<CronetEngine> getFakeCronetEngines() {
150         synchronized (sInstances) {
151             return new ArrayList<>(sInstances);
152         }
153     }
154 
155     /**
156      * Removes a fake {@link CronetEngine} from the list of {@link CronetEngine} instances.
157      *
158      * @param cronetEngine the instance to remove
159      */
removeFakeCronetEngine(CronetEngine cronetEngine)160     static void removeFakeCronetEngine(CronetEngine cronetEngine) {
161         sInstances.remove(cronetEngine);
162     }
163 
164     /**
165      * Add a CronetEngine to the list of CronetEngines.
166      *
167      * @param engine the {@link CronetEngine} to add
168      */
addFakeCronetEngine(FakeCronetEngine engine)169     static void addFakeCronetEngine(FakeCronetEngine engine) {
170         sInstances.add(engine);
171     }
172 
173     /**
174      * Gets a response for specified request details if there is one, or a "404" failed response
175      * if there is no {@link ResponseMatcher} with a {@link FakeUrlResponse} for this request.
176      *
177      * @param url        the URL that the {@link UrlRequest} is connecting to
178      * @param httpMethod the HTTP method that the {@link UrlRequest} is using to connect with
179      * @param headers    the headers supplied by the {@link UrlRequest}
180      * @param body       the body of the fake HTTP request
181      * @return a {@link FakeUrlResponse} if there is one, or a failed "404" response if none found
182      */
getResponse( String url, String httpMethod, List<Map.Entry<String, String>> headers, byte[] body)183     FakeUrlResponse getResponse(
184             String url, String httpMethod, List<Map.Entry<String, String>> headers, byte[] body) {
185         synchronized (mResponseMatchers) {
186             for (ResponseMatcher responseMatcher : mResponseMatchers) {
187                 FakeUrlResponse matchedResponse =
188                         responseMatcher.getMatchingResponse(url, httpMethod, headers, body);
189                 if (matchedResponse != null) {
190                     return matchedResponse;
191                 }
192             }
193         }
194         return getFailedResponse(404);
195     }
196 
197     /**
198      * Creates and returns a failed response with the specified HTTP status code.
199      *
200      * @param statusCode the HTTP code that the returned response will have
201      * @return a {@link FakeUrlResponse} with the specified code
202      */
getFailedResponse(int statusCode)203     private static FakeUrlResponse getFailedResponse(int statusCode) {
204         if (statusCode < 400) {
205             throw new IllegalArgumentException(
206                     "Expected HTTP error code (code >= 400), but was: " + statusCode);
207         }
208         return new FakeUrlResponse.Builder().setHttpStatusCode(statusCode).build();
209     }
210 }
211