e5a83843db
Add resilient usage analytics, OOBE gateway flows, clipboard semantic ranking, purchase recovery, style learning, and managed current-information search.
162 lines
5.8 KiB
Swift
162 lines
5.8 KiB
Swift
@testable import OSGKeyboardShared
|
|
import XCTest
|
|
|
|
final class AIHistoryAndUsageTests: XCTestCase {
|
|
@MainActor
|
|
func testAIHistoryMutationPreservesSource() throws {
|
|
let defaults = try makeDefaults()
|
|
let store = SpeechHistoryStore(defaults: defaults)
|
|
let mutation = HistoryMutation(
|
|
action: .append,
|
|
entryID: UUID(),
|
|
text: "AI 答案",
|
|
engineMode: "local",
|
|
source: .ai,
|
|
usageCategory: .ai
|
|
)
|
|
|
|
let entry = try XCTUnwrap(store.applyHistoryMutation(mutation))
|
|
|
|
XCTAssertEqual(entry.text, "AI 答案")
|
|
XCTAssertEqual(entry.source, .ai)
|
|
}
|
|
|
|
@MainActor
|
|
func testAICharacterCommitIsIdempotent() throws {
|
|
let defaults = try makeDefaults()
|
|
let store = UsageStatisticsStore(defaults: defaults)
|
|
let commitID = UUID()
|
|
|
|
store.recordAIInsertion(text: "四个字符", commitID: commitID)
|
|
store.recordAIInsertion(text: "四个字符", commitID: commitID)
|
|
|
|
XCTAssertEqual(store.aiCharacterCount, 4)
|
|
XCTAssertEqual(store.totalInputCharacterCount, 4)
|
|
}
|
|
|
|
func testLegacyHistoryEntryDefaultsToDictationSource() throws {
|
|
let payload: [String: Any] = [
|
|
"id": UUID().uuidString,
|
|
"text": "旧记录",
|
|
"createdAt": Date().timeIntervalSinceReferenceDate,
|
|
"modifiedAt": Date().timeIntervalSinceReferenceDate,
|
|
"revision": 0
|
|
]
|
|
let data = try JSONSerialization.data(withJSONObject: payload)
|
|
let decoder = JSONDecoder()
|
|
|
|
let entry = try decoder.decode(SpeechHistoryEntry.self, from: data)
|
|
|
|
XCTAssertEqual(entry.source, .dictation)
|
|
XCTAssertNil(entry.prePolishText)
|
|
XCTAssertFalse(entry.wasTranslation)
|
|
XCTAssertNil(entry.polishStyleID)
|
|
XCTAssertNil(entry.polishStylePromptFingerprint)
|
|
}
|
|
|
|
@MainActor
|
|
func testHistoryPersistsPrePolishTranscriptWithoutChangingDisplayText() throws {
|
|
let defaults = try makeDefaults()
|
|
let store = SpeechHistoryStore(defaults: defaults)
|
|
|
|
let entry = try XCTUnwrap(
|
|
store.append(
|
|
text: "润色后的内容",
|
|
prePolishText: "呃 润色以前的内容",
|
|
wasTranslation: true,
|
|
polishStyleID: "builtin.formal",
|
|
polishStylePrompt: "翻译样本不应保存风格 Prompt",
|
|
engineMode: "local"
|
|
)
|
|
)
|
|
|
|
XCTAssertEqual(entry.text, "润色后的内容")
|
|
XCTAssertEqual(entry.prePolishText, "呃 润色以前的内容")
|
|
XCTAssertTrue(entry.wasTranslation)
|
|
XCTAssertEqual(entry.polishStyleID, "builtin.formal")
|
|
XCTAssertNil(entry.polishStylePromptFingerprint)
|
|
let reloaded = SpeechHistoryStore(defaults: defaults)
|
|
XCTAssertEqual(reloaded.entries.first?.prePolishText, "呃 润色以前的内容")
|
|
}
|
|
|
|
@MainActor
|
|
func testHistoryDeduplicatesExactPolishPromptSnapshot() throws {
|
|
let defaults = try makeDefaults()
|
|
let store = SpeechHistoryStore(defaults: defaults)
|
|
let prompt = "# 角色\n自然表达\n# 风格边界\n保持原意\n# 示例\n输入 → 输出"
|
|
|
|
let first = try XCTUnwrap(
|
|
store.append(
|
|
text: "第一条润色文本",
|
|
prePolishText: "第一条原始口述",
|
|
polishStyleID: "user.personal",
|
|
polishStylePrompt: prompt
|
|
)
|
|
)
|
|
let second = try XCTUnwrap(
|
|
store.append(
|
|
text: "第二条润色文本",
|
|
prePolishText: "第二条原始口述",
|
|
polishStyleID: "user.personal",
|
|
polishStylePrompt: prompt
|
|
)
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
first.polishStylePromptFingerprint,
|
|
second.polishStylePromptFingerprint
|
|
)
|
|
let snapshot = store.snapshot()
|
|
XCTAssertEqual(snapshot.polishStylePromptSnapshots.count, 1)
|
|
XCTAssertEqual(
|
|
snapshot.polishStylePromptSnapshots[first.polishStylePromptFingerprint ?? ""],
|
|
prompt
|
|
)
|
|
let corpus = PolishStyleLearningCorpusBuilder.build(from: snapshot)
|
|
XCTAssertEqual(corpus.examples.first?.polishStylePrompt, prompt)
|
|
}
|
|
|
|
@MainActor
|
|
func testEditingVisibleHistoryPreservesPrePolishTranscript() throws {
|
|
let defaults = try makeDefaults()
|
|
let store = SpeechHistoryStore(defaults: defaults)
|
|
let entry = try XCTUnwrap(
|
|
store.append(
|
|
text: "第一次润色",
|
|
prePolishText: "原始口述",
|
|
polishStyleID: "builtin.chat",
|
|
polishStylePrompt: "历史 Prompt",
|
|
engineMode: "local"
|
|
)
|
|
)
|
|
|
|
let updated = try XCTUnwrap(
|
|
store.applyHistoryMutation(
|
|
HistoryMutation(
|
|
action: .update,
|
|
entryID: entry.id,
|
|
expectedRevision: entry.revision,
|
|
text: "再次编辑"
|
|
)
|
|
)
|
|
)
|
|
|
|
XCTAssertEqual(updated.text, "再次编辑")
|
|
XCTAssertEqual(updated.prePolishText, "原始口述")
|
|
XCTAssertEqual(updated.polishStyleID, "builtin.chat")
|
|
XCTAssertEqual(
|
|
updated.polishStylePromptFingerprint,
|
|
entry.polishStylePromptFingerprint
|
|
)
|
|
let corpus = PolishStyleLearningCorpusBuilder.build(from: store.snapshot())
|
|
XCTAssertTrue(corpus.examples.first?.wasUserEdited == true)
|
|
}
|
|
|
|
private func makeDefaults() throws -> UserDefaults {
|
|
let name = "AIHistoryAndUsageTests.\(UUID().uuidString)"
|
|
let defaults = try XCTUnwrap(UserDefaults(suiteName: name))
|
|
defaults.removePersistentDomain(forName: name)
|
|
return defaults
|
|
}
|
|
}
|