Skip to content

Commit

Permalink
Convert integration tests into examples
Browse files Browse the repository at this point in the history
Examples are still executed during `cargo test`,
because test = true has been set in Cargo.toml for each example binary.

Signed-off-by: David Gräff <[email protected]>
  • Loading branch information
David Gräff authored and davidgraeff committed Jan 20, 2021
1 parent 7d4b3df commit 8e74e6a
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 129 deletions.
17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,20 @@ native-tls = ["reqwest/native-tls"]
native-tls-vendored = ["reqwest/native-tls-vendored"]
unstable = []
external_doc = []

[[example]]
name = "create_read_write_document"
test = true

[[example]]
name = "firebase_user"
test = true

[[example]]
name = "own_auth"
test = true

[[example]]
name = "rocket_http_protected_route"
test = true
required-features = ["rustls-tls","rocket_support"]
205 changes: 144 additions & 61 deletions tests/documents.rs → examples/create_read_write_document.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use serde::{Deserialize, Serialize};
use firestore_db_and_auth::{
documents, dto, errors, sessions, Credentials, FirebaseAuthBearer, JWKSet, ServiceSession,
};

use firestore_db_and_auth::errors::FirebaseError;
use firestore_db_and_auth::*;
use firestore_db_and_auth::documents::WriteResult;
use firestore_db_and_auth::jwt::download_google_jwks;
use serde::{Deserialize, Serialize};

const TEST_USER_ID: &str = include_str!("test_user_id.txt");

Expand All @@ -21,21 +24,7 @@ struct DemoDTOPartial {
an_int: u32,
}

#[test]
fn service_account_session() -> errors::Result<()> {
use std::path::PathBuf;
let mut credential_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
credential_file.push("firebase-service-account.json");

let cred = credentials::Credentials::from_file(credential_file.to_str().unwrap()).expect("Read credentials file");
cred.verify()?;

let mut session = ServiceSession::new(cred).unwrap();
let b = session.access_token().to_owned();

// Check if cached value is used
assert_eq!(session.access_token(), b);

fn write_document(session: &mut ServiceSession, doc_id: &str) -> errors::Result<WriteResult> {
println!("Write document");

let obj = DemoDTO {
Expand All @@ -44,20 +33,10 @@ fn service_account_session() -> errors::Result<()> {
a_timestamp: chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, true),
};

documents::write(
&mut session,
"tests",
Some("service_test"),
&obj,
documents::WriteOptions::default(),
)?;

println!("Read and compare document");
let read: DemoDTO = documents::read(&mut session, "tests", "service_test")?;

assert_eq!(read.a_string, "abcd");
assert_eq!(read.an_int, 14);
documents::write(session, "tests", Some(doc_id), &obj, documents::WriteOptions::default())
}

fn write_partial_document(session: &mut ServiceSession, doc_id: &str) -> errors::Result<WriteResult> {
println!("Partial write document");

let obj = DemoDTOPartial {
Expand All @@ -66,15 +45,45 @@ fn service_account_session() -> errors::Result<()> {
};

documents::write(
&mut session,
session,
"tests",
Some("service_test"),
Some(doc_id),
&obj,
documents::WriteOptions { merge: true },
)?;
)
}

fn check_write(result: WriteResult, doc_id: &str) {
assert_eq!(result.document_id, doc_id);
let duration = chrono::Utc::now().signed_duration_since(result.update_time.unwrap());
assert!(
duration.num_seconds() < 60,
"now = {}, updated: {}, created: {}",
chrono::Utc::now(),
result.update_time.unwrap(),
result.create_time.unwrap()
);
}

fn service_account_session(cred: Credentials) -> errors::Result<()> {
let mut session = ServiceSession::new(cred).unwrap();
let b = session.access_token().to_owned();

let doc_id = "service_test";
check_write(write_document(&mut session, doc_id)?, doc_id);

// Check if cached value is used
assert_eq!(session.access_token(), b);

println!("Read and compare document");
let read: DemoDTO = documents::read(&mut session, "tests", doc_id)?;

assert_eq!(read.a_string, "abcd");
assert_eq!(read.an_int, 14);

check_write(write_partial_document(&mut session, doc_id)?, doc_id);
println!("Read and compare document");
let read: DemoDTOPartial = documents::read(&mut session, "tests", "service_test")?;
let read: DemoDTOPartial = documents::read(&mut session, "tests", doc_id)?;

// Should be updated
assert_eq!(read.an_int, 16);
Expand All @@ -84,14 +93,7 @@ fn service_account_session() -> errors::Result<()> {
Ok(())
}

#[test]
fn user_account_session() -> errors::Result<()> {
use std::path::PathBuf;
let mut credential_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
credential_file.push("firebase-service-account.json");

let cred = credentials::Credentials::from_file(credential_file.to_str().unwrap()).expect("Read credentials file");

fn user_session_with_cached_refresh_token(cred: &Credentials) -> errors::Result<sessions::user::Session> {
println!("Refresh token from file");
// Read refresh token from file if possible instead of generating a new refresh token each time
let refresh_token: String = match std::fs::read_to_string("refresh-token-for-tests.txt") {
Expand All @@ -115,6 +117,12 @@ fn user_account_session() -> errors::Result<()> {
sessions::user::Session::by_refresh_token(&cred, &refresh_token)?
};

Ok(user_session)
}

fn user_account_session(cred: Credentials) -> errors::Result<()> {
let user_session = user_session_with_cached_refresh_token(&cred)?;

assert_eq!(user_session.user_id, TEST_USER_ID);
assert_eq!(user_session.project_id(), cred.project_id);

Expand All @@ -131,26 +139,21 @@ fn user_account_session() -> errors::Result<()> {

// Test writing
println!("user::Session documents::write");
let result = documents::write(
&user_session,
"tests",
Some("test"),
&obj,
documents::WriteOptions::default(),
)?;
assert_eq!(result.document_id, "test");
let duration = chrono::Utc::now().signed_duration_since(result.update_time.unwrap());
assert!(
duration.num_seconds() < 60,
"now = {}, updated: {}, created: {}",
chrono::Utc::now(),
result.update_time.unwrap(),
result.create_time.unwrap()
let doc_id = "user_doc";
check_write(
documents::write(
&user_session,
"tests",
Some(doc_id),
&obj,
documents::WriteOptions::default(),
)?,
doc_id,
);

// Test reading
println!("user::Session documents::read");
let read: DemoDTO = documents::read(&user_session, "tests", "test")?;
let read: DemoDTO = documents::read(&user_session, "tests", doc_id)?;

assert_eq!(read.a_string, "abc");
assert_eq!(read.an_int, 12);
Expand Down Expand Up @@ -180,15 +183,15 @@ fn user_account_session() -> errors::Result<()> {
let r = documents::delete(&user_session, "tests/non_existing", true);
assert!(r.is_err());
match r.err().unwrap() {
FirebaseError::APIError(code, message, context) => {
errors::FirebaseError::APIError(code, message, context) => {
assert_eq!(code, 404);
assert!(message.contains("No document to update"));
assert_eq!(context, "tests/non_existing");
}
_ => panic!("Expected an APIError"),
};

documents::delete(&user_session, "tests/test", false)?;
documents::delete(&user_session, &("tests/".to_owned() + doc_id), false)?;

// Check if document is indeed removed
println!("user::Session documents::query");
Expand All @@ -209,3 +212,83 @@ fn user_account_session() -> errors::Result<()> {

Ok(())
}

/// Download the two public key JWKS files if necessary and cache the content at the given file path.
/// Only use this option in cloud functions if the given file path is persistent.
/// You can use [`Credentials::add_jwks_public_keys`] to manually add more public keys later on.
pub fn from_cache_file(cache_file: &std::path::Path, c: &Credentials) -> errors::Result<JWKSet> {
use std::fs::File;
use std::io::BufReader;

Ok(if cache_file.exists() {
let f = BufReader::new(File::open(cache_file)?);
let jwks_set: JWKSet = serde_json::from_reader(f)?;
jwks_set
} else {
// If not present, download the two jwks (specific service account + google system account),
// merge them into one set of keys and store them in the cache file.
let mut jwks = JWKSet::new(&download_google_jwks(&c.client_email)?)?;
jwks.keys
.append(&mut JWKSet::new(&download_google_jwks("[email protected]")?)?.keys);
let f = File::create(cache_file)?;
serde_json::to_writer_pretty(f, &jwks)?;
jwks
})
}

fn main() -> errors::Result<()> {
// Search for a credentials file in the root directory
use std::path::PathBuf;
let mut credential_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
credential_file.push("firebase-service-account.json");
let mut cred = Credentials::from_file(credential_file.to_str().unwrap())?;

// Only download the public keys once, and cache them.
let jwkset = from_cache_file(credential_file.with_file_name("cached_jwks.jwks").as_path(), &cred)?;
cred.add_jwks_public_keys(&jwkset);
cred.verify()?;

// Perform some db operations via a service account session
service_account_session(cred.clone())?;

// Perform some db operations via a firebase user session
user_account_session(cred)?;

Ok(())
}

/// For integration tests and doc code snippets: Create a Credentials instance.
/// Necessary public jwk sets are downloaded or re-used if already present.
#[cfg(test)]
fn valid_test_credentials() -> errors::Result<Credentials> {
use std::path::PathBuf;
let mut jwks_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
jwks_path.push("firebase-service-account.jwks");

let mut cred: Credentials = Credentials::new(include_str!("../firebase-service-account.json"))?;

// Only download the public keys once, and cache them.
let jwkset = from_cache_file(jwks_path.as_path(), &cred)?;
cred.add_jwks_public_keys(&jwkset);
cred.verify()?;

Ok(cred)
}

#[test]
fn valid_test_credentials_test() -> errors::Result<()> {
valid_test_credentials()?;
Ok(())
}

#[test]
fn service_account_session_test() -> errors::Result<()> {
service_account_session(valid_test_credentials()?)?;
Ok(())
}

#[test]
fn user_account_session_test() -> errors::Result<()> {
user_account_session(valid_test_credentials()?)?;
Ok(())
}
11 changes: 8 additions & 3 deletions tests/users.rs → examples/firebase_user.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use firestore_db_and_auth::Credentials;
use firestore_db_and_auth::*;

const TEST_USER_ID: &str = include_str!("test_user_id.txt");

#[test]
fn user_info() -> errors::Result<()> {
let cred = credentials::Credentials::from_file("firebase-service-account.json").expect("Read credentials file");
fn main() -> errors::Result<()> {
let cred = Credentials::from_file("firebase-service-account.json").expect("Read credentials file");

let user_session = UserSession::by_user_id(&cred, TEST_USER_ID, false)?;

Expand All @@ -14,3 +14,8 @@ fn user_info() -> errors::Result<()> {

Ok(())
}

#[test]
fn firebase_user_test() {
main().unwrap();
}
47 changes: 35 additions & 12 deletions examples/own_auth/src/main.rs → examples/own_auth.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use firestore_db_and_auth::{Credentials, FirebaseAuthBearer, documents};
use firestore_db_and_auth::errors::FirebaseError::APIError;
use firestore_db_and_auth::{documents, errors, Credentials, FirebaseAuthBearer};

/// Define your own structure that will implement the FirebaseAuthBearer trait
struct MyOwnSession {
/// The google credentials
pub credentials: Credentials,
pub blocking_client: reqwest::blocking::Client,
pub client: reqwest::Client,
access_token: String,
}
Expand All @@ -23,27 +25,48 @@ impl FirebaseAuthBearer for MyOwnSession {
}
/// The reqwest http client.
/// The `Client` holds a connection pool internally, so it is advised that it is reused for multiple, successive connections.
fn client(&self) -> &reqwest::Client {
fn client(&self) -> &reqwest::blocking::Client {
&self.blocking_client
}

fn client_async(&self) -> &reqwest::Client {
&self.client
}
}

fn main() {
let credentials = Credentials::from_file("firebase-service-account.json").unwrap();
fn main() -> errors::Result<()> {
let credentials = Credentials::from_file("firebase-service-account.json")?;
#[derive(serde::Serialize)]
struct TestData {
an_int: u32
};
let t = TestData {
an_int: 12
};

an_int: u32,
}
let t = TestData { an_int: 12 };

let session = MyOwnSession {
credentials,
blocking_client: reqwest::blocking::Client::new(),
client: reqwest::Client::new(),
access_token: "The access token".to_owned()
access_token: "The access token".to_owned(),
};

// Use any of the document functions with your own session object
documents::write(&session, "tests", Some("test_doc"), &t, documents::WriteOptions::default()).unwrap();
documents::write(
&session,
"tests",
Some("test_doc"),
&t,
documents::WriteOptions::default(),
)?;
Ok(())
}

#[test]
fn own_auth_test() {
if let Err(APIError(code, str_code, context)) = main() {
assert_eq!(str_code, "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.");
assert_eq!(context, "test_doc");
assert_eq!(code, 401);
return;
}
panic!("Expected a failure with invalid access token");
}
Loading

0 comments on commit 8e74e6a

Please sign in to comment.