oauth-device-flows 0.1.0

A specialized Rust library implementing OAuth 2.0 Device Authorization Grant (RFC 8628)
Documentation
//! Integration tests for OAuth device flows

use oauth_device_flows::{DeviceFlow, DeviceFlowConfig, DeviceFlowError, Provider};
use std::time::Duration;
use wiremock::{
    matchers::{method, path},
    Mock, MockServer, ResponseTemplate,
};

#[tokio::test]
async fn test_microsoft_device_flow_initialization() {
    let mock_server = MockServer::start().await;

    // Mock the device authorization endpoint
    Mock::given(method("POST"))
        .and(path("/oauth2/v2.0/devicecode"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "device_code": "device123",
            "user_code": "ABC123",
            "verification_uri": "https://microsoft.com/devicelogin",
            "expires_in": 900,
            "interval": 5
        })))
        .mount(&mock_server)
        .await;

    let config = DeviceFlowConfig::new()
        .client_id("test-client-id")
        .scopes(vec!["user.read".to_string()]);

    let device_flow = DeviceFlow::new(Provider::Microsoft, config).unwrap();

    // This would normally call the real Microsoft endpoint,
    // but for testing we'd need to mock the HTTP client
    // For now, just test that the configuration is valid
    assert!(device_flow.config().validate(Provider::Microsoft).is_ok());
}

#[test]
fn test_device_flow_config_validation() {
    // Test valid configuration
    let valid_config = DeviceFlowConfig::new()
        .client_id("test-client-id")
        .scopes(vec!["scope1".to_string()]);

    assert!(valid_config.validate(Provider::Microsoft).is_ok());

    // Test invalid configuration (empty client ID)
    let invalid_config = DeviceFlowConfig::new();

    assert!(matches!(
        invalid_config.validate(Provider::Microsoft),
        Err(DeviceFlowError::InvalidClient(_))
    ));
}

#[test]
fn test_provider_endpoints() {
    assert_eq!(
        Provider::Microsoft.device_authorization_endpoint(),
        "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode"
    );

    assert_eq!(
        Provider::GitHub.device_authorization_endpoint(),
        "https://github.com/login/device/code"
    );

    assert_eq!(
        Provider::Google.token_endpoint(),
        "https://oauth2.googleapis.com/token"
    );
}

#[test]
fn test_provider_configuration() {
    // Test that Microsoft doesn't require client secret
    assert!(!Provider::Microsoft.requires_client_secret());

    // Test that Google requires client secret
    assert!(Provider::Google.requires_client_secret());

    // Test default scopes
    let microsoft_scopes = Provider::Microsoft.default_scopes();
    assert!(!microsoft_scopes.is_empty());
    assert!(microsoft_scopes.contains(&"https://graph.microsoft.com/.default"));
}

#[test]
fn test_config_builder_pattern() {
    let config = DeviceFlowConfig::new()
        .client_id("test-id")
        .scope("scope1")
        .scope("scope2")
        .poll_interval(Duration::from_secs(10))
        .max_attempts(30)
        .header("Custom-Header", "value");

    assert_eq!(config.client_id, "test-id");
    assert_eq!(config.scopes, vec!["scope1", "scope2"]);
    assert_eq!(config.poll_interval, Duration::from_secs(10));
    assert_eq!(config.max_attempts, 30);
    assert!(config.additional_headers.contains_key("Custom-Header"));
}

#[test]
fn test_error_types() {
    let auth_error = DeviceFlowError::oauth_error("invalid_request", "Invalid request");
    assert!(matches!(auth_error, DeviceFlowError::OAuth { .. }));

    let client_error = DeviceFlowError::invalid_client("Missing client ID");
    assert!(matches!(client_error, DeviceFlowError::InvalidClient(_)));

    // Test error retryability
    assert!(DeviceFlowError::AuthorizationPending.is_retryable());
    assert!(DeviceFlowError::SlowDown.is_retryable());
    assert!(!DeviceFlowError::AuthorizationDenied.is_retryable());
    assert!(!DeviceFlowError::ExpiredToken.is_retryable());
}