private_artifact.rs (23527B)
1 use radroots_storage::{ 2 Error, 3 private_artifact::{ 4 ArtifactCommitment, ArtifactKind, ArtifactSchemaId, DeletionReason, DurableSecretReference, 5 PrivateArtifactEnvelopeMigrationStatus, PrivateArtifactId, PrivateArtifactMetadata, 6 PrivateArtifactResealDisposition, PrivateArtifactResealId, PrivateArtifactResealRequest, 7 PrivateArtifactRevision, PrivateArtifactStage, PrivateArtifactStore, RetentionPolicy, 8 }, 9 }; 10 11 #[cfg(feature = "memory")] 12 use futures_executor::block_on; 13 #[cfg(feature = "memory")] 14 use radroots_storage::memory::MemoryStorage; 15 16 fn metadata(retention: RetentionPolicy) -> PrivateArtifactMetadata { 17 PrivateArtifactMetadata::new( 18 PrivateArtifactId::new([1; 16]).expect("artifact id"), 19 ArtifactKind::parse("trade.private_terms").expect("artifact kind"), 20 ArtifactSchemaId::parse("trade.private_terms.v1").expect("schema id"), 21 ArtifactCommitment::new([2; 32]), 22 512, 23 DurableSecretReference::new("keyring", "opaque-key-token", 3).expect("secret reference"), 24 retention, 25 100, 26 ) 27 .expect("metadata") 28 } 29 30 #[test] 31 fn retention_distinguishes_expiry_from_earliest_deletion() { 32 let policy = RetentionPolicy::new(Some(400), Some(300)).expect("retention policy"); 33 assert!(!policy.is_expired_at(299)); 34 assert!(policy.is_expired_at(300)); 35 assert!(!policy.permits_deletion_at(399)); 36 assert!(policy.permits_deletion_at(400)); 37 assert_eq!( 38 RetentionPolicy::new(Some(0), None), 39 Err(Error::InvalidPrivateArtifactRetention) 40 ); 41 } 42 43 #[test] 44 fn expiry_is_revision_bound_monotonic_and_policy_driven() { 45 let active = metadata(RetentionPolicy::new(None, Some(300)).expect("retention")); 46 assert_eq!( 47 active.mark_expired(PrivateArtifactRevision::INITIAL, 299), 48 Err(Error::PrivateArtifactNotExpired) 49 ); 50 let expired = active 51 .mark_expired(PrivateArtifactRevision::INITIAL, 300) 52 .expect("expired metadata"); 53 assert_eq!(expired.stage(), PrivateArtifactStage::Expired); 54 assert_eq!(expired.revision().get(), 2); 55 assert_eq!( 56 expired.mark_expired(PrivateArtifactRevision::INITIAL, 301), 57 Err(Error::PrivateArtifactRevisionConflict) 58 ); 59 } 60 61 #[test] 62 fn tombstones_preserve_commitment_and_enforce_retention() { 63 let active = metadata(RetentionPolicy::new(Some(400), Some(300)).expect("retention")); 64 let expired = active 65 .mark_expired(PrivateArtifactRevision::INITIAL, 300) 66 .expect("expired metadata"); 67 assert_eq!( 68 expired.tombstone(expired.revision(), 399, DeletionReason::RetentionExpired,), 69 Err(Error::PrivateArtifactRetentionActive) 70 ); 71 let deleted = expired 72 .tombstone(expired.revision(), 400, DeletionReason::RetentionExpired) 73 .expect("tombstone"); 74 assert_eq!(deleted.stage(), PrivateArtifactStage::Tombstoned); 75 let tombstone = deleted.tombstone_record().expect("tombstone record"); 76 assert_eq!(tombstone.commitment(), active.commitment()); 77 assert_eq!(tombstone.deleted_at_unix_ms(), 400); 78 assert_eq!(tombstone.reason(), DeletionReason::RetentionExpired); 79 assert_eq!( 80 deleted.tombstone(deleted.revision(), 401, DeletionReason::OperatorRequested,), 81 Err(Error::PrivateArtifactTombstoned) 82 ); 83 } 84 85 #[test] 86 #[cfg(feature = "serde")] 87 fn secret_references_are_bounded_redacted_and_round_trip() { 88 let reference = 89 DurableSecretReference::new("keyring", "opaque-key-token", 3).expect("secret reference"); 90 let diagnostic = format!("{reference:?}"); 91 assert!(diagnostic.contains("[REDACTED]")); 92 assert!(!diagnostic.contains("opaque-key-token")); 93 94 let encoded = serde_json::to_string(&reference).expect("reference JSON"); 95 let decoded: DurableSecretReference = 96 serde_json::from_str(encoded.as_str()).expect("reference round trip"); 97 assert_eq!(decoded, reference); 98 assert!( 99 serde_json::from_str::<DurableSecretReference>( 100 r#"{"provider":"keyring","opaque_reference":"token","key_version":0}"#, 101 ) 102 .is_err() 103 ); 104 assert_eq!( 105 DurableSecretReference::new("keyring", "token", 0), 106 Err(Error::InvalidPrivateArtifactSecretReference) 107 ); 108 } 109 110 #[test] 111 fn metadata_contains_only_protected_size_commitment_and_reference() { 112 fn accepts_dyn(_: Option<&dyn PrivateArtifactStore>) {} 113 accepts_dyn(None); 114 115 let value = metadata(RetentionPolicy::indefinite()); 116 assert_eq!(value.protected_size_bytes(), 512); 117 assert_eq!(value.secret_reference().provider(), "keyring"); 118 assert_eq!(value.secret_reference().key_version(), 3); 119 assert_eq!( 120 PrivateArtifactId::new([0; 16]), 121 Err(Error::InvalidPrivateArtifactId) 122 ); 123 assert_eq!( 124 ArtifactKind::parse("Not Canonical"), 125 Err(Error::InvalidPrivateArtifactKind) 126 ); 127 } 128 129 #[test] 130 fn private_artifact_value_matrix_covers_every_bound_and_accessor() { 131 let id = PrivateArtifactId::new([1; 16]).expect("id"); 132 assert_eq!(id.as_bytes(), &[1; 16]); 133 for value in ["", "Uppercase", " leading", "trailing ", "bad/slash"] { 134 assert_eq!( 135 ArtifactKind::parse(value), 136 Err(Error::InvalidPrivateArtifactKind) 137 ); 138 assert_eq!( 139 ArtifactSchemaId::parse(value), 140 Err(Error::InvalidPrivateArtifactSchema) 141 ); 142 } 143 assert_eq!( 144 ArtifactKind::parse( 145 "x".repeat(radroots_storage::private_artifact::ARTIFACT_KIND_MAX_BYTES + 1) 146 ), 147 Err(Error::InvalidPrivateArtifactKind) 148 ); 149 assert_eq!( 150 ArtifactSchemaId::parse( 151 "x".repeat(radroots_storage::private_artifact::ARTIFACT_SCHEMA_MAX_BYTES + 1) 152 ), 153 Err(Error::InvalidPrivateArtifactSchema) 154 ); 155 let kind = ArtifactKind::parse("trade.private_terms").unwrap(); 156 let schema = ArtifactSchemaId::parse("trade.private_terms.v1").unwrap(); 157 assert_eq!(kind.as_str(), "trade.private_terms"); 158 assert_eq!(schema.as_str(), "trade.private_terms.v1"); 159 let commitment = ArtifactCommitment::new([2; 32]); 160 assert_eq!(commitment.as_bytes(), &[2; 32]); 161 162 for (provider, reference, version) in [ 163 ("", "token", 1), 164 ("Bad", "token", 1), 165 ("keyring", "", 1), 166 ("keyring", " token", 1), 167 ("keyring", "token ", 1), 168 ("keyring", "bad\ntoken", 1), 169 ("keyring", "token", 0), 170 ] { 171 assert_eq!( 172 DurableSecretReference::new(provider, reference, version), 173 Err(Error::InvalidPrivateArtifactSecretReference) 174 ); 175 } 176 assert_eq!( 177 DurableSecretReference::new( 178 "x".repeat(radroots_storage::private_artifact::SECRET_PROVIDER_MAX_BYTES + 1), 179 "token", 180 1, 181 ), 182 Err(Error::InvalidPrivateArtifactSecretReference) 183 ); 184 assert_eq!( 185 DurableSecretReference::new( 186 "keyring", 187 "x".repeat(radroots_storage::private_artifact::SECRET_REFERENCE_MAX_BYTES + 1), 188 1, 189 ), 190 Err(Error::InvalidPrivateArtifactSecretReference) 191 ); 192 let secret = DurableSecretReference::new("keyring", "opaque", 1).unwrap(); 193 assert_eq!(secret.provider(), "keyring"); 194 assert_eq!(secret.opaque_reference(), "opaque"); 195 assert_eq!(secret.key_version(), 1); 196 197 assert_eq!( 198 RetentionPolicy::new(None, Some(0)), 199 Err(Error::InvalidPrivateArtifactRetention) 200 ); 201 let retention = RetentionPolicy::new(Some(200), Some(150)).unwrap(); 202 assert_eq!(retention.delete_not_before_unix_ms(), Some(200)); 203 assert_eq!(retention.expires_at_unix_ms(), Some(150)); 204 assert!(!retention.is_expired_at(149)); 205 assert!(retention.is_expired_at(150)); 206 assert!(!retention.permits_deletion_at(199)); 207 assert!(retention.permits_deletion_at(200)); 208 let indefinite = RetentionPolicy::indefinite(); 209 assert!(!indefinite.is_expired_at(u64::MAX)); 210 assert!(indefinite.permits_deletion_at(0)); 211 assert_eq!( 212 PrivateArtifactRevision::new(0), 213 Err(Error::InvalidPrivateArtifactRevision) 214 ); 215 assert_eq!(PrivateArtifactRevision::new(2).unwrap().get(), 2); 216 217 let value = metadata(retention); 218 assert_eq!(value.artifact_id(), id); 219 assert_eq!(value.kind(), &kind); 220 assert_eq!(value.schema_id(), &schema); 221 assert_eq!(value.commitment(), commitment); 222 assert_eq!(value.protected_size_bytes(), 512); 223 assert_eq!( 224 value.secret_reference().opaque_reference(), 225 "opaque-key-token" 226 ); 227 assert_eq!(value.retention(), retention); 228 assert_eq!(value.revision(), PrivateArtifactRevision::INITIAL); 229 assert_eq!(value.stage(), PrivateArtifactStage::Active); 230 assert_eq!(value.created_at_unix_ms(), 100); 231 assert_eq!(value.updated_at_unix_ms(), 100); 232 assert!(value.tombstone_record().is_none()); 233 } 234 235 #[test] 236 fn metadata_construction_and_durable_state_fail_closed() { 237 let id = PrivateArtifactId::new([1; 16]).unwrap(); 238 let kind = ArtifactKind::parse("trade.private_terms").unwrap(); 239 let schema = ArtifactSchemaId::parse("trade.private_terms.v1").unwrap(); 240 let commitment = ArtifactCommitment::new([2; 32]); 241 let secret = DurableSecretReference::new("keyring", "opaque", 1).unwrap(); 242 for (size, created, retention) in [ 243 (0, 100, RetentionPolicy::indefinite()), 244 (1, 0, RetentionPolicy::indefinite()), 245 (1, 100, RetentionPolicy::new(Some(99), None).unwrap()), 246 (1, 100, RetentionPolicy::new(None, Some(99)).unwrap()), 247 ] { 248 assert_eq!( 249 PrivateArtifactMetadata::new( 250 id, 251 kind.clone(), 252 schema.clone(), 253 commitment, 254 size, 255 secret.clone(), 256 retention, 257 created, 258 ), 259 Err(Error::InvalidPrivateArtifactMetadata) 260 ); 261 } 262 263 let retention = RetentionPolicy::new(Some(200), Some(150)).unwrap(); 264 let durable = |revision, stage, updated, tombstone| { 265 PrivateArtifactMetadata::from_durable_parts( 266 id, 267 kind.clone(), 268 schema.clone(), 269 commitment, 270 1, 271 secret.clone(), 272 retention, 273 revision, 274 stage, 275 100, 276 updated, 277 tombstone, 278 ) 279 }; 280 assert!( 281 durable( 282 PrivateArtifactRevision::INITIAL, 283 PrivateArtifactStage::Active, 284 100, 285 None 286 ) 287 .is_ok() 288 ); 289 assert!( 290 durable( 291 PrivateArtifactRevision::new(2).unwrap(), 292 PrivateArtifactStage::Expired, 293 150, 294 None 295 ) 296 .is_ok() 297 ); 298 assert!( 299 durable( 300 PrivateArtifactRevision::new(3).unwrap(), 301 PrivateArtifactStage::Tombstoned, 302 200, 303 Some((200, DeletionReason::RetentionExpired, commitment)), 304 ) 305 .is_ok() 306 ); 307 for result in [ 308 durable( 309 PrivateArtifactRevision::INITIAL, 310 PrivateArtifactStage::Active, 311 99, 312 None, 313 ), 314 durable( 315 PrivateArtifactRevision::new(2).unwrap(), 316 PrivateArtifactStage::Active, 317 100, 318 None, 319 ), 320 durable( 321 PrivateArtifactRevision::new(2).unwrap(), 322 PrivateArtifactStage::Expired, 323 149, 324 None, 325 ), 326 durable( 327 PrivateArtifactRevision::new(3).unwrap(), 328 PrivateArtifactStage::Tombstoned, 329 200, 330 None, 331 ), 332 durable( 333 PrivateArtifactRevision::new(3).unwrap(), 334 PrivateArtifactStage::Tombstoned, 335 200, 336 Some((199, DeletionReason::UserRequested, commitment)), 337 ), 338 durable( 339 PrivateArtifactRevision::new(3).unwrap(), 340 PrivateArtifactStage::Tombstoned, 341 200, 342 Some(( 343 200, 344 DeletionReason::UserRequested, 345 ArtifactCommitment::new([9; 32]), 346 )), 347 ), 348 durable( 349 PrivateArtifactRevision::new(3).unwrap(), 350 PrivateArtifactStage::Tombstoned, 351 199, 352 Some((199, DeletionReason::UserRequested, commitment)), 353 ), 354 durable( 355 PrivateArtifactRevision::new(3).unwrap(), 356 PrivateArtifactStage::Tombstoned, 357 149, 358 Some((149, DeletionReason::RetentionExpired, commitment)), 359 ), 360 ] { 361 assert_eq!(result, Err(Error::CorruptPrivateArtifactMetadata)); 362 } 363 } 364 365 #[test] 366 fn transition_and_status_edge_matrix_is_complete() { 367 let active = metadata(RetentionPolicy::new(Some(200), Some(150)).unwrap()); 368 assert_eq!( 369 active.mark_expired(PrivateArtifactRevision::new(2).unwrap(), 150), 370 Err(Error::PrivateArtifactRevisionConflict) 371 ); 372 assert_eq!( 373 active.mark_expired(PrivateArtifactRevision::INITIAL, 99), 374 Err(Error::InvalidPrivateArtifactTimestamp) 375 ); 376 assert_eq!( 377 active.tombstone( 378 PrivateArtifactRevision::INITIAL, 379 150, 380 DeletionReason::RetentionExpired, 381 ), 382 Err(Error::PrivateArtifactRetentionActive) 383 ); 384 let direct = active 385 .tombstone( 386 PrivateArtifactRevision::INITIAL, 387 200, 388 DeletionReason::UserRequested, 389 ) 390 .unwrap(); 391 assert_eq!(direct.revision().get(), 2); 392 assert_eq!(direct.stage(), PrivateArtifactStage::Tombstoned); 393 let tombstone = direct.tombstone_record().unwrap(); 394 assert_eq!(tombstone.deleted_at_unix_ms(), 200); 395 assert_eq!(tombstone.reason(), DeletionReason::UserRequested); 396 assert_eq!(tombstone.commitment(), active.commitment()); 397 398 assert_eq!( 399 radroots_storage::private_artifact::PrivateArtifactStatus { 400 active: 1, 401 expired: 2, 402 tombstoned: 3, 403 } 404 .total(), 405 Some(6) 406 ); 407 assert_eq!( 408 radroots_storage::private_artifact::PrivateArtifactStatus { 409 active: u64::MAX, 410 expired: 1, 411 tombstoned: 0, 412 } 413 .total(), 414 None 415 ); 416 assert_eq!( 417 radroots_storage::private_artifact::PrivateArtifactStatus { 418 active: 0, 419 expired: u64::MAX, 420 tombstoned: 1, 421 } 422 .total(), 423 None 424 ); 425 } 426 427 #[test] 428 fn envelope_context_is_derived_and_transplant_resistant() { 429 let original = metadata(RetentionPolicy::indefinite()); 430 let context = original.envelope_context(); 431 assert_eq!( 432 context.purpose(), 433 "radroots.private_artifact.trade.private_terms" 434 ); 435 assert_eq!(context.subject_type(), "private_artifact"); 436 assert_eq!(context.subject(), "01010101010101010101010101010101"); 437 assert_eq!(context.payload_schema(), "trade.private_terms.v1"); 438 439 let with_id = |id, kind, schema| { 440 PrivateArtifactMetadata::new( 441 PrivateArtifactId::new(id).unwrap(), 442 ArtifactKind::parse(kind).unwrap(), 443 ArtifactSchemaId::parse(schema).unwrap(), 444 ArtifactCommitment::new([2; 32]), 445 512, 446 DurableSecretReference::new("keyring", "opaque-key-token", 3).unwrap(), 447 RetentionPolicy::indefinite(), 448 100, 449 ) 450 .unwrap() 451 .envelope_context() 452 .fingerprint() 453 }; 454 let fingerprint = context.fingerprint(); 455 assert_ne!( 456 fingerprint, 457 with_id([3; 16], "trade.private_terms", "trade.private_terms.v1") 458 ); 459 assert_ne!( 460 fingerprint, 461 with_id([1; 16], "trade.other_terms", "trade.private_terms.v1") 462 ); 463 assert_ne!( 464 fingerprint, 465 with_id([1; 16], "trade.private_terms", "trade.private_terms.v2") 466 ); 467 468 for invalid in ["trade..terms", ".trade.terms", "trade.1terms", "trade"] { 469 assert_eq!( 470 ArtifactKind::parse(invalid), 471 Err(Error::InvalidPrivateArtifactKind) 472 ); 473 } 474 for invalid in [ 475 "trade..terms.v1", 476 "trade.terms", 477 "trade.terms.latest", 478 "trade.terms.v", 479 ] { 480 assert_eq!( 481 ArtifactSchemaId::parse(invalid), 482 Err(Error::InvalidPrivateArtifactSchema) 483 ); 484 } 485 let diagnostic = format!("{context:?} {original:?}"); 486 assert!(!diagnostic.contains("01010101010101010101010101010101")); 487 } 488 489 #[test] 490 #[cfg(feature = "memory")] 491 fn reseal_contract_distinguishes_exact_replay_and_conflict() { 492 let store = MemoryStorage::default(); 493 let initial = metadata(RetentionPolicy::indefinite()); 494 block_on(store.put_metadata(initial.clone())).unwrap(); 495 let reseal_id = PrivateArtifactResealId::new([9; 16]).unwrap(); 496 let next_reference = DurableSecretReference::new("keyring", "fresh-token", 4).unwrap(); 497 for (next_commitment, next_size, committed_at) in [ 498 (initial.commitment(), 640, 200), 499 (ArtifactCommitment::new([8; 32]), 0, 200), 500 (ArtifactCommitment::new([8; 32]), 640, 0), 501 ] { 502 assert_eq!( 503 PrivateArtifactResealRequest::new( 504 reseal_id, 505 initial.artifact_id(), 506 initial.revision(), 507 initial.commitment(), 508 next_commitment, 509 next_size, 510 next_reference.clone(), 511 committed_at, 512 ), 513 Err(Error::InvalidPrivateArtifactResealRequest) 514 ); 515 } 516 let request = PrivateArtifactResealRequest::new( 517 reseal_id, 518 initial.artifact_id(), 519 initial.revision(), 520 initial.commitment(), 521 ArtifactCommitment::new([8; 32]), 522 640, 523 next_reference, 524 200, 525 ) 526 .unwrap(); 527 let committed = block_on(store.reseal_metadata(request.clone())).unwrap(); 528 assert_eq!( 529 committed.disposition(), 530 PrivateArtifactResealDisposition::Committed 531 ); 532 assert_eq!(committed.committed_revision().get(), 2); 533 assert_eq!(committed.request_fingerprint(), request.fingerprint()); 534 535 let wrong_reseal_id = PrivateArtifactResealRequest::new( 536 PrivateArtifactResealId::new([7; 16]).unwrap(), 537 request.artifact_id(), 538 request.expected_revision(), 539 request.expected_commitment(), 540 request.next_commitment(), 541 request.next_protected_size_bytes(), 542 request.next_secret_reference().clone(), 543 request.committed_at_unix_ms(), 544 ) 545 .unwrap(); 546 assert_eq!( 547 committed.replay(&wrong_reseal_id), 548 Err(Error::PrivateArtifactResealConflict) 549 ); 550 let wrong_artifact = PrivateArtifactResealRequest::new( 551 request.reseal_id(), 552 PrivateArtifactId::new([7; 16]).unwrap(), 553 request.expected_revision(), 554 request.expected_commitment(), 555 request.next_commitment(), 556 request.next_protected_size_bytes(), 557 request.next_secret_reference().clone(), 558 request.committed_at_unix_ms(), 559 ) 560 .unwrap(); 561 assert_eq!( 562 committed.replay(&wrong_artifact), 563 Err(Error::PrivateArtifactResealConflict) 564 ); 565 assert_eq!( 566 initial.resealed(&wrong_artifact), 567 Err(Error::PrivateArtifactResealConflict) 568 ); 569 let wrong_revision = PrivateArtifactResealRequest::new( 570 request.reseal_id(), 571 request.artifact_id(), 572 PrivateArtifactRevision::new(2).unwrap(), 573 request.expected_commitment(), 574 request.next_commitment(), 575 request.next_protected_size_bytes(), 576 request.next_secret_reference().clone(), 577 request.committed_at_unix_ms(), 578 ) 579 .unwrap(); 580 assert_eq!( 581 initial.resealed(&wrong_revision), 582 Err(Error::PrivateArtifactResealConflict) 583 ); 584 let wrong_commitment = PrivateArtifactResealRequest::new( 585 request.reseal_id(), 586 request.artifact_id(), 587 request.expected_revision(), 588 ArtifactCommitment::new([6; 32]), 589 request.next_commitment(), 590 request.next_protected_size_bytes(), 591 request.next_secret_reference().clone(), 592 request.committed_at_unix_ms(), 593 ) 594 .unwrap(); 595 assert_eq!( 596 initial.resealed(&wrong_commitment), 597 Err(Error::PrivateArtifactResealConflict) 598 ); 599 let stale_timestamp = PrivateArtifactResealRequest::new( 600 request.reseal_id(), 601 request.artifact_id(), 602 request.expected_revision(), 603 request.expected_commitment(), 604 request.next_commitment(), 605 request.next_protected_size_bytes(), 606 request.next_secret_reference().clone(), 607 initial.updated_at_unix_ms(), 608 ) 609 .unwrap(); 610 assert_eq!( 611 initial.resealed(&stale_timestamp), 612 Err(Error::InvalidPrivateArtifactTimestamp) 613 ); 614 615 let replayed = block_on(store.reseal_metadata(request.clone())).unwrap(); 616 assert_eq!( 617 replayed.disposition(), 618 PrivateArtifactResealDisposition::Replayed 619 ); 620 assert_eq!( 621 replayed.committed_revision(), 622 committed.committed_revision() 623 ); 624 625 let conflicting = PrivateArtifactResealRequest::new( 626 request.reseal_id(), 627 request.artifact_id(), 628 request.expected_revision(), 629 request.expected_commitment(), 630 ArtifactCommitment::new([7; 32]), 631 request.next_protected_size_bytes(), 632 request.next_secret_reference().clone(), 633 request.committed_at_unix_ms(), 634 ) 635 .unwrap(); 636 assert_eq!( 637 block_on(store.reseal_metadata(conflicting)), 638 Err(Error::PrivateArtifactResealConflict) 639 ); 640 assert_eq!( 641 PrivateArtifactResealId::new([0; 16]), 642 Err(Error::InvalidPrivateArtifactResealId) 643 ); 644 } 645 646 #[test] 647 fn migration_status_is_bounded_and_overflow_safe() { 648 assert_eq!( 649 PrivateArtifactEnvelopeMigrationStatus { 650 v1_pending: 1, 651 v2_current: 2, 652 corrupt: 3, 653 blocked_provider: 4, 654 conflicted: 5, 655 } 656 .total(), 657 Some(15) 658 ); 659 assert_eq!( 660 PrivateArtifactEnvelopeMigrationStatus { 661 v1_pending: u64::MAX, 662 v2_current: 1, 663 ..PrivateArtifactEnvelopeMigrationStatus::default() 664 } 665 .total(), 666 None 667 ); 668 } 669 670 #[test] 671 #[cfg(feature = "serde")] 672 fn serde_cannot_bypass_private_artifact_invariants() { 673 let value = metadata(RetentionPolicy::indefinite()); 674 let encoded = serde_json::to_string(&value).expect("metadata JSON"); 675 let decoded: PrivateArtifactMetadata = 676 serde_json::from_str(&encoded).expect("validated metadata round trip"); 677 assert_eq!(decoded, value); 678 679 assert!(serde_json::from_str::<PrivateArtifactId>(&format!("{:?}", [0_u8; 16])).is_err()); 680 assert!(serde_json::from_str::<PrivateArtifactResealId>(&format!("{:?}", [0_u8; 16])).is_err()); 681 assert!(serde_json::from_str::<ArtifactKind>(r#""not_namespaced""#).is_err()); 682 assert!(serde_json::from_str::<ArtifactSchemaId>(r#""trade.private_terms.latest""#).is_err()); 683 assert!(serde_json::from_str::<PrivateArtifactRevision>("0").is_err()); 684 assert!( 685 serde_json::from_str::<RetentionPolicy>( 686 r#"{"delete_not_before_unix_ms":0,"expires_at_unix_ms":null}"#, 687 ) 688 .is_err() 689 ); 690 691 let mut forged = serde_json::to_value(&value).expect("metadata value"); 692 forged["revision"] = serde_json::json!(2); 693 assert!(serde_json::from_value::<PrivateArtifactMetadata>(forged).is_err()); 694 }