Ver Fonte

GoogleUtilities: NSURLSession promise extension (#6753)

* GoogleUtilities: NSURLSession promise extension

* Imports fix

* Changelog

* API and API docs

* style

* Changelog fix
Maksym Malyhin há 5 anos atrás
pai
commit
7e70a6dd0b

+ 4 - 1
GoogleUtilities.podspec

@@ -118,7 +118,10 @@ other Google CocoaPods. They're not intended for direct public usage.
   s.test_spec 'unit' do |unit_tests|
     # All tests require arc except Tests/Network/third_party/GTMHTTPServer.m
     unit_tests.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'}
-    unit_tests.source_files = 'GoogleUtilities/Tests/Unit/**/*.[mh]'
+    unit_tests.source_files = [
+      'GoogleUtilities/Tests/Unit/**/*.[mh]',
+      'SharedTestUtilities/URLSession/*.[mh]',
+  ]
     unit_tests.requires_arc = 'GoogleUtilities/Tests/Unit/*/*.[mh]'
     unit_tests.requires_app_host = true
     unit_tests.dependency 'OCMock'

+ 4 - 1
GoogleUtilities/CHANGELOG.md

@@ -1,6 +1,9 @@
+# 7.1.0 -- Unreleased
+- Added `NSURLSession` promise extension. (#6753)
+
 # 7.0.0
 - All APIs are now public. All CocoaPods private headers are transitioned to public. Note that
-- GoogleUtilities may have frequent breaking changes than Firebase. (#6588)
+  GoogleUtilities may have more frequent breaking changes than Firebase. (#6588)
 - Fixed writing heartbeat to disk on tvOS devices. (#6658)
 - Refactor `GULSwizzledObject` to ARC to unblock SwiftPM support. (#5862)
 

+ 31 - 0
GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.h

@@ -0,0 +1,31 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** The class represents HTTP response received from `NSURLSession`. */
+@interface GULURLSessionDataResponse : NSObject
+
+@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse;
+@property(nonatomic, nullable, readonly) NSData *HTTPBody;
+
+- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(nullable NSData *)body;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 30 - 0
GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.m

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.h"
+
+@implementation GULURLSessionDataResponse
+
+- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(NSData *)body {
+  self = [super init];
+  if (self) {
+    _HTTPResponse = response;
+    _HTTPBody = body;
+  }
+  return self;
+}
+
+@end

+ 37 - 0
GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.h

@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class FBLPromise<Value>;
+@class GULURLSessionDataResponse;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** Promise based API for `NSURLSession`. */
+@interface NSURLSession (GULPromises)
+
+/** Creates a promise wrapping `-[NSURLSession dataTaskWithRequest:completionHandler:]` method.
+ * @param URLRequest The request to create a data task with.
+ * @return A promise that is fulfilled when an HTTP response is received (with any response code),
+ * or is rejected with the error passed to the task completion.
+ */
+- (FBLPromise<GULURLSessionDataResponse *> *)gul_dataTaskPromiseWithRequest:
+    (NSURLRequest *)URLRequest;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 46 - 0
GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.m

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.h"
+
+#if __has_include(<FBLPromises/FBLPromises.h>)
+#import <FBLPromises/FBLPromises.h>
+#else
+#import "FBLPromises.h"
+#endif
+
+#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.h"
+
+@implementation NSURLSession (GULPromises)
+
+- (FBLPromise<GULURLSessionDataResponse *> *)gul_dataTaskPromiseWithRequest:
+    (NSURLRequest *)URLRequest {
+  return [FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {
+    [[self dataTaskWithRequest:URLRequest
+             completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
+                                 NSError *_Nullable error) {
+               if (error) {
+                 reject(error);
+               } else {
+                 fulfill([[GULURLSessionDataResponse alloc]
+                     initWithResponse:(NSHTTPURLResponse *)response
+                             HTTPBody:data]);
+               }
+             }] resume];
+  }];
+}
+
+@end

+ 100 - 0
GoogleUtilities/Tests/Unit/Environment/NSURLSession+GULPromisesTests.m

@@ -0,0 +1,100 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <XCTest/XCTest.h>
+
+#import "FBLPromise+Testing.h"
+#import "OCMock.h"
+#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h"
+
+#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.h"
+#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.h"
+
+@interface NSURLSession_GULPromisesTests : XCTestCase
+@property(nonatomic) NSURLSession *URLSession;
+@property(nonatomic) id URLSessionMock;
+@end
+
+@implementation NSURLSession_GULPromisesTests
+
+- (void)setUp {
+  self.URLSession = [NSURLSession
+      sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
+  self.URLSessionMock = OCMPartialMock(self.URLSession);
+}
+
+- (void)tearDown {
+  [self.URLSessionMock stopMocking];
+  self.URLSessionMock = nil;
+  self.URLSession = nil;
+}
+
+- (void)testDataTaskPromiseWithRequestSuccess {
+  NSURL *url = [NSURL URLWithString:@"https://localhost"];
+  NSURLRequest *request = [NSURLRequest requestWithURL:url];
+
+  NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:url
+                                                                    statusCode:200
+                                                                   HTTPVersion:@"1.1"
+                                                                  headerFields:nil];
+  NSData *expectedBody = [@"body" dataUsingEncoding:NSUTF8StringEncoding];
+
+  [FIRURLSessionOCMockStub
+      stubURLSessionDataTaskWithResponse:expectedResponse
+                                    body:expectedBody
+                                   error:nil
+                          URLSessionMock:self.URLSessionMock
+                  requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
+                    return [sentRequest isEqual:request];
+                  }];
+
+  __auto_type taskPromise = [self.URLSessionMock gul_dataTaskPromiseWithRequest:request];
+
+  XCTAssert(FBLWaitForPromisesWithTimeout(0.5));
+
+  XCTAssertTrue(taskPromise.isFulfilled);
+  XCTAssertNil(taskPromise.error);
+  XCTAssertEqualObjects(taskPromise.value.HTTPResponse, expectedResponse);
+  XCTAssertEqualObjects(taskPromise.value.HTTPBody, expectedBody);
+}
+
+- (void)testDataTaskPromiseWithRequestError {
+  NSURL *url = [NSURL URLWithString:@"https://localhost"];
+  NSURLRequest *request = [NSURLRequest requestWithURL:url];
+
+  NSError *expectedError = [NSError errorWithDomain:@"testDataTaskPromiseWithRequestError"
+                                               code:-1
+                                           userInfo:nil];
+
+  [FIRURLSessionOCMockStub
+      stubURLSessionDataTaskWithResponse:nil
+                                    body:nil
+                                   error:expectedError
+                          URLSessionMock:self.URLSessionMock
+                  requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
+                    return [sentRequest isEqual:request];
+                  }];
+
+  __auto_type taskPromise = [self.URLSessionMock gul_dataTaskPromiseWithRequest:request];
+
+  XCTAssert(FBLWaitForPromisesWithTimeout(0.5));
+
+  XCTAssertTrue(taskPromise.isRejected);
+  XCTAssertEqualObjects(taskPromise.error, expectedError);
+  XCTAssertNil(taskPromise.value);
+}
+
+@end

+ 35 - 0
SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef BOOL (^FIRRequestValidationBlock)(NSURLRequest *request);
+
+@interface FIRURLSessionOCMockStub : NSObject
+
++ (id)stubURLSessionDataTaskWithResponse:(nullable NSHTTPURLResponse *)response
+                                    body:(nullable NSData *)body
+                                   error:(nullable NSError *)error
+                          URLSessionMock:(id)URLSessionMock
+                  requestValidationBlock:(nullable FIRRequestValidationBlock)requestValidationBlock;
+
++ (NSHTTPURLResponse *)HTTPResponseWithCode:(NSInteger)statusCode;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 65 - 0
SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.m

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h"
+
+#import "OCMock.h"
+
+@implementation FIRURLSessionOCMockStub
+
++ (id)stubURLSessionDataTaskWithResponse:(NSHTTPURLResponse *)response
+                                    body:(NSData *)body
+                                   error:(NSError *)error
+                          URLSessionMock:(id)URLSessionMock
+                  requestValidationBlock:(FIRRequestValidationBlock)requestValidationBlock {
+  id mockDataTask = OCMStrictClassMock([NSURLSessionDataTask class]);
+
+  // Validate request content.
+  FIRRequestValidationBlock nonOptionalRequestValidationBlock =
+      requestValidationBlock ?: ^BOOL(id request) {
+        return YES;
+      };
+
+  id URLRequestValidationArg = [OCMArg checkWithBlock:nonOptionalRequestValidationBlock];
+
+  // Save task completion to be called on the `[NSURLSessionDataTask resume]`
+  __block void (^taskCompletion)(NSData *, NSURLResponse *, NSError *);
+  id completionArg = [OCMArg checkWithBlock:^BOOL(id obj) {
+    taskCompletion = obj;
+    return YES;
+  }];
+
+  // Expect `dataTaskWithRequest` to be called.
+  OCMExpect([URLSessionMock dataTaskWithRequest:URLRequestValidationArg
+                              completionHandler:completionArg])
+      .andReturn(mockDataTask);
+
+  // Expect the task to be resumed and call the task completion.
+  OCMExpect([(NSURLSessionDataTask *)mockDataTask resume]).andDo(^(NSInvocation *invocation) {
+    taskCompletion(body, response, error);
+  });
+
+  return mockDataTask;
+}
+
++ (NSHTTPURLResponse *)HTTPResponseWithCode:(NSInteger)statusCode {
+  return [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"http://localhost"]
+                                     statusCode:statusCode
+                                    HTTPVersion:@"HTTP/1.1"
+                                   headerFields:nil];
+}
+
+@end