Skip to content

Commit

Permalink
Accept credentials as impl Read (#9)
Browse files Browse the repository at this point in the history
Co-authored-by: Jack <[email protected]>
  • Loading branch information
aengelas and brownjohnf authored Jun 27, 2024
1 parent 9309167 commit fe362d7
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Changed
- Accept FCM credentials as impl Read (#9)
- Handle ```duration_since``` error differently in the token_manager. (#1)


Expand Down
6 changes: 4 additions & 2 deletions examples/axum_example.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use axum::{extract::Extension, routing::post, Router};
use oauth_fcm::{create_shared_token_manager, send_fcm_message, SharedTokenManager};
use serde::Serialize;
use std::fs::File;

#[derive(Serialize)]
struct MyData {
Expand Down Expand Up @@ -28,8 +29,9 @@ async fn send_notification(

#[tokio::main]
async fn main() {
let shared_token_manager = create_shared_token_manager("path/to/google/credentials.json")
.expect("Could not find credentials.json");
let shared_token_manager =
create_shared_token_manager(File::open("path/to/google/credentials.json").unwrap())
.expect("Could not find credentials.json");

let app = Router::new()
.route("/send", post(send_notification))
Expand Down
4 changes: 3 additions & 1 deletion examples/rocket_example.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rocket::{post, State};
use serde::Serialize;
use std::fs::File;

use oauth_fcm::{create_shared_token_manager, send_fcm_message, SharedTokenManager};

Expand Down Expand Up @@ -35,7 +36,8 @@ async fn send_notification(token_manager: &State<SharedTokenManager>) -> Result<
#[rocket::main]
async fn main() {
let shared_token_manager =
create_shared_token_manager("path/to/google/credentials.json").unwrap();
create_shared_token_manager(File::open("path/to/google/credentials.json").unwrap())
.unwrap();

rocket::build()
.manage(shared_token_manager)
Expand Down
4 changes: 3 additions & 1 deletion src/fcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct FcmNotification {
/// # Example
///
/// ```rust no_run
/// use std::fs::File;
///
/// use oauth_fcm::{create_shared_token_manager, send_fcm_message, SharedTokenManager};
///
/// # tokio_test::block_on(async {
Expand All @@ -42,7 +44,7 @@ pub struct FcmNotification {
/// title: "Test Title".to_string(),
/// body: "Test Body".to_string(),
/// };
/// let token_manager = create_shared_token_manager("path_to_google_credentials.json").expect("Failed to create SharedTokenManager");
/// let token_manager = create_shared_token_manager(File::open("path_to_google_credentials.json").expect("Failed to open file")).expect("Failed to create SharedTokenManager");
/// let project_id = "project_id";
/// send_fcm_message(device_token, Some(notification), Some(data), &token_manager, project_id)
/// .await
Expand Down
13 changes: 8 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub use error::{FcmError, NetworkError};
pub use fcm::{send_fcm_message, send_fcm_message_with_url, FcmNotification};
pub use token_manager::{SharedTokenManager, TokenManager};

use std::{fmt::Debug, io::Read};
use tracing::{info, instrument};

/// Creates a new `SharedTokenManager`.
Expand All @@ -24,17 +25,19 @@ use tracing::{info, instrument};
/// # Example
///
/// ```rust no_run
/// use std::fs::File;
///
/// use oauth_fcm::create_shared_token_manager;
///
/// # fn main() {
/// let shared_token_manager = create_shared_token_manager("path_to_google_credentials.json").expect("Failed to create SharedTokenManager");
/// let shared_token_manager = create_shared_token_manager(File::open("path_to_google_credentials.json").expect("Failed to open file")).expect("Failed to create SharedTokenManager");
/// # }
/// ```
#[instrument(level = "info")]
pub fn create_shared_token_manager(
google_credentials_location: &str,
#[instrument(level = "info", skip_all)]
pub fn create_shared_token_manager<T: Read + Debug>(
credentials: T,
) -> Result<SharedTokenManager, FcmError> {
info!("Creating shared token manager");
let manager = TokenManager::new(google_credentials_location)?;
let manager = TokenManager::new(credentials)?;
Ok(std::sync::Arc::new(tokio::sync::Mutex::new(manager)))
}
19 changes: 12 additions & 7 deletions src/token_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ use jsonwebtoken::{encode, EncodingKey, Header};
use reqwest::Client;
use serde::Deserialize;
use serde_json::json;
use std::time::{Duration, Instant, SystemTime};
use std::{
fmt::Debug,
io::Read,
time::{Duration, Instant, SystemTime},
};
use tracing::{debug, info, instrument};

/// A thread-safe, shared reference to a `TokenManager`.
Expand All @@ -21,10 +25,12 @@ pub type SharedTokenManager = std::sync::Arc<tokio::sync::Mutex<TokenManager>>;
/// # Example
///
/// ```rust no_run
/// use std::fs::File;
///
/// use oauth_fcm::TokenManager;
///
/// # tokio_test::block_on(async {
/// let mut token_manager = TokenManager::new("./tests/mock_credentials.json").expect("Failed to create TokenManager");
/// let mut token_manager = TokenManager::new(File::open("./tests/mock_credentials.json").expect("Failed to open file")).expect("Failed to create TokenManager");
/// let token = token_manager.get_token().await.expect("Failed to get token");
/// # });
/// ```
Expand Down Expand Up @@ -53,12 +59,11 @@ impl TokenManager {
/// # Errors
///
/// This function will return an error if the Google credentials could not be read or parsed.
#[instrument(level = "info")]
pub fn new(google_credentials_location: &str) -> Result<Self, FcmError> {
#[instrument(level = "info", skip_all)]
pub fn new<T: Read + Debug>(credentials: T) -> Result<Self, FcmError> {
info!("Creating new TokenManager");
let service_account_key_json = std::fs::read_to_string(google_credentials_location)?;
let service_account_key: ServiceAccountKey =
serde_json::from_str(&service_account_key_json)?;

let service_account_key = serde_json::from_reader(credentials)?;

Ok(TokenManager {
token: None,
Expand Down
6 changes: 4 additions & 2 deletions tests/fcm_request_error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde_json::json;
use std::fs::File;

use oauth_fcm::{create_shared_token_manager, send_fcm_message_with_url, FcmError, NetworkError};

Expand Down Expand Up @@ -35,8 +36,9 @@ async fn test_fcm_request_error() {
)
.create();

let shared_token_manager = create_shared_token_manager("tests/mock_credentials.json")
.expect("Failed to create SharedTokenManager");
let shared_token_manager =
create_shared_token_manager(File::open("tests/mock_credentials.json").unwrap())
.expect("Failed to create SharedTokenManager");
// Force refresh with valid url
shared_token_manager
.lock()
Expand Down
6 changes: 4 additions & 2 deletions tests/fcm_server_error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde_json::json;
use std::fs::File;

use oauth_fcm::{create_shared_token_manager, send_fcm_message_with_url, FcmError, NetworkError};

Expand Down Expand Up @@ -41,8 +42,9 @@ async fn test_fcm_server_error() {
.with_body("Internal Server Error")
.create();

let shared_token_manager = create_shared_token_manager("tests/mock_credentials.json")
.expect("Failed to create SharedTokenManager");
let shared_token_manager =
create_shared_token_manager(File::open("tests/mock_credentials.json").unwrap())
.expect("Failed to create SharedTokenManager");
// Force refresh with valid url
shared_token_manager
.lock()
Expand Down
7 changes: 5 additions & 2 deletions tests/oauth_error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fs::File;

use oauth_fcm::create_shared_token_manager;

use crate::test_helpers::FcmBaseTest;
Expand All @@ -24,8 +26,9 @@ async fn failing_oauth_token_refresh() {
.with_status(400)
.create();

let shared_token_manager = create_shared_token_manager("tests/mock_credentials.json")
.expect("Failed to create SharedTokenManager");
let shared_token_manager =
create_shared_token_manager(File::open("tests/mock_credentials.json").unwrap())
.expect("Failed to create SharedTokenManager");

let res = {
let mut guard = shared_token_manager.lock().await;
Expand Down
87 changes: 84 additions & 3 deletions tests/valid_fcm_tests.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,95 @@
use serde_json::json;
use std::{
fs::{self, File},
sync::Once,
};

use oauth_fcm::{create_shared_token_manager, send_fcm_message_with_url};

use crate::test_helpers::{FcmBaseTest, TestData};

mod test_helpers;

static TRACING: Once = Once::new();

#[tokio::test]
async fn successful_fcm_test() {
// Output logs to the console
tracing_subscriber::fmt::init();
TRACING.call_once(tracing_subscriber::fmt::init);

let mut server = mockito::Server::new_async().await;

let project_id = "mock_project_id";
let base = FcmBaseTest::new(
server.url(),
"/token".to_string(),
server.url(),
format!("/v1/projects/{}/messages:send", project_id),
);

let mock_auth = server
.mock("POST", base.oauth_path.as_str())
.with_status(200)
.with_body(
json!({
"access_token": base.access_token,
"scope": "https://www.googleapis.com/auth/prediction",
"token_type": "Bearer",
"expires_in": 3600,
})
.to_string(),
)
.create();

let mock_fcm = server
.mock("POST", base.fcm_path.as_str())
.with_status(200)
.create();

let shared_token_manager =
create_shared_token_manager(File::open("tests/mock_credentials.json").unwrap())
.expect("Failed to create SharedTokenManager");

{
let mut guard = shared_token_manager.lock().await;

assert!(guard.is_token_expired());

// Get a valid first token from the mock instead of the real server
guard
.refresh_token_with_url(&base.mock_auth_url())
.await
.expect("Failed to refresh token");

assert!(!guard.is_token_expired());
assert!(guard.get_token().await.is_ok());
assert!(!guard.get_token().await.unwrap().is_empty());
}

let data = TestData {
title: "Test title".to_string(),
description: "Test description".to_string(),
};

let result = send_fcm_message_with_url(
&base.device_token,
None,
Some(data),
&shared_token_manager,
&base.mock_fcm_url(),
)
.await;

assert!(result.is_ok());

mock_auth.assert_async().await;
mock_fcm.assert_async().await;
}

#[tokio::test]
async fn successful_fcm_test_from_string() {
// Output logs to the console
TRACING.call_once(tracing_subscriber::fmt::init);

let mut server = mockito::Server::new_async().await;

Expand Down Expand Up @@ -40,8 +120,9 @@ async fn successful_fcm_test() {
.with_status(200)
.create();

let shared_token_manager = create_shared_token_manager("tests/mock_credentials.json")
.expect("Failed to create SharedTokenManager");
let creds = fs::read_to_string("tests/mock_credentials.json").unwrap();
let shared_token_manager =
create_shared_token_manager(creds.as_bytes()).expect("Failed to create SharedTokenManager");

{
let mut guard = shared_token_manager.lock().await;
Expand Down

0 comments on commit fe362d7

Please sign in to comment.