исправить: закрыть ревью сквозной корреляции
CI / Rust Checks (pull_request) Successful in 8m33s
CI / UI Checks (pull_request) Successful in 5s
CI / Frontend E2E (pull_request) Successful in 6m51s
CI / Community Image Smoke (pull_request) Failing after 9m10s
CI / Deploy (pull_request) Has been skipped

This commit is contained in:
2026-07-31 02:37:45 +03:00
parent 0e8f1ca03a
commit 9a7d60593a
28 changed files with 667 additions and 98 deletions
@@ -2,6 +2,5 @@ mod integration {
mod confirmation;
mod idempotency;
mod no_input_get;
mod stages;
mod valkey;
}
@@ -25,8 +25,12 @@ async fn valkey_coordination_and_rate_limit_operations_are_atomic() {
.get_host_port_ipv4(6379.tcp())
.await
.expect("Valkey port must be mapped");
let host = container
.get_host()
.await
.expect("Docker host must be resolved");
let store = Arc::new(
RedisCacheStore::connect(CacheBackend::Valkey, &format!("redis://127.0.0.1:{port}/0"))
RedisCacheStore::connect(CacheBackend::Valkey, &format!("redis://{host}:{port}/0"))
.await
.expect("runtime store must connect to Valkey"),
);
@@ -20,9 +20,13 @@ use serde_json::json;
use time::OffsetDateTime;
use tracing::{Id, Instrument, Subscriber, field::Visit, instrument::WithSubscriber};
use tracing_subscriber::{Layer, layer::SubscriberExt, registry::LookupSpan};
use uuid::Version;
static TRACING_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
#[tokio::test]
async fn successful_execution_has_real_stages_and_omits_inapplicable_ones() {
let _tracing_test_guard = TRACING_TEST_LOCK.lock().await;
let capture = TraceCapture::default();
let subscriber = tracing_subscriber::registry().with(capture.clone());
let executor = RuntimeExecutorBuilder::new()
@@ -82,6 +86,7 @@ async fn successful_execution_has_real_stages_and_omits_inapplicable_ones() {
#[tokio::test]
async fn failed_mapping_records_closed_category_and_stops_later_stages() {
let _tracing_test_guard = TRACING_TEST_LOCK.lock().await;
let capture = TraceCapture::default();
let subscriber = tracing_subscriber::registry().with(capture.clone());
let executor = RuntimeExecutorBuilder::new()
@@ -110,8 +115,46 @@ async fn failed_mapping_records_closed_category_and_stops_later_stages() {
);
}
#[tokio::test]
async fn execution_without_context_sends_generated_correlation_headers() {
let _tracing_test_guard = TRACING_TEST_LOCK.lock().await;
let captured_headers = Arc::new(Mutex::new(None));
let executor = RuntimeExecutorBuilder::new()
.register_adapter(Arc::new(ContextCapturingAdapter {
captured_headers: Arc::clone(&captured_headers),
}))
.build();
let operation = operation().into();
let result = async {
executor
.execute(&operation, &json!({"name": "without-context"}))
.await
}
.with_subscriber(tracing_subscriber::registry())
.await;
assert_eq!(result.unwrap(), json!({"accepted": true}));
let headers = captured_headers
.lock()
.expect("captured headers lock")
.clone()
.expect("adapter headers");
let request_id = headers.get("x-request-id").expect("x-request-id");
let correlation_id = headers.get("x-correlation-id").expect("x-correlation-id");
assert!(!request_id.is_empty());
assert_eq!(correlation_id, request_id);
assert_eq!(
uuid::Uuid::parse_str(request_id)
.expect("generated UUID")
.get_version(),
Some(Version::SortRand)
);
}
#[tokio::test]
async fn approval_stage_is_present_only_when_confirmation_is_required() {
let _tracing_test_guard = TRACING_TEST_LOCK.lock().await;
let capture = TraceCapture::default();
let subscriber = tracing_subscriber::registry().with(capture.clone());
let executor = RuntimeExecutorBuilder::new()
@@ -151,6 +194,7 @@ async fn approval_stage_is_present_only_when_confirmation_is_required() {
#[tokio::test]
async fn idempotency_stage_distinguishes_execution_from_replay() {
let _tracing_test_guard = TRACING_TEST_LOCK.lock().await;
let capture = TraceCapture::default();
let subscriber = tracing_subscriber::registry().with(capture.clone());
let executor = RuntimeExecutorBuilder::new()
@@ -238,6 +282,37 @@ impl ProtocolAdapter for SuccessAdapter {
}
}
struct ContextCapturingAdapter {
captured_headers: Arc<Mutex<Option<BTreeMap<String, String>>>>,
}
#[async_trait]
impl ProtocolAdapter for ContextCapturingAdapter {
fn protocol(&self) -> Protocol {
Protocol::Rest
}
fn supports_mode(&self, mode: ExecutionMode) -> bool {
mode == ExecutionMode::Unary
}
async fn invoke_unary(
&self,
_target: &Target,
_prepared: &crank_core::PreparedRequest,
context: &crank_core::RuntimeRequestContext,
) -> Result<AdapterResponse, ProtocolAdapterError> {
*self.captured_headers.lock().expect("captured headers lock") =
Some(context.outbound_headers());
Ok(AdapterResponse {
status_code: 200,
headers: BTreeMap::new(),
body: json!({"accepted": true}),
data: json!({"accepted": true}),
})
}
}
fn operation() -> Operation<Schema, MappingSet> {
Operation {
id: OperationId::new("op_stage_test"),