Files
yoi/crates/worker/tests/service_contract_architecture_test.rs
Hare 6826db7e0c refactor: own Worker and Ticket service contracts outside providers
Move shared control/lifecycle/observation/Ticket contracts into feature::contracts. Consumers and host bindings import the neutral owner; providers retain current authorization, explicit combined routing and persisted composition.

Document stable semantic IDs and exact compatibility epochs; connect consumer requirements to the existing planner without a new resolver. Exercise consumer installation/dispatch with contract-only test providers, atomic rejection paths, and parsed-source architecture guards including negative fixtures.

Validation: worker and worker-runtime normal tests and doctests passed; root cargo check, rustdoc with warnings denied, fmt, diff and state-contract names passed. Dependent Server authorization tests require separate stale native-v1 fixture updates captured next.
2026-10-28 16:51:46 +09:00

109 lines
3.8 KiB
Rust

//! Source dependency regression for the neutral contracts and representative consumers. Parse Rust
//! syntax so grouped/renamed imports and fully qualified type/expression paths are checked alike.
use syn::visit::Visit;
#[derive(Default)]
struct ProviderDependencies(Vec<String>);
impl ProviderDependencies {
fn check(&mut self, path: &[String]) {
// These modules are provider/composition namespaces. `super::*` inside a consumer's own
// unit tests is local access, but importing/aliasing the parent namespace is not.
if path.windows(2).any(|pair| pair == ["feature", "builtin"])
|| path.first().is_some_and(|segment| segment == "builtin")
|| (path.first().is_some_and(|segment| segment == "super")
&& path.get(1).is_none_or(|segment| segment != "*"))
{
self.0.push(path.join("::"));
}
}
fn use_tree(&mut self, prefix: &mut Vec<String>, tree: &syn::UseTree) {
match tree {
syn::UseTree::Path(path) => {
prefix.push(path.ident.to_string());
self.use_tree(prefix, &path.tree);
prefix.pop();
}
syn::UseTree::Group(group) => {
for item in &group.items {
self.use_tree(prefix, item);
}
}
syn::UseTree::Name(name) => {
prefix.push(name.ident.to_string());
self.check(prefix);
prefix.pop();
}
syn::UseTree::Rename(rename) => {
prefix.push(rename.ident.to_string());
self.check(prefix);
prefix.pop();
}
syn::UseTree::Glob(_) => {
prefix.push("*".into());
self.check(prefix);
prefix.pop();
}
}
}
}
impl<'ast> Visit<'ast> for ProviderDependencies {
fn visit_item_use(&mut self, item: &'ast syn::ItemUse) {
self.use_tree(&mut Vec::new(), &item.tree);
}
fn visit_path(&mut self, path: &'ast syn::Path) {
self.check(
&path
.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect::<Vec<_>>(),
);
syn::visit::visit_path(self, path);
}
}
fn dependencies(source: &str) -> Vec<String> {
let file = syn::parse_file(source).expect("valid Rust source");
let mut visitor = ProviderDependencies::default();
visitor.visit_file(&file);
visitor.0
}
#[test]
fn contracts_and_consumers_do_not_depend_on_provider_namespaces() {
for (name, source) in [
("contracts", include_str!("../src/feature/contracts.rs")),
(
"orchestration",
include_str!("../src/feature/builtin/orchestration.rs"),
),
(
"worker_observation",
include_str!("../src/feature/builtin/worker_observation.rs"),
),
] {
assert_eq!(dependencies(source), Vec::<String>::new(), "{name}");
}
}
#[test]
fn architecture_guard_detects_imports_reexports_aliases_and_qualified_provider_paths() {
for source in [
"use super::manage_worker::WorkerControlService;",
"pub use super::ticket::TicketService;",
"use crate::feature::{builtin::{manage_worker as provider, ticket::*}};",
"use crate::feature::builtin as providers;",
"use super as providers;",
"type Contract = dyn crate::feature::builtin::manage_worker::WorkerControlService;",
"fn consumer() { crate::feature::builtin::manage_worker::manage_worker_feature(); }",
] {
assert!(!dependencies(source).is_empty(), "guard missed {source}");
}
assert!(dependencies("use ticket::TicketError; use crate::feature::contracts::TicketService; mod tests { use super::*; }").is_empty());
}