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