generated from amazon-archives/__template_Apache-2.0
-
Notifications
You must be signed in to change notification settings - Fork 36
Add integration tests for cache behavior, version management, and cross-account access #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
175969e
Add integration tests for cache behavior, version management, and cro…
reyhankoyun 1fa5180
Remove Clone traits
reyhankoyun c0eb46e
Fix format
reyhankoyun 500158b
Update tests
reyhankoyun d6f0612
Format tests
reyhankoyun 6077ad8
Improve integration tests: remove arbitrary sleeps and fix type consi…
reyhankoyun bf45881
replace arbitrary delay with validation + targeted sync
reyhankoyun e597756
Update cache_behavior.rs
simonmarty e1873b1
Address PR feedback
reyhankoyun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| mod common; | ||
|
|
||
| use common::*; | ||
| use std::time::Duration; | ||
| use tokio::time::sleep; | ||
|
|
||
| #[tokio::test] | ||
| async fn test_stale_cache_during_secret_update() { | ||
| let secrets = TestSecrets::setup().await; | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let secret_name = secrets.secret_name(SecretType::Basic); | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| let agent = AgentProcess::start().await; | ||
|
|
||
| // First request - populate cache with original value | ||
| let query = AgentQueryBuilder::default() | ||
| .secret_id(&secret_name) | ||
| .build() | ||
| .unwrap(); | ||
| let response1 = agent.make_request(&query).await; | ||
| let json1: serde_json::Value = serde_json::from_str(&response1).unwrap(); | ||
| let original_secret = json1["SecretString"].as_str().unwrap(); | ||
| assert!(original_secret.contains("testuser")); | ||
|
|
||
| // Update the secret in AWS (simulating manual update, not automatic rotation) | ||
| let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; | ||
| let client = aws_sdk_secretsmanager::Client::new(&config); | ||
|
|
||
| let updated_secret_value = r#"{"username":"rotateduser","password":"rotatedpass123"}"#; | ||
| client | ||
| .update_secret() | ||
| .secret_id(&secret_name) | ||
| .secret_string(updated_secret_value) | ||
| .send() | ||
| .await | ||
| .expect("Failed to update secret"); | ||
|
|
||
| // Wait for the update to propagate | ||
| sleep(Duration::from_secs(2)).await; | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Second request without refreshNow - should return stale cached value | ||
| let response2 = agent.make_request(&query).await; | ||
| let json2: serde_json::Value = serde_json::from_str(&response2).unwrap(); | ||
| let cached_secret = json2["SecretString"].as_str().unwrap(); | ||
|
|
||
| // Should still have the old value from cache | ||
| assert!(cached_secret.contains("testuser")); | ||
| assert!(!cached_secret.contains("rotateduser")); | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Third request with refreshNow=true - should get fresh value | ||
| let refresh_query = AgentQueryBuilder::default() | ||
| .secret_id(&secret_name) | ||
| .refresh_now(true) | ||
| .build() | ||
| .unwrap(); | ||
| let response3 = agent.make_request(&refresh_query).await; | ||
| let json3: serde_json::Value = serde_json::from_str(&response3).unwrap(); | ||
| let fresh_secret = json3["SecretString"].as_str().unwrap(); | ||
|
|
||
| // Should now have the updated value | ||
| assert!(fresh_secret.contains("rotateduser")); | ||
| assert!(!fresh_secret.contains("testuser")); | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_cache_expiration_and_refresh() { | ||
| let secrets = TestSecrets::setup().await; | ||
| let secret_name = secrets.secret_name(SecretType::Basic); | ||
|
|
||
| // Start agent with short TTL (5 seconds) for faster testing | ||
| let agent = AgentProcess::start_with_config(2777, 5).await; | ||
|
|
||
| let query = AgentQueryBuilder::default() | ||
| .secret_id(&secret_name) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| // First request - populate cache | ||
| let response1 = agent.make_request(&query).await; | ||
| let json1: serde_json::Value = serde_json::from_str(&response1).unwrap(); | ||
| let version1 = json1["VersionId"].as_str().unwrap(); | ||
reyhankoyun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| assert!(json1["SecretString"].as_str().unwrap().contains("testuser")); | ||
|
|
||
| // Second request immediately - should hit cache (same version) | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let response2 = agent.make_request(&query).await; | ||
| let json2: serde_json::Value = serde_json::from_str(&response2).unwrap(); | ||
| assert_eq!(json1["VersionId"], json2["VersionId"]); | ||
|
|
||
| // Update secret while cache is still valid | ||
| let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; | ||
| let client = aws_sdk_secretsmanager::Client::new(&config); | ||
|
|
||
| client | ||
| .update_secret() | ||
| .secret_id(&secret_name) | ||
| .secret_string(r#"{"username":"expireduser","password":"expiredpass789"}"#) | ||
| .send() | ||
| .await | ||
| .expect("Failed to update secret"); | ||
|
|
||
| // Wait for update to propagate | ||
| sleep(Duration::from_secs(2)).await; | ||
|
|
||
reyhankoyun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Third request before TTL expires - should still return cached value | ||
| let response3 = agent.make_request(&query).await; | ||
| let json3: serde_json::Value = serde_json::from_str(&response3).unwrap(); | ||
| assert_eq!(json3["VersionId"], version1); // Same version as cached | ||
| assert!(json3["SecretString"].as_str().unwrap().contains("testuser")); | ||
|
|
||
| // Wait for TTL to expire (5 seconds + buffer) | ||
| sleep(Duration::from_secs(4)).await; | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Fourth request after TTL expiry - should fetch fresh value from AWS | ||
| let response4 = agent.make_request(&query).await; | ||
| let json4: serde_json::Value = serde_json::from_str(&response4).unwrap(); | ||
|
|
||
| // Should now have the updated value and different version | ||
| assert_ne!(json4["VersionId"], version1); | ||
| assert!(json4["SecretString"] | ||
| .as_str() | ||
| .unwrap() | ||
| .contains("expireduser")); | ||
| assert!(!json4["SecretString"].as_str().unwrap().contains("testuser")); | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Fifth request immediately after - should use newly cached value | ||
| let response5 = agent.make_request(&query).await; | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let json5: serde_json::Value = serde_json::from_str(&response5).unwrap(); | ||
| assert_eq!(json4["VersionId"], json5["VersionId"]); // Same as previous | ||
| assert!(json5["SecretString"] | ||
| .as_str() | ||
| .unwrap() | ||
| .contains("expireduser")); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| mod common; | ||
|
|
||
| use common::*; | ||
|
|
||
| #[tokio::test] | ||
| async fn test_cross_account_secret_access() { | ||
| let secrets = TestSecrets::setup().await; | ||
| let secret_name = secrets.secret_name(SecretType::Basic); | ||
reyhankoyun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| let agent = AgentProcess::start().await; | ||
|
|
||
| // Get the ARN of the secret for cross-account testing | ||
| let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; | ||
| let client = aws_sdk_secretsmanager::Client::new(&config); | ||
|
|
||
| let describe_response = client | ||
| .describe_secret() | ||
| .secret_id(&secret_name) | ||
| .send() | ||
| .await | ||
| .expect("Failed to describe secret"); | ||
|
|
||
| let secret_arn = describe_response.arn().expect("Secret ARN not found"); | ||
|
|
||
| // Test accessing secret by ARN (simulates cross-account access pattern) | ||
| let arn_query = AgentQueryBuilder::default() | ||
| .secret_id(secret_arn) | ||
| .build() | ||
| .unwrap(); | ||
| let arn_response = agent.make_request(&arn_query).await; | ||
| let arn_json: serde_json::Value = serde_json::from_str(&arn_response).unwrap(); | ||
|
|
||
| // Verify the response contains the correct data | ||
| assert_eq!(arn_json["ARN"], secret_arn); | ||
| assert_eq!(arn_json["Name"], secret_name); | ||
| assert!(arn_json["SecretString"] | ||
| .as_str() | ||
| .unwrap() | ||
| .contains("testuser")); | ||
| assert!(arn_json["VersionId"].is_string()); | ||
|
|
||
| // Test accessing secret by name (same account pattern) | ||
| let name_query = AgentQueryBuilder::default() | ||
| .secret_id(&secret_name) | ||
| .build() | ||
| .unwrap(); | ||
| let name_response = agent.make_request(&name_query).await; | ||
| let name_json: serde_json::Value = serde_json::from_str(&name_response).unwrap(); | ||
|
|
||
| // Both responses should contain the same secret data | ||
| assert_eq!(arn_json["VersionId"], name_json["VersionId"]); | ||
| assert_eq!(arn_json["SecretString"], name_json["SecretString"]); | ||
| assert_eq!(arn_json["CreatedDate"], name_json["CreatedDate"]); | ||
|
|
||
| // Test that ARN-based access works with version stages | ||
| let arn_version_query = AgentQueryBuilder::default() | ||
| .secret_id(secret_arn) | ||
| .version_stage("AWSCURRENT") | ||
| .build() | ||
| .unwrap(); | ||
| let arn_version_response = agent.make_request(&arn_version_query).await; | ||
| let arn_version_json: serde_json::Value = serde_json::from_str(&arn_version_response).unwrap(); | ||
|
|
||
| assert_eq!(arn_version_json["ARN"], secret_arn); | ||
| assert!(arn_version_json["VersionStages"] | ||
| .as_array() | ||
| .unwrap() | ||
| .contains(&serde_json::Value::String("AWSCURRENT".to_string()))); | ||
|
|
||
| // Test that ARN-based access works with refreshNow | ||
| let arn_refresh_query = AgentQueryBuilder::default() | ||
| .secret_id(secret_arn) | ||
| .refresh_now(true) | ||
| .build() | ||
| .unwrap(); | ||
| let arn_refresh_response = agent.make_request(&arn_refresh_query).await; | ||
| let arn_refresh_json: serde_json::Value = serde_json::from_str(&arn_refresh_response).unwrap(); | ||
|
|
||
| assert_eq!(arn_refresh_json["ARN"], secret_arn); | ||
| assert_eq!(arn_refresh_json["VersionId"], arn_json["VersionId"]); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| mod common; | ||
|
|
||
| use common::*; | ||
| use std::time::Duration; | ||
| use tokio::time::sleep; | ||
|
|
||
| #[tokio::test] | ||
| async fn test_version_stage_transitions() { | ||
reyhankoyun marked this conversation as resolved.
Show resolved
Hide resolved
reyhankoyun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let secrets = TestSecrets::setup().await; | ||
| let secret_name = secrets.secret_name(SecretType::Versioned); | ||
|
|
||
| let agent = AgentProcess::start().await; | ||
|
|
||
| // Wait for AWSPENDING version to be available | ||
| let _ = secrets | ||
| .wait_for_pending_version(SecretType::Versioned) | ||
| .await; | ||
|
|
||
| // Get the version IDs for both stages | ||
| let (current_version_id, pending_version_id) = | ||
| secrets.get_version_ids(SecretType::Versioned).await; | ||
|
|
||
| // Test AWSPENDING stage before promotion | ||
| let pending_query = AgentQueryBuilder::default() | ||
| .secret_id(&secret_name) | ||
| .version_stage("AWSPENDING") | ||
| .build() | ||
| .unwrap(); | ||
| let pending_response = agent.make_request(&pending_query).await; | ||
| let pending_json: serde_json::Value = serde_json::from_str(&pending_response).unwrap(); | ||
|
|
||
| assert_eq!(pending_json["VersionId"], pending_version_id); | ||
| assert!(pending_json["SecretString"] | ||
| .as_str() | ||
| .unwrap() | ||
| .contains("pendinguser")); | ||
|
|
||
| // Test AWSCURRENT stage before promotion | ||
| let current_query = AgentQueryBuilder::default() | ||
| .secret_id(&secret_name) | ||
| .version_stage("AWSCURRENT") | ||
| .build() | ||
| .unwrap(); | ||
| let current_response = agent.make_request(¤t_query).await; | ||
| let current_json: serde_json::Value = serde_json::from_str(¤t_response).unwrap(); | ||
|
|
||
| assert_eq!(current_json["VersionId"], current_version_id); | ||
| assert!(current_json["SecretString"] | ||
| .as_str() | ||
| .unwrap() | ||
| .contains("currentuser")); | ||
|
|
||
| // Promote AWSPENDING to AWSCURRENT using update_secret_version_stage | ||
| let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; | ||
| let client = aws_sdk_secretsmanager::Client::new(&config); | ||
|
|
||
| client | ||
| .update_secret_version_stage() | ||
| .secret_id(&secret_name) | ||
| .version_stage("AWSCURRENT") | ||
| .move_to_version_id(&pending_version_id) | ||
| .remove_from_version_id(¤t_version_id) | ||
| .send() | ||
| .await | ||
| .expect("Failed to promote version stage"); | ||
|
|
||
| // Wait for the promotion to propagate | ||
| sleep(Duration::from_secs(3)).await; | ||
|
|
||
| // Test that AWSCURRENT now points to the previously pending version (with refreshNow) | ||
| let promoted_query = AgentQueryBuilder::default() | ||
| .secret_id(&secret_name) | ||
| .version_stage("AWSCURRENT") | ||
| .refresh_now(true) | ||
| .build() | ||
| .unwrap(); | ||
| let promoted_response = agent.make_request(&promoted_query).await; | ||
| let promoted_json: serde_json::Value = serde_json::from_str(&promoted_response).unwrap(); | ||
|
|
||
| // After promotion, AWSCURRENT should now have the pending version ID and content | ||
| assert_eq!(promoted_json["VersionId"], pending_version_id); | ||
| assert!(promoted_json["SecretString"] | ||
| .as_str() | ||
| .unwrap() | ||
| .contains("pendinguser")); | ||
| assert!(promoted_json["VersionStages"] | ||
| .as_array() | ||
| .unwrap() | ||
| .contains(&serde_json::Value::String("AWSCURRENT".to_string()))); | ||
|
|
||
| // Verify the old current version is no longer AWSCURRENT | ||
| let old_current_query = AgentQueryBuilder::default() | ||
| .secret_id(&secret_name) | ||
| .version_id(¤t_version_id) | ||
| .refresh_now(true) | ||
| .build() | ||
| .unwrap(); | ||
| let old_current_response = agent.make_request(&old_current_query).await; | ||
| let old_current_json: serde_json::Value = serde_json::from_str(&old_current_response).unwrap(); | ||
|
|
||
| // The old version should still exist but not have AWSCURRENT stage | ||
| assert_eq!(old_current_json["VersionId"], current_version_id); | ||
| assert!(old_current_json["SecretString"] | ||
| .as_str() | ||
| .unwrap() | ||
| .contains("currentuser")); | ||
| assert!(!old_current_json["VersionStages"] | ||
| .as_array() | ||
| .unwrap() | ||
| .contains(&serde_json::Value::String("AWSCURRENT".to_string()))); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.