Files
OSGKeyboard/OSGKeyboardTests/ClipboardSemanticAnalyzerTests.swift
T
Rocky b275b6b0d9 test(ai): expand AI flow and clipboard semantic coverage
- Cover reply-variant decision paths, scene-detector abstention, and
  the merged reply center stance catalog.
- Lock in clipboard semantic analyzer thresholds, complaint-only
  suppression, and existing-skill routing.
- Add durable session, refresh, and managed-gateway reliability tests
  for the Apple account path.
- Verify personal-style prompt derivation, low-confidence ASR
  tendencies, and Polish style learning against real corpus evidence.
- Exercise the assistant keyboard and Polish styles UI flows in
  end-to-end UI tests.
2026-08-29 11:51:25 +08:00

501 lines
18 KiB
Swift

// ClipboardSemanticAnalyzerTests.swift
// OSGKeyboardTests
@testable import OSGKeyboardShared
import XCTest
final class ClipboardSemanticAnalyzerTests: XCTestCase {
func testEmptyTextReturnsNoLabels() async {
let analysis = await ClipboardSemanticAnalyzer().analyze(" \n ")
XCTAssertNil(analysis.language)
XCTAssertEqual(analysis.sentiment, .unknown)
XCTAssertFalse(analysis.task.isDetected)
XCTAssertFalse(analysis.question.isDetected)
XCTAssertFalse(analysis.invitation.isDetected)
XCTAssertFalse(analysis.complaint.isDetected)
XCTAssertFalse(analysis.replyableMessage.isDetected)
XCTAssertFalse(analysis.scheduleNegotiation.isDetected)
XCTAssertFalse(analysis.confirmationDecision.isDetected)
XCTAssertFalse(analysis.followUpReminder.isDetected)
XCTAssertFalse(analysis.blessing.isDetected)
XCTAssertFalse(analysis.assistantCommand.isDetected)
XCTAssertFalse(analysis.informationQuery.isDetected)
XCTAssertFalse(analysis.systemNotification.isDetected)
XCTAssertNil(analysis.domain)
XCTAssertNil(analysis.domainConfidence)
}
func testCurrentManifestWithoutExtendedClassifiersFailsClosed() async {
let analysis = await ClipboardSemanticAnalyzer().analyze(
"What is the weather in Shanghai tomorrow?"
)
XCTAssertEqual(analysis.assistantCommand, .notDetected)
XCTAssertEqual(analysis.informationQuery, .notDetected)
XCTAssertEqual(analysis.systemNotification, .notDetected)
XCTAssertNil(analysis.domain)
XCTAssertNil(analysis.domainConfidence)
}
func testPublicDomainTaxonomyContainsTwelveStableLabels() {
XCTAssertEqual(
ClipboardSemanticDomain.allCases.map(\.rawValue),
[
"finance",
"travel",
"calendar",
"communication",
"media",
"smartHome",
"shopping",
"dining",
"health",
"weather",
"accountService",
"generalKnowledge"
]
)
}
func testComplaintTaskPolicySuppressesImplicitFailure() {
XCTAssertTrue(
ClipboardSemanticAnalyzer.shouldSuppressTask(
text: "The same failure happened again and delivery is delayed.",
complaintConfidence: 0.76
)
)
}
func testComplaintTaskPolicyPreservesExplicitAssignment() {
XCTAssertFalse(
ClipboardSemanticAnalyzer.shouldSuppressTask(
text: "The attachment still fails. Please send the corrected file today.",
complaintConfidence: 0.92
)
)
}
func testComplaintTaskPolicyIgnoresWeakComplaintEvidence() {
XCTAssertFalse(
ClipboardSemanticAnalyzer.shouldSuppressTask(
text: "Delivery is delayed.",
complaintConfidence: 0.59
)
)
}
func testBlessingRoutingAcceptsBroadExplicitWishes() {
let samples = [
"一路平安,到了记得报个平安。",
"祝愿她早日康复,重新回到喜欢的生活。",
"恭喜顺利毕业,前程似锦!",
"端午安康,愿大家平安喜乐。",
"Safe travels and all the best for the new chapter.",
"Get well soon. We are all thinking of you.",
"Happy anniversary! May your days stay full of love."
]
for text in samples {
let result = ClipboardSemanticAnalyzer.adjustedBlessingLabel(
blessingCandidate(confidence: 0),
text: text
)
XCTAssertTrue(result.isDetected, "Missed broad blessing: \(text)")
XCTAssertTrue(result.isApprovedForAutomaticRouting)
}
}
func testBlessingRoutingRejectsBoundaryContexts() {
let samples = [
"谢谢大家发来的生日祝福。",
"帮我写一段适合春节发微信的祝福语。",
"文档里引用了“祝你生日快乐”这句话。",
"他们说晚点会祝你生日快乐。",
"我们应该找个时间庆祝项目上线。",
"The article quotes the phrase “wishing you good health.”",
"Thanks for all the wonderful birthday wishes.",
"Thanks for the birthday wish you sent yesterday.",
"Find me a message template that says happy birthday."
]
for text in samples {
let result = ClipboardSemanticAnalyzer.adjustedBlessingLabel(
blessingCandidate(confidence: 0.999),
text: text
)
XCTAssertFalse(result.isDetected, "Boundary routed as blessing: \(text)")
XCTAssertTrue(
ClipboardSemanticAnalyzer.isRejectedBlessingContext(in: text),
"Boundary was not rejected: \(text)"
)
}
}
func testBlessingRoutingAllowsReciprocalWish() {
let samples = [
"谢谢你的祝福,也祝你新年快乐、万事如意!",
"Thanks for the kind wishes. Wishing you a wonderful year too!"
]
for text in samples {
let result = ClipboardSemanticAnalyzer.adjustedBlessingLabel(
blessingCandidate(confidence: 0),
text: text
)
XCTAssertTrue(result.isDetected, "Reciprocal wish was rejected: \(text)")
}
}
func testBlessingRoutingUsesStrictModelFallback() {
let implicitWish = "希望接下来的日子都有温暖和惊喜。"
let highConfidence = ClipboardSemanticAnalyzer.adjustedBlessingLabel(
blessingCandidate(confidence: 0.99),
text: implicitWish
)
let lowerConfidence = ClipboardSemanticAnalyzer.adjustedBlessingLabel(
blessingCandidate(confidence: 0.97),
text: implicitWish
)
XCTAssertTrue(highConfidence.isDetected)
XCTAssertEqual(highConfidence.threshold, 0.98)
XCTAssertFalse(lowerConfidence.isDetected)
XCTAssertEqual(lowerConfidence.threshold, 0.98)
}
func testImplicitComplaintDoesNotRouteAsTask() async {
let analysis = await ClipboardSemanticAnalyzer().analyze(
"""
It is the same outcome again: tracking has not moved for five days, \
leaving delivery delayed.
"""
)
XCTAssertGreaterThanOrEqual(analysis.complaint.confidence, 0.60)
XCTAssertFalse(analysis.task.isDetected)
}
func testDetectsLanguageAndStructuredDataLocally() async {
let text = """
Meet on August 28, 2026 at 3:00 PM at 1 Apple Park Way, Cupertino, CA 95014.
Call +1 408-996-1010 or visit https://www.apple.com.
"""
let analysis = await ClipboardSemanticAnalyzer().analyze(text)
XCTAssertEqual(analysis.language?.identifier, "en")
XCTAssertTrue(analysis.hasDateOrTime)
XCTAssertTrue(analysis.hasAddress)
XCTAssertTrue(analysis.hasPhoneNumber)
XCTAssertTrue(analysis.hasURL)
}
func testStandaloneHTTPSLinkBecomesSingleWebURL() async {
let analysis = await ClipboardSemanticAnalyzer().analyze(
"https://www.apple.com/newsroom/"
)
XCTAssertEqual(
analysis.singleWebURL?.absoluteString,
"https://www.apple.com/newsroom/"
)
}
func testBareDomainIsNormalizedToHTTPS() {
let url = ClipboardWebLinkResolver.singleWebURL(
in: "详情见 www.apple.com/newsroom/"
)
XCTAssertEqual(
url?.absoluteString,
"https://www.apple.com/newsroom/"
)
}
func testMultipleLinksHaveNoSingleActionTarget() {
XCTAssertNil(
ClipboardWebLinkResolver.singleWebURL(
in: "https://example.com/a 和 https://example.com/b"
)
)
}
func testStandalonePhoneNumberBecomesSingleActionTarget() async throws {
let analysis = await ClipboardSemanticAnalyzer().analyze(
"请拨打 +1 (408) 996-1010"
)
XCTAssertEqual(analysis.singlePhoneNumber, "+14089961010")
XCTAssertEqual(
AIPhoneNumberResolver.telephoneURL(
for: try XCTUnwrap(analysis.singlePhoneNumber)
)?.absoluteString,
"tel:+14089961010"
)
}
func testMultiplePhoneNumbersHaveNoSingleActionTarget() {
let labels = [
ClipboardTextLabel(sourceText: "+1 408-996-1010"),
ClipboardTextLabel(sourceText: "400-666-8800")
]
XCTAssertNil(AIPhoneNumberResolver.singlePhoneNumber(from: labels))
}
func testIntentModelsPreserveAutomaticRoutingApproval() async {
let analyzer = ClipboardSemanticAnalyzer()
let task = await analyzer.analyze(
"请今天下班前发送会议纪要,完成后发给项目群。"
)
let question = await analyzer.analyze(
"退款流程具体是怎么安排的?"
)
let invitation = await analyzer.analyze(
"今晚七点在老地方吃饭,你能来吗?"
)
let complaint = await analyzer.analyze(
"应用一直闪退,数据还丢了,你们能尽快处理吗?"
)
XCTAssertTrue(task.task.isApprovedForAutomaticRouting)
XCTAssertTrue(task.task.isDetected)
XCTAssertTrue(question.question.isApprovedForAutomaticRouting)
XCTAssertTrue(question.question.isDetected)
XCTAssertTrue(isThresholdCrossing(invitation.invitation))
XCTAssertEqual(
invitation.invitation.isDetected,
invitation.invitation.isApprovedForAutomaticRouting
)
XCTAssertTrue(isThresholdCrossing(complaint.complaint))
XCTAssertEqual(
complaint.complaint.isDetected,
complaint.complaint.isApprovedForAutomaticRouting
)
}
func testReplyableModelDistinguishesConversationFromAcknowledgment() async {
let analyzer = ClipboardSemanticAnalyzer()
let conversation = await analyzer.analyze(
"我刚到家,今天真是累坏了。"
)
let acknowledgment = await analyzer.analyze(
"收到,谢谢。"
)
XCTAssertTrue(conversation.replyableMessage.isApprovedForAutomaticRouting)
XCTAssertTrue(conversation.replyableMessage.isDetected)
XCTAssertTrue(acknowledgment.replyableMessage.isApprovedForAutomaticRouting)
XCTAssertFalse(acknowledgment.replyableMessage.isDetected)
}
func testPersonalPlanDoesNotBecomeAutomaticTask() async {
let analysis = await ClipboardSemanticAnalyzer().analyze(
"私人备忘:我准备周五自己整理完这份报告。"
)
XCTAssertTrue(analysis.task.isApprovedForAutomaticRouting)
XCTAssertFalse(analysis.task.isDetected)
XCTAssertFalse(analysis.replyableMessage.isDetected)
}
func testNewIntentModelsDetectEnglishAndChineseExamples() async {
let analyzer = ClipboardSemanticAnalyzer()
let samples: [
(
name: String,
text: String,
label: KeyPath<ClipboardSemanticAnalysis, ClipboardIntentLabel>
)
] = [
(
"English schedule negotiation",
"We need to reschedule the review. Is Tuesday or Thursday better?",
\.scheduleNegotiation
),
(
"Chinese schedule negotiation",
"周二下午还是周三下午开会更方便?",
\.scheduleNegotiation
),
(
"English confirmation decision",
"I approve the revised proposal; proceed with this version.",
\.confirmationDecision
),
(
"Chinese confirmation decision",
"我批准这版方案,就按这个版本继续推进。",
\.confirmationDecision
),
(
"English follow-up reminder",
"Reminder: create the release tag before 3 PM.",
\.followUpReminder
),
(
"Chinese follow-up reminder",
"提醒一下,下次会议前要创建发布标签。",
\.followUpReminder
),
(
"English blessing",
"Happy birthday! Wishing you a joyful year filled with good health.",
\.blessing
),
(
"Chinese third-party blessing",
"群里的朋友们,一起祝王老师生日快乐、身体健康!",
\.blessing
)
]
for sample in samples {
let analysis = await analyzer.analyze(sample.text)
let label = analysis[keyPath: sample.label]
XCTAssertTrue(
isThresholdCrossing(label),
"\(sample.name) confidence \(label.confidence) is below \(label.threshold)"
)
XCTAssertEqual(
label.isDetected,
label.isApprovedForAutomaticRouting,
"\(sample.name) does not preserve routing approval"
)
}
}
func testNewIntentModelsRejectLexicallySimilarHardNegatives() async {
let analyzer = ClipboardSemanticAnalyzer()
let samples: [
(
name: String,
text: String,
label: KeyPath<ClipboardSemanticAnalysis, ClipboardIntentLabel>
)
] = [
(
"English fixed schedule",
"The meeting was confirmed for Tuesday and the calendar is already updated.",
\.scheduleNegotiation
),
(
"Chinese fixed schedule",
"会议已经确定在周二,日历也更新好了。",
\.scheduleNegotiation
),
(
"English pending approval",
"Message received; this does not mean approval.",
\.confirmationDecision
),
(
"Chinese pending approval",
"消息已阅,不代表审批通过。",
\.confirmationDecision
),
(
"English uncertain follow-up",
"Someone may follow up if there is time, but it is uncertain.",
\.followUpReminder
),
(
"Chinese uncertain follow-up",
"有空的话或许跟进,但不确定。",
\.followUpReminder
),
(
"English quoted blessing",
"The article quotes the phrase “wishing you good health.”",
\.blessing
),
(
"Chinese occasion announcement",
"今天是小林生日,蛋糕已经送到会议室。",
\.blessing
)
]
for sample in samples {
let analysis = await analyzer.analyze(sample.text)
let label = analysis[keyPath: sample.label]
XCTAssertFalse(
isThresholdCrossing(label),
"\(sample.name) false positive at confidence \(label.confidence)"
)
XCTAssertFalse(label.isDetected)
}
}
func testNewIntentModelsSupportMultipleLabels() async {
let analyzer = ClipboardSemanticAnalyzer()
let scheduleQuestion = await analyzer.analyze(
"We need to reschedule the review. Is Tuesday or Thursday better?"
)
XCTAssertTrue(isThresholdCrossing(scheduleQuestion.scheduleNegotiation))
XCTAssertTrue(isThresholdCrossing(scheduleQuestion.question))
}
func testScheduleNegotiationCombinesWithDeterministicDateDetection() async {
let analysis = await ClipboardSemanticAnalyzer().analyze(
"Would September 2 or September 3, 2026 at 3 PM work for the review?"
)
XCTAssertTrue(isThresholdCrossing(analysis.scheduleNegotiation))
XCTAssertTrue(analysis.hasDateOrTime)
XCTAssertGreaterThanOrEqual(analysis.dates.count, 2)
}
func testTenModelColdAndWarmLatencyBudgets() async {
let analyzer = ClipboardSemanticAnalyzer()
let text = "Could we move Tuesday's review to Thursday, then remind me to follow up?"
let clock = ContinuousClock()
let coldStart = clock.now
_ = await analyzer.analyze(text)
let coldMilliseconds = milliseconds(from: coldStart.duration(to: clock.now))
let iterations = 20
let warmStart = clock.now
for _ in 0..<iterations {
_ = await analyzer.analyze(text)
}
let warmTotalMilliseconds = milliseconds(from: warmStart.duration(to: clock.now))
let warmAverageMilliseconds = warmTotalMilliseconds / Double(iterations)
print(
"CLIPBOARD_SEMANTIC_BENCHMARK "
+ "coldMs=\(coldMilliseconds) "
+ "warmAverageMs=\(warmAverageMilliseconds)"
)
XCTAssertLessThan(coldMilliseconds, 2_000)
XCTAssertLessThan(warmAverageMilliseconds, 200)
}
private func isThresholdCrossing(_ label: ClipboardIntentLabel) -> Bool {
label.confidence > 0 && label.confidence >= label.threshold
}
private func blessingCandidate(confidence: Double) -> ClipboardIntentLabel {
ClipboardIntentLabel(
confidence: confidence,
threshold: 0.77,
isDetected: confidence >= 0.77,
isApprovedForAutomaticRouting: true
)
}
private func milliseconds(from duration: Duration) -> Double {
let components = duration.components
return Double(components.seconds) * 1_000
+ Double(components.attoseconds) / 1_000_000_000_000_000
}
}