Some checks failed
Build and Test / Validate Setup (push) Has been cancelled
Build and Test / Build (darwin-amd64) (push) Has been cancelled
Build and Test / Build (darwin-arm64) (push) Has been cancelled
Build and Test / Build (linux-amd64) (push) Has been cancelled
Build and Test / Build (windows-amd64) (push) Has been cancelled
Build and Test / Build (linux-arm64) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Package Results (push) Has been cancelled
Build and Test / Quality Gate (push) Has been cancelled
422 lines
13 KiB
Rust
422 lines
13 KiB
Rust
//! Unit tests for the authentication plugin.
|
|
|
|
use crate::auth::{self, Claims, VerificationResult};
|
|
use crate::error::{AuthError, AuthErrorKind};
|
|
use crate::helpers::{SessionInfo, UserInfo, VerifyResponse};
|
|
use crate::keyring;
|
|
use nu_protocol::Span;
|
|
|
|
// =============================================================================
|
|
// Auth Module Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn test_verification_result_success() {
|
|
let claims = Claims {
|
|
sub: "user-123".to_string(),
|
|
username: "admin".to_string(),
|
|
email: "admin@example.com".to_string(),
|
|
roles: vec!["admin".to_string(), "user".to_string()],
|
|
exp: chrono::Utc::now().timestamp() + 3600,
|
|
iat: chrono::Utc::now().timestamp(),
|
|
nbf: 0,
|
|
jti: "jti-123".to_string(),
|
|
iss: "provisioning".to_string(),
|
|
aud: "cli".to_string(),
|
|
};
|
|
|
|
let result = VerificationResult::success(claims);
|
|
assert!(result.valid);
|
|
assert!(result.claims.is_some());
|
|
assert!(result.error.is_none());
|
|
assert!(result.expires_in.is_some());
|
|
assert!(result.expires_in.unwrap() > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_verification_result_failure() {
|
|
let result = VerificationResult::failure("Token expired");
|
|
assert!(!result.valid);
|
|
assert!(result.claims.is_none());
|
|
assert!(result.error.is_some());
|
|
assert_eq!(result.error.unwrap(), "Token expired");
|
|
}
|
|
|
|
#[test]
|
|
fn test_decode_claims_invalid_token_format() {
|
|
let result = auth::decode_claims_unverified("invalid");
|
|
assert!(result.is_err());
|
|
|
|
let error = result.unwrap_err();
|
|
assert_eq!(error.kind, AuthErrorKind::InvalidToken);
|
|
}
|
|
|
|
#[test]
|
|
fn test_decode_claims_missing_parts() {
|
|
let result = auth::decode_claims_unverified("only.two");
|
|
assert!(result.is_err());
|
|
|
|
let error = result.unwrap_err();
|
|
assert_eq!(error.kind, AuthErrorKind::InvalidToken);
|
|
assert!(error.context.contains("3 parts"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_decode_claims_invalid_base64() {
|
|
let result = auth::decode_claims_unverified("header.not_valid_base64!@#$.signature");
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Error Module Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn test_auth_error_display() {
|
|
let error = AuthError::new(AuthErrorKind::InvalidCredentials, "Wrong password");
|
|
let display = format!("{}", error);
|
|
assert!(display.contains("invalid credentials"));
|
|
assert!(display.contains("Wrong password"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_auth_error_with_source() {
|
|
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
|
|
let error = AuthError::with_source(AuthErrorKind::KeyringError, "keyring failed", io_error);
|
|
|
|
let display = format!("{}", error);
|
|
assert!(display.contains("keyring"));
|
|
assert!(display.contains("caused by"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_auth_error_convenience_constructors() {
|
|
let error = AuthError::invalid_credentials("bad password");
|
|
assert_eq!(error.kind, AuthErrorKind::InvalidCredentials);
|
|
|
|
let error = AuthError::token_expired("expired 5 minutes ago");
|
|
assert_eq!(error.kind, AuthErrorKind::TokenExpired);
|
|
|
|
let error = AuthError::network_error("connection refused");
|
|
assert_eq!(error.kind, AuthErrorKind::NetworkError);
|
|
|
|
let error = AuthError::server_error("500 Internal Server Error");
|
|
assert_eq!(error.kind, AuthErrorKind::ServerError);
|
|
|
|
let error = AuthError::mfa_failed("invalid code");
|
|
assert_eq!(error.kind, AuthErrorKind::MfaFailed);
|
|
|
|
let error = AuthError::configuration_error("missing public key");
|
|
assert_eq!(error.kind, AuthErrorKind::ConfigurationError);
|
|
}
|
|
|
|
#[test]
|
|
fn test_auth_error_kind_display() {
|
|
assert_eq!(
|
|
AuthErrorKind::InvalidCredentials.to_string(),
|
|
"invalid credentials"
|
|
);
|
|
assert_eq!(AuthErrorKind::TokenExpired.to_string(), "token expired");
|
|
assert_eq!(AuthErrorKind::InvalidToken.to_string(), "invalid token format");
|
|
assert_eq!(AuthErrorKind::KeyringError.to_string(), "keyring operation failed");
|
|
assert_eq!(AuthErrorKind::NetworkError.to_string(), "network error");
|
|
assert_eq!(AuthErrorKind::MfaFailed.to_string(), "MFA verification failed");
|
|
}
|
|
|
|
#[test]
|
|
fn test_auth_error_to_labeled_error() {
|
|
let error = AuthError::new(AuthErrorKind::InvalidCredentials, "test error");
|
|
let labeled: nu_protocol::LabeledError = error.into();
|
|
// LabeledError was created successfully
|
|
assert!(format!("{:?}", labeled).contains("test error"));
|
|
}
|
|
|
|
// =============================================================================
|
|
// Keyring Module Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn test_get_current_username() {
|
|
let username = keyring::get_current_username();
|
|
// Should return a non-empty string
|
|
assert!(!username.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_tokens_nonexistent_user() {
|
|
// Should return false for a user that doesn't exist
|
|
let result = keyring::has_tokens("__test_nonexistent_user_xyz_123__");
|
|
assert!(!result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_tokens_nonexistent_user() {
|
|
// Should not error when removing tokens for non-existent user
|
|
let result = keyring::remove_tokens("__test_nonexistent_user_xyz_123__");
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_list_stored_users() {
|
|
// Currently returns empty list (platform-specific)
|
|
let users = keyring::list_stored_users();
|
|
// Just verify it doesn't panic
|
|
let _ = users.len();
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helpers Module Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn test_user_info_to_value() {
|
|
use crate::helpers::user_info_to_value;
|
|
|
|
let user = UserInfo {
|
|
id: "user-123".to_string(),
|
|
username: "admin".to_string(),
|
|
email: "admin@example.com".to_string(),
|
|
roles: vec!["admin".to_string(), "user".to_string()],
|
|
};
|
|
|
|
let value = user_info_to_value(&user, Span::test_data());
|
|
assert!(matches!(value, nu_protocol::Value::Record { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_session_info_to_value() {
|
|
use crate::helpers::session_info_to_value;
|
|
|
|
let session = SessionInfo {
|
|
id: "sess-123".to_string(),
|
|
user_id: "user-123".to_string(),
|
|
username: "admin".to_string(),
|
|
roles: vec!["admin".to_string()],
|
|
created_at: "2024-01-01T00:00:00Z".to_string(),
|
|
expires_at: "2024-01-02T00:00:00Z".to_string(),
|
|
is_active: true,
|
|
ip_address: "127.0.0.1".to_string(),
|
|
user_agent: "Mozilla/5.0".to_string(),
|
|
};
|
|
|
|
let value = session_info_to_value(&session, Span::test_data());
|
|
assert!(matches!(value, nu_protocol::Value::Record { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify_response_to_value_valid() {
|
|
use crate::helpers::verify_response_to_value;
|
|
|
|
let response = VerifyResponse {
|
|
valid: true,
|
|
user_id: Some("user-123".to_string()),
|
|
username: Some("admin".to_string()),
|
|
roles: Some(vec!["admin".to_string()]),
|
|
expires_at: Some("2024-12-31T23:59:59Z".to_string()),
|
|
};
|
|
|
|
let value = verify_response_to_value(&response, Span::test_data());
|
|
assert!(matches!(value, nu_protocol::Value::Record { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify_response_to_value_invalid() {
|
|
use crate::helpers::verify_response_to_value;
|
|
|
|
let response = VerifyResponse {
|
|
valid: false,
|
|
user_id: None,
|
|
username: None,
|
|
roles: None,
|
|
expires_at: None,
|
|
};
|
|
|
|
let value = verify_response_to_value(&response, Span::test_data());
|
|
assert!(matches!(value, nu_protocol::Value::Record { .. }));
|
|
}
|
|
|
|
// =============================================================================
|
|
// Data Structure Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn test_claims_serialization() {
|
|
let claims = Claims {
|
|
sub: "user-123".to_string(),
|
|
username: "admin".to_string(),
|
|
email: "admin@example.com".to_string(),
|
|
roles: vec!["admin".to_string()],
|
|
exp: 1700000000,
|
|
iat: 1699996400,
|
|
nbf: 0,
|
|
jti: "jti-123".to_string(),
|
|
iss: "provisioning".to_string(),
|
|
aud: "cli".to_string(),
|
|
};
|
|
|
|
// Test that claims can be serialized
|
|
let json = serde_json::to_string(&claims).expect("Should serialize");
|
|
assert!(json.contains("user-123"));
|
|
assert!(json.contains("admin"));
|
|
|
|
// Test that claims can be deserialized
|
|
let deserialized: Claims = serde_json::from_str(&json).expect("Should deserialize");
|
|
assert_eq!(deserialized.sub, "user-123");
|
|
assert_eq!(deserialized.username, "admin");
|
|
}
|
|
|
|
#[test]
|
|
fn test_user_info_serialization() {
|
|
let user = UserInfo {
|
|
id: "user-123".to_string(),
|
|
username: "admin".to_string(),
|
|
email: "admin@example.com".to_string(),
|
|
roles: vec!["admin".to_string(), "user".to_string()],
|
|
};
|
|
|
|
let json = serde_json::to_string(&user).expect("Should serialize");
|
|
assert!(json.contains("user-123"));
|
|
assert!(json.contains("admin@example.com"));
|
|
|
|
let deserialized: UserInfo = serde_json::from_str(&json).expect("Should deserialize");
|
|
assert_eq!(deserialized.id, "user-123");
|
|
assert_eq!(deserialized.roles.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_session_info_serialization() {
|
|
let session = SessionInfo {
|
|
id: "sess-123".to_string(),
|
|
user_id: "user-123".to_string(),
|
|
username: "admin".to_string(),
|
|
roles: vec!["admin".to_string()],
|
|
created_at: "2024-01-01T00:00:00Z".to_string(),
|
|
expires_at: "2024-01-02T00:00:00Z".to_string(),
|
|
is_active: true,
|
|
ip_address: "".to_string(),
|
|
user_agent: "".to_string(),
|
|
};
|
|
|
|
let json = serde_json::to_string(&session).expect("Should serialize");
|
|
let deserialized: SessionInfo = serde_json::from_str(&json).expect("Should deserialize");
|
|
assert_eq!(deserialized.id, "sess-123");
|
|
assert!(deserialized.is_active);
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify_response_serialization() {
|
|
let response = VerifyResponse {
|
|
valid: true,
|
|
user_id: Some("user-123".to_string()),
|
|
username: Some("admin".to_string()),
|
|
roles: Some(vec!["admin".to_string()]),
|
|
expires_at: Some("2024-12-31T23:59:59Z".to_string()),
|
|
};
|
|
|
|
let json = serde_json::to_string(&response).expect("Should serialize");
|
|
let deserialized: VerifyResponse = serde_json::from_str(&json).expect("Should deserialize");
|
|
assert!(deserialized.valid);
|
|
assert_eq!(deserialized.user_id, Some("user-123".to_string()));
|
|
}
|
|
|
|
// =============================================================================
|
|
// Plugin Structure Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn test_plugin_version() {
|
|
use crate::AuthPlugin;
|
|
use nu_plugin::Plugin;
|
|
|
|
let plugin = AuthPlugin;
|
|
let version = plugin.version();
|
|
// Version should be non-empty and match Cargo.toml
|
|
assert!(!version.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_plugin_commands() {
|
|
use crate::AuthPlugin;
|
|
use nu_plugin::Plugin;
|
|
|
|
let plugin = AuthPlugin;
|
|
let commands = plugin.commands();
|
|
|
|
// Should have 6 commands
|
|
assert_eq!(commands.len(), 6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_login_command_signature() {
|
|
use crate::Login;
|
|
use nu_plugin::SimplePluginCommand;
|
|
|
|
let login = Login;
|
|
assert_eq!(SimplePluginCommand::name(&login), "auth login");
|
|
|
|
let sig = SimplePluginCommand::signature(&login);
|
|
// Check required positional argument
|
|
assert!(!sig.required_positional.is_empty());
|
|
// Check examples exist
|
|
assert!(!SimplePluginCommand::examples(&login).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_logout_command_signature() {
|
|
use crate::Logout;
|
|
use nu_plugin::SimplePluginCommand;
|
|
|
|
let logout = Logout;
|
|
assert_eq!(SimplePluginCommand::name(&logout), "auth logout");
|
|
assert!(!SimplePluginCommand::examples(&logout).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify_command_signature() {
|
|
use crate::Verify;
|
|
use nu_plugin::SimplePluginCommand;
|
|
|
|
let verify = Verify;
|
|
assert_eq!(SimplePluginCommand::name(&verify), "auth verify");
|
|
assert!(!SimplePluginCommand::examples(&verify).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_sessions_command_signature() {
|
|
use crate::Sessions;
|
|
use nu_plugin::SimplePluginCommand;
|
|
|
|
let sessions = Sessions;
|
|
assert_eq!(SimplePluginCommand::name(&sessions), "auth sessions");
|
|
assert!(!SimplePluginCommand::examples(&sessions).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_mfa_enroll_command_signature() {
|
|
use crate::MfaEnroll;
|
|
use nu_plugin::SimplePluginCommand;
|
|
|
|
let mfa_enroll = MfaEnroll;
|
|
assert_eq!(SimplePluginCommand::name(&mfa_enroll), "auth mfa enroll");
|
|
assert!(!SimplePluginCommand::examples(&mfa_enroll).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_mfa_verify_command_signature() {
|
|
use crate::MfaVerify;
|
|
use nu_plugin::SimplePluginCommand;
|
|
|
|
let mfa_verify = MfaVerify;
|
|
assert_eq!(SimplePluginCommand::name(&mfa_verify), "auth mfa verify");
|
|
assert!(!SimplePluginCommand::examples(&mfa_verify).is_empty());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Constants Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn test_default_control_center_url() {
|
|
assert_eq!(auth::DEFAULT_CONTROL_CENTER_URL, "http://localhost:8081");
|
|
}
|