// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // See console setup instructions in FIRStorageIntegrationTests.m import FirebaseAuth import FirebaseCore import FirebaseStorage import XCTest class StorageIntegration: XCTestCase { var app: FirebaseApp! var auth: Auth! var storage: Storage! static var once = false static var signedIn = false override class func setUp() { FirebaseApp.configure() } override func setUp() { super.setUp() app = FirebaseApp.app() auth = Auth.auth(app: app) storage = Storage.storage(app: app!) if !StorageIntegration.signedIn { signInAndWait() } if !StorageIntegration.once { let setupExpectation = expectation(description: "setUp") let largeFiles = ["ios/public/swift-1mb"] let emptyFiles = ["ios/public/empty", "ios/public/list/a", "ios/public/list/b", "ios/public/list/prefix/c"] setupExpectation.expectedFulfillmentCount = largeFiles.count + emptyFiles.count do { let bundle = Bundle(for: StorageIntegration.self) let filePath = try XCTUnwrap(bundle.path(forResource: "1mb", ofType: "dat"), "Failed to get filePath") let data = try XCTUnwrap(try Data(contentsOf: URL(fileURLWithPath: filePath)), "Failed to load file") for largeFile in largeFiles { let ref = storage.reference().child(largeFile) ref.putData(data, metadata: nil, completion: { _, error in XCTAssertNil(error, "Error should be nil") setupExpectation.fulfill() }) } for emptyFile in emptyFiles { let ref = storage.reference().child(emptyFile) ref.putData(Data(), metadata: nil, completion: { _, error in XCTAssertNil(error, "Error should be nil") setupExpectation.fulfill() }) } } catch { XCTFail("Exception thrown setting up files in setUp") } waitForExpectations() StorageIntegration.once = true } } override func tearDown() { app = nil auth = nil storage = nil super.tearDown() } func testName() { let aGS = app.options.projectID let aGSURI = "gs://\(aGS!).appspot.com/path/to" let ref = storage.reference(forURL: aGSURI) XCTAssertEqual(ref.description, aGSURI) } func testGetMetadata() { let expectation = self.expectation(description: #function) let ref = storage.reference().child("ios/public/1mb") ref.getMetadata(completion: { (metadata, error) -> Void in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") expectation.fulfill() }) waitForExpectations() } func testGetMetadataUnauthorized() { let expectation = self.expectation(description: #function) let ref = storage.reference().child("ios/private/secretfile.txt") ref.getMetadata(completion: { (metadata, error) -> Void in XCTAssertNil(metadata, "Metadata should be nil") XCTAssertNotNil(error, "Error should not be nil") XCTAssertEqual((error! as NSError).code, StorageErrorCode.unauthorized.rawValue) expectation.fulfill() }) waitForExpectations() } func testUpdateMetadata() { let expectation = self.expectation(description: #function) let meta = StorageMetadata() meta.contentType = "lol/custom" meta.customMetadata = ["lol": "custom metadata is neat", "ちかてつ": "🚇", "shinkansen": "新幹線"] let ref = storage.reference(withPath: "ios/public/1mb") ref.updateMetadata(meta, completion: { metadata, error in XCTAssertEqual(meta.contentType, metadata!.contentType) XCTAssertEqual(meta.customMetadata!["lol"], metadata?.customMetadata!["lol"]) XCTAssertEqual(meta.customMetadata!["ちかてつ"], metadata?.customMetadata!["ちかてつ"]) XCTAssertEqual(meta.customMetadata!["shinkansen"], metadata?.customMetadata!["shinkansen"]) XCTAssertNil(error, "Error should be nil") expectation.fulfill() }) waitForExpectations() } func testDelete() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/fileToDelete") let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed") ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") ref.delete(completion: { error in XCTAssertNil(error, "Error should be nil") expectation.fulfill() }) }) waitForExpectations() } func testDeleteNonExistingFile() { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/fileThatDoesNotExist") ref.delete { error in XCTAssertNotNil(error, "Error should not be nil") XCTAssertEqual((error! as NSError).code, StorageErrorCode.objectNotFound.rawValue) expectation.fulfill() } waitForExpectations() } func testDeleteFileUnauthorized() { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/private/secretfile.txt") ref.delete { error in XCTAssertNotNil(error, "Error should not be nil") XCTAssertEqual((error! as NSError).code, StorageErrorCode.unauthorized.rawValue) expectation.fulfill() } waitForExpectations() } func testDeleteWithNilCompletion() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/fileToDelete") let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed") ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") ref.delete(completion: nil) expectation.fulfill() }) waitForExpectations() } func testSimplePutData() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/testBytesUpload") let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed") ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") expectation.fulfill() }) waitForExpectations() } func testSimplePutSpecialCharacter() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/-._~!$'()*,=:@&+;") let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed") ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") expectation.fulfill() }) waitForExpectations() } func testSimplePutDataInBackgroundQueue() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/testBytesUpload") let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed") DispatchQueue.global(qos: .background).async { ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") expectation.fulfill() }) } waitForExpectations() } func testSimplePutEmptyData() { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/testSimplePutEmptyData") let data = Data() ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") expectation.fulfill() }) waitForExpectations() } func testSimplePutDataUnauthorized() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/private/secretfile.txt") let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed") ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNil(metadata, "Metadata should be nil") XCTAssertNotNil(error, "Error should not be nil") XCTAssertEqual((error! as NSError).code, StorageErrorCode.unauthorized.rawValue) expectation.fulfill() }) waitForExpectations() } func testSimplePutFile() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/testSimplePutFile") let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed") let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory()) let fileURL = tmpDirURL.appendingPathComponent("hello.txt") try data.write(to: fileURL, options: .atomicWrite) let task = ref.putFile(from: fileURL, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") }) task.observe(StorageTaskStatus.success, handler: { snapshot in XCTAssertEqual(snapshot.description, "") expectation.fulfill() }) var uploadedBytes: Int64 = -1 task.observe(StorageTaskStatus.progress, handler: { snapshot in XCTAssertTrue(snapshot.description.starts(with: "() let callbackQueue = DispatchQueue(label: callbackQueueLabel) callbackQueue.setSpecific(key: callbackQueueKey, value: callbackQueueLabel) storage.callbackQueue = callbackQueue let ref = storage.reference(withPath: "ios/public/1mb") ref.getData(maxSize: 1024 * 1024) { data, error in XCTAssertNotNil(data, "Data should not be nil") XCTAssertNil(error, "Error should be nil") XCTAssertFalse(Thread.isMainThread) let currentQueueLabel = DispatchQueue.getSpecific(key: callbackQueueKey) XCTAssertEqual(currentQueueLabel, callbackQueueLabel) expectation.fulfill() // Reset the callbackQueue to default (main queue). self.storage.callbackQueue = DispatchQueue.main callbackQueue.setSpecific(key: callbackQueueKey, value: nil) } waitForExpectations() } func testSimpleGetDataTooSmall() { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/1mb") ref.getData(maxSize: 1024, completion: { data, error in XCTAssertNil(data, "Data should be nil") XCTAssertNotNil(error, "Error should not be nil") XCTAssertEqual((error! as NSError).code, StorageErrorCode.downloadSizeExceeded.rawValue) expectation.fulfill() }) waitForExpectations() } func testSimpleGetDownloadURL() { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/1mb") // Download URL format is // "https://firebasestorage.googleapis.com/v0/b/{bucket}/o/{path}?alt=media&token={token}" let downloadURLPattern = "^https:\\/\\/firebasestorage.googleapis.com\\/v0\\/b\\/[^\\/]*\\/o\\/" + "ios%2Fpublic%2F1mb\\?alt=media&token=[a-z0-9-]*$" ref.downloadURL(completion: { downloadURL, error in XCTAssertNil(error, "Error should be nil") do { let testRegex = try NSRegularExpression(pattern: downloadURLPattern) let downloadURL = try XCTUnwrap(downloadURL, "Failed to unwrap downloadURL") let urlString = downloadURL.absoluteString XCTAssertEqual(testRegex.numberOfMatches(in: urlString, range: NSRange(location: 0, length: urlString.count)), 1) expectation.fulfill() } catch { XCTFail("Throw in downloadURL completion block") } }) waitForExpectations() } func testSimpleGetFileWithCompletion() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/cookie") let cookieString = "Here's a 🍪, yay!" let data = try XCTUnwrap(cookieString.data(using: .utf8), "Data construction failed") ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory()) let fileURL = tmpDirURL.appendingPathComponent("cookie.txt") ref.write(toFile: fileURL) { url, error in XCTAssertNil(error, "Error should be nil") guard let url = url else { XCTFail("Failed to unwrap url") return } XCTAssertEqual(fileURL, url) do { let stringData = try String(contentsOf: fileURL, encoding: .utf8) XCTAssertEqual(stringData, cookieString) expectation.fulfill() } catch { XCTFail("Could not get String contents of fetched data") } } }) waitForExpectations() } func testSimpleGetFile() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/helloworld") let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory()) let fileURL = tmpDirURL.appendingPathComponent("hello.txt") let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed") ref.putData(data, metadata: nil, completion: { metadata, error in XCTAssertNotNil(metadata, "Metadata should not be nil") XCTAssertNil(error, "Error should be nil") let task = ref.write(toFile: fileURL) task.observe(StorageTaskStatus.success, handler: { snapshot in do { let stringData = try String(contentsOf: fileURL, encoding: .utf8) XCTAssertEqual(stringData, "Hello Swift World") XCTAssertEqual(snapshot.description, "") expectation.fulfill() } catch { XCTFail("Exception processing success snapshot") } }) task.observe(StorageTaskStatus.progress, handler: { snapshot in XCTAssertNil(snapshot.error, "Error should be nil") guard let progress = snapshot.progress else { XCTFail("Missing progress") return } print("\(progress.completedUnitCount) of \(progress.totalUnitCount)") }) task.observe(StorageTaskStatus.failure, handler: { snapshot in XCTAssertNil(snapshot.error, "Error should be nil") }) }) waitForExpectations() } func testCancelDownload() throws { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/1mb") let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory()) let fileURL = tmpDirURL.appendingPathComponent("hello.dat") let task = ref.write(toFile: fileURL) var failed = false // Only fail once task.observe(StorageTaskStatus.failure, handler: { snapshot in XCTAssertTrue(snapshot.description.starts(with: "") expectation.fulfill() }) var resumeAtBytes: Int32 = 256 * 1024 var downloadedBytes: Int64 = 0 var computationResult: Double = 0.0 task.observe(StorageTaskStatus.progress, handler: { snapshot in XCTAssertTrue(snapshot.description.starts(with: " resumeAtBytes { // Making sure the main run loop is busy. for i: Int32 in 0 ... 499 { DispatchQueue.global(qos: .default).async { computationResult = sqrt(Double(INT_MAX - i)) } } print("Pausing") task.pause() resumeAtBytes = INT_MAX } }) task.observe(StorageTaskStatus.pause, handler: { snapshot in XCTAssertEqual(snapshot.description, "") print("Resuming") task.resume() }) waitForExpectations() XCTAssertEqual(INT_MAX, resumeAtBytes) XCTAssertEqual(sqrt(Double(INT_MAX - 499)), computationResult, accuracy: 0.1) } func testResumeGetFileInBackgroundQueue() { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/1mb") let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory()) let fileURL = tmpDirURL.appendingPathComponent("hello.txt") let task = ref.write(toFile: fileURL) task.observe(StorageTaskStatus.success, handler: { snapshot in XCTAssertEqual(snapshot.description, "") expectation.fulfill() }) var resumeAtBytes: Int32 = 256 * 1024 var downloadedBytes: Int64 = 0 task.observe(StorageTaskStatus.progress, handler: { snapshot in XCTAssertTrue(snapshot.description.starts(with: " resumeAtBytes { print("Pausing") DispatchQueue.global(qos: .background).async { task.pause() } resumeAtBytes = INT_MAX } }) task.observe(StorageTaskStatus.pause, handler: { snapshot in XCTAssertEqual(snapshot.description, "") print("Resuming") task.resume() }) waitForExpectations() XCTAssertEqual(INT_MAX, resumeAtBytes) } func testPagedListFiles() { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/list") ref.list(maxResults: 2) { listResult, error in XCTAssertNotNil(listResult, "listResult should not be nil") XCTAssertNil(error, "Error should be nil") XCTAssertEqual(listResult.items, [ref.child("a"), ref.child("b")]) XCTAssertEqual(listResult.prefixes, []) guard let pageToken = listResult.pageToken else { XCTFail("pageToken should not be nil") return } ref.list(maxResults: 2, pageToken: pageToken) { listResult, error in XCTAssertNotNil(listResult, "listResult should not be nil") XCTAssertNil(error, "Error should be nil") XCTAssertEqual(listResult.items, []) XCTAssertEqual(listResult.prefixes, [ref.child("prefix")]) XCTAssertNil(listResult.pageToken, "pageToken should be nil") expectation.fulfill() } } waitForExpectations() } func testListAllFiles() { let expectation = self.expectation(description: #function) let ref = storage.reference(withPath: "ios/public/list") ref.listAll { listResult, error in XCTAssertNotNil(listResult, "listResult should not be nil") XCTAssertNil(error, "Error should be nil") XCTAssertEqual(listResult.items, [ref.child("a"), ref.child("b")]) XCTAssertEqual(listResult.prefixes, [ref.child("prefix")]) XCTAssertNil(listResult.pageToken, "pageToken should be nil") expectation.fulfill() } waitForExpectations() } private func signInAndWait() { let expectation = self.expectation(description: #function) auth.signIn(withEmail: Credentials.kUserName, password: Credentials.kPassword) { result, error in XCTAssertNil(error) StorageIntegration.signedIn = true print("Successfully signed in") expectation.fulfill() } waitForExpectations() } private func waitForExpectations() { let kFIRStorageIntegrationTestTimeout = 60.0 waitForExpectations(timeout: kFIRStorageIntegrationTestTimeout, handler: { (error) -> Void in if let error = error { print(error) } }) } }