diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 209fcae..59aad03 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,7 +34,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - rust_release: [1.45, stable, nightly] + rust_release: [1.46, stable, nightly] os: [ubuntu-latest, windows-latest, macOS-latest] steps: @@ -45,8 +45,24 @@ jobs: - name: Build run: cargo build --verbose + unit_test: + name: Unit test (${{ matrix.rust_release }}/${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + rust_release: [1.46, stable, nightly] + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust_release }} + - name: test + run: cargo test --lib + integration_test: - name: Integration Tests (stable/ubuntu-latest) + name: Integration Tests for Influxdb 1.x (stable/ubuntu-latest) runs-on: ubuntu-latest strategy: matrix: @@ -66,11 +82,32 @@ jobs: INFLUXDB_ADMIN_PASSWORD: password INFLUXDB_USER: nopriv_user INFLUXDB_USER_PASSWORD: password + steps: + - uses: actions/checkout@v1 + - uses: dtolnay/rust-toolchain@stable + - run: cargo test --manifest-path=./influxdb/Cargo.toml --no-default-features --features 'use-serde derive ${{ matrix.http-backend }}' --no-fail-fast --test integration_tests + integration_test_v2: + name: Integration Tests for Influxdb 2.0 (stable/ubuntu-latest) + runs-on: ubuntu-latest + strategy: + matrix: + http-backend: [curl-client, h1-client, h1-client-rustls, hyper-client] + services: + influxdbv2: + image: influxdb:2.0 + ports: + - 9086:8086 + env: + DOCKER_INFLUXDB_INIT_USERNAME: admin + DOCKER_INFLUXDB_INIT_PASSWORD: password + DOCKER_INFLUXDB_INIT_ORG: testing + DOCKER_INFLUXDB_INIT_BUCKET: mydb + DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: admintoken steps: - uses: actions/checkout@v1 - uses: dtolnay/rust-toolchain@stable - - run: cargo test --manifest-path=./influxdb/Cargo.toml --no-default-features --features 'use-serde derive ${{ matrix.http-backend }}' --no-fail-fast + - run: cargo test --manifest-path=./influxdb/Cargo.toml --no-default-features --features 'use-serde derive ${{ matrix.http-backend }}' --no-fail-fast --test integration_tests_v2 coverage: name: Code Coverage (stable/ubuntu-20.04) diff --git a/benches/client.rs b/benches/client.rs index 2b8ca5d..c1ed98a 100644 --- a/benches/client.rs +++ b/benches/client.rs @@ -67,7 +67,7 @@ async fn main() { async fn prepare_influxdb(client: &Client, db_name: &str) { let create_db_stmt = format!("CREATE DATABASE {}", db_name); client - .query(&Query::raw_read_query(create_db_stmt)) + .query(&::raw_read_query(create_db_stmt)) .await .expect("failed to create database"); } diff --git a/influxdb/src/client/mod.rs b/influxdb/src/client/mod.rs index 468b0ae..098e496 100644 --- a/influxdb/src/client/mod.rs +++ b/influxdb/src/client/mod.rs @@ -16,7 +16,7 @@ //! ``` use futures::prelude::*; -use surf::{self, Client as SurfClient, StatusCode}; +use surf::{self, Client as SurfClient, RequestBuilder, StatusCode}; use crate::query::QueryType; use crate::Error; @@ -30,6 +30,7 @@ pub struct Client { pub(crate) url: Arc, pub(crate) parameters: Arc>, pub(crate) client: SurfClient, + pub(crate) token: Option, } impl Client { @@ -58,6 +59,7 @@ impl Client { url: Arc::new(url.into()), parameters: Arc::new(parameters), client: SurfClient::new(), + token: None, } } @@ -87,6 +89,19 @@ impl Client { self } + /// Add authorization token to [`Client`](crate::Client) + /// + /// This is designed for influxdb 2.0's backward-compatible API which + /// requires authrozation by default. You can create such token from + /// console of influxdb 2.0 . + pub fn with_token(mut self, token: S) -> Self + where + S: Into, + { + self.token = Some(token.into()); + self + } + /// Returns the name of the database the client is using pub fn database_name(&self) -> &str { // safe to unwrap: we always set the database name in `Self::new` @@ -164,11 +179,11 @@ impl Client { error: err.to_string(), })?; + let mut parameters = self.parameters.as_ref().clone(); let request_builder = match q.get_type() { QueryType::ReadQuery => { let read_query = query.get(); let url = &format!("{}/query", &self.url); - let mut parameters = self.parameters.as_ref().clone(); parameters.insert("q", read_query.clone()); if read_query.contains("SELECT") || read_query.contains("SHOW") { @@ -189,7 +204,7 @@ impl Client { error: err.to_string(), })?; - let request = request_builder.build(); + let request = self.auth_if_needed(request_builder).build(); let mut res = self .client .send(request) @@ -220,6 +235,14 @@ impl Client { Ok(s) } + + fn auth_if_needed(&self, rb: RequestBuilder) -> RequestBuilder { + if let Some(ref token) = self.token { + rb.header("Authorization", format!("Token {}", token)) + } else { + rb + } + } } #[cfg(test)] @@ -244,5 +267,11 @@ mod tests { assert_eq!(with_auth.parameters.get("db").unwrap(), "database"); assert_eq!(with_auth.parameters.get("u").unwrap(), "username"); assert_eq!(with_auth.parameters.get("p").unwrap(), "password"); + + let client = Client::new("http://localhost:8068", "database"); + let with_auth = client.with_token("token"); + assert_eq!(with_auth.parameters.len(), 1); + assert_eq!(with_auth.parameters.get("db").unwrap(), "database"); + assert_eq!(with_auth.token.unwrap(), "token"); } } diff --git a/influxdb/src/query/read_query.rs b/influxdb/src/query/read_query.rs index 44ec57f..e0274e1 100644 --- a/influxdb/src/query/read_query.rs +++ b/influxdb/src/query/read_query.rs @@ -47,14 +47,14 @@ mod tests { #[test] fn test_read_builder_single_query() { - let query = Query::raw_read_query("SELECT * FROM aachen").build(); + let query = ::raw_read_query("SELECT * FROM aachen").build(); assert_eq!(query.unwrap(), "SELECT * FROM aachen"); } #[test] fn test_read_builder_multi_query() { - let query = Query::raw_read_query("SELECT * FROM aachen") + let query = ::raw_read_query("SELECT * FROM aachen") .add_query("SELECT * FROM cologne") .build(); @@ -63,7 +63,7 @@ mod tests { #[test] fn test_correct_query_type() { - let query = Query::raw_read_query("SELECT * FROM aachen"); + let query = ::raw_read_query("SELECT * FROM aachen"); assert_eq!(query.get_type(), QueryType::ReadQuery); } diff --git a/influxdb/tests/derive_integration_tests.rs b/influxdb/tests/derive_integration_tests.rs index 463fed9..5ede8a7 100644 --- a/influxdb/tests/derive_integration_tests.rs +++ b/influxdb/tests/derive_integration_tests.rs @@ -101,8 +101,9 @@ async fn test_write_and_read_option() { .query(&weather_reading.into_query("weather_reading".to_string())) .await; assert_result_ok(&write_result); - let query = - Query::raw_read_query("SELECT time, pressure, wind_strength FROM weather_reading"); + let query = ::raw_read_query( + "SELECT time, pressure, wind_strength FROM weather_reading", + ); let result = client.json_query(query).await.and_then(|mut db_result| { println!("{:?}", db_result); db_result.deserialize_next::() diff --git a/influxdb/tests/integration_tests.rs b/influxdb/tests/integration_tests.rs index d92f405..cb67332 100644 --- a/influxdb/tests/integration_tests.rs +++ b/influxdb/tests/integration_tests.rs @@ -52,7 +52,7 @@ async fn test_connection_error() { let test_name = "test_connection_error"; let client = Client::new("http://127.0.0.1:10086", test_name).with_auth("nopriv_user", "password"); - let read_query = Query::raw_read_query("SELECT * FROM weather"); + let read_query = ::raw_read_query("SELECT * FROM weather"); let read_result = client.query(&read_query).await; assert_result_err(&read_result); match read_result { @@ -78,7 +78,7 @@ async fn test_authed_write_and_read() { Client::new("http://127.0.0.1:9086", TEST_NAME).with_auth("admin", "password"); let query = format!("CREATE DATABASE {}", TEST_NAME); client - .query(&Query::raw_read_query(query)) + .query(&::raw_read_query(query)) .await .expect("could not setup db"); @@ -90,7 +90,7 @@ async fn test_authed_write_and_read() { let write_result = client.query(&write_query).await; assert_result_ok(&write_result); - let read_query = Query::raw_read_query("SELECT * FROM weather"); + let read_query = ::raw_read_query("SELECT * FROM weather"); let read_result = client.query(&read_query).await; assert_result_ok(&read_result); assert!( @@ -104,7 +104,7 @@ async fn test_authed_write_and_read() { let query = format!("DROP DATABASE {}", TEST_NAME); client - .query(&Query::raw_read_query(query)) + .query(&::raw_read_query(query)) .await .expect("could not clean up db"); }, @@ -126,7 +126,7 @@ async fn test_wrong_authed_write_and_read() { Client::new("http://127.0.0.1:9086", TEST_NAME).with_auth("admin", "password"); let query = format!("CREATE DATABASE {}", TEST_NAME); client - .query(&Query::raw_read_query(query)) + .query(&::raw_read_query(query)) .await .expect("could not setup db"); @@ -145,7 +145,7 @@ async fn test_wrong_authed_write_and_read() { ), } - let read_query = Query::raw_read_query("SELECT * FROM weather"); + let read_query = ::raw_read_query("SELECT * FROM weather"); let read_result = client.query(&read_query).await; assert_result_err(&read_result); match read_result { @@ -158,7 +158,7 @@ async fn test_wrong_authed_write_and_read() { let client = Client::new("http://127.0.0.1:9086", TEST_NAME) .with_auth("nopriv_user", "password"); - let read_query = Query::raw_read_query("SELECT * FROM weather"); + let read_query = ::raw_read_query("SELECT * FROM weather"); let read_result = client.query(&read_query).await; assert_result_err(&read_result); match read_result { @@ -174,7 +174,7 @@ async fn test_wrong_authed_write_and_read() { Client::new("http://127.0.0.1:9086", TEST_NAME).with_auth("admin", "password"); let query = format!("DROP DATABASE {}", TEST_NAME); client - .query(&Query::raw_read_query(query)) + .query(&::raw_read_query(query)) .await .expect("could not clean up db"); }, @@ -196,7 +196,7 @@ async fn test_non_authed_write_and_read() { Client::new("http://127.0.0.1:9086", TEST_NAME).with_auth("admin", "password"); let query = format!("CREATE DATABASE {}", TEST_NAME); client - .query(&Query::raw_read_query(query)) + .query(&::raw_read_query(query)) .await .expect("could not setup db"); let non_authed_client = Client::new("http://127.0.0.1:9086", TEST_NAME); @@ -213,7 +213,7 @@ async fn test_non_authed_write_and_read() { ), } - let read_query = Query::raw_read_query("SELECT * FROM weather"); + let read_query = ::raw_read_query("SELECT * FROM weather"); let read_result = non_authed_client.query(&read_query).await; assert_result_err(&read_result); match read_result { @@ -229,7 +229,7 @@ async fn test_non_authed_write_and_read() { Client::new("http://127.0.0.1:9086", TEST_NAME).with_auth("admin", "password"); let query = format!("DROP DATABASE {}", TEST_NAME); client - .query(&Query::raw_read_query(query)) + .query(&::raw_read_query(query)) .await .expect("could not clean up db"); }, @@ -255,7 +255,7 @@ async fn test_write_and_read_field() { let write_result = client.query(&write_query).await; assert_result_ok(&write_result); - let read_query = Query::raw_read_query("SELECT * FROM weather"); + let read_query = ::raw_read_query("SELECT * FROM weather"); let read_result = client.query(&read_query).await; assert_result_ok(&read_result); assert!( @@ -304,8 +304,9 @@ async fn test_write_and_read_option() { temperature: i32, } - let query = - Query::raw_read_query("SELECT time, temperature, wind_strength FROM weather"); + let query = ::raw_read_query( + "SELECT time, temperature, wind_strength FROM weather", + ); let result = client .json_query(query) .await @@ -361,7 +362,7 @@ async fn test_json_query() { temperature: i32, } - let query = Query::raw_read_query("SELECT * FROM weather"); + let query = ::raw_read_query("SELECT * FROM weather"); let result = client .json_query(query) .await @@ -419,7 +420,7 @@ async fn test_json_query_tagged() { temperature: i32, } - let query = Query::raw_read_query("SELECT * FROM weather GROUP BY location"); + let query = ::raw_read_query("SELECT * FROM weather GROUP BY location"); let result = client.json_query(query).await.and_then(|mut db_result| { db_result.deserialize_next_tagged::() }); @@ -485,7 +486,7 @@ async fn test_json_query_vec() { temperature: i32, } - let query = Query::raw_read_query("SELECT * FROM temperature_vec"); + let query = ::raw_read_query("SELECT * FROM temperature_vec"); let result = client .json_query(query) .await @@ -542,7 +543,7 @@ async fn test_serde_multi_query() { let result = client .json_query( - Query::raw_read_query("SELECT * FROM temperature") + ::raw_read_query("SELECT * FROM temperature") .add_query("SELECT * FROM humidity"), ) .await @@ -586,7 +587,9 @@ async fn test_serde_multi_query() { async fn test_wrong_query_errors() { let client = create_client("test_name"); let result = client - .json_query(Query::raw_read_query("CREATE DATABASE this_should_fail")) + .json_query(::raw_read_query( + "CREATE DATABASE this_should_fail", + )) .await; assert!( result.is_err(), diff --git a/influxdb/tests/integration_tests_v2.rs b/influxdb/tests/integration_tests_v2.rs new file mode 100644 index 0000000..b229d34 --- /dev/null +++ b/influxdb/tests/integration_tests_v2.rs @@ -0,0 +1,173 @@ +extern crate influxdb; + +#[path = "./utilities.rs"] +mod utilities; +use utilities::{assert_result_err, assert_result_ok, run_test}; + +use influxdb::InfluxDbWriteable; +use influxdb::{Client, Error, Query, Timestamp}; + +/// INTEGRATION TEST +/// +/// This test case tests the Authentication +#[async_std::test] +#[cfg(not(tarpaulin_include))] +async fn test_authed_write_and_read() { + const TEST_NAME: &str = "test_authed_write_and_read"; + + run_test( + || async move { + let client = Client::new("http://127.0.0.1:9086", TEST_NAME).with_token("admintoken"); + let query = format!("CREATE DATABASE {}", TEST_NAME); + client + .query(&::raw_read_query(query)) + .await + .expect("could not setup db"); + + let client = Client::new("http://127.0.0.1:9086", TEST_NAME).with_token("admintoken"); + let write_query = Timestamp::Hours(11) + .into_query("weather") + .add_field("temperature", 82); + let write_result = client.query(&write_query).await; + assert_result_ok(&write_result); + + let read_query = ::raw_read_query("SELECT * FROM weather"); + let read_result = client.query(&read_query).await; + assert_result_ok(&read_result); + assert!( + !read_result.unwrap().contains("error"), + "Data contained a database error" + ); + }, + || async move { + let client = Client::new("http://127.0.0.1:9086", TEST_NAME).with_token("admintoken"); + let query = format!("DROP DATABASE {}", TEST_NAME); + + client + .query(&::raw_read_query(query)) + .await + .expect("could not clean up db"); + }, + ) + .await; +} + +/// INTEGRATION TEST +/// +/// This test case tests the Authentication +#[async_std::test] +#[cfg(not(tarpaulin_include))] +async fn test_wrong_authed_write_and_read() { + const TEST_NAME: &str = "test_wrong_authed_write_and_read"; + + run_test( + || async move { + let client = Client::new("http://127.0.0.1:9086", TEST_NAME).with_token("admintoken"); + let query = format!("CREATE DATABASE {}", TEST_NAME); + client + .query(&::raw_read_query(query)) + .await + .expect("could not setup db"); + + let client = Client::new("http://127.0.0.1:9086", TEST_NAME).with_token("falsetoken"); + let write_query = Timestamp::Hours(11) + .into_query("weather") + .add_field("temperature", 82); + let write_result = client.query(&write_query).await; + assert_result_err(&write_result); + match write_result { + Err(Error::AuthorizationError) => {} + _ => panic!( + "Should be an AuthorizationError: {}", + write_result.unwrap_err() + ), + } + + let read_query = ::raw_read_query("SELECT * FROM weather"); + let read_result = client.query(&read_query).await; + assert_result_err(&read_result); + match read_result { + Err(Error::AuthorizationError) => {} + _ => panic!( + "Should be an AuthorizationError: {}", + read_result.unwrap_err() + ), + } + + let client = Client::new("http://127.0.0.1:9086", TEST_NAME) + .with_auth("nopriv_user", "password"); + let read_query = ::raw_read_query("SELECT * FROM weather"); + let read_result = client.query(&read_query).await; + assert_result_err(&read_result); + match read_result { + Err(Error::AuthenticationError) => {} + _ => panic!( + "Should be an AuthenticationError: {}", + read_result.unwrap_err() + ), + } + }, + || async move { + let client = Client::new("http://127.0.0.1:9086", TEST_NAME).with_token("admintoken"); + let query = format!("DROP DATABASE {}", TEST_NAME); + client + .query(&::raw_read_query(query)) + .await + .expect("could not clean up db"); + }, + ) + .await; +} + +/// INTEGRATION TEST +/// +/// This test case tests the Authentication +#[async_std::test] +#[cfg(not(tarpaulin_include))] +async fn test_non_authed_write_and_read() { + const TEST_NAME: &str = "test_non_authed_write_and_read"; + + run_test( + || async move { + let client = Client::new("http://127.0.0.1:9086", TEST_NAME).with_token("admintoken"); + let query = format!("CREATE DATABASE {}", TEST_NAME); + client + .query(&::raw_read_query(query)) + .await + .expect("could not setup db"); + let non_authed_client = Client::new("http://127.0.0.1:9086", TEST_NAME); + let write_query = Timestamp::Hours(11) + .into_query("weather") + .add_field("temperature", 82); + let write_result = non_authed_client.query(&write_query).await; + assert_result_err(&write_result); + match write_result { + Err(Error::AuthorizationError) => {} + _ => panic!( + "Should be an AuthorizationError: {}", + write_result.unwrap_err() + ), + } + + let read_query = ::raw_read_query("SELECT * FROM weather"); + let read_result = non_authed_client.query(&read_query).await; + assert_result_err(&read_result); + match read_result { + Err(Error::AuthorizationError) => {} + _ => panic!( + "Should be an AuthorizationError: {}", + read_result.unwrap_err() + ), + } + }, + || async move { + let client = Client::new("http://127.0.0.1:9086", TEST_NAME).with_token("admintoken"); + let query = format!("DROP DATABASE {}", TEST_NAME); + client + .query(&::raw_read_query(query)) + .await + .expect("could not clean up db"); + }, + ) + .await; +} diff --git a/influxdb/tests/utilities.rs b/influxdb/tests/utilities.rs index b850d2d..aa51a4b 100644 --- a/influxdb/tests/utilities.rs +++ b/influxdb/tests/utilities.rs @@ -13,6 +13,7 @@ pub fn assert_result_ok(result: &Result< result.as_ref().expect("assert_result_ok failed"); } +#[allow(dead_code)] #[cfg(not(tarpaulin_include))] pub fn create_client(db_name: T) -> Client where @@ -21,6 +22,7 @@ where Client::new("http://127.0.0.1:8086", db_name) } +#[allow(dead_code)] #[cfg(not(tarpaulin_include))] pub async fn create_db(name: T) -> Result where @@ -29,10 +31,11 @@ where let test_name = name.into(); let query = format!("CREATE DATABASE {}", test_name); create_client(test_name) - .query(&Query::raw_read_query(query)) + .query(&::raw_read_query(query)) .await } +#[allow(dead_code)] #[cfg(not(tarpaulin_include))] pub async fn delete_db(name: T) -> Result where @@ -41,7 +44,7 @@ where let test_name = name.into(); let query = format!("DROP DATABASE {}", test_name); create_client(test_name) - .query(&Query::raw_read_query(query)) + .query(&::raw_read_query(query)) .await }