Files
el/ui/vessels/el-services/src/tests.rs
T

262 lines
12 KiB
Rust

//! Tests for el-services.
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::sync::Arc;
use crate::{
binding::{
direct::DirectBinding,
grpc::GrpcBinding,
rest::RestBinding,
websocket::WebSocketBinding,
Binding, BindingKind, ServiceRequest, ServiceResponse,
},
config::{AuthConfig, ServiceConfig},
proxy::{ServiceMethod, ServiceProxy},
registry::ServiceRegistry,
};
fn rest_config(base_url: &str) -> ServiceConfig {
ServiceConfig::new("UserService", BindingKind::Rest)
.with_base_url(base_url)
}
// ── Test 1: BindingKind::from_str parses all kinds ───────────────────────
#[test]
fn test_binding_kind_from_str() {
assert_eq!(BindingKind::from_str("rest"), Some(BindingKind::Rest));
assert_eq!(BindingKind::from_str("websocket"), Some(BindingKind::WebSocket));
assert_eq!(BindingKind::from_str("ws"), Some(BindingKind::WebSocket));
assert_eq!(BindingKind::from_str("grpc"), Some(BindingKind::Grpc));
assert_eq!(BindingKind::from_str("direct"), Some(BindingKind::Direct));
assert_eq!(BindingKind::from_str("unknown"), None);
}
// ── Test 2: RestBinding derives correct HTTP methods ─────────────────────
#[test]
fn test_rest_http_method_derivation() {
assert_eq!(RestBinding::http_method("get_user"), "GET");
assert_eq!(RestBinding::http_method("list_users"), "GET");
assert_eq!(RestBinding::http_method("create_user"), "POST");
assert_eq!(RestBinding::http_method("update_user"), "PUT");
assert_eq!(RestBinding::http_method("delete_user"), "DELETE");
assert_eq!(RestBinding::http_method("patch_user"), "PATCH");
assert_eq!(RestBinding::http_method("process"), "POST");
}
// ── Test 3: RestBinding derives correct URL path ──────────────────────────
#[test]
fn test_rest_url_path() {
let config = rest_config("https://api.example.com");
let binding = RestBinding::new(config);
let mut params = HashMap::new();
params.insert("id".to_string(), "123".to_string());
let url = binding.url_path("get_user", &params);
assert!(url.contains("users"), "should use pluralized resource");
assert!(url.contains("123"), "should include id in URL");
}
// ── Test 4: RestBinding call returns ok response ──────────────────────────
#[test]
fn test_rest_binding_call() {
let config = rest_config("https://api.example.com");
let binding = RestBinding::new(config);
let request = ServiceRequest::new("UserService", "get_user")
.with_param("id", "42");
let response = binding.call(request).unwrap();
assert!(response.is_success());
assert!(response.body.contains("UserService"));
}
// ── Test 5: RestBinding without base_url returns error ───────────────────
#[test]
fn test_rest_binding_no_base_url_errors() {
let config = ServiceConfig::new("UserService", BindingKind::Rest);
let binding = RestBinding::new(config);
let request = ServiceRequest::new("UserService", "get_user");
let result = binding.call(request);
assert!(result.is_err(), "should error without base_url");
}
// ── Test 6: DirectBinding registers and calls handlers ───────────────────
#[test]
fn test_direct_binding_register_and_call() {
let binding = DirectBinding::new();
binding.register("UserService", "get_user", |params| {
let id = params.get("id").cloned().unwrap_or_default();
Ok(format!("{{\"id\":\"{}\",\"name\":\"Alice\"}}", id))
});
let request = ServiceRequest::new("UserService", "get_user")
.with_param("id", "42");
let response = binding.call(request).unwrap();
assert!(response.is_success());
assert!(response.body.contains("Alice"));
assert!(response.body.contains("42"));
}
// ── Test 7: DirectBinding returns error for missing handler ──────────────
#[test]
fn test_direct_binding_missing_handler() {
let binding = DirectBinding::new();
let request = ServiceRequest::new("UserService", "nonexistent");
let result = binding.call(request);
assert!(result.is_err(), "should error on missing handler");
}
// ── Test 8: DirectBinding::has_handler works ─────────────────────────────
#[test]
fn test_direct_binding_has_handler() {
let binding = DirectBinding::new();
assert!(!binding.has_handler("UserService", "get_user"));
binding.register("UserService", "get_user", |_| Ok("{}".into()));
assert!(binding.has_handler("UserService", "get_user"));
}
// ── Test 9: GrpcBinding builds correct endpoint path ────────────────────
#[test]
fn test_grpc_endpoint_path() {
let config = ServiceConfig::new("UserService", BindingKind::Grpc)
.with_base_url("http://localhost:50051");
let binding = GrpcBinding::new(config).with_package("myapp.v1");
let endpoint = binding.grpc_endpoint("get_user");
assert_eq!(endpoint, "/myapp.v1.UserService/GetUser");
}
// ── Test 10: GrpcBinding without package ────────────────────────────────
#[test]
fn test_grpc_endpoint_no_package() {
let config = ServiceConfig::new("OrderService", BindingKind::Grpc)
.with_base_url("http://localhost:50051");
let binding = GrpcBinding::new(config);
let endpoint = binding.grpc_endpoint("list_orders");
assert_eq!(endpoint, "/OrderService/ListOrders");
}
// ── Test 11: WebSocketBinding converts URL protocol ──────────────────────
#[test]
fn test_websocket_url_conversion() {
let config = ServiceConfig::new("ChatService", BindingKind::WebSocket)
.with_base_url("https://ws.example.com");
let binding = WebSocketBinding::new(config);
let ws_url = binding.ws_url().unwrap();
assert!(ws_url.starts_with("wss://"), "should convert https to wss");
}
// ── Test 12: WebSocketBinding serializes request correctly ───────────────
#[test]
fn test_websocket_request_serialization() {
let config = ServiceConfig::new("ChatService", BindingKind::WebSocket)
.with_base_url("wss://ws.example.com");
let binding = WebSocketBinding::new(config);
let request = ServiceRequest::new("ChatService", "send_message")
.with_param("content", "hello");
let msg = binding.serialize_request(&request, "test-id-123");
assert!(msg.contains("test-id-123"));
assert!(msg.contains("ChatService"));
assert!(msg.contains("send_message"));
assert!(msg.contains("hello"));
}
// ── Test 13: ServiceProxy::call_positional maps args to params ───────────
#[test]
fn test_service_proxy_positional_call() {
let binding = DirectBinding::new();
binding.register("UserService", "create_user", |params| {
let name = params.get("name").cloned().unwrap_or_default();
let email = params.get("email").cloned().unwrap_or_default();
Ok(format!("{{\"name\":\"{}\",\"email\":\"{}\"}}", name, email))
});
let config = ServiceConfig::new("UserService", BindingKind::Direct);
let method = ServiceMethod::new("create_user", "User")
.with_params(vec!["name", "email"]);
let proxy = ServiceProxy::new("UserService", config, Arc::new(binding))
.with_method(method);
let response = proxy
.call_positional("create_user", vec!["Alice".into(), "alice@example.com".into()])
.unwrap();
assert!(response.body.contains("Alice"));
assert!(response.body.contains("alice@example.com"));
}
// ── Test 14: ServiceRegistry stores and retrieves services ───────────────
#[test]
fn test_service_registry() {
let registry = ServiceRegistry::new();
let config = ServiceConfig::new("UserService", BindingKind::Direct);
let proxy = ServiceProxy::new("UserService", config, Arc::new(DirectBinding::new()));
registry.register(proxy);
let retrieved = registry.get("UserService").unwrap();
assert_eq!(retrieved.service_name, "UserService");
}
// ── Test 15: ServiceRegistry returns error for unknown service ────────────
#[test]
fn test_service_registry_not_found() {
let registry = ServiceRegistry::new();
let result = registry.get("NonExistentService");
assert!(result.is_err());
}
// ── Test 16: ServiceConfig with_auth sets bearer token ───────────────────
#[test]
fn test_service_config_with_auth() {
let config = ServiceConfig::new("UserService", BindingKind::Rest)
.with_base_url("https://api.example.com")
.with_auth(AuthConfig::Bearer { token_env: "API_TOKEN".into() });
match &config.auth {
AuthConfig::Bearer { token_env } => assert_eq!(token_env, "API_TOKEN"),
_ => panic!("expected Bearer auth"),
}
}
// ── Test 17: ServiceResponse::is_success ─────────────────────────────────
#[test]
fn test_service_response_success() {
assert!(ServiceResponse::ok("{}").is_success());
assert!(!ServiceResponse::error(404, "not found").is_success());
assert!(!ServiceResponse::error(500, "error").is_success());
}
// ── Test 18: WebSocketBinding supports streaming ─────────────────────────
#[test]
fn test_websocket_supports_streaming() {
let config = ServiceConfig::new("ChatService", BindingKind::WebSocket)
.with_base_url("wss://ws.example.com");
let binding = WebSocketBinding::new(config);
assert!(binding.supports_streaming());
}
// ── Test 19: RestBinding::build_request_description includes method ───────
#[test]
fn test_rest_request_description() {
let config = rest_config("https://api.example.com");
let binding = RestBinding::new(config);
let request = ServiceRequest::new("UserService", "create_user")
.with_param("name", "Alice");
let desc = binding.build_request_description(&request);
assert!(desc.contains("POST"), "create_ maps to POST");
assert!(desc.contains("users"), "resource name derived from service");
}
// ── Test 20: ServiceRegistry lists all service names ─────────────────────
#[test]
fn test_service_registry_list() {
let registry = ServiceRegistry::new();
for name in ["UserService", "OrderService", "ProductService"] {
let config = ServiceConfig::new(name, BindingKind::Direct);
let proxy = ServiceProxy::new(name, config, Arc::new(DirectBinding::new()));
registry.register(proxy);
}
let mut names = registry.service_names();
names.sort();
assert_eq!(names, vec!["OrderService", "ProductService", "UserService"]);
assert_eq!(registry.len(), 3);
}
}