Files
bitchat/bitchatTests/KeychainIntegrationTests.swift
T
jack b61904bee9 Remove message retention and /save command
- Delete MessageRetentionService.swift
- Remove retentionEnabledChannels from ChatViewModel
- Remove /save command handling
- Remove retention UI elements from ContentView
- Remove channelRetention message type from protocol
- Update documentation and tests
2025-07-16 15:59:38 +02:00

183 lines
6.9 KiB
Swift

//
// KeychainIntegrationTests.swift
// bitchatTests
//
// Integration tests for keychain functionality
//
import XCTest
@testable import bitchat
class KeychainIntegrationTests: XCTestCase {
override func setUp() {
super.setUp()
// Start with clean state
_ = KeychainManager.shared.deleteAllKeychainData()
}
override func tearDown() {
// Clean up test data
_ = KeychainManager.shared.deleteAllKeychainData()
super.tearDown()
}
// MARK: - App Lifecycle Simulation Tests
func testCompleteAppLifecycle() {
print("\n🧪 Testing Complete App Lifecycle")
// 1. First app launch - create identity
print("1️⃣ First launch...")
let service1 = NoiseEncryptionService()
let fingerprint1 = service1.getIdentityFingerprint()
print(" Initial fingerprint: \(fingerprint1)")
// Verify stored in keychain
let keychainData1 = KeychainManager.shared.getIdentityKey(forKey: "noiseStaticKey")
XCTAssertNotNil(keychainData1, "Identity should be in keychain after first launch")
// 2. App goes to background and comes back
print("2️⃣ Background/foreground cycle...")
let service2 = NoiseEncryptionService()
let fingerprint2 = service2.getIdentityFingerprint()
XCTAssertEqual(fingerprint1, fingerprint2, "Identity should persist through background")
// 3. App terminates and relaunches
print("3️⃣ Terminate and relaunch...")
// In real app this would be a new process
let service3 = NoiseEncryptionService()
let fingerprint3 = service3.getIdentityFingerprint()
XCTAssertEqual(fingerprint1, fingerprint3, "Identity should persist through termination")
// 4. User triggers panic mode
print("4️⃣ Panic mode triggered...")
service3.clearPersistentIdentity()
// 5. App creates new identity
print("5️⃣ New identity after panic...")
let service4 = NoiseEncryptionService()
let fingerprint4 = service4.getIdentityFingerprint()
XCTAssertNotEqual(fingerprint1, fingerprint4, "New identity should be created after panic")
print(" New fingerprint: \(fingerprint4)")
print("✅ Lifecycle test complete\n")
}
// MARK: - Channel Password Tests
func testChannelPasswordPersistence() {
let channel1 = "#testchannel1"
let channel2 = "#testchannel2"
let password1 = "password123"
let password2 = "differentpass456"
// Save passwords
XCTAssertTrue(KeychainManager.shared.saveChannelPassword(password1, for: channel1))
XCTAssertTrue(KeychainManager.shared.saveChannelPassword(password2, for: channel2))
// Retrieve passwords
XCTAssertEqual(KeychainManager.shared.getChannelPassword(for: channel1), password1)
XCTAssertEqual(KeychainManager.shared.getChannelPassword(for: channel2), password2)
// Test getAllChannelPasswords
let allPasswords = KeychainManager.shared.getAllChannelPasswords()
XCTAssertEqual(allPasswords.count, 2)
XCTAssertEqual(allPasswords[channel1], password1)
XCTAssertEqual(allPasswords[channel2], password2)
// Delete one password
XCTAssertTrue(KeychainManager.shared.deleteChannelPassword(for: channel1))
XCTAssertNil(KeychainManager.shared.getChannelPassword(for: channel1))
XCTAssertEqual(KeychainManager.shared.getChannelPassword(for: channel2), password2)
}
// MARK: - Security Tests
func testNoPlaintextInUserDefaults() {
// Create services to generate keys
_ = NoiseEncryptionService()
// Check UserDefaults for any sensitive data
let keysToCheck = [
"bitchat.noiseIdentityKey",
"bitchat.channelPasswords",
"bitchat.identityKey",
"bitchat.staticKey"
]
for key in keysToCheck {
let data = UserDefaults.standard.object(forKey: key)
XCTAssertNil(data, "UserDefaults should not contain: \(key)")
}
}
// MARK: - Error Handling Tests
func testKeychainErrorRecovery() {
// Test that the app can recover from keychain errors
// This is difficult to test without mocking, but we can verify
// that multiple save attempts don't crash
let testData = "test".data(using: .utf8)!
// Rapid saves
for i in 0..<10 {
let saved = KeychainManager.shared.saveIdentityKey(testData, forKey: "rapidTest\(i)")
XCTAssertTrue(saved, "Save \(i) should succeed")
}
// Rapid deletes
for i in 0..<10 {
_ = KeychainManager.shared.deleteIdentityKey(forKey: "rapidTest\(i)")
}
}
// MARK: - Cleanup Tests
func testAggressiveCleanupOnlyDeletesBitchatItems() {
// This test verifies we don't delete other apps' keychain items
// Add a non-bitchat item (simulating another app)
let otherAppQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.otherapp.service",
kSecAttrAccount as String: "other_app_account",
kSecValueData as String: "other app data".data(using: .utf8)!
]
// Clean first
SecItemDelete(otherAppQuery as CFDictionary)
// Add the item
let addStatus = SecItemAdd(otherAppQuery as CFDictionary, nil)
XCTAssertEqual(addStatus, errSecSuccess, "Should add other app item")
// Add a bitchat legacy item
let bitchatQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.bitchat.legacy",
kSecAttrAccount as String: "test_account",
kSecValueData as String: "bitchat data".data(using: .utf8)!
]
SecItemDelete(bitchatQuery as CFDictionary)
let bitchatStatus = SecItemAdd(bitchatQuery as CFDictionary, nil)
XCTAssertEqual(bitchatStatus, errSecSuccess, "Should add bitchat item")
// Run aggressive cleanup
_ = KeychainManager.shared.aggressiveCleanupLegacyItems()
// Verify other app item still exists
var result: AnyObject?
let checkStatus = SecItemCopyMatching(otherAppQuery as CFDictionary, &result)
XCTAssertEqual(checkStatus, errSecSuccess, "Other app item should still exist")
// Verify bitchat item was deleted
var bitchatResult: AnyObject?
let bitchatCheck = SecItemCopyMatching(bitchatQuery as CFDictionary, &bitchatResult)
XCTAssertEqual(bitchatCheck, errSecItemNotFound, "Bitchat legacy item should be deleted")
// Clean up
SecItemDelete(otherAppQuery as CFDictionary)
}
}