use super::check_contract;
use crate::services::lifecycle::site::{
SiteChange,
test_support::{Fixture, TestResult, spec},
};
fn registration(output: &str) -> liminal::protocol::WorkerRegistration {
liminal::protocol::WorkerRegistration {
namespaces: vec!["echo.request".into()],
task_queue: "echo.request".into(),
node: None,
activity_types: vec!["echo.request".into()],
identity: "builder".into(),
activities: vec![liminal::protocol::WorkerActivityDescriptor {
name: "echo.request".into(),
input_schema_json: "{\"type\":\"object\"}".into(),
output_schema_json: output.into(),
}],
}
}
#[test]
fn output_contract_mismatch_names_the_subject_and_schema() -> TestResult {
let fixture = Fixture::new()?;
fixture.execute("create", SiteChange::CreateSite(spec()))?;
let record = fixture.lifecycle.record("echo")?;
check_contract(&record, ®istration("{\"type\":\"object\"}"))?;
let refused = check_contract(&record, ®istration("{\"type\":\"string\"}"))
.err()
.ok_or("wrong output admitted")?;
assert_eq!(
refused.to_string(),
"subject echo.request: registered output schema differs from site"
);
Ok(())
}
#[test]
fn completion_refuses_every_live_trial_connection_before_any_revocation() -> TestResult {
let fixture = Fixture::new()?;
fixture.execute("create", SiteChange::CreateSite(spec()))?;
fixture.execute(
"bind",
SiteChange::BindBuilder {
agent: "builder".into(),
},
)?;
fixture.lifecycle.lock()?.trials.insert(
"echo".into(),
std::collections::BTreeMap::from([(
19,
super::Registration {
subject: "echo.request".into(),
live: true,
},
)]),
);
let outcome = fixture.execute("complete", SiteChange::Complete)?;
let lys_pass::drafts::Execution::RefusedOnExecution { reason, .. } = outcome else {
return Err("live trial completed".into());
};
assert_eq!(reason, "trial still registered on connection 19");
assert_eq!(
fixture
.issuer
.grants
.lock()
.map_err(|_| "fixture grants poisoned")?
.len(),
2
);
fixture.lifecycle.trial_closed(19)?;
assert!(fixture.lifecycle.trial_connection("echo")?.is_none());
Ok(())
}
#[derive(Debug)]
struct Agent(&'static str);
impl crate::auth_lys::session::LysSession for Agent {
fn holder(&self) -> &str {
self.0
}
fn holder_kind(&self) -> Option<&str> {
Some("agent")
}
fn decide(
&self,
_subject: &crate::auth_lys::SubjectBinding,
_action: crate::auth_lys::HotAction,
_now: u64,
) -> Result<crate::auth_lys::GrantReceipt, crate::auth_lys::AccessDecisionError> {
Ok(crate::auth_lys::GrantReceipt {
grant: "grant-network".to_owned(),
})
}
fn decide_link(
&self,
subject: &crate::auth_lys::SubjectBinding,
_action: crate::link::LinkAction,
now: u64,
) -> Result<crate::auth_lys::GrantReceipt, crate::auth_lys::AccessDecisionError> {
self.decide(subject, crate::auth_lys::HotAction::Serve, now)
}
fn open_link(
&self,
_link: &crate::auth_lys::AccessResource,
_end: crate::link::LinkAction,
_now: u64,
) -> Result<crate::auth_lys::DeliberateDecision, crate::auth_lys::AccessDecisionError> {
Ok(crate::auth_lys::DeliberateDecision::Outright(
crate::auth_lys::GrantReceipt {
grant: "grant-network".to_owned(),
},
))
}
fn decide_live(
&self,
link: &crate::auth_lys::AccessResource,
_action: crate::auth_lys::AccessAction,
now: u64,
) -> Result<crate::auth_lys::DeliberateDecision, crate::auth_lys::AccessDecisionError> {
self.open_link(link, crate::link::LinkAction::Export, now)
}
}
#[test]
fn a_bound_builder_serves_only_its_sites_subjects_whatever_its_grant_reaches() -> TestResult {
let fixture = Fixture::new()?;
fixture.execute("create", SiteChange::CreateSite(spec()))?;
fixture.execute(
"bind",
SiteChange::BindBuilder {
agent: "builder".into(),
},
)?;
let mut elsewhere = registration("{\"type\":\"object\"}");
elsewhere.task_queue = "billing.charge".into();
elsewhere.namespaces = vec!["billing.charge".into()];
let refused = fixture
.lifecycle
.prepare_trial(7, Some(&Agent("builder")), &elsewhere, None, 30_000)
.err()
.ok_or("a bound builder served a subject outside its site")?;
assert_eq!(
refused.to_string(),
"builder of echo serves only its site's subjects"
);
assert!(
fixture
.lifecycle
.prepare_trial(8, Some(&Agent("someone")), &elsewhere, None, 30_000)?
.is_none()
);
Ok(())
}