field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

commit fce2a6d110daa98b7a3e5501f3005c71530ee82c
parent df50d6f95d224873175298e07ebfb93e89b23c58
Author: triesap <tyson@radroots.org>
Date:   Thu, 13 Aug 2026 18:20:57 +0000

test: close mobile core coverage gate

- gate feature-only imports at their exact configurations
- remove an invalid coverage attribute from a data type
- exercise media and Today fail-closed branch inventories
- verify default and all-feature tests, Clippy, and 90.44% branches

Diffstat:
Mcore/crates/tera_core/src/runtime/builder.rs | 1+
Mcore/crates/tera_core/src/runtime/product_surface/media.rs | 598+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/crates/tera_core/src/runtime/product_surface/projection.rs | 42++++++++++++++++++++++++++++++++++++++++++
Mcore/crates/tera_core/src/runtime/product_surface/today.rs | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/crates/tera_core/src/runtime/sdk.rs | 1+
5 files changed, 725 insertions(+), 0 deletions(-)

diff --git a/core/crates/tera_core/src/runtime/builder.rs b/core/crates/tera_core/src/runtime/builder.rs @@ -85,6 +85,7 @@ impl RuntimeBuilder { #[cfg(test)] mod tests { use super::RuntimeBuilder; + #[cfg(feature = "mobile-social")] use crate::runtime::sdk::SdkRelayAccessRecord; use crate::runtime::store::{MobileUserStoreConfig, ProtectedDataAvailability}; diff --git a/core/crates/tera_core/src/runtime/product_surface/media.rs b/core/crates/tera_core/src/runtime/product_surface/media.rs @@ -1304,6 +1304,9 @@ fn update_optional_u64(digest: &mut Sha256Hasher, value: Option<u64>) { mod tests { use super::*; + type ReceiptMutation = Box<dyn Fn(&mut Phase1VerifiedMediaReceipt)>; + type CacheMutation = Box<dyn Fn(&mut Phase1MediaCacheIndex)>; + const HASH: &str = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"; fn reference(alt: Option<&str>) -> Phase1StructuralMediaReference { @@ -1384,6 +1387,207 @@ mod tests { } #[test] + fn structural_reference_rejects_each_noncanonical_field_independently() { + for source_url in [ + "", + "ftp://media.example/file.jpg", + "https:///file.jpg", + "https://user@media.example/file.jpg", + "https://user:password@media.example/file.jpg", + "https://media.example/file.jpg\n", + ] { + assert!( + Phase1StructuralMediaReference::new( + source_url, + None, + None, + None, + None, + None, + None, + ) + .is_err(), + "source URL should fail: {source_url:?}" + ); + } + assert!( + Phase1StructuralMediaReference::new( + format!("https://media.example/{}", "x".repeat(MEDIA_URL_MAX_BYTES)), + None, + None, + None, + None, + None, + None, + ) + .is_err() + ); + for digest in ["not-hex".to_owned(), HASH.to_ascii_uppercase()] { + assert!( + Phase1StructuralMediaReference::new( + "https://media.example/file.jpg", + Some(digest), + None, + None, + None, + None, + None, + ) + .is_err() + ); + } + assert!( + Phase1StructuralMediaReference::new( + "https://media.example/file.jpg", + Some(HASH.to_owned()), + Some("not a media type".to_owned()), + None, + None, + None, + None, + ) + .is_err() + ); + for (width, height) in [ + (Some(1), None), + (None, Some(1)), + (Some(0), Some(1)), + (Some(1), Some(0)), + (Some(MEDIA_DIMENSION_MAX_EDGE + 1), Some(1)), + (Some(1), Some(MEDIA_DIMENSION_MAX_EDGE + 1)), + (Some(10_001), Some(10_000)), + ] { + assert!( + Phase1StructuralMediaReference::new( + "https://media.example/file.jpg", + Some(HASH.to_owned()), + Some("image/jpeg".to_owned()), + width, + height, + Some(5), + None, + ) + .is_err() + ); + } + for alt in [ + "x".repeat(MEDIA_ALT_MAX_BYTES + 1), + "line\nbreak".to_owned(), + ] { + assert!( + Phase1StructuralMediaReference::new( + "https://media.example/file.jpg", + Some(HASH.to_owned()), + Some("image/jpeg".to_owned()), + None, + None, + Some(5), + Some(alt), + ) + .is_err() + ); + } + assert!( + Phase1StructuralMediaReference::new( + "https://media.example/file.jpg", + Some(HASH.to_owned()), + Some("image/jpeg".to_owned()), + None, + None, + Some(0), + None, + ) + .is_err() + ); + + let mut unsupported = reference(None); + unsupported.schema_version = 2; + assert_eq!( + unsupported.validate(), + Err(Phase1InboundMediaError::UnsupportedSchema) + ); + let mut corrupt = reference(None); + corrupt.fingerprint = [9; 32]; + assert_eq!( + corrupt.validate(), + Err(Phase1InboundMediaError::CorruptState) + ); + } + + #[test] + fn operation_and_failure_evidence_reject_each_invalid_field() { + assert!(Phase1MediaConfigurationFingerprint::new([0; 32]).is_err()); + assert!(Phase1MediaConfigurationFingerprint::parse("not-hex").is_err()); + assert!(Phase1MediaConfigurationFingerprint::parse("00").is_err()); + assert!(Phase1MediaArtifactId::parse("not-hex").is_err()); + + assert!(Phase1InboundMediaPending::new([0; 16], configuration(1), 1).is_err()); + assert!(Phase1InboundMediaPending::new([1; 16], configuration(1), 0).is_err()); + assert!( + Phase1InboundMediaPending::new( + [1; 16], + Phase1MediaConfigurationFingerprint([0; 32]), + 1, + ) + .is_err() + ); + + for (operation_id, code, failed_at) in [ + ([0; 16], "failed".to_owned(), 1), + ([1; 16], "failed".to_owned(), 0), + ([1; 16], String::new(), 1), + ([1; 16], "x".repeat(MEDIA_FAILURE_CODE_MAX_BYTES + 1), 1), + ([1; 16], "Not_Safe".to_owned(), 1), + ] { + assert!(Phase1InboundMediaFailure::new(operation_id, code, true, failed_at).is_err()); + } + let evidence = Phase1InboundMediaFailure::new([2; 16], "retry_2", true, 9).unwrap(); + assert_eq!(evidence.safe_code(), "retry_2"); + assert!(evidence.retryable()); + } + + #[test] + fn media_helper_vocabularies_cover_every_closed_outcome() { + assert!(validate_url_text("https://example.test/media").is_ok()); + assert_eq!( + validate_url_text(""), + Err(Phase1InboundMediaError::InvalidReference) + ); + assert_eq!( + validate_url_text(&"x".repeat(MEDIA_URL_MAX_BYTES + 1)), + Err(Phase1InboundMediaError::InvalidReference) + ); + assert_eq!( + validate_url_text("bad\nurl"), + Err(Phase1InboundMediaError::InvalidReference) + ); + assert!(canonical_media_type("image/jpeg")); + assert!(!canonical_media_type("IMAGE/JPEG")); + assert_eq!( + canonical_extension(&MediaType::parse("image/gif").unwrap()), + Some("gif") + ); + assert_eq!( + canonical_extension(&MediaType::parse("image/jpeg").unwrap()), + Some("jpg") + ); + assert_eq!( + canonical_extension(&MediaType::parse("image/png").unwrap()), + Some("png") + ); + assert_eq!( + canonical_extension(&MediaType::parse("image/webp").unwrap()), + Some("webp") + ); + assert_eq!( + canonical_extension(&MediaType::parse("image/svg+xml").unwrap()), + None + ); + assert!(validate_dimensions(None, None).is_ok()); + assert!(validate_dimensions(Some(10_000), Some(10_000)).is_ok()); + } + + #[test] fn verified_state_requires_matching_operation_bytes_and_configuration() { let structural = reference(None); let mut media = MediaReference::new(structural.clone()).unwrap(); @@ -1400,6 +1604,86 @@ mod tests { media.retrieval(), Phase1InboundMediaState::Verified(_) )); + media + .restore( + Phase1InboundMediaState::Unavailable, + &Phase1MediaCacheIndex::default(), + ) + .unwrap(); + assert!(matches!( + media.retrieval(), + Phase1InboundMediaState::Unavailable + )); + } + + #[test] + fn media_state_transitions_and_renderability_cover_every_state() { + let structural = reference(None); + let config = configuration(3); + let verified = receipt(&structural, config); + let pending = Phase1InboundMediaPending::new([7; 16], config, 9).unwrap(); + let failure = Phase1InboundMediaFailure::new([7; 16], "network", true, 10).unwrap(); + let wrong_failure = Phase1InboundMediaFailure::new([8; 16], "network", true, 10).unwrap(); + + let mut media = MediaReference::new(structural.clone()).unwrap(); + assert_eq!(media.invalidate(), None); + assert!(!media.is_renderable_with(&Phase1MediaCacheIndex::default(), config)); + assert_eq!( + media.fail(failure.clone()), + Err(Phase1InboundMediaError::OperationMismatch) + ); + assert_eq!( + media.verify([7; 16], verified.clone()), + Err(Phase1InboundMediaError::OperationMismatch) + ); + + media.begin(pending.clone()).unwrap(); + assert_eq!( + media.fail(wrong_failure), + Err(Phase1InboundMediaError::OperationMismatch) + ); + assert_eq!( + media.verify([7; 16], receipt(&structural, configuration(4))), + Err(Phase1InboundMediaError::OperationMismatch) + ); + media.fail(failure).unwrap(); + assert!(matches!( + media.retrieval(), + Phase1InboundMediaState::Failed(_) + )); + assert!(media.validate().is_ok()); + + media.begin(pending).unwrap(); + media.verify([7; 16], verified.clone()).unwrap(); + assert!(!media.is_renderable_with(&Phase1MediaCacheIndex::default(), config)); + let mut cache = Phase1MediaCacheIndex::default(); + cache + .admit(&verified, Phase1MediaCachePolicy::new(5, 1).unwrap(), 10) + .unwrap(); + assert!(media.is_renderable_with(&cache, config)); + assert!(!media.is_renderable_with(&cache, configuration(4))); + assert_eq!(media.invalidate(), Some(verified.artifact_id())); + + media + .restore( + Phase1InboundMediaState::Verified(Box::new(verified.clone())), + &Phase1MediaCacheIndex::default(), + ) + .unwrap(); + assert!(matches!( + media.retrieval(), + Phase1InboundMediaState::Unavailable + )); + media + .restore( + Phase1InboundMediaState::Verified(Box::new(verified)), + &cache, + ) + .unwrap(); + assert!(matches!( + media.retrieval(), + Phase1InboundMediaState::Verified(_) + )); } #[test] @@ -1437,6 +1721,320 @@ mod tests { } #[test] + fn receipt_validation_rejects_each_bound_field_independently() { + let expected = reference(None); + let commitment = + ByteCommitment::from_bytes(b"hello", MediaType::parse("image/jpeg").unwrap()); + let final_url = BlobUrl::parse(&format!("https://cdn.example/{HASH}.jpg")).unwrap(); + assert!( + Phase1VerifiedMediaReceipt::from_commitment( + &expected, + final_url.clone(), + &commitment, + 2, + 3, + configuration(1), + 0, + ) + .is_err() + ); + + let mutations: [fn(&mut Phase1StructuralMediaReference); 4] = [ + |value: &mut Phase1StructuralMediaReference| value.expected_byte_size = Some(6), + |value: &mut Phase1StructuralMediaReference| { + value.expected_media_type = Some("image/png".to_owned()) + }, + |value: &mut Phase1StructuralMediaReference| value.expected_width = Some(3), + |value: &mut Phase1StructuralMediaReference| value.expected_height = Some(4), + ]; + for mutate in mutations { + let mut changed = expected.clone(); + mutate(&mut changed); + changed.fingerprint = changed.derive_fingerprint(); + assert!( + Phase1VerifiedMediaReceipt::from_commitment( + &changed, + final_url.clone(), + &commitment, + 2, + 3, + configuration(1), + 1, + ) + .is_err() + ); + } + + let mut without_digest = Phase1StructuralMediaReference::new( + "https://media.example/file.jpg", + None, + Some("image/jpeg".to_owned()), + Some(2), + Some(3), + Some(5), + None, + ) + .unwrap(); + without_digest.expected_sha256 = None; + without_digest.fingerprint = without_digest.derive_fingerprint(); + assert!( + Phase1VerifiedMediaReceipt::from_commitment( + &without_digest, + final_url, + &commitment, + 2, + 3, + configuration(1), + 1, + ) + .is_err() + ); + + let valid = receipt(&expected, configuration(1)); + let mut corruptions: Vec<ReceiptMutation> = vec![ + Box::new(|value| value.schema_version = 2), + Box::new(|value| value.expected_sha256 = "a".repeat(64)), + Box::new(|value| value.artifact_id = Phase1MediaArtifactId([8; 32])), + Box::new(|value| value.byte_size = 0), + Box::new(|value| value.verified_at_unix_ms = 0), + Box::new(|value| value.configuration = Phase1MediaConfigurationFingerprint([0; 32])), + Box::new(|value| value.canonical_final_url = "not a url".to_owned()), + Box::new(|value| value.canonical_final_url.push_str("?changed=1")), + Box::new(|value| value.extension = "png".to_owned()), + Box::new(|value| value.media_type = "not a media type".to_owned()), + Box::new(|value| value.media_type = "image/svg+xml".to_owned()), + Box::new(|value| value.width = 0), + ]; + for mutate in corruptions.drain(..) { + let mut changed = valid.clone(); + mutate(&mut changed); + assert_eq!( + changed.validate_intrinsic(), + Err(Phase1InboundMediaError::CorruptReceipt) + ); + } + assert_eq!(valid.artifact_id().to_hex(), HASH); + assert_eq!(valid.configuration(), configuration(1)); + assert_eq!( + valid.canonical_final_url(), + format!("https://cdn.example/{HASH}.jpg") + ); + assert_eq!(valid.observed_sha256(), HASH); + assert_eq!(valid.byte_size(), 5); + assert_eq!(valid.media_type(), "image/jpeg"); + assert_eq!(valid.extension(), "jpg"); + assert_eq!( + (valid.width(), valid.height(), valid.verified_at_unix_ms()), + (2, 3, 10) + ); + + let wrong_extension = BlobUrl::parse(&format!("https://cdn.example/{HASH}.png")).unwrap(); + assert_eq!( + Phase1VerifiedMediaReceipt::from_commitment( + &expected, + wrong_extension, + &commitment, + 2, + 3, + configuration(1), + 1, + ), + Err(Phase1InboundMediaError::MetadataMismatch) + ); + let wrong_path_hash = "a".repeat(64); + assert_eq!( + Phase1VerifiedMediaReceipt::from_commitment( + &expected, + BlobUrl::parse(&format!("https://cdn.example/{wrong_path_hash}.jpg")).unwrap(), + &commitment, + 2, + 3, + configuration(1), + 1, + ), + Err(Phase1InboundMediaError::MetadataMismatch) + ); + + let reference_mutations: [fn(&mut Phase1StructuralMediaReference); 7] = [ + |value| value.fingerprint = [8; 32], + |value| value.source_url = "https://other.example/file.jpg".to_owned(), + |value| value.expected_sha256 = Some("a".repeat(64)), + |value| value.expected_byte_size = Some(6), + |value| value.expected_media_type = Some("image/png".to_owned()), + |value| value.expected_width = Some(3), + |value| value.expected_height = Some(4), + ]; + for mutate in reference_mutations { + let mut changed = expected.clone(); + mutate(&mut changed); + assert_eq!( + valid.validate(&changed), + Err(Phase1InboundMediaError::CorruptReceipt) + ); + } + } + + #[test] + fn cache_validation_rejects_each_invalid_field_independently() { + assert!(Phase1MediaCachePolicy::new(0, 1).is_err()); + assert!(Phase1MediaCachePolicy::new(1, 0).is_err()); + let policy = Phase1MediaCachePolicy::default(); + assert_eq!(policy.max_bytes(), 256 * 1024 * 1024); + assert_eq!(policy.max_artifacts(), 2_000); + + let structural = reference(None); + let verified = receipt(&structural, configuration(4)); + assert!(Phase1MediaCacheEntry::from_receipt(&verified, 9).is_err()); + let mut cache = Phase1MediaCacheIndex::default(); + assert!(!cache.touch(verified.artifact_id(), 1).unwrap()); + assert!(cache.touch(verified.artifact_id(), 0).is_err()); + assert!(!cache.invalidate_artifact(verified.artifact_id())); + assert!(cache.invalidate_configuration(configuration(4)).is_empty()); + cache + .admit(&verified, Phase1MediaCachePolicy::new(5, 1).unwrap(), 10) + .unwrap(); + assert!(cache.touch(verified.artifact_id(), 11).unwrap()); + assert!(cache.invalidate_artifact(verified.artifact_id())); + + let mut baseline = Phase1MediaCacheIndex::default(); + baseline + .admit(&verified, Phase1MediaCachePolicy::new(5, 1).unwrap(), 10) + .unwrap(); + let key = verified.artifact_id().to_hex(); + let mutations: Vec<CacheMutation> = vec![ + Box::new(|value| value.schema_version = 2), + Box::new(|value| value.configuration = None), + Box::new({ + let key = key.clone(); + move |value| value.entries.get_mut(&key).unwrap().byte_size = 0 + }), + Box::new({ + let key = key.clone(); + move |value| value.entries.get_mut(&key).unwrap().cached_at_unix_ms = 0 + }), + Box::new({ + let key = key.clone(); + move |value| { + value + .entries + .get_mut(&key) + .unwrap() + .last_accessed_at_unix_ms = 9 + } + }), + Box::new({ + let key = key.clone(); + move |value| value.entries.get_mut(&key).unwrap().media_type = "bad".to_owned() + }), + Box::new({ + let key = key.clone(); + move |value| value.entries.get_mut(&key).unwrap().extension.clear() + }), + Box::new({ + let key = key.clone(); + move |value| value.entries.get_mut(&key).unwrap().width = 0 + }), + Box::new(|value| { + let entry = value.entries.pop_first().unwrap().1; + value.entries.insert("wrong-key".to_owned(), entry); + }), + Box::new(|value| { + value.configuration = Some(Phase1MediaConfigurationFingerprint([0; 32])); + }), + ]; + for mutate in mutations { + let mut changed = baseline.clone(); + mutate(&mut changed); + assert_eq!(changed.status(), Err(Phase1InboundMediaError::CorruptState)); + } + assert_eq!( + baseline.admit(&verified, Phase1MediaCachePolicy::new(4, 1).unwrap(), 10), + Err(Phase1InboundMediaError::CacheQuotaExceeded) + ); + + let mut wrong_schema = baseline.clone(); + wrong_schema.schema_version = 2; + assert!(!wrong_schema.contains(&verified)); + let mut wrong_configuration = baseline.clone(); + wrong_configuration.configuration = Some(configuration(5)); + assert!(!wrong_configuration.contains(&verified)); + let mut missing = baseline.clone(); + missing.entries.clear(); + assert!(!missing.contains(&verified)); + let mut mismatched = baseline.clone(); + mismatched.entries.get_mut(&key).unwrap().height = 4; + assert!(!mismatched.contains(&verified)); + + let receipt_mutations: [fn(&mut Phase1VerifiedMediaReceipt); 6] = [ + |value| value.artifact_id = Phase1MediaArtifactId([8; 32]), + |value| value.byte_size = 6, + |value| value.media_type = "image/png".to_owned(), + |value| value.extension = "png".to_owned(), + |value| value.width = 3, + |value| value.height = 4, + ]; + let entry = baseline.entries.get(&key).unwrap(); + for mutate in receipt_mutations { + let mut changed = verified.clone(); + mutate(&mut changed); + assert!(!entry.matches(&changed)); + } + + let mut collision = baseline.clone(); + collision.entries.get_mut(&key).unwrap().byte_size = 4; + assert_eq!( + collision.admit(&verified, Phase1MediaCachePolicy::new(10, 2).unwrap(), 10), + Err(Phase1InboundMediaError::ArtifactCollision) + ); + + let second_hash = Sha256::digest(b"world").to_hex(); + let second_reference = Phase1StructuralMediaReference::new( + format!("https://media.example/{second_hash}.jpg"), + Some(second_hash.clone()), + Some("image/jpeg".to_owned()), + Some(2), + Some(3), + Some(5), + None, + ) + .unwrap(); + let second = Phase1VerifiedMediaReceipt::from_commitment( + &second_reference, + BlobUrl::parse(&format!("https://cdn.example/{second_hash}.jpg")).unwrap(), + &ByteCommitment::from_bytes(b"world", MediaType::parse("image/jpeg").unwrap()), + 2, + 3, + configuration(4), + 11, + ) + .unwrap(); + let mut byte_limited = baseline.clone(); + assert_eq!( + byte_limited + .admit(&second, Phase1MediaCachePolicy::new(8, 2).unwrap(), 11) + .unwrap(), + vec![verified.artifact_id()] + ); + + let mut overflow = Phase1MediaCacheIndex { + configuration: Some(configuration(4)), + ..Phase1MediaCacheIndex::default() + }; + let mut first_entry = Phase1MediaCacheEntry::from_receipt(&verified, 10).unwrap(); + first_entry.byte_size = u64::MAX; + overflow.entries.insert(key, first_entry); + let second_key = second.artifact_id().to_hex(); + overflow.entries.insert( + second_key, + Phase1MediaCacheEntry::from_receipt(&second, 11).unwrap(), + ); + assert_eq!( + overflow.total_bytes(), + Err(Phase1InboundMediaError::CorruptState) + ); + } + + #[test] fn cache_is_content_addressed_bounded_lru_and_configuration_scoped() { let config = configuration(4); let first_reference = reference(None); diff --git a/core/crates/tera_core/src/runtime/product_surface/projection.rs b/core/crates/tera_core/src/runtime/product_surface/projection.rs @@ -510,6 +510,48 @@ mod tests { } #[test] + fn malformed_address_and_media_helpers_fail_closed() { + assert_eq!(tag_value(&[Vec::new()], &["title"]), None); + assert_eq!(tag_value(&[vec!["title".to_owned()]], &["title"]), None); + assert_eq!(tag_values(&[Vec::new()], "price"), None); + assert_eq!(tag_values(&[vec!["price".to_owned()]], "price"), None); + assert_eq!( + tag_values( + &[vec!["price".to_owned(), "3".to_owned(), "CAD".to_owned()]], + "price" + ), + Some(vec!["3".to_owned(), "CAD".to_owned()]) + ); + assert_eq!( + tag_time(&[vec!["start".to_owned(), "bad".to_owned()]], "start"), + None + ); + assert!( + tag_time( + &[vec!["start".to_owned(), "2026-08-13".to_owned()]], + "start" + ) + .is_some() + ); + + assert_eq!(blossom_digest("not-a-url"), None); + assert_eq!(blossom_digest("https://example.test"), None); + assert_eq!(blossom_digest("https://example.test/short"), None); + assert_eq!( + blossom_digest(&format!("https://example.test/{}", "G".repeat(64))), + None + ); + assert_eq!( + blossom_digest(&format!("https://example.test/{}/file.jpg", "a".repeat(64))), + Some("a".repeat(64)) + ); + assert_eq!( + media_reference("not-a-url", None, None, None, None, None, None,), + None + ); + } + + #[test] fn supporting_profiles_never_become_cards_and_nonmatches_are_excluded() { let profile = admitted(0, vec![], r#"{"name":"Farm"}"#); let reply = admitted(1, vec![vec!["e", &"a".repeat(64), "", "root"]], "Reply"); diff --git a/core/crates/tera_core/src/runtime/product_surface/today.rs b/core/crates/tera_core/src/runtime/product_surface/today.rs @@ -2692,6 +2692,17 @@ mod tests { .await .expect("search"); assert!(search.iter().any(|result| result.profile.is_some())); + let card_search = runtime + .phase1_search(&context, "fresh field photo", 10, 2_000_000_200) + .await + .expect("card search"); + assert!(card_search.iter().any(|result| result.card.is_some())); + let profile_limited = runtime + .phase1_search(&context, "moss@example.com", 1, 2_000_000_200) + .await + .expect("profile-limited search"); + assert_eq!(profile_limited.len(), 1); + assert!(profile_limited[0].profile.is_some()); let me = runtime .phase1_me(&context, &author, 2_000_000_200) .await @@ -2753,6 +2764,48 @@ mod tests { Phase1InboundMediaState::Verified(receipt) => receipt.artifact_id(), state => panic!("unexpected profile state: {state:?}"), }; + assert!(matches!( + runtime + .phase1_fail_media_retrieval( + &context, + *photo_reference.fingerprint(), + Phase1InboundMediaFailure::new([3; 16], "network", true, 29).unwrap(), + ) + .await, + Err(TodayError::InboundMedia( + Phase1InboundMediaError::OperationMismatch + )) + )); + assert!(matches!( + runtime + .phase1_begin_media_retrieval( + &context, + *photo_reference.fingerprint(), + Phase1InboundMediaPending::new( + [3; 16], + Phase1MediaConfigurationFingerprint::new([8; 32]).unwrap(), + 29, + ) + .unwrap(), + ) + .await, + Err(TodayError::InboundMedia( + Phase1InboundMediaError::ConfigurationMismatch + )) + )); + assert!(matches!( + runtime + .phase1_commit_media_receipt( + &context, + [0; 32], + [3; 16], + photo_receipt.clone(), + Phase1MediaCachePolicy::new(1_024, 8).unwrap(), + 29, + ) + .await, + Err(TodayError::InvalidRequest) + )); runtime .phase1_begin_media_retrieval( &context, @@ -2809,6 +2862,25 @@ mod tests { .await .expect("configuration invalidation"); assert_eq!(removed.len(), 1); + assert!( + runtime + .phase1_invalidate_media_configuration( + &context, + Phase1MediaConfigurationFingerprint::new([8; 32]).unwrap(), + ) + .await + .expect("idempotent configuration invalidation") + .is_empty() + ); + assert!( + !runtime + .phase1_invalidate_media_artifact( + &context, + Phase1MediaArtifactId::parse(&"f".repeat(64)).unwrap(), + ) + .await + .expect("missing artifact invalidation") + ); let invalidated = runtime .phase1_today_page(&context, TodayPageRequest::first(100, 2_000_000_203)) .await @@ -3686,7 +3758,18 @@ mod tests { TodayCursor::scope("bad"), Err(CursorError::Malformed) )); + assert!(!valid_public_key("short")); assert!(!valid_public_key("A".repeat(64).as_str())); + assert!(valid_public_key("a".repeat(64).as_str())); + assert_eq!(locality_evidence(None, &[]), LocalityEvidence::Missing); + assert_eq!(blossom_digest("https://blob.example"), None); + assert_eq!( + blossom_digest(&format!( + "https://blob.example/{}/image.jpg", + "a".repeat(64) + )), + Some("a".repeat(64)) + ); assert_eq!(TODAY_PAGE_LIMIT_MAX, 100); assert_eq!(TODAY_SEARCH_LIMIT_MAX, 100); } diff --git a/core/crates/tera_core/src/runtime/sdk.rs b/core/crates/tera_core/src/runtime/sdk.rs @@ -1,6 +1,7 @@ use radroots_sdk::capability::{Availability, Maturity}; use super::RadrootsRuntime; +#[cfg(feature = "mobile-social")] use super::product_surface::{BlossomPreferences, RelayPreferences}; use crate::RadrootsAppError;