This repository has been archived on 2026-05-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-ui-retired/vessels/el-services/src/tests.rs
T
Will Anderson f4abfe6fdc feat: rename crates/ → vessels/ + add El ports per sub-vessel
Belated rename commit for foundation/el-ui — was missed in the
workspace-wide crates→vessels pass earlier today. Same structural
intent as the rename in the other repos: 'crates' is the Rust word,
'vessel' is El's, and the directory rename is the marker that this
slot holds an El buildable unit even if its current contents are
still Rust pending port.

Plus the El ports themselves — manifest.el + src/main.el per sub-
vessel (el-aop, el-auth, el-config, el-i18n, el-identity, el-layout,
el-platform, el-publish, el-secrets, el-services, el-style, el-ui-
compiler). The ui-compiler is a stub: elc only emits C right now;
generating browser-target JS/Wasm is the biggest open language gap
and gets its own project. Until then, el-ui-compiler emits a JS
module that throws elc.backend_missing so callers fail loudly.
Cross-repo path dependencies in Cargo.toml updated to vessels/.
2026-04-30 18:18:39 -05:00

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);
}
}