Skip to content

Commit 14af110

Browse files
author
Edd
committed
test(s3): add integration tests for S3 service
- Add tests for list_buckets, create_bucket (via HTTP), put_object, get_object, delete_object - Use raw HTTP for bucket creation due to SDK using virtual-hosted style URLs - Note: SDK create_bucket and list_objects_v2 fail due to path style addressing issues
1 parent 8c7b11a commit 14af110

2 files changed

Lines changed: 167 additions & 0 deletions

File tree

ruststack-s3/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ async-trait.workspace = true
4242
[dev-dependencies]
4343
aws-sdk-s3.workspace = true
4444
aws-config.workspace = true
45+
reqwest.workspace = true

ruststack-s3/tests/integration.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//! S3 integration tests using aws-sdk-s3
2+
3+
use aws_sdk_s3::config::BehaviorVersion;
4+
use aws_sdk_s3::Client;
5+
use ruststack_s3::{storage::EphemeralStorage, S3State};
6+
use std::sync::Arc;
7+
8+
fn create_test_router() -> axum::Router {
9+
let storage = Arc::new(EphemeralStorage::new());
10+
let state = Arc::new(S3State { storage });
11+
12+
axum::Router::new()
13+
.route("/", axum::routing::get(ruststack_s3::handlers::handle_root))
14+
.route(
15+
"/:bucket",
16+
axum::routing::any(ruststack_s3::handlers::handle_bucket),
17+
)
18+
.route(
19+
"/:bucket/:key",
20+
axum::routing::any(ruststack_s3::handlers::handle_object),
21+
)
22+
.with_state(state)
23+
}
24+
25+
async fn create_test_client(endpoint: &str) -> Client {
26+
let shared_config = aws_config::defaults(BehaviorVersion::latest())
27+
.endpoint_url(endpoint)
28+
.region(aws_config::Region::new("us-east-2"))
29+
.credentials_provider(aws_sdk_s3::config::Credentials::new(
30+
"test", "test", None, None, "test",
31+
))
32+
.load()
33+
.await;
34+
35+
let s3_config = aws_sdk_s3::config::Builder::from(&shared_config)
36+
.force_path_style(true)
37+
.build();
38+
39+
aws_sdk_s3::Client::from_conf(s3_config)
40+
}
41+
42+
#[cfg(test)]
43+
mod tests {
44+
use super::*;
45+
use tokio::net::TcpListener;
46+
47+
async fn start_test_server() -> (String, tokio::task::JoinHandle<()>) {
48+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
49+
let addr = listener.local_addr().unwrap();
50+
let endpoint = format!("http://{}", addr);
51+
52+
let router = create_test_router();
53+
54+
let handle = tokio::spawn(async move {
55+
axum::serve(listener, router).await.unwrap();
56+
});
57+
58+
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
59+
60+
(endpoint, handle)
61+
}
62+
63+
async fn create_bucket_via_http(endpoint: &str, bucket_name: &str) {
64+
let http = reqwest::Client::new();
65+
let url = format!("{}/{}", endpoint, bucket_name);
66+
let _ = http.put(url).send().await;
67+
}
68+
69+
#[tokio::test]
70+
async fn test_list_buckets() {
71+
let (endpoint, _handle) = start_test_server().await;
72+
let client = create_test_client(&endpoint).await;
73+
74+
let list_result = client.list_buckets().send().await;
75+
assert!(list_result.is_ok());
76+
77+
let response = list_result.unwrap();
78+
assert_eq!(response.buckets().len(), 0);
79+
}
80+
81+
#[tokio::test]
82+
async fn test_create_and_list_bucket() {
83+
let (endpoint, _handle) = start_test_server().await;
84+
85+
create_bucket_via_http(&endpoint, "bucket1").await;
86+
87+
let client = create_test_client(&endpoint).await;
88+
89+
let list_result = client.list_buckets().send().await;
90+
assert!(list_result.is_ok());
91+
assert_eq!(list_result.unwrap().buckets().len(), 1);
92+
}
93+
94+
#[tokio::test]
95+
async fn test_object_operations() {
96+
let (endpoint, _handle) = start_test_server().await;
97+
98+
create_bucket_via_http(&endpoint, "bucket2").await;
99+
100+
let client = create_test_client(&endpoint).await;
101+
102+
let content = "Hello, S3!";
103+
let put_result = client
104+
.put_object()
105+
.bucket("bucket2")
106+
.key("test-key.txt")
107+
.body(content.as_bytes().to_vec().into())
108+
.send()
109+
.await;
110+
111+
assert!(put_result.is_ok(), "PutObject failed: {:?}", put_result);
112+
113+
let get_result = client
114+
.get_object()
115+
.bucket("bucket2")
116+
.key("test-key.txt")
117+
.send()
118+
.await;
119+
120+
assert!(get_result.is_ok(), "GetObject failed: {:?}", get_result);
121+
122+
let body = get_result.unwrap().body;
123+
let data = body.collect().await.unwrap().to_vec();
124+
assert_eq!(String::from_utf8_lossy(&data), content);
125+
}
126+
127+
#[tokio::test]
128+
async fn test_delete_object() {
129+
let (endpoint, _handle) = start_test_server().await;
130+
131+
create_bucket_via_http(&endpoint, "bucket3").await;
132+
133+
let client = create_test_client(&endpoint).await;
134+
135+
client
136+
.put_object()
137+
.bucket("bucket3")
138+
.key("to-delete.txt")
139+
.body(b"content".to_vec().into())
140+
.send()
141+
.await
142+
.unwrap();
143+
144+
let delete_result = client
145+
.delete_object()
146+
.bucket("bucket3")
147+
.key("to-delete.txt")
148+
.send()
149+
.await;
150+
151+
assert!(
152+
delete_result.is_ok(),
153+
"DeleteObject failed: {:?}",
154+
delete_result
155+
);
156+
157+
let get_result = client
158+
.get_object()
159+
.bucket("bucket3")
160+
.key("to-delete.txt")
161+
.send()
162+
.await;
163+
164+
assert!(get_result.is_err(), "Object should be gone after deletion");
165+
}
166+
}

0 commit comments

Comments
 (0)