e5a83843db
Add resilient usage analytics, OOBE gateway flows, clipboard semantic ranking, purchase recovery, style learning, and managed current-information search.
110 lines
4.4 KiB
Swift
110 lines
4.4 KiB
Swift
// AnalyticsAIOperationTests.swift
|
|
// OSGKeyboardTests
|
|
//
|
|
// Ordering, duration, and exactly-once terminal semantics for AI operations.
|
|
|
|
import Foundation
|
|
@testable import OSGKeyboardShared
|
|
import XCTest
|
|
|
|
final class AnalyticsAIOperationTests: XCTestCase {
|
|
func testStartedIsPersistedBeforeTerminalWithDurationBucket() async throws {
|
|
let monotonicClock = AnalyticsTestMonotonicClock(1_000)
|
|
let repository = AnalyticsRepository(
|
|
configuration: AnalyticsRepositoryConfiguration(
|
|
databaseURL: try analyticsTemporaryDatabaseURL()
|
|
),
|
|
clock: AnalyticsTestWallClock(),
|
|
uuidGenerator: AnalyticsTestUUIDGenerator()
|
|
)
|
|
let client = LiveAnalyticsClient(
|
|
repository: repository,
|
|
context: analyticsTestContext,
|
|
monotonicClock: monotonicClock
|
|
)
|
|
|
|
let operation = client.startAIFeature(.polish, executionMode: .managed)
|
|
monotonicClock.set(4_000_001_000)
|
|
operation.succeed()
|
|
|
|
_ = await analyticsWaitForPendingEventCount(2, repository: repository)
|
|
let events = try await analyticsDecodePendingEvents(repository: repository)
|
|
XCTAssertEqual(events.map(\.eventType), [.aiFeatureStarted, .aiFeatureSucceeded])
|
|
XCTAssertEqual(events.map(\.feature), [.polish, .polish])
|
|
XCTAssertEqual(events.map(\.executionMode), [.managed, .managed])
|
|
XCTAssertNil(events[0].durationBucket)
|
|
XCTAssertEqual(events[1].durationBucket, .threeToTenSeconds)
|
|
XCTAssertNil(events[1].failureCategory)
|
|
}
|
|
|
|
func testConcurrentTerminalCallsPersistExactlyOneTerminalEvent() async throws {
|
|
let monotonicClock = AnalyticsTestMonotonicClock(10)
|
|
let repository = AnalyticsRepository(
|
|
configuration: AnalyticsRepositoryConfiguration(
|
|
databaseURL: try analyticsTemporaryDatabaseURL()
|
|
),
|
|
clock: AnalyticsTestWallClock(),
|
|
uuidGenerator: AnalyticsTestUUIDGenerator()
|
|
)
|
|
let client = LiveAnalyticsClient(
|
|
repository: repository,
|
|
context: analyticsTestContext,
|
|
monotonicClock: monotonicClock
|
|
)
|
|
let operation = client.startAIFeature(.aiAssistant, executionMode: .local)
|
|
monotonicClock.set(2_000_000_010)
|
|
|
|
await withTaskGroup(of: Void.self) { group in
|
|
for index in 0..<60 {
|
|
group.addTask {
|
|
switch index % 3 {
|
|
case 0:
|
|
operation.succeed()
|
|
case 1:
|
|
operation.fail(category: .provider)
|
|
default:
|
|
operation.cancel()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_ = await analyticsWaitForPendingEventCount(2, repository: repository)
|
|
let events = try await analyticsDecodePendingEvents(repository: repository)
|
|
XCTAssertEqual(events.first?.eventType, .aiFeatureStarted)
|
|
XCTAssertEqual(events.count, 2)
|
|
let terminalEvents = events.filter {
|
|
[.aiFeatureSucceeded, .aiFeatureFailed].contains($0.eventType)
|
|
}
|
|
XCTAssertEqual(terminalEvents.count, 1)
|
|
XCTAssertEqual(terminalEvents.first?.feature, .aiAssistant)
|
|
XCTAssertEqual(terminalEvents.first?.executionMode, .local)
|
|
XCTAssertEqual(terminalEvents.first?.durationBucket, .oneToThreeSeconds)
|
|
}
|
|
|
|
func testCancelPersistsFailedWithCancelledCategory() async throws {
|
|
let repository = AnalyticsRepository(
|
|
configuration: AnalyticsRepositoryConfiguration(
|
|
databaseURL: try analyticsTemporaryDatabaseURL()
|
|
),
|
|
clock: AnalyticsTestWallClock(),
|
|
uuidGenerator: AnalyticsTestUUIDGenerator()
|
|
)
|
|
let client = LiveAnalyticsClient(
|
|
repository: repository,
|
|
context: analyticsTestContext,
|
|
monotonicClock: AnalyticsTestMonotonicClock()
|
|
)
|
|
|
|
client.startAIFeature(
|
|
.transcription,
|
|
executionMode: .managed
|
|
).cancel()
|
|
|
|
_ = await analyticsWaitForPendingEventCount(2, repository: repository)
|
|
let events = try await analyticsDecodePendingEvents(repository: repository)
|
|
XCTAssertEqual(events.map(\.eventType), [.aiFeatureStarted, .aiFeatureFailed])
|
|
XCTAssertEqual(events.last?.failureCategory, .cancelled)
|
|
}
|
|
}
|