use anyhow::Result;
use serde::Deserialize;
use crate::gmail::client::GmailClient;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct Profile {
#[serde(rename = "emailAddress")]
pub email_address: String,
#[serde(rename = "messagesTotal")]
pub messages_total: i64,
#[serde(rename = "threadsTotal")]
pub threads_total: i64,
#[serde(rename = "historyId")]
pub history_id: String,
}
#[derive(Debug)]
pub struct ProfileApi<'a> {
client: &'a GmailClient,
}
impl<'a> ProfileApi<'a> {
#[must_use]
pub fn new(client: &'a GmailClient) -> Self {
Self { client }
}
pub async fn get(&self) -> Result<Profile> {
let url = GmailClient::api_url(self.client.base_url(), "/gmail/v1/users/me/profile")?;
self.client
.get_parsed(url.as_str(), "Failed to parse users.getProfile response")
.await
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::gmail::auth::{GmailCredentials, GmailScope};
use crate::utils::secret::Secret;
fn test_credentials() -> GmailCredentials {
GmailCredentials {
client_id: "client-1".to_string(),
client_secret: Secret::new("secret-1"),
refresh_token: Secret::new("refresh-1"),
scope: GmailScope::ReadOnly,
}
}
async fn client_with_bootstrapped_token(server: &wiremock::MockServer) -> GmailClient {
wiremock::Mock::given(wiremock::matchers::method("POST"))
.and(wiremock::matchers::path("/token"))
.respond_with(
wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({
"access_token": "test-token",
"expires_in": 3600,
})),
)
.mount(server)
.await;
let mut client = GmailClient::new(&server.uri(), &test_credentials()).unwrap();
crate::gmail::client::test_support::replace_session(
&mut client,
&test_credentials(),
&format!("{}/token", server.uri()),
);
client
}
#[tokio::test]
async fn get_parses_profile_fields() {
let server = wiremock::MockServer::start().await;
let client = client_with_bootstrapped_token(&server).await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/gmail/v1/users/me/profile"))
.respond_with(
wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({
"emailAddress": "user@example.com",
"messagesTotal": 100,
"threadsTotal": 42,
"historyId": "123456",
})),
)
.expect(1)
.mount(&server)
.await;
let profile = ProfileApi::new(&client).get().await.unwrap();
assert_eq!(profile.email_address, "user@example.com");
assert_eq!(profile.messages_total, 100);
assert_eq!(profile.threads_total, 42);
assert_eq!(profile.history_id, "123456");
}
#[tokio::test]
async fn get_propagates_api_errors() {
let server = wiremock::MockServer::start().await;
let client = client_with_bootstrapped_token(&server).await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/gmail/v1/users/me/profile"))
.respond_with(wiremock::ResponseTemplate::new(403).set_body_string("Forbidden"))
.mount(&server)
.await;
let err = ProfileApi::new(&client).get().await.unwrap_err();
assert!(err.to_string().contains("403"));
}
#[tokio::test]
async fn get_rejects_invalid_base_url() {
let client = GmailClient::new("not a url", &test_credentials()).unwrap();
let err = ProfileApi::new(&client).get().await.unwrap_err();
assert!(err.to_string().contains("Invalid Gmail base URL"));
}
}