use std::collections::HashMap;
use std::path::PathBuf;
use http::StatusCode;
use crate::integration::IntegrationTest;
use crate::integration::common::TEST_JWKS_ENDPOINT;
// NOTE: if these tests fail for haltAt/warnAt related reasons (that they're in the past), go to
// jwt.io and doublecheck that those claims are still sensible. There's an issue when using
// Instants to schedule things (like we do for license streams) if those Instants are derived from
// some far-future SystemTime: tokio has an upper bound for how far out it schedules, putting a
// pretty hard limit (about a year) for what we can set the haltAt/warnAt values in JWTs to
const LICENSE_ALLOWED_FEATURES_DOES_NOT_INCLUDE_FEATURE_MSG: &str =
"license violation, the router is using features not available for your license";
const LICENSE_EXPIRED_MESSAGE: &str =
"License has expired. The Router will no longer serve requests.";
const JWT_WITH_EMPTY_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiYWxsb3dlZEZlYXR1cmVzIjogW10sCiAgImlzcyI6ICJodHRwczovL3d3dy5hcG9sbG9ncmFwaHFsLmNvbS8iLAogICJzdWIiOiAiYXBvbGxvIiwKICAiYXVkIjogIlNFTEZfSE9TVEVEIiwgCiAgIndhcm5BdCI6IDE3ODcwMDAwMDAsCiAgImhhbHRBdCI6IDE3ODcwMDAwMDAKfQ.nERzNxBzt7KLgBD4ouHydbht6_1jgyCYF8aKzFKGjhI"; // gitleaks:allow
const JWT_WITH_COPROCESSORS_IN_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiYWxsb3dlZEZlYXR1cmVzIjogWyJjb3Byb2Nlc3NvcnMiXSwKICAiaXNzIjogImh0dHBzOi8vd3d3LmFwb2xsb2dyYXBocWwuY29tLyIsCiAgInN1YiI6ICJhcG9sbG8iLAogICJhdWQiOiAiU0VMRl9IT1NURUQiLCAKICAid2FybkF0IjogMTc4NzAwMDAwMCwKICAiaGFsdEF0IjogMTc4NzAwMDAwMAp9.UD2JZtyvCSY6oXeDOsmWZehNGQjDqdhOiw-1f2TW4Og"; // gitleaks:allow
// In the CI environment we only install Redis on x86_64 Linux; this jwt is part of testing that
// flow
#[cfg(any(not(feature = "ci"), all(target_arch = "x86_64", target_os = "linux")))]
const JWT_WITH_ENTITY_CACHING_COPROCESSORS_IN_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiaXNzIjogImh0dHBzOi8vd3d3LmFwb2xsb2dyYXBocWwuY29tLyIsCiAgInN1YiI6ICJhcG9sbG8iLAogICJhbGxvd2VkRmVhdHVyZXMiOiBbImVudGl0eV9jYWNoaW5nIiwgImNvcHJvY2Vzc29ycyJdLAogICJhdWQiOiAiU0VMRl9IT1NURUQiLCAKICAid2FybkF0IjogMTc4NzAwMDAwMCwgCiAgImhhbHRBdCI6IDE3ODcwMDAwMDAKfQ.HD_xzVtrXzXp8PdosAircXWPtnVaPRE-N2ZDlv6Llfo"; // gitleaks:allow
const JWT_WITH_COPROCESSORS_SUBSCRIPTION_IN_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiYWxsb3dlZEZlYXR1cmVzIjogWwogICAgImNvcHJvY2Vzc29ycyIsCiAgICAic3Vic2NyaXB0aW9ucyIKICBdLAogICJpc3MiOiAiaHR0cHM6Ly93d3cuYXBvbGxvZ3JhcGhxbC5jb20vIiwKICAic3ViIjogImFwb2xsbyIsCiAgImF1ZCI6ICJTRUxGX0hPU1RFRCIsIAogICJ3YXJuQXQiOiAxNzg3MDAwMDAwLAogICJoYWx0QXQiOiAxNzg3MDAwMDAwCn0.MxjeQOea7wBjvs1J0-44oEfdoaVwKuEexy-JdgZ-3R8"; // gitleaks:allow
const JWT_WITH_ALLOWED_FEATURES_NONE: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiaXNzIjogImh0dHBzOi8vd3d3LmFwb2xsb2dyYXBocWwuY29tLyIsCiAgInN1YiI6ICJhcG9sbG8iLAogICJhdWQiOiAiU0VMRl9IT1NURUQiLCAKICAid2FybkF0IjogMTc4NzAwMDAwMCwKICAiaGFsdEF0IjogMTc4NzAwMDAwMAp9.LPNJgPY20DH054mXgrzaxEFiME656ZJ-ge5y9Zh3kkc"; // gitleaks:allow
const JWT_WITH_ALLOWED_FEATURES_COPROCESSOR_WITH_FEATURE_UNDEFINED_IN_ROUTER: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiYWxsb3dlZEZlYXR1cmVzIjogWwogICAgImNvcHJvY2Vzc29ycyIsCiAgICAicmFuZG9tIiwKICAgICJzdWJzY3JpcHRpb25zIgogIF0sCiAgImlzcyI6ICJodHRwczovL3d3dy5hcG9sbG9ncmFwaHFsLmNvbS8iLAogICJzdWIiOiAiYXBvbGxvIiwKICAiYXVkIjogIlNFTEZfSE9TVEVEIiwgCiAgIndhcm5BdCI6IDE3ODcwMDAwMDAsCiAgImhhbHRBdCI6IDE3ODcwMDAwMDAKfQ.l4O-YLwIu2hjoSq1HseJQMS_9qFNL9v304I7gfLqV3w"; // gitleaks:allow
const JWT_WITH_ENTITY_CACHING_COPROCESSORS_TRAFFIC_SHAPING_IN_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiaXNzIjogImh0dHBzOi8vd3d3LmFwb2xsb2dyYXBocWwuY29tLyIsCiAgInN1YiI6ICJhcG9sbG8iLAogICJhbGxvd2VkRmVhdHVyZXMiOiBbImVudGl0eV9jYWNoaW5nIiwgImNvcHJvY2Vzc29ycyIsICJ0cmFmZmljX3NoYXBpbmciXSwKICAiYXVkIjogIlNFTEZfSE9TVEVEIiwgCiAgIndhcm5BdCI6IDE3ODcwMDAwMDAsIAogICJoYWx0QXQiOiAxNzg3MDAwMDAwCn0.HHfLHmDAjTdQwouAJguvWnpxnHsLzTWswQl70gmkMEM"; // gitleaks:allow
const JWT_PAST_EXPIRY_WITH_COPROCESSORS_ENTITY_CACHING_TRAFFIC_SHAPING_SUBSCRIPTIONS_IN_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiaXNzIjogImh0dHBzOi8vd3d3LmFwb2xsb2dyYXBocWwuY29tLyIsCiAgInN1YiI6ICJhcG9sbG8iLAogICJhbGxvd2VkRmVhdHVyZXMiOiBbImNvcHJvY2Vzc29ycyIsICJlbnRpdHlfY2FjaGluZyIsICJ0cmFmZmljX3NoYXBpbmciLCAic3Vic2NyaXB0aW9ucyJdLAogICJhdWQiOiAiU0VMRl9IT1NURUQiLCAKICAid2FybkF0IjogMTc1NTMwMjQwMCwgCiAgImhhbHRBdCI6IDE3NTUzMDI0MDAKfQ.2TPyUd9BUn3NCc2Kq8WsJS_6V16s2lgitElhf0lNcwg"; // gitleaks:allow
const JWT_PAST_EXPIRY_WITH_COPROCESSORS_ENTITY_CACHING_TRAFFIC_SHAPING_IN_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiaXNzIjogImh0dHBzOi8vd3d3LmFwb2xsb2dyYXBocWwuY29tLyIsCiAgInN1YiI6ICJhcG9sbG8iLAogICJhbGxvd2VkRmVhdHVyZXMiOiBbImNvcHJvY2Vzc29ycyIsICJlbnRpdHlfY2FjaGluZyIsICJ0cmFmZmljX3NoYXBpbmciXSwKICAiYXVkIjogIlNFTEZfSE9TVEVEIiwgCiAgIndhcm5BdCI6IDE3NTUzMDI0MDAsIAogICJoYWx0QXQiOiAxNzU1MzAyNDAwCn0.CERblSGfOVmKt6PtfB2LjnY-ahzMsNB4EGajXZfKWU4"; // gitleaks:allow
const JWT_PAST_WARN_AT_BUT_NOT_EXPIRED_WITH_COPROCESSORS_ENTITY_CACHING_TRAFFIC_SHAPING_IN_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiaXNzIjogImh0dHBzOi8vd3d3LmFwb2xsb2dyYXBocWwuY29tLyIsCiAgInN1YiI6ICJhcG9sbG8iLAogICJhbGxvd2VkRmVhdHVyZXMiOiBbImVudGl0eV9jYWNoaW5nIiwgImNvcHJvY2Vzc29ycyIsICJ0cmFmZmljX3NoYXBpbmciXSwKICAiYXVkIjogIlNFTEZfSE9TVEVEIiwgCiAgIndhcm5BdCI6IDE3NjU5MTA0MDAsIAogICJoYWx0QXQiOiAxNzg3MDAwMDAwCn0.33EWawSaU8dv5KqI8QbAzYFa0KKTcvqTXGaJfRkg-DU"; // gitleaks:allow
const JWT_PAST_WARN_AT_BUT_NOT_EXPIRED_WITH_COPROCESSORS_SUBSCRIPTIONS_IN_ALLOWED_FEATURES: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJleHAiOiAxMDAwMDAwMDAwMCwKICAiaXNzIjogImh0dHBzOi8vd3d3LmFwb2xsb2dyYXBocWwuY29tLyIsCiAgInN1YiI6ICJhcG9sbG8iLAogICJhbGxvd2VkRmVhdHVyZXMiOiBbInN1YnNjcmlwdGlvbnMiLCAiY29wcm9jZXNzb3JzIl0sCiAgImF1ZCI6ICJTRUxGX0hPU1RFRCIsIAogICJ3YXJuQXQiOiAxNzU1MzAyNDAwLCAKICAiaGFsdEF0IjogMTc4NzAwMDAwMAp9.nxyKlFquWBijtIOtL8FnknNfAwvBaZh9TFIDcG7NtiE"; // gitleaks:allow
const SUBSCRIPTION_CONFIG: &str = include_str!("subscriptions/fixtures/subscription.router.yaml");
const SUBSCRIPTION_COPROCESSOR_CONFIG: &str =
include_str!("subscriptions/fixtures/subscription_coprocessor.router.yaml");
const FILE_UPLOADS_CONFIG: &str =
include_str!("../../tests/fixtures/file_upload/default.router.yaml");
/*
* GIVEN
* - a valid license whose `allowed_features` claim contains the feature
* - a valid config
* - a valid schema
*
* THEN
* - since the feature is part of the `allowed_features` set
* the router should start successfully with no license violations
* */
#[tokio::test(flavor = "multi_thread")]
async fn traffic_shaping_when_allowed_features_contains_feature() {
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.config(
r#"
telemetry:
exporters:
metrics:
prometheus:
listen: 127.0.0.1:4000
enabled: true
path: /metrics
include_subgraph_errors:
all: true
traffic_shaping:
all:
timeout: 1ns
"#,
)
.env(env)
.jwt(JWT_WITH_ENTITY_CACHING_COPROCESSORS_TRAFFIC_SHAPING_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
router.assert_started().await;
router.assert_no_error_logs();
}
// In the CI environment we only install Redis on x86_64 Linux
#[cfg(any(not(feature = "ci"), all(target_arch = "x86_64", target_os = "linux")))]
#[tokio::test(flavor = "multi_thread")]
async fn connectors_with_entity_caching_enabled_when_allowed_features_contains_features() {
use crate::integration::common::TEST_JWKS_ENDPOINT;
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.config(
r#"
preview_entity_cache:
enabled: true
subgraph:
all:
redis:
urls: ["redis://127.0.0.1:6379"]
ttl: "10m"
required_to_start: true
subgraphs:
connectors:
enabled: true
"#,
)
.supergraph(PathBuf::from_iter([
"tests",
"fixtures",
"connectors",
"quickstart.graphql",
]))
.env(env)
.jwt(JWT_WITH_ENTITY_CACHING_COPROCESSORS_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
router.assert_started().await;
router.assert_no_error_logs();
}
#[tokio::test(flavor = "multi_thread")]
async fn subscription_coprocessors_enabled_when_allowed_features_contains_both_features() {
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.supergraph(PathBuf::from_iter([
"tests",
"integration",
"subscriptions",
"fixtures",
"supergraph.graphql",
]))
.config(SUBSCRIPTION_COPROCESSOR_CONFIG)
.env(env)
.jwt(JWT_WITH_COPROCESSORS_SUBSCRIPTION_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.replace_config_string("http://localhost:{{PRODUCTS_PORT}}", "5000");
router.replace_config_string("http://localhost:{{ACCOUNTS_PORT}}", "5001");
router.replace_config_string("http://localhost:{{COPROCESSOR_PORT}}", "5002");
router.start().await;
router.assert_started().await;
router.assert_no_error_logs();
}
/*
* GIVEN
* - a valid license that does not contain an `allowed_features` claim
* - a valid config
* - a valid schema
*
* THEN
* - router should start successfully
* NB: this behavior will change once allowed_features claim is contained in all licenses
*/
#[tokio::test(flavor = "multi_thread")]
async fn oss_feature_apq_enabled_when_allowed_features_empty() {
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.config(
r#"
apq:
enabled: true
"#,
)
.env(env)
.jwt(JWT_WITH_EMPTY_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
// Apq is an oss feature
router.assert_started().await;
router.assert_no_error_logs();
}
#[tokio::test(flavor = "multi_thread")]
async fn oss_feature_file_uploads_enabled_with_non_empty_allowed_features() {
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.config(FILE_UPLOADS_CONFIG)
.env(env)
.jwt(JWT_WITH_ENTITY_CACHING_COPROCESSORS_TRAFFIC_SHAPING_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
// File uploads is an oss plugin
router.assert_started().await;
router.assert_no_error_logs();
}
#[tokio::test(flavor = "multi_thread")]
async fn router_starts_when_allowed_features_contains_feature_undefined_in_router() {
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mock_server = wiremock::MockServer::start().await;
let coprocessor_address = mock_server.uri();
let mut router = IntegrationTest::builder()
.config(
include_str!("fixtures/coprocessor.router.yaml")
.replace("<replace>", &coprocessor_address),
)
.env(env)
.jwt(JWT_WITH_ALLOWED_FEATURES_COPROCESSOR_WITH_FEATURE_UNDEFINED_IN_ROUTER.to_string())
.build()
.await;
router.start().await;
router.assert_started().await;
router.assert_no_error_logs();
}
/*
* GIVEN
* - a valid license that does not contain an `allowed_features` claim
* - a valid config
* - a valid schema
*
* THEN
* - router should start successfully
* NB: this behavior will change once allowed_features claim is contained in all licenses
*/
#[tokio::test(flavor = "multi_thread")]
async fn subscription_coprocessors_enabled_when_allowed_features_none() {
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.supergraph(PathBuf::from_iter([
"tests",
"integration",
"subscriptions",
"fixtures",
"supergraph.graphql",
]))
.config(SUBSCRIPTION_COPROCESSOR_CONFIG)
.env(env)
.jwt(JWT_WITH_ALLOWED_FEATURES_NONE.to_string())
.build()
.await;
router.replace_config_string("http://localhost:{{PRODUCTS_PORT}}", "5000");
router.replace_config_string("http://localhost:{{ACCOUNTS_PORT}}", "5001");
router.replace_config_string("http://localhost:{{COPROCESSOR_PORT}}", "5002");
router.start().await;
router.assert_started().await;
router.assert_no_error_logs();
}
#[tokio::test(flavor = "multi_thread")]
async fn demand_control_enabledwhen_allowed_features_none() {
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mock_server = wiremock::MockServer::start().await;
let coprocessor_address = mock_server.uri();
let mut router = IntegrationTest::builder()
.config(
include_str!("fixtures/coprocessor_demand_control.router.yaml")
.replace("<replace>", &coprocessor_address),
)
.env(env)
.jwt(JWT_WITH_ALLOWED_FEATURES_NONE.to_string())
.build()
.await;
router.start().await;
router.assert_started().await;
router.assert_no_error_logs();
}
/*
* GIVEN
* - a valid license whose `allowed_features` claim is empty (does not contain any features)
* or more features
* - a valid config
* - a valid schema
*
* THEN
* - since the feature(s) is/are not part of the `allowed_features` set
* the router should should emit an error log containing the license violations
* */
#[tokio::test(flavor = "multi_thread")]
async fn feature_violation_when_allowed_features_empty_with_coprocessor_in_config() {
let mock_server = wiremock::MockServer::start().await;
let coprocessor_address = mock_server.uri();
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.config(
include_str!("fixtures/coprocessor.router.yaml")
.replace("<replace>", &coprocessor_address),
)
.env(env)
.jwt(JWT_WITH_EMPTY_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
router
.assert_error_log_contained(LICENSE_ALLOWED_FEATURES_DOES_NOT_INCLUDE_FEATURE_MSG)
.await;
}
#[tokio::test(flavor = "multi_thread")]
async fn feature_violation_when_allowed_features_empty_with_subscripton_in_config() {
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.supergraph("tests/integration/subscriptions/fixtures/supergraph.graphql")
.config(SUBSCRIPTION_CONFIG)
.env(env)
.jwt(JWT_WITH_EMPTY_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
router
.assert_error_log_contained(LICENSE_ALLOWED_FEATURES_DOES_NOT_INCLUDE_FEATURE_MSG)
.await;
}
/*
* GIVEN
* - a valid license whose `allowed_features` claim does not contain one
* or more features
* - a valid config
* - a valid schema
*
* THEN
* - since the feature(s) is/are not part of the `allowed_features` set
* the router should should emit an error log containing the license violations
* */
#[tokio::test(flavor = "multi_thread")]
async fn feature_violation_when_allowed_features_does_not_contain_feature_demand_control() {
let mock_server = wiremock::MockServer::start().await;
let coprocessor_address = mock_server.uri();
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.config(
include_str!("fixtures/coprocessor_demand_control.router.yaml")
.replace("<replace>", &coprocessor_address),
)
.env(env)
.jwt(JWT_WITH_COPROCESSORS_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
router
.assert_error_log_contained(LICENSE_ALLOWED_FEATURES_DOES_NOT_INCLUDE_FEATURE_MSG)
.await;
}
#[tokio::test(flavor = "multi_thread")]
async fn feature_violation_when_allowed_features_with_coprocessor_only_with_subscripton_and_coprocessor_in_config()
{
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.supergraph("tests/integration/subscriptions/fixtures/supergraph.graphql")
.config(SUBSCRIPTION_COPROCESSOR_CONFIG)
.env(env)
.jwt(JWT_WITH_COPROCESSORS_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
router
.assert_error_log_contained(LICENSE_ALLOWED_FEATURES_DOES_NOT_INCLUDE_FEATURE_MSG)
.await;
}
#[tokio::test(flavor = "multi_thread")]
async fn canned_response_when_license_halted_with_valid_config_and_schema() {
/*
* GIVEN
* - an expired license
* - a valid config
* - a valid schema
* */
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.supergraph("tests/integration/subscriptions/fixtures/supergraph.graphql")
.config(SUBSCRIPTION_COPROCESSOR_CONFIG)
.env(env)
.jwt(JWT_PAST_EXPIRY_WITH_COPROCESSORS_ENTITY_CACHING_TRAFFIC_SHAPING_SUBSCRIPTIONS_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.replace_config_string("http://localhost:{{PRODUCTS_PORT}}", "localhost:4001");
router.replace_config_string("http://localhost:{{ACCOUNTS_PORT}}", "localhost:4002");
router.replace_config_string("http://localhost:{{COPROCESSOR_PORT}}", "5002");
/*
* THEN
* - since the license is expired and using restricted features the router should start but
* the axum middleware, license_handler, should return a 500
* */
router.start().await;
router
.assert_error_log_contained(LICENSE_EXPIRED_MESSAGE)
.await;
let (_, response) = router.execute_default_query().await;
// We expect the axum middleware for handling halted licenses to return a server error
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test(flavor = "multi_thread")]
async fn canned_response_when_license_halted_with_restricted_config_and_valid_schema() {
/*
* GIVEN
* - an expired license
* - an invalid config - that contains a feature not in the allowedFeatures claim
* - a valid schema
* */
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
// subscriptions not an allowed feature--config invalid
let mut router = IntegrationTest::builder()
.supergraph("tests/integration/subscriptions/fixtures/supergraph.graphql")
.config(SUBSCRIPTION_COPROCESSOR_CONFIG)
.env(env)
// jwt's allowed features does not contain subscriptions
.jwt(
JWT_PAST_EXPIRY_WITH_COPROCESSORS_ENTITY_CACHING_TRAFFIC_SHAPING_IN_ALLOWED_FEATURES
.to_string(),
)
.build()
.await;
router.replace_config_string("http://localhost:{{PRODUCTS_PORT}}", "localhost:4001");
router.replace_config_string("http://localhost:{{ACCOUNTS_PORT}}", "localhost:4002");
router.replace_config_string("http://localhost:{{COPROCESSOR_PORT}}", "5002");
/*
* THEN
* - since the license is expired and using restricted features the router should start but
* the axum middleware, license_handler, should return a 500
* */
router.start().await;
router
.assert_error_log_contained(LICENSE_EXPIRED_MESSAGE)
.await;
let (_, response) = router.execute_default_query().await;
// We expect the axum middleware for handling halted licenses to return a server error
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test(flavor = "multi_thread")]
async fn canned_response_when_license_halted_with_valid_config_and_invalid_schema() {
/*
* GIVEN
* - an expired license
* - a valid config
* - a invalid schema - that contains a feature not in the allowedFeatures claim
* */
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
// contextArgument is restricted for this JWT
let mut router = IntegrationTest::builder()
.supergraph("tests/integration/fixtures/authenticated_directive.graphql")
.config(FILE_UPLOADS_CONFIG)
.env(env)
.jwt(JWT_PAST_EXPIRY_WITH_COPROCESSORS_ENTITY_CACHING_TRAFFIC_SHAPING_SUBSCRIPTIONS_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.replace_config_string("http://localhost:{{PRODUCTS_PORT}}", "localhost:4001");
router.replace_config_string("http://localhost:{{ACCOUNTS_PORT}}", "localhost:4002");
router.replace_config_string("http://localhost:{{COPROCESSOR_PORT}}", "5002");
/*
* THEN
* - since the license is expired and using restricted features the router should start but
* the axum middleware, license_handler, should return a 500
* */
router.start().await;
router
.assert_error_log_contained(LICENSE_EXPIRED_MESSAGE)
.await;
let (_, response) = router.execute_default_query().await;
// We expect the axum middleware for handling halted licenses to return a server error
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
/*
* GIVEN
* - a license past the warnAt date but not yet expired but the features in use contained
* in the allowedFeatures claim
* - a valid config
* - a valid schema
*
* THEN
* - since the license is not yet expired, the router should start with restricted features in use
* */
#[tokio::test(flavor = "multi_thread")]
async fn router_starts_when_license_past_warn_at_but_not_expired_allowed_features_contains_feature_subscriptions()
{
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.supergraph("tests/integration/subscriptions/fixtures/supergraph.graphql")
.config(SUBSCRIPTION_COPROCESSOR_CONFIG)
.env(env)
.jwt(
JWT_PAST_WARN_AT_BUT_NOT_EXPIRED_WITH_COPROCESSORS_SUBSCRIPTIONS_IN_ALLOWED_FEATURES
.to_string(),
)
.build()
.await;
router.replace_config_string("http://localhost:{{PRODUCTS_PORT}}", "localhost:4001");
router.replace_config_string("http://localhost:{{ACCOUNTS_PORT}}", "localhost:4002");
router.replace_config_string("http://localhost:{{COPROCESSOR_PORT}}", "5002");
router.start().await;
router.assert_started().await;
}
// In the CI environment we only install Redis on x86_64 Linux
#[cfg(any(not(feature = "ci"), all(target_arch = "x86_64", target_os = "linux")))]
#[tokio::test(flavor = "multi_thread")]
async fn router_starts_when_license_past_warn_at_but_not_expired_allowed_features_contains_feature_entity_caching()
{
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.config(
r#"
preview_entity_cache:
enabled: true
subgraph:
all:
redis:
urls: ["redis://127.0.0.1:6379"]
ttl: "10m"
required_to_start: true
subgraphs:
connectors:
enabled: true
"#,
)
.supergraph(PathBuf::from_iter([
"tests",
"fixtures",
"connectors",
"quickstart.graphql",
]))
.env(env)
.jwt(JWT_PAST_WARN_AT_BUT_NOT_EXPIRED_WITH_COPROCESSORS_ENTITY_CACHING_TRAFFIC_SHAPING_IN_ALLOWED_FEATURES.to_string())
.build()
.await;
router.start().await;
router.assert_started().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn feature_violation_when_license_past_warn_at_but_not_expired_allowed_features_does_not_contain_feature()
{
let mut env = HashMap::new();
env.insert(
"APOLLO_TEST_INTERNAL_UPLINK_JWKS".to_string(),
TEST_JWKS_ENDPOINT.as_os_str().into(),
);
let mut router = IntegrationTest::builder()
.supergraph("tests/integration/subscriptions/fixtures/supergraph.graphql")
.config(SUBSCRIPTION_COPROCESSOR_CONFIG)
.env(env)
// jwt's allowed features does not contain subscriptions
.jwt(
JWT_PAST_WARN_AT_BUT_NOT_EXPIRED_WITH_COPROCESSORS_ENTITY_CACHING_TRAFFIC_SHAPING_IN_ALLOWED_FEATURES
.to_string(),
)
.build()
.await;
router.replace_config_string("http://localhost:{{PRODUCTS_PORT}}", "5000");
router.replace_config_string("http://localhost:{{ACCOUNTS_PORT}}", "5001");
router.replace_config_string("http://localhost:{{COPROCESSOR_PORT}}", "5002");
router.replace_config_string("http://localhost:{{PRODUCTS_PORT}}", "localhost:4001");
router.replace_config_string("http://localhost:{{ACCOUNTS_PORT}}", "localhost:4002");
router.start().await;
router
.assert_error_log_contained(LICENSE_ALLOWED_FEATURES_DOES_NOT_INCLUDE_FEATURE_MSG)
.await;
}