dbx-tools-auth 0.6.182

Provider-neutral OAuth, credential storage, and locking
Documentation
use std::{sync::Arc, time::Duration};

use dbx_tools_auth::{CredentialStore, FileStore, MemoryStore, Token};
use tempfile::tempdir;
use time::OffsetDateTime;

fn token(value: &str) -> Token {
    Token {
        access_token: value.to_owned(),
        token_type: "Bearer".to_owned(),
        refresh_token: Some("refresh".to_owned()),
        expires_at: Some(OffsetDateTime::now_utc() + time::Duration::hours(1)),
        scopes: vec!["all-apis".to_owned(), "offline_access".to_owned()],
    }
}

#[tokio::test]
async fn memory_store_round_trips_tokens() {
    let store = MemoryStore::new();
    store.save("profile", &token("access")).await.unwrap();
    assert_eq!(
        store.load("profile").await.unwrap().unwrap().access_token,
        "access"
    );
    store.delete("profile").await.unwrap();
    assert!(store.load("profile").await.unwrap().is_none());
}

#[tokio::test]
async fn file_store_round_trips_tokens() {
    let directory = tempdir().unwrap();
    let store = FileStore::new(directory.path().to_path_buf()).unwrap();
    store.save("profile", &token("access")).await.unwrap();
    assert_eq!(
        store.load("profile").await.unwrap().unwrap().access_token,
        "access"
    );
    let raw = std::fs::read_to_string(directory.path().join("token-cache.json")).unwrap();
    assert!(raw.contains("\"version\": 1"));
    assert!(raw.contains("\"profile\""));
    assert!(raw.contains("\"expiry\""));
}

#[tokio::test]
async fn file_refresh_locks_are_exclusive_across_profiles() {
    let directory = tempdir().unwrap();
    let first = Arc::new(FileStore::new(directory.path().to_path_buf()).unwrap());
    let second = Arc::new(FileStore::new(directory.path().to_path_buf()).unwrap());
    let held = first.lock("profile", Duration::from_secs(1)).await.unwrap();
    let error = match second
        .lock("other-profile", Duration::from_millis(100))
        .await
    {
        Ok(_) => panic!("second store unexpectedly acquired the profile lock"),
        Err(error) => error,
    };
    assert!(error.to_string().contains("timed out"));
    drop(held);
    second
        .lock("profile", Duration::from_secs(1))
        .await
        .unwrap();
}

#[tokio::test]
async fn file_store_preserves_other_cli_entries() {
    let directory = tempdir().unwrap();
    let path = directory.path().join("token-cache.json");
    let existing = serde_json::json!({"access_token": "cli", "custom": {"future": true}});
    std::fs::write(
        &path,
        serde_json::to_vec(&serde_json::json!({
            "version": 1, "tokens": {"other": existing}
        }))
        .unwrap(),
    )
    .unwrap();
    let store = FileStore::new(directory.path().to_path_buf()).unwrap();
    let held = store.lock("profile", Duration::from_secs(1)).await.unwrap();
    store.save("profile", &token("access")).await.unwrap();
    store.delete("profile").await.unwrap();
    drop(held);
    let actual: serde_json::Value = serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap();
    assert_eq!(actual["tokens"]["other"], existing);
}