@@ -2,13 +2,25 @@ import XCTest
22@testable import ParseableViewer
33
44final 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")
10+ if saved {
11+ KeychainService.deleteData(for: "_test_probe")
12+ }
13+ return saved
14+ }()
15+
516 override func tearDown() {
617 super.tearDown()
718 KeychainService.deleteData(for: "parseable_connections_list")
819 KeychainService.deleteData(for: "parseable_active_connection_id")
920 }
1021
11− func testSaveNilActiveConnectionID() {
22+ func testSaveNilActiveConnectionID() throws {
23+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
1224 let id = UUID()
1325 ConnectionStore.saveActiveConnectionID(id)
1426 ConnectionStore.saveActiveConnectionID(nil)
@@ -30,7 +42,69 @@ final class ConnectionStoreTests: XCTestCase {
3042 XCTAssertTrue(connections.isEmpty)
3143 }
3244
33− func testPasswordNotInKeychainConnectionData() {
45+ func testSaveAndLoadActiveConnectionID() throws {
46+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
47+ let id = UUID()
48+ ConnectionStore.saveActiveConnectionID(id)
49+ let loaded = ConnectionStore.loadActiveConnectionID()
50+ XCTAssertEqual(loaded, id)
51+ }
52+
53+ func testRoundTripConnectionPersistence() throws {
54+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
55+ let conn = ServerConnection(
56+ name: "Prod",
57+ url: "https://logs.example.com",
58+ username: "admin",
59+ password: "s3cret!"
60+ )
61+ ConnectionStore.saveConnections([conn])
62+
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!")
70+
71+ // Clean up
72+ KeychainService.deletePassword(for: conn.id)
73+ }
74+
75+ func testRoundTripMultipleConnections() throws {
76+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
77+ let conn1 = ServerConnection(
78+ name: "Server A",
79+ url: "https://a.example.com",
80+ username: "user1",
81+ password: "pass1"
82+ )
83+ let conn2 = ServerConnection(
84+ name: "Server B",
85+ url: "https://b.example.com",
86+ username: "user2",
87+ password: "pass2"
88+ )
89+ ConnectionStore.saveConnections([conn1, conn2])
90+
91+ let loaded = ConnectionStore.loadConnections()
92+ XCTAssertEqual(loaded.count, 2)
93+
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")
100+
101+ // Clean up
102+ KeychainService.deletePassword(for: conn1.id)
103+ KeychainService.deletePassword(for: conn2.id)
104+ }
105+
106+ func testPasswordNotInKeychainConnectionData() throws {
107+ try XCTSkipUnless(Self.keychainAvailable, "Data-protection keychain unavailable")
34108 let connection = ServerConnection(
35109 name: "Test",
36110 url: "https://example.com",