Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ios / crnet / test / crnet_http_tests.mm
blobd0c60510b9470f51e173436ee4f446627523132c
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import <Foundation/Foundation.h>
7 #import "CrNet.h"
9 #include "base/logging.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/strings/sys_string_conversions.h"
12 #import "ios/third_party/gcdwebserver/src/GCDWebServer/Core/GCDWebServer.h"
13 #include "net/base/mac/url_conversions.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "testing/gtest_mac.h"
16 #include "url/gurl.h"
18 @interface TestDelegate : NSObject<NSURLSessionDataDelegate,
19                                    NSURLSessionDelegate,
20                                    NSURLSessionTaskDelegate>
22 // Completion semaphore for this TestDelegate. When the request this delegate is
23 // attached to finishes (either successfully or with an error), this delegate
24 // signals this semaphore.
25 @property(assign, nonatomic) dispatch_semaphore_t semaphore;
27 // Total count of bytes received by the request this delegate is attached to.
28 @property(nonatomic) unsigned long receivedBytes;
30 // Error the request this delegate is attached to failed with, if any.
31 @property(retain, nonatomic) NSError* error;
33 @end
35 @implementation TestDelegate
36 @synthesize semaphore = _semaphore;
37 @synthesize receivedBytes = _receivedBytes;
38 @synthesize error = _error;
40 - (id)init {
41   if (self = [super init]) {
42     _semaphore = dispatch_semaphore_create(0);
43   }
44   return self;
47 - (void)dealloc {
48   dispatch_release(_semaphore);
49   [_error release];
50   _error = nil;
51   [super dealloc];
54 - (void)URLSession:(NSURLSession*)session
55     didBecomeInvalidWithError:(NSError*)error {
58 - (void)URLSession:(NSURLSession*)session
59                     task:(NSURLSessionTask*)task
60     didCompleteWithError:(NSError*)error {
61   [self setError:error];
62   dispatch_semaphore_signal(_semaphore);
65 - (void)URLSession:(NSURLSession*)session
66                    task:(NSURLSessionTask*)task
67     didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge
68       completionHandler:
69           (void (^)(NSURLSessionAuthChallengeDisposition disp,
70                     NSURLCredential* credential))completionHandler {
71   completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
74 - (void)URLSession:(NSURLSession*)session
75               dataTask:(NSURLSessionDataTask*)dataTask
76     didReceiveResponse:(NSURLResponse*)response
77      completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))
78                            completionHandler {
79   completionHandler(NSURLSessionResponseAllow);
82 - (void)URLSession:(NSURLSession*)session
83           dataTask:(NSURLSessionDataTask*)dataTask
84     didReceiveData:(NSData*)data {
85   _receivedBytes += (unsigned long)data.length;
88 - (void)URLSession:(NSURLSession*)session
89              dataTask:(NSURLSessionDataTask*)dataTask
90     willCacheResponse:(NSCachedURLResponse*)proposedResponse
91     completionHandler:
92         (void (^)(NSCachedURLResponse* cachedResponse))completionHandler {
93   completionHandler(proposedResponse);
96 @end
98 // base::TimeDelta would normally be ideal for this but it does not support
99 // nanosecond resolution.
100 static const int64_t ns_in_second = 1000000000LL;
102 class HttpTest : public ::testing::Test {
103  protected:
104   HttpTest() {}
105   ~HttpTest() override {}
107   void SetUp() override {
108     [CrNet setPartialUserAgent:@"CrNetTest/1.0.0.0"];
109     [CrNet install];
110     NSURLSessionConfiguration* config =
111         [NSURLSessionConfiguration ephemeralSessionConfiguration];
112     [CrNet installIntoSessionConfiguration:config];
113     delegate_.reset([[TestDelegate alloc] init]);
114     NSURLSession* session = [NSURLSession sessionWithConfiguration:config
115                                                           delegate:delegate_
116                                                      delegateQueue:nil];
117     // Take a reference to the session and store it so it doesn't get
118     // deallocated until this object does.
119     session_.reset([session retain]);
120     web_server_.reset([[GCDWebServer alloc] init]);
121   }
123   void TearDown() override {
124     [CrNet uninstall];
125     [web_server_ stop];
126   }
128   // Starts a GCDWebServer instance on localhost port 8080, and remembers the
129   // root URL for later; tests can use GetURL() to produce a URL referring to a
130   // specific resource under the root URL.
131   void StartWebServer() {
132     [web_server_ startWithPort:8080 bonjourName:nil];
133     server_root_ = net::GURLWithNSURL([web_server_ serverURL]);
134   }
136   // Registers a fixed response |text| to be returned to requests for |path|,
137   // which is relative to |server_root_|.
138   void RegisterPathText(const std::string& path, const std::string& text) {
139     NSString* nspath = base::SysUTF8ToNSString(path);
140     NSData* data = [NSData dataWithBytes:text.c_str() length:text.length()];
141     [web_server_ addGETHandlerForPath:nspath
142                           staticData:data
143                          contentType:@"text/plain"
144                             cacheAge:30];
145   }
147   void RegisterPathHandler(const std::string& path,
148                            GCDWebServerProcessBlock handler) {
149     NSString* nspath = base::SysUTF8ToNSString(path);
150     [web_server_ addHandlerForMethod:@"GET"
151                                 path:nspath
152                         requestClass:NSClassFromString(@"GCDWebServerRequest")
153                         processBlock:handler];
154   }
156   // Launches the supplied |task| and blocks until it completes, with a timeout
157   // of 1 second.
158   void StartDataTaskAndWaitForCompletion(NSURLSessionDataTask* task) {
159     [task resume];
160     int64_t deadline_ns = 1 * ns_in_second;
161     dispatch_semaphore_wait([delegate_ semaphore],
162                             dispatch_time(DISPATCH_TIME_NOW, deadline_ns));
163   }
165   // Returns a URL to refer to the resource named |path| served by the test
166   // server. If |path| starts with a /, the leading / will be stripped.
167   GURL GetURL(const std::string& path) {
168     std::string real_path = path[0] == '/' ? path.substr(1) : path;
169     return server_root_.Resolve(real_path);
170   }
172   // Some convenience functions for working with GCDWebServerRequest and
173   // GCDWebServerResponse.
175   // Returns true if the value for the request header |header| is not nil and
176   // contains the string |target|.
177   bool HeaderValueContains(GCDWebServerRequest* request,
178                            const std::string& header,
179                            const std::string& target) {
180     NSString* key = base::SysUTF8ToNSString(header);
181     NSString* needle = base::SysUTF8ToNSString(target);
182     NSString* haystack = request.headers[key];
183     if (!haystack)
184       return false;
185     return [haystack rangeOfString:needle].location != NSNotFound;
186   }
188   base::scoped_nsobject<NSURLSession> session_;
189   base::scoped_nsobject<TestDelegate> delegate_;
191  private:
192   base::scoped_nsobject<GCDWebServer> web_server_;
193   GURL server_root_;
196 TEST_F(HttpTest, NSURLConnectionReceivesData) {
197   const char kData[] = "foobar";
198   const char kPath[] = "/foo";
199   RegisterPathText(kPath, kData);
200   StartWebServer();
202   NSURL* url = net::NSURLWithGURL(GetURL(kPath));
203   NSURLRequest* req = [NSURLRequest requestWithURL:url];
204   NSURLResponse* resp = nil;
205   NSData* received = [NSURLConnection sendSynchronousRequest:req
206                                            returningResponse:&resp
207                                                        error:nullptr];
208   EXPECT_EQ(0, memcmp([received bytes], kData, sizeof(kData)));
211 TEST_F(HttpTest, NSURLSessionReceivesData) {
212   const char kPath[] = "/foo";
213   const char kData[] = "foobar";
214   RegisterPathText(kPath, kData);
215   StartWebServer();
217   NSURL* url = net::NSURLWithGURL(GetURL(kPath));
218   NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
219   StartDataTaskAndWaitForCompletion(task);
220   EXPECT_EQ(nil, [delegate_ error]);
221   EXPECT_EQ(strlen(kData), [delegate_ receivedBytes]);
224 TEST_F(HttpTest, SdchDisabledByDefault) {
225   const char kPath[] = "/foo";
226   RegisterPathHandler(kPath,
227       ^GCDWebServerResponse* (GCDWebServerRequest* req) {
228         EXPECT_FALSE(HeaderValueContains(req, "Accept-Encoding", "sdch"));
229         return nil;
230       });
231   StartWebServer();
232   NSURL* url = net::NSURLWithGURL(GetURL(kPath));
233   NSURLRequest* req = [NSURLRequest requestWithURL:url];
234   NSURLResponse* resp = nil;
235   NSError* error = nil;
236   NSData* received = [NSURLConnection sendSynchronousRequest:req
237                                            returningResponse:&resp
238                                                        error:&error];
239   DCHECK(received);
242 // TODO(ellyjones): There needs to be a test that enabling SDCH works, but
243 // because CrNet is static and 'uninstall' only disables it, there is no way to
244 // have an individual test enable or disable SDCH.
245 // Probably there is a way to get gtest tests to run in a separate process, but
246 // I'm not sure what it is.