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