Skip to content

Commit 4887e66

Browse files
committed
test: move testing code out of src/
1 parent dbb5a1a commit 4887e66

File tree

6 files changed

+166
-283
lines changed

6 files changed

+166
-283
lines changed

src/cache.rs

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -271,49 +271,3 @@ fn hash_cacheable<T: Cacheable>(item: &T) -> u64 {
271271
item.cache_key().hash(&mut hasher);
272272
hasher.finish()
273273
}
274-
275-
#[cfg(test)]
276-
mod tests {
277-
use super::*;
278-
use tempfile::tempdir;
279-
280-
#[tokio::test]
281-
async fn test_cache_basic_functionality() -> Result<()> {
282-
let temp_dir = tempdir()
283-
.map_err(|e| CacheError::Create(anyhow::anyhow!("Failed to create temp dir: {}", e)))?;
284-
let config = CacheConfig {
285-
disk_capacity: 1024 * 1024, // 1 MB
286-
disk_path: temp_dir.path().to_string_lossy().to_string(),
287-
};
288-
289-
let cache_manager = CacheManager::new(config).await?;
290-
291-
let meaning = Meaning {
292-
owner: "test_owner".to_string(),
293-
repo: "test_repo".to_string(),
294-
theme: "dark".to_string(),
295-
};
296-
297-
// Test cache miss and creation
298-
let result = cache_manager
299-
.get_or_create(meaning.clone(), || async {
300-
Ok(b"test_image_data".to_vec())
301-
})
302-
.await?;
303-
304-
assert_eq!(result.image_data, b"test_image_data");
305-
assert_eq!(result.meaning, meaning);
306-
assert_eq!(result.access_count, 1);
307-
308-
// Test cache hit
309-
let result2 = cache_manager
310-
.get_or_create(meaning, || async {
311-
panic!("This should not be called on cache hit");
312-
})
313-
.await?;
314-
315-
assert_eq!(result2.image_data, b"test_image_data");
316-
317-
Ok(())
318-
}
319-
}

src/ratelimit.rs

Lines changed: 0 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -12,61 +12,6 @@ use tokio::sync::RwLock;
1212
use tokio::time::interval;
1313
use tracing::{debug, warn};
1414

15-
/// Time provider trait for mocking in tests
16-
#[cfg(test)]
17-
pub trait TimeProvider {
18-
fn now(&self) -> Instant;
19-
fn advance(&mut self, duration: Duration);
20-
}
21-
22-
/// Real time provider for production
23-
#[cfg(test)]
24-
pub struct RealTimeProvider;
25-
26-
#[cfg(test)]
27-
impl TimeProvider for RealTimeProvider {
28-
fn now(&self) -> Instant {
29-
Instant::now()
30-
}
31-
32-
fn advance(&mut self, _duration: Duration) {
33-
// No-op for real time
34-
}
35-
}
36-
37-
/// Mock time provider for tests
38-
#[cfg(test)]
39-
pub struct MockTimeProvider {
40-
current_time: Instant,
41-
}
42-
43-
#[cfg(test)]
44-
impl MockTimeProvider {
45-
pub fn new() -> Self {
46-
Self {
47-
current_time: Instant::now(),
48-
}
49-
}
50-
}
51-
52-
#[cfg(test)]
53-
impl Default for MockTimeProvider {
54-
fn default() -> Self {
55-
Self::new()
56-
}
57-
}
58-
59-
#[cfg(test)]
60-
impl TimeProvider for MockTimeProvider {
61-
fn now(&self) -> Instant {
62-
self.current_time
63-
}
64-
65-
fn advance(&mut self, duration: Duration) {
66-
self.current_time += duration;
67-
}
68-
}
69-
7015
/// Configuration for rate limiting
7116
#[derive(Clone, Debug)]
7217
pub struct RateLimitConfig {
@@ -92,16 +37,6 @@ impl Default for RateLimitConfig {
9237
}
9338

9439
/// Token bucket for rate limiting
95-
#[cfg(test)]
96-
pub struct TokenBucket {
97-
tokens: AtomicU32,
98-
max_tokens: u32,
99-
refill_rate: u32, // tokens per refill interval
100-
last_refill: RwLock<Instant>,
101-
time_provider: Arc<RwLock<Box<dyn TimeProvider + Send + Sync>>>,
102-
}
103-
104-
#[cfg(not(test))]
10540
struct TokenBucket {
10641
tokens: AtomicU32,
10742
max_tokens: u32,
@@ -128,7 +63,6 @@ impl std::fmt::Debug for TokenBucket {
12863
}
12964

13065
impl TokenBucket {
131-
#[cfg(not(test))]
13266
fn new(max_tokens: u32, refill_rate: u32) -> Self {
13367
Self {
13468
tokens: AtomicU32::new(max_tokens),
@@ -138,34 +72,7 @@ impl TokenBucket {
13872
}
13973
}
14074

141-
#[cfg(test)]
142-
pub fn new(max_tokens: u32, refill_rate: u32) -> Self {
143-
Self {
144-
tokens: AtomicU32::new(max_tokens),
145-
max_tokens,
146-
refill_rate,
147-
last_refill: RwLock::new(Instant::now()),
148-
time_provider: Arc::new(RwLock::new(Box::new(RealTimeProvider))),
149-
}
150-
}
151-
152-
#[cfg(test)]
153-
pub fn new_with_time_provider(
154-
max_tokens: u32,
155-
refill_rate: u32,
156-
time_provider: Box<dyn TimeProvider + Send + Sync>,
157-
) -> Self {
158-
Self {
159-
tokens: AtomicU32::new(max_tokens),
160-
max_tokens,
161-
refill_rate,
162-
last_refill: RwLock::new(Instant::now()),
163-
time_provider: Arc::new(RwLock::new(time_provider)),
164-
}
165-
}
166-
16775
/// Try to consume a token. Returns true if successful, false if rate limited.
168-
#[cfg(not(test))]
16976
async fn try_consume(&self) -> bool {
17077
self.refill().await;
17178

@@ -192,35 +99,7 @@ impl TokenBucket {
19299
}
193100
}
194101

195-
#[cfg(test)]
196-
pub async fn try_consume(&self) -> bool {
197-
self.refill().await;
198-
199-
// Use a loop instead of recursion to avoid boxing
200-
loop {
201-
let current_tokens = self.tokens.load(Ordering::Acquire);
202-
if current_tokens > 0 {
203-
// Try to decrement atomically
204-
match self.tokens.compare_exchange_weak(
205-
current_tokens,
206-
current_tokens - 1,
207-
Ordering::Release,
208-
Ordering::Relaxed,
209-
) {
210-
Ok(_) => return true,
211-
Err(_) => {
212-
// Someone else consumed the token, try again
213-
continue;
214-
}
215-
}
216-
} else {
217-
return false;
218-
}
219-
}
220-
}
221-
222102
/// Refill tokens based on elapsed time
223-
#[cfg(not(test))]
224103
async fn refill(&self) {
225104
let now = Instant::now();
226105

@@ -248,51 +127,10 @@ impl TokenBucket {
248127
}
249128
}
250129

251-
#[cfg(test)]
252-
pub async fn refill(&self) {
253-
#[cfg(test)]
254-
let now = {
255-
let time_provider = self.time_provider.read().await;
256-
time_provider.now()
257-
};
258-
#[cfg(not(test))]
259-
let now = Instant::now();
260-
261-
let mut last_refill = self.last_refill.write().await;
262-
263-
let elapsed = now.duration_since(*last_refill);
264-
if elapsed >= Duration::from_secs(1) {
265-
let seconds_passed = elapsed.as_secs() as u32;
266-
let tokens_to_add = seconds_passed * self.refill_rate;
267-
268-
if tokens_to_add > 0 {
269-
let current_tokens = self.tokens.load(Ordering::Acquire);
270-
let new_tokens = (current_tokens + tokens_to_add).min(self.max_tokens);
271-
self.tokens.store(new_tokens, Ordering::Release);
272-
*last_refill = now;
273-
274-
// Only log if we actually added tokens and it's significant
275-
if tokens_to_add > 0 && current_tokens < self.max_tokens / 2 {
276-
debug!(
277-
"Refilled {} tokens, current: {}/{}",
278-
tokens_to_add, new_tokens, self.max_tokens
279-
);
280-
}
281-
}
282-
}
283-
}
284-
285130
/// Get current token count (for monitoring)
286131
fn current_tokens(&self) -> u32 {
287132
self.tokens.load(Ordering::Acquire)
288133
}
289-
290-
#[cfg(test)]
291-
/// Advance time for testing
292-
pub async fn advance_time(&self, duration: Duration) {
293-
let mut time_provider = self.time_provider.write().await;
294-
time_provider.advance(duration);
295-
}
296134
}
297135

298136
/// Rate limiter with global and per-IP limits

src/server.rs

Lines changed: 4 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ impl SvgInputData {
7777

7878
/// Query parameters for image generation
7979
#[derive(Debug, Deserialize)]
80-
struct ImageQuery {
80+
pub struct ImageQuery {
8181
#[serde(rename = "scale")]
82-
scale: Option<String>,
82+
pub scale: Option<String>,
8383
#[serde(rename = "s")]
84-
s: Option<String>,
84+
pub s: Option<String>,
8585
}
8686

8787
/// Application state containing the rate limiter
@@ -506,7 +506,7 @@ pub fn parse_repo_name_and_format(repo_name: &str) -> (String, Option<ImageForma
506506
///
507507
/// # Returns
508508
/// Optional scale factor (None if not provided or invalid)
509-
fn parse_scale_parameter(query: &ImageQuery) -> Option<f64> {
509+
pub fn parse_scale_parameter(query: &ImageQuery) -> Option<f64> {
510510
// Try 'scale' parameter first, then fallback to 's'
511511
let scale_str = query.scale.as_deref().or(query.s.as_deref())?;
512512

@@ -709,74 +709,3 @@ pub fn parse_address_components(
709709
)))),
710710
}
711711
}
712-
713-
#[cfg(test)]
714-
mod tests {
715-
use super::*;
716-
717-
#[test]
718-
fn test_parse_scale_parameter() {
719-
// Test valid scale parameters
720-
let query = ImageQuery {
721-
scale: Some("1.5".to_string()),
722-
s: None,
723-
};
724-
assert_eq!(parse_scale_parameter(&query), Some(1.5));
725-
726-
let query = ImageQuery {
727-
scale: None,
728-
s: Some("2.0".to_string()),
729-
};
730-
assert_eq!(parse_scale_parameter(&query), Some(2.0));
731-
732-
// Test fallback from scale to s
733-
let query = ImageQuery {
734-
scale: None,
735-
s: Some("1.2".to_string()),
736-
};
737-
assert_eq!(parse_scale_parameter(&query), Some(1.2));
738-
739-
// Test invalid parameters
740-
let query = ImageQuery {
741-
scale: Some("0.05".to_string()), // Below minimum - gets clamped to 0.1
742-
s: None,
743-
};
744-
assert_eq!(parse_scale_parameter(&query), Some(0.1));
745-
746-
let query = ImageQuery {
747-
scale: Some("12345678901".to_string()), // Too long after trimming (>10 chars)
748-
s: None,
749-
};
750-
assert_eq!(parse_scale_parameter(&query), None);
751-
752-
let query = ImageQuery {
753-
scale: Some("abc".to_string()), // Invalid number
754-
s: None,
755-
};
756-
assert_eq!(parse_scale_parameter(&query), None);
757-
758-
// Test no parameters
759-
let query = ImageQuery {
760-
scale: None,
761-
s: None,
762-
};
763-
assert_eq!(parse_scale_parameter(&query), None);
764-
}
765-
766-
#[test]
767-
fn test_scale_parameter_length_validation() {
768-
// Test that trailing zeros are trimmed correctly
769-
let query = ImageQuery {
770-
scale: Some("1.2000".to_string()),
771-
s: None,
772-
};
773-
assert_eq!(parse_scale_parameter(&query), Some(1.2));
774-
775-
// Test that long strings are rejected (>10 chars after trimming)
776-
let query = ImageQuery {
777-
scale: Some("1.2345678901".to_string()),
778-
s: None,
779-
};
780-
assert_eq!(parse_scale_parameter(&query), None);
781-
}
782-
}

0 commit comments

Comments
 (0)