use super::support::{
build_workspace, cli_mcp_config, compute_touchpoints_for, empty_cfg_test, ports_app_cli_mcp,
three_layer,
};
use std::collections::HashSet;
fn assert_set(actual: HashSet<String>, expected: &[&str]) {
let expected_set: HashSet<String> = expected.iter().map(|s| s.to_string()).collect();
assert_eq!(
actual, expected_set,
"touchpoint set mismatch — actual={actual:?} expected={expected_set:?}"
);
}
#[test]
fn touchpoints_direct_call() {
let ws = build_workspace(&[
("src/application/session.rs", "pub fn search() {}"),
(
"src/cli/handlers.rs",
r#"
use crate::application::session::search;
pub fn cmd_search() { search(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_search",
&empty_cfg_test(),
);
assert_set(touchpoints, &["crate::application::session::search"]);
}
#[test]
fn touchpoints_via_adapter_helper() {
let ws = build_workspace(&[
("src/application/session.rs", "pub fn search() {}"),
(
"src/cli/helpers.rs",
r#"
use crate::application::session::search;
pub fn format_query() { search(); }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::cli::helpers::format_query;
pub fn cmd_search() { format_query(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_search",
&empty_cfg_test(),
);
assert_set(touchpoints, &["crate::application::session::search"]);
}
#[test]
fn touchpoints_no_delegation() {
let ws = build_workspace(&[
("src/application/session.rs", "pub fn search() {}"),
(
"src/cli/helpers.rs",
r#"
pub fn adapter_helper2() {}
pub fn adapter_helper() { adapter_helper2(); }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::cli::helpers::adapter_helper;
pub fn cmd_local_only() { adapter_helper(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_local_only",
&empty_cfg_test(),
);
assert!(
touchpoints.is_empty(),
"no-delegation handler should produce empty touchpoint set, got {touchpoints:?}"
);
}
#[test]
fn touchpoints_branch_two_targets() {
let ws = build_workspace(&[
(
"src/application/session.rs",
r#"
pub fn foo() {}
pub fn bar() {}
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::application::session::{foo, bar};
pub fn cmd_branch(cond: bool) {
if cond { foo(); } else { bar(); }
}
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_branch",
&empty_cfg_test(),
);
assert_set(
touchpoints,
&[
"crate::application::session::foo",
"crate::application::session::bar",
],
);
}
#[test]
fn touchpoints_loop_same_target() {
let ws = build_workspace(&[
("src/application/session.rs", "pub fn process(_x: u32) {}"),
(
"src/cli/handlers.rs",
r#"
use crate::application::session::process;
pub fn cmd_batch() {
for x in 0..10 { process(x); }
}
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_batch",
&empty_cfg_test(),
);
assert_set(touchpoints, &["crate::application::session::process"]);
}
#[test]
fn touchpoints_stop_at_target_boundary() {
let ws = build_workspace(&[
(
"src/application/session.rs",
r#"
use crate::application::middleware::record_operation;
pub fn search() { record_operation(); }
"#,
),
(
"src/application/middleware.rs",
r#"
pub fn record_operation() {}
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::application::session::search;
pub fn cmd_search() { search(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_search",
&empty_cfg_test(),
);
assert_set(touchpoints, &["crate::application::session::search"]);
}
#[test]
fn touchpoints_call_depth_exceeded() {
let ws = build_workspace(&[
("src/application/session.rs", "pub fn search() {}"),
(
"src/cli/helpers.rs",
r#"
use crate::application::session::search;
pub fn h4() { search(); }
pub fn h3() { h4(); }
pub fn h2() { h3(); }
pub fn h1() { h2(); }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::cli::helpers::h1;
pub fn cmd_deep() { h1(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_deep",
&empty_cfg_test(),
);
assert!(
touchpoints.is_empty(),
"depth=3 should not reach a 5-hop target, got {touchpoints:?}"
);
}
#[test]
fn touchpoints_call_depth_just_inside() {
let ws = build_workspace(&[
("src/application/session.rs", "pub fn search() {}"),
(
"src/cli/helpers.rs",
r#"
use crate::application::session::search;
pub fn h2() { search(); }
pub fn h1() { h2(); }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::cli::helpers::h1;
pub fn cmd_depth3() { h1(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_depth3",
&empty_cfg_test(),
);
assert_set(touchpoints, &["crate::application::session::search"]);
}
#[test]
fn touchpoints_trait_dispatch_collapses_to_anchor() {
let ws = build_workspace(&[
(
"src/application/handler.rs",
r#"
pub trait Handler { fn handle(&self); }
pub struct LoggingHandler;
impl Handler for LoggingHandler { fn handle(&self) {} }
pub struct MetricsHandler;
impl Handler for MetricsHandler { fn handle(&self) {} }
pub struct AuditHandler;
impl Handler for AuditHandler { fn handle(&self) {} }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::application::handler::Handler;
pub fn cmd_dispatch(h: &dyn Handler) { h.handle(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_dispatch",
&empty_cfg_test(),
);
assert_set(
touchpoints,
&["crate::application::handler::Handler::handle"],
);
}
#[test]
fn touchpoints_trait_anchor_recognized_when_trait_lives_in_ports_layer() {
let ws = build_workspace(&[
(
"src/ports/handler.rs",
"pub trait Handler { fn handle(&self); }",
),
(
"src/application/logging.rs",
r#"
use crate::ports::handler::Handler;
pub struct LoggingHandler;
impl Handler for LoggingHandler { fn handle(&self) {} }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::ports::handler::Handler;
pub fn cmd_dispatch(h: &dyn Handler) { h.handle(); }
"#,
),
]);
let mut config = cli_mcp_config(3);
config.target = "application".to_string();
let touchpoints = compute_touchpoints_for(
&ws,
&ports_app_cli_mcp(),
&config,
"cmd_dispatch",
&empty_cfg_test(),
);
assert_set(touchpoints, &["crate::ports::handler::Handler::handle"]);
}
#[test]
fn touchpoints_skip_anchor_declared_in_peer_adapter_layer() {
let ws = build_workspace(&[
(
"src/mcp/handler.rs",
"pub trait Handler { fn handle(&self); }",
),
(
"src/application/logging.rs",
r#"
use crate::mcp::handler::Handler;
pub struct LoggingHandler;
impl Handler for LoggingHandler { fn handle(&self) {} }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::mcp::handler::Handler;
pub fn cmd_via_dyn_peer(h: &dyn Handler) { h.handle(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_via_dyn_peer",
&empty_cfg_test(),
);
assert_set(touchpoints, &[]);
}
#[test]
fn touchpoints_reject_phantom_inherited_default_concrete_canonical() {
let ws = build_workspace(&[
(
"src/ports/handler.rs",
"pub trait Handler { fn handle(&self) {} }",
),
(
"src/application/logging.rs",
r#"
use crate::ports::handler::Handler;
pub struct AppHandler;
impl Handler for AppHandler {}
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::application::logging::AppHandler;
pub fn cmd_log() { AppHandler::handle(&AppHandler); }
"#,
),
]);
let tps = compute_touchpoints_for(
&ws,
&ports_app_cli_mcp(),
&cli_mcp_config(3),
"cmd_log",
&empty_cfg_test(),
);
let phantom = "crate::application::logging::AppHandler::handle";
assert!(
!tps.contains(phantom),
"phantom inherited-default canonical must NOT be registered as touchpoint (no real fn node); got {tps:?}"
);
}
#[test]
fn touchpoints_route_inherited_default_concrete_to_anchor() {
let ws = build_workspace(&[
(
"src/application/handler.rs",
"pub trait Handler { fn handle(&self) {} }",
),
(
"src/application/logging.rs",
r#"
use crate::application::handler::Handler;
pub struct AppHandler;
impl Handler for AppHandler {}
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::application::logging::AppHandler;
pub fn cmd_log() { AppHandler::handle(&AppHandler); }
"#,
),
]);
let tps = compute_touchpoints_for(
&ws,
&ports_app_cli_mcp(),
&cli_mcp_config(3),
"cmd_log",
&empty_cfg_test(),
);
let anchor = "crate::application::handler::Handler::handle";
assert!(
tps.contains(anchor),
"inherited-default concrete call must route to the trait anchor when the anchor is a target capability; got {tps:?}"
);
}
#[test]
fn touchpoints_skip_rewrite_when_multiple_traits_share_default_method_name() {
let ws = build_workspace(&[
(
"src/application/handler.rs",
r#"
pub trait Greeting { fn handle(&self) {} }
pub trait Logging { fn handle(&self) {} }
"#,
),
(
"src/application/logging.rs",
r#"
use crate::application::handler::{Greeting, Logging};
pub struct AppHandler;
impl Greeting for AppHandler {}
impl Logging for AppHandler {}
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::application::logging::AppHandler;
pub fn cmd_log() { AppHandler::handle(&AppHandler); }
"#,
),
]);
let tps = compute_touchpoints_for(
&ws,
&ports_app_cli_mcp(),
&cli_mcp_config(3),
"cmd_log",
&empty_cfg_test(),
);
let greeting_anchor = "crate::application::handler::Greeting::handle";
let logging_anchor = "crate::application::handler::Logging::handle";
assert!(
!tps.contains(greeting_anchor) && !tps.contains(logging_anchor),
"ambiguous inherited-default rewrite must leave the call unresolved (non-deterministic otherwise); got {tps:?}"
);
}
#[test]
fn touchpoints_recognise_real_target_fn_node() {
let ws = build_workspace(&[
("src/application/stats.rs", "pub fn get_stats() {}"),
(
"src/cli/handlers.rs",
r#"
use crate::application::stats::get_stats;
pub fn cmd_stats() { get_stats(); }
"#,
),
]);
let tps = compute_touchpoints_for(
&ws,
&ports_app_cli_mcp(),
&cli_mcp_config(3),
"cmd_stats",
&empty_cfg_test(),
);
let real = "crate::application::stats::get_stats";
assert!(
tps.contains(real),
"real target-layer pub fn must be a touchpoint; got {tps:?}"
);
let ws = build_workspace(&[
(
"src/mcp/handler.rs",
"pub trait Handler { fn handle(&self); }",
),
(
"src/application/logging.rs",
r#"
use crate::mcp::handler::Handler;
pub struct LoggingHandler;
impl Handler for LoggingHandler { fn handle(&self) {} }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::mcp::handler::Handler;
pub fn cmd_via_dyn_peer(h: &dyn Handler) { h.handle(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_via_dyn_peer",
&empty_cfg_test(),
);
assert_set(touchpoints, &[]);
}
#[test]
fn touchpoints_do_not_traverse_peer_adapter() {
let ws = build_workspace(&[
("src/application/session.rs", "pub fn search() {}"),
(
"src/mcp/handlers.rs",
r#"
use crate::application::session::search;
pub fn mcp_search() { search(); }
"#,
),
(
"src/cli/handlers.rs",
r#"
use crate::mcp::handlers::mcp_search;
pub fn cmd_via_mcp() { mcp_search(); }
"#,
),
]);
let touchpoints = compute_touchpoints_for(
&ws,
&three_layer(),
&cli_mcp_config(3),
"cmd_via_mcp",
&empty_cfg_test(),
);
assert_set(touchpoints, &[]);
}