use super::*;
use crate::{ApplicationId, ModuleId, OperationId, RpcCorrelationId, ServiceId, SpanId, TraceId};
fn call(rpc: &str, span: u64) -> CallContext {
CallContext::new(
ApplicationId::from("app"),
ModuleId::from("module"),
ServiceId::from("svc"),
OperationId::from("operation"),
TraceId::from_u128(1),
SpanId::from_u64(span),
)
.with_rpc_correlation_id(RpcCorrelationId::new(rpc))
}
fn group(request: &str) -> RequestIdentityGroup {
RequestIdentityGroup::from_validated(
&call("0", 1),
request,
"route",
1,
ContextFact::Present(ContextLabel::checked("local").unwrap()),
)
.unwrap()
}
fn owner() -> RequestRootPublisher {
RequestRootPublisher::create(
ContextLabel::checked("app").unwrap(),
ContextFact::NotEstablished,
)
.unwrap()
}
#[test]
fn frozen_u2_publication_is_atomic_and_old_view_stays_early() {
let mut root = owner();
let read = root.reference();
let early = read.view(RequestLocalFacts::new(RequestViewPhase::Reading));
let before = serde_json::to_value(&early).unwrap();
assert_eq!(before["trace_id"]["state"], "not_established");
assert!(root.publish(group("request-a")).is_ok());
assert!(root.publish(group("request-a")).is_ok());
assert_eq!(serde_json::to_value(&early).unwrap(), before);
let current = early.refresh(&read).unwrap();
let bound = serde_json::to_value(¤t).unwrap();
let rejected = root.publish(group("request-b"));
assert!(matches!(rejected, Err((ContextConflict::IdentityGroup, _))));
assert_eq!(serde_json::to_value(¤t).unwrap(), bound);
assert_eq!(bound["request"]["value"], "request-a");
assert!(early.refresh(&owner().reference()).is_err());
}
#[test]
fn frozen_u1_u3_single_root_shared_local_and_child_are_immutable() {
let mut root = owner();
assert!(root.publish(group("request-a")).is_ok());
let read = root.reference();
let first = read.view(
RequestLocalFacts::new(RequestViewPhase::Database)
.with_task(ContextFact::Present(7))
.with_db_operation(RegisteredContextOperation::checked("db.first").unwrap()),
);
let held = first.clone();
assert!(first.same_view(&held));
let second = first.with_db_operation(RegisteredContextOperation::checked("db.second").unwrap());
assert!(first.same_request(&second));
assert!(!first.same_view(&second));
assert_eq!(
serde_json::to_value(&held).unwrap()["db_operation"]["value"],
"db.first"
);
let child = first
.child(&call("0.1", 2), "request-a", "child", 1)
.unwrap();
assert_eq!(serde_json::to_value(&child).unwrap()["task"]["value"], 7);
assert_eq!(serde_json::to_value(&held).unwrap()["rpc_id"]["value"], "0");
assert!(
first
.child(&call("0.1.1", 2), "request-a", "child", 1)
.is_err()
);
assert!(first.child(&call("0.1", 2), "foreign", "child", 1).is_err());
assert!(
first
.child(&call("0.1", 1), "request-a", "child", 1)
.is_err()
);
let id = root.root.local;
drop((root, read, first, held, second, child));
let events = EVENTS.lock().unwrap();
let count = |kind| {
events
.iter()
.filter(|(r, e)| *r == id && *e == kind)
.count()
};
assert_eq!(count("root_create"), 1);
assert_eq!(count("root_drop"), 1);
assert_eq!(count("view_create"), 3);
assert_eq!(count("view_drop"), 3);
let local_events = LOCAL_EVENTS.lock().unwrap();
let created: Vec<_> = local_events
.iter()
.filter(|(_, r, _, e)| *r == id && *e == "create")
.map(|(id, _, kind, _)| (*id, *kind))
.collect();
assert_eq!(created.len(), 4); for (object, kind) in created {
assert_eq!(
local_events
.iter()
.filter(|(o, r, k, e)| *o == object && *r == id && *k == kind && *e == "destroy")
.count(),
1
);
}
let ordered = ORDERED_EVENTS.lock().unwrap();
let root_end = ordered
.iter()
.find(|(_, r, _, _, e)| *r == id && *e == "root_drop")
.unwrap()
.0;
assert!(
ordered
.iter()
.filter(|(_, r, _, k, e)| *r == id && *k == "view" && *e == "destroy")
.all(|(s, _, _, _, _)| *s < root_end)
);
}
#[test]
fn frozen_k3_unread_bytes_do_not_retain_objects() {
let mut root = owner();
assert!(root.publish(group("request-a")).is_ok());
let reference = root.reference();
let view = reference.view(RequestLocalFacts::new(RequestViewPhase::Handler));
let (tx, rx) = std::sync::mpsc::sync_channel(1);
tx.try_send(serde_json::to_vec(&view).unwrap()).unwrap();
let id = root.root.local;
drop((root, reference, view));
assert_eq!(
EVENTS
.lock()
.unwrap()
.iter()
.filter(|(r, e)| *r == id && *e == "root_drop")
.count(),
1
);
let json: serde_json::Value = serde_json::from_slice(&rx.recv().unwrap()).unwrap();
assert_eq!(json["request"]["value"], "request-a");
}
#[test]
fn frozen_u3_scope_pair_foreign_and_next_operation_keep_original() {
let root = owner();
let view = root
.reference()
.view(RequestLocalFacts::new(RequestViewPhase::Database));
let (_, issuer) = crate::DbPhysicalDispositionIssuer::issue().into_startup_and_request_issuer();
let (request, execution) = issuer.issue_request().unwrap();
let checked = request
.project_diagnostic_context(&execution, view.clone())
.ok()
.unwrap();
let scoped = view.in_db_scope(&checked).unwrap();
let before = serde_json::to_value(&scoped).unwrap();
assert_eq!(before["scope"]["state"], "present");
let foreign = owner()
.reference()
.view(RequestLocalFacts::new(RequestViewPhase::Database));
assert!(matches!(
foreign.in_db_scope(&checked),
Err(ContextConflict::ForeignRoot)
));
let second = scoped.with_db_operation(RegisteredContextOperation::checked("next").unwrap());
assert_eq!(
serde_json::to_value(&second).unwrap()["scope"],
before["scope"]
);
assert_eq!(serde_json::to_value(&scoped).unwrap(), before);
}
#[test]
fn frozen_o2_bounded_safe_inputs_and_layout_are_explicit() {
assert!(ContextIdentity::checked(&"a".repeat(256)).is_ok());
assert!(ContextIdentity::checked(&"a".repeat(257)).is_err());
assert!(ContextIdentity::checked("trace\n").is_err());
assert!(ContextLabel::checked("https://user:password@example").is_err());
for (payload, shared) in request_context_layouts() {
assert!(shared.size() >= payload.size() + 2 * size_of::<usize>());
assert!(shared.align() >= payload.align());
}
}