use lenso_service::{
MODULE_CONTRACT_SCHEMA_JSON, MODULE_RELEASE_SCHEMA_JSON, ModuleContract, ModuleManifest,
SERVICE_CONTRACT_SCHEMA_JSON, SERVICE_SYSTEM_SCHEMA_JSON, SERVICE_WORKSPACE_SCHEMA_JSON,
ServiceCompatibility, ServiceContract, ServiceHealth, ServiceLocalProcess, ServiceProvider,
ServiceSystem, ServiceSystemDependency, ServiceSystemModule, ServiceSystemService,
ServiceWorkspace, ServiceWorkspaceService, service_system_graph,
validate_module_contract_value, validate_service_contract_value, validate_service_system_value,
validate_service_workspace_value,
};
use serde_json::json;
#[test]
fn service_contract_serializes_provider_and_modules() {
let contract = ServiceContract::new(
"support-suite-provider",
vec![
ModuleManifest::builder("support-ticket")
.capabilities(vec!["support_ticket.tickets.read".to_owned()])
.build(),
],
)
.version("0.2.0")
.provider(ServiceProvider {
name: "support-suite-provider".to_owned(),
vendor: Some("Lenso".to_owned()),
summary: Some("Support workflow provider".to_owned()),
homepage: None,
})
.compatibility(ServiceCompatibility {
remote_protocol_version: Some("1".to_owned()),
required_host_features: vec!["service.status".to_owned()],
sdk_language: Some("rust".to_owned()),
sdk_version: Some("0.1.0".to_owned()),
})
.health(ServiceHealth {
ready_url: Some("http://127.0.0.1:4110/lenso/service/v1/ready".to_owned()),
status_url: Some("http://127.0.0.1:4110/lenso/service/v1/status".to_owned()),
..ServiceHealth::default()
})
.local_process(ServiceLocalProcess {
command: "cargo run".to_owned(),
cwd: None,
env: Default::default(),
auto_start: true,
ready_timeout_ms: 30_000,
});
let value = serde_json::to_value(contract).unwrap();
assert_eq!(value["name"], "support-suite-provider");
assert_eq!(value["version"], "0.2.0");
assert_eq!(value["provider"]["vendor"], "Lenso");
assert_eq!(value["compatibility"]["remoteProtocolVersion"], "1");
assert_eq!(
value["health"]["readyUrl"],
"http://127.0.0.1:4110/lenso/service/v1/ready"
);
assert_eq!(
value["health"]["statusUrl"],
"http://127.0.0.1:4110/lenso/service/v1/status"
);
assert_eq!(value["modules"][0]["name"], "support-ticket");
let provider = value["provider"].as_object().unwrap();
let health = value["health"].as_object().unwrap();
assert!(!provider.contains_key("homepage"));
assert!(!health.contains_key("manifestUrl"));
assert!(!health.contains_key("livenessUrl"));
assert!(validate_service_contract_value(&value).is_empty());
}
#[test]
fn service_contract_schema_is_packaged_with_the_sdk() {
let schema: serde_json::Value = serde_json::from_str(SERVICE_CONTRACT_SCHEMA_JSON).unwrap();
assert_eq!(schema["title"], "LensoServiceContract");
assert_eq!(schema["required"], json!(["name", "modules"]));
}
#[test]
fn module_release_schema_is_packaged_with_the_sdk() {
let schema: serde_json::Value = serde_json::from_str(MODULE_RELEASE_SCHEMA_JSON).unwrap();
assert_eq!(schema["title"], "LensoModuleRelease");
assert_eq!(
schema["required"],
json!(["protocol", "name", "version", "source"])
);
}
#[test]
fn module_contract_schema_is_packaged_with_the_sdk() {
let schema: serde_json::Value = serde_json::from_str(MODULE_CONTRACT_SCHEMA_JSON).unwrap();
assert_eq!(schema["title"], "LensoModuleContract");
assert_eq!(
schema["required"],
json!(["protocol", "name", "version", "source"])
);
}
#[test]
fn service_workspace_schema_is_packaged_with_the_sdk() {
let schema: serde_json::Value = serde_json::from_str(SERVICE_WORKSPACE_SCHEMA_JSON).unwrap();
assert_eq!(schema["title"], "LensoServiceWorkspace");
assert_eq!(schema["required"], json!(["protocol"]));
}
#[test]
fn service_system_schema_is_packaged_with_the_sdk() {
let schema: serde_json::Value = serde_json::from_str(SERVICE_SYSTEM_SCHEMA_JSON).unwrap();
assert_eq!(schema["title"], "LensoServiceSystem");
assert_eq!(schema["required"], json!(["protocol", "name"]));
}
#[test]
fn service_workspace_serializes_local_services() {
let workspace = ServiceWorkspace::new(vec![ServiceWorkspaceService {
auto_start: true,
command: "pnpm start".to_owned(),
cwd: "services/support-suite-provider".to_owned(),
lang: "ts".to_owned(),
manifest: "lenso.service.json".to_owned(),
modules: vec!["support-ticket".to_owned()],
name: "support-suite-provider".to_owned(),
ready_timeout_ms: 10_000,
ready_url: "http://127.0.0.1:4110/lenso/service/v1/status".to_owned(),
}]);
let value = serde_json::to_value(workspace).unwrap();
assert_eq!(value["protocol"], "lenso.service-workspace.v1");
assert_eq!(value["services"][0]["name"], "support-suite-provider");
assert_eq!(
value["services"][0]["readyUrl"],
"http://127.0.0.1:4110/lenso/service/v1/status"
);
assert!(validate_service_workspace_value(&value).is_empty());
}
#[test]
fn service_system_serializes_services_modules_and_dependencies() {
let mut system = ServiceSystem::new("support-platform");
system.environments = vec!["local".to_owned(), "staging".to_owned(), "prod".to_owned()];
system.services = vec![
ServiceSystemService {
cwd: Some("services/support".to_owned()),
manifest: Some("lenso.service.json".to_owned()),
modules: vec!["support-ticket".to_owned()],
name: "support".to_owned(),
target: "local".to_owned(),
},
ServiceSystemService {
cwd: None,
manifest: None,
modules: vec!["invoice".to_owned()],
name: "billing".to_owned(),
target: "kubernetes".to_owned(),
},
];
system.modules = vec![
ServiceSystemModule {
capabilities: vec!["support.ticket.read".to_owned()],
dependencies: vec!["billing.invoice.read".to_owned()],
install_to: Some("service:support".to_owned()),
name: "support-ticket".to_owned(),
},
ServiceSystemModule {
capabilities: vec!["billing.invoice.read".to_owned()],
dependencies: Vec::new(),
install_to: Some("service:billing".to_owned()),
name: "invoice".to_owned(),
},
];
system.dependencies = vec![ServiceSystemDependency {
capability: "billing.invoice.read".to_owned(),
from: "support".to_owned(),
to: Some("billing".to_owned()),
}];
let value = serde_json::to_value(&system).unwrap();
assert_eq!(value["protocol"], "lenso.system.v1");
assert_eq!(value["services"][0]["modules"][0], "support-ticket");
assert!(validate_service_system_value(&value).is_empty());
let graph = service_system_graph(&system);
assert_eq!(graph.name, "support-platform");
assert_eq!(graph.modules[0].owner, "support");
assert_eq!(graph.dependencies[0].state, "resolved");
assert!(graph.issues.is_empty());
}
#[test]
fn service_system_graph_reports_unresolved_dependencies() {
let mut system = ServiceSystem::new("support-platform");
system.services = vec![ServiceSystemService {
cwd: None,
manifest: None,
modules: vec!["support-ticket".to_owned()],
name: "support".to_owned(),
target: "local".to_owned(),
}];
system.modules = vec![ServiceSystemModule {
capabilities: Vec::new(),
dependencies: vec!["billing.invoice.read".to_owned()],
install_to: Some("service:support".to_owned()),
name: "support-ticket".to_owned(),
}];
let graph = service_system_graph(&system);
assert_eq!(graph.dependencies[0].state, "unresolved");
assert_eq!(graph.issues[0].code, "dependency_unresolved");
}
#[test]
fn service_system_graph_checks_explicit_target_capabilities() {
let mut system = ServiceSystem::new("support-platform");
system.services = vec![ServiceSystemService {
cwd: None,
manifest: None,
modules: vec!["billing".to_owned()],
name: "billing-service".to_owned(),
target: "external".to_owned(),
}];
system.modules = vec![ServiceSystemModule {
capabilities: vec!["billing.invoice.read".to_owned()],
dependencies: Vec::new(),
install_to: Some("service:billing-service".to_owned()),
name: "billing".to_owned(),
}];
system.dependencies = vec![ServiceSystemDependency {
capability: "billing.invoice.write".to_owned(),
from: "support-service".to_owned(),
to: Some("billing-service".to_owned()),
}];
let graph = service_system_graph(&system);
assert_eq!(graph.dependencies[0].state, "missing_capability");
assert_eq!(graph.issues[0].code, "dependency_missing_capability");
}
#[test]
fn module_contract_serializes_standalone_module_shape() {
let contract = ModuleContract::new("support-ticket", "0.2.0", "linked").manifest(
ModuleManifest::builder("support-ticket")
.capabilities(vec!["support_ticket.tickets.read".to_owned()])
.build(),
);
let value = serde_json::to_value(contract).unwrap();
assert_eq!(value["protocol"], "lenso.module.v1");
assert_eq!(value["source"], "linked");
assert_eq!(value["manifest"]["name"], "support-ticket");
assert!(validate_module_contract_value(&value).is_empty());
}
#[test]
fn service_contract_validation_reports_paths() {
let issues = validate_service_contract_value(&json!({
"name": "",
"install": {
"services": [
{ "name": "support-suite-provider" }
]
},
"modules": [
{
"name": "support-ticket",
"capabilities": ["support_ticket.read", 42]
},
{
"name": "support-ticket"
}
]
}));
let paths = issues
.iter()
.map(|issue| issue.path.as_str())
.collect::<Vec<_>>();
assert!(paths.contains(&"$.name"));
assert!(paths.contains(&"$.install.services[0].command"));
assert!(paths.contains(&"$.modules[0].capabilities[1]"));
assert!(paths.contains(&"$.modules[1].name"));
}
#[test]
fn service_system_validation_reports_paths() {
let issues = validate_service_system_value(&json!({
"protocol": "lenso.system.v1",
"name": "",
"services": [
{ "name": "support", "target": "local", "modules": ["support-ticket"] },
{ "name": "support", "target": "" }
],
"modules": [
{ "name": "support-ticket", "installTo": "service:support", "dependencies": [42] }
],
"dependencies": [
{ "from": "support" }
]
}));
let paths = issues
.iter()
.map(|issue| issue.path.as_str())
.collect::<Vec<_>>();
assert!(paths.contains(&"$.name"));
assert!(paths.contains(&"$.services[1].name"));
assert!(paths.contains(&"$.services[1].target"));
assert!(paths.contains(&"$.modules[0].dependencies[0]"));
assert!(paths.contains(&"$.dependencies[0].capability"));
}