aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/Storage
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Example/Storage
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Example/Storage')
-rw-r--r--Example/Storage/App/1mb.datbin0 -> 1048576 bytes
-rw-r--r--Example/Storage/App/Base.lproj/LaunchScreen.storyboard27
-rw-r--r--Example/Storage/App/Base.lproj/Main.storyboard27
-rw-r--r--Example/Storage/App/FIRAppDelegate.h23
-rw-r--r--Example/Storage/App/FIRAppDelegate.m52
-rw-r--r--Example/Storage/App/FIRViewController.h21
-rw-r--r--Example/Storage/App/FIRViewController.m35
-rw-r--r--Example/Storage/App/Storage-Info.plist49
-rw-r--r--Example/Storage/App/main.m23
-rw-r--r--Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m520
-rw-r--r--Example/Storage/Tests/Tests-Info.plist22
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageDeleteTests.m164
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageGetMetadataTests.m188
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageMetadataTests.m282
-rw-r--r--Example/Storage/Tests/Unit/FIRStoragePathTests.m234
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageReferenceTests.m163
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageTestHelpers.h127
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageTestHelpers.m128
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageTests.m214
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageTokenAuthorizerTests.m226
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m199
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageUtilsTests.m125
22 files changed, 2849 insertions, 0 deletions
diff --git a/Example/Storage/App/1mb.dat b/Example/Storage/App/1mb.dat
new file mode 100644
index 0000000..9e0f96a
--- /dev/null
+++ b/Example/Storage/App/1mb.dat
Binary files differ
diff --git a/Example/Storage/App/Base.lproj/LaunchScreen.storyboard b/Example/Storage/App/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..66a7681
--- /dev/null
+++ b/Example/Storage/App/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="EHf-IW-A2E">
+ <objects>
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="53" y="375"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Storage/App/Base.lproj/Main.storyboard b/Example/Storage/App/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..d164a23
--- /dev/null
+++ b/Example/Storage/App/Base.lproj/Main.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="whP-gf-Uak">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="wQg-tq-qST">
+ <objects>
+ <viewController id="whP-gf-Uak" customClass="FIRViewController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/>
+ <viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="TpU-gO-2f1">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="tc2-Qw-aMS" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="305" y="433"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Storage/App/FIRAppDelegate.h b/Example/Storage/App/FIRAppDelegate.h
new file mode 100644
index 0000000..e3fba8f
--- /dev/null
+++ b/Example/Storage/App/FIRAppDelegate.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 UIKit;
+
+@interface FIRAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Example/Storage/App/FIRAppDelegate.m b/Example/Storage/App/FIRAppDelegate.m
new file mode 100644
index 0000000..0ecfdea
--- /dev/null
+++ b/Example/Storage/App/FIRAppDelegate.m
@@ -0,0 +1,52 @@
+// Copyright 2017 Google
+//
+// 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 "FIRAppDelegate.h"
+
+@implementation FIRAppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ // Override point for customization after application launch.
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application
+{
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application
+{
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application
+{
+ // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application
+{
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application
+{
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Example/Storage/App/FIRViewController.h b/Example/Storage/App/FIRViewController.h
new file mode 100644
index 0000000..64b4b74
--- /dev/null
+++ b/Example/Storage/App/FIRViewController.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 UIKit;
+
+@interface FIRViewController : UIViewController
+
+@end
diff --git a/Example/Storage/App/FIRViewController.m b/Example/Storage/App/FIRViewController.m
new file mode 100644
index 0000000..901accf
--- /dev/null
+++ b/Example/Storage/App/FIRViewController.m
@@ -0,0 +1,35 @@
+// Copyright 2017 Google
+//
+// 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 "FIRViewController.h"
+
+@interface FIRViewController ()
+
+@end
+
+@implementation FIRViewController
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Example/Storage/App/Storage-Info.plist b/Example/Storage/App/Storage-Info.plist
new file mode 100644
index 0000000..7576a0d
--- /dev/null
+++ b/Example/Storage/App/Storage-Info.plist
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/Example/Storage/App/main.m b/Example/Storage/App/main.m
new file mode 100644
index 0000000..03b5c12
--- /dev/null
+++ b/Example/Storage/App/main.m
@@ -0,0 +1,23 @@
+// Copyright 2017 Google
+//
+// 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 UIKit;
+#import "FIRAppDelegate.h"
+
+int main(int argc, char * argv[])
+{
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([FIRAppDelegate class]));
+ }
+}
diff --git a/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m b/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m
new file mode 100644
index 0000000..60a2496
--- /dev/null
+++ b/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m
@@ -0,0 +1,520 @@
+// Copyright 2017 Google
+//
+// 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 <math.h>
+#import <XCTest/XCTest.h>
+
+#import "FirebaseStorage.h"
+
+#import "FIRApp.h"
+#import "FIROptions.h"
+
+NSTimeInterval kFIRStorageIntegrationTestTimeout = 30;
+
+/**
+ * Firebase Storage Integration tests
+ *
+ * To run these tests, you need to define the following access rights for your Firebase App:
+ * - unauthentication read/write access to /ios/public
+ * - authentication read/write access to /ios/private
+ *
+ * A sample configuration may look like:
+ *
+ * service firebase.storage {
+ * match /b/{YOUR_PROJECT_ID}.appspot.com/o {
+ * ...
+ * match /ios {
+ * match /public/{allPaths=**} {
+ * allow read, write;
+ * }
+ * match /private/{allPaths=**} {
+ * allow none;
+ * }
+ * }
+ * }
+ * }
+ *
+ * You can define these access rights in the Firebase Console of your project.
+ */
+@interface FIRStorageIntegrationTests : XCTestCase
+
+@property(strong, nonatomic) FIRApp *app;
+@property(strong, nonatomic) FIRStorage *storage;
+
+@end
+
+@implementation FIRStorageIntegrationTests
+
+
++ (void)setUp {
+ [FIRApp configure];
+}
+
+- (void)setUp {
+ [super setUp];
+
+ self.app = [FIRApp defaultApp];
+ self.storage = [FIRStorage storageForApp:self.app];
+
+ static dispatch_once_t once;
+ dispatch_once(&once, ^{
+ XCTestExpectation *expectation = [self expectationWithDescription:@"setup"];
+
+ FIRStorageReference *ref = [[FIRStorage storage].reference child:@"ios/public/1mb"];
+ NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1mb" ofType:@"dat"]];
+ XCTAssertNotNil(data, "Could not load bundled file");
+ [ref putData:data metadata:nil completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+ });
+}
+
+- (void)tearDown {
+ self.app = nil;
+ self.storage = nil;
+
+ [super tearDown];
+}
+
+- (void)testName {
+ NSString *aGSURI = [NSString stringWithFormat:@"gs://%@.appspot.com/path/to", [[FIRApp defaultApp] options].projectID];
+ FIRStorageReference *ref = [self.storage referenceForURL:aGSURI];
+ XCTAssertEqualObjects(ref.description, aGSURI);
+}
+
+- (void)testUnauthenticatedGetMetadata {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedGetMetadata"];
+ FIRStorageReference *ref = [self.storage.reference child:@"ios/public/1mb"];
+
+ [ref metadataWithCompletion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedUpdateMetadata {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedUpdateMetadata"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/1mb"];
+
+ FIRStorageMetadata *meta = [[FIRStorageMetadata alloc] init];
+ [meta setContentType:@"lol/custom"];
+ [meta setCustomMetadata:@{
+ @"lol" : @"custom metadata is neat",
+ @"ちかてつ" : @"🚇",
+ @"shinkansen" : @"新幹線"
+ }];
+
+ [ref updateMetadata:meta
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertEqualObjects(meta.contentType, metadata.contentType);
+ XCTAssertEqualObjects(meta.customMetadata[@"lol"],
+ metadata.customMetadata[@"lol"]);
+ XCTAssertEqualObjects(meta.customMetadata[@"ちかてつ"],
+ metadata.customMetadata[@"ちかてつ"]);
+ XCTAssertEqualObjects(meta.customMetadata[@"shinkansen"],
+ metadata.customMetadata[@"shinkansen"]);
+ XCTAssertNil(error, "Error should be nil");
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedDelete {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testUnauthenticatedDelete"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/fileToDelete"];
+
+ NSData *data = [@"Delete me!!!!!!!" dataUsingEncoding:NSUTF8StringEncoding];
+
+ [ref putData:data
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ [ref deleteWithCompletion:^(NSError *error) {
+ XCTAssertNil(error, "Error should be nil");
+ [expectation fulfill];
+ }];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testDeleteWithNilCompletion {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testDeleteWithNilCompletion"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/fileToDelete"];
+
+ NSData *data = [@"Delete me!!!!!!!" dataUsingEncoding:NSUTF8StringEncoding];
+
+ [ref putData:data
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ [ref deleteWithCompletion:nil];
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimplePutData {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimplePutData"];
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/testBytesUpload"];
+
+ NSData *data = [@"Hello World" dataUsingEncoding:NSUTF8StringEncoding];
+
+ [ref putData:data
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimplePutEmptyData {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimplePutEmptyData"];
+
+ FIRStorageReference *ref =
+ [self.storage referenceWithPath:@"ios/public/testUnauthenticatedSimplePutEmptyData"];
+
+ NSData *data = [[NSData alloc] init];
+
+ [ref putData:data
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimplePutDataUnauthorized {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimplePutDataUnauthorized"];
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/private/secretfile.txt"];
+
+ NSData *data = [@"Hello World" dataUsingEncoding:NSUTF8StringEncoding];
+
+ [ref putData:data
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNil(metadata, "Metadata should be nil");
+ XCTAssertNotNil(error, "Error should not be nil");
+ XCTAssertEqual(error.code, FIRStorageErrorCodeUnauthorized);
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimplePutFile {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimplePutFile"];
+
+ FIRStorageReference *ref =
+ [self.storage referenceWithPath:@"ios/public/testUnauthenticatedSimplePutFile"];
+
+ NSData *data = [@"Hello World" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL =
+ [[tmpDirURL URLByAppendingPathComponent:@"hello"] URLByAppendingPathExtension:@"txt"];
+ [data writeToURL:fileURL atomically:YES];
+
+ FIRStorageUploadTask *task = [ref putFile:fileURL
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ }];
+
+ __block long uploadedBytes = -1;
+
+ [task observeStatus:FIRStorageTaskStatusSuccess
+ handler:^(FIRStorageTaskSnapshot *snapshot) {
+ XCTAssertEqualObjects([snapshot description], @"<State: Success>");
+ [expectation fulfill];
+ }];
+
+ [task observeStatus:FIRStorageTaskStatusProgress
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ XCTAssertTrue([[snapshot description] containsString:@"State: Progress"] ||
+ [[snapshot description] containsString:@"State: Resume"]);
+ NSProgress *progress = snapshot.progress;
+ XCTAssertGreaterThanOrEqual(progress.completedUnitCount, uploadedBytes);
+ uploadedBytes = progress.completedUnitCount;
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testPutFileWithSpecialCharacters {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testPutFileWithSpecialCharacters"];
+
+ NSString *fileName = @"hello&+@_ .txt";
+ FIRStorageReference *ref =
+ [self.storage referenceWithPath:[@"ios/public/" stringByAppendingString:fileName]];
+
+ NSData *data = [@"Hello World" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL = [tmpDirURL URLByAppendingPathComponent:fileName];
+ [data writeToURL:fileURL atomically:YES];
+
+ [ref putFile:fileURL
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ XCTAssertEqualObjects(fileName, metadata.name);
+ FIRStorageReference *download =
+ [self.storage referenceWithPath:[@"ios/public/" stringByAppendingString:fileName]];
+ [download metadataWithCompletion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ XCTAssertEqualObjects(fileName, metadata.name);
+ [expectation fulfill];
+ }];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimplePutDataNoMetadata {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimplePutDataNoMetadata"];
+
+ FIRStorageReference *ref =
+ [self.storage referenceWithPath:@"ios/public/testUnauthenticatedSimplePutDataNoMetadata"];
+
+ NSData *data = [@"Hello World" dataUsingEncoding:NSUTF8StringEncoding];
+
+ [ref putData:data
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimplePutFileNoMetadata {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimplePutFileNoMetadata"];
+
+ FIRStorageReference *ref =
+ [self.storage referenceWithPath:@"ios/public/testUnauthenticatedSimplePutFileNoMetadata"];
+
+ NSData *data = [@"Hello World" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL =
+ [[tmpDirURL URLByAppendingPathComponent:@"hello"] URLByAppendingPathExtension:@"txt"];
+ [data writeToURL:fileURL atomically:YES];
+
+ [ref putFile:fileURL
+ metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertNotNil(metadata, "Metadata should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimpleGetData {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimpleGetData"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/1mb"];
+
+ [ref dataWithMaxSize:1 * 1024 * 1024
+ completion:^(NSData *data, NSError *error) {
+ XCTAssertNotNil(data, "Data should not be nil");
+ XCTAssertNil(error, "Error should be nil");
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimpleGetDataTooSmall {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimpleGetDataTooSmall"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/1mb"];
+
+ /// Only allow 1kB size, which is smaller than our file
+ [ref dataWithMaxSize:1 * 1024
+ completion:^(NSData *data, NSError *error) {
+ XCTAssertEqual(data, nil);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeDownloadSizeExceeded);
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedSimpleGetFile {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimpleGetData"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/helloworld"];
+
+ NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL =
+ [[tmpDirURL URLByAppendingPathComponent:@"hello"] URLByAppendingPathExtension:@"txt"];
+
+ [ref putData:[@"Hello World" dataUsingEncoding:NSUTF8StringEncoding] metadata:nil
+ completion:^(FIRStorageMetadata *metadata, NSError *error)
+ {
+ FIRStorageDownloadTask *task = [ref writeToFile:fileURL];
+
+ [task observeStatus:FIRStorageTaskStatusSuccess
+ handler:^(FIRStorageTaskSnapshot *snapshot) {
+ NSString *data = [NSString stringWithContentsOfURL:fileURL
+ encoding:NSUTF8StringEncoding
+ error:NULL];
+ NSString *expectedData = @"Hello World";
+ XCTAssertEqualObjects(data, expectedData);
+ XCTAssertEqualObjects([snapshot description], @"<State: Success>");
+ [expectation fulfill];
+ }];
+
+ [task observeStatus:FIRStorageTaskStatusProgress
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ NSProgress *progress = snapshot.progress;
+ NSLog(@"%lld of %lld", progress.completedUnitCount, progress.totalUnitCount);
+ }];
+
+ [task observeStatus:FIRStorageTaskStatusFailure
+ handler:^(FIRStorageTaskSnapshot *snapshot) {
+ XCTAssertNil(snapshot.error);
+ [expectation fulfill];
+ }];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testCancelDownload {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testCancelDownload"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/1mb"];
+
+ NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL =
+ [[tmpDirURL URLByAppendingPathComponent:@"hello"] URLByAppendingPathExtension:@"dat"];
+
+ FIRStorageDownloadTask *task = [ref writeToFile:fileURL];
+
+ [task observeStatus:FIRStorageTaskStatusFailure
+ handler:^(FIRStorageTaskSnapshot *snapshot) {
+ XCTAssertTrue([[snapshot description] containsString:@"State: Failed"]);
+ [expectation fulfill];
+ }];
+
+ [task observeStatus:FIRStorageTaskStatusProgress
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ [task cancel];
+ }];
+
+ [self waitForExpectations];
+}
+
+- (void)testUnauthenticatedResumeGetFile {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedResumeGetFile"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/1mb"];
+
+ NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL =
+ [[tmpDirURL URLByAppendingPathComponent:@"hello"] URLByAppendingPathExtension:@"txt"];
+
+ __block long resumeAtBytes = 256 * 1024;
+ __block long downloadedBytes = 0;
+ __block double computationResult = 0;
+
+ FIRStorageDownloadTask *task = [ref writeToFile:fileURL];
+
+ [task observeStatus:FIRStorageTaskStatusSuccess
+ handler:^(FIRStorageTaskSnapshot *snapshot) {
+ XCTAssertEqualObjects([snapshot description], @"<State: Success>");
+ [expectation fulfill];
+ }];
+
+ [task observeStatus:FIRStorageTaskStatusProgress
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ XCTAssertTrue([[snapshot description] containsString:@"State: Progress"] ||
+ [[snapshot description] containsString:@"State: Resume"]);
+ NSProgress *progress = snapshot.progress;
+ XCTAssertGreaterThanOrEqual(progress.completedUnitCount, downloadedBytes);
+ downloadedBytes = progress.completedUnitCount;
+ if (progress.completedUnitCount > resumeAtBytes) {
+ // Making sure the main run loop is busy.
+ for (int i = 0; i < 500; ++i) {
+ dispatch_async(dispatch_get_main_queue(), ^ {
+ computationResult = sqrt(INT_MAX - i);
+ });
+ }
+ NSLog(@"Pausing");
+ [task pause];
+ resumeAtBytes = INT_MAX;
+ }
+ }];
+
+ [task observeStatus:FIRStorageTaskStatusPause
+ handler:^(FIRStorageTaskSnapshot *snapshot) {
+ XCTAssertEqualObjects([snapshot description], @"<State: Paused>");
+ NSLog(@"Resuming");
+ [task resume];
+ }];
+
+ [self waitForExpectations];
+ XCTAssertEqual(INT_MAX, resumeAtBytes);
+ XCTAssertEqualWithAccuracy(sqrt(INT_MAX - 499), computationResult, 0.1);
+}
+
+- (void)waitForExpectations {
+ [self waitForExpectationsWithTimeout:kFIRStorageIntegrationTestTimeout
+ handler:^(NSError *_Nullable error) {
+ if (error) {
+ NSLog(@"%@", error);
+ }
+ }];
+}
+
+@end
diff --git a/Example/Storage/Tests/Tests-Info.plist b/Example/Storage/Tests/Tests-Info.plist
new file mode 100644
index 0000000..169b6f7
--- /dev/null
+++ b/Example/Storage/Tests/Tests-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/Example/Storage/Tests/Unit/FIRStorageDeleteTests.m b/Example/Storage/Tests/Unit/FIRStorageDeleteTests.m
new file mode 100644
index 0000000..42a3b1a
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageDeleteTests.m
@@ -0,0 +1,164 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStorageDeleteTask.h"
+#import "FIRStorageTestHelpers.h"
+
+@interface FIRStorageDeleteTests : XCTestCase
+
+@property(strong, nonatomic) GTMSessionFetcherService *fetcherService;
+@property(strong, nonatomic) FIRStorageMetadata *metadata;
+@property(strong, nonatomic) FIRStorage *storage;
+@property(strong, nonatomic) id mockApp;
+
+@end
+
+@implementation FIRStorageDeleteTests
+
+- (void)setUp {
+ [super setUp];
+
+ NSDictionary *metadataDict = @{ @"bucket" : @"bucket", @"name" : @"path/to/object" };
+ self.metadata = [[FIRStorageMetadata alloc] initWithDictionary:metadataDict];
+
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"bucket.appspot.com");
+
+ self.mockApp = OCMClassMock([FIRApp class]);
+ OCMStub([self.mockApp name]).andReturn(kFIRStorageAppName);
+ OCMStub([(FIRApp *)self.mockApp options]).andReturn(mockOptions);
+
+ self.fetcherService = [[GTMSessionFetcherService alloc] init];
+ self.fetcherService.authorizer =
+ [[FIRStorageTokenAuthorizer alloc] initWithApp:self.mockApp
+ fetcherService:self.fetcherService];
+
+ self.storage = [FIRStorage storageForApp:self.mockApp];
+}
+
+- (void)tearDown {
+ self.fetcherService = nil;
+ self.storage = nil;
+ self.mockApp = nil;
+ [super tearDown];
+}
+
+- (void)testFetcherConfiguration {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testSuccessfulFetch"];
+
+ self.fetcherService.testBlock =
+ ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-retain-cycles"
+ XCTAssertEqualObjects(fetcher.request.URL, [FIRStorageTestHelpers objectURL]);
+#pragma clang diagnostic pop
+ XCTAssertEqualObjects(fetcher.request.HTTPMethod, @"DELETE");
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:200
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, nil);
+ };
+
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageDeleteTask *task = [[FIRStorageDeleteTask alloc] initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(NSError *error) {
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testSuccessfulFetch {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testSuccessfulFetch"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers successBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageDeleteTask *task = [[FIRStorageDeleteTask alloc] initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(NSError *error) {
+ XCTAssertEqual(error, nil);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchUnauthenticated {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchUnauthenticated"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers unauthenticatedBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageDeleteTask *task =
+ [[FIRStorageDeleteTask alloc] initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(NSError *error) {
+ XCTAssertEqual(error.code,
+ FIRStorageErrorCodeUnauthenticated);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchUnauthorized {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchUnauthorized"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers unauthorizedBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageDeleteTask *task =
+ [[FIRStorageDeleteTask alloc] initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(NSError *error) {
+ XCTAssertEqual(error.code,
+ FIRStorageErrorCodeUnauthorized);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchObjectDoesntExist {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchObjectDoesntExist"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers notFoundBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers notFoundPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageDeleteTask *task =
+ [[FIRStorageDeleteTask alloc] initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(NSError *error) {
+ XCTAssertEqual(error.code,
+ FIRStorageErrorCodeObjectNotFound);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStorageGetMetadataTests.m b/Example/Storage/Tests/Unit/FIRStorageGetMetadataTests.m
new file mode 100644
index 0000000..72a057d
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageGetMetadataTests.m
@@ -0,0 +1,188 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStorageGetMetadataTask.h"
+#import "FIRStorageTestHelpers.h"
+
+@interface FIRStorageGetMetadataTests : XCTestCase
+
+@property(strong, nonatomic) GTMSessionFetcherService *fetcherService;
+@property(strong, nonatomic) FIRStorageMetadata *metadata;
+@property(strong, nonatomic) FIRStorage *storage;
+@property(strong, nonatomic) id mockApp;
+
+@end
+
+@implementation FIRStorageGetMetadataTests
+
+- (void)setUp {
+ [super setUp];
+
+ NSDictionary *metadataDict = @{ @"bucket" : @"bucket", @"name" : @"path/to/object" };
+ self.metadata = [[FIRStorageMetadata alloc] initWithDictionary:metadataDict];
+
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"bucket.appspot.com");
+
+ self.mockApp = OCMClassMock([FIRApp class]);
+ OCMStub([self.mockApp name]).andReturn(kFIRStorageAppName);
+ OCMStub([(FIRApp *)self.mockApp options]).andReturn(mockOptions);
+
+ self.fetcherService = [[GTMSessionFetcherService alloc] init];
+ self.fetcherService.authorizer =
+ [[FIRStorageTokenAuthorizer alloc] initWithApp:self.mockApp
+ fetcherService:self.fetcherService];
+
+ self.storage = [FIRStorage storageForApp:self.mockApp];
+}
+
+- (void)tearDown {
+ self.fetcherService = nil;
+ self.storage = nil;
+ self.mockApp = nil;
+ [super tearDown];
+}
+
+- (void)testFetcherConfiguration {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testSuccessfulFetch"];
+
+ self.fetcherService.testBlock =
+ ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-retain-cycles"
+ XCTAssertEqualObjects(fetcher.request.URL, [FIRStorageTestHelpers objectURL]);
+#pragma clang diagnostic pop
+ XCTAssertEqualObjects(fetcher.request.HTTPMethod, @"GET");
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:200
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, nil);
+ };
+
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageGetMetadataTask *task = [[FIRStorageGetMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testSuccessfulFetch {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testSuccessfulFetch"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers successBlockWithMetadata:self.metadata];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageGetMetadataTask *task = [[FIRStorageGetMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertEqualObjects(self.metadata.bucket, metadata.bucket);
+ XCTAssertEqualObjects(self.metadata.name, metadata.name);
+ XCTAssertEqual(error, nil);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchUnauthenticated {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchUnauthenticated"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers unauthenticatedBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageGetMetadataTask *task = [[FIRStorageGetMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertEqual(metadata, nil);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeUnauthenticated);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchUnauthorized {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchUnauthorized"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers unauthorizedBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageGetMetadataTask *task = [[FIRStorageGetMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertEqual(metadata, nil);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeUnauthorized);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchObjectDoesntExist {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchObjectDoesntExist"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers notFoundBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers notFoundPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageGetMetadataTask *task = [[FIRStorageGetMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertEqual(metadata, nil);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeObjectNotFound);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchBadJSON {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchBadJSON"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers invalidJSONBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageGetMetadataTask *task = [[FIRStorageGetMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ completion:^(FIRStorageMetadata *metadata, NSError *error) {
+ XCTAssertEqual(metadata, nil);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeUnknown);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m b/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m
new file mode 100644
index 0000000..f5fb3b3
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m
@@ -0,0 +1,282 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStorageMetadata.h"
+#import "FIRStorageMetadata_Private.h"
+#import "FIRStorageUtils.h"
+
+@interface FIRStorageMetadataTests : XCTestCase
+
+@end
+
+@implementation FIRStorageMetadataTests
+
+- (void)testInitialzeNoMetadata {
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:@{}];
+ XCTAssertNotNil(metadata);
+}
+
+- (void)testInitialzeFullMetadata {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataCacheControl : @"max-age=3600, no-cache",
+ kFIRStorageMetadataContentDisposition : @"inline",
+ kFIRStorageMetadataContentEncoding : @"gzip",
+ kFIRStorageMetadataContentLanguage : @"en-us",
+ kFIRStorageMetadataContentType : @"application/octet-stream",
+ kFIRStorageMetadataCustomMetadata : @{@"foo" : @{@"bar" : @"baz"}},
+ kFIRStorageMetadataDownloadTokens : @"1234567890",
+ kFIRStorageMetadataGeneration : @"12345",
+ kFIRStorageMetadataMetageneration : @"67890",
+ kFIRStorageMetadataName : @"path/to/object",
+ kFIRStorageMetadataTimeCreated : @"1992-08-07T17:22:53.108Z",
+ kFIRStorageMetadataUpdated : @"2016-03-01T20:16:01.673Z"
+ };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotNil(metadata);
+ XCTAssertEqualObjects(metadata.bucket, metaDict[kFIRStorageMetadataBucket]);
+ XCTAssertEqualObjects(metadata.cacheControl, metaDict[kFIRStorageMetadataCacheControl]);
+ XCTAssertEqualObjects(metadata.contentDisposition,
+ metaDict[kFIRStorageMetadataContentDisposition]);
+ XCTAssertEqualObjects(metadata.contentEncoding, metaDict[kFIRStorageMetadataContentEncoding], );
+ XCTAssertEqualObjects(metadata.contentType, metaDict[kFIRStorageMetadataContentType]);
+ XCTAssertEqualObjects(metadata.customMetadata, metaDict[kFIRStorageMetadataCustomMetadata]);
+ NSString *URLFormat = @"https://firebasestorage.googleapis.com/v0/b/%@/o/%@?alt=media&token=%@";
+ NSString *URLString = [NSString
+ stringWithFormat:URLFormat, metaDict[kFIRStorageMetadataBucket],
+ [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
+ metaDict[kFIRStorageMetadataDownloadTokens]];
+ XCTAssertEqualObjects([metadata.downloadURL description], URLString);
+ NSString *generation = [NSString stringWithFormat:@"%lld", metadata.generation];
+ XCTAssertEqualObjects(generation, metaDict[kFIRStorageMetadataGeneration]);
+ NSString *metageneration = [NSString stringWithFormat:@"%lld", metadata.metageneration];
+ XCTAssertEqualObjects(metageneration, metaDict[kFIRStorageMetadataMetageneration]);
+ XCTAssertEqualObjects(metadata.path, metaDict[kFIRStorageMetadataName]);
+ XCTAssertEqualObjects([metadata RFC3339StringFromDate:metadata.timeCreated],
+ metaDict[kFIRStorageMetadataTimeCreated]);
+ XCTAssertEqualObjects([metadata RFC3339StringFromDate:metadata.updated],
+ metaDict[kFIRStorageMetadataUpdated]);
+}
+
+- (void)testDictionaryRepresentation {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataCacheControl : @"max-age=3600, no-cache",
+ kFIRStorageMetadataContentDisposition : @"inline",
+ kFIRStorageMetadataContentEncoding : @"gzip",
+ kFIRStorageMetadataContentLanguage : @"en-us",
+ kFIRStorageMetadataContentType : @"application/octet-stream",
+ kFIRStorageMetadataCustomMetadata : @{@"foo" : @{@"bar" : @"baz"}},
+ kFIRStorageMetadataDownloadTokens : @"1234567890",
+ kFIRStorageMetadataGeneration : @"12345",
+ kFIRStorageMetadataMetageneration : @"67890",
+ kFIRStorageMetadataName : @"path/to/object",
+ kFIRStorageMetadataTimeCreated : @"1992-08-07T17:22:53.108Z",
+ kFIRStorageMetadataUpdated : @"2016-03-01T20:16:01.673Z"
+ };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ NSDictionary *dictRepresentation = [metadata dictionaryRepresentation];
+ XCTAssertNotEqual(dictRepresentation, nil);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataBucket],
+ metaDict[kFIRStorageMetadataBucket]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataCacheControl],
+ metaDict[kFIRStorageMetadataCacheControl]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataContentDisposition],
+ metaDict[kFIRStorageMetadataContentDisposition]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataContentEncoding],
+ metaDict[kFIRStorageMetadataContentEncoding]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataContentLanguage],
+ metaDict[kFIRStorageMetadataContentLanguage]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataContentType],
+ metaDict[kFIRStorageMetadataContentType]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataCustomMetadata],
+ metaDict[kFIRStorageMetadataCustomMetadata]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataDownloadTokens],
+ metaDict[kFIRStorageMetadataDownloadTokens]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataGeneration],
+ metaDict[kFIRStorageMetadataGeneration]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataMetageneration],
+ metaDict[kFIRStorageMetadataMetageneration]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataName],
+ metaDict[kFIRStorageMetadataName]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataTimeCreated],
+ metaDict[kFIRStorageMetadataTimeCreated]);
+ XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataUpdated],
+ metaDict[kFIRStorageMetadataUpdated]);
+}
+
+- (void)testInitialzeNoDownloadTokensGetToken {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/object",
+ };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotNil(metadata);
+ XCTAssertEqual(metadata.downloadURL, nil);
+ XCTAssertEqual(metadata.downloadURLs, nil);
+}
+
+- (void)testInitialzeMultipleDownloadTokensGetToken {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataDownloadTokens : @"12345,67890",
+ kFIRStorageMetadataName : @"path/to/object",
+ };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotNil(metadata);
+ NSString *URLformat = @"https://firebasestorage.googleapis.com/v0/b/%@/o/%@?alt=media&token=%@";
+ NSString *URLString0 = [NSString
+ stringWithFormat:URLformat, metaDict[kFIRStorageMetadataBucket],
+ [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
+ @"12345"];
+ NSString *URLString1 = [NSString
+ stringWithFormat:URLformat, metaDict[kFIRStorageMetadataBucket],
+ [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
+ @"67890"];
+ XCTAssertEqualObjects([metadata.downloadURL absoluteString], URLString0);
+ XCTAssertEqualObjects([metadata.downloadURLs[0] absoluteString], URLString0);
+ XCTAssertEqualObjects([metadata.downloadURLs[1] absoluteString], URLString1);
+}
+
+- (void)testMultipleDownloadURLsGetToken {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/object",
+ };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ NSString *URLformat = @"https://firebasestorage.googleapis.com/v0/b/%@/o/%@?alt=media&token=%@";
+ NSString *URLString0 = [NSString
+ stringWithFormat:URLformat, metaDict[kFIRStorageMetadataBucket],
+ [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
+ @"12345"];
+ NSString *URLString1 = [NSString
+ stringWithFormat:URLformat, metaDict[kFIRStorageMetadataBucket],
+ [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
+ @"67890"];
+ NSURL *URL0 = [NSURL URLWithString:URLString0];
+ NSURL *URL1 = [NSURL URLWithString:URLString1];
+ NSArray *downloadURLs = @[ URL0, URL1 ];
+ [metadata setValue:downloadURLs forKey:@"downloadURLs"];
+ NSDictionary *newMetaDict = metadata.dictionaryRepresentation;
+ XCTAssertEqualObjects(newMetaDict[kFIRStorageMetadataDownloadTokens], @"12345,67890");
+}
+
+- (void)testInitialzeMetadataWithFile {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/file",
+ };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ [metadata setType:FIRStorageMetadataTypeFile];
+ XCTAssertEqual(metadata.isFile, YES);
+ XCTAssertEqual(metadata.isFolder, NO);
+}
+
+- (void)testInitialzeMetadataWithFolder {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/folder/",
+ };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ [metadata setType:FIRStorageMetadataTypeFolder];
+ XCTAssertEqual(metadata.isFolder, YES);
+ XCTAssertEqual(metadata.isFile, NO);
+}
+
+- (void)testReflexiveMetadataEquality {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/object",
+ };
+ FIRStorageMetadata *metadata0 = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ FIRStorageMetadata *metadata1 = metadata0;
+ XCTAssertEqual(metadata0, metadata1);
+ XCTAssertEqualObjects(metadata0, metadata1);
+}
+
+- (void)testNonsenseMetadataEquality {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/object",
+ };
+ FIRStorageMetadata *metadata0 = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotEqualObjects(metadata0, @"I'm not object metadata!");
+}
+
+- (void)testMetadataEquality {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/object",
+ };
+ FIRStorageMetadata *metadata0 = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ FIRStorageMetadata *metadata1 = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotEqual(metadata0, metadata1);
+ XCTAssertEqualObjects(metadata0, metadata1);
+}
+
+- (void)testMetadataCopy {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/object",
+ };
+ FIRStorageMetadata *metadata0 = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ FIRStorageMetadata *metadata1 = [metadata0 copy];
+ XCTAssertNotEqual(metadata0, metadata1);
+ XCTAssertEqualObjects(metadata0, metadata1);
+}
+
+- (void)testMetadataHashEquality {
+ NSDictionary *metaDict = @{
+ kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataName : @"path/to/object",
+ };
+ FIRStorageMetadata *metadata0 = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ FIRStorageMetadata *metadata1 = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotEqual(metadata0, metadata1);
+ XCTAssertEqual([metadata0 hash], [metadata1 hash]);
+}
+
+- (void)testZuluTimeOffset {
+ NSDictionary *metaDict = @{ kFIRStorageMetadataTimeCreated : @"1992-08-07T17:22:53.108Z" };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotNil(metadata.timeCreated);
+}
+
+- (void)testZuluZeroTimeOffset {
+ NSDictionary *metaDict = @{ kFIRStorageMetadataTimeCreated : @"1992-08-07T17:22:53.108+0000" };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotNil(metadata.timeCreated);
+}
+
+- (void)testGoogleStandardTimeOffset {
+ NSDictionary *metaDict = @{ kFIRStorageMetadataTimeCreated : @"1992-08-07T17:22:53.108-0700" };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotNil(metadata.timeCreated);
+}
+
+- (void)testUnspecifiedTimeOffset {
+ NSDictionary *metaDict = @{ kFIRStorageMetadataTimeCreated : @"1992-08-07T17:22:53.108-0000" };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNotNil(metadata.timeCreated);
+}
+
+- (void)testNoTimeOffset {
+ NSDictionary *metaDict = @{ kFIRStorageMetadataTimeCreated : @"1992-08-07T17:22:53.108" };
+ FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
+ XCTAssertNil(metadata.timeCreated);
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStoragePathTests.m b/Example/Storage/Tests/Unit/FIRStoragePathTests.m
new file mode 100644
index 0000000..017f41d
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStoragePathTests.m
@@ -0,0 +1,234 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStoragePath.h"
+
+@interface FIRStoragePathTests : XCTestCase
+
+@end
+
+@implementation FIRStoragePathTests
+
+- (void)testGSURI {
+ FIRStoragePath *path = [FIRStoragePath pathFromString:@"gs://bucket/path/to/object"];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertEqualObjects(path.object, @"path/to/object");
+}
+
+- (void)testHTTPURL {
+ NSString *httpURL =
+ @"http://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object?token=signed_url_params";
+ FIRStoragePath *path = [FIRStoragePath pathFromString:httpURL];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertEqualObjects(path.object, @"path/to/object");
+}
+
+- (void)testGSURINoPath {
+ FIRStoragePath *path = [FIRStoragePath pathFromString:@"gs://bucket/"];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertNil(path.object);
+}
+
+- (void)testHTTPURLNoPath {
+ FIRStoragePath *path =
+ [FIRStoragePath pathFromString:@"http://firebasestorage.googleapis.com/v0/b/bucket/"];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertNil(path.object);
+}
+
+- (void)testGSURINoTrailingSlash {
+ FIRStoragePath *path = [FIRStoragePath pathFromString:@"gs://bucket"];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertNil(path.object);
+}
+
+- (void)testHTTPURLNoTrailingSlash {
+ FIRStoragePath *path =
+ [FIRStoragePath pathFromString:@"http://firebasestorage.googleapis.com/v0/b/bucket"];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertNil(path.object);
+}
+
+- (void)testGSURIPercentEncoding {
+ FIRStoragePath *path = [FIRStoragePath pathFromString:@"gs://bucket/?/%/#"];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertEqualObjects(path.object, @"?/%/#");
+}
+
+- (void)testHTTPURLPercentEncoding {
+ NSString *httpURL =
+ @"http://firebasestorage.googleapis.com/v0/b/bucket/o/%3F/%25/%23?token=signed_url_params";
+ FIRStoragePath *path = [FIRStoragePath pathFromString:httpURL];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertEqualObjects(path.object, @"?/%/#");
+}
+
+- (void)testHTTPURLNoToken {
+ NSString *httpURL = @"http://firebasestorage.googleapis.com/v0/b/bucket/o/%23hashtag/no/token";
+ FIRStoragePath *path = [FIRStoragePath pathFromString:httpURL];
+ XCTAssertEqualObjects(path.bucket, @"bucket");
+ XCTAssertEqualObjects(path.object, @"#hashtag/no/token");
+}
+
+- (void)testGSURIThrowsOnNoBucket {
+ XCTAssertThrows([FIRStoragePath pathFromString:@"gs://"]);
+}
+
+- (void)testHTTPURLThrowsOnNoBucket {
+ XCTAssertThrows([FIRStoragePath pathFromString:@"http://firebasestorage.googleapis.com/"]);
+}
+
+- (void)testThrowsOnInvalidScheme {
+ NSString *ftpURL = @"ftp://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object";
+ XCTAssertThrows([FIRStoragePath pathFromString:ftpURL]);
+}
+
+- (void)testHTTPURLNilIncorrectHost {
+ NSString *httpURL = @"http://foo.google.com/v0/b/bucket/o/%3F/%25/%23?token=signed_url_params";
+ XCTAssertThrows([FIRStoragePath pathFromString:httpURL]);
+}
+
+- (void)testchildToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:@"object"];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/object");
+}
+
+- (void)testChildByAppendingNilToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:nil];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/");
+}
+
+- (void)testChildByAppendingNoPathToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:@""];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/");
+}
+
+- (void)testChildByAppendingLeadingSlashChildToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:@"/object"];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/object");
+}
+
+- (void)testChildByAppendingTrailingSlashChildToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:@"object/"];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/object");
+}
+
+- (void)testChildByAppendingLeadingAndTrailingSlashChildToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:@"/object/"];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/object");
+}
+
+- (void)testChildByAppendingMultipleChildrenToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:@"path/to/object"];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/path/to/object");
+}
+
+- (void)testChildByAppendingMultipleChildrenWithMultipleSlashesToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:@"/path//to///object////"];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/path/to/object");
+}
+
+- (void)testChildByAppendingOnlySlashesToRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *childPath = [path child:@"//////////"];
+ XCTAssertEqualObjects([childPath stringValue], @"gs://bucket/");
+}
+
+- (void)testParentAtRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *parent = [path parent];
+ XCTAssertNil(parent);
+}
+
+- (void)testParentChildPath {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"path/to/object"];
+ FIRStoragePath *parent = [path parent];
+ XCTAssertEqualObjects([parent stringValue], @"gs://bucket/path/to");
+}
+
+- (void)testParentChildPathSlashes {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"/path//to///"];
+ FIRStoragePath *parent = [path parent];
+ XCTAssertEqualObjects([parent stringValue], @"gs://bucket/path");
+}
+
+- (void)testParentChildPathOnlySlashs {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"/////"];
+ FIRStoragePath *parent = [path parent];
+ XCTAssertNil(parent);
+}
+
+- (void)testRootAtRoot {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *root = [path root];
+ XCTAssertEqualObjects([root stringValue], @"gs://bucket/");
+}
+
+- (void)testRootAtChildPath {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"path/to/object"];
+ FIRStoragePath *root = [path root];
+ XCTAssertEqualObjects([root stringValue], @"gs://bucket/");
+}
+
+- (void)testRootAtSlashPath {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"//////////"];
+ FIRStoragePath *root = [path root];
+ XCTAssertEqualObjects([root stringValue], @"gs://bucket/");
+}
+
+- (void)testCopy {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"object"];
+ FIRStoragePath *copiedPath = [path copy];
+ XCTAssertNotEqual(copiedPath, path);
+ XCTAssertEqualObjects(copiedPath, path);
+}
+
+- (void)testCopyNoBucket {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:nil object:@"object"];
+#pragma clang diagnostic pop
+ FIRStoragePath *copiedPath = [path copy];
+ XCTAssertNotEqual(copiedPath, path);
+ XCTAssertEqualObjects(copiedPath, path);
+}
+
+- (void)testCopyNoObject {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStoragePath *copiedPath = [path copy];
+ XCTAssertNotEqual(copiedPath, path);
+ XCTAssertEqualObjects(copiedPath, path);
+}
+
+- (void)testCopyNothing {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:nil object:nil];
+#pragma clang diagnostic pop
+ FIRStoragePath *copiedPath = [path copy];
+ XCTAssertNotEqual(copiedPath, path);
+ XCTAssertEqualObjects(copiedPath, path);
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStorageReferenceTests.m b/Example/Storage/Tests/Unit/FIRStorageReferenceTests.m
new file mode 100644
index 0000000..e54896c
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageReferenceTests.m
@@ -0,0 +1,163 @@
+// Copyright 2017 Google
+//
+// 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 "FirebaseStorage.h"
+
+#import "FIRStorageReference_Private.h"
+#import "FIRStorageTestHelpers.h"
+
+@interface FIRStorageReferenceTests : XCTestCase
+
+@property(strong, nonatomic) FIRStorage *storage;
+
+@end
+
+@implementation FIRStorageReferenceTests
+
+- (void)setUp {
+ [super setUp];
+
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"bucket");
+
+ id mockApp = OCMClassMock([FIRApp class]);
+ OCMStub([mockApp name]).andReturn(kFIRStorageAppName);
+ OCMStub([(FIRApp *)mockApp options]).andReturn(mockOptions);
+ self.storage = [FIRStorage storageForApp:mockApp];
+}
+
+- (void)tearDown {
+ self.storage = nil;
+ [super tearDown];
+}
+
+- (void)testRoot {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/path/to/object"];
+ XCTAssertEqualObjects([ref.root stringValue], @"gs://bucket/");
+}
+
+- (void)testRootWithNoPath {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ XCTAssertEqualObjects([ref.root stringValue], @"gs://bucket/");
+}
+
+- (void)testSingleChild {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ FIRStorageReference *childRef = [ref child:@"path"];
+ XCTAssertEqualObjects([childRef stringValue], @"gs://bucket/path");
+}
+
+- (void)testMultipleChildrenSingleString {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ FIRStorageReference *childRef = [ref child:@"path/to/object"];
+ XCTAssertEqualObjects([childRef stringValue], @"gs://bucket/path/to/object");
+}
+
+- (void)testMultipleChildrenMultipleStrings {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ FIRStorageReference *childRef = [ref child:@"path"];
+ childRef = [childRef child:@"to"];
+ childRef = [childRef child:@"object"];
+ XCTAssertEqualObjects([childRef stringValue], @"gs://bucket/path/to/object");
+}
+
+- (void)testSameChildDifferentRef {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ FIRStorageReference *firstRef = [ref child:@"1"];
+ FIRStorageReference *secondRef = [ref child:@"1"];
+ XCTAssertEqualObjects([ref stringValue], @"gs://bucket/");
+ XCTAssertEqualObjects(firstRef, secondRef);
+ XCTAssertNotEqual(firstRef, secondRef);
+}
+
+- (void)testDifferentChildDifferentRef {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ FIRStorageReference *firstRef = [ref child:@"1"];
+ FIRStorageReference *secondRef = [ref child:@"2"];
+ XCTAssertEqualObjects([ref stringValue], @"gs://bucket/");
+ XCTAssertNotEqual(firstRef, secondRef);
+}
+
+- (void)testChildWithTrailingSlash {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/path/to/object/"];
+ XCTAssertEqualObjects([ref stringValue], @"gs://bucket/path/to/object");
+}
+
+- (void)testChildWithLeadingSlash {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket//path/to/object/"];
+ XCTAssertEqualObjects([ref stringValue], @"gs://bucket/path/to/object");
+}
+
+- (void)testChildCompressSlashes {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket//path///to////object////"];
+ XCTAssertEqualObjects([ref stringValue], @"gs://bucket/path/to/object");
+}
+
+- (void)testParent {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/path/to/object"];
+ FIRStorageReference *parentRef = [ref parent];
+ XCTAssertEqualObjects([parentRef stringValue], @"gs://bucket/path/to");
+}
+
+- (void)testParentToRoot {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/path"];
+ FIRStorageReference *parentRef = [ref parent];
+ XCTAssertEqualObjects([parentRef stringValue], @"gs://bucket/");
+}
+
+- (void)testParentToRootTrailingSlash {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/path/"];
+ FIRStorageReference *parentRef = [ref parent];
+ XCTAssertEqualObjects([parentRef stringValue], @"gs://bucket/");
+}
+
+- (void)testParentAtRoot {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ FIRStorageReference *parentRef = [ref parent];
+ XCTAssertNil(parentRef);
+}
+
+- (void)testBucket {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/path/to/object"];
+ XCTAssertEqualObjects(ref.bucket, @"bucket");
+}
+
+- (void)testName {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/path/to/object"];
+ XCTAssertEqualObjects(ref.name, @"object");
+}
+
+- (void)testNameNoObject {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ XCTAssertEqualObjects(ref.name, @"");
+}
+
+- (void)testFullPath {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/path/to/object"];
+ XCTAssertEqualObjects(ref.fullPath, @"path/to/object");
+}
+
+- (void)testFullPathNoObject {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ XCTAssertEqualObjects(ref.fullPath, @"");
+}
+
+- (void)testCopy {
+ FIRStorageReference *ref = [self.storage referenceForURL:@"gs://bucket/"];
+ FIRStorageReference *copiedRef = [ref copy];
+ XCTAssertEqualObjects(ref, copiedRef);
+ XCTAssertNotEqual(ref, copiedRef);
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStorageTestHelpers.h b/Example/Storage/Tests/Unit/FIRStorageTestHelpers.h
new file mode 100644
index 0000000..4489c07
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageTestHelpers.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "FIRAppInternal.h"
+
+#import <Foundation/Foundation.h>
+#import <OCMock/OCMock.h>
+#import <XCTest/XCTest.h>
+
+#import "FIRApp.h"
+#import "FIROptions.h"
+
+#import "FIRStorageConstants.h"
+#import "FIRStorageConstants_Private.h"
+#import "FIRStorageErrors.h"
+#import "FIRStorageMetadata.h"
+#import "FIRStorageReference.h"
+#import "FIRStorageReference_Private.h"
+#import "FIRStorageTask.h"
+#import "FIRStorageTask_Private.h"
+#import "FIRStorageTokenAuthorizer.h"
+#import "FIRStorageUtils.h"
+
+#import <GTMSessionFetcher/GTMSessionFetcher.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+FOUNDATION_EXPORT NSString *const kGoogleHTTPErrorDomain;
+FOUNDATION_EXPORT NSString *const kHTTPVersion;
+FOUNDATION_EXPORT NSString *const kUnauthenticatedResponseString;
+FOUNDATION_EXPORT NSString *const kUnauthorizedResponseString;
+FOUNDATION_EXPORT NSString *const kNotFoundResponseString;
+FOUNDATION_EXPORT NSString *const kInvalidJSONResponseString;
+FOUNDATION_EXPORT NSString *const kFIRStorageValidURL;
+FOUNDATION_EXPORT NSString *const kFIRStorageNotFoundURL;
+FOUNDATION_EXPORT NSString *const kFIRStorageTestAuthToken;
+FOUNDATION_EXPORT NSString *const kFIRStorageAppName;
+
+/**
+ * Standard timeout for all async tests.
+ */
+static NSTimeInterval kExpectationTimeoutSeconds = 10;
+
+@interface FIRStorageTestHelpers : NSObject
+
+/**
+ * Returns a valid URL for an object stored.
+ */
++ (NSURL *)objectURL;
+
+/**
+ * Returns a valid URL for a bucket.
+ */
++ (NSURL *)bucketURL;
+
+/**
+ * Returns a valid URL for an object not found in the current storage bucket.
+ */
++ (NSURL *)notFoundURL;
+
+/**
+ * Returns a valid FIRStoragePath for an object stored.
+ */
++ (FIRStoragePath *)objectPath;
+
+/**
+ * Returns a valid FIRStoragePath for a bucket (no object).
+ */
++ (FIRStoragePath *)bucketPath;
+
+/**
+ * Returns a valid FIRStoragePath for an object not found in the current storage bucket.
+ */
++ (FIRStoragePath *)notFoundPath;
+
+/**
+ * Returns a successful response block.
+ */
++ (GTMSessionFetcherTestBlock)successBlock;
+
+/**
+ * Returns a successful response block containing object metadata.
+ * @param metadata Metadata returned in the request.
+ */
++ (GTMSessionFetcherTestBlock)successBlockWithMetadata:(nullable FIRStorageMetadata *)metadata;
+
+/**
+ * Returns a unsuccessful response block due to improper authentication.
+ */
++ (GTMSessionFetcherTestBlock)unauthenticatedBlock;
+
+/**
+ * Returns a unsuccessful response block due to improper authorization.
+ */
++ (GTMSessionFetcherTestBlock)unauthorizedBlock;
+
+/**
+ * Returns a unsuccessful response block due the object not being found.
+ */
++ (GTMSessionFetcherTestBlock)notFoundBlock;
+
+/**
+ * Returns a unsuccessful response block due invalid JSON returned by the server.
+ */
++ (GTMSessionFetcherTestBlock)invalidJSONBlock;
+
+/**
+ * Waits for the given test case to time out by wrapping -waitForExpectation.
+ */
++ (void)waitForExpectation:(id)test;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Storage/Tests/Unit/FIRStorageTestHelpers.m b/Example/Storage/Tests/Unit/FIRStorageTestHelpers.m
new file mode 100644
index 0000000..fef67ef
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageTestHelpers.m
@@ -0,0 +1,128 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStorageTestHelpers.h"
+
+NSString *const kGoogleHTTPErrorDomain = @"com.google.HTTPStatus";
+NSString *const kHTTPVersion = @"HTTP/1.1";
+NSString *const kUnauthenticatedResponseString =
+ @"<html><body><p>User not authenticated. Authentication via Authorization header required. "
+ @"Authorization Header does not match expected format of 'Authorization: Firebase "
+ @"<JWT>'.</p></body></html>";
+NSString *const kUnauthorizedResponseString =
+ @"<html><body><p>User not authorized. Authentication via Authorization header required. "
+ @"Authorization Header does not match expected format of 'Authorization: Firebase "
+ @"<JWT>'.</p></body></html>";
+NSString *const kNotFoundResponseString = @"<html><body><p>Object not found.</p></body></html>";
+NSString *const kInvalidJSONResponseString = @"This is not a JSON object";
+NSString *const kFIRStorageObjectURL =
+ @"https://firebasestorage.googleapis.com/v0/b/bucket/o/object";
+NSString *const kFIRStorageBucketURL = @"https://firebasestorage.googleapis.com/v0/b/bucket/o";
+NSString *const kFIRStorageNotFoundURL =
+ @"https://firebasestorage.googleapis.com/v0/b/bucket/o/i/dont/exist";
+NSString *const kFIRStorageTestAuthToken = @"1234-5678-9012-3456-7890";
+NSString *const kFIRStorageAppName = @"app";
+
+@implementation FIRStorageTestHelpers
+
++ (NSURL *)objectURL {
+ return [NSURL URLWithString:kFIRStorageObjectURL];
+}
+
++ (NSURL *)bucketURL {
+ return [NSURL URLWithString:kFIRStorageBucketURL];
+}
+
++ (NSURL *)notFoundURL {
+ return [NSURL URLWithString:kFIRStorageNotFoundURL];
+}
+
++ (FIRStoragePath *)objectPath {
+ return [FIRStoragePath pathFromString:kFIRStorageObjectURL];
+}
+
++ (FIRStoragePath *)bucketPath {
+ return [FIRStoragePath pathFromString:kFIRStorageBucketURL];
+}
+
++ (FIRStoragePath *)notFoundPath {
+ return [FIRStoragePath pathFromString:kFIRStorageNotFoundURL];
+}
+
++ (GTMSessionFetcherTestBlock)successBlock {
+ return [FIRStorageTestHelpers successBlockWithMetadata:nil];
+}
+
++ (GTMSessionFetcherTestBlock)successBlockWithMetadata:(nullable FIRStorageMetadata *)metadata {
+ NSData *data;
+ if (metadata) {
+ data = [NSData frs_dataFromJSONDictionary:[metadata dictionaryRepresentation]];
+ }
+ return [FIRStorageTestHelpers blockForData:data statusCode:200];
+}
+
++ (GTMSessionFetcherTestBlock)unauthenticatedBlock {
+ NSData *data = [kUnauthenticatedResponseString dataUsingEncoding:NSUTF8StringEncoding];
+ return [FIRStorageTestHelpers blockForData:data statusCode:401];
+}
+
++ (GTMSessionFetcherTestBlock)unauthorizedBlock {
+ NSData *data = [kUnauthorizedResponseString dataUsingEncoding:NSUTF8StringEncoding];
+ return [FIRStorageTestHelpers blockForData:data statusCode:403];
+}
+
++ (GTMSessionFetcherTestBlock)notFoundBlock {
+ NSData *data = [kNotFoundResponseString dataUsingEncoding:NSUTF8StringEncoding];
+ return [FIRStorageTestHelpers blockForData:data statusCode:404];
+}
+
++ (GTMSessionFetcherTestBlock)invalidJSONBlock {
+ NSData *data = [kInvalidJSONResponseString dataUsingEncoding:NSUTF8StringEncoding];
+ return [FIRStorageTestHelpers blockForData:data statusCode:200];
+}
+
+#pragma mark - Private methods
+
++ (GTMSessionFetcherTestBlock)blockForData:(nullable NSData *)data statusCode:(NSInteger)code {
+ GTMSessionFetcherTestBlock block =
+ ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:code
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ NSError *error;
+ if (code >= 400) {
+ NSDictionary *userInfo;
+ if (data) {
+ userInfo = @{ @"data" : data };
+ }
+ error = [NSError errorWithDomain:kGoogleHTTPErrorDomain code:code userInfo:userInfo];
+ }
+
+ response(httpResponse, data, error);
+ };
+ return block;
+}
+
++ (void)waitForExpectation:(id)test {
+ [test waitForExpectationsWithTimeout:kExpectationTimeoutSeconds
+ handler:^(NSError *_Nullable error) {
+ if (error) {
+ NSLog(@"Error: %@", error);
+ }
+ }];
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStorageTests.m b/Example/Storage/Tests/Unit/FIRStorageTests.m
new file mode 100644
index 0000000..1b45295
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageTests.m
@@ -0,0 +1,214 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStorageTestHelpers.h"
+
+#import "FIRStorageReference.h"
+#import "FIRStorageReference_Private.h"
+#import "FIRStorage_Private.h"
+
+@interface FIRStorageTests : XCTestCase
+
+@property(strong, nonatomic) id app;
+
+@end
+
+@implementation FIRStorageTests
+
+- (void)setUp {
+ [super setUp];
+
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"bucket");
+
+ self.app = OCMClassMock([FIRApp class]);
+ OCMStub([self.app name]).andReturn(kFIRStorageAppName);
+ OCMStub([(FIRApp *)self.app options]).andReturn(mockOptions);
+}
+
+- (void)tearDown {
+ self.app = nil;
+ [super tearDown];
+}
+
+- (void)testBucketNotEnforced {
+ FIROptions * mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"");
+ FIRApp *app = OCMClassMock([FIRApp class]);
+ OCMStub([app name]).andReturn(kFIRStorageAppName);
+ OCMStub([(FIRApp *)app options]).andReturn(mockOptions);
+
+ FIRStorage *storage = [FIRStorage storageForApp:app];
+ [storage referenceForURL:@"gs://benwu-test1.storage.firebase.com/child"];
+ [storage referenceForURL:@"gs://benwu-test2.storage.firebase.com/child"];
+}
+
+- (void)testBucketEnforced {
+ FIRStorage *storage = [FIRStorage storageForApp:self.app
+ URL:@"gs://benwu-test1.storage.firebase.com"];
+ [storage referenceForURL:@"gs://benwu-test1.storage.firebase.com/child"];
+ storage = [FIRStorage storageForApp:self.app URL:@"gs://benwu-test1.storage.firebase.com/"];
+ [storage referenceForURL:@"gs://benwu-test1.storage.firebase.com/child"];
+ XCTAssertThrows([storage referenceForURL:@"gs://benwu-test2.storage.firebase.com/child"]);
+}
+
+- (void) testInitWithCustomUrl {
+ FIRStorage *storage = [FIRStorage storageForApp:self.app URL:@"gs://foo-bar.appspot.com"];
+ XCTAssertEqualObjects(@"gs://foo-bar.appspot.com/", [[storage reference] description]);
+ storage = [FIRStorage storageForApp:self.app URL:@"gs://foo-bar.appspot.com/"];
+ XCTAssertEqualObjects(@"gs://foo-bar.appspot.com/", [[storage reference] description]);
+}
+
+- (void) testInitWithWrongScheme {
+ XCTAssertThrows([FIRStorage storageForApp:self.app URL:@"http://foo-bar.appspot.com"]);
+}
+
+- (void) testInitWithNoScheme {
+ XCTAssertThrows([FIRStorage storageForApp:self.app URL:@"foo-bar.appspot.com"]);
+}
+
+- (void) testInitWithNilURL {
+ XCTAssertThrows([FIRStorage storageForApp:self.app URL:nil]);
+}
+
+- (void) testInitWithPath {
+ XCTAssertThrows([FIRStorage storageForApp:self.app URL:@"gs://foo-bar.appspot.com/child"]);
+}
+
+- (void) testInitWithDefaultAndCustomUrl {
+ FIRStorage *customInstance = [FIRStorage storageForApp:self.app
+ URL:@"gs://foo-bar.appspot.com"];
+ FIRStorage *defaultInstance = [FIRStorage storageForApp:self.app];
+ XCTAssertEqualObjects(@"gs://foo-bar.appspot.com/", [[customInstance reference] description]);
+ XCTAssertEqualObjects(@"gs://bucket/", [[defaultInstance reference] description]);
+}
+
+- (void)testStorageDefaultApp {
+ FIRStorage *storage = [FIRStorage storageForApp:self.app];
+ XCTAssertEqualObjects(storage.app.name, ((FIRApp *)self.app).name);
+ XCTAssertNotNil(storage.fetcherServiceForApp);
+}
+
+- (void)testStorageCustomApp {
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"bucket");
+ id secondApp = OCMClassMock([FIRApp class]);
+ OCMStub([secondApp name]).andReturn(@"secondApp");
+ OCMStub([(FIRApp *)secondApp options]).andReturn(mockOptions);
+ FIRStorage *storage = [FIRStorage storageForApp:secondApp];
+ XCTAssertNotEqual(storage.app.name, ((FIRApp *)self.app).name);
+ XCTAssertNotNil(storage.fetcherServiceForApp);
+ XCTAssertNotEqualObjects(storage.fetcherServiceForApp,
+ [FIRStorage storageForApp:self.app].fetcherServiceForApp);
+}
+
+- (void)testStorageNoBucketInConfig {
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(nil);
+ id secondApp = OCMClassMock([FIRApp class]);
+ OCMStub([secondApp name]).andReturn(@"secondApp");
+ OCMStub([(FIRApp *)secondApp options]).andReturn(mockOptions);
+ XCTAssertThrows([FIRStorage storageForApp:secondApp]);
+}
+
+- (void)testStorageEmptyBucketInConfig {
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"");
+ id secondApp = OCMClassMock([FIRApp class]);
+ OCMStub([secondApp name]).andReturn(@"secondApp");
+ OCMStub([(FIRApp *)secondApp options]).andReturn(mockOptions);
+ FIRStorage *storage = [FIRStorage storageForApp:secondApp];
+ FIRStorageReference *storageRef = [storage referenceForURL:@"gs://bucket/path/to/object"];
+ XCTAssertEqualObjects(storageRef.bucket, @"bucket");
+}
+
+- (void)testStorageWrongBucketInConfig {
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"notMyBucket");
+ id secondApp = OCMClassMock([FIRApp class]);
+ OCMStub([secondApp name]).andReturn(@"secondApp");
+ OCMStub([(FIRApp *)secondApp options]).andReturn(mockOptions);
+ FIRStorage *storage = [FIRStorage storageForApp:secondApp];
+ XCTAssertEqualObjects([(FIRApp *)secondApp options].storageBucket, @"notMyBucket");
+ XCTAssertThrows([storage referenceForURL:@"gs://bucket/path/to/object"]);
+}
+
+- (void)testRefDefaultApp {
+ FIRStorageReference *convenienceRef =
+ [[FIRStorage storageForApp:self.app] referenceForURL:@"gs://bucket/path/to/object"];
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"path/to/object"];
+ FIRStorageReference *builtRef =
+ [[FIRStorageReference alloc] initWithStorage:[FIRStorage storageForApp:self.app] path:path];
+ XCTAssertEqualObjects([convenienceRef description], [builtRef description]);
+ XCTAssertEqualObjects(convenienceRef.storage.app, builtRef.storage.app);
+}
+
+- (void)testRefCustomApp {
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"bucket");
+ id secondApp = OCMClassMock([FIRApp class]);
+ OCMStub([secondApp name]).andReturn(@"secondApp");
+ OCMStub([(FIRApp *)secondApp options]).andReturn(mockOptions);
+ FIRStorageReference *convenienceRef =
+ [[FIRStorage storageForApp:secondApp] referenceForURL:@"gs://bucket/path/to/object"];
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"path/to/object"];
+ FIRStorageReference *builtRef =
+ [[FIRStorageReference alloc] initWithStorage:[FIRStorage storageForApp:secondApp] path:path];
+ XCTAssertEqualObjects([convenienceRef description], [builtRef description]);
+ XCTAssertEqualObjects(convenienceRef.storage.app, builtRef.storage.app);
+}
+
+- (void)testRootRefDefaultApp {
+ FIRStorageReference *convenienceRef = [[FIRStorage storageForApp:self.app] reference];
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ FIRStorageReference *builtRef =
+ [[FIRStorageReference alloc] initWithStorage:[FIRStorage storageForApp:self.app] path:path];
+ XCTAssertEqualObjects([convenienceRef description], [builtRef description]);
+ XCTAssertEqualObjects(convenienceRef.storage.app, builtRef.storage.app);
+}
+
+- (void)testRefWithPathDefaultApp {
+ FIRStorageReference *convenienceRef =
+ [[FIRStorage storageForApp:self.app] referenceWithPath:@"path/to/object"];
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"path/to/object"];
+ FIRStorageReference *builtRef =
+ [[FIRStorageReference alloc] initWithStorage:[FIRStorage storageForApp:self.app] path:path];
+ XCTAssertEqualObjects([convenienceRef description], [builtRef description]);
+ XCTAssertEqualObjects(convenienceRef.storage.app, builtRef.storage.app);
+}
+
+- (void)testEqual {
+ FIRStorage *storage = [FIRStorage storageForApp:self.app];
+ FIRStorage *copy = [storage copy];
+ XCTAssertEqualObjects(storage.app.name, copy.app.name);
+}
+
+- (void)testNotEqual {
+ FIRStorage *storage = [FIRStorage storageForApp:self.app];
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"bucket");
+ id secondApp = OCMClassMock([FIRApp class]);
+ OCMStub([secondApp name]).andReturn(@"secondApp");
+ OCMStub([(FIRApp *)secondApp options]).andReturn(mockOptions);
+ FIRStorage *secondStorage = [FIRStorage storageForApp:secondApp];
+ XCTAssertNotEqualObjects(storage, secondStorage);
+}
+
+- (void)testHash {
+ FIRStorage *storage = [FIRStorage storageForApp:self.app];
+ FIRStorage *copy = [storage copy];
+ XCTAssertEqual([storage hash], [copy hash]);
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStorageTokenAuthorizerTests.m b/Example/Storage/Tests/Unit/FIRStorageTokenAuthorizerTests.m
new file mode 100644
index 0000000..e98ffbb
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageTokenAuthorizerTests.m
@@ -0,0 +1,226 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStorageTestHelpers.h"
+
+@interface FIRStorageTokenAuthorizerTests : XCTestCase
+
+@property(strong, nonatomic) GTMSessionFetcher *fetcher;
+@property(strong, nonatomic) id mockApp;
+
+@end
+
+@implementation FIRStorageTokenAuthorizerTests
+
+- (void)setUp {
+ [super setUp];
+ NSURLRequest *fetchRequest = [NSURLRequest requestWithURL:[FIRStorageTestHelpers objectURL]];
+ self.fetcher = [GTMSessionFetcher fetcherWithRequest:fetchRequest];
+
+ self.mockApp = OCMClassMock([FIRApp class]);
+ OCMStub([self.mockApp getTokenImplementation])
+ .andReturn(^{
+ });
+ FIRTokenCallback mockCallback =
+ [OCMArg invokeBlockWithArgs:kFIRStorageTestAuthToken, [NSNull null], nil];
+ OCMStub([self.mockApp getTokenForcingRefresh:NO withCallback:mockCallback]);
+ GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];
+ self.fetcher.authorizer =
+ [[FIRStorageTokenAuthorizer alloc] initWithApp:self.mockApp fetcherService:fetcherService];
+}
+
+- (void)tearDown {
+ self.fetcher = nil;
+ self.mockApp = nil;
+ [super tearDown];
+}
+
+- (void)testSuccessfulAuth {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testSuccessfulAuth"];
+
+ self.fetcher.testBlock = ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-retain-cycles"
+ XCTAssertTrue([self.fetcher.authorizer isAuthorizedRequest:fetcher.request]);
+#pragma clang diagnostic pop
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:200
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, nil);
+ };
+
+ [self.fetcher beginFetchWithCompletionHandler:^(NSData *_Nullable data,
+ NSError *_Nullable error) {
+ NSDictionary<NSString *, NSString *> *headers = self.fetcher.request.allHTTPHeaderFields;
+ NSString *authHeader = [headers objectForKey:@"Authorization"];
+ NSString *firebaseToken =
+ [NSString stringWithFormat:kFIRStorageAuthTokenFormat, kFIRStorageTestAuthToken];
+ XCTAssertEqualObjects(authHeader, firebaseToken);
+ [expectation fulfill];
+ }];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulAuth {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testUnsuccessfulAuth"];
+
+ NSError *authError = [NSError errorWithDomain:FIRStorageErrorDomain
+ code:FIRStorageErrorCodeUnauthenticated
+ userInfo:nil];
+ id unsuccessfulApp = OCMClassMock([FIRApp class]);
+ OCMStub([unsuccessfulApp getTokenImplementation])
+ .andReturn(^{
+ });
+ FIRTokenCallback mockCallback = [OCMArg invokeBlockWithArgs:[NSNull null], authError, nil];
+ OCMStub([unsuccessfulApp getTokenForcingRefresh:NO withCallback:mockCallback]);
+ GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];
+ self.fetcher.authorizer =
+ [[FIRStorageTokenAuthorizer alloc] initWithApp:unsuccessfulApp fetcherService:fetcherService];
+
+ self.fetcher.testBlock = ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-retain-cycles"
+ XCTAssertEqual([self.fetcher.authorizer isAuthorizedRequest:fetcher.request], NO);
+#pragma cland diagnostic pop
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:401
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, authError);
+ };
+
+ [self.fetcher beginFetchWithCompletionHandler:^(NSData *_Nullable data,
+ NSError *_Nullable error) {
+ NSDictionary<NSString *, NSString *> *headers = self.fetcher.request.allHTTPHeaderFields;
+ NSString *authHeader = [headers objectForKey:@"Authorization"];
+ XCTAssertNil(authHeader);
+ XCTAssertEqualObjects(error.domain, FIRStorageErrorDomain);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeUnauthenticated);
+ [expectation fulfill];
+ }];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testSuccessfulUnauthenticatedAuth {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testSuccessfulUnauthenticatedAuth"];
+
+ // Note that self.mockApp is left with null properties--this simulates no token present
+ self.mockApp = OCMClassMock([FIRApp class]);
+ GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];
+ self.fetcher.authorizer =
+ [[FIRStorageTokenAuthorizer alloc] initWithApp:self.mockApp fetcherService:fetcherService];
+
+ self.fetcher.testBlock = ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-retain-cycles"
+ XCTAssertFalse([self.fetcher.authorizer isAuthorizedRequest:fetcher.request]);
+#pragma cland diagnostic pop
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:200
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, nil);
+ };
+
+ [self.fetcher beginFetchWithCompletionHandler:^(NSData *_Nullable data,
+ NSError *_Nullable error) {
+ NSDictionary<NSString *, NSString *> *headers = self.fetcher.request.allHTTPHeaderFields;
+ NSString *authHeader = [headers objectForKey:@"Authorization"];
+ XCTAssertNil(authHeader);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testIsAuthorizing {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testIsAuthorizing"];
+
+ self.fetcher.testBlock = ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+ XCTAssertFalse([fetcher.authorizer isAuthorizingRequest:fetcher.request]);
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:200
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, nil);
+ };
+
+ [self.fetcher
+ beginFetchWithCompletionHandler:^(NSData *_Nullable data, NSError *_Nullable error) {
+ [expectation fulfill];
+ }];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testStopAuthorizingNoop {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testStopAuthorizingNoop"];
+
+ self.fetcher.testBlock = ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+ // Since both of these are noops, we expect that invoking them
+ // will still result in successful authentication
+ [fetcher.authorizer stopAuthorization];
+ [fetcher.authorizer stopAuthorizationForRequest:fetcher.request];
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:200
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, nil);
+ };
+
+ [self.fetcher beginFetchWithCompletionHandler:^(NSData *_Nullable data,
+ NSError *_Nullable error) {
+ NSDictionary<NSString *, NSString *> *headers = self.fetcher.request.allHTTPHeaderFields;
+ NSString *authHeader = [headers objectForKey:@"Authorization"];
+ NSString *firebaseToken =
+ [NSString stringWithFormat:kFIRStorageAuthTokenFormat, kFIRStorageTestAuthToken];
+ XCTAssertEqualObjects(authHeader, firebaseToken);
+ [expectation fulfill];
+ }];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testEmail {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testEmail"];
+
+ self.fetcher.testBlock = ^(GTMSessionFetcher *fetcher, GTMSessionFetcherTestResponse response) {
+ XCTAssertNil([fetcher.authorizer userEmail]);
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:200
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, nil);
+ };
+
+ [self.fetcher
+ beginFetchWithCompletionHandler:^(NSData *_Nullable data, NSError *_Nullable error) {
+ [expectation fulfill];
+ }];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m b/Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m
new file mode 100644
index 0000000..a85d181
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m
@@ -0,0 +1,199 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStorageTestHelpers.h"
+#import "FIRStorageUpdateMetadataTask.h"
+
+@interface FIRStorageUpdateMetadataTests : XCTestCase
+
+@property(strong, nonatomic) GTMSessionFetcherService *fetcherService;
+@property(strong, nonatomic) FIRStorageMetadata *metadata;
+@property(strong, nonatomic) FIRStorage *storage;
+@property(strong, nonatomic) id mockApp;
+
+@end
+
+@implementation FIRStorageUpdateMetadataTests
+
+- (void)setUp {
+ [super setUp];
+
+ NSDictionary *metadataDict = @{ @"bucket" : @"bucket", @"name" : @"path/to/object" };
+ self.metadata = [[FIRStorageMetadata alloc] initWithDictionary:metadataDict];
+
+ id mockOptions = OCMClassMock([FIROptions class]);
+ OCMStub([mockOptions storageBucket]).andReturn(@"bucket.appspot.com");
+
+ self.mockApp = OCMClassMock([FIRApp class]);
+ OCMStub([self.mockApp name]).andReturn(kFIRStorageAppName);
+ OCMStub([(FIRApp *)self.mockApp options]).andReturn(mockOptions);
+
+ self.fetcherService = [[GTMSessionFetcherService alloc] init];
+ self.fetcherService.authorizer =
+ [[FIRStorageTokenAuthorizer alloc] initWithApp:self.mockApp
+ fetcherService:self.fetcherService];
+
+ self.storage = [FIRStorage storageForApp:self.mockApp];
+}
+
+- (void)tearDown {
+ self.fetcherService = nil;
+ self.storage = nil;
+ self.mockApp = nil;
+ [super tearDown];
+}
+
+- (void)testFetcherConfiguration {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testSuccessfulFetch"];
+
+ self.fetcherService.testBlock = ^(GTMSessionFetcher *fetcher,
+ GTMSessionFetcherTestResponse response) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-retain-cycles"
+ XCTAssertEqualObjects(fetcher.request.URL, [FIRStorageTestHelpers objectURL]);
+ XCTAssertEqualObjects(fetcher.request.HTTPMethod, @"PATCH");
+ NSData *bodyData = [NSData frs_dataFromJSONDictionary:[self.metadata dictionaryRepresentation]];
+ XCTAssertEqualObjects(fetcher.request.HTTPBody, bodyData);
+ NSDictionary *HTTPHeaders = fetcher.request.allHTTPHeaderFields;
+ XCTAssertEqualObjects(HTTPHeaders[@"Content-Type"], @"application/json; charset=UTF-8");
+ XCTAssertEqualObjects(HTTPHeaders[@"Content-Length"], [@(bodyData.length) stringValue]);
+#pragma clang diagnostic pop
+ NSHTTPURLResponse *httpResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:fetcher.request.URL
+ statusCode:200
+ HTTPVersion:kHTTPVersion
+ headerFields:nil];
+ response(httpResponse, nil, nil);
+ };
+
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageUpdateMetadataTask *task = [[FIRStorageUpdateMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ metadata:self.metadata
+ completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testSuccessfulFetch {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"testSuccessfulFetch"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers successBlockWithMetadata:self.metadata];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageUpdateMetadataTask *task = [[FIRStorageUpdateMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ metadata:self.metadata
+ completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
+ XCTAssertEqualObjects(self.metadata.bucket, metadata.bucket);
+ XCTAssertEqualObjects(self.metadata.name, metadata.name);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchUnauthenticated {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchUnauthenticated"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers unauthenticatedBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageUpdateMetadataTask *task = [[FIRStorageUpdateMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ metadata:self.metadata
+ completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
+ XCTAssertNil(metadata);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeUnauthenticated);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchUnauthorized {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchUnauthorized"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers unauthorizedBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageUpdateMetadataTask *task = [[FIRStorageUpdateMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ metadata:self.metadata
+ completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
+ XCTAssertNil(metadata);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeUnauthorized);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchObjectDoesntExist {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchObjectDoesntExist"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers notFoundBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers notFoundPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageUpdateMetadataTask *task = [[FIRStorageUpdateMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ metadata:self.metadata
+ completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
+ XCTAssertNil(metadata);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeObjectNotFound);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+- (void)testUnsuccessfulFetchBadJSON {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnsuccessfulFetchBadJSON"];
+
+ self.fetcherService.testBlock = [FIRStorageTestHelpers invalidJSONBlock];
+ FIRStoragePath *path = [FIRStorageTestHelpers objectPath];
+ FIRStorageReference *ref = [[FIRStorageReference alloc] initWithStorage:self.storage path:path];
+ FIRStorageUpdateMetadataTask *task = [[FIRStorageUpdateMetadataTask alloc]
+ initWithReference:ref
+ fetcherService:self.fetcherService
+ metadata:self.metadata
+ completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
+ XCTAssertNil(metadata);
+ XCTAssertEqual(error.code, FIRStorageErrorCodeUnknown);
+ [expectation fulfill];
+ }];
+ [task enqueue];
+
+ [FIRStorageTestHelpers waitForExpectation:self];
+}
+
+@end
diff --git a/Example/Storage/Tests/Unit/FIRStorageUtilsTests.m b/Example/Storage/Tests/Unit/FIRStorageUtilsTests.m
new file mode 100644
index 0000000..8bf4c7e
--- /dev/null
+++ b/Example/Storage/Tests/Unit/FIRStorageUtilsTests.m
@@ -0,0 +1,125 @@
+// Copyright 2017 Google
+//
+// 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 "FIRStorageUtils.h"
+
+#import "FIRStoragePath.h"
+
+@interface FIRStorageUtilsTests : XCTestCase
+
+@end
+
+@implementation FIRStorageUtilsTests
+
+- (void)testCommonExtensionToMIMEType {
+ NSDictionary<NSString *, NSString *> *extensionToMIMEType = @{
+ @"txt" : @"text/plain",
+ @"png" : @"image/png",
+ @"mp3" : @"audio/mpeg",
+ @"mov" : @"video/quicktime",
+ @"gif" : @"image/gif"
+ };
+ [extensionToMIMEType
+ enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull extension, NSString *_Nonnull MIMEType,
+ BOOL *_Nonnull stop) {
+ XCTAssertEqualObjects([FIRStorageUtils MIMETypeForExtension:extension], MIMEType);
+ }];
+}
+
+- (void)testParseGoodDataToDict {
+ NSString *JSONString = @"{\"hello\" : \"world\"}";
+ NSData *JSONData = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
+ NSDictionary *JSONDictionary = [NSDictionary frs_dictionaryFromJSONData:JSONData];
+ NSDictionary *expectedDictionary = @{ @"hello" : @"world" };
+ XCTAssertEqualObjects(JSONDictionary, expectedDictionary);
+}
+
+- (void)testParseBadDataToDict {
+ NSString *JSONString = @"Invalid JSON Object";
+ NSData *JSONData = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
+ NSDictionary *JSONDictionary = [NSDictionary frs_dictionaryFromJSONData:JSONData];
+ XCTAssertNil(JSONDictionary);
+}
+
+- (void)testParseNilToDict {
+ NSDictionary *JSONDictionary = [NSDictionary frs_dictionaryFromJSONData:nil];
+ XCTAssertNil(JSONDictionary);
+}
+
+- (void)testParseGoodDictToData {
+ NSDictionary *JSONDictionary = @{ @"hello" : @"world" };
+ NSData *expectedData = [NSData frs_dataFromJSONDictionary:JSONDictionary];
+ NSString *JSONString = [[NSString alloc] initWithData:expectedData encoding:NSUTF8StringEncoding];
+ NSString *expectedString = @"{\"hello\":\"world\"}";
+ XCTAssertEqualObjects(JSONString, expectedString);
+}
+
+- (void)testParseNilToData {
+ NSData *JSONData = [NSData frs_dataFromJSONDictionary:nil];
+ XCTAssertNil(JSONData);
+}
+
+- (void)testNilDictToQueryString {
+ NSDictionary *params;
+ NSString *queryString = [FIRStorageUtils queryStringForDictionary:params];
+ XCTAssertEqualObjects(queryString, @"");
+}
+
+- (void)testEmptyDictToQueryString {
+ NSDictionary *params = @{};
+ NSString *queryString = [FIRStorageUtils queryStringForDictionary:params];
+ XCTAssertEqualObjects(queryString, @"");
+}
+
+- (void)testSingleItemToQueryString {
+ NSDictionary *params = @{ @"foo" : @"bar" };
+ NSString *queryString = [FIRStorageUtils queryStringForDictionary:params];
+ XCTAssertEqualObjects(queryString, @"foo=bar");
+}
+
+- (void)testMultiItemDictToQueryString {
+ NSDictionary *params = @{ @"foo" : @"bar", @"baz" : @"qux" };
+ NSString *queryString = [FIRStorageUtils queryStringForDictionary:params];
+ XCTAssertEqualObjects(queryString, @"foo=bar&baz=qux");
+}
+
+- (void)testDefaultRequestForFullPath {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"path/to/object"];
+ NSURLRequest *request = [FIRStorageUtils defaultRequestForPath:path];
+ XCTAssertEqualObjects([request.URL absoluteString],
+ @"https://firebasestorage.googleapis.com/v0/b/bucket/o/path%2Fto%2Fobject");
+}
+
+- (void)testDefaultRequestForNoPath {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ NSURLRequest *request = [FIRStorageUtils defaultRequestForPath:path];
+ XCTAssertEqualObjects([request.URL absoluteString],
+ @"https://firebasestorage.googleapis.com/v0/b/bucket/o");
+}
+
+- (void)testEncodedURLForFullPath {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:@"path/to/object"];
+ NSString *encodedURL = [FIRStorageUtils encodedURLForPath:path];
+ XCTAssertEqualObjects(encodedURL, @"/v0/b/bucket/o/path%2Fto%2Fobject");
+}
+
+- (void)testEncodedURLForNoPath {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:@"bucket" object:nil];
+ NSString *encodedURL = [FIRStorageUtils encodedURLForPath:path];
+ XCTAssertEqualObjects(encodedURL, @"/v0/b/bucket/o");
+}
+
+@end