this repo has no description
1use bspds::validation::{ 2 RecordValidator, ValidationError, ValidationStatus, validate_collection_nsid, 3 validate_record_key, 4}; 5use serde_json::json; 6 7fn now() -> String { 8 chrono::Utc::now().to_rfc3339() 9} 10 11#[test] 12fn test_validate_post_valid() { 13 let validator = RecordValidator::new(); 14 let post = json!({ 15 "$type": "app.bsky.feed.post", 16 "text": "Hello world!", 17 "createdAt": now() 18 }); 19 let result = validator.validate(&post, "app.bsky.feed.post"); 20 assert_eq!(result.unwrap(), ValidationStatus::Valid); 21} 22 23#[test] 24fn test_validate_post_missing_text() { 25 let validator = RecordValidator::new(); 26 let post = json!({ 27 "$type": "app.bsky.feed.post", 28 "createdAt": now() 29 }); 30 let result = validator.validate(&post, "app.bsky.feed.post"); 31 assert!(matches!(result, Err(ValidationError::MissingField(f)) if f == "text")); 32} 33 34#[test] 35fn test_validate_post_missing_created_at() { 36 let validator = RecordValidator::new(); 37 let post = json!({ 38 "$type": "app.bsky.feed.post", 39 "text": "Hello" 40 }); 41 let result = validator.validate(&post, "app.bsky.feed.post"); 42 assert!(matches!(result, Err(ValidationError::MissingField(f)) if f == "createdAt")); 43} 44 45#[test] 46fn test_validate_post_text_too_long() { 47 let validator = RecordValidator::new(); 48 let long_text = "a".repeat(3001); 49 let post = json!({ 50 "$type": "app.bsky.feed.post", 51 "text": long_text, 52 "createdAt": now() 53 }); 54 let result = validator.validate(&post, "app.bsky.feed.post"); 55 assert!(matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "text")); 56} 57 58#[test] 59fn test_validate_post_text_at_limit() { 60 let validator = RecordValidator::new(); 61 let limit_text = "a".repeat(3000); 62 let post = json!({ 63 "$type": "app.bsky.feed.post", 64 "text": limit_text, 65 "createdAt": now() 66 }); 67 let result = validator.validate(&post, "app.bsky.feed.post"); 68 assert_eq!(result.unwrap(), ValidationStatus::Valid); 69} 70 71#[test] 72fn test_validate_post_too_many_langs() { 73 let validator = RecordValidator::new(); 74 let post = json!({ 75 "$type": "app.bsky.feed.post", 76 "text": "Hello", 77 "createdAt": now(), 78 "langs": ["en", "fr", "de", "es"] 79 }); 80 let result = validator.validate(&post, "app.bsky.feed.post"); 81 assert!(matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "langs")); 82} 83 84#[test] 85fn test_validate_post_three_langs_ok() { 86 let validator = RecordValidator::new(); 87 let post = json!({ 88 "$type": "app.bsky.feed.post", 89 "text": "Hello", 90 "createdAt": now(), 91 "langs": ["en", "fr", "de"] 92 }); 93 let result = validator.validate(&post, "app.bsky.feed.post"); 94 assert_eq!(result.unwrap(), ValidationStatus::Valid); 95} 96 97#[test] 98fn test_validate_post_too_many_tags() { 99 let validator = RecordValidator::new(); 100 let post = json!({ 101 "$type": "app.bsky.feed.post", 102 "text": "Hello", 103 "createdAt": now(), 104 "tags": ["tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9"] 105 }); 106 let result = validator.validate(&post, "app.bsky.feed.post"); 107 assert!(matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "tags")); 108} 109 110#[test] 111fn test_validate_post_eight_tags_ok() { 112 let validator = RecordValidator::new(); 113 let post = json!({ 114 "$type": "app.bsky.feed.post", 115 "text": "Hello", 116 "createdAt": now(), 117 "tags": ["tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8"] 118 }); 119 let result = validator.validate(&post, "app.bsky.feed.post"); 120 assert_eq!(result.unwrap(), ValidationStatus::Valid); 121} 122 123#[test] 124fn test_validate_post_tag_too_long() { 125 let validator = RecordValidator::new(); 126 let long_tag = "t".repeat(641); 127 let post = json!({ 128 "$type": "app.bsky.feed.post", 129 "text": "Hello", 130 "createdAt": now(), 131 "tags": [long_tag] 132 }); 133 let result = validator.validate(&post, "app.bsky.feed.post"); 134 assert!( 135 matches!(result, Err(ValidationError::InvalidField { path, .. }) if path.starts_with("tags/")) 136 ); 137} 138 139#[test] 140fn test_validate_profile_valid() { 141 let validator = RecordValidator::new(); 142 let profile = json!({ 143 "$type": "app.bsky.actor.profile", 144 "displayName": "Test User", 145 "description": "A test user profile" 146 }); 147 let result = validator.validate(&profile, "app.bsky.actor.profile"); 148 assert_eq!(result.unwrap(), ValidationStatus::Valid); 149} 150 151#[test] 152fn test_validate_profile_empty_ok() { 153 let validator = RecordValidator::new(); 154 let profile = json!({ 155 "$type": "app.bsky.actor.profile" 156 }); 157 let result = validator.validate(&profile, "app.bsky.actor.profile"); 158 assert_eq!(result.unwrap(), ValidationStatus::Valid); 159} 160 161#[test] 162fn test_validate_profile_displayname_too_long() { 163 let validator = RecordValidator::new(); 164 let long_name = "n".repeat(641); 165 let profile = json!({ 166 "$type": "app.bsky.actor.profile", 167 "displayName": long_name 168 }); 169 let result = validator.validate(&profile, "app.bsky.actor.profile"); 170 assert!( 171 matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "displayName") 172 ); 173} 174 175#[test] 176fn test_validate_profile_description_too_long() { 177 let validator = RecordValidator::new(); 178 let long_desc = "d".repeat(2561); 179 let profile = json!({ 180 "$type": "app.bsky.actor.profile", 181 "description": long_desc 182 }); 183 let result = validator.validate(&profile, "app.bsky.actor.profile"); 184 assert!( 185 matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "description") 186 ); 187} 188 189#[test] 190fn test_validate_like_valid() { 191 let validator = RecordValidator::new(); 192 let like = json!({ 193 "$type": "app.bsky.feed.like", 194 "subject": { 195 "uri": "at://did:plc:test/app.bsky.feed.post/123", 196 "cid": "bafyreig6xxxxxyyyyyzzzzzz" 197 }, 198 "createdAt": now() 199 }); 200 let result = validator.validate(&like, "app.bsky.feed.like"); 201 assert_eq!(result.unwrap(), ValidationStatus::Valid); 202} 203 204#[test] 205fn test_validate_like_missing_subject() { 206 let validator = RecordValidator::new(); 207 let like = json!({ 208 "$type": "app.bsky.feed.like", 209 "createdAt": now() 210 }); 211 let result = validator.validate(&like, "app.bsky.feed.like"); 212 assert!(matches!(result, Err(ValidationError::MissingField(f)) if f == "subject")); 213} 214 215#[test] 216fn test_validate_like_missing_subject_uri() { 217 let validator = RecordValidator::new(); 218 let like = json!({ 219 "$type": "app.bsky.feed.like", 220 "subject": { 221 "cid": "bafyreig6xxxxxyyyyyzzzzzz" 222 }, 223 "createdAt": now() 224 }); 225 let result = validator.validate(&like, "app.bsky.feed.like"); 226 assert!(matches!(result, Err(ValidationError::MissingField(f)) if f.contains("uri"))); 227} 228 229#[test] 230fn test_validate_like_invalid_subject_uri() { 231 let validator = RecordValidator::new(); 232 let like = json!({ 233 "$type": "app.bsky.feed.like", 234 "subject": { 235 "uri": "https://example.com/not-at-uri", 236 "cid": "bafyreig6xxxxxyyyyyzzzzzz" 237 }, 238 "createdAt": now() 239 }); 240 let result = validator.validate(&like, "app.bsky.feed.like"); 241 assert!( 242 matches!(result, Err(ValidationError::InvalidField { path, .. }) if path.contains("uri")) 243 ); 244} 245 246#[test] 247fn test_validate_repost_valid() { 248 let validator = RecordValidator::new(); 249 let repost = json!({ 250 "$type": "app.bsky.feed.repost", 251 "subject": { 252 "uri": "at://did:plc:test/app.bsky.feed.post/123", 253 "cid": "bafyreig6xxxxxyyyyyzzzzzz" 254 }, 255 "createdAt": now() 256 }); 257 let result = validator.validate(&repost, "app.bsky.feed.repost"); 258 assert_eq!(result.unwrap(), ValidationStatus::Valid); 259} 260 261#[test] 262fn test_validate_repost_missing_subject() { 263 let validator = RecordValidator::new(); 264 let repost = json!({ 265 "$type": "app.bsky.feed.repost", 266 "createdAt": now() 267 }); 268 let result = validator.validate(&repost, "app.bsky.feed.repost"); 269 assert!(matches!(result, Err(ValidationError::MissingField(f)) if f == "subject")); 270} 271 272#[test] 273fn test_validate_follow_valid() { 274 let validator = RecordValidator::new(); 275 let follow = json!({ 276 "$type": "app.bsky.graph.follow", 277 "subject": "did:plc:test12345", 278 "createdAt": now() 279 }); 280 let result = validator.validate(&follow, "app.bsky.graph.follow"); 281 assert_eq!(result.unwrap(), ValidationStatus::Valid); 282} 283 284#[test] 285fn test_validate_follow_missing_subject() { 286 let validator = RecordValidator::new(); 287 let follow = json!({ 288 "$type": "app.bsky.graph.follow", 289 "createdAt": now() 290 }); 291 let result = validator.validate(&follow, "app.bsky.graph.follow"); 292 assert!(matches!(result, Err(ValidationError::MissingField(f)) if f == "subject")); 293} 294 295#[test] 296fn test_validate_follow_invalid_subject() { 297 let validator = RecordValidator::new(); 298 let follow = json!({ 299 "$type": "app.bsky.graph.follow", 300 "subject": "not-a-did", 301 "createdAt": now() 302 }); 303 let result = validator.validate(&follow, "app.bsky.graph.follow"); 304 assert!(matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "subject")); 305} 306 307#[test] 308fn test_validate_block_valid() { 309 let validator = RecordValidator::new(); 310 let block = json!({ 311 "$type": "app.bsky.graph.block", 312 "subject": "did:plc:blocked123", 313 "createdAt": now() 314 }); 315 let result = validator.validate(&block, "app.bsky.graph.block"); 316 assert_eq!(result.unwrap(), ValidationStatus::Valid); 317} 318 319#[test] 320fn test_validate_block_invalid_subject() { 321 let validator = RecordValidator::new(); 322 let block = json!({ 323 "$type": "app.bsky.graph.block", 324 "subject": "not-a-did", 325 "createdAt": now() 326 }); 327 let result = validator.validate(&block, "app.bsky.graph.block"); 328 assert!(matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "subject")); 329} 330 331#[test] 332fn test_validate_list_valid() { 333 let validator = RecordValidator::new(); 334 let list = json!({ 335 "$type": "app.bsky.graph.list", 336 "name": "My List", 337 "purpose": "app.bsky.graph.defs#modlist", 338 "createdAt": now() 339 }); 340 let result = validator.validate(&list, "app.bsky.graph.list"); 341 assert_eq!(result.unwrap(), ValidationStatus::Valid); 342} 343 344#[test] 345fn test_validate_list_name_too_long() { 346 let validator = RecordValidator::new(); 347 let long_name = "n".repeat(65); 348 let list = json!({ 349 "$type": "app.bsky.graph.list", 350 "name": long_name, 351 "purpose": "app.bsky.graph.defs#modlist", 352 "createdAt": now() 353 }); 354 let result = validator.validate(&list, "app.bsky.graph.list"); 355 assert!(matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "name")); 356} 357 358#[test] 359fn test_validate_list_empty_name() { 360 let validator = RecordValidator::new(); 361 let list = json!({ 362 "$type": "app.bsky.graph.list", 363 "name": "", 364 "purpose": "app.bsky.graph.defs#modlist", 365 "createdAt": now() 366 }); 367 let result = validator.validate(&list, "app.bsky.graph.list"); 368 assert!(matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "name")); 369} 370 371#[test] 372fn test_validate_feed_generator_valid() { 373 let validator = RecordValidator::new(); 374 let generator = json!({ 375 "$type": "app.bsky.feed.generator", 376 "did": "did:web:example.com", 377 "displayName": "My Feed", 378 "createdAt": now() 379 }); 380 let result = validator.validate(&generator, "app.bsky.feed.generator"); 381 assert_eq!(result.unwrap(), ValidationStatus::Valid); 382} 383 384#[test] 385fn test_validate_feed_generator_displayname_too_long() { 386 let validator = RecordValidator::new(); 387 let long_name = "f".repeat(241); 388 let generator = json!({ 389 "$type": "app.bsky.feed.generator", 390 "did": "did:web:example.com", 391 "displayName": long_name, 392 "createdAt": now() 393 }); 394 let result = validator.validate(&generator, "app.bsky.feed.generator"); 395 assert!( 396 matches!(result, Err(ValidationError::InvalidField { path, .. }) if path == "displayName") 397 ); 398} 399 400#[test] 401fn test_validate_unknown_type_returns_unknown() { 402 let validator = RecordValidator::new(); 403 let custom = json!({ 404 "$type": "com.custom.record", 405 "data": "test" 406 }); 407 let result = validator.validate(&custom, "com.custom.record"); 408 assert_eq!(result.unwrap(), ValidationStatus::Unknown); 409} 410 411#[test] 412fn test_validate_unknown_type_strict_rejects() { 413 let validator = RecordValidator::new().require_lexicon(true); 414 let custom = json!({ 415 "$type": "com.custom.record", 416 "data": "test" 417 }); 418 let result = validator.validate(&custom, "com.custom.record"); 419 assert!(matches!(result, Err(ValidationError::UnknownType(_)))); 420} 421 422#[test] 423fn test_validate_type_mismatch() { 424 let validator = RecordValidator::new(); 425 let record = json!({ 426 "$type": "app.bsky.feed.like", 427 "subject": {"uri": "at://test", "cid": "bafytest"}, 428 "createdAt": now() 429 }); 430 let result = validator.validate(&record, "app.bsky.feed.post"); 431 assert!( 432 matches!(result, Err(ValidationError::TypeMismatch { expected, actual }) 433 if expected == "app.bsky.feed.post" && actual == "app.bsky.feed.like") 434 ); 435} 436 437#[test] 438fn test_validate_missing_type() { 439 let validator = RecordValidator::new(); 440 let record = json!({ 441 "text": "Hello" 442 }); 443 let result = validator.validate(&record, "app.bsky.feed.post"); 444 assert!(matches!(result, Err(ValidationError::MissingType))); 445} 446 447#[test] 448fn test_validate_not_object() { 449 let validator = RecordValidator::new(); 450 let record = json!("just a string"); 451 let result = validator.validate(&record, "app.bsky.feed.post"); 452 assert!(matches!(result, Err(ValidationError::InvalidRecord(_)))); 453} 454 455#[test] 456fn test_validate_datetime_format_valid() { 457 let validator = RecordValidator::new(); 458 let post = json!({ 459 "$type": "app.bsky.feed.post", 460 "text": "Test", 461 "createdAt": "2024-01-15T10:30:00.000Z" 462 }); 463 let result = validator.validate(&post, "app.bsky.feed.post"); 464 assert_eq!(result.unwrap(), ValidationStatus::Valid); 465} 466 467#[test] 468fn test_validate_datetime_with_offset() { 469 let validator = RecordValidator::new(); 470 let post = json!({ 471 "$type": "app.bsky.feed.post", 472 "text": "Test", 473 "createdAt": "2024-01-15T10:30:00+05:30" 474 }); 475 let result = validator.validate(&post, "app.bsky.feed.post"); 476 assert_eq!(result.unwrap(), ValidationStatus::Valid); 477} 478 479#[test] 480fn test_validate_datetime_invalid_format() { 481 let validator = RecordValidator::new(); 482 let post = json!({ 483 "$type": "app.bsky.feed.post", 484 "text": "Test", 485 "createdAt": "2024/01/15" 486 }); 487 let result = validator.validate(&post, "app.bsky.feed.post"); 488 assert!(matches!( 489 result, 490 Err(ValidationError::InvalidDatetime { .. }) 491 )); 492} 493 494#[test] 495fn test_validate_record_key_valid() { 496 assert!(validate_record_key("3k2n5j2").is_ok()); 497 assert!(validate_record_key("valid-key").is_ok()); 498 assert!(validate_record_key("valid_key").is_ok()); 499 assert!(validate_record_key("valid.key").is_ok()); 500 assert!(validate_record_key("valid~key").is_ok()); 501 assert!(validate_record_key("self").is_ok()); 502} 503 504#[test] 505fn test_validate_record_key_empty() { 506 let result = validate_record_key(""); 507 assert!(matches!(result, Err(ValidationError::InvalidRecord(_)))); 508} 509 510#[test] 511fn test_validate_record_key_dot() { 512 assert!(validate_record_key(".").is_err()); 513 assert!(validate_record_key("..").is_err()); 514} 515 516#[test] 517fn test_validate_record_key_invalid_chars() { 518 assert!(validate_record_key("invalid/key").is_err()); 519 assert!(validate_record_key("invalid key").is_err()); 520 assert!(validate_record_key("invalid@key").is_err()); 521 assert!(validate_record_key("invalid#key").is_err()); 522} 523 524#[test] 525fn test_validate_record_key_too_long() { 526 let long_key = "k".repeat(513); 527 let result = validate_record_key(&long_key); 528 assert!(matches!(result, Err(ValidationError::InvalidRecord(_)))); 529} 530 531#[test] 532fn test_validate_record_key_at_max_length() { 533 let max_key = "k".repeat(512); 534 assert!(validate_record_key(&max_key).is_ok()); 535} 536 537#[test] 538fn test_validate_collection_nsid_valid() { 539 assert!(validate_collection_nsid("app.bsky.feed.post").is_ok()); 540 assert!(validate_collection_nsid("com.atproto.repo.record").is_ok()); 541 assert!(validate_collection_nsid("a.b.c").is_ok()); 542 assert!(validate_collection_nsid("my-app.domain.record-type").is_ok()); 543} 544 545#[test] 546fn test_validate_collection_nsid_empty() { 547 let result = validate_collection_nsid(""); 548 assert!(matches!(result, Err(ValidationError::InvalidRecord(_)))); 549} 550 551#[test] 552fn test_validate_collection_nsid_too_few_segments() { 553 assert!(validate_collection_nsid("a").is_err()); 554 assert!(validate_collection_nsid("a.b").is_err()); 555} 556 557#[test] 558fn test_validate_collection_nsid_empty_segment() { 559 assert!(validate_collection_nsid("a..b.c").is_err()); 560 assert!(validate_collection_nsid(".a.b.c").is_err()); 561 assert!(validate_collection_nsid("a.b.c.").is_err()); 562} 563 564#[test] 565fn test_validate_collection_nsid_invalid_chars() { 566 assert!(validate_collection_nsid("a.b.c/d").is_err()); 567 assert!(validate_collection_nsid("a.b.c_d").is_err()); 568 assert!(validate_collection_nsid("a.b.c@d").is_err()); 569} 570 571#[test] 572fn test_validate_threadgate() { 573 let validator = RecordValidator::new(); 574 let gate = json!({ 575 "$type": "app.bsky.feed.threadgate", 576 "post": "at://did:plc:test/app.bsky.feed.post/123", 577 "createdAt": now() 578 }); 579 let result = validator.validate(&gate, "app.bsky.feed.threadgate"); 580 assert_eq!(result.unwrap(), ValidationStatus::Valid); 581} 582 583#[test] 584fn test_validate_labeler_service() { 585 let validator = RecordValidator::new(); 586 let labeler = json!({ 587 "$type": "app.bsky.labeler.service", 588 "policies": { 589 "labelValues": ["spam", "nsfw"] 590 }, 591 "createdAt": now() 592 }); 593 let result = validator.validate(&labeler, "app.bsky.labeler.service"); 594 assert_eq!(result.unwrap(), ValidationStatus::Valid); 595} 596 597#[test] 598fn test_validate_list_item() { 599 let validator = RecordValidator::new(); 600 let item = json!({ 601 "$type": "app.bsky.graph.listitem", 602 "subject": "did:plc:test123", 603 "list": "at://did:plc:owner/app.bsky.graph.list/mylist", 604 "createdAt": now() 605 }); 606 let result = validator.validate(&item, "app.bsky.graph.listitem"); 607 assert_eq!(result.unwrap(), ValidationStatus::Valid); 608}