From 6d6d05a03cd3cb707ed5be70bcfba5dd6422bdeb Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 17 Oct 2026 14:49:45 +0530 Subject: [PATCH 1/4] fix: serve server-side keys on /flags and /identities Evaluation contexts are cached under the client key, but these endpoints looked them up by the presented key, so server-side keys got 503. Closes #24 --- src/services/environment.rs | 18 ++++++------- tests/test_server.rs | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/services/environment.rs b/src/services/environment.rs index 423a89b..5ef66dd 100644 --- a/src/services/environment.rs +++ b/src/services/environment.rs @@ -296,13 +296,11 @@ impl EnvironmentService { environment_key: &str, feature_name: Option<&str>, ) -> Result> { - // TODO: serve server-side keys here, see - // https://github.com/Flagsmith/edge-proxy-rs/issues/24 - self.resolve_key(environment_key)?; + let keys = self.resolve_key(environment_key)?; let context = self .cache - .get_context(environment_key) + .get_context(&keys.client_key) .await .ok_or_else(|| { EdgeProxyError::ServiceUnavailable("Environment not loaded".to_string()) @@ -319,8 +317,8 @@ impl EnvironmentService { } } - if !environment_key.starts_with("ser.") { - if let Some(document) = self.cache.get_environment(environment_key).await { + if environment_key == keys.client_key { + if let Some(document) = self.cache.get_environment(&keys.client_key).await { let server_key_only_ids = Self::extract_server_key_only_ids(&document); flag_results = filter_out_server_key_only_flag_results(flag_results, &server_key_only_ids); @@ -342,12 +340,12 @@ impl EnvironmentService { identity: &IdentityWithTraits, environment_key: &str, ) -> Result { - self.resolve_key(environment_key)?; + let keys = self.resolve_key(environment_key)?; // Get pre-computed context from cache let context = self .cache - .get_context(environment_key) + .get_context(&keys.client_key) .await .ok_or_else(|| { EdgeProxyError::ServiceUnavailable("Environment not loaded".to_string()) @@ -364,9 +362,9 @@ impl EnvironmentService { let mut flag_results: Vec = evaluation_result.flags.into_values().collect(); // Filter out server-key-only features if using client key - if !environment_key.starts_with("ser.") { + if environment_key == keys.client_key { // Get server_key_only_feature_ids from cached document - if let Some(document) = self.cache.get_environment(environment_key).await { + if let Some(document) = self.cache.get_environment(&keys.client_key).await { let server_key_only_ids = Self::extract_server_key_only_ids(&document); flag_results = filter_out_server_key_only_flag_results(flag_results, &server_key_only_ids); diff --git a/tests/test_server.rs b/tests/test_server.rs index 2a599af..9dab6cc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -116,6 +116,56 @@ async fn test_get_flags_unknown_key() { assert!(body["message"].as_str().unwrap().contains("unknown key")); } +#[tokio::test] +async fn test_get_flags_server_side_key_includes_server_key_only_features() { + // Given + let server = setup_test_server().await; + + // When + let response = server + .get("/api/v1/flags") + .add_header("X-Environment-Key", "ser.test_key") + .await; + + // Then + response.assert_status_ok(); + let body: serde_json::Value = response.json(); + let mut feature_names: Vec<&str> = body + .as_array() + .unwrap() + .iter() + .map(|f| f["feature"]["name"].as_str().unwrap()) + .collect(); + feature_names.sort(); + assert_eq!(feature_names, ["feature_1", "feature_2", "feature_3"]); +} + +#[tokio::test] +async fn test_post_identities_server_side_key_includes_server_key_only_features() { + // Given + let server = setup_test_server().await; + let identity_data = json!({"identifier": "test_identity", "traits": []}); + + // When + let response = server + .post("/api/v1/identities/") + .add_header("X-Environment-Key", "ser.test_key") + .json(&identity_data) + .await; + + // Then + response.assert_status_ok(); + let body: serde_json::Value = response.json(); + let mut feature_names: Vec<&str> = body["flags"] + .as_array() + .unwrap() + .iter() + .map(|f| f["feature"]["name"].as_str().unwrap()) + .collect(); + feature_names.sort(); + assert_eq!(feature_names, ["feature_1", "feature_2", "feature_3"]); +} + #[tokio::test] async fn test_post_identity_with_traits() { // Given From 1648ff961db3381c99c16e34fd5251252c693895 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 17 Oct 2026 14:51:20 +0530 Subject: [PATCH 2/4] test: run key-agnostic tests with client and server keys --- Cargo.lock | 113 +++++++++++++++++++++++++++++-- Cargo.toml | 1 + tests/fixtures.rs | 4 ++ tests/test_cors.rs | 2 +- tests/test_remove_environment.rs | 51 ++++++++++---- tests/test_server.rs | 108 ++++++++++++++--------------- 6 files changed, 200 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b54ddeb..b951b23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,6 +519,7 @@ dependencies = [ "mockall", "parking_lot", "reqwest", + "rstest", "serde", "serde_json", "thiserror 2.0.17", @@ -730,6 +731,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + [[package]] name = "futures-util" version = "0.3.31" @@ -785,6 +792,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + [[package]] name = "h2" version = "0.4.12" @@ -806,9 +819,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hermit-abi" @@ -1066,9 +1079,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown", @@ -1497,6 +1510,15 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1666,6 +1688,12 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "reqwest" version = "0.13.1" @@ -1729,6 +1757,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rstest" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "948203e6d13b83e51a90d7cf236dd58a0065f23f4b902f00f306e7eb2bd09b9c" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d92eaf7b6e51e12471d71ddc72608e7151573de44099aacfbb1128177c0da4" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.106", + "unicode-ident", +] + [[package]] name = "rust-multipart-rfc7578_2" version = "0.8.0" @@ -1750,6 +1807,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustls" version = "0.23.33" @@ -2336,6 +2402,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + [[package]] name = "tower" version = "0.5.2" @@ -3078,6 +3174,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "wiremock" version = "0.6.5" diff --git a/Cargo.toml b/Cargo.toml index a6dde39..68cfbad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ flagsmith-flag-engine = "0.6" [dev-dependencies] axum-test = "18" mockall = "0.14" +rstest = "0.27" tokio-test = "0.4" tracing-test = { version = "0.2", features = ["no-env-filter"] } wiremock = "0.6" diff --git a/tests/fixtures.rs b/tests/fixtures.rs index a4f12ba..59781a1 100644 --- a/tests/fixtures.rs +++ b/tests/fixtures.rs @@ -4,6 +4,10 @@ pub fn environment_1_api_key() -> String { "environment_1_api_key".to_string() } +pub fn environment_1_server_key() -> String { + "ser.test_key".to_string() +} + pub fn environment_1() -> serde_json::Value { json!({ "updated_at": "1969-07-20T20:17:40Z", diff --git a/tests/test_cors.rs b/tests/test_cors.rs index c34414c..c7fa68b 100644 --- a/tests/test_cors.rs +++ b/tests/test_cors.rs @@ -11,7 +11,7 @@ const ORIGIN: &str = "https://app.example.com"; async fn server_allowing(allow_origins: &[&str]) -> TestServer { let settings = AppSettings { environment_key_pairs: vec![EnvironmentKeyPair { - server_side_key: "ser.test_key".to_string(), + server_side_key: environment_1_server_key(), client_side_key: environment_1_api_key(), }], allow_origins: allow_origins.iter().map(|o| o.to_string()).collect(), diff --git a/tests/test_remove_environment.rs b/tests/test_remove_environment.rs index a97af3e..f627543 100644 --- a/tests/test_remove_environment.rs +++ b/tests/test_remove_environment.rs @@ -3,17 +3,16 @@ use edge_proxy::config::settings::{AppSettings, EnvironmentKeyPair}; use edge_proxy::error::EdgeProxyError; use edge_proxy::models::IdentityWithTraits; use edge_proxy::services::environment::EnvironmentService; +use rstest::rstest; use std::sync::Arc; mod fixtures; -use fixtures::{environment_1, environment_1_api_key}; - -const TEST_SERVER_KEY: &str = "ser.test_server_key"; +use fixtures::{environment_1, environment_1_api_key, environment_1_server_key}; fn create_settings(client_key: &str) -> AppSettings { AppSettings { environment_key_pairs: vec![EnvironmentKeyPair { - server_side_key: TEST_SERVER_KEY.to_string(), + server_side_key: environment_1_server_key(), client_side_key: client_key.to_string(), }], api_url: "http://test.api".to_string(), @@ -37,7 +36,12 @@ async fn test_removed_environment_rejects_client_and_server_keys() { // Given let (service, client_key) = create_loaded_service().await; assert!(service.get_environment(&client_key).await.is_ok()); - assert!(service.get_environment(TEST_SERVER_KEY).await.is_ok()); + assert!( + service + .get_environment(&environment_1_server_key()) + .await + .is_ok() + ); // When service.remove_environment(&client_key).await; @@ -48,7 +52,7 @@ async fn test_removed_environment_rejects_client_and_server_keys() { Err(EdgeProxyError::FlagsmithUnknownKey(_)) )); assert!(matches!( - service.get_environment(TEST_SERVER_KEY).await, + service.get_environment(&environment_1_server_key()).await, Err(EdgeProxyError::FlagsmithUnknownKey(_)) )); } @@ -59,7 +63,9 @@ async fn test_remove_by_server_key_removes_the_whole_environment() { let (service, client_key) = create_loaded_service().await; // When - service.remove_environment(TEST_SERVER_KEY).await; + service + .remove_environment(&environment_1_server_key()) + .await; // Then assert!(matches!( @@ -68,13 +74,16 @@ async fn test_remove_by_server_key_removes_the_whole_environment() { )); } +#[rstest] #[tokio::test] -async fn test_removed_key_is_rejected_by_flags() { +async fn test_removed_key_is_rejected_by_flags( + #[values(environment_1_api_key(), environment_1_server_key())] environment_key: String, +) { // Given let (service, client_key) = create_loaded_service().await; assert!( service - .get_flags_response_data(&client_key, None) + .get_flags_response_data(&environment_key, None) .await .is_ok() ); @@ -84,19 +93,24 @@ async fn test_removed_key_is_rejected_by_flags() { // Then assert!(matches!( - service.get_flags_response_data(&client_key, None).await, + service + .get_flags_response_data(&environment_key, None) + .await, Err(EdgeProxyError::FlagsmithUnknownKey(_)) )); } +#[rstest] #[tokio::test] -async fn test_removed_key_is_rejected_by_identities() { +async fn test_removed_key_is_rejected_by_identities( + #[values(environment_1_api_key(), environment_1_server_key())] environment_key: String, +) { // Given let (service, client_key) = create_loaded_service().await; let identity = IdentityWithTraits::new("some-user".to_string()); assert!( service - .get_identity_response_data(&identity, &client_key) + .get_identity_response_data(&identity, &environment_key) .await .is_ok() ); @@ -107,7 +121,7 @@ async fn test_removed_key_is_rejected_by_identities() { // Then assert!(matches!( service - .get_identity_response_data(&identity, &client_key) + .get_identity_response_data(&identity, &environment_key) .await, Err(EdgeProxyError::FlagsmithUnknownKey(_)) )); @@ -118,7 +132,12 @@ async fn test_removed_key_is_rejected_by_environment_document() { // Given let (service, client_key) = create_loaded_service().await; assert!(service.get_environment_bytes(&client_key).await.is_ok()); - assert!(service.get_environment_bytes(TEST_SERVER_KEY).await.is_ok()); + assert!( + service + .get_environment_bytes(&environment_1_server_key()) + .await + .is_ok() + ); // When service.remove_environment(&client_key).await; @@ -129,7 +148,9 @@ async fn test_removed_key_is_rejected_by_environment_document() { Err(EdgeProxyError::FlagsmithUnknownKey(_)) )); assert!(matches!( - service.get_environment_bytes(TEST_SERVER_KEY).await, + service + .get_environment_bytes(&environment_1_server_key()) + .await, Err(EdgeProxyError::FlagsmithUnknownKey(_)) )); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 9dab6cc..66aeafd 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -4,12 +4,13 @@ use axum_test::TestServer; use edge_proxy::config::settings::{AppSettings, EnvironmentKeyPair}; use edge_proxy::routes::create_router; use fixtures::*; +use rstest::rstest; use serde_json::json; async fn setup_test_server() -> TestServer { let settings = AppSettings { environment_key_pairs: vec![EnvironmentKeyPair { - server_side_key: "ser.test_key".to_string(), + server_side_key: environment_1_server_key(), client_side_key: environment_1_api_key(), }], api_url: "http://test".to_string(), @@ -26,22 +27,30 @@ async fn setup_test_server() -> TestServer { TestServer::new(app).unwrap() } +#[rstest] +#[case::client_key(environment_1_api_key(), &["feature_1", "feature_2"])] +#[case::server_key(environment_1_server_key(), &["feature_1", "feature_2", "feature_3"])] #[tokio::test] -async fn test_get_flags() { +async fn test_get_flags(#[case] environment_key: String, #[case] expected_features: &[&str]) { // Given let server = setup_test_server().await; // When let response = server .get("/api/v1/flags") - .add_header("X-Environment-Key", &environment_1_api_key()) + .add_header("X-Environment-Key", &environment_key) .await; // Then response.assert_status_ok(); let body: serde_json::Value = response.json(); let flags = body.as_array().unwrap(); - assert_eq!(flags.len(), 2); + let mut feature_names: Vec<&str> = flags + .iter() + .map(|f| f["feature"]["name"].as_str().unwrap()) + .collect(); + feature_names.sort(); + assert_eq!(feature_names, expected_features); let feature_1 = flags .iter() @@ -60,15 +69,18 @@ async fn test_get_flags() { assert_eq!(feature_2["feature_state_value"], "2.3"); } +#[rstest] #[tokio::test] -async fn test_get_flags_single_feature() { +async fn test_get_flags_single_feature( + #[values(environment_1_api_key(), environment_1_server_key())] environment_key: String, +) { // Given let server = setup_test_server().await; // When let response = server .get("/api/v1/flags?feature=feature_1") - .add_header("X-Environment-Key", &environment_1_api_key()) + .add_header("X-Environment-Key", &environment_key) .await; // Then @@ -99,75 +111,48 @@ async fn test_get_flags_single_feature_server_key_only_returns_404() { } #[tokio::test] -async fn test_get_flags_unknown_key() { - // Given - let server = setup_test_server().await; - - // When - let response = server - .get("/api/v1/flags") - .add_header("X-Environment-Key", "unknown_key") - .await; - - // Then - response.assert_status(axum::http::StatusCode::UNAUTHORIZED); - let body: serde_json::Value = response.json(); - assert_eq!(body["status"], "unauthorized"); - assert!(body["message"].as_str().unwrap().contains("unknown key")); -} - -#[tokio::test] -async fn test_get_flags_server_side_key_includes_server_key_only_features() { +async fn test_get_flags_single_feature_server_key_only_with_server_key() { // Given let server = setup_test_server().await; // When let response = server - .get("/api/v1/flags") - .add_header("X-Environment-Key", "ser.test_key") + .get("/api/v1/flags?feature=feature_3") + .add_header("X-Environment-Key", &environment_1_server_key()) .await; // Then response.assert_status_ok(); let body: serde_json::Value = response.json(); - let mut feature_names: Vec<&str> = body - .as_array() - .unwrap() - .iter() - .map(|f| f["feature"]["name"].as_str().unwrap()) - .collect(); - feature_names.sort(); - assert_eq!(feature_names, ["feature_1", "feature_2", "feature_3"]); + assert_eq!(body["feature"]["name"], "feature_3"); } #[tokio::test] -async fn test_post_identities_server_side_key_includes_server_key_only_features() { +async fn test_get_flags_unknown_key() { // Given let server = setup_test_server().await; - let identity_data = json!({"identifier": "test_identity", "traits": []}); // When let response = server - .post("/api/v1/identities/") - .add_header("X-Environment-Key", "ser.test_key") - .json(&identity_data) + .get("/api/v1/flags") + .add_header("X-Environment-Key", "unknown_key") .await; // Then - response.assert_status_ok(); + response.assert_status(axum::http::StatusCode::UNAUTHORIZED); let body: serde_json::Value = response.json(); - let mut feature_names: Vec<&str> = body["flags"] - .as_array() - .unwrap() - .iter() - .map(|f| f["feature"]["name"].as_str().unwrap()) - .collect(); - feature_names.sort(); - assert_eq!(feature_names, ["feature_1", "feature_2", "feature_3"]); + assert_eq!(body["status"], "unauthorized"); + assert!(body["message"].as_str().unwrap().contains("unknown key")); } +#[rstest] +#[case::client_key(environment_1_api_key(), &["feature_1", "feature_2"])] +#[case::server_key(environment_1_server_key(), &["feature_1", "feature_2", "feature_3"])] #[tokio::test] -async fn test_post_identity_with_traits() { +async fn test_post_identity_with_traits( + #[case] environment_key: String, + #[case] expected_features: &[&str], +) { // Given let server = setup_test_server().await; let identity_data = json!({ @@ -180,7 +165,7 @@ async fn test_post_identity_with_traits() { // When let response = server .post("/api/v1/identities/") - .add_header("X-Environment-Key", &environment_1_api_key()) + .add_header("X-Environment-Key", &environment_key) .json(&identity_data) .await; @@ -189,20 +174,22 @@ async fn test_post_identity_with_traits() { let body: serde_json::Value = response.json(); assert!(body["flags"].is_array()); let flags = body["flags"].as_array().unwrap(); - assert_eq!(flags.len(), 2); assert_eq!(body["traits"][0]["trait_key"], "email"); assert_eq!(body["traits"][0]["trait_value"], "test@example.com"); - let feature_names: Vec<&str> = flags + let mut feature_names: Vec<&str> = flags .iter() .map(|f| f["feature"]["name"].as_str().unwrap()) .collect(); - assert!(feature_names.contains(&"feature_1")); - assert!(feature_names.contains(&"feature_2")); + feature_names.sort(); + assert_eq!(feature_names, expected_features); } +#[rstest] #[tokio::test] -async fn test_post_identity_with_override() { +async fn test_post_identity_with_override( + #[values(environment_1_api_key(), environment_1_server_key())] environment_key: String, +) { // Given let server = setup_test_server().await; let identity_data = json!({ @@ -213,7 +200,7 @@ async fn test_post_identity_with_override() { // When let response = server .post("/api/v1/identities/") - .add_header("X-Environment-Key", &environment_1_api_key()) + .add_header("X-Environment-Key", &environment_key) .json(&identity_data) .await; @@ -229,15 +216,18 @@ async fn test_post_identity_with_override() { assert_eq!(feature_1["enabled"], true); } +#[rstest] #[tokio::test] -async fn test_get_identities() { +async fn test_get_identities( + #[values(environment_1_api_key(), environment_1_server_key())] environment_key: String, +) { // Given let server = setup_test_server().await; // When let response = server .get("/api/v1/identities/?identifier=test_identity") - .add_header("X-Environment-Key", &environment_1_api_key()) + .add_header("X-Environment-Key", &environment_key) .await; // Then From ac9064d82e78d564c8f99449104ab663b2e3149a Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 17 Oct 2026 14:58:46 +0530 Subject: [PATCH 3/4] test: drop redundant server-key single-feature test --- tests/test_server.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 66aeafd..46f58e9 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -110,23 +110,6 @@ async fn test_get_flags_single_feature_server_key_only_returns_404() { assert_eq!(body["message"], "feature 'feature_3' not found"); } -#[tokio::test] -async fn test_get_flags_single_feature_server_key_only_with_server_key() { - // Given - let server = setup_test_server().await; - - // When - let response = server - .get("/api/v1/flags?feature=feature_3") - .add_header("X-Environment-Key", &environment_1_server_key()) - .await; - - // Then - response.assert_status_ok(); - let body: serde_json::Value = response.json(); - assert_eq!(body["feature"]["name"], "feature_3"); -} - #[tokio::test] async fn test_get_flags_unknown_key() { // Given From 4133667bb53298e924e9678fc432ce8956d4a096 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 17 Oct 2026 15:17:57 +0530 Subject: [PATCH 4/4] test: assert server-key-only features on GET /identities --- tests/test_server.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 46f58e9..32a1b4e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -200,10 +200,10 @@ async fn test_post_identity_with_override( } #[rstest] +#[case::client_key(environment_1_api_key(), &["feature_1", "feature_2"])] +#[case::server_key(environment_1_server_key(), &["feature_1", "feature_2", "feature_3"])] #[tokio::test] -async fn test_get_identities( - #[values(environment_1_api_key(), environment_1_server_key())] environment_key: String, -) { +async fn test_get_identities(#[case] environment_key: String, #[case] expected_features: &[&str]) { // Given let server = setup_test_server().await; @@ -216,7 +216,14 @@ async fn test_get_identities( // Then response.assert_status_ok(); let body: serde_json::Value = response.json(); - assert!(body["flags"].is_array()); + let mut feature_names: Vec<&str> = body["flags"] + .as_array() + .unwrap() + .iter() + .map(|f| f["feature"]["name"].as_str().unwrap()) + .collect(); + feature_names.sort(); + assert_eq!(feature_names, expected_features); assert!(body["traits"].is_array()); assert_eq!(body["traits"].as_array().unwrap().len(), 0); }