learning_ai_common_plat/packages/swift-platform-sdk/Tests/BLFieldEncryptTests.swift
saravanakumardb1 ee762b4612 feat(native-sdks): add BLFieldEncrypt to Swift + Kotlin platform SDKs
- Swift: BLFieldEncrypt.swift + BLFieldEncryptTests.swift (22 tests)
  - CryptoKit AES-256-GCM, BLEncryptedField Codable struct
  - encrypt/decrypt, AAD support, generateKey, keyFromHex, isEncrypted
  - Data hex helpers (hexString, init?(hexString:))

- Kotlin: BLFieldEncrypt.kt + BLFieldEncryptTest.kt (21 tests)
  - javax.crypto AES-256-GCM, BLEncryptedField data class
  - encrypt/decrypt, AAD support, generateKey, keyFromHex, isEncrypted
  - ByteArray/String hex extension functions

- Wire-compatible: same EncryptedField JSON structure as @bytelyst/field-encrypt (TS)
  - { __encrypted: true, v: 1, alg: 'aes-256-gcm', ct, iv, tag, dekId }
  - All hex-encoded, 12-byte IV, 16-byte auth tag

- Fix: ByteLystPlatform.kt getString() → read() (pre-existing compile error)
2026-03-21 10:58:02 -07:00

187 lines
6.7 KiB
Swift

import XCTest
import CryptoKit
@testable import ByteLystPlatformSDK
final class BLFieldEncryptTests: XCTestCase {
private var key: SymmetricKey!
private let dekId = "dek_user1_test"
override func setUp() {
super.setUp()
key = BLFieldEncrypt.generateKey()
}
// MARK: - Encrypt / Decrypt Roundtrip
func testEncryptDecryptRoundtrip() throws {
let plaintext = "Hello, World!"
let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId)
let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
XCTAssertEqual(decrypted, plaintext)
}
func testEncryptDecryptEmptyString() throws {
let plaintext = ""
let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId)
let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
XCTAssertEqual(decrypted, plaintext)
}
func testEncryptDecryptUnicode() throws {
let plaintext = "こんにちは世界 🌍 مرحبا Ñoño"
let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId)
let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
XCTAssertEqual(decrypted, plaintext)
}
func testEncryptDecryptLargePayload() throws {
let plaintext = String(repeating: "A", count: 100_000)
let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId)
let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
XCTAssertEqual(decrypted, plaintext)
}
// MARK: - EncryptedField Structure
func testEncryptedFieldHasCorrectSentinel() throws {
let encrypted = try BLFieldEncrypt.encrypt("test", key: key, dekId: dekId)
XCTAssertTrue(encrypted.__encrypted)
XCTAssertEqual(encrypted.v, 1)
XCTAssertEqual(encrypted.alg, "aes-256-gcm")
XCTAssertEqual(encrypted.dekId, dekId)
}
func testEncryptedFieldHasCorrectHexLengths() throws {
let encrypted = try BLFieldEncrypt.encrypt("test", key: key, dekId: dekId)
// IV: 12 bytes = 24 hex chars
XCTAssertEqual(encrypted.iv.count, 24)
// Tag: 16 bytes = 32 hex chars
XCTAssertEqual(encrypted.tag.count, 32)
// ct should be non-empty hex
XCTAssertFalse(encrypted.ct.isEmpty)
}
func testEachEncryptionProducesUniqueIV() throws {
let a = try BLFieldEncrypt.encrypt("same", key: key, dekId: dekId)
let b = try BLFieldEncrypt.encrypt("same", key: key, dekId: dekId)
XCTAssertNotEqual(a.iv, b.iv, "Each encryption should use a unique IV")
XCTAssertNotEqual(a.ct, b.ct, "Ciphertext should differ with different IVs")
}
// MARK: - AAD (Additional Authenticated Data)
func testEncryptDecryptWithAAD() throws {
let plaintext = "secret data"
let aad = "user123:notes"
let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId, aad: aad)
let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key, aad: aad)
XCTAssertEqual(decrypted, plaintext)
}
func testDecryptWithWrongAADFails() throws {
let encrypted = try BLFieldEncrypt.encrypt("secret", key: key, dekId: dekId, aad: "correct")
XCTAssertThrowsError(try BLFieldEncrypt.decrypt(encrypted, key: key, aad: "wrong"))
}
func testDecryptWithMissingAADFails() throws {
let encrypted = try BLFieldEncrypt.encrypt("secret", key: key, dekId: dekId, aad: "some-aad")
XCTAssertThrowsError(try BLFieldEncrypt.decrypt(encrypted, key: key))
}
// MARK: - Wrong Key
func testDecryptWithWrongKeyFails() throws {
let encrypted = try BLFieldEncrypt.encrypt("secret", key: key, dekId: dekId)
let wrongKey = BLFieldEncrypt.generateKey()
XCTAssertThrowsError(try BLFieldEncrypt.decrypt(encrypted, key: wrongKey))
}
// MARK: - Key Size Validation
func testEncryptRejectsShortKey() {
let shortKey = SymmetricKey(size: .bits128)
XCTAssertThrowsError(try BLFieldEncrypt.encrypt("test", key: shortKey, dekId: dekId)) { error in
guard let encError = error as? BLFieldEncryptError,
case .invalidKeySize = encError else {
XCTFail("Expected invalidKeySize error")
return
}
}
}
// MARK: - Key from Hex
func testKeyFromHex() throws {
let hex = String(repeating: "ab", count: 32) // 64 hex chars = 32 bytes
let key = try BLFieldEncrypt.keyFromHex(hex)
let encrypted = try BLFieldEncrypt.encrypt("test", key: key, dekId: dekId)
let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
XCTAssertEqual(decrypted, "test")
}
func testKeyFromHexRejectsInvalidLength() {
XCTAssertThrowsError(try BLFieldEncrypt.keyFromHex("aabb"))
}
// MARK: - isEncrypted Type Guard
func testIsEncryptedWithValidField() throws {
let encrypted = try BLFieldEncrypt.encrypt("test", key: key, dekId: dekId)
XCTAssertTrue(BLFieldEncrypt.isEncrypted(encrypted))
}
func testIsEncryptedWithNil() {
XCTAssertFalse(BLFieldEncrypt.isEncrypted(nil as BLEncryptedField?))
}
func testIsEncryptedWithDictionary() {
let dict: [String: Any] = [
"__encrypted": true,
"v": 1,
"alg": "aes-256-gcm",
"ct": "abcd",
"iv": "1234",
"tag": "5678",
"dekId": "dek_test",
]
XCTAssertTrue(BLFieldEncrypt.isEncrypted(dict))
}
func testIsEncryptedWithPlainString() {
XCTAssertFalse(BLFieldEncrypt.isEncrypted("just a string" as Any))
}
func testIsEncryptedWithIncompleteDict() {
let dict: [String: Any] = ["__encrypted": true, "v": 1]
XCTAssertFalse(BLFieldEncrypt.isEncrypted(dict))
}
// MARK: - JSON Codable Roundtrip
func testEncryptedFieldCodableRoundtrip() throws {
let encrypted = try BLFieldEncrypt.encrypt("codable test", key: key, dekId: dekId)
let encoder = JSONEncoder()
let data = try encoder.encode(encrypted)
let decoder = JSONDecoder()
let decoded = try decoder.decode(BLEncryptedField.self, from: data)
let decrypted = try BLFieldEncrypt.decrypt(decoded, key: key)
XCTAssertEqual(decrypted, "codable test")
}
// MARK: - Hex Helpers
func testDataHexRoundtrip() {
let original = Data([0x00, 0x0f, 0xff, 0xab, 0xcd])
let hex = original.hexString
XCTAssertEqual(hex, "000fffabcd")
let restored = Data(hexString: hex)
XCTAssertEqual(restored, original)
}
func testDataHexInvalidString() {
XCTAssertNil(Data(hexString: "xyz"))
XCTAssertNil(Data(hexString: "a")) // odd length
}
}