наблюдаемость: завершить базовый контур Community
CI / Rust Checks (push) Failing after 4m28s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Community Image Smoke (push) Has been skipped
CI / Deploy (push) Has been skipped

Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
This commit is contained in:
2026-07-31 01:01:14 +03:00
parent 99bd05c145
commit 0e8f1ca03a
160 changed files with 13506 additions and 1499 deletions
@@ -8,9 +8,19 @@ use axum::{
routing::{get, post},
};
use crank_adapter_rest::{OutboundHttpPolicy, RestAdapter, RestAdapterError, RestRequest};
use crank_core::{HttpMethod, RestTarget};
use crank_core::{
HttpMethod, PreparedRequest, ProtocolAdapter, RestTarget, RuntimeRequestContext, Target,
};
use opentelemetry::{
global,
trace::{TraceContextExt, TracerProvider as _},
};
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::SdkTracerProvider};
use serde_json::{Value, json};
use tokio::net::TcpListener;
use tracing::Instrument;
use tracing_opentelemetry::OpenTelemetrySpanExt;
use tracing_subscriber::layer::SubscriberExt;
#[tokio::test]
async fn executes_rest_request_and_normalizes_json_response() {
@@ -45,6 +55,120 @@ async fn executes_rest_request_and_normalizes_json_response() {
);
}
#[tokio::test]
async fn protocol_context_overrides_mapped_correlation_headers() {
let base_url = spawn_test_server().await;
let adapter = test_adapter();
let target = Target::Rest(RestTarget {
base_url,
method: HttpMethod::Post,
path_template: "/users/{user_id}".to_owned(),
static_headers: BTreeMap::from([
("x-request-id".to_owned(), "static-request".to_owned()),
(
"x-correlation-id".to_owned(),
"static-correlation".to_owned(),
),
]),
});
let prepared = PreparedRequest {
path_params: BTreeMap::from([("user_id".to_owned(), "42".to_owned())]),
headers: BTreeMap::from([
("x-request-id".to_owned(), "mapped-request".to_owned()),
(
"x-correlation-id".to_owned(),
"mapped-correlation".to_owned(),
),
]),
body: Some(json!({ "name": "Ada" })),
timeout_ms: 1_000,
..PreparedRequest::default()
};
let context = RuntimeRequestContext::new("req-runtime", "corr-runtime");
let response = adapter
.invoke_unary(&target, &prepared, &context)
.await
.unwrap();
assert_eq!(response.body["request_id"], "req-runtime");
assert_eq!(response.body["correlation_id"], "corr-runtime");
}
#[tokio::test(flavor = "current_thread")]
async fn current_trace_context_overrides_mapped_traceparent() {
global::set_text_map_propagator(TraceContextPropagator::new());
let provider = SdkTracerProvider::builder().build();
let tracer = provider.tracer("rest-propagation-test");
let subscriber =
tracing_subscriber::registry().with(tracing_opentelemetry::layer().with_tracer(tracer));
let dispatch = tracing::Dispatch::new(subscriber);
let _dispatch_guard = tracing::dispatcher::set_default(&dispatch);
let span = tracing::info_span!("runtime.execute");
let context = span.context();
let expected_trace_id = context.span().span_context().trace_id().to_string();
let base_url = spawn_test_server().await;
let target = RestTarget {
base_url,
method: HttpMethod::Post,
path_template: "/users/{user_id}".to_owned(),
static_headers: BTreeMap::new(),
};
let request = RestRequest {
path_params: BTreeMap::from([("user_id".to_owned(), "42".to_owned())]),
query_params: BTreeMap::new(),
headers: BTreeMap::from([(
"traceparent".to_owned(),
"00-11111111111111111111111111111111-2222222222222222-01".to_owned(),
)]),
body: Some(json!({ "name": "Ada" })),
timeout_ms: 1_000,
};
let response = test_adapter()
.execute(&target, &request)
.instrument(span)
.await
.unwrap();
assert_eq!(
&response.body["traceparent"].as_str().unwrap()[3..35],
expected_trace_id
);
provider.shutdown().unwrap();
}
#[tokio::test]
async fn user_configured_propagation_headers_are_removed_without_trusted_context() {
let base_url = spawn_test_server().await;
let target = RestTarget {
base_url,
method: HttpMethod::Post,
path_template: "/users/{user_id}".to_owned(),
static_headers: BTreeMap::from([
(
"traceparent".to_owned(),
"untrusted\ninvalid-value".to_owned(),
),
("tracestate".to_owned(), "vendor=value".to_owned()),
("baggage".to_owned(), "secret=must-not-leave".to_owned()),
]),
};
let request = RestRequest {
path_params: BTreeMap::from([("user_id".to_owned(), "42".to_owned())]),
query_params: BTreeMap::new(),
headers: BTreeMap::new(),
body: Some(json!({ "name": "Ada" })),
timeout_ms: 1_000,
};
let response = test_adapter().execute(&target, &request).await.unwrap();
assert!(response.body.get("traceparent").is_none());
assert!(response.body.get("tracestate").is_none());
assert!(response.body.get("baggage").is_none());
}
#[tokio::test]
async fn returns_unexpected_status_with_normalized_body() {
let base_url = spawn_test_server().await;
@@ -186,14 +310,57 @@ async fn create_user(
.get("x-static")
.and_then(|value| value.to_str().ok())
.unwrap_or_default();
let request_id = headers
.get("x-request-id")
.and_then(|value| value.to_str().ok());
let correlation_id = headers
.get("x-correlation-id")
.and_then(|value| value.to_str().ok());
let traceparent = headers
.get("traceparent")
.and_then(|value| value.to_str().ok());
let tracestate = headers
.get("tracestate")
.and_then(|value| value.to_str().ok());
let baggage = headers.get("baggage").and_then(|value| value.to_str().ok());
Json(json!({
let mut response = json!({
"id": user_id,
"query": query.get("expand").cloned().unwrap_or_default(),
"trace": trace,
"static": static_header,
"payload": payload
}))
});
let response = response.as_object_mut().unwrap();
if let Some(request_id) = request_id {
response.insert(
"request_id".to_owned(),
Value::String(request_id.to_owned()),
);
}
if let Some(correlation_id) = correlation_id {
response.insert(
"correlation_id".to_owned(),
Value::String(correlation_id.to_owned()),
);
}
if let Some(traceparent) = traceparent {
response.insert(
"traceparent".to_owned(),
Value::String(traceparent.to_owned()),
);
}
if let Some(tracestate) = tracestate {
response.insert(
"tracestate".to_owned(),
Value::String(tracestate.to_owned()),
);
}
if let Some(baggage) = baggage {
response.insert("baggage".to_owned(), Value::String(baggage.to_owned()));
}
Json(Value::Object(response.clone()))
}
async fn fail() -> (axum::http::StatusCode, Json<Value>) {