summaryrefslogtreecommitdiff
path: root/SoraTests/SettingsManagerSyncTests.swift
diff options
context:
space:
mode:
Diffstat (limited to 'SoraTests/SettingsManagerSyncTests.swift')
-rw-r--r--SoraTests/SettingsManagerSyncTests.swift270
1 files changed, 72 insertions, 198 deletions
diff --git a/SoraTests/SettingsManagerSyncTests.swift b/SoraTests/SettingsManagerSyncTests.swift
index 72cf2ad..50106dc 100644
--- a/SoraTests/SettingsManagerSyncTests.swift
+++ b/SoraTests/SettingsManagerSyncTests.swift
@@ -1,7 +1,5 @@
-import Foundation
import XCTest
-// swiftlint:disable type_body_length
final class SettingsManagerSyncTests: XCTestCase {
func testBookmarkMutationPathReusesEncodedPayload() throws {
let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift")
@@ -120,210 +118,86 @@ final class SettingsManagerSyncTests: XCTestCase {
)
}
- private func loadSource(at relativePath: String) throws -> String {
- let currentFile = URL(fileURLWithPath: #filePath)
- let repositoryRoot = currentFile.deletingLastPathComponent().deletingLastPathComponent()
- let fileURL = repositoryRoot.appendingPathComponent(relativePath)
-
- return try String(contentsOf: fileURL, encoding: .utf8)
- }
-
- // swiftlint:disable:next cyclomatic_complexity
- private func extractFunction(named signature: String, from source: String) throws -> String {
- guard let signatureRange = source.range(of: signature) else {
- throw NSError(domain: "SettingsManagerSyncTests", code: 1)
- }
- guard let openingBrace = source[signatureRange.upperBound...].firstIndex(of: "{") else {
- throw NSError(domain: "SettingsManagerSyncTests", code: 2)
- }
-
- let characters = Array(source)
- let startOffset = source.distance(from: source.startIndex, to: signatureRange.lowerBound)
- var currentOffset = source.distance(from: source.startIndex, to: openingBrace)
-
- var braceDepth = 0
- var inLineComment = false
- var blockCommentDepth = 0
- var inString = false
- var isEscaped = false
-
- while currentOffset < characters.count {
- let current = characters[currentOffset]
- let next: Character? = currentOffset + 1 < characters.count ? characters[currentOffset + 1] : nil
-
- if inLineComment {
- if current == "\n" {
- inLineComment = false
- }
- currentOffset += 1
- continue
- }
-
- if blockCommentDepth > 0 {
- if current == "/", next == "*" {
- blockCommentDepth += 1
- currentOffset += 2
- continue
- }
-
- if current == "*", next == "/" {
- blockCommentDepth -= 1
- currentOffset += 2
- continue
- }
-
- currentOffset += 1
- continue
- }
-
- if inString {
- if isEscaped {
- isEscaped = false
- } else if current == "\\" {
- isEscaped = true
- } else if current == "\"" {
- inString = false
- }
-
- currentOffset += 1
- continue
- }
-
- if current == "/", next == "/" {
- inLineComment = true
- currentOffset += 2
- continue
- }
-
- if current == "/", next == "*" {
- blockCommentDepth = 1
- currentOffset += 2
- continue
- }
-
- if current == "\"" {
- inString = true
- currentOffset += 1
- continue
- }
-
- if current == "{" {
- braceDepth += 1
- } else if current == "}" {
- braceDepth -= 1
-
- if braceDepth == 0 {
- let endIndex = source.index(source.startIndex, offsetBy: currentOffset + 1)
- let startIndex = source.index(source.startIndex, offsetBy: startOffset)
-
- return String(source[startIndex..<endIndex])
- }
- }
+ func testBatchedSyncUsesCoordinatorQueue() throws {
+ let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift")
+ let triggerBatchSection = try extractFunction(
+ named: "private func triggerBatchedSync()",
+ from: source
+ )
+ let normalizedSection = strippingCommentsAndStrings(from: triggerBatchSection)
- currentOffset += 1
- }
+ let coordinatorEnqueueCount = tokenCount(
+ matching: #"\bsyncCoordinator\s*\.\s*enqueue\s*\("#,
+ in: normalizedSection
+ )
+ let detachedCount = tokenCount(
+ matching: #"\bTask\s*\.\s*detached\b"#,
+ in: normalizedSection
+ )
- throw NSError(domain: "SettingsManagerSyncTests", code: 3)
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertGreaterThan(
+ coordinatorEnqueueCount,
+ 0,
+ "Batched sync should enqueue through a single sync coordinator."
+ )
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertEqual(
+ detachedCount,
+ 0,
+ "Batched sync should not launch detached tasks directly."
+ )
}
- // swiftlint:disable:next cyclomatic_complexity
- private func strippingCommentsAndStrings(from source: String) -> String {
- let characters = Array(source)
- var result: [Character] = []
- result.reserveCapacity(characters.count)
-
- var currentOffset = 0
- var inLineComment = false
- var blockCommentDepth = 0
- var inString = false
- var isEscaped = false
-
- while currentOffset < characters.count {
- let current = characters[currentOffset]
- let next: Character? = currentOffset + 1 < characters.count ? characters[currentOffset + 1] : nil
-
- if inLineComment {
- if current == "\n" {
- inLineComment = false
- result.append("\n")
- } else {
- result.append(" ")
- }
- currentOffset += 1
- continue
- }
-
- if blockCommentDepth > 0 {
- if current == "/", next == "*" {
- blockCommentDepth += 1
- result.append(" ")
- result.append(" ")
- currentOffset += 2
- continue
- }
-
- if current == "*", next == "/" {
- blockCommentDepth -= 1
- result.append(" ")
- result.append(" ")
- currentOffset += 2
- continue
- }
-
- result.append(current == "\n" ? "\n" : " ")
- currentOffset += 1
- continue
- }
-
- if inString {
- if isEscaped {
- isEscaped = false
- } else if current == "\\" {
- isEscaped = true
- } else if current == "\"" {
- inString = false
- }
-
- result.append(current == "\n" ? "\n" : " ")
- currentOffset += 1
- continue
- }
-
- if current == "/", next == "/" {
- inLineComment = true
- result.append(" ")
- result.append(" ")
- currentOffset += 2
- continue
- }
-
- if current == "/", next == "*" {
- blockCommentDepth = 1
- result.append(" ")
- result.append(" ")
- currentOffset += 2
- continue
- }
-
- if current == "\"" {
- inString = true
- result.append(" ")
- currentOffset += 1
- continue
- }
+ func testPerKeySyncPathAvoidsDetachedFanOut() throws {
+ let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift")
+ let triggerSyncSection = try extractFunction(
+ named: "private func triggerSyncIfNeeded(for key: SettingsSyncKey)",
+ from: source
+ )
+ let normalizedSection = strippingCommentsAndStrings(from: triggerSyncSection)
- result.append(current)
- currentOffset += 1
- }
+ let detachedCount = tokenCount(
+ matching: #"\bTask\s*\.\s*detached\b"#,
+ in: normalizedSection
+ )
- return String(result)
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertEqual(
+ detachedCount,
+ 0,
+ "Per-key sync should run on the coordinator path without detached fan-out."
+ )
}
- private func tokenCount(matching pattern: String, in source: String) -> Int {
- guard let regex = try? NSRegularExpression(pattern: pattern) else { return 0 }
- let range = NSRange(source.startIndex..<source.endIndex, in: source)
+ func testManualFullSyncAggregatesChangeNotification() throws {
+ let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift")
+ let fullSyncSection = try extractFunction(
+ named: "func triggerSyncIfNeededForAll()",
+ from: source
+ )
+ let normalizedSection = strippingCommentsAndStrings(from: fullSyncSection)
- return regex.numberOfMatches(in: source, range: range)
+ let bookmarksKeyCount = tokenCount(matching: #"\.bookmarks\b"#, in: normalizedSection)
+ let favoritesKeyCount = tokenCount(matching: #"\.favorites\b"#, in: normalizedSection)
+ let searchHistoryKeyCount = tokenCount(matching: #"\.searchHistory\b"#, in: normalizedSection)
+ let customProvidersKeyCount = tokenCount(matching: #"\.customProviders\b"#, in: normalizedSection)
+ let objectWillChangeCount = tokenCount(
+ matching: #"\bobjectWillChange\s*\.\s*send\s*\("#,
+ in: normalizedSection
+ )
+
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertEqual(
+ bookmarksKeyCount + favoritesKeyCount + searchHistoryKeyCount + customProvidersKeyCount,
+ 4,
+ "Full sync should evaluate all supported sync keys."
+ )
+ // swiftlint:disable:next prefer_nimble
+ XCTAssertGreaterThan(
+ objectWillChangeCount,
+ 0,
+ "Full sync should emit a consolidated objectWillChange notification when merged state changes."
+ )
}
}
-// swiftlint:enable type_body_length