Skip to content

Commit b63c4fc

Browse files
committed
Add comprehensive caching integration tests
- test_cache_hit_behavior: Verifies cache hits are faster than AWS calls - test_refresh_now_bypasses_cache: Confirms refreshNow=true bypasses cache - test_cache_after_secret_update: Tests stale cache behavior after secret updates - test_real_ttl_expiration_timing: Validates TTL expiration and cache refresh - test_ttl_zero_disables_caching: Ensures TTL=0 disables caching completely These tests cover all critical caching behaviors that cannot be unit tested, including timing-based assertions and AWS integration scenarios.
1 parent ab15067 commit b63c4fc

File tree

1 file changed

+265
-0
lines changed

1 file changed

+265
-0
lines changed

integration-tests/tests/caching.rs

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
mod common;
2+
3+
use common::*;
4+
use std::time::{Duration, Instant};
5+
6+
#[tokio::test]
7+
async fn test_cache_hit_behavior() {
8+
let secrets = TestSecrets::setup().await;
9+
let secret_name = secrets.secret_name(SecretType::Basic);
10+
11+
let agent = AgentProcess::start().await;
12+
13+
// First request - should fetch from AWS and cache
14+
let query = AgentQueryBuilder::default()
15+
.secret_id(&secret_name)
16+
.build()
17+
.unwrap();
18+
19+
let start_time = Instant::now();
20+
let response1 = agent.make_request(&query).await;
21+
let first_request_duration = start_time.elapsed();
22+
23+
let json1: serde_json::Value = serde_json::from_str(&response1).unwrap();
24+
assert_eq!(json1["Name"], secret_name);
25+
assert!(json1["SecretString"].as_str().unwrap().contains("testuser"));
26+
27+
// Second request - should be served from cache (much faster)
28+
let start_time = Instant::now();
29+
let response2 = agent.make_request(&query).await;
30+
let second_request_duration = start_time.elapsed();
31+
32+
let json2: serde_json::Value = serde_json::from_str(&response2).unwrap();
33+
assert_eq!(json2["Name"], secret_name);
34+
assert!(json2["SecretString"].as_str().unwrap().contains("testuser"));
35+
36+
// Verify responses are identical (from cache)
37+
assert_eq!(json1["VersionId"], json2["VersionId"]);
38+
assert_eq!(json1["SecretString"], json2["SecretString"]);
39+
40+
// Cache hit should be significantly faster than initial AWS call
41+
// Allow some tolerance for timing variations
42+
assert!(
43+
second_request_duration < first_request_duration / 2,
44+
"Cache hit should be faster. First: {:?}, Second: {:?}",
45+
first_request_duration,
46+
second_request_duration
47+
);
48+
}
49+
50+
#[tokio::test]
51+
async fn test_refresh_now_bypasses_cache() {
52+
let secrets = TestSecrets::setup().await;
53+
let secret_name = secrets.secret_name(SecretType::Basic);
54+
55+
let agent = AgentProcess::start().await;
56+
57+
// First request - populate cache
58+
let query = AgentQueryBuilder::default()
59+
.secret_id(&secret_name)
60+
.build()
61+
.unwrap();
62+
let response1 = agent.make_request(&query).await;
63+
let _json1: serde_json::Value = serde_json::from_str(&response1).unwrap();
64+
65+
// Second request with refreshNow=true - should bypass cache
66+
let refresh_query = AgentQueryBuilder::default()
67+
.secret_id(&secret_name)
68+
.refresh_now(true)
69+
.build()
70+
.unwrap();
71+
72+
let start_time = Instant::now();
73+
let response2 = agent.make_request(&refresh_query).await;
74+
let refresh_duration = start_time.elapsed();
75+
76+
let json2: serde_json::Value = serde_json::from_str(&response2).unwrap();
77+
78+
// Verify we got a valid response
79+
assert_eq!(json2["Name"], secret_name);
80+
assert!(json2["SecretString"].as_str().unwrap().contains("testuser"));
81+
82+
// refreshNow should take longer than a cache hit (it goes to AWS)
83+
// This is a network call, so should be measurably slower than cache
84+
assert!(
85+
refresh_duration > Duration::from_millis(10),
86+
"refreshNow should make AWS call, duration: {:?}",
87+
refresh_duration
88+
);
89+
90+
// Third request without refreshNow - should use updated cache
91+
let response3 = agent.make_request(&query).await;
92+
let json3: serde_json::Value = serde_json::from_str(&response3).unwrap();
93+
94+
assert_eq!(json3["Name"], secret_name);
95+
assert!(json3["SecretString"].as_str().unwrap().contains("testuser"));
96+
}
97+
98+
#[tokio::test]
99+
async fn test_cache_after_secret_update() {
100+
let secrets = TestSecrets::setup().await;
101+
let secret_name = secrets.secret_name(SecretType::Basic);
102+
103+
let agent = AgentProcess::start().await;
104+
105+
// First request - populate cache with original value
106+
let query = AgentQueryBuilder::default()
107+
.secret_id(&secret_name)
108+
.build()
109+
.unwrap();
110+
let response1 = agent.make_request(&query).await;
111+
let json1: serde_json::Value = serde_json::from_str(&response1).unwrap();
112+
let original_secret = json1["SecretString"].as_str().unwrap();
113+
assert!(original_secret.contains("testuser"));
114+
115+
// Update the secret in AWS
116+
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
117+
let client = aws_sdk_secretsmanager::Client::new(&config);
118+
119+
let updated_secret_value = r#"{"username":"updateduser","password":"updatedpass456"}"#;
120+
client
121+
.update_secret()
122+
.secret_id(&secret_name)
123+
.secret_string(updated_secret_value)
124+
.send()
125+
.await
126+
.expect("Failed to update secret");
127+
128+
// Wait a moment for the update to propagate
129+
tokio::time::sleep(Duration::from_secs(2)).await;
130+
131+
// Second request without refreshNow - should return stale cached value
132+
let response2 = agent.make_request(&query).await;
133+
let json2: serde_json::Value = serde_json::from_str(&response2).unwrap();
134+
let cached_secret = json2["SecretString"].as_str().unwrap();
135+
136+
// Should still have the old value from cache
137+
assert!(cached_secret.contains("testuser"));
138+
assert!(!cached_secret.contains("updateduser"));
139+
140+
// Third request with refreshNow=true - should get fresh value
141+
let refresh_query = AgentQueryBuilder::default()
142+
.secret_id(&secret_name)
143+
.refresh_now(true)
144+
.build()
145+
.unwrap();
146+
let response3 = agent.make_request(&refresh_query).await;
147+
let json3: serde_json::Value = serde_json::from_str(&response3).unwrap();
148+
let fresh_secret = json3["SecretString"].as_str().unwrap();
149+
150+
// Should now have the updated value
151+
assert!(fresh_secret.contains("updateduser"));
152+
assert!(!fresh_secret.contains("testuser"));
153+
154+
// Fourth request without refreshNow - cache should now have updated value
155+
let response4 = agent.make_request(&query).await;
156+
let json4: serde_json::Value = serde_json::from_str(&response4).unwrap();
157+
let updated_cached_secret = json4["SecretString"].as_str().unwrap();
158+
159+
// Cache should now contain the updated value
160+
assert!(updated_cached_secret.contains("updateduser"));
161+
assert!(!updated_cached_secret.contains("testuser"));
162+
}
163+
164+
#[tokio::test]
165+
async fn test_real_ttl_expiration_timing() {
166+
let secrets = TestSecrets::setup().await;
167+
let secret_name = secrets.secret_name(SecretType::Basic);
168+
169+
// Start agent with short TTL (3 seconds) for faster testing
170+
let agent = AgentProcess::start_with_config(2775, 3).await;
171+
172+
let query = AgentQueryBuilder::default()
173+
.secret_id(&secret_name)
174+
.build()
175+
.unwrap();
176+
177+
// First request - populate cache
178+
let start_time = Instant::now();
179+
let response1 = agent.make_request(&query).await;
180+
let first_duration = start_time.elapsed();
181+
let json1: serde_json::Value = serde_json::from_str(&response1).unwrap();
182+
assert!(json1["SecretString"].as_str().unwrap().contains("testuser"));
183+
184+
// Second request immediately - should hit cache (fast)
185+
let start_time = Instant::now();
186+
let response2 = agent.make_request(&query).await;
187+
let cache_hit_duration = start_time.elapsed();
188+
let json2: serde_json::Value = serde_json::from_str(&response2).unwrap();
189+
190+
// Verify cache hit is faster
191+
assert!(cache_hit_duration < first_duration / 2);
192+
assert_eq!(json1["VersionId"], json2["VersionId"]);
193+
194+
// Wait for TTL to expire (3 seconds + buffer)
195+
tokio::time::sleep(Duration::from_secs(4)).await;
196+
197+
// Third request after TTL expiry - should fetch from AWS again (slower)
198+
let start_time = Instant::now();
199+
let response3 = agent.make_request(&query).await;
200+
let post_ttl_duration = start_time.elapsed();
201+
let json3: serde_json::Value = serde_json::from_str(&response3).unwrap();
202+
203+
// Post-TTL request should be slower than cache hit (goes to AWS)
204+
assert!(
205+
post_ttl_duration > cache_hit_duration * 2,
206+
"Post-TTL request should be slower. Cache hit: {:?}, Post-TTL: {:?}",
207+
cache_hit_duration,
208+
post_ttl_duration
209+
);
210+
211+
// Should still get valid response
212+
assert!(json3["SecretString"].as_str().unwrap().contains("testuser"));
213+
}
214+
215+
#[tokio::test]
216+
async fn test_ttl_zero_disables_caching() {
217+
let secrets = TestSecrets::setup().await;
218+
let secret_name = secrets.secret_name(SecretType::Basic);
219+
220+
// Start agent with TTL=0 to disable caching
221+
let agent = AgentProcess::start_with_config(2775, 0).await;
222+
223+
let query = AgentQueryBuilder::default()
224+
.secret_id(&secret_name)
225+
.build()
226+
.unwrap();
227+
228+
// Make multiple requests and verify they all take significant time (go to AWS)
229+
let mut durations = Vec::new();
230+
231+
for i in 0..4 {
232+
let start_time = Instant::now();
233+
let response = agent.make_request(&query).await;
234+
let duration = start_time.elapsed();
235+
durations.push(duration);
236+
237+
let json: serde_json::Value = serde_json::from_str(&response).unwrap();
238+
assert!(json["SecretString"].as_str().unwrap().contains("testuser"));
239+
240+
// Each request should take significant time (network call to AWS)
241+
assert!(
242+
duration > Duration::from_millis(20),
243+
"Request {} should go to AWS with TTL=0, duration: {:?}",
244+
i + 1,
245+
duration
246+
);
247+
248+
// Small delay between requests to avoid overwhelming AWS
249+
if i < 3 {
250+
tokio::time::sleep(Duration::from_millis(100)).await;
251+
}
252+
}
253+
254+
// Verify no request was significantly faster (indicating cache hit)
255+
let min_duration = durations.iter().min().unwrap();
256+
257+
// All requests should be in reasonable range (no cache speedup)
258+
// Allow for network variance but ensure no sub-15ms cache hits
259+
assert!(
260+
min_duration > &Duration::from_millis(15),
261+
"Minimum duration too fast, suggests caching: {:?}. All durations: {:?}",
262+
min_duration,
263+
durations
264+
);
265+
}

0 commit comments

Comments
 (0)