From 68fe2bbfd2e191b39825c6449c3c49ef1c849dee Mon Sep 17 00:00:00 2001 From: Neil Kakkar Date: Thu, 2 May 2024 18:00:48 +0100 Subject: [PATCH] clean add test --- Cargo.lock | 33 ++++++++-------- Cargo.toml | 2 +- feature-flags/Cargo.toml | 11 +++--- feature-flags/src/config.rs | 7 +--- feature-flags/src/router.rs | 7 +--- feature-flags/src/v0_endpoint.rs | 6 +-- feature-flags/src/v0_request.rs | 7 ---- feature-flags/tests/common.rs | 66 +++++++++++++++++++++++++++++++ feature-flags/tests/test_flags.rs | 43 ++++++++++++++++++++ 9 files changed, 137 insertions(+), 45 deletions(-) create mode 100644 feature-flags/tests/common.rs create mode 100644 feature-flags/tests/test_flags.rs diff --git a/Cargo.lock b/Cargo.lock index 4de877a..5fb1287 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.1.0", + "hyper 1.3.1", "hyper-util", "itoa", "matchit", @@ -273,7 +273,7 @@ dependencies = [ "bytes", "http 1.1.0", "http-body 1.0.0", - "hyper 1.1.0", + "hyper 1.3.1", "reqwest 0.11.24", "serde", "tokio", @@ -352,9 +352,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "capture" @@ -711,21 +711,19 @@ name = "feature-flags" version = "0.1.0" dependencies = [ "anyhow", + "assert-json-diff", "async-trait", "axum 0.7.5", "axum-client-ip", "base64 0.22.0", "bytes", "envconfig", - "flate2", - "governor", - "metrics", + "once_cell", "rand", - "rdkafka", "redis", + "reqwest 0.12.3", "serde", "serde_json", - "serde_urlencoded", "thiserror", "tokio", "tracing", @@ -1266,9 +1264,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -1280,6 +1278,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "smallvec", "tokio", "want", ] @@ -1318,7 +1317,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.1.0", + "hyper 1.3.1", "hyper-util", "native-tls", "tokio", @@ -1337,7 +1336,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.1.0", + "hyper 1.3.1", "pin-project-lite", "socket2 0.5.5", "tokio", @@ -1559,7 +1558,7 @@ checksum = "5d58e362dc7206e9456ddbcdbd53c71ba441020e62104703075a69151e38d85f" dependencies = [ "base64 0.22.0", "http-body-util", - "hyper 1.1.0", + "hyper 1.3.1", "hyper-tls", "hyper-util", "indexmap 2.2.2", @@ -2350,7 +2349,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.1.0", + "hyper 1.3.1", "hyper-tls", "hyper-util", "ipnet", @@ -3092,9 +3091,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 265fe5f..54e216a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ metrics = "0.22.0" metrics-exporter-prometheus = "0.14.0" rand = "0.8.5" rdkafka = { version = "0.36.0", features = ["cmake-build", "ssl", "tracing"] } -reqwest = { version = "0.12.3" } +reqwest = { version = "0.12.3", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_derive = { version = "1.0" } serde_json = { version = "1.0" } diff --git a/feature-flags/Cargo.toml b/feature-flags/Cargo.toml index 088b069..f1f03ac 100644 --- a/feature-flags/Cargo.toml +++ b/feature-flags/Cargo.toml @@ -16,11 +16,7 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } base64 = { workspace = true } bytes = { workspace = true } -flate2 = { workspace = true } -governor = { workspace = true } -metrics = { workspace = true } rand = { workspace = true } -rdkafka = { workspace = true } redis = { version = "0.23.3", features = [ "tokio-comp", "cluster", @@ -28,8 +24,13 @@ redis = { version = "0.23.3", features = [ ] } serde = { workspace = true } serde_json = { workspace = true } -serde_urlencoded = { workspace = true } thiserror = { workspace = true } [lints] workspace = true + +[dev-dependencies] +assert-json-diff = { workspace = true } +once_cell = "1.18.0" +reqwest = { workspace = true } + diff --git a/feature-flags/src/config.rs b/feature-flags/src/config.rs index 46d2983..3fa6f50 100644 --- a/feature-flags/src/config.rs +++ b/feature-flags/src/config.rs @@ -1,13 +1,10 @@ -use std::{net::SocketAddr, num::NonZeroU32}; +use std::net::SocketAddr; use envconfig::Envconfig; #[derive(Envconfig, Clone)] pub struct Config { - #[envconfig(default = "false")] - pub print_sink: bool, - - #[envconfig(default = "127.0.0.1:3001")] + #[envconfig(default = "127.0.0.1:0")] pub address: SocketAddr, #[envconfig(default = "postgres://posthog:posthog@localhost:15432/test_database")] diff --git a/feature-flags/src/router.rs b/feature-flags/src/router.rs index 33c7a36..58dc8cb 100644 --- a/feature-flags/src/router.rs +++ b/feature-flags/src/router.rs @@ -1,11 +1,6 @@ -use std::future::ready; use std::sync::Arc; -use axum::http::Method; -use axum::{ - routing::{get, post}, - Router, -}; +use axum::{routing::post, Router}; use crate::{redis::Client, v0_endpoint}; diff --git a/feature-flags/src/v0_endpoint.rs b/feature-flags/src/v0_endpoint.rs index a677278..8f77611 100644 --- a/feature-flags/src/v0_endpoint.rs +++ b/feature-flags/src/v0_endpoint.rs @@ -1,6 +1,4 @@ use std::collections::HashMap; -use std::ops::Deref; -use std::sync::Arc; use axum::{debug_handler, Json}; use bytes::Bytes; @@ -8,7 +6,6 @@ use bytes::Bytes; use axum::extract::{MatchedPath, Query, State}; use axum::http::{HeaderMap, Method}; use axum_client_ip::InsecureClientIp; -use base64::Engine; use tracing::instrument; use crate::{ @@ -36,7 +33,7 @@ use crate::{ )] #[debug_handler] pub async fn flags( - state: State, + _state: State, InsecureClientIp(ip): InsecureClientIp, meta: Query, headers: HeaderMap, @@ -56,6 +53,7 @@ pub async fn flags( tracing::Span::current().record("version", meta.version.clone()); tracing::Span::current().record("method", method.as_str()); tracing::Span::current().record("path", path.as_str().trim_end_matches('/')); + tracing::Span::current().record("ip", ip.to_string()); let request = match headers .get("content-type") diff --git a/feature-flags/src/v0_request.rs b/feature-flags/src/v0_request.rs index 7a869d0..f2269df 100644 --- a/feature-flags/src/v0_request.rs +++ b/feature-flags/src/v0_request.rs @@ -1,13 +1,9 @@ use std::collections::HashMap; -// use std::io::prelude::*; use bytes::Bytes; use serde::{Deserialize, Serialize}; use serde_json::Value; -// use time::format_description::well_known::Iso8601; -// use time::OffsetDateTime; use tracing::instrument; -// use uuid::Uuid; use crate::api::FlagError; @@ -15,9 +11,6 @@ use crate::api::FlagError; pub struct FlagsQueryParams { #[serde(alias = "v")] pub version: Option, - - #[serde(alias = "_")] - sent_at: Option, } #[derive(Default, Debug, Deserialize, Serialize)] diff --git a/feature-flags/tests/common.rs b/feature-flags/tests/common.rs new file mode 100644 index 0000000..f66a11f --- /dev/null +++ b/feature-flags/tests/common.rs @@ -0,0 +1,66 @@ +use std::net::SocketAddr; +use std::str::FromStr; +use std::string::ToString; +use std::sync::Arc; + +use once_cell::sync::Lazy; +use rand::distributions::Alphanumeric; +use rand::Rng; +use tokio::net::TcpListener; +use tokio::sync::Notify; + +use feature_flags::config::Config; +use feature_flags::server::serve; + +pub static DEFAULT_CONFIG: Lazy = Lazy::new(|| Config { + address: SocketAddr::from_str("127.0.0.1:0").unwrap(), + redis_url: "redis://localhost:6379/".to_string(), + write_database_url: "postgres://posthog:posthog@localhost:15432/test_database".to_string(), + read_database_url: "postgres://posthog:posthog@localhost:15432/test_database".to_string(), + max_concurrent_jobs: 1024, + max_pg_connections: 100, +}); + +pub struct ServerHandle { + pub addr: SocketAddr, + shutdown: Arc, +} + +impl ServerHandle { + pub async fn for_config(config: Config) -> ServerHandle { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let notify = Arc::new(Notify::new()); + let shutdown = notify.clone(); + + tokio::spawn(async move { + serve(config, listener, async move { notify.notified().await }).await + }); + ServerHandle { addr, shutdown } + } + + pub async fn send_flags_request>(&self, body: T) -> reqwest::Response { + let client = reqwest::Client::new(); + client + .post(format!("http://{:?}/flags", self.addr)) + .body(body) + .send() + .await + .expect("failed to send request") + } +} + +impl Drop for ServerHandle { + fn drop(&mut self) { + self.shutdown.notify_one() + } +} + +pub fn random_string(prefix: &str, length: usize) -> String { + let suffix: String = rand::thread_rng() + .sample_iter(Alphanumeric) + .take(length) + .map(char::from) + .collect(); + format!("{}_{}", prefix, suffix) +} diff --git a/feature-flags/tests/test_flags.rs b/feature-flags/tests/test_flags.rs new file mode 100644 index 0000000..82f41f0 --- /dev/null +++ b/feature-flags/tests/test_flags.rs @@ -0,0 +1,43 @@ +use anyhow::Result; +use assert_json_diff::assert_json_include; + +use reqwest::StatusCode; +use serde_json::{json, Value}; + +use crate::common::*; +mod common; + +#[tokio::test] +async fn it_sends_flag_request() -> Result<()> { + let token = random_string("token", 16); + let distinct_id = "user_distinct_id".to_string(); + + let config = DEFAULT_CONFIG.clone(); + + let server = ServerHandle::for_config(config).await; + + let payload = json!({ + "token": token, + "distinct_id": distinct_id, + "groups": {"group1": "group1"} + }); + let res = server.send_flags_request(payload.to_string()).await; + assert_eq!(StatusCode::OK, res.status()); + + // We don't want to deserialize the data into a flagResponse struct here, + // because we want to assert the shape of the raw json data. + let json_data = res.json::().await?; + + assert_json_include!( + actual: json_data, + expected: json!({ + "errorWhileComputingFlags": false, + "featureFlags": { + "beta-feature": "variant-1", + "rollout-flag": "true", + } + }) + ); + + Ok(()) +}