498f407585
Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
135 lines
4.8 KiB
Swift
135 lines
4.8 KiB
Swift
// UsageStatisticsCloudSyncTests.swift
|
|
// OSGKeyboardTests
|
|
//
|
|
// Hermetic tests for cumulative usage statistics iCloud merge.
|
|
|
|
@testable import OSGKeyboardShared
|
|
import XCTest
|
|
|
|
@MainActor
|
|
final class UsageStatisticsCloudSyncTests: XCTestCase {
|
|
|
|
private var suiteName: String!
|
|
private var defaults: UserDefaults!
|
|
private var store: AppGroupStore!
|
|
private var kvs: FakeUbiquitousKeyValueStore!
|
|
private var sync: UsageStatisticsCloudSync!
|
|
private let deviceA = "device-a"
|
|
private let deviceB = "device-b"
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
suiteName = "group.com.osgkeyboard.shared.tests.usage.\(UUID().uuidString)"
|
|
defaults = UserDefaults(suiteName: suiteName)!
|
|
defaults.removePersistentDomain(forName: suiteName)
|
|
defaults.set(deviceA, forKey: "sync.deviceID.v1")
|
|
store = AppGroupStore(defaults: defaults)
|
|
store.setSettingsICloudSyncEnabled(true)
|
|
kvs = FakeUbiquitousKeyValueStore()
|
|
sync = UsageStatisticsCloudSync(kvs: kvs) { [unowned self] in store }
|
|
}
|
|
|
|
override func tearDown() {
|
|
defaults.removePersistentDomain(forName: suiteName)
|
|
super.tearDown()
|
|
}
|
|
|
|
func testGCounterMergeSumsAcrossDevices() {
|
|
let local = SyncedUsageStatisticsV2(devices: [
|
|
deviceA: UsageStatisticsDeviceSlice(
|
|
updatedAt: Date(timeIntervalSince1970: 100),
|
|
dictationDurationSeconds: 30,
|
|
dictationCharacterCount: 120,
|
|
translationCharacterCount: 10
|
|
)
|
|
])
|
|
let remote = SyncedUsageStatisticsV2(devices: [
|
|
deviceB: UsageStatisticsDeviceSlice(
|
|
updatedAt: Date(timeIntervalSince1970: 200),
|
|
dictationDurationSeconds: 45,
|
|
dictationCharacterCount: 80,
|
|
translationCharacterCount: 25
|
|
)
|
|
])
|
|
|
|
let merged = SyncedUsageStatisticsV2.merge(local: local, remote: remote).aggregated
|
|
|
|
XCTAssertEqual(merged.dictationDurationSeconds, 75)
|
|
XCTAssertEqual(merged.dictationCharacterCount, 200)
|
|
XCTAssertEqual(merged.translationCharacterCount, 35)
|
|
}
|
|
|
|
func testGCounterMergeTakesMaxForSameDevice() {
|
|
let local = SyncedUsageStatisticsV2(devices: [
|
|
deviceA: UsageStatisticsDeviceSlice(
|
|
updatedAt: Date(timeIntervalSince1970: 100),
|
|
dictationDurationSeconds: 30,
|
|
dictationCharacterCount: 120,
|
|
translationCharacterCount: 10
|
|
)
|
|
])
|
|
let remote = SyncedUsageStatisticsV2(devices: [
|
|
deviceA: UsageStatisticsDeviceSlice(
|
|
updatedAt: Date(timeIntervalSince1970: 200),
|
|
dictationDurationSeconds: 45,
|
|
dictationCharacterCount: 80,
|
|
translationCharacterCount: 25
|
|
)
|
|
])
|
|
|
|
let merged = SyncedUsageStatisticsV2.merge(local: local, remote: remote).aggregated
|
|
|
|
XCTAssertEqual(merged.dictationDurationSeconds, 45)
|
|
XCTAssertEqual(merged.dictationCharacterCount, 120)
|
|
XCTAssertEqual(merged.translationCharacterCount, 25)
|
|
}
|
|
|
|
func testPullAndMergeAppliesRemoteTotals() async throws {
|
|
let remote = SyncedUsageStatisticsV2(devices: [
|
|
deviceB: UsageStatisticsDeviceSlice(
|
|
updatedAt: Date(),
|
|
dictationDurationSeconds: 90,
|
|
dictationCharacterCount: 500,
|
|
translationCharacterCount: 40
|
|
)
|
|
])
|
|
try sync.push(remote)
|
|
|
|
await sync.pullAndMerge(store: store)
|
|
|
|
let loaded = SyncedUsageStatisticsStorage.load(from: defaults).aggregated
|
|
XCTAssertEqual(loaded.dictationDurationSeconds, 90)
|
|
XCTAssertEqual(loaded.dictationCharacterCount, 500)
|
|
XCTAssertEqual(loaded.translationCharacterCount, 40)
|
|
}
|
|
|
|
func testPullUnionsIndependentDeviceTotals() async throws {
|
|
SyncedUsageStatisticsStorage.upsertCurrentDeviceSlice(
|
|
UsageStatisticsDeviceSlice(
|
|
updatedAt: Date(),
|
|
dictationDurationSeconds: 10,
|
|
dictationCharacterCount: 300,
|
|
translationCharacterCount: 0
|
|
),
|
|
defaults: defaults,
|
|
deviceID: deviceA
|
|
)
|
|
let remote = SyncedUsageStatisticsV2(devices: [
|
|
deviceB: UsageStatisticsDeviceSlice(
|
|
updatedAt: Date(),
|
|
dictationDurationSeconds: 20,
|
|
dictationCharacterCount: 150,
|
|
translationCharacterCount: 5
|
|
)
|
|
])
|
|
try sync.push(remote)
|
|
|
|
await sync.pullAndMerge(store: store)
|
|
|
|
let loaded = SyncedUsageStatisticsStorage.load(from: defaults).aggregated
|
|
XCTAssertEqual(loaded.dictationDurationSeconds, 30)
|
|
XCTAssertEqual(loaded.dictationCharacterCount, 450)
|
|
XCTAssertEqual(loaded.translationCharacterCount, 5)
|
|
}
|
|
}
|