this repo has no description
1mod common;
2
3use common::{base_url, client};
4use reqwest::StatusCode;
5use serde_json::json;
6
7#[tokio::test]
8async fn test_login_rate_limiting() {
9 let client = client();
10 let url = format!("{}/xrpc/com.atproto.server.createSession", base_url().await);
11
12 let payload = json!({
13 "identifier": "nonexistent_user_for_rate_limit_test",
14 "password": "wrongpassword"
15 });
16
17 let mut rate_limited_count = 0;
18 let mut auth_failed_count = 0;
19
20 for _ in 0..15 {
21 let res = client
22 .post(&url)
23 .json(&payload)
24 .send()
25 .await
26 .expect("Request failed");
27
28 match res.status() {
29 StatusCode::TOO_MANY_REQUESTS => {
30 rate_limited_count += 1;
31 }
32 StatusCode::UNAUTHORIZED => {
33 auth_failed_count += 1;
34 }
35 status => {
36 panic!("Unexpected status: {}", status);
37 }
38 }
39 }
40
41 assert!(
42 rate_limited_count > 0,
43 "Expected at least one rate-limited response after 15 login attempts. Got {} auth failures and {} rate limits.",
44 auth_failed_count,
45 rate_limited_count
46 );
47}
48
49#[tokio::test]
50async fn test_password_reset_rate_limiting() {
51 let client = client();
52 let url = format!(
53 "{}/xrpc/com.atproto.server.requestPasswordReset",
54 base_url().await
55 );
56
57 let mut rate_limited_count = 0;
58 let mut success_count = 0;
59
60 for i in 0..8 {
61 let payload = json!({
62 "email": format!("ratelimit_test_{}@example.com", i)
63 });
64
65 let res = client
66 .post(&url)
67 .json(&payload)
68 .send()
69 .await
70 .expect("Request failed");
71
72 match res.status() {
73 StatusCode::TOO_MANY_REQUESTS => {
74 rate_limited_count += 1;
75 }
76 StatusCode::OK => {
77 success_count += 1;
78 }
79 status => {
80 panic!("Unexpected status: {} - {:?}", status, res.text().await);
81 }
82 }
83 }
84
85 assert!(
86 rate_limited_count > 0,
87 "Expected rate limiting after {} password reset requests. Got {} successes.",
88 success_count + rate_limited_count,
89 success_count
90 );
91}
92
93#[tokio::test]
94async fn test_account_creation_rate_limiting() {
95 let client = client();
96 let url = format!(
97 "{}/xrpc/com.atproto.server.createAccount",
98 base_url().await
99 );
100
101 let mut rate_limited_count = 0;
102 let mut other_count = 0;
103
104 for i in 0..15 {
105 let unique_id = uuid::Uuid::new_v4();
106 let payload = json!({
107 "handle": format!("ratelimit_{}_{}", i, unique_id),
108 "email": format!("ratelimit_{}_{}@example.com", i, unique_id),
109 "password": "testpassword123"
110 });
111
112 let res = client
113 .post(&url)
114 .json(&payload)
115 .send()
116 .await
117 .expect("Request failed");
118
119 match res.status() {
120 StatusCode::TOO_MANY_REQUESTS => {
121 rate_limited_count += 1;
122 }
123 _ => {
124 other_count += 1;
125 }
126 }
127 }
128
129 assert!(
130 rate_limited_count > 0,
131 "Expected rate limiting after account creation attempts. Got {} other responses and {} rate limits.",
132 other_count,
133 rate_limited_count
134 );
135}
136
137#[tokio::test]
138async fn test_valkey_connection() {
139 if std::env::var("VALKEY_URL").is_err() {
140 println!("VALKEY_URL not set, skipping Valkey connection test");
141 return;
142 }
143
144 let valkey_url = std::env::var("VALKEY_URL").unwrap();
145 let client = redis::Client::open(valkey_url.as_str()).expect("Failed to create Redis client");
146 let mut conn = client
147 .get_multiplexed_async_connection()
148 .await
149 .expect("Failed to connect to Valkey");
150
151 let pong: String = redis::cmd("PING")
152 .query_async(&mut conn)
153 .await
154 .expect("PING failed");
155 assert_eq!(pong, "PONG");
156
157 let _: () = redis::cmd("SET")
158 .arg("test_key")
159 .arg("test_value")
160 .arg("EX")
161 .arg(10)
162 .query_async(&mut conn)
163 .await
164 .expect("SET failed");
165
166 let value: String = redis::cmd("GET")
167 .arg("test_key")
168 .query_async(&mut conn)
169 .await
170 .expect("GET failed");
171 assert_eq!(value, "test_value");
172
173 let _: () = redis::cmd("DEL")
174 .arg("test_key")
175 .query_async(&mut conn)
176 .await
177 .expect("DEL failed");
178}
179
180#[tokio::test]
181async fn test_distributed_rate_limiter_directly() {
182 if std::env::var("VALKEY_URL").is_err() {
183 println!("VALKEY_URL not set, skipping distributed rate limiter test");
184 return;
185 }
186
187 use bspds::cache::{DistributedRateLimiter, RedisRateLimiter};
188
189 let valkey_url = std::env::var("VALKEY_URL").unwrap();
190 let client = redis::Client::open(valkey_url.as_str()).expect("Failed to create Redis client");
191 let conn = client
192 .get_connection_manager()
193 .await
194 .expect("Failed to get connection manager");
195
196 let rate_limiter = RedisRateLimiter::new(conn);
197
198 let test_key = format!("test_rate_limit_{}", uuid::Uuid::new_v4());
199 let limit = 5;
200 let window_ms = 60_000;
201
202 for i in 0..limit {
203 let allowed = rate_limiter
204 .check_rate_limit(&test_key, limit, window_ms)
205 .await;
206 assert!(
207 allowed,
208 "Request {} should have been allowed (limit: {})",
209 i + 1,
210 limit
211 );
212 }
213
214 let allowed = rate_limiter
215 .check_rate_limit(&test_key, limit, window_ms)
216 .await;
217 assert!(
218 !allowed,
219 "Request {} should have been rate limited (limit: {})",
220 limit + 1,
221 limit
222 );
223
224 let allowed = rate_limiter
225 .check_rate_limit(&test_key, limit, window_ms)
226 .await;
227 assert!(!allowed, "Subsequent request should also be rate limited");
228}