forked from
lewis.moe/bspds-sandbox
I've been saying "PDSes seem easy enough, they're what, some CRUD to a db? I can do that in my sleep". well i'm sleeping rn so let's go
1use tranquil_pds::api::validation::{
2 HandleValidationError, MAX_DOMAIN_LABEL_LENGTH, MAX_EMAIL_LENGTH, MAX_LOCAL_PART_LENGTH,
3 MAX_SERVICE_HANDLE_LOCAL_PART, is_valid_email, validate_short_handle,
4};
5use tranquil_pds::validation::{
6 is_valid_did, validate_collection_nsid, validate_password, validate_record_key,
7};
8
9#[test]
10fn test_record_key_boundary_min() {
11 assert!(validate_record_key("a").is_ok());
12 assert!(validate_record_key("1").is_ok());
13 assert!(validate_record_key("-").is_ok());
14 assert!(validate_record_key("_").is_ok());
15 assert!(validate_record_key("~").is_ok());
16}
17
18#[test]
19fn test_record_key_boundary_max() {
20 assert!(validate_record_key(&"a".repeat(512)).is_ok());
21 assert!(validate_record_key(&"a".repeat(513)).is_err());
22 assert!(validate_record_key(&"a".repeat(1000)).is_err());
23}
24
25#[test]
26fn test_record_key_special_dot_cases() {
27 assert!(validate_record_key(".").is_err());
28 assert!(validate_record_key("..").is_err());
29 assert!(validate_record_key("...").is_ok());
30 assert!(validate_record_key("a.b").is_ok());
31 assert!(validate_record_key(".a").is_ok());
32 assert!(validate_record_key("a.").is_ok());
33 assert!(validate_record_key("a..b").is_ok());
34}
35
36#[test]
37fn test_record_key_all_valid_chars() {
38 assert!(validate_record_key("abc").is_ok());
39 assert!(validate_record_key("ABC").is_ok());
40 assert!(validate_record_key("123").is_ok());
41 assert!(validate_record_key("a-b").is_ok());
42 assert!(validate_record_key("a_b").is_ok());
43 assert!(validate_record_key("a~b").is_ok());
44 assert!(validate_record_key("a.b").is_ok());
45 assert!(validate_record_key("aA1-_.~").is_ok());
46}
47
48#[test]
49fn test_record_key_invalid_chars() {
50 assert!(validate_record_key("a/b").is_err());
51 assert!(validate_record_key("a\\b").is_err());
52 assert!(validate_record_key("a b").is_err());
53 assert!(validate_record_key("a@b").is_err());
54 assert!(validate_record_key("a#b").is_err());
55 assert!(validate_record_key("a$b").is_err());
56 assert!(validate_record_key("a%b").is_err());
57 assert!(validate_record_key("a&b").is_err());
58 assert!(validate_record_key("a*b").is_err());
59 assert!(validate_record_key("a+b").is_err());
60 assert!(validate_record_key("a=b").is_err());
61 assert!(validate_record_key("a?b").is_err());
62 assert!(validate_record_key("a:b").is_err());
63 assert!(validate_record_key("a;b").is_err());
64 assert!(validate_record_key("a<b").is_err());
65 assert!(validate_record_key("a>b").is_err());
66 assert!(validate_record_key("a[b").is_err());
67 assert!(validate_record_key("a]b").is_err());
68 assert!(validate_record_key("a{b").is_err());
69 assert!(validate_record_key("a}b").is_err());
70 assert!(validate_record_key("a|b").is_err());
71 assert!(validate_record_key("a`b").is_err());
72 assert!(validate_record_key("a'b").is_err());
73 assert!(validate_record_key("a\"b").is_err());
74 assert!(validate_record_key("a\nb").is_err());
75 assert!(validate_record_key("a\tb").is_err());
76 assert!(validate_record_key("a\rb").is_err());
77 assert!(validate_record_key("a\0b").is_err());
78}
79
80#[test]
81fn test_record_key_unicode() {
82 assert!(validate_record_key("café").is_err());
83 assert!(validate_record_key("日本語").is_err());
84 assert!(validate_record_key("emoji😀").is_err());
85}
86
87#[test]
88fn test_password_length_boundaries() {
89 let base_valid = "Aa1";
90
91 let pass_7 = format!("{}{}", base_valid, "x".repeat(4));
92 assert!(validate_password(&pass_7).is_err());
93
94 let pass_8 = format!("{}{}", base_valid, "x".repeat(5));
95 assert!(validate_password(&pass_8).is_ok());
96
97 let pass_256 = format!("{}{}", base_valid, "x".repeat(253));
98 assert!(validate_password(&pass_256).is_ok());
99
100 let pass_257 = format!("{}{}", base_valid, "x".repeat(254));
101 assert!(validate_password(&pass_257).is_err());
102}
103
104#[test]
105fn test_password_missing_requirements() {
106 assert!(validate_password("abcdefgh").is_err());
107 assert!(validate_password("ABCDEFGH").is_err());
108 assert!(validate_password("12345678").is_err());
109
110 assert!(validate_password("abcd1234").is_err());
111 assert!(validate_password("ABCD1234").is_err());
112 assert!(validate_password("abcdABCD").is_err());
113
114 assert!(validate_password("aB1xxxxx").is_ok());
115}
116
117#[test]
118fn test_password_common_passwords() {
119 assert!(validate_password("Password1").is_err());
120 assert!(validate_password("PASSWORD1").is_err());
121 assert!(validate_password("password1").is_err());
122 assert!(validate_password("Qwerty123").is_err());
123 assert!(validate_password("Bluesky123").is_err());
124}
125
126#[test]
127fn test_password_special_chars_allowed() {
128 assert!(validate_password("Aa1!@#$%").is_ok());
129 assert!(validate_password("Aa1^&*()").is_ok());
130 assert!(validate_password("Aa1 space").is_ok());
131}
132
133#[test]
134fn test_did_validation_basic() {
135 assert!(is_valid_did("did:plc:abc123"));
136 assert!(is_valid_did("did:web:example.com"));
137 assert!(is_valid_did(
138 "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
139 ));
140}
141
142#[test]
143fn test_did_validation_invalid() {
144 assert!(!is_valid_did(""));
145 assert!(!is_valid_did("did"));
146 assert!(!is_valid_did("did:"));
147 assert!(!is_valid_did("did::"));
148 assert!(!is_valid_did("did:plc"));
149 assert!(!is_valid_did("did:plc:"));
150 assert!(!is_valid_did(":plc:abc"));
151 assert!(!is_valid_did("plc:abc"));
152}
153
154#[test]
155fn test_did_validation_method_case() {
156 assert!(!is_valid_did("did:PLC:abc123"));
157 assert!(!is_valid_did("did:Plc:abc123"));
158 assert!(!is_valid_did("DID:plc:abc123"));
159}
160
161#[test]
162fn test_did_validation_method_chars() {
163 assert!(!is_valid_did("did:plc1:abc"));
164 assert!(!is_valid_did("did:plc-x:abc"));
165 assert!(!is_valid_did("did:plc_x:abc"));
166}
167
168#[test]
169fn test_collection_nsid_minimum_segments() {
170 assert!(validate_collection_nsid("a.b.c").is_ok());
171 assert!(validate_collection_nsid("a.b").is_err());
172 assert!(validate_collection_nsid("a").is_err());
173 assert!(validate_collection_nsid("").is_err());
174}
175
176#[test]
177fn test_collection_nsid_many_segments() {
178 assert!(validate_collection_nsid("a.b.c.d.e.f.g.h.i.j").is_ok());
179}
180
181#[test]
182fn test_collection_nsid_empty_segments() {
183 assert!(validate_collection_nsid("a..b.c").is_err());
184 assert!(validate_collection_nsid(".a.b.c").is_err());
185 assert!(validate_collection_nsid("a.b.c.").is_err());
186 assert!(validate_collection_nsid("a.b..c").is_err());
187}
188
189#[test]
190fn test_collection_nsid_valid_chars() {
191 assert!(validate_collection_nsid("app.bsky.feed.post").is_ok());
192 assert!(validate_collection_nsid("com.example.my-record").is_ok());
193 assert!(validate_collection_nsid("app.example.record123").is_ok());
194 assert!(validate_collection_nsid("APP.BSKY.FEED.POST").is_ok());
195}
196
197#[test]
198fn test_collection_nsid_invalid_chars() {
199 assert!(validate_collection_nsid("app.bsky.feed_post").is_err());
200 assert!(validate_collection_nsid("app.bsky.feed/post").is_err());
201 assert!(validate_collection_nsid("app.bsky.feed:post").is_err());
202 assert!(validate_collection_nsid("app.bsky.feed@post").is_err());
203}
204
205#[test]
206fn test_handle_boundary_lengths() {
207 let min_handle = "abc";
208 assert!(validate_short_handle(min_handle).is_ok());
209
210 let under_min = "ab";
211 assert!(matches!(
212 validate_short_handle(under_min),
213 Err(HandleValidationError::TooShort)
214 ));
215
216 let at_max = "a".repeat(MAX_SERVICE_HANDLE_LOCAL_PART);
217 assert!(validate_short_handle(&at_max).is_ok());
218
219 let over_max = "a".repeat(MAX_SERVICE_HANDLE_LOCAL_PART + 1);
220 assert!(matches!(
221 validate_short_handle(&over_max),
222 Err(HandleValidationError::TooLong)
223 ));
224}
225
226#[test]
227fn test_handle_hyphen_positions() {
228 assert!(validate_short_handle("a-b-c").is_ok());
229 assert!(validate_short_handle("a--b").is_ok());
230 assert!(validate_short_handle("---").is_err());
231 assert!(matches!(
232 validate_short_handle("-abc"),
233 Err(HandleValidationError::StartsWithInvalidChar)
234 ));
235 assert!(matches!(
236 validate_short_handle("abc-"),
237 Err(HandleValidationError::EndsWithInvalidChar)
238 ));
239}
240
241#[test]
242fn test_handle_case_normalization() {
243 assert_eq!(validate_short_handle("ABC").unwrap(), "abc");
244 assert_eq!(validate_short_handle("AbC123").unwrap(), "abc123");
245 assert_eq!(validate_short_handle("MixedCase").unwrap(), "mixedcase");
246}
247
248#[test]
249fn test_handle_whitespace_handling() {
250 assert_eq!(validate_short_handle(" abc ").unwrap(), "abc");
251 assert!(matches!(
252 validate_short_handle("a b c"),
253 Err(HandleValidationError::ContainsSpaces)
254 ));
255 assert!(matches!(
256 validate_short_handle("a\tb"),
257 Err(HandleValidationError::ContainsSpaces)
258 ));
259 assert!(matches!(
260 validate_short_handle("a\nb"),
261 Err(HandleValidationError::ContainsSpaces)
262 ));
263}
264
265#[test]
266fn test_email_length_boundaries() {
267 let long_local = format!("{}@example.com", "a".repeat(MAX_LOCAL_PART_LENGTH));
268 assert!(is_valid_email(&long_local));
269
270 let too_long_local = format!("{}@example.com", "a".repeat(MAX_LOCAL_PART_LENGTH + 1));
271 assert!(!is_valid_email(&too_long_local));
272
273 let very_long_email = format!("a@{}.com", "a".repeat(240));
274 if very_long_email.len() <= MAX_EMAIL_LENGTH {
275 assert!(is_valid_email(&very_long_email) || !is_valid_email(&very_long_email));
276 }
277}
278
279#[test]
280fn test_email_local_part_special_chars() {
281 assert!(is_valid_email("user.name@example.com"));
282 assert!(is_valid_email("user+tag@example.com"));
283 assert!(is_valid_email("user!def@example.com"));
284 assert!(is_valid_email("user#abc@example.com"));
285 assert!(is_valid_email("user$def@example.com"));
286 assert!(is_valid_email("user%abc@example.com"));
287 assert!(is_valid_email("user&def@example.com"));
288 assert!(is_valid_email("user'abc@example.com"));
289 assert!(is_valid_email("user*def@example.com"));
290 assert!(is_valid_email("user=abc@example.com"));
291 assert!(is_valid_email("user?def@example.com"));
292 assert!(is_valid_email("user^abc@example.com"));
293 assert!(is_valid_email("user_def@example.com"));
294 assert!(is_valid_email("user`abc@example.com"));
295 assert!(is_valid_email("user{def@example.com"));
296 assert!(is_valid_email("user|abc@example.com"));
297 assert!(is_valid_email("user}def@example.com"));
298 assert!(is_valid_email("user~abc@example.com"));
299 assert!(is_valid_email("user-def@example.com"));
300}
301
302#[test]
303fn test_email_local_part_dots() {
304 assert!(!is_valid_email(".user@example.com"));
305 assert!(!is_valid_email("user.@example.com"));
306 assert!(!is_valid_email("user..name@example.com"));
307 assert!(is_valid_email("user.name@example.com"));
308 assert!(is_valid_email("u.s.e.r@example.com"));
309}
310
311#[test]
312fn test_email_domain_labels() {
313 let long_label = "a".repeat(MAX_DOMAIN_LABEL_LENGTH);
314 let valid_domain = format!("user@{}.com", long_label);
315 assert!(is_valid_email(&valid_domain));
316
317 let too_long_label = "a".repeat(MAX_DOMAIN_LABEL_LENGTH + 1);
318 let invalid_domain = format!("user@{}.com", too_long_label);
319 assert!(!is_valid_email(&invalid_domain));
320}
321
322#[test]
323fn test_email_domain_hyphens() {
324 assert!(!is_valid_email("user@-example.com"));
325 assert!(!is_valid_email("user@example-.com"));
326 assert!(is_valid_email("user@ex-ample.com"));
327 assert!(is_valid_email("user@ex--ample.com"));
328}
329
330#[test]
331fn test_email_domain_must_have_dot() {
332 assert!(!is_valid_email("user@localhost"));
333 assert!(!is_valid_email("user@example"));
334 assert!(is_valid_email("user@a.b"));
335}
336
337#[test]
338fn test_email_invalid_chars() {
339 assert!(!is_valid_email("user name@example.com"));
340 assert!(!is_valid_email("user\t@example.com"));
341 assert!(!is_valid_email("user\n@example.com"));
342 assert!(!is_valid_email("user@exam ple.com"));
343}