Skip to content

Commit b2fb0ca

Browse files
authored
feat: Add an E2E test for killswitches (#241)
Adds a test that ensures killswitches are integrated properly into the middlewares or request handlers. The new test module can also be used for asserting rate limits and other limits in the future.
1 parent e59ed38 commit b2fb0ca

File tree

8 files changed

+105
-11
lines changed

8 files changed

+105
-11
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,5 @@ tokio = "1.47.0"
4949
tokio-stream = "0.1.17"
5050
tokio-util = "0.7.15"
5151
tracing = "0.1.41"
52+
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] }
5253
uuid = { version = "1.17.0", features = ["v4"] }

objectstore-server/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,12 @@ tower-http = { version = "0.6.6", default-features = false, features = [
5050
"trace",
5151
] }
5252
tracing = { workspace = true }
53-
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] }
53+
tracing-subscriber = { workspace = true }
5454
uuid = { workspace = true, features = ["v7"] }
5555

5656
[dev-dependencies]
5757
nix = { version = "0.30.1", features = ["signal"] }
58+
objectstore-test = { workspace = true }
5859
serde_json = { workspace = true }
5960
stresstest = { workspace = true }
6061
tempfile = { workspace = true }

objectstore-server/tests/limits.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//! Blackbox tests for limits and restrictions.
2+
//!
3+
//! These tests assert safety-related behavior of the objectstore-server, such as enforcing
4+
//! maximum object sizes, rate limiting, and killswitches.
5+
6+
use std::collections::BTreeMap;
7+
8+
use anyhow::Result;
9+
use objectstore_server::config::Config;
10+
use objectstore_server::killswitches::{Killswitch, Killswitches};
11+
use objectstore_test::server::TestServer;
12+
13+
#[tokio::test]
14+
async fn test_killswitches() -> Result<()> {
15+
let server = TestServer::with_config(Config {
16+
killswitches: Killswitches(vec![Killswitch {
17+
usecase: Some("blocked".to_string()),
18+
scopes: BTreeMap::from_iter([("org".to_string(), "42".to_string())]),
19+
}]),
20+
..Default::default()
21+
})
22+
.await;
23+
24+
let client = reqwest::Client::new();
25+
26+
// Object-level
27+
let response = client
28+
.get(server.url("/v1/objects/blocked/org=42;project=4711/foo"))
29+
.send()
30+
.await?;
31+
assert_eq!(response.status(), reqwest::StatusCode::FORBIDDEN);
32+
33+
// Collection-level
34+
let response = client
35+
.post(server.url("/v1/objects/blocked/org=42;project=4711/"))
36+
.body("test data")
37+
.send()
38+
.await?;
39+
assert_eq!(response.status(), reqwest::StatusCode::FORBIDDEN);
40+
41+
// Sanity check: Allowed access on non-existing object
42+
let response = client
43+
.get(server.url("/v1/objects/allowed/org=43;project=4711/foo"))
44+
.send()
45+
.await?;
46+
assert_eq!(response.status(), reqwest::StatusCode::NOT_FOUND);
47+
48+
Ok(())
49+
}

objectstore-test/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ publish = false
1313
objectstore-server = { workspace = true }
1414
tempfile = { workspace = true }
1515
tokio = { workspace = true }
16+
tracing-subscriber = { workspace = true }

objectstore-test/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
//! modules for all available utilities.
55
66
pub mod server;
7+
pub mod tracing;

objectstore-test/src/server.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,27 @@ pub struct TestServer {
3131
}
3232

3333
impl TestServer {
34-
pub async fn new() -> Self {
34+
/// Spawns a new test server with the given configuration.
35+
///
36+
/// Unless overridden to a different kind of backend, the long-term and high-volume storage
37+
/// backends will use temporary directories.
38+
pub async fn with_config(mut config: Config) -> Self {
3539
let addr = SocketAddr::from(([127, 0, 0, 1], 0));
3640
let listener = TcpListener::bind(addr).unwrap();
3741
listener.set_nonblocking(true).unwrap();
3842
let socket = listener.local_addr().unwrap();
3943

44+
config.logging.level = "trace".parse().unwrap();
45+
crate::tracing::init();
46+
4047
let long_term_tempdir = tempfile::tempdir().unwrap();
48+
if let Storage::FileSystem { ref mut path } = config.long_term_storage {
49+
*path = long_term_tempdir.path().into();
50+
}
4151
let high_volume_tempdir = tempfile::tempdir().unwrap();
42-
let config = Config {
43-
long_term_storage: Storage::FileSystem {
44-
path: long_term_tempdir.path().into(),
45-
},
46-
high_volume_storage: Storage::FileSystem {
47-
path: high_volume_tempdir.path().into(),
48-
},
49-
..Default::default()
50-
};
52+
if let Storage::FileSystem { ref mut path } = config.high_volume_storage {
53+
*path = high_volume_tempdir.path().into();
54+
}
5155

5256
let state = Services::spawn(config).await.unwrap();
5357
let app = App::new(state);
@@ -65,6 +69,11 @@ impl TestServer {
6569
}
6670
}
6771

72+
/// Spawns a new test server with default configuration.
73+
pub async fn new() -> Self {
74+
Self::with_config(Config::default()).await
75+
}
76+
6877
/// Returns a full URL pointing to the given path.
6978
///
7079
/// This URL uses `localhost` as hostname.

objectstore-test/src/tracing.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use tracing_subscriber::EnvFilter;
2+
3+
const CRATE_NAMES: &[&str] = &["objectstore", "objectstore_service", "objectstore_types"];
4+
5+
/// Initialize the logger for testing.
6+
///
7+
/// This logs to the stdout registered by the Rust test runner, and only captures logs from the
8+
/// calling crate.
9+
///
10+
/// # Example
11+
///
12+
/// ```
13+
/// objectstore_test::tracing::init();
14+
/// ```
15+
pub fn init() {
16+
let mut env_filter = EnvFilter::new("ERROR");
17+
18+
// Add all internal modules with maximum log-level.
19+
for name in CRATE_NAMES {
20+
env_filter = env_filter.add_directive(format!("{name}=TRACE").parse().unwrap());
21+
}
22+
23+
tracing_subscriber::fmt::fmt()
24+
.with_env_filter(env_filter)
25+
.with_target(true)
26+
.with_test_writer()
27+
.compact()
28+
.try_init()
29+
.ok();
30+
}

0 commit comments

Comments
 (0)