369 lines
10 KiB
Swift
369 lines
10 KiB
Swift
/*
|
|
* Copyright 2023 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 DocumentIntegrationTests: XCTestCase {
|
|
var cancellables = Set<AnyCancellable>()
|
|
|
|
let rpcAddress = RPCAddress(host: "localhost", port: 8080)
|
|
|
|
var c1: Client!
|
|
var c2: Client!
|
|
var d1: Document!
|
|
var d2: Document!
|
|
var d3: Document!
|
|
|
|
func test_single_client_document_deletion() async throws {
|
|
let options = ClientOptions()
|
|
let docKey = "\(self.description)-\(Date().description)".toDocKey
|
|
|
|
self.c1 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
|
|
self.d1 = Document(key: docKey)
|
|
|
|
// 01. client is not activated.
|
|
do {
|
|
try await self.c1.remove(self.d1)
|
|
} catch {
|
|
if case YorkieError.clientNotActive(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
// 02. document is not attached.
|
|
do {
|
|
try await self.c1.activate()
|
|
try await self.c1.remove(self.d1)
|
|
} catch {
|
|
if case YorkieError.documentNotAttached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
// 03. document is attached.
|
|
do {
|
|
try await self.c1.attach(self.d1)
|
|
try await self.c1.remove(self.d1)
|
|
} catch {
|
|
XCTAssert(false)
|
|
}
|
|
|
|
let docStatus = await self.d1.status
|
|
|
|
XCTAssertEqual(docStatus, .removed)
|
|
|
|
// 04. try to update a removed document.
|
|
do {
|
|
try await self.d1.update { root in
|
|
root.k1 = String("v1")
|
|
}
|
|
} catch {
|
|
if case YorkieError.documentRemoved = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
// 05. try to attach a removed document.
|
|
do {
|
|
try await self.c1.attach(self.d1)
|
|
} catch {
|
|
if case YorkieError.documentNotDetached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
try await self.c1.deactivate()
|
|
}
|
|
|
|
func test_removed_document_creation() async throws {
|
|
let options = ClientOptions()
|
|
let docKey = "\(self.description)-\(Date().description)".toDocKey
|
|
|
|
self.c1 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
self.c2 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
|
|
try await self.c1.activate()
|
|
try await self.c2.activate()
|
|
|
|
self.d1 = Document(key: docKey)
|
|
|
|
// 01. c1 create d1 and remove it.
|
|
try await self.d1.update { root in
|
|
root.k1 = "v1"
|
|
}
|
|
|
|
try await self.c1.attach(self.d1)
|
|
try await self.c1.detach(self.d1) // remove(self.d1)
|
|
|
|
// 02. c2 creates d2 with the same key.
|
|
self.d2 = Document(key: docKey)
|
|
|
|
try await self.c2.attach(self.d2)
|
|
|
|
// 03. c1 creates d3 with the same key.
|
|
self.d3 = Document(key: docKey)
|
|
try await self.c1.attach(self.d3)
|
|
|
|
let doc2Content = await d2.toSortedJSON()
|
|
let doc3Content = await d3.toSortedJSON()
|
|
|
|
XCTAssertEqual(doc2Content, doc3Content)
|
|
|
|
try await self.c1.detach(self.d2)
|
|
try await self.c2.detach(self.d3)
|
|
|
|
try await self.c1.deactivate()
|
|
try await self.c1.deactivate()
|
|
}
|
|
|
|
func test_removed_document_pushpull() async throws {
|
|
let options = ClientOptions()
|
|
let docKey = "\(self.description)-\(Date().description)".toDocKey
|
|
|
|
self.c1 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
self.c2 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
|
|
try await self.c1.activate()
|
|
try await self.c2.activate()
|
|
|
|
self.d1 = Document(key: docKey)
|
|
|
|
// 01. c1 creates d1 and c2 syncs.
|
|
try await self.d1.update { root in
|
|
root.k1 = "v1"
|
|
}
|
|
|
|
try await self.c1.attach(self.d1)
|
|
|
|
self.d2 = Document(key: docKey)
|
|
|
|
try await self.c2.attach(self.d2)
|
|
|
|
try await self.c1.sync()
|
|
try await self.c2.sync()
|
|
|
|
let doc1Content = await d1.toSortedJSON()
|
|
let doc2Content = await d2.toSortedJSON()
|
|
|
|
XCTAssertEqual(doc1Content, doc2Content)
|
|
|
|
// 02. c1 updates d1 and removes it.
|
|
try await self.d1.update { root in
|
|
root.k1 = "v2"
|
|
}
|
|
|
|
try await self.c1.remove(self.d1)
|
|
|
|
// 03. c2 syncs and checks that d2 is removed.
|
|
try await self.c2.sync()
|
|
|
|
let doc1Status = await d1.status
|
|
let doc2Status = await d2.status
|
|
|
|
XCTAssertEqual(doc1Status, doc2Status)
|
|
|
|
try await self.c1.deactivate()
|
|
try await self.c2.deactivate()
|
|
}
|
|
|
|
func test_removed_document_detachment() async throws {
|
|
let options = ClientOptions()
|
|
let docKey = "\(self.description)-\(Date().description)".toDocKey
|
|
|
|
self.c1 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
self.c2 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
|
|
try await self.c1.activate()
|
|
try await self.c2.activate()
|
|
|
|
self.d1 = Document(key: docKey)
|
|
|
|
// 01. c1 creates d1 and c2 syncs.
|
|
try await self.d1.update { root in
|
|
root.k1 = "v1"
|
|
}
|
|
|
|
try await self.c1.attach(self.d1)
|
|
|
|
self.d2 = Document(key: docKey)
|
|
|
|
try await self.c2.attach(self.d2)
|
|
|
|
try await self.c1.sync()
|
|
try await self.c2.sync()
|
|
|
|
let doc1Content = await d1.toSortedJSON()
|
|
let doc2Content = await d2.toSortedJSON()
|
|
|
|
XCTAssertEqual(doc1Content, doc2Content)
|
|
|
|
// 02. c1 removes d1 and c1 detaches d2
|
|
try await self.c1.remove(self.d1)
|
|
try await self.c2.detach(self.d2)
|
|
|
|
let doc1Status = await d1.status
|
|
let doc2Status = await d2.status
|
|
|
|
XCTAssertEqual(doc1Status, .removed)
|
|
XCTAssertEqual(doc2Status, .removed)
|
|
|
|
try await self.c1.deactivate()
|
|
try await self.c2.deactivate()
|
|
}
|
|
|
|
func test_removed_document_removal() async throws {
|
|
let options = ClientOptions()
|
|
let docKey = "\(self.description)-\(Date().description)".toDocKey
|
|
|
|
self.c1 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
self.c2 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
|
|
try await self.c1.activate()
|
|
try await self.c2.activate()
|
|
|
|
self.d1 = Document(key: docKey)
|
|
|
|
// 01. c1 creates d1 and c2 syncs.
|
|
try await self.d1.update { root in
|
|
root.k1 = "v1"
|
|
}
|
|
|
|
try await self.c1.attach(self.d1)
|
|
|
|
self.d2 = Document(key: docKey)
|
|
|
|
try await self.c2.attach(self.d2)
|
|
|
|
try await self.c1.sync()
|
|
try await self.c2.sync()
|
|
|
|
let doc1Content = await d1.toSortedJSON()
|
|
let doc2Content = await d2.toSortedJSON()
|
|
|
|
XCTAssertEqual(doc1Content, doc2Content)
|
|
|
|
// 02. c1 removes d1 and c1 removes d2
|
|
try await self.c1.remove(self.d1)
|
|
try await self.c2.remove(self.d2)
|
|
|
|
let doc1Status = await d1.status
|
|
let doc2Status = await d2.status
|
|
|
|
XCTAssertEqual(doc1Status, .removed)
|
|
XCTAssertEqual(doc2Status, .removed)
|
|
|
|
try await self.c1.deactivate()
|
|
try await self.c2.deactivate()
|
|
}
|
|
|
|
// State transition of document
|
|
// ┌──────────┐ Attach ┌──────────┐ Remove ┌─────────┐
|
|
// │ Detached ├───────►│ Attached ├───────►│ Removed │
|
|
// └──────────┘ └─┬─┬──────┘ └─────────┘
|
|
// ▲ │ │ ▲
|
|
// └───────────┘ └─────┘
|
|
// Detach PushPull
|
|
func test_document_state_transition() async throws {
|
|
let options = ClientOptions()
|
|
let docKey = "\(self.description)-\(Date().description)".toDocKey
|
|
|
|
self.c1 = Client(rpcAddress: self.rpcAddress, options: options)
|
|
|
|
try await self.c1.activate()
|
|
|
|
// 01. abnormal behavior on detached state
|
|
self.d1 = Document(key: docKey)
|
|
|
|
do {
|
|
try await self.c1.detach(self.d1)
|
|
} catch {
|
|
if case YorkieError.documentNotAttached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
do {
|
|
try await self.c1.sync()
|
|
} catch {
|
|
if case YorkieError.documentNotAttached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
do {
|
|
try await self.c1.remove(self.d1)
|
|
} catch {
|
|
if case YorkieError.documentNotAttached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
// 02. abnormal behavior on attached state
|
|
try await self.c1.attach(self.d1)
|
|
|
|
do {
|
|
try await self.c1.attach(self.d1)
|
|
} catch {
|
|
if case YorkieError.documentNotDetached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
// 03. abnormal behavior on removed state
|
|
try await self.c1.remove(self.d1)
|
|
|
|
do {
|
|
try await self.c1.remove(self.d1)
|
|
} catch {
|
|
if case YorkieError.documentNotAttached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
do {
|
|
try await self.c1.sync()
|
|
} catch {
|
|
if case YorkieError.documentNotAttached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
do {
|
|
try await self.c1.detach(self.d1)
|
|
} catch {
|
|
if case YorkieError.documentNotAttached(message:) = error {
|
|
} else {
|
|
XCTAssert(false)
|
|
}
|
|
}
|
|
|
|
try await self.c1.deactivate()
|
|
}
|
|
}
|