// PolishRouterTests.swift // OSGKeyboard · Tests // // Locks ABE routing: sparse gate (A), prompt hard-brakes (B), and // style-specific degradation (E) without calling a real LLM. import XCTest @testable import OSGKeyboardShared final class PolishRouterTests: XCTestCase { func testSparseShortForcesConservativeLightForFunStyles() { let decision = PolishRouter.decide( text: "这个还行吧", styleID: "builtin.xhs", intensity: .heavy ) XCTAssertEqual(decision.mode, .conservative) XCTAssertEqual(decision.effectiveIntensity, .light) XCTAssertEqual(decision.effectiveStyleID, "builtin.xhs") XCTAssertTrue(decision.reasons.contains("A:sparse")) } func testDibaWithoutOpponentFallsBackToChat() { let decision = PolishRouter.decide( text: "不是这样的", styleID: "builtin.diba", intensity: .heavy ) XCTAssertEqual(decision.mode, .chatFallback) XCTAssertEqual(decision.effectiveStyleID, "builtin.chat") XCTAssertEqual(decision.effectiveIntensity, .light) XCTAssertTrue(decision.reasons.contains("E:diba_no_opponent")) } func testDibaWithOpponentQuoteStaysFull() { let decision = PolishRouter.decide( text: "回他你这叫为你好那对方不同意你还要强行是吧", styleID: "builtin.diba", intensity: .heavy ) XCTAssertEqual(decision.mode, .full) XCTAssertEqual(decision.effectiveStyleID, "builtin.diba") XCTAssertEqual(decision.effectiveIntensity, .heavy) } func testDatingSparseForcesConservative() { let decision = PolishRouter.decide( text: "还行吧", styleID: "builtin.dating", intensity: .heavy ) XCTAssertEqual(decision.mode, .conservative) XCTAssertEqual(decision.effectiveIntensity, .light) XCTAssertTrue(decision.reasons.contains("E:dating_short_no_flirt")) } func testDatingInviteQuestionStaysFull() { let decision = PolishRouter.decide( text: "今晚有空吗", styleID: "builtin.dating", intensity: .heavy ) XCTAssertEqual(decision.mode, .full) XCTAssertEqual(decision.effectiveIntensity, .heavy) } func testChatSparseForcesConservativeNoReply() { let decision = PolishRouter.decide( text: "没事", styleID: "builtin.chat", intensity: .medium ) XCTAssertEqual(decision.mode, .conservative) XCTAssertEqual(decision.effectiveIntensity, .light) XCTAssertTrue(decision.reasons.contains("E:chat_no_reply")) } func testFormalKeepsFullEvenWhenShort() { let decision = PolishRouter.decide( text: "收到", styleID: "builtin.formal", intensity: .heavy ) XCTAssertEqual(decision.mode, .full) XCTAssertEqual(decision.effectiveIntensity, .heavy) } func testContentfulMediumStaysFullForXHS() { let decision = PolishRouter.decide( text: "这款防晒霜我用了不油夏天可以推荐", styleID: "builtin.xhs", intensity: .heavy ) XCTAssertEqual(decision.mode, .full) XCTAssertEqual(decision.effectiveIntensity, .heavy) } func testPromptBlockIncludesHardBrakeForFunStyles() { let block = PolishRouter.promptBlock( mode: .conservative, styleID: "builtin.xhs", useChineseGuidance: true ) XCTAssertTrue(block.contains("信息不足时的硬刹车")) XCTAssertTrue(block.contains("本次模式:保守清理")) XCTAssertTrue(block.contains("小红书专属降级")) } func testComposerInjectsRoutingBlock() { let style = PolishStylePackCatalog.resolve( id: "builtin.dating", userCatalog: .empty ) let prompt = PolishPromptComposer.compose( text: "还行", style: style, context: PolishContext(intensity: .light), dictionaryBlock: "", globalContract: "GLOBAL", useChineseGuidance: true, routingMode: .conservative ) XCTAssertTrue(prompt.contains("信息不足时的硬刹车")) XCTAssertTrue(prompt.contains("直男癌专属降级")) XCTAssertTrue(prompt.contains("本次模式:保守清理")) } func testQuestionDraftIsDetectedAcrossStyles() { for id in ["builtin.xhs", "builtin.dating", "builtin.flex", "builtin.corp", "builtin.chat"] { let decision = PolishRouter.decide( text: "你觉得这个包怎么样", styleID: id, intensity: .heavy ) XCTAssertTrue(decision.preservesQuestion, id) XCTAssertTrue(decision.reasons.contains("Q:keep_question"), id) } } /// DiBa quotes the other party, so the user's reply may answer that question. func testDibaOpponentQuoteDoesNotTriggerQuestionGuard() { let decision = PolishRouter.decide( text: "回他别老说大家都觉得你点名是谁", styleID: "builtin.diba", intensity: .heavy ) XCTAssertFalse(decision.preservesQuestion) } func testStatementDraftDoesNotTriggerQuestionGuard() { let decision = PolishRouter.decide( text: "这款防晒霜我用了不油夏天可以推荐", styleID: "builtin.xhs", intensity: .heavy ) XCTAssertFalse(decision.preservesQuestion) } func testPromptBlockAlwaysCarriesNeverAnswerBoundary() { for id in ["builtin.light", "builtin.structured", "builtin.formal", "builtin.chat", "builtin.dating", "builtin.flex", "builtin.corp", "builtin.diba", "builtin.xhs"] { let block = PolishRouter.promptBlock( mode: .full, styleID: id, useChineseGuidance: true ) XCTAssertTrue(block.contains("绝对边界:只润色,不作答"), id) } } func testPromptBlockAddsQuestionGuardWhenAsking() { let guarded = PolishRouter.promptBlock( mode: .full, styleID: "builtin.dating", useChineseGuidance: true, preservesQuestion: true ) XCTAssertTrue(guarded.contains("问句守卫")) XCTAssertTrue(guarded.contains("同一个人提出的同一个问句")) let unguarded = PolishRouter.promptBlock( mode: .full, styleID: "builtin.dating", useChineseGuidance: true ) XCTAssertFalse(unguarded.contains("问句守卫")) } func testComposerCarriesQuestionGuardIntoPrompt() { let style = PolishStylePackCatalog.resolve( id: "builtin.dating", userCatalog: .empty ) let prompt = PolishPromptComposer.compose( text: "你觉得这个包怎么样", style: style, context: PolishContext(intensity: .heavy), dictionaryBlock: "", globalContract: "GLOBAL", useChineseGuidance: true, routingMode: .full, preservesQuestion: true ) XCTAssertTrue(prompt.contains("问句守卫")) XCTAssertTrue(prompt.contains("绝对边界:只润色,不作答")) } func testIsInformationSparseDetectsHollowShorts() { XCTAssertTrue(PolishRouter.isInformationSparse("香香的")) XCTAssertTrue(PolishRouter.isInformationSparse("这个还行吧")) XCTAssertFalse(PolishRouter.isInformationSparse( "这款防晒霜我用了不油夏天可以推荐" )) } }