262 lines
12 KiB
Rust
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", ¶ms);
|
|
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);
|
|
}
|
|
}
|