feat: add grpc support
This commit is contained in:
+170
-4
@@ -8,8 +8,9 @@ use crate::{
|
||||
auth_profiles::{create_auth_profile, get_auth_profile, list_auth_profiles},
|
||||
operations::{
|
||||
create_operation, create_version, export_operation, generate_draft, get_operation,
|
||||
get_operation_version, list_operations, publish_operation, run_test, upload_input_json,
|
||||
upload_output_json,
|
||||
get_operation_version, list_grpc_services, list_operations, publish_operation,
|
||||
run_test, upload_descriptor_set, upload_input_json, upload_output_json,
|
||||
upload_proto_descriptor,
|
||||
},
|
||||
},
|
||||
state::AppState,
|
||||
@@ -38,6 +39,18 @@ pub fn build_app(state: AppState) -> Router {
|
||||
"/operations/{operation_id}/samples/output-json",
|
||||
post(upload_output_json),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/descriptors/proto",
|
||||
post(upload_proto_descriptor),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/descriptors/descriptor-set",
|
||||
post(upload_descriptor_set),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/grpc/services",
|
||||
get(list_grpc_services),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/drafts/generate",
|
||||
post(generate_draft),
|
||||
@@ -67,9 +80,10 @@ mod tests {
|
||||
};
|
||||
|
||||
use axum::{Json, Router, routing::post};
|
||||
use mcpaas_adapter_grpc::test_support as grpc_test_support;
|
||||
use mcpaas_core::{
|
||||
ExecutionConfig, GraphqlOperationType, GraphqlTarget, HttpMethod, Protocol, RestTarget,
|
||||
Target, ToolDescription,
|
||||
DescriptorId, ExecutionConfig, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod,
|
||||
Protocol, RestTarget, Target, ToolDescription,
|
||||
};
|
||||
use mcpaas_mapping::{MappingRule, MappingSet};
|
||||
use mcpaas_registry::PostgresRegistry;
|
||||
@@ -199,6 +213,105 @@ mod tests {
|
||||
assert_eq!(test_run["response_preview"]["id"], "lead_123");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn uploads_descriptor_set_and_lists_grpc_services() {
|
||||
let registry = test_registry().await;
|
||||
let storage_root = test_storage_root("grpc_descriptor");
|
||||
let server_addr = grpc_test_support::spawn_unary_echo_server().await;
|
||||
let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let created = client
|
||||
.post(format!("{base_url}/operations"))
|
||||
.json(&test_grpc_operation_payload(
|
||||
&server_addr,
|
||||
"echo_descriptor",
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id = created["operation_id"].as_str().unwrap().to_owned();
|
||||
|
||||
let uploaded = client
|
||||
.post(format!(
|
||||
"{base_url}/operations/{operation_id}/descriptors/descriptor-set"
|
||||
))
|
||||
.header("x-file-name", "echo_descriptor.bin")
|
||||
.body(grpc_test_support::echo::FILE_DESCRIPTOR_SET.to_vec())
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
let services = client
|
||||
.get(format!(
|
||||
"{base_url}/operations/{operation_id}/grpc/services"
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(uploaded["descriptor_id"].as_str().is_some());
|
||||
assert_eq!(services["services"][0]["package"], "echo");
|
||||
assert_eq!(services["services"][0]["service"], "EchoService");
|
||||
assert_eq!(services["services"][0]["methods"][0]["name"], "UnaryEcho");
|
||||
assert_eq!(services["services"][0]["methods"][0]["kind"], "unary");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn creates_publishes_and_tests_grpc_operation() {
|
||||
let registry = test_registry().await;
|
||||
let storage_root = test_storage_root("grpc_runtime");
|
||||
let server_addr = grpc_test_support::spawn_unary_echo_server().await;
|
||||
let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let created = client
|
||||
.post(format!("{base_url}/operations"))
|
||||
.json(&test_grpc_operation_payload(&server_addr, "echo_runtime"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id = created["operation_id"].as_str().unwrap().to_owned();
|
||||
|
||||
let published = client
|
||||
.post(format!("{base_url}/operations/{operation_id}/publish"))
|
||||
.json(&json!({ "version": 1 }))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
let test_run = client
|
||||
.post(format!("{base_url}/operations/{operation_id}/test-runs"))
|
||||
.json(&json!({
|
||||
"version": 1,
|
||||
"input": { "message": "hello" }
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(published["published_version"], 1);
|
||||
assert_eq!(test_run["ok"], true);
|
||||
assert_eq!(test_run["request_preview"]["grpc"]["message"], "hello");
|
||||
assert_eq!(test_run["response_preview"]["message"], "hello");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn manages_auth_profiles_and_yaml_upsert() {
|
||||
let registry = test_registry().await;
|
||||
@@ -536,6 +649,59 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn test_grpc_operation_payload(server_addr: &str, name: &str) -> OperationPayload {
|
||||
OperationPayload {
|
||||
name: name.to_owned(),
|
||||
display_name: "Unary Echo gRPC".to_owned(),
|
||||
protocol: Protocol::Grpc,
|
||||
target: Target::Grpc(GrpcTarget {
|
||||
server_addr: server_addr.to_owned(),
|
||||
package: "echo".to_owned(),
|
||||
service: "EchoService".to_owned(),
|
||||
method: "UnaryEcho".to_owned(),
|
||||
descriptor_ref: DescriptorId::new("desc_echo"),
|
||||
descriptor_set_b64: grpc_test_support::descriptor_set_b64(),
|
||||
}),
|
||||
input_schema: object_schema("message"),
|
||||
output_schema: object_schema("message"),
|
||||
input_mapping: MappingSet {
|
||||
rules: vec![MappingRule {
|
||||
source: "$.mcp.message".to_owned(),
|
||||
target: "$.request.grpc.message".to_owned(),
|
||||
required: true,
|
||||
default_value: None,
|
||||
transform: None,
|
||||
condition: None,
|
||||
notes: None,
|
||||
}],
|
||||
},
|
||||
output_mapping: MappingSet {
|
||||
rules: vec![MappingRule {
|
||||
source: "$.response.data.message".to_owned(),
|
||||
target: "$.output.message".to_owned(),
|
||||
required: true,
|
||||
default_value: None,
|
||||
transform: None,
|
||||
condition: None,
|
||||
notes: None,
|
||||
}],
|
||||
},
|
||||
execution_config: ExecutionConfig {
|
||||
timeout_ms: 1_000,
|
||||
retry_policy: None,
|
||||
auth_profile_ref: None,
|
||||
headers: BTreeMap::new(),
|
||||
protocol_options: None,
|
||||
},
|
||||
tool_description: ToolDescription {
|
||||
title: "Unary Echo gRPC".to_owned(),
|
||||
description: "Echoes a unary gRPC payload".to_owned(),
|
||||
tags: vec!["grpc".to_owned()],
|
||||
examples: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn object_schema(field_name: &str) -> Schema {
|
||||
Schema {
|
||||
kind: SchemaKind::Object,
|
||||
|
||||
Reference in New Issue
Block a user