//! 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 = 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 { .. })); }