yorkie-ios-sdk/Tests/Integration/ClientIntegrationTests.swift

368 lines
12 KiB
Swift

/*
* Copyright 2022 The Yorkie Authors. All rights reserved.
*
* 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 Combine
import XCTest
@testable import Yorkie
final class ClientIntegrationTests: XCTestCase {
var cancellables = Set<AnyCancellable>()
let rpcAddress = RPCAddress(host: "localhost", port: 8080)
func test_can_handle_sync() async throws {
let options = ClientOptions()
let docKey = "\(self.description)-\(Date().description)".toDocKey
let c1 = Client(rpcAddress: self.rpcAddress, options: options)
let c2 = Client(rpcAddress: self.rpcAddress, options: options)
let d1 = Document(key: docKey)
try await d1.update { root in
root.k1 = ""
root.k2 = ""
root.k3 = ""
}
let d2 = Document(key: docKey)
try await d1.update { root in
root.k1 = ""
root.k2 = ""
root.k3 = ""
}
try await c1.activate()
try await c2.activate()
try await c1.attach(d1, false)
try await c2.attach(d2, false)
try await d1.update { root in
root.k1 = "v1"
}
try await c1.sync()
try await c2.sync()
var result = await d2.getRoot().get(key: "k1") as? String
XCTAssert(result == "v1")
try await d1.update { root in
root.k2 = "v2"
}
try await c1.sync()
try await c2.sync()
result = await d2.getRoot().get(key: "k2") as? String
XCTAssert(result == "v2")
try await d1.update { root in
root.k3 = "v3"
}
try await c1.sync()
try await c2.sync()
result = await d2.getRoot().get(key: "k3") as? String
XCTAssert(result == "v3")
try await c1.detach(d1)
try await c2.detach(d2)
try await c1.deactivate()
try await c2.deactivate()
}
func test_can_handle_sync_auto() async throws {
let options = ClientOptions()
let docKey = "\(self.description)-\(Date().description)".toDocKey
let c1 = Client(rpcAddress: self.rpcAddress, options: options)
let c2 = Client(rpcAddress: self.rpcAddress, options: options)
let d1 = Document(key: docKey)
try await d1.update { root in
root.k1 = ""
root.k2 = ""
root.k3 = ""
}
let d2 = Document(key: docKey)
try await d1.update { root in
root.k1 = ""
root.k2 = ""
root.k3 = ""
}
try await c1.activate()
try await c2.activate()
try await c1.attach(d1, true)
try await c2.attach(d2, true)
try await d1.update { root in
root.k1 = "v1"
root.k2 = "v2"
root.k3 = "v3"
}
try await Task.sleep(nanoseconds: 1_500_000_000)
var result = await d2.getRoot().get(key: "k1") as? String
XCTAssert(result == "v1")
result = await d2.getRoot().get(key: "k2") as? String
XCTAssert(result == "v2")
result = await d2.getRoot().get(key: "k3") as? String
XCTAssert(result == "v3")
try await d1.update { root in
root.integer = Int32.max
root.long = Int64.max
root.double = Double.pi
}
try await Task.sleep(nanoseconds: 1_500_000_000)
let resultInteger = await d2.getRoot().get(key: "integer") as? Int32
XCTAssert(resultInteger == Int32.max)
let resultLong = await d2.getRoot().get(key: "long") as? Int64
XCTAssert(resultLong == Int64.max)
let resultDouble = await d2.getRoot().get(key: "double") as? Double
XCTAssert(resultDouble == Double.pi)
let curr = Date()
try await d1.update { root in
root.true = true
root.false = false
root.date = curr
}
try await Task.sleep(nanoseconds: 1_500_000_000)
let resultTrue = await d2.getRoot().get(key: "true") as? Bool
XCTAssert(resultTrue == true)
let resultFalse = await d2.getRoot().get(key: "false") as? Bool
XCTAssert(resultFalse == false)
let resultDate = await d2.getRoot().get(key: "date") as? Date
XCTAssert(resultDate?.trimedLessThanMilliseconds == curr.trimedLessThanMilliseconds)
try await c1.detach(d1)
try await c2.detach(d2)
try await c1.deactivate()
try await c2.deactivate()
}
func test_stream_connection_evnts() async throws {
let docKey = "\(self.description)-\(Date().description)".toDocKey
let c1 = Client(rpcAddress: rpcAddress, options: ClientOptions())
var eventCount = 0
c1.eventStream.sink { event in
switch event {
case let event as StreamConnectionStatusChangedEvent:
eventCount += 1
switch event.value {
case .connected:
XCTAssertEqual(eventCount, 1)
case .disconnected:
XCTAssertEqual(eventCount, 2)
}
default:
break
}
}.store(in: &self.cancellables)
try await c1.activate()
let d1 = Document(key: docKey)
try await c1.attach(d1)
try await c1.detach(d1)
try await c1.deactivate()
XCTAssertEqual(eventCount, 2)
}
// swiftlint: disable force_cast
func test_send_peer_changed_event_to_the_user_who_updated_presence() async throws {
struct Cursor: Codable {
// swiftlint: disable identifier_name
var x: Int
var y: Int
}
struct PresenceType: Codable {
var name: String
var cursor: Cursor
}
var option = ClientOptions()
option.presence = PresenceType(name: "c1", cursor: Cursor(x: 0, y: 0)).createdDictionary
let c1 = Client(rpcAddress: rpcAddress, options: option)
option.presence = PresenceType(name: "c2", cursor: Cursor(x: 1, y: 1)).createdDictionary
let c2 = Client(rpcAddress: rpcAddress, options: option)
try await c1.activate()
try await c2.activate()
let docKey = "\(self.description)-\(Date().description)".toDocKey
let c1ActorID = await c1.id!
let c2ActorID = await c2.id!
let d1 = Document(key: docKey)
let d2 = Document(key: docKey)
var c1Name = "c1"
var c2Name = "c2"
var c1NumberOfEvents = 0
var c2NumberOfEvents = 0
let c1ExpectedValues = [
PeersChangedValue(type: .initialized, peers: [docKey: [c1ActorID: PresenceType(name: "c1", cursor: Cursor(x: 0, y: 0)).createdDictionary]]),
PeersChangedValue(type: .watched, peers: [docKey: [c2ActorID: PresenceType(name: "c2", cursor: Cursor(x: 1, y: 1)).createdDictionary]]),
PeersChangedValue(type: .presenceChanged, peers: [docKey: [c1ActorID: PresenceType(name: "c1+", cursor: Cursor(x: 0, y: 0)).createdDictionary]]),
PeersChangedValue(type: .presenceChanged, peers: [docKey: [c2ActorID: PresenceType(name: "c2+", cursor: Cursor(x: 1, y: 1)).createdDictionary]])
]
let c2ExpectedValues = [
PeersChangedValue(type: .initialized, peers: [docKey: [c1ActorID: PresenceType(name: "c1", cursor: Cursor(x: 0, y: 0)).createdDictionary,
c2ActorID: PresenceType(name: "c2", cursor: Cursor(x: 1, y: 1)).createdDictionary]]),
PeersChangedValue(type: .presenceChanged, peers: [docKey: [c1ActorID: PresenceType(name: "c1+", cursor: Cursor(x: 0, y: 0)).createdDictionary]]),
PeersChangedValue(type: .presenceChanged, peers: [docKey: [c2ActorID: PresenceType(name: "c2+", cursor: Cursor(x: 1, y: 1)).createdDictionary]]),
PeersChangedValue(type: .unwatched, peers: [docKey: [c1ActorID: PresenceType(name: "c1+", cursor: Cursor(x: 0, y: 0)).createdDictionary]])
]
c1.eventStream.sink { event in
switch event {
case let event as PeerChangedEvent:
print("#### c1 \(event)")
XCTAssertEqual(event.value, c1ExpectedValues[c1NumberOfEvents])
c1NumberOfEvents += 1
default:
break
}
}.store(in: &self.cancellables)
c2.eventStream.sink { event in
switch event {
case let event as PeerChangedEvent:
print("#### c2 \(event)")
XCTAssertEqual(event.value, c2ExpectedValues[c2NumberOfEvents])
c2NumberOfEvents += 1
default:
break
}
}.store(in: &self.cancellables)
try await c1.attach(d1)
try await c2.attach(d2)
// To catch the watched event first.
try await Task.sleep(nanoseconds: 1_500_000_000)
c1Name = "c1+"
try await c1.updatePresence("name", c1Name)
try await c1.sync()
try await c2.sync()
let presence1: PresenceType = self.decodePresence(await c2.getPeers(key: d2.getKey())[c1.id!]!)!
XCTAssert(c1Name == presence1.name)
c2Name = "c2+"
try await c2.updatePresence("name", c2Name)
try await c2.sync()
try await c1.sync()
let presence2: PresenceType = self.decodePresence(await c1.getPeers(key: d1.getKey())[c2.id!]!)!
XCTAssert(c2Name == presence2.name)
let c1Peer = await c1.getPeers(key: d1.getKey()) as NSDictionary
let c2Peer = (await c2.getPeers(key: d2.getKey()) as NSDictionary) as! [AnyHashable: Any]
XCTAssert(c1Peer.isEqual(to: c2Peer))
try await c1.detach(d1)
// Keep the watchLoop of c2 for catch the detach event of c1.
try await Task.sleep(nanoseconds: 1_500_000_000)
try await c2.detach(d2)
try await c1.deactivate()
try await c2.deactivate()
}
func test_client_pause_resume() async throws {
let c1 = Client(rpcAddress: self.rpcAddress, options: ClientOptions())
try await c1.activate()
let docKey = "\(self.description)-\(Date().description)".toDocKey
let d1 = Document(key: docKey)
var c1NumberOfEvents = 0
let c1ExpectedValues = [
StreamConnectionStatus.connected,
StreamConnectionStatus.disconnected,
StreamConnectionStatus.connected,
StreamConnectionStatus.disconnected
]
c1.eventStream.sink { event in
switch event {
case let event as StreamConnectionStatusChangedEvent:
XCTAssertEqual(event.value, c1ExpectedValues[c1NumberOfEvents])
c1NumberOfEvents += 1
default:
break
}
}.store(in: &self.cancellables)
try await c1.attach(d1)
try await c1.pause(d1)
try await c1.resume(d1)
try await c1.detach(d1)
try await c1.deactivate()
}
private func decodePresence<T: Decodable>(_ dictionary: [String: Any]) -> T? {
guard let data = try? JSONSerialization.data(withJSONObject: dictionary, options: []) else {
return nil
}
return try? JSONDecoder().decode(T.self, from: data)
}
}