@@ -2,13 +2,25 @@ import XCTest
2@testable import ParseableViewer
2@testable import ParseableViewer
4final class ConnectionStoreTests: XCTestCase {
4final class ConnectionStoreTests: XCTestCase {
5+ /// Whether the data-protection keychain is available in this environment.
6+ /// CI runners may lack the entitlements needed for `kSecUseDataProtectionKeychain`.
7+ private static let keychainAvailable: Bool = {
8+ let probe = "keychain-probe"
9+ let saved = KeychainService.saveData(Data(probe.utf8), for: "_test_probe")
11+ KeychainService.deleteData(for: "_test_probe")
5 override func tearDown() {
16 override func tearDown() {
6 super.tearDown()
17 super.tearDown()
7 KeychainService.deleteData(for: "parseable_connections_list")
18 KeychainService.deleteData(for: "parseable_connections_list")
8 KeychainService.deleteData(for: "parseable_active_connection_id")
19 KeychainService.deleteData(for: "parseable_active_connection_id")
11− func testSaveNilActiveConnectionID() {
22+ func testSaveNilActiveConnectionID() throws {
23+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
12 let id = UUID()
24 let id = UUID()
13 ConnectionStore.saveActiveConnectionID(id)
25 ConnectionStore.saveActiveConnectionID(id)
14 ConnectionStore.saveActiveConnectionID(nil)
26 ConnectionStore.saveActiveConnectionID(nil)
@@ -30,7 +42,69 @@ final class ConnectionStoreTests: XCTestCase {
30 XCTAssertTrue(connections.isEmpty)
42 XCTAssertTrue(connections.isEmpty)
45+ func testSaveAndLoadActiveConnectionID() throws {
46+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
48+ ConnectionStore.saveActiveConnectionID(id)
49+ let loaded = ConnectionStore.loadActiveConnectionID()
50+ XCTAssertEqual(loaded, id)
53+ func testRoundTripConnectionPersistence() throws {
54+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
55+ let conn = ServerConnection(
57+ url: "https://logs.example.com",
61+ ConnectionStore.saveConnections([conn])
63+ let loaded = ConnectionStore.loadConnections()
64+ XCTAssertEqual(loaded.count, 1)
65+ XCTAssertEqual(loaded.first?.id, conn.id)
66+ XCTAssertEqual(loaded.first?.name, "Prod")
67+ XCTAssertEqual(loaded.first?.url, "https://logs.example.com")
68+ XCTAssertEqual(loaded.first?.username, "admin")
69+ XCTAssertEqual(loaded.first?.password, "s3cret!")
72+ KeychainService.deletePassword(for: conn.id)
75+ func testRoundTripMultipleConnections() throws {
76+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
77+ let conn1 = ServerConnection(
79+ url: "https://a.example.com",
83+ let conn2 = ServerConnection(
85+ url: "https://b.example.com",
89+ ConnectionStore.saveConnections([conn1, conn2])
91+ let loaded = ConnectionStore.loadConnections()
92+ XCTAssertEqual(loaded.count, 2)
94+ let loadedA = loaded.first { $0.id == conn1.id }
95+ let loadedB = loaded.first { $0.id == conn2.id }
96+ XCTAssertEqual(loadedA?.name, "Server A")
97+ XCTAssertEqual(loadedA?.password, "pass1")
98+ XCTAssertEqual(loadedB?.name, "Server B")
99+ XCTAssertEqual(loadedB?.password, "pass2")
102+ KeychainService.deletePassword(for: conn1.id)
103+ KeychainService.deletePassword(for: conn2.id)
33− func testPasswordNotInKeychainConnectionData() {
106+ func testPasswordNotInKeychainConnectionData() throws {
107+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
34 let connection = ServerConnection(
108 let connection = ServerConnection(
35 name: "Test",
109 name: "Test",
36 url: "https://example.com",
110 url: "https://example.com",