test: add unit tests for RPC framing, SDK state machine, and server domain services

Add comprehensive tests across three layers:
- RPC framing: empty payloads, max boundary, truncated frames, multi-frame buffers,
  all status codes, all method ID ranges, payload-too-large for response/push
- SDK: event broadcast send/receive, multiple subscribers, clone preservation,
  conversation upsert, missing conversation, message ID roundtrip, member keys
- Server domain: auth session validation/expiry, channel creation/symmetry/validation,
  delivery peek/ack/sequence ordering/fetch-limited, key package upload/fetch/validation,
  hybrid key batch fetch, size boundary tests
- CI: MSRV (1.75) check job, macOS cross-platform build check
This commit is contained in:
2026-03-08 18:07:43 +01:00
parent e4c5868b31
commit 872695e5f1
8 changed files with 1114 additions and 0 deletions

View File

@@ -95,3 +95,241 @@ impl KeyService {
Ok(FetchHybridKeysResp { keys })
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::storage::FileBackedStore;
fn test_service() -> (tempfile::TempDir, KeyService) {
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(FileBackedStore::open(dir.path()).unwrap());
let svc = KeyService { store };
(dir, svc)
}
fn test_auth() -> CallerAuth {
CallerAuth {
identity_key: vec![1u8; 32],
token: vec![0u8; 16],
device_id: None,
}
}
#[test]
fn upload_and_fetch_key_package() {
let (_dir, svc) = test_service();
let auth = test_auth();
let ik = vec![1u8; 32];
let package = vec![42u8; 128];
let resp = svc
.upload_key_package(
UploadKeyPackageReq {
identity_key: ik.clone(),
package: package.clone(),
},
&auth,
)
.unwrap();
// Fingerprint is SHA-256 of the package
assert_eq!(resp.fingerprint.len(), 32);
let fetched = svc
.fetch_key_package(FetchKeyPackageReq { identity_key: ik }, &auth)
.unwrap();
assert_eq!(fetched.package, package);
}
#[test]
fn fetch_key_package_missing() {
let (_dir, svc) = test_service();
let auth = test_auth();
let resp = svc
.fetch_key_package(
FetchKeyPackageReq { identity_key: vec![99u8; 32] },
&auth,
)
.unwrap();
assert!(resp.package.is_empty());
}
#[test]
fn upload_key_package_rejects_invalid_identity_key() {
let (_dir, svc) = test_service();
let auth = test_auth();
let err = svc
.upload_key_package(
UploadKeyPackageReq {
identity_key: vec![1u8; 31],
package: vec![1u8; 10],
},
&auth,
)
.unwrap_err();
assert!(matches!(err, DomainError::InvalidIdentityKey(31)));
}
#[test]
fn upload_key_package_rejects_empty_package() {
let (_dir, svc) = test_service();
let auth = test_auth();
let err = svc
.upload_key_package(
UploadKeyPackageReq {
identity_key: vec![1u8; 32],
package: vec![],
},
&auth,
)
.unwrap_err();
assert!(matches!(err, DomainError::EmptyPackage));
}
#[test]
fn upload_key_package_rejects_oversized() {
let (_dir, svc) = test_service();
let auth = test_auth();
let err = svc
.upload_key_package(
UploadKeyPackageReq {
identity_key: vec![1u8; 32],
package: vec![0u8; MAX_KEYPACKAGE_BYTES + 1],
},
&auth,
)
.unwrap_err();
assert!(matches!(err, DomainError::PackageTooLarge(_)));
}
#[test]
fn upload_and_fetch_hybrid_key() {
let (_dir, svc) = test_service();
let auth = test_auth();
let ik = vec![2u8; 32];
let hk = vec![0xABu8; 1184]; // ML-KEM-768 public key size
svc.upload_hybrid_key(
UploadHybridKeyReq {
identity_key: ik.clone(),
hybrid_public_key: hk.clone(),
},
&auth,
)
.unwrap();
let resp = svc
.fetch_hybrid_key(FetchHybridKeyReq { identity_key: ik }, &auth)
.unwrap();
assert_eq!(resp.hybrid_public_key, hk);
}
#[test]
fn fetch_hybrid_key_missing() {
let (_dir, svc) = test_service();
let auth = test_auth();
let resp = svc
.fetch_hybrid_key(
FetchHybridKeyReq { identity_key: vec![99u8; 32] },
&auth,
)
.unwrap();
assert!(resp.hybrid_public_key.is_empty());
}
#[test]
fn upload_hybrid_key_rejects_invalid_identity() {
let (_dir, svc) = test_service();
let auth = test_auth();
let err = svc
.upload_hybrid_key(
UploadHybridKeyReq {
identity_key: vec![1u8; 10],
hybrid_public_key: vec![1u8; 100],
},
&auth,
)
.unwrap_err();
assert!(matches!(err, DomainError::InvalidIdentityKey(10)));
}
#[test]
fn upload_hybrid_key_rejects_empty() {
let (_dir, svc) = test_service();
let auth = test_auth();
let err = svc
.upload_hybrid_key(
UploadHybridKeyReq {
identity_key: vec![1u8; 32],
hybrid_public_key: vec![],
},
&auth,
)
.unwrap_err();
assert!(matches!(err, DomainError::EmptyHybridKey));
}
#[test]
fn fetch_hybrid_keys_batch() {
let (_dir, svc) = test_service();
let auth = test_auth();
let ik1 = vec![1u8; 32];
let ik2 = vec![2u8; 32];
let ik3 = vec![3u8; 32]; // no hybrid key uploaded
svc.upload_hybrid_key(
UploadHybridKeyReq {
identity_key: ik1.clone(),
hybrid_public_key: vec![0xAAu8; 64],
},
&auth,
)
.unwrap();
svc.upload_hybrid_key(
UploadHybridKeyReq {
identity_key: ik2.clone(),
hybrid_public_key: vec![0xBBu8; 64],
},
&auth,
)
.unwrap();
let resp = svc
.fetch_hybrid_keys(
FetchHybridKeysReq {
identity_keys: vec![ik1, ik2, ik3],
},
&auth,
)
.unwrap();
assert_eq!(resp.keys.len(), 3);
assert_eq!(resp.keys[0], vec![0xAAu8; 64]);
assert_eq!(resp.keys[1], vec![0xBBu8; 64]);
assert!(resp.keys[2].is_empty()); // missing key returns empty
}
#[test]
fn upload_key_package_at_max_size() {
let (_dir, svc) = test_service();
let auth = test_auth();
// Exactly at max should succeed
let resp = svc
.upload_key_package(
UploadKeyPackageReq {
identity_key: vec![1u8; 32],
package: vec![0u8; MAX_KEYPACKAGE_BYTES],
},
&auth,
);
assert!(resp.is_ok());
}
}