diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4972adc90..0313e4a5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,12 +128,18 @@ jobs: bundle install - name: Create Jazzy Docs run: ./Scripts/jazzy.sh + env: + BUILD_VERSION: '1.8.3' cocoapods: needs: xcode-build-watchos runs-on: macos-latest steps: - uses: actions/checkout@v2 + - name: Update Framework Version + run: ./Scripts/update_build + env: + BUILD_VERSION: '1.8.3' - name: CocoaPods run: pod lib lint --allow-warnings diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c86433369..ad3a57f74 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,12 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v2 + - name: Get release version + run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + - name: Update Framework Version + run: ./Scripts/update_build + env: + BUILD_VERSION: ${{ env.TAG }} - name: CocoaPods run: set -o pipefail && env NSUnbufferedIO=YES pod lib lint --allow-warnings --verbose - name: Deploy CocoaPods @@ -33,8 +39,12 @@ jobs: run: | bundle config path vendor/bundle bundle install + - name: Get release version + run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV - name: Create Jazzy Docs run: ./Scripts/jazzy.sh + env: + BUILD_VERSION: ${{ env.TAG }} - name: Deploy Jazzy Docs uses: peaceiris/actions-gh-pages@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 14ba87884..f4ac07c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # Parse-Swift Changelog ### main -[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.3...main) +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.4...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +### 1.8.4 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.3...1.8.4) + __Fixes__ +- Switched context header X-Parse-Context to X-Parse-Cloud-Context to match server ([#170](https://github.com/parse-community/Parse-Swift/pull/170)), thanks to [Corey Baker](https://github.com/cbaker6). - Fixed a bug in LiveQuery that prevented reconnecting after a connection was closed. Also added a sendPing method to LiveQuery ([#172](https://github.com/parse-community/Parse-Swift/pull/172)), thanks to [Corey Baker](https://github.com/cbaker6). ### 1.8.3 diff --git a/ParseSwift.playground/Pages/11 - LiveQuery.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/11 - LiveQuery.xcplaygroundpage/Contents.swift index cf5fee33b..d5104d757 100644 --- a/ParseSwift.playground/Pages/11 - LiveQuery.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/11 - LiveQuery.xcplaygroundpage/Contents.swift @@ -157,6 +157,9 @@ do { print(error) } +//: If you look at your server log, you will notice the client and server disconnnected. +//: This is because there is no more LiveQuery subscriptions. + //: Ping the LiveQuery server. This should produce an error //: because LiveQuery is disconnected. ParseLiveQuery.client?.sendPing { error in @@ -176,7 +179,7 @@ query2.fields("score") //: Subscribe to your new query. let subscription2 = query2.subscribeCallback! -//: As before, setup your subscription and event handlers. +//: As before, setup your subscription, event, and unsubscribe handlers. subscription2.handleSubscribe { subscribedQuery, isNew in //: You can check this subscription is for this query. @@ -203,6 +206,10 @@ subscription2.handleEvent { _, event in } } +subscription2.handleUnsubscribe { query in + print("Unsubscribed from \(query)") +} + //: To close the current LiveQuery connection. ParseLiveQuery.client?.close() @@ -219,41 +226,15 @@ ParseLiveQuery.client?.sendPing { error in } } -//: Subscribe to your new query. +//: Resubscribe to your previous query. +//: Since we never unsubscribed you can use your previous handlers. let subscription3 = query2.subscribeCallback! -//: As before, setup your subscription and event handlers. -subscription3.handleSubscribe { subscribedQuery, isNew in - - //: You can check this subscription is for this query. - if isNew { - print("Successfully subscribed to new query \(subscribedQuery)") - } else { - print("Successfully updated subscription to new query \(subscribedQuery)") - } -} - -subscription3.handleEvent { _, event in - switch event { - - case .entered(let object): - print("Entered: \(object)") - case .left(let object): - print("Left: \(object)") - case .created(let object): - print("Created: \(object)") - case .updated(let object): - print("Updated: \(object)") - case .deleted(let object): - print("Deleted: \(object)") - } -} - -//: Now lets subscribe to an additional query. +//: Resubscribe to another previous query. +//: This one needs new handlers. let subscription4 = query.subscribeCallback! -//: This is how you receive notifications about the success -//: of your subscription. +//: Need a new handler because we previously unsubscribed. subscription4.handleSubscribe { subscribedQuery, isNew in //: You can check this subscription is for this query @@ -264,7 +245,7 @@ subscription4.handleSubscribe { subscribedQuery, isNew in } } -//: This is how you register to receive notifications of events related to your LiveQuery. +//: Need a new event handler because we previously unsubscribed. subscription4.handleEvent { _, event in switch event { @@ -281,8 +262,8 @@ subscription4.handleEvent { _, event in } } -//: Now we will will unsubscribe from one of the subsriptions, but maintain the connection. -subscription3.handleUnsubscribe { query in +//: Need a new unsubscribe handler because we previously unsubscribed. +subscription4.handleUnsubscribe { query in print("Unsubscribed from \(query)") } @@ -302,5 +283,15 @@ ParseLiveQuery.client?.sendPing { error in } } +//: To unsubscribe from your your last query. +do { + try query.unsubscribe() +} catch { + print(error) +} + +//: If you look at your server log, you will notice the client and server disconnnected. +//: This is because there is no more LiveQuery subscriptions. + PlaygroundPage.current.finishExecution() //: [Next](@next) diff --git a/ParseSwift.podspec b/ParseSwift.podtemplate similarity index 98% rename from ParseSwift.podspec rename to ParseSwift.podtemplate index 5b58a472e..28d3cd101 100644 --- a/ParseSwift.podspec +++ b/ParseSwift.podtemplate @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ParseSwift" - s.version = "1.8.3" + s.version = "[[VERSION]]" s.summary = "Parse Pure Swift SDK" s.homepage = "https://github.com/parse-community/Parse-Swift" s.authors = { diff --git a/README.md b/README.md index 15f56c85e..23474c05b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ import PackageDescription let package = Package( name: "YOUR_PROJECT_NAME", dependencies: [ - .package(url: "https://github.com/parse-community/Parse-Swift", from: "1.8.3"), + .package(url: "https://github.com/parse-community/Parse-Swift", from: "1.8.4"), ] ) ``` diff --git a/Scripts/jazzy.sh b/Scripts/jazzy.sh index 847065d8e..297d9ac14 100755 --- a/Scripts/jazzy.sh +++ b/Scripts/jazzy.sh @@ -1,11 +1,10 @@ -ver=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ParseSwift-iOS/Info.plist` bundle exec jazzy \ --clean \ --author "Parse Community" \ --author_url http://parseplatform.org \ --github_url https://github.com/parse-community/Parse-Swift \ --root-url http://parseplatform.org/Parse-Swift/api/ \ - --module-version 1.8.3 \ + --module-version ${BUILD_VERSION} \ --theme fullwidth \ --skip-undocumented \ --output ./docs/api \ diff --git a/Scripts/update_build b/Scripts/update_build new file mode 100755 index 000000000..cb28eda12 --- /dev/null +++ b/Scripts/update_build @@ -0,0 +1,10 @@ +#!/bin/bash + +# Prepare podspec update +TEMPLATE_FILE_NAME="ParseSwift.podtemplate" +OUT_FILE_NAME="ParseSwift.podspec" + +# Load the template podspec and replace version +TEMPLATE=$(cat "$TEMPLATE_FILE_NAME" | sed "s/\[\[VERSION\]\]/${BUILD_VERSION}/g") + +echo "$TEMPLATE" > "$OUT_FILE_NAME" diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index 6e71bc20d..a35a850bb 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -201,7 +201,7 @@ public struct API { let context = AnyEncodable(context) if let encoded = try? ParseCoding.jsonEncoder().encode(context), let encodedString = String(data: encoded, encoding: .utf8) { - headers["X-Parse-Context"] = encodedString + headers["X-Parse-Cloud-Context"] = encodedString } } } diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift index 28877a5d2..c1bf5163c 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift @@ -199,7 +199,8 @@ public final class ParseLiveQuery: NSObject { } components.scheme = (components.scheme == "https" || components.scheme == "wss") ? "wss" : "ws" url = components.url - self.createTask() + self.task = URLSession.liveQuery.createTask(self.url) + self.resumeTask() if isDefault { Self.setDefault(self) @@ -226,12 +227,11 @@ extension ParseLiveQuery { return Int.random(in: 0 ..< Int(truncating: min)) } - func createTask() { + func resumeTask() { synchronizationQueue.sync { - if self.task == nil { - self.task = URLSession.liveQuery.createTask(self.url) + if self.task.state == .suspended { + self.task.resume() } - self.task.resume() URLSession.liveQuery.receive(self.task) URLSession.liveQuery.delegates[self.task] = self } @@ -317,7 +317,7 @@ extension ParseLiveQuery: LiveQuerySocketDelegate { self.isSocketEstablished = false if !self.isDisconnectedByUser { //Try to reconnect - self.createTask() + self.resumeTask() } } } @@ -329,7 +329,7 @@ extension ParseLiveQuery: LiveQuerySocketDelegate { if self.isConnected { self.close(useDedicatedQueue: true) //Try to reconnect - self.createTask() + self.resumeTask() } } return @@ -344,7 +344,8 @@ extension ParseLiveQuery: LiveQuerySocketDelegate { guard let parseError = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) else { //Turn LiveQuery error into ParseError let parseError = ParseError(code: .unknownError, - message: "LiveQuery error code: \(error.code) message: \(error.error)") + // swiftlint:disable:next line_length + message: "ParseLiveQuery Error: code: \(error.code), message: \(error.error)") self.notificationQueue.async { self.receiveDelegate?.received(parseError) } @@ -529,10 +530,11 @@ extension ParseLiveQuery { self.synchronizationQueue .asyncAfter(deadline: .now() + DispatchTimeInterval .seconds(reconnectInterval)) { - self.createTask() + self.resumeTask() self.attempts += 1 let error = ParseError(code: .unknownError, - message: "Attempted to open socket \(self.attempts) time(s)") + // swiftlint:disable:next line_length + message: "ParseLiveQuery Error: attempted to open socket \(self.attempts) time(s)") completion(error) } } @@ -543,13 +545,12 @@ extension ParseLiveQuery { public func close() { synchronizationQueue.sync { if self.isConnected { - self.task.cancel() + self.task.cancel(with: .goingAway, reason: nil) self.isDisconnectedByUser = true } - if task != nil { - URLSession.liveQuery.delegates.removeValue(forKey: self.task) - } - self.task = nil + URLSession.liveQuery.delegates.removeValue(forKey: self.task) + isSocketEstablished = false + self.task = URLSession.liveQuery.createTask(self.url) // Prepare new task for future use. } } @@ -568,11 +569,12 @@ extension ParseLiveQuery { */ public func sendPing(pongReceiveHandler: @escaping (Error?) -> Void) { synchronizationQueue.sync { - if isSocketEstablished { + if self.task.state == .running { URLSession.liveQuery.sendPing(task, pongReceiveHandler: pongReceiveHandler) } else { let error = ParseError(code: .unknownError, - message: "Need to open the websocket before it can be pinged.") + // swiftlint:disable:next line_length + message: "ParseLiveQuery Error: socket status needs to be \"\(URLSessionTask.State.running.rawValue)\" before pinging server. Current status is \"\(self.task.state.rawValue)\". Try calling \"open()\" to change socket status.") pongReceiveHandler(error) } } @@ -582,19 +584,18 @@ extension ParseLiveQuery { if useDedicatedQueue { synchronizationQueue.async { if self.isConnected { - self.task.cancel() + self.task.cancel(with: .goingAway, reason: nil) } URLSession.liveQuery.delegates.removeValue(forKey: self.task) - self.task = nil + self.task = URLSession.liveQuery.createTask(self.url) // Prepare new task for future use. } } else { if self.isConnected { - self.task.cancel() - } - if self.task != nil { - URLSession.liveQuery.delegates.removeValue(forKey: self.task) + self.task.cancel(with: .goingAway, reason: nil) } - self.task = nil + URLSession.liveQuery.delegates.removeValue(forKey: self.task) + isSocketEstablished = false + self.task = URLSession.liveQuery.createTask(self.url) // Prepare new task for future use. } } diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index 7abbddf5d..ba5f4ec0b 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -10,7 +10,7 @@ import Foundation enum ParseConstants { static let sdk = "swift" - static let version = "1.8.1" + static let version = "1.8.4" static let hashingKey = "parseSwift" static let fileManagementDirectory = "parse/" static let fileManagementPrivateDocumentsDirectory = "Private Documents/" diff --git a/Sources/ParseSwift/Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift index aca7dd7ec..543fd2fa8 100644 --- a/Sources/ParseSwift/Types/ParseError.swift +++ b/Sources/ParseSwift/Types/ParseError.swift @@ -11,7 +11,7 @@ import Foundation /** An object with a Parse code and message. */ -public struct ParseError: ParseType, Decodable, Swift.Error { +public struct ParseError: ParseType, Decodable, Swift.Error, CustomDebugStringConvertible { /// The value representing the error from the Parse Server. public let code: Code /// The text representing the error from the Parse Server. @@ -24,15 +24,6 @@ public struct ParseError: ParseType, Decodable, Swift.Error { self.otherCode = nil } - /// A textual representation of this error. - public var localizedDescription: String { - if let otherCode = otherCode { - return "ParseError code=\(code.rawValue) error=\(message) otherCode=\(otherCode)" - } else { - return "ParseError code=\(code.rawValue) error=\(message)" - } - } - enum CodingKeys: String, CodingKey { case code case message = "error" @@ -368,6 +359,17 @@ public struct ParseError: ParseType, Decodable, Swift.Error { } } +// MARK: Encodable +extension ParseError { + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(code, forKey: .code) + try container.encode(message, forKey: .message) + } +} + +// MARK: Decodable extension ParseError { public init(from decoder: Decoder) throws { @@ -381,10 +383,14 @@ extension ParseError { } message = try values.decode(String.self, forKey: .message) } +} - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(code, forKey: .code) - try container.encode(message, forKey: .message) +// MARK: CustomDebugStringConvertible +extension ParseError { + public var debugDescription: String { + guard let otherCode = otherCode else { + return "ParseError code=\(code.rawValue) error=\(message)" + } + return "ParseError code=\(code.rawValue) error=\(message) otherCode=\(otherCode)" } } diff --git a/Tests/ParseSwiftTests/APICommandTests.swift b/Tests/ParseSwiftTests/APICommandTests.swift index 601266558..7b38df1aa 100644 --- a/Tests/ParseSwiftTests/APICommandTests.swift +++ b/Tests/ParseSwiftTests/APICommandTests.swift @@ -673,7 +673,7 @@ class APICommandTests: XCTestCase { func testContextHeader() { let headers = API.getHeaders(options: []) - XCTAssertNil(headers["X-Parse-Context"]) + XCTAssertNil(headers["X-Parse-Cloud-Context"]) let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in return nil @@ -682,7 +682,7 @@ class APICommandTests: XCTestCase { switch post.prepareURLRequest(options: [.context(["hello": "world"])]) { case .success(let request): - XCTAssertEqual(request.allHTTPHeaderFields?["X-Parse-Context"], "{\"hello\":\"world\"}") + XCTAssertEqual(request.allHTTPHeaderFields?["X-Parse-Cloud-Context"], "{\"hello\":\"world\"}") case .failure(let error): XCTFail(error.localizedDescription) } diff --git a/Tests/ParseSwiftTests/ParseErrorTests.swift b/Tests/ParseSwiftTests/ParseErrorTests.swift index 18bcd68bf..7c5ffabb8 100644 --- a/Tests/ParseSwiftTests/ParseErrorTests.swift +++ b/Tests/ParseSwiftTests/ParseErrorTests.swift @@ -44,7 +44,7 @@ class ParseErrorTests: XCTestCase { let decoded = try ParseCoding.jsonDecoder().decode(ParseError.self, from: encoded) XCTAssertEqual(decoded.code.rawValue, code) XCTAssertEqual(decoded.message, message) - XCTAssertEqual(decoded.localizedDescription, "ParseError code=\(code) error=\(message)") + XCTAssertEqual(decoded.debugDescription, "ParseError code=\(code) error=\(message)") } func testEncodeOther() throws { @@ -57,7 +57,7 @@ class ParseErrorTests: XCTestCase { let decoded = try ParseCoding.jsonDecoder().decode(ParseError.self, from: encoded) XCTAssertEqual(decoded.code, .other) XCTAssertEqual(decoded.message, message) - XCTAssertEqual(decoded.localizedDescription, + XCTAssertEqual(decoded.debugDescription, "ParseError code=\(ParseError.Code.other.rawValue) error=\(message) otherCode=\(code)") XCTAssertEqual(decoded.otherCode, code) } diff --git a/Tests/ParseSwiftTests/ParseLiveQueryCombineTests.swift b/Tests/ParseSwiftTests/ParseLiveQueryCombineTests.swift index 538c9fbff..88b8e7687 100644 --- a/Tests/ParseSwiftTests/ParseLiveQueryCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseLiveQueryCombineTests.swift @@ -86,14 +86,13 @@ class ParseLiveQueryCombineTests: XCTestCase { XCTFail("Should have produced failure") case .failure(let error): XCTAssertEqual(client.isSocketEstablished, false) - XCTAssertNil(client.task) guard let parseError = error as? ParseError else { XCTFail("Should have casted to ParseError.") expectation1.fulfill() return } XCTAssertEqual(parseError.code, ParseError.Code.unknownError) - XCTAssertTrue(parseError.message.contains("pinged")) + XCTAssertTrue(parseError.message.contains("socket status")) } expectation1.fulfill() diff --git a/Tests/ParseSwiftTests/ParseLiveQueryTests.swift b/Tests/ParseSwiftTests/ParseLiveQueryTests.swift index 1f716639b..83f0fe031 100644 --- a/Tests/ParseSwiftTests/ParseLiveQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseLiveQueryTests.swift @@ -67,6 +67,7 @@ class ParseLiveQueryTests: XCTestCase { guard let originalURL = URL(string: "http://localhost:1337/1"), var components = URLComponents(url: originalURL, resolvingAgainstBaseURL: false) else { + XCTFail("Should have retrieved URL components") return } components.scheme = (components.scheme == "https" || components.scheme == "wss") ? "wss" : "ws" @@ -93,6 +94,7 @@ class ParseLiveQueryTests: XCTestCase { guard let originalURL = URL(string: "http://parse:1337/1"), var components = URLComponents(url: originalURL, resolvingAgainstBaseURL: false) else { + XCTFail("Should have retrieved URL components") return } components.scheme = (components.scheme == "https" || components.scheme == "wss") ? "wss" : "ws" @@ -424,7 +426,6 @@ class ParseLiveQueryTests: XCTestCase { client.closeAll() client.synchronizationQueue.asyncAfter(deadline: .now() + 2) { XCTAssertFalse(client.isSocketEstablished) - XCTAssertNil(client.task) } } @@ -437,14 +438,13 @@ class ParseLiveQueryTests: XCTestCase { let expectation1 = XCTestExpectation(description: "Send Ping") client.sendPing { error in XCTAssertEqual(client.isSocketEstablished, false) - XCTAssertNil(client.task) guard let parseError = error as? ParseError else { XCTFail("Should have casted to ParseError.") expectation1.fulfill() return } XCTAssertEqual(parseError.code, ParseError.Code.unknownError) - XCTAssertTrue(parseError.message.contains("pinged")) + XCTAssertTrue(parseError.message.contains("socket status")) expectation1.fulfill() } wait(for: [expectation1], timeout: 20.0) @@ -514,8 +514,8 @@ class ParseLiveQueryTests: XCTestCase { func pretendToBeConnected() throws { guard let client = ParseLiveQuery.getDefault() else { - XCTFail("Should be able to get client") - return + throw ParseError(code: .unknownError, + message: "Should be able to get client") } client.task = URLSession.liveQuery.createTask(client.url) client.status(.open) @@ -544,13 +544,13 @@ class ParseLiveQueryTests: XCTestCase { guard let subscribed = subscription.subscribed else { XCTFail("Should unwrap subscribed.") expectation1.fulfill() + expectation2.fulfill() return } XCTAssertEqual(query, subscribed.query) XCTAssertTrue(subscribed.isNew) XCTAssertNil(subscription.unsubscribed) XCTAssertNil(subscription.event) - expectation1.fulfill() //Unsubscribe DispatchQueue.main.asyncAfter(deadline: .now() + 2) { @@ -571,16 +571,18 @@ class ParseLiveQueryTests: XCTestCase { //Received Unsubscribe let response2 = PreliminaryMessageResponse(op: .unsubscribed, - requestId: 1, - clientId: "yolo", - installationId: "naw") + requestId: 1, + clientId: "yolo", + installationId: "naw") guard let encoded2 = try? ParseCoding.jsonEncoder().encode(response2) else { + XCTFail("Should have encoded second response") expectation2.fulfill() return } client.received(encoded2) XCTAssertEqual(client.pendingSubscriptions.count, 0) XCTAssertEqual(client.subscriptions.count, 0) + expectation1.fulfill() } XCTAssertFalse(try client.isSubscribed(query)) @@ -638,6 +640,7 @@ class ParseLiveQueryTests: XCTestCase { clientId: "yolo", installationId: "naw") guard let encoded2 = try? ParseCoding.jsonEncoder().encode(response2) else { + XCTFail("Should have encoded second response") expectation2.fulfill() return } @@ -689,7 +692,7 @@ class ParseLiveQueryTests: XCTestCase { XCTAssertNotNil(ParseLiveQuery.client?.task) originalTask = ParseLiveQuery.client?.task expectation1.fulfill() - } else if count == 2 { + } else { XCTAssertNotNil(ParseLiveQuery.client?.task) XCTAssertFalse(originalTask == ParseLiveQuery.client?.task) expectation2.fulfill() @@ -698,11 +701,12 @@ class ParseLiveQueryTests: XCTestCase { ParseLiveQuery.client?.close() ParseLiveQuery.client?.synchronizationQueue.sync { - XCTAssertNil(ParseLiveQuery.client?.task) if let socketEstablished = ParseLiveQuery.client?.isSocketEstablished { XCTAssertFalse(socketEstablished) } else { XCTFail("Should have socket that isn't established") + expectation2.fulfill() + return } //Resubscribe @@ -711,15 +715,17 @@ class ParseLiveQueryTests: XCTestCase { subscription = try Query.subscribe(handler) } catch { XCTFail("\(error)") + expectation2.fulfill() + return } try? self.pretendToBeConnected() - let response2 = PreliminaryMessageResponse(op: .subscribed, requestId: 2, clientId: "yolo", installationId: "naw") guard let encoded2 = try? ParseCoding.jsonEncoder().encode(response2) else { + XCTFail("Should have encoded second response") expectation2.fulfill() return }