test .gpg-id support

mostly using mocks
This commit is contained in:
Lysann Tranvouez 2026-03-12 09:31:37 +01:00
parent 38649f96fe
commit 5c416bfb21
2 changed files with 105 additions and 18 deletions

View file

@ -27,6 +27,18 @@ final class PasswordStoreTest: XCTestCase {
passwordStore = PasswordStore(url: localRepoURL, pgpAgent: pgpAgent)
}
private func setUpMockedPGPInterface() -> MockPGPInterface {
let mockPGPInterface = MockPGPInterface()
keyStore = DictBasedKeychain()
pgpAgent = PGPAgent(keyStore: keyStore, pgpInterface: mockPGPInterface)
passwordStore = PasswordStore(url: localRepoURL, pgpAgent: pgpAgent)
// Set pgpKeyPassphrase key so checkAndInit() doesn't re-init and overwrite our mock.
keyStore.add(string: "dummy", for: Globals.pgpKeyPassphrase)
return mockPGPInterface
}
override func tearDown() {
passwordStore.erase()
passwordStore = nil
@ -322,31 +334,106 @@ final class PasswordStoreTest: XCTestCase {
// MARK: - .gpg-id support
func testCloneAndDecryptMultiKeys() throws {
func testReadGPGIDFile() throws {
try cloneRepository(.withGPGID)
try importMultiplePGPKeys()
Defaults.isEnableGPGIDOn = true
[
("work/github.com", ["4712286271220DB299883EA7062E678DA1024DAE"]),
("personal/github.com", ["787EAE1A5FA3E749AA34CC6AA0645EBED862027E"]),
("shared/github.com", ["4712286271220DB299883EA7062E678DA1024DAE", "787EAE1A5FA3E749AA34CC6AA0645EBED862027E"]),
].forEach { path, keyIDs in
("", [RSA4096.longFingerprint]),
("family", [String(NISTP384.longFingerprint.suffix(16))]),
("personal", [RSA4096.longFingerprint]),
("shared", [RSA2048.longFingerprint, RSA4096.longFingerprint]),
("work", [RSA2048.longFingerprint]),
].forEach { path, expectedKeyIDs in
let foundKeyIDs = findGPGIDs(from: localRepoURL.appendingPathComponent(path))
XCTAssertEqual(foundKeyIDs, keyIDs)
XCTAssertEqual(foundKeyIDs, expectedKeyIDs.map { $0.uppercased() })
}
}
let personal = try decrypt(path: "personal/github.com.gpg")
XCTAssertEqual(personal.plainText, "passwordforpersonal\n")
let work = try decrypt(path: "work/github.com.gpg")
XCTAssertEqual(work.plainText, "passwordforwork\n")
func testAddPasswordInRoot_WithSingleEntryInPGPIDFile_EncryptsWithThatKey() throws {
let mockPGPInterface = setUpMockedPGPInterface()
mockPGPInterface.publicKeyIDs = Set(RSA2048_RSA4096.fingerprints)
try cloneRepository(.withGPGID)
Defaults.isEnableGPGIDOn = true
let testPassword = Password(name: "test", path: "test.gpg", plainText: "testpassword")
let testPasswordEntity = try passwordStore.add(password: testPassword)!
let testPasswordPlain = try passwordStore.decrypt(passwordEntity: testPasswordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
XCTAssertEqual(testPasswordPlain.plainText, "testpassword")
_ = try passwordStore.add(password: testPassword)
XCTAssertEqual(mockPGPInterface.encryptMultiKeyCalls.count, 1)
let encryptCall = mockPGPInterface.encryptMultiKeyCalls.first
XCTAssertEqual(encryptCall?.plainData, testPassword.plainData)
XCTAssertEqual(encryptCall?.keyIDs, [RSA4096.longFingerprint].map { $0.uppercased() })
}
func testEncryptWithSingleKeyViaGPGIDFileInSubDirectory() throws {
let mockPGPInterface = setUpMockedPGPInterface()
mockPGPInterface.publicKeyIDs = Set(RSA2048_RSA4096.fingerprints)
try cloneRepository(.withGPGID)
Defaults.isEnableGPGIDOn = true
let testPassword = Password(name: "test", path: "family/test.gpg", plainText: "testpassword")
_ = try passwordStore.add(password: testPassword)
XCTAssertEqual(mockPGPInterface.encryptMultiKeyCalls.count, 1)
let encryptCall = mockPGPInterface.encryptMultiKeyCalls.first
XCTAssertEqual(encryptCall?.plainData, testPassword.plainData)
XCTAssertEqual(encryptCall?.keyIDs, [String(NISTP384.longFingerprint.suffix(16))].map { $0.uppercased() })
}
func testEncryptWithSingleKeyViaGPGIDFileInParentDir() throws {
let mockPGPInterface = setUpMockedPGPInterface()
mockPGPInterface.publicKeyIDs = Set(RSA2048_RSA4096.fingerprints)
try cloneRepository(.withGPGID)
Defaults.isEnableGPGIDOn = true
// /personal doesn't have its own .gpg-id file, but should inherit from the root .gpg-id file
let testPassword = Password(name: "test", path: "personal/test.gpg", plainText: "testpassword")
_ = try passwordStore.add(password: testPassword)
XCTAssertEqual(mockPGPInterface.encryptMultiKeyCalls.count, 1)
let encryptCall = mockPGPInterface.encryptMultiKeyCalls.first
XCTAssertEqual(encryptCall?.plainData, testPassword.plainData)
XCTAssertEqual(encryptCall?.keyIDs, [RSA4096.longFingerprint].map { $0.uppercased() })
}
func testEncryptWithMultipleKeysViaGPGIDFile() throws {
let mockPGPInterface = setUpMockedPGPInterface()
mockPGPInterface.publicKeyIDs = Set(RSA2048_RSA4096.fingerprints)
try cloneRepository(.withGPGID)
Defaults.isEnableGPGIDOn = true
// /shared uses both RSA2048 and RSA4096
let testPassword = Password(name: "test", path: "shared/test.gpg", plainText: "testpassword")
_ = try passwordStore.add(password: testPassword)
XCTAssertEqual(mockPGPInterface.encryptMultiKeyCalls.count, 1)
let encryptCall = mockPGPInterface.encryptMultiKeyCalls.first
XCTAssertEqual(encryptCall?.plainData, testPassword.plainData)
XCTAssertEqual(encryptCall?.keyIDs, RSA2048_RSA4096.longFingerprints.map { $0.uppercased() })
}
func testEncryptWithSingleKeyViaGPGFile_MissingKey() throws {
try cloneRepository(.withGPGID)
try importSinglePGPKey() // Only import RSA4096, but not RSA2048
Defaults.isEnableGPGIDOn = true
// /work uses RSA2048, but we didn't import that one
let testPassword = Password(name: "test", path: "work/test.gpg", plainText: "testpassword")
XCTAssertThrowsError(try passwordStore.add(password: testPassword)) {
XCTAssertEqual($0 as? AppError, .pgpPublicKeyNotFound(keyID: RSA2048.longFingerprint.uppercased()))
}
}
func testEncryptWithMultipleKeysViaGPGFile_MissingKey() throws {
try cloneRepository(.withGPGID)
try importSinglePGPKey() // Only import RSA4096, but not RSA2048
Defaults.isEnableGPGIDOn = true
// /shared uses both RSA2048 and RSA4096, but we only imported RSA4096, so encryption should fail since one of the keys is missing
let testPassword = Password(name: "test", path: "shared/test.gpg", plainText: "testpassword")
XCTAssertThrowsError(try passwordStore.add(password: testPassword)) {
XCTAssertEqual($0 as? AppError, .pgpPublicKeyNotFound(keyID: RSA2048.longFingerprint.uppercased()))
}
}
// MARK: - Helpers