158 lines
No EOL
4.9 KiB
Rust
158 lines
No EOL
4.9 KiB
Rust
//! Unit tests for the API service and core logic.
|
|
|
|
use kra_etims_sdk::{
|
|
config::{Config, CorsConfig, Environment},
|
|
errors::SdkError,
|
|
middleware::{auth::extract_bearer_token, cors::is_origin_allowed},
|
|
models::auth::TokenInfo,
|
|
validation::validate_date_format,
|
|
};
|
|
|
|
// ── Token validity ───────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn token_not_valid_when_none() {
|
|
let info: Option<TokenInfo> = None;
|
|
assert!(info.as_ref().map(|t| t.is_valid()).unwrap_or(false) == false);
|
|
}
|
|
|
|
#[test]
|
|
fn token_not_valid_when_expired() {
|
|
let info = TokenInfo {
|
|
token: "expired".into(),
|
|
expiry: chrono::Utc::now() - chrono::Duration::seconds(60),
|
|
};
|
|
assert!(!info.is_valid());
|
|
}
|
|
|
|
#[test]
|
|
fn token_valid_when_future() {
|
|
let info = TokenInfo {
|
|
token: "valid".into(),
|
|
expiry: chrono::Utc::now() + chrono::Duration::hours(1),
|
|
};
|
|
assert!(info.is_valid());
|
|
}
|
|
|
|
// ── Auth middleware ──────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn extract_bearer_success() {
|
|
let result = extract_bearer_token(Some("Bearer abc123"));
|
|
assert!(result.is_ok());
|
|
assert_eq!(result.unwrap(), "abc123");
|
|
}
|
|
|
|
#[test]
|
|
fn extract_bearer_missing_header() {
|
|
let result = extract_bearer_token(None);
|
|
assert!(result.is_err());
|
|
assert!(matches!(result.unwrap_err(), SdkError::MissingCredentials));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_bearer_wrong_scheme() {
|
|
let result = extract_bearer_token(Some("Basic abc123"));
|
|
assert!(result.is_err());
|
|
assert!(matches!(result.unwrap_err(), SdkError::MissingCredentials));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_bearer_empty_token() {
|
|
let result = extract_bearer_token(Some("Bearer "));
|
|
assert!(result.is_err());
|
|
assert!(matches!(result.unwrap_err(), SdkError::InvalidToken));
|
|
}
|
|
|
|
// ── CORS ─────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn cors_allows_no_origin() {
|
|
let cfg = CorsConfig::default();
|
|
assert!(is_origin_allowed(None, &cfg));
|
|
}
|
|
|
|
#[test]
|
|
fn cors_allows_whitelisted() {
|
|
let cfg = CorsConfig::default();
|
|
assert!(is_origin_allowed(Some("http://localhost:3000"), &cfg));
|
|
}
|
|
|
|
#[test]
|
|
fn cors_blocks_unknown() {
|
|
let cfg = CorsConfig::default();
|
|
assert!(!is_origin_allowed(Some("https://evil.com"), &cfg));
|
|
}
|
|
|
|
#[test]
|
|
fn cors_wildcard_allows_all() {
|
|
let cfg = CorsConfig {
|
|
allowed_origins: vec!["*".into()],
|
|
..CorsConfig::default()
|
|
};
|
|
assert!(is_origin_allowed(Some("https://anything.dev"), &cfg));
|
|
}
|
|
|
|
// ── Validation ───────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn date_format_valid() {
|
|
assert!(validate_date_format("20220101010101").is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn date_format_too_short() {
|
|
assert!(validate_date_format("202201").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn date_format_non_digits() {
|
|
assert!(validate_date_format("2022-01-01T01:").is_err());
|
|
}
|
|
|
|
// ── Config ───────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn environment_from_str() {
|
|
assert_eq!(Environment::from("production"), Environment::Production);
|
|
assert_eq!(Environment::from("prod"), Environment::Production);
|
|
assert_eq!(Environment::from("development"), Environment::Development);
|
|
assert_eq!(Environment::from("anything"), Environment::Development);
|
|
}
|
|
|
|
#[test]
|
|
fn config_postcard_roundtrip() {
|
|
let cfg = Config {
|
|
api_base_url: "https://example.com".into(),
|
|
api_username: "user".into(),
|
|
api_password: "pass".into(),
|
|
environment: Environment::Development,
|
|
port: 5000,
|
|
cors: CorsConfig::default(),
|
|
endpoints: kra_etims_sdk::config::Endpoints::default(),
|
|
};
|
|
|
|
let bytes = cfg.to_postcard_bytes().expect("serialise");
|
|
let restored = Config::from_postcard_bytes(&bytes).expect("deserialise");
|
|
assert_eq!(restored.api_base_url, cfg.api_base_url);
|
|
assert_eq!(restored.port, 5000);
|
|
}
|
|
|
|
// ── Error set conversions ────────────────────────────────────
|
|
|
|
#[test]
|
|
fn auth_error_converts_to_sdk_error() {
|
|
// AuthError variants are a subset of SdkError, so `.into()` works.
|
|
let auth_err = SdkError::InvalidToken;
|
|
let sdk_err: SdkError = auth_err; // identity, but proves the type system
|
|
assert!(matches!(sdk_err, SdkError::InvalidToken));
|
|
}
|
|
|
|
#[test]
|
|
fn validation_error_converts_to_sdk_error() {
|
|
let val_err = SdkError::FieldError {
|
|
message: "bad".into(),
|
|
};
|
|
let sdk_err: SdkError = val_err;
|
|
assert!(matches!(sdk_err, SdkError::FieldError { .. }));
|
|
} |