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}