Split command execution into run, output, stdin, cancel and list tools for the assistant and MCP. Preserve bounded output tails and use conversation-scoped handles and secret storage with AgentKit 1.20.0. Add the built-in ssh-exec skill with timestamped log redirection and text/secret stdin workflows. Expand long tool output without adjacent copy controls, update localized presentation, and refresh regression coverage and the website tool name. BREAKING CHANGE: Replace ssh_exec and ssh_wait with ssh_exec_run and ssh_exec_output. Move stdin to ssh_exec_stdin and remove execution timeout and cwd arguments. Use run_in_background to control foreground execution.
209 lines
9.4 KiB
Swift
209 lines
9.4 KiB
Swift
import Foundation
|
|
import TermindApplication
|
|
import TermindDomain
|
|
import TermindPresentation
|
|
import Testing
|
|
|
|
@testable import Termind
|
|
|
|
struct AppLanguageTests {
|
|
@Test func hostQuotaUsesPaywallWithoutAnErrorMessage() {
|
|
let error: any Error = HostAccessError.hostLimit
|
|
#expect(HostAccessError.presentationDescription(of: error) == nil)
|
|
}
|
|
|
|
@Test
|
|
func hostAccessErrorsKeepTheirLocalizedMessageWhenErasedToError() {
|
|
let error: any Error = HostAccessError.entitlementVerificationFailed
|
|
#expect(error.localizedDescription == String(localized: AppStrings.Pro.accessVerificationFailed))
|
|
#expect(
|
|
HostAccessError.presentationDescription(of: error)
|
|
== String(localized: AppStrings.Pro.accessVerificationFailed))
|
|
}
|
|
|
|
@Test
|
|
func languagePickerKeepsSystemFirstThenSortsByLanguageCode() {
|
|
#expect(AppLanguage.allCases.first == .system)
|
|
let languageCodes = AppLanguage.allCases.dropFirst().map(\.rawValue)
|
|
#expect(languageCodes == languageCodes.sorted())
|
|
}
|
|
|
|
@Test(arguments: [AppLanguage.korean, .german, .french])
|
|
@MainActor
|
|
func screenshotConversationLocalizesProseWithoutTranslatingTechnicalReferences(language: AppLanguage) {
|
|
let english = ConversationModel.demo(language: .english)
|
|
let localized = ConversationModel.demo(language: language)
|
|
#expect(localized.customTitle != english.customTitle)
|
|
#expect(localized.messages.count == english.messages.count)
|
|
for (message, source) in zip(localized.messages, english.messages) {
|
|
#expect(message.text != source.text)
|
|
#expect(message.role == source.role)
|
|
}
|
|
#expect(localized.messages.last?.text.contains("`127.0.0.1:8080`") == true)
|
|
#expect(localized.messages.last?.text.contains("`/health`") == true)
|
|
#expect(localized.toolCards.map(\.invocation.call.name) == ["ssh_exec_run", "ssh_exec_run"])
|
|
}
|
|
|
|
@Test
|
|
func missingAndUnknownPreferencesFollowSystem() throws {
|
|
let defaults = try makeDefaults()
|
|
#expect(AppLanguage.stored(in: defaults) == .system)
|
|
|
|
defaults.set("unsupported", forKey: SettingsKey.appLanguage)
|
|
#expect(AppLanguage.stored(in: defaults) == .system)
|
|
}
|
|
|
|
@Test
|
|
func explicitLanguagesWriteCanonicalLanguageTags() throws {
|
|
let defaults = try makeDefaults()
|
|
|
|
AppLanguage.store(.english, in: defaults)
|
|
#expect(AppLanguage.stored(in: defaults) == .english)
|
|
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["en"])
|
|
|
|
AppLanguage.store(.japanese, in: defaults)
|
|
#expect(AppLanguage.stored(in: defaults) == .japanese)
|
|
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["ja"])
|
|
#expect(AppLanguage.japanese.locale.identifier == "ja")
|
|
#expect(AppLanguage.japanese.displayName == "日本語")
|
|
|
|
AppLanguage.store(.simplifiedChinese, in: defaults)
|
|
#expect(AppLanguage.stored(in: defaults) == .simplifiedChinese)
|
|
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["zh-Hans"])
|
|
#expect(AppLanguage.simplifiedChinese.locale.identifier == "zh-Hans")
|
|
#expect(AppLanguage.simplifiedChinese.displayName == "简体中文")
|
|
|
|
AppLanguage.store(.traditionalChinese, in: defaults)
|
|
#expect(AppLanguage.stored(in: defaults) == .traditionalChinese)
|
|
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["zh-Hant"])
|
|
#expect(AppLanguage.traditionalChinese.locale.identifier == "zh-Hant")
|
|
#expect(AppLanguage.traditionalChinese.displayName == "繁體中文")
|
|
}
|
|
|
|
@Test(arguments: [
|
|
(AppLanguage.german, "de", "Deutsch"),
|
|
(.french, "fr", "Français"),
|
|
(.korean, "ko", "한국어"),
|
|
])
|
|
func additionalLanguageChoicesPersistAndRestoreSystemNegotiation(
|
|
language: AppLanguage, tag: String, name: String
|
|
) throws {
|
|
let suiteName = "AppLanguageTests.\(UUID().uuidString)"
|
|
let defaults = try #require(UserDefaults(suiteName: suiteName))
|
|
defer { defaults.removePersistentDomain(forName: suiteName) }
|
|
#expect(AppLanguage.allCases.contains(language))
|
|
#expect(language.displayName == name)
|
|
#expect(language.locale.language.languageCode?.identifier == tag)
|
|
AppLanguage.store(language, in: defaults)
|
|
#expect(AppLanguage.stored(in: defaults) == language)
|
|
#expect(defaults.stringArray(forKey: "AppleLanguages") == [tag])
|
|
AppLanguage.store(.system, in: defaults)
|
|
#expect(AppLanguage.stored(in: defaults) == .system)
|
|
#expect(defaults.persistentDomain(forName: suiteName)?["AppleLanguages"] == nil)
|
|
}
|
|
|
|
@Test(arguments: ["zh-Hans-CN", "zh-CN", "zh_CN", "zh-Hans-SG", "zh"])
|
|
func legacySimplifiedChinesePreferencesMigrateToCanonicalTag(identifier: String) throws {
|
|
let defaults = try makeDefaults()
|
|
defaults.set(identifier, forKey: SettingsKey.appLanguage)
|
|
defaults.set([identifier], forKey: "AppleLanguages")
|
|
|
|
#expect(AppLanguage.stored(in: defaults) == .simplifiedChinese)
|
|
#expect(defaults.string(forKey: SettingsKey.appLanguage) == "zh-Hans")
|
|
#expect(defaults.stringArray(forKey: "AppleLanguages") == ["zh-Hans"])
|
|
}
|
|
|
|
@Test(arguments: ["zh-TW", "zh-Hant-TW", "zh-HK", "zh-MO", "unsupported"])
|
|
func unknownPreferencesContinueToFollowSystemWithoutMigration(identifier: String) throws {
|
|
let defaults = try makeDefaults()
|
|
defaults.set(identifier, forKey: SettingsKey.appLanguage)
|
|
|
|
#expect(AppLanguage.stored(in: defaults) == .system)
|
|
#expect(defaults.string(forKey: SettingsKey.appLanguage) == identifier)
|
|
}
|
|
|
|
@Test
|
|
func traditionalChineseDisplayNameRequiresTaiwanRegionAndVisibleFlag() {
|
|
#expect(
|
|
AppLanguage.traditionalChinese.displayName(deviceRegion: "TW", canDisplayTaiwanFlag: true)
|
|
== "正體中文")
|
|
#expect(
|
|
AppLanguage.traditionalChinese.displayName(deviceRegion: "TW", canDisplayTaiwanFlag: false)
|
|
== "繁體中文")
|
|
#expect(
|
|
AppLanguage.traditionalChinese.displayName(deviceRegion: "HK", canDisplayTaiwanFlag: true)
|
|
== "繁體中文")
|
|
#expect(
|
|
AppLanguage.traditionalChinese.displayName(deviceRegion: nil, canDisplayTaiwanFlag: false)
|
|
== "繁體中文")
|
|
#expect(AppLanguage.english.displayName(deviceRegion: "TW", canDisplayTaiwanFlag: true) == "English")
|
|
}
|
|
|
|
@Test(arguments: ["zh-TW", "zh-Hant-TW", "zh-HK", "zh-MO"])
|
|
func traditionalChineseSystemLocalesResolveTraditionalCatalog(identifier: String) {
|
|
let traditional = AppLocalization.string("Settings", locale: Locale(identifier: "zh-Hant"))
|
|
#expect(AppLocalization.string("Settings", locale: Locale(identifier: identifier)) == traditional)
|
|
}
|
|
|
|
@Test
|
|
func simplifiedAndTraditionalChineseCatalogsDoNotCrossFallback() {
|
|
let simplified = AppLocalization.string("Settings", locale: Locale(identifier: "zh-Hans"))
|
|
let traditional = AppLocalization.string("Settings", locale: Locale(identifier: "zh-Hant"))
|
|
#expect(!simplified.isEmpty)
|
|
#expect(!traditional.isEmpty)
|
|
#expect(simplified != traditional)
|
|
}
|
|
|
|
@Test(arguments: ["Google", "OpenAI", "사용자 모델"])
|
|
func koreanDynamicLabelsDoNotAssumeTheFinalConsonantOfAProviderName(provider: String) {
|
|
let locale = Locale(identifier: "ko")
|
|
#expect(
|
|
AppLocalization.resolve(
|
|
AppStrings.Assistant.modelHelp(modelName: "model-01", providerName: provider, effortName: nil),
|
|
locale: locale
|
|
) == "model-01 · 제공업체: \(provider)")
|
|
#expect(
|
|
AppLocalization.resolve(
|
|
AppStrings.Assistant.modelHelp(modelName: "model-01", providerName: provider, effortName: "높음"),
|
|
locale: locale
|
|
) == "model-01 · 제공업체: \(provider) · 추론 강도: 높음")
|
|
#expect(
|
|
AppLocalization.resolve(
|
|
AppStrings.Assistant.compactionSummary(summarized: "2,000 토큰", retained: "500 토큰"),
|
|
locale: locale
|
|
) == "압축됨 — 2,000 토큰 → 500 토큰")
|
|
}
|
|
|
|
@Test
|
|
func followSystemRemovesAppleLanguageOverride() throws {
|
|
let suiteName = "AppLanguageTests.\(UUID().uuidString)"
|
|
let defaults = try #require(UserDefaults(suiteName: suiteName))
|
|
defaults.removePersistentDomain(forName: suiteName)
|
|
defaults.set(["zh-Hans"], forKey: "AppleLanguages")
|
|
|
|
AppLanguage.store(.system, in: defaults)
|
|
|
|
#expect(AppLanguage.stored(in: defaults) == .system)
|
|
#expect(defaults.persistentDomain(forName: suiteName)?["AppleLanguages"] == nil)
|
|
}
|
|
|
|
@Test
|
|
func taiwanFlagColorDetectionHandlesColorGrayscaleAndMissingPixels() {
|
|
#expect(DeviceCensorship.containsColoredPixel([255, 0, 0, 255]))
|
|
#expect(!DeviceCensorship.containsColoredPixel([128, 128, 128, 255]))
|
|
#expect(!DeviceCensorship.containsColoredPixel([]))
|
|
}
|
|
|
|
private func pluralForms(in localization: [String: Any]) -> [String: Any] {
|
|
let variations = localization["variations"] as? [String: Any]
|
|
return variations?["plural"] as? [String: Any] ?? [:]
|
|
}
|
|
|
|
private func makeDefaults() throws -> UserDefaults {
|
|
let suiteName = "AppLanguageTests.\(UUID().uuidString)"
|
|
let defaults = try #require(UserDefaults(suiteName: suiteName))
|
|
defaults.removePersistentDomain(forName: suiteName)
|
|
return defaults
|
|
}
|
|
}
|