{"repo": "1011-a/mnemosyne", "files": {"Sources/Mnemosyne/Util/Config.swift": {"9c9f3975c97bdfa2a5873930a1d0990f7645c57a": {"t": 1785745886.814071, "n": 4621, "h": "2f6344390b3a96ac", "c": "import Foundation\n\n/// Loads app configuration. Secrets come from macOS Keychain by default, while\n/// process environment variables and an explicit `MNEMOSYNE_ENV_PATH` can still\n/// override values for development/testing.\nstruct Config: Sendable {\n enum DeepSeekKeySource: Sendable {\n case missing\n case settings\n case environment\n }\n\n let deepSeekKey: String\n let deepSeekBaseURL: URL\n let deepSeekModel: String\n let ollamaBaseURL: URL\n let ollamaVisionModel: String\n let deepSeekKeySource: DeepSeekKeySource\n\n /// Optional env file. There is intentionally no personal/default path here;\n /// users configure the DeepSeek key in Settings.\n static func envPath(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n guard let p = environment[\"MNEMOSYNE_ENV_PATH\"], !p.isEmpty else { return nil }\n return p\n }\n\n /// Known DeepSeek models the user can pick between.\n static let availableModels = [\"deepseek-v4-flash\", \"deepseek-v4-pro\"]\n\n /// A copy with the DeepSeek model swapped — lets agents pick a model live.\n func overriding(model: String) -> Config {\n Config(deepSeekKey: deepSeekKey, deepSeekBaseURL: deepSeekBaseURL,\n deepSeekModel: model.isEmpty ? deepSeekModel : model,\n ollamaBaseURL: ollamaBaseURL, ollamaVisionModel: ollamaVisionModel,\n deepSeekKeySource: deepSeekKeySource)\n }\n\n /// A copy with the DeepSeek key swapped from live Settings/Keychain.\n func overriding(deepSeekKey key: String) -> Config {\n let source: DeepSeekKeySource = deepSeekKeySource == .environment\n ? .environment\n : (key.isEmpty ? .missing : .settings)\n return Config(deepSeekKey: key, deepSeekBaseURL: deepSeekBaseURL,\n deepSeekModel: deepSeekModel,\n ollamaBaseURL: ollamaBaseURL, ollamaVisionModel: ollamaVisionModel,\n deepSeekKeySource: source)\n }\n\n static func load(settings: SettingsStore = SettingsStore(),\n environment: [String: String] = ProcessInfo.processInfo.environment,\n arguments: [String] = ProcessInfo.processInfo.arguments) -> Config {\n // Under XCUITest (`--uitest`) don't read the Desktop env file — Desktop is\n // a TCC-protected folder, so touching it makes every fresh test build prompt\n // \"Mnemosyne would like to access your Desktop\". There is no default env\n // file anymore, but keep this guard for explicit paths in UI tests.\n let uitest = arguments.contains(\"--uitest\")\n let dotenv = (uitest ? nil : envPath(environment: environment)).map { parseDotEnv(at: $0) } ?? [:]\n func value(_ keys: [String]) -> String? {\n for k in keys {\n if let v = environment[k], !v.isEmpty { return v }\n if let v = dotenv[k], !v.isEmpty { return v }\n }\n return nil\n }\n let explicitKey = value([\"DEEPSEEK_API_KEY\", \"deepseek_api\"])\n let settingsKey = settings.deepSeekKey\n let key = explicitKey ?? settingsKey\n let keySource: DeepSeekKeySource = explicitKey != nil\n ? .environment\n : (settingsKey.isEmpty ? .missing : .settings)\n return Config(\n deepSeekKey: key,\n deepSeekBaseURL: URL(string: value([\"DEEPSEEK_BASE_URL\"]) ?? \"https://api.deepseek.com\")!,\n deepSeekModel: value([\"DEEPSEEK_MODEL\"]) ?? \"deepseek-v4-flash\",\n ollamaBaseURL: URL(string: value([\"OLLAMA_BASE_URL\"]) ?? \"http://127.0.0.1:11434\")!,\n ollamaVisionModel: value([\"OLLAMA_VISION_MODEL\"]) ?? \"gemma3:12b\",\n deepSeekKeySource: keySource\n )\n }\n\n private static func parseDotEnv(at path: String) -> [String: String] {\n guard let text = try? String(contentsOfFile: path, encoding: .utf8) else { return [:] }\n var out: [String: String] = [:]\n for raw in text.split(separator: \"\\n\") {\n let line = raw.trimmingCharacters(in: .whitespaces)\n guard !line.isEmpty, !line.hasPrefix(\"#\"), let eq = line.firstIndex(of: \"=\") else { continue }\n let k = String(line[..= 2, (v.hasPrefix(\"\\\"\") && v.hasSuffix(\"\\\"\")) || (v.hasPrefix(\"'\") && v.hasSuffix(\"'\")) {\n v = String(v.dropFirst().dropLast())\n }\n out[k] = v\n }\n return out\n }\n}\n"}, "19a0e6313a3504ba2c93c766485c10f268fccd26": {"t": 1785757651.7821326, "n": 4621, "h": "2f6344390b3a96ac", "c": "import Foundation\n\n/// Loads app configuration. Secrets come from macOS Keychain by default, while\n/// process environment variables and an explicit `MNEMOSYNE_ENV_PATH` can still\n/// override values for development/testing.\nstruct Config: Sendable {\n enum DeepSeekKeySource: Sendable {\n case missing\n case settings\n case environment\n }\n\n let deepSeekKey: String\n let deepSeekBaseURL: URL\n let deepSeekModel: String\n let ollamaBaseURL: URL\n let ollamaVisionModel: String\n let deepSeekKeySource: DeepSeekKeySource\n\n /// Optional env file. There is intentionally no personal/default path here;\n /// users configure the DeepSeek key in Settings.\n static func envPath(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n guard let p = environment[\"MNEMOSYNE_ENV_PATH\"], !p.isEmpty else { return nil }\n return p\n }\n\n /// Known DeepSeek models the user can pick between.\n static let availableModels = [\"deepseek-v4-flash\", \"deepseek-v4-pro\"]\n\n /// A copy with the DeepSeek model swapped — lets agents pick a model live.\n func overriding(model: String) -> Config {\n Config(deepSeekKey: deepSeekKey, deepSeekBaseURL: deepSeekBaseURL,\n deepSeekModel: model.isEmpty ? deepSeekModel : model,\n ollamaBaseURL: ollamaBaseURL, ollamaVisionModel: ollamaVisionModel,\n deepSeekKeySource: deepSeekKeySource)\n }\n\n /// A copy with the DeepSeek key swapped from live Settings/Keychain.\n func overriding(deepSeekKey key: String) -> Config {\n let source: DeepSeekKeySource = deepSeekKeySource == .environment\n ? .environment\n : (key.isEmpty ? .missing : .settings)\n return Config(deepSeekKey: key, deepSeekBaseURL: deepSeekBaseURL,\n deepSeekModel: deepSeekModel,\n ollamaBaseURL: ollamaBaseURL, ollamaVisionModel: ollamaVisionModel,\n deepSeekKeySource: source)\n }\n\n static func load(settings: SettingsStore = SettingsStore(),\n environment: [String: String] = ProcessInfo.processInfo.environment,\n arguments: [String] = ProcessInfo.processInfo.arguments) -> Config {\n // Under XCUITest (`--uitest`) don't read the Desktop env file — Desktop is\n // a TCC-protected folder, so touching it makes every fresh test build prompt\n // \"Mnemosyne would like to access your Desktop\". There is no default env\n // file anymore, but keep this guard for explicit paths in UI tests.\n let uitest = arguments.contains(\"--uitest\")\n let dotenv = (uitest ? nil : envPath(environment: environment)).map { parseDotEnv(at: $0) } ?? [:]\n func value(_ keys: [String]) -> String? {\n for k in keys {\n if let v = environment[k], !v.isEmpty { return v }\n if let v = dotenv[k], !v.isEmpty { return v }\n }\n return nil\n }\n let explicitKey = value([\"DEEPSEEK_API_KEY\", \"deepseek_api\"])\n let settingsKey = settings.deepSeekKey\n let key = explicitKey ?? settingsKey\n let keySource: DeepSeekKeySource = explicitKey != nil\n ? .environment\n : (settingsKey.isEmpty ? .missing : .settings)\n return Config(\n deepSeekKey: key,\n deepSeekBaseURL: URL(string: value([\"DEEPSEEK_BASE_URL\"]) ?? \"https://api.deepseek.com\")!,\n deepSeekModel: value([\"DEEPSEEK_MODEL\"]) ?? \"deepseek-v4-flash\",\n ollamaBaseURL: URL(string: value([\"OLLAMA_BASE_URL\"]) ?? \"http://127.0.0.1:11434\")!,\n ollamaVisionModel: value([\"OLLAMA_VISION_MODEL\"]) ?? \"gemma3:12b\",\n deepSeekKeySource: keySource\n )\n }\n\n private static func parseDotEnv(at path: String) -> [String: String] {\n guard let text = try? String(contentsOfFile: path, encoding: .utf8) else { return [:] }\n var out: [String: String] = [:]\n for raw in text.split(separator: \"\\n\") {\n let line = raw.trimmingCharacters(in: .whitespaces)\n guard !line.isEmpty, !line.hasPrefix(\"#\"), let eq = line.firstIndex(of: \"=\") else { continue }\n let k = String(line[..= 2, (v.hasPrefix(\"\\\"\") && v.hasSuffix(\"\\\"\")) || (v.hasPrefix(\"'\") && v.hasSuffix(\"'\")) {\n v = String(v.dropFirst().dropLast())\n }\n out[k] = v\n }\n return out\n }\n}\n"}, "a54dfcd38c681823a19855a3ee1339ae3ed390aa": {"t": 1785757653.0425417, "n": 4615, "h": "549b6cb6e5111a99", "c": "import Foundation\n\n/// Loads app configuration. Secrets come from macOS Keychain by default, while\n/// process environment variables and an explicit `MNEMOSYNE_ENV_PATH` can still\n/// override values for development/testing.\nstruct Config: Sendable {\n enum DeepSeekKeySource: Sendable {\n case missing\n case settings\n case environment\n }\n\n let deepSeekKey: String\n let deepSeekBaseURL: URL\n let deepSeekModel: String\n let ollamaBaseURL: URL\n let ollamaVisionModel: String\n let deepSeekKeySource: DeepSeekKeySource\n\n /// Optional env file. There is intentionally no personal/default path here;\n /// users configure the DeepSeek key in Settings.\n static func envPath(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n guard let p = environment[\"MNEMOSYNE_ENV_PATH\"], !p.isEmpty else { return nil }\n return p\n }\n\n /// Known DeepSeek models the user can pick between.\n static let availableModels = [\"deepseek-chat\", \"deepseek-reasoner\"]\n\n /// A copy with the DeepSeek model swapped — lets agents pick a model live.\n func overriding(model: String) -> Config {\n Config(deepSeekKey: deepSeekKey, deepSeekBaseURL: deepSeekBaseURL,\n deepSeekModel: model.isEmpty ? deepSeekModel : model,\n ollamaBaseURL: ollamaBaseURL, ollamaVisionModel: ollamaVisionModel,\n deepSeekKeySource: deepSeekKeySource)\n }\n\n /// A copy with the DeepSeek key swapped from live Settings/Keychain.\n func overriding(deepSeekKey key: String) -> Config {\n let source: DeepSeekKeySource = deepSeekKeySource == .environment\n ? .environment\n : (key.isEmpty ? .missing : .settings)\n return Config(deepSeekKey: key, deepSeekBaseURL: deepSeekBaseURL,\n deepSeekModel: deepSeekModel,\n ollamaBaseURL: ollamaBaseURL, ollamaVisionModel: ollamaVisionModel,\n deepSeekKeySource: source)\n }\n\n static func load(settings: SettingsStore = SettingsStore(),\n environment: [String: String] = ProcessInfo.processInfo.environment,\n arguments: [String] = ProcessInfo.processInfo.arguments) -> Config {\n // Under XCUITest (`--uitest`) don't read the Desktop env file — Desktop is\n // a TCC-protected folder, so touching it makes every fresh test build prompt\n // \"Mnemosyne would like to access your Desktop\". There is no default env\n // file anymore, but keep this guard for explicit paths in UI tests.\n let uitest = arguments.contains(\"--uitest\")\n let dotenv = (uitest ? nil : envPath(environment: environment)).map { parseDotEnv(at: $0) } ?? [:]\n func value(_ keys: [String]) -> String? {\n for k in keys {\n if let v = environment[k], !v.isEmpty { return v }\n if let v = dotenv[k], !v.isEmpty { return v }\n }\n return nil\n }\n let explicitKey = value([\"DEEPSEEK_API_KEY\", \"deepseek_api\"])\n let settingsKey = settings.deepSeekKey\n let key = explicitKey ?? settingsKey\n let keySource: DeepSeekKeySource = explicitKey != nil\n ? .environment\n : (settingsKey.isEmpty ? .missing : .settings)\n return Config(\n deepSeekKey: key,\n deepSeekBaseURL: URL(string: value([\"DEEPSEEK_BASE_URL\"]) ?? \"https://api.deepseek.com\")!,\n deepSeekModel: value([\"DEEPSEEK_MODEL\"]) ?? \"deepseek-chat\",\n ollamaBaseURL: URL(string: value([\"OLLAMA_BASE_URL\"]) ?? \"http://127.0.0.1:11434\")!,\n ollamaVisionModel: value([\"OLLAMA_VISION_MODEL\"]) ?? \"gemma3:12b\",\n deepSeekKeySource: keySource\n )\n }\n\n private static func parseDotEnv(at path: String) -> [String: String] {\n guard let text = try? String(contentsOfFile: path, encoding: .utf8) else { return [:] }\n var out: [String: String] = [:]\n for raw in text.split(separator: \"\\n\") {\n let line = raw.trimmingCharacters(in: .whitespaces)\n guard !line.isEmpty, !line.hasPrefix(\"#\"), let eq = line.firstIndex(of: \"=\") else { continue }\n let k = String(line[..= 2, (v.hasPrefix(\"\\\"\") && v.hasSuffix(\"\\\"\")) || (v.hasPrefix(\"'\") && v.hasSuffix(\"'\")) {\n v = String(v.dropFirst().dropLast())\n }\n out[k] = v\n }\n return out\n }\n}\n"}}, "Tests/MnemosyneTests/ModelPickerTests.swift": {"9c9f3975c97bdfa2a5873930a1d0990f7645c57a": {"t": 1785745893.3585331, "n": 4158, "h": "869b61054291b2cc", "c": "import XCTest\n@testable import Mnemosyne\n\nfinal class ModelPickerTests: XCTestCase {\n\n func testConfigOverridingModelPreservesOtherFields() {\n let base = Config.load()\n let overridden = base.overriding(model: \"deepseek-v4-pro\")\n XCTAssertEqual(overridden.deepSeekModel, \"deepseek-v4-pro\")\n XCTAssertEqual(overridden.deepSeekKey, base.deepSeekKey)\n XCTAssertEqual(overridden.deepSeekBaseURL, base.deepSeekBaseURL)\n XCTAssertEqual(overridden.ollamaVisionModel, base.ollamaVisionModel)\n }\n\n private func tempSettings() -> (SettingsStore, String) {\n let path = NSTemporaryDirectory() + \"secrets-\\(UUID().uuidString).json\"\n return (SettingsStore(defaults: UserDefaults(suiteName: \"Cfg-\\(UUID().uuidString)\")!,\n keychainService: \"com.mnemosyne.tests.\\(UUID().uuidString)\",\n secrets: SecretsFile(path: path)), path)\n }\n\n func testConfigLoadsDeepSeekKeyFromSettings() {\n let (settings, path) = tempSettings()\n defer { try? FileManager.default.removeItem(atPath: path) }\n XCTAssertTrue(settings.setDeepSeekKey(\"sk-from-settings\"))\n\n let config = Config.load(settings: settings, environment: [:], arguments: [])\n XCTAssertEqual(config.deepSeekKey, \"sk-from-settings\")\n XCTAssertEqual(config.deepSeekKeySource, .settings)\n }\n\n func testEnvironmentDeepSeekKeyOverridesSettings() {\n let (settings, path) = tempSettings()\n defer { try? FileManager.default.removeItem(atPath: path) }\n XCTAssertTrue(settings.setDeepSeekKey(\"sk-from-settings\"))\n\n let config = Config.load(settings: settings,\n environment: [\"DEEPSEEK_API_KEY\": \"sk-from-env\"],\n arguments: [])\n XCTAssertEqual(config.deepSeekKey, \"sk-from-env\")\n XCTAssertEqual(config.deepSeekKeySource, .environment)\n }\n\n func testMissingDeepSeekKeyFailsBeforeNetwork() async throws {\n let config = Config(\n deepSeekKey: \"\",\n deepSeekBaseURL: URL(string: \"https://example.invalid\")!,\n deepSeekModel: \"deepseek-v4-flash\",\n ollamaBaseURL: URL(string: \"http://127.0.0.1:11434\")!,\n ollamaVisionModel: \"gemma3:12b\",\n deepSeekKeySource: .missing)\n let client = DeepSeekClient(config: config)\n\n do {\n _ = try await client.complete([ChatMessage(role: .user, content: \"hello\")])\n XCTFail(\"Missing key should fail before making a network request\")\n } catch ClientError.missingDeepSeekKey {\n XCTAssertEqual(ClientError.missingDeepSeekKey.localizedDescription,\n \"DeepSeek API key is missing. Add it in Settings.\")\n }\n }\n\n func testOverridingWithEmptyKeepsExistingModel() {\n let base = Config.load()\n XCTAssertEqual(base.overriding(model: \"\").deepSeekModel, base.deepSeekModel)\n }\n\n func testSettingsModelDefaultAndPersist() {\n let s = SettingsStore(defaults: UserDefaults(suiteName: \"Model-\\(UUID().uuidString)\")!)\n XCTAssertEqual(s.model, \"deepseek-v4-flash\")\n s.model = \"deepseek-v4-pro\"\n XCTAssertEqual(s.model, \"deepseek-v4-pro\")\n XCTAssertTrue(Config.availableModels.contains(s.model))\n }\n\n /// Live: the reasoner model answers through the same client path.\n func testLiveReasonerModelAnswers() async throws {\n try XCTSkipUnless(TestSupport.liveDeepSeekEnabled,\n \"set MNEMO_LIVE_DEEPSEEK=1 to run this quota-spending live test\")\n let config = Config.load()\n try XCTSkipIf(config.deepSeekKey.isEmpty, \"no DeepSeek key\")\n let client = DeepSeekClient(config: config.overriding(model: \"deepseek-v4-pro\"))\n let reply: String\n do {\n reply = try await client.complete([ChatMessage(role: .user, content: \"Reply with exactly: REASONER_OK\")])\n } catch {\n throw XCTSkip(\"network/API unavailable: \\(error.localizedDescription)\")\n }\n XCTAssertTrue(reply.contains(\"REASONER_OK\"), \"got: \\(reply.prefix(120))\")\n }\n}\n"}, "19a0e6313a3504ba2c93c766485c10f268fccd26": {"t": 1785757652.9365444, "n": 4158, "h": "869b61054291b2cc", "c": "import XCTest\n@testable import Mnemosyne\n\nfinal class ModelPickerTests: XCTestCase {\n\n func testConfigOverridingModelPreservesOtherFields() {\n let base = Config.load()\n let overridden = base.overriding(model: \"deepseek-v4-pro\")\n XCTAssertEqual(overridden.deepSeekModel, \"deepseek-v4-pro\")\n XCTAssertEqual(overridden.deepSeekKey, base.deepSeekKey)\n XCTAssertEqual(overridden.deepSeekBaseURL, base.deepSeekBaseURL)\n XCTAssertEqual(overridden.ollamaVisionModel, base.ollamaVisionModel)\n }\n\n private func tempSettings() -> (SettingsStore, String) {\n let path = NSTemporaryDirectory() + \"secrets-\\(UUID().uuidString).json\"\n return (SettingsStore(defaults: UserDefaults(suiteName: \"Cfg-\\(UUID().uuidString)\")!,\n keychainService: \"com.mnemosyne.tests.\\(UUID().uuidString)\",\n secrets: SecretsFile(path: path)), path)\n }\n\n func testConfigLoadsDeepSeekKeyFromSettings() {\n let (settings, path) = tempSettings()\n defer { try? FileManager.default.removeItem(atPath: path) }\n XCTAssertTrue(settings.setDeepSeekKey(\"sk-from-settings\"))\n\n let config = Config.load(settings: settings, environment: [:], arguments: [])\n XCTAssertEqual(config.deepSeekKey, \"sk-from-settings\")\n XCTAssertEqual(config.deepSeekKeySource, .settings)\n }\n\n func testEnvironmentDeepSeekKeyOverridesSettings() {\n let (settings, path) = tempSettings()\n defer { try? FileManager.default.removeItem(atPath: path) }\n XCTAssertTrue(settings.setDeepSeekKey(\"sk-from-settings\"))\n\n let config = Config.load(settings: settings,\n environment: [\"DEEPSEEK_API_KEY\": \"sk-from-env\"],\n arguments: [])\n XCTAssertEqual(config.deepSeekKey, \"sk-from-env\")\n XCTAssertEqual(config.deepSeekKeySource, .environment)\n }\n\n func testMissingDeepSeekKeyFailsBeforeNetwork() async throws {\n let config = Config(\n deepSeekKey: \"\",\n deepSeekBaseURL: URL(string: \"https://example.invalid\")!,\n deepSeekModel: \"deepseek-v4-flash\",\n ollamaBaseURL: URL(string: \"http://127.0.0.1:11434\")!,\n ollamaVisionModel: \"gemma3:12b\",\n deepSeekKeySource: .missing)\n let client = DeepSeekClient(config: config)\n\n do {\n _ = try await client.complete([ChatMessage(role: .user, content: \"hello\")])\n XCTFail(\"Missing key should fail before making a network request\")\n } catch ClientError.missingDeepSeekKey {\n XCTAssertEqual(ClientError.missingDeepSeekKey.localizedDescription,\n \"DeepSeek API key is missing. Add it in Settings.\")\n }\n }\n\n func testOverridingWithEmptyKeepsExistingModel() {\n let base = Config.load()\n XCTAssertEqual(base.overriding(model: \"\").deepSeekModel, base.deepSeekModel)\n }\n\n func testSettingsModelDefaultAndPersist() {\n let s = SettingsStore(defaults: UserDefaults(suiteName: \"Model-\\(UUID().uuidString)\")!)\n XCTAssertEqual(s.model, \"deepseek-v4-flash\")\n s.model = \"deepseek-v4-pro\"\n XCTAssertEqual(s.model, \"deepseek-v4-pro\")\n XCTAssertTrue(Config.availableModels.contains(s.model))\n }\n\n /// Live: the reasoner model answers through the same client path.\n func testLiveReasonerModelAnswers() async throws {\n try XCTSkipUnless(TestSupport.liveDeepSeekEnabled,\n \"set MNEMO_LIVE_DEEPSEEK=1 to run this quota-spending live test\")\n let config = Config.load()\n try XCTSkipIf(config.deepSeekKey.isEmpty, \"no DeepSeek key\")\n let client = DeepSeekClient(config: config.overriding(model: \"deepseek-v4-pro\"))\n let reply: String\n do {\n reply = try await client.complete([ChatMessage(role: .user, content: \"Reply with exactly: REASONER_OK\")])\n } catch {\n throw XCTSkip(\"network/API unavailable: \\(error.localizedDescription)\")\n }\n XCTAssertTrue(reply.contains(\"REASONER_OK\"), \"got: \\(reply.prefix(120))\")\n }\n}\n"}, "04ac94d0ebbad7ffc17577fce4e354cf5f33ce2e": {"t": 1785757654.1932504, "n": 4160, "h": "6f1e2b21c67f002e", "c": "import XCTest\n@testable import Mnemosyne\n\nfinal class ModelPickerTests: XCTestCase {\n\n func testConfigOverridingModelPreservesOtherFields() {\n let base = Config.load()\n let overridden = base.overriding(model: \"deepseek-reasoner\")\n XCTAssertEqual(overridden.deepSeekModel, \"deepseek-reasoner\")\n XCTAssertEqual(overridden.deepSeekKey, base.deepSeekKey)\n XCTAssertEqual(overridden.deepSeekBaseURL, base.deepSeekBaseURL)\n XCTAssertEqual(overridden.ollamaVisionModel, base.ollamaVisionModel)\n }\n\n private func tempSettings() -> (SettingsStore, String) {\n let path = NSTemporaryDirectory() + \"secrets-\\(UUID().uuidString).json\"\n return (SettingsStore(defaults: UserDefaults(suiteName: \"Cfg-\\(UUID().uuidString)\")!,\n keychainService: \"com.mnemosyne.tests.\\(UUID().uuidString)\",\n secrets: SecretsFile(path: path)), path)\n }\n\n func testConfigLoadsDeepSeekKeyFromSettings() {\n let (settings, path) = tempSettings()\n defer { try? FileManager.default.removeItem(atPath: path) }\n XCTAssertTrue(settings.setDeepSeekKey(\"sk-from-settings\"))\n\n let config = Config.load(settings: settings, environment: [:], arguments: [])\n XCTAssertEqual(config.deepSeekKey, \"sk-from-settings\")\n XCTAssertEqual(config.deepSeekKeySource, .settings)\n }\n\n func testEnvironmentDeepSeekKeyOverridesSettings() {\n let (settings, path) = tempSettings()\n defer { try? FileManager.default.removeItem(atPath: path) }\n XCTAssertTrue(settings.setDeepSeekKey(\"sk-from-settings\"))\n\n let config = Config.load(settings: settings,\n environment: [\"DEEPSEEK_API_KEY\": \"sk-from-env\"],\n arguments: [])\n XCTAssertEqual(config.deepSeekKey, \"sk-from-env\")\n XCTAssertEqual(config.deepSeekKeySource, .environment)\n }\n\n func testMissingDeepSeekKeyFailsBeforeNetwork() async throws {\n let config = Config(\n deepSeekKey: \"\",\n deepSeekBaseURL: URL(string: \"https://example.invalid\")!,\n deepSeekModel: \"deepseek-chat\",\n ollamaBaseURL: URL(string: \"http://127.0.0.1:11434\")!,\n ollamaVisionModel: \"gemma3:12b\",\n deepSeekKeySource: .missing)\n let client = DeepSeekClient(config: config)\n\n do {\n _ = try await client.complete([ChatMessage(role: .user, content: \"hello\")])\n XCTFail(\"Missing key should fail before making a network request\")\n } catch ClientError.missingDeepSeekKey {\n XCTAssertEqual(ClientError.missingDeepSeekKey.localizedDescription,\n \"DeepSeek API key is missing. Add it in Settings.\")\n }\n }\n\n func testOverridingWithEmptyKeepsExistingModel() {\n let base = Config.load()\n XCTAssertEqual(base.overriding(model: \"\").deepSeekModel, base.deepSeekModel)\n }\n\n func testSettingsModelDefaultAndPersist() {\n let s = SettingsStore(defaults: UserDefaults(suiteName: \"Model-\\(UUID().uuidString)\")!)\n XCTAssertEqual(s.model, \"deepseek-chat\")\n s.model = \"deepseek-reasoner\"\n XCTAssertEqual(s.model, \"deepseek-reasoner\")\n XCTAssertTrue(Config.availableModels.contains(s.model))\n }\n\n /// Live: the reasoner model answers through the same client path.\n func testLiveReasonerModelAnswers() async throws {\n try XCTSkipUnless(TestSupport.liveDeepSeekEnabled,\n \"set MNEMO_LIVE_DEEPSEEK=1 to run this quota-spending live test\")\n let config = Config.load()\n try XCTSkipIf(config.deepSeekKey.isEmpty, \"no DeepSeek key\")\n let client = DeepSeekClient(config: config.overriding(model: \"deepseek-reasoner\"))\n let reply: String\n do {\n reply = try await client.complete([ChatMessage(role: .user, content: \"Reply with exactly: REASONER_OK\")])\n } catch {\n throw XCTSkip(\"network/API unavailable: \\(error.localizedDescription)\")\n }\n XCTAssertTrue(reply.contains(\"REASONER_OK\"), \"got: \\(reply.prefix(120))\")\n }\n}\n"}, "a54dfcd38c681823a19855a3ee1339ae3ed390aa": {"t": 1785757655.4826934, "n": 4168, "h": "cda31c59a6fb7dbb", "c": "import XCTest\n@testable import Mnemosyne\n\nfinal class ModelPickerTests: XCTestCase {\n\n func testConfigOverridingModelPreservesOtherFields() {\n let base = Config.load()\n let overridden = base.overriding(model: \"deepseek-reasoner\")\n XCTAssertEqual(overridden.deepSeekModel, \"deepseek-reasoner\")\n XCTAssertEqual(overridden.deepSeekKey, base.deepSeekKey)\n XCTAssertEqual(overridden.deepSeekBaseURL, base.deepSeekBaseURL)\n XCTAssertEqual(overridden.ollamaVisionModel, base.ollamaVisionModel)\n }\n\n func testConfigLoadsDeepSeekKeyFromSettings() {\n let service = \"com.mnemosyne.configtests.\\(UUID().uuidString)\"\n defer { KeychainStore.delete(service: service, account: \"deepseek.apiKey\") }\n let settings = SettingsStore(defaults: UserDefaults(suiteName: \"Cfg-\\(UUID().uuidString)\")!,\n keychainService: service)\n XCTAssertTrue(settings.setDeepSeekKey(\"sk-from-settings\"))\n\n let config = Config.load(settings: settings, environment: [:], arguments: [])\n XCTAssertEqual(config.deepSeekKey, \"sk-from-settings\")\n XCTAssertEqual(config.deepSeekKeySource, .settings)\n }\n\n func testEnvironmentDeepSeekKeyOverridesSettings() {\n let service = \"com.mnemosyne.configtests.\\(UUID().uuidString)\"\n defer { KeychainStore.delete(service: service, account: \"deepseek.apiKey\") }\n let settings = SettingsStore(defaults: UserDefaults(suiteName: \"Cfg-\\(UUID().uuidString)\")!,\n keychainService: service)\n XCTAssertTrue(settings.setDeepSeekKey(\"sk-from-settings\"))\n\n let config = Config.load(settings: settings,\n environment: [\"DEEPSEEK_API_KEY\": \"sk-from-env\"],\n arguments: [])\n XCTAssertEqual(config.deepSeekKey, \"sk-from-env\")\n XCTAssertEqual(config.deepSeekKeySource, .environment)\n }\n\n func testMissingDeepSeekKeyFailsBeforeNetwork() async throws {\n let config = Config(\n deepSeekKey: \"\",\n deepSeekBaseURL: URL(string: \"https://example.invalid\")!,\n deepSeekModel: \"deepseek-chat\",\n ollamaBaseURL: URL(string: \"http://127.0.0.1:11434\")!,\n ollamaVisionModel: \"gemma3:12b\",\n deepSeekKeySource: .missing)\n let client = DeepSeekClient(config: config)\n\n do {\n _ = try await client.complete([ChatMessage(role: .user, content: \"hello\")])\n XCTFail(\"Missing key should fail before making a network request\")\n } catch ClientError.missingDeepSeekKey {\n XCTAssertEqual(ClientError.missingDeepSeekKey.localizedDescription,\n \"DeepSeek API key is missing. Add it in Settings.\")\n }\n }\n\n func testOverridingWithEmptyKeepsExistingModel() {\n let base = Config.load()\n XCTAssertEqual(base.overriding(model: \"\").deepSeekModel, base.deepSeekModel)\n }\n\n func testSettingsModelDefaultAndPersist() {\n let s = SettingsStore(defaults: UserDefaults(suiteName: \"Model-\\(UUID().uuidString)\")!)\n XCTAssertEqual(s.model, \"deepseek-chat\")\n s.model = \"deepseek-reasoner\"\n XCTAssertEqual(s.model, \"deepseek-reasoner\")\n XCTAssertTrue(Config.availableModels.contains(s.model))\n }\n\n /// Live: the reasoner model answers through the same client path.\n func testLiveReasonerModelAnswers() async throws {\n try XCTSkipUnless(TestSupport.liveDeepSeekEnabled,\n \"set MNEMO_LIVE_DEEPSEEK=1 to run this quota-spending live test\")\n let config = Config.load()\n try XCTSkipIf(config.deepSeekKey.isEmpty, \"no DeepSeek key\")\n let client = DeepSeekClient(config: config.overriding(model: \"deepseek-reasoner\"))\n let reply: String\n do {\n reply = try await client.complete([ChatMessage(role: .user, content: \"Reply with exactly: REASONER_OK\")])\n } catch {\n throw XCTSkip(\"network/API unavailable: \\(error.localizedDescription)\")\n }\n XCTAssertTrue(reply.contains(\"REASONER_OK\"), \"got: \\(reply.prefix(120))\")\n }\n}\n"}}}}