Wire breadcrumbs into the bread event fabric (app id crumbs)
All checks were successful
check / check (push) Successful in 36s
All checks were successful
check / check (push) Successful in 36s
The watch daemon publishes bread.crumbs.profile.changed and bread.crumbs.health.changed on real transitions (not every poll tick) and honors bread.command.crumbs.set_profile via the existing state::set_profile path. BreadClient is fail-silent: if breadd is down, breadcrumbs behaves exactly as before. Document the contract in EVENTS.md. CLAUDE.md now points at CONTRIBUTING (single-trunk, no three-branch model), bakery, and EVENTS.md.
This commit is contained in:
parent
0f48b1499d
commit
9009404536
11 changed files with 588 additions and 19 deletions
|
|
@ -10,8 +10,11 @@ mod common;
|
|||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use bread_utils::bread_client::BreadEvent;
|
||||
use breadcrumbs::bread_events;
|
||||
use breadcrumbs::config::{Config, NetworkDef, Profile, Settings};
|
||||
use breadcrumbs::flow;
|
||||
use breadcrumbs::state::{self, State};
|
||||
use breadcrumbs::util::with_runner;
|
||||
use breadcrumbs::watch::{classify, Health};
|
||||
|
||||
|
|
@ -470,3 +473,103 @@ fn classify_reports_up_when_tailscale_healthy() {
|
|||
|
||||
assert_eq!(health, Health::Up);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// bread.command.crumbs.set_profile — persists via the same path as the
|
||||
// CLI, and must not depend on breadd being reachable (`emit` is
|
||||
// fire-and-forget).
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
fn command_event(event: &str, data: serde_json::Value) -> BreadEvent {
|
||||
BreadEvent {
|
||||
event: event.to_string(),
|
||||
timestamp: 0,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_profile_command_persists_even_with_no_daemon_reachable() {
|
||||
let _env = EnvSandbox::new();
|
||||
let cfg = Config::load().expect("fresh config");
|
||||
state::set_profile(&cfg, "away").unwrap();
|
||||
assert_eq!(State::load("away").profile, "away");
|
||||
|
||||
let acted = bread_events::handle_command(&command_event(
|
||||
"bread.command.crumbs.set_profile",
|
||||
serde_json::json!({ "profile": "home" }),
|
||||
));
|
||||
|
||||
assert!(acted, "known profile must persist");
|
||||
assert_eq!(State::load("away").profile, "home");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_profile_command_rejects_unknown_profile() {
|
||||
let _env = EnvSandbox::new();
|
||||
let cfg = Config::load().expect("fresh config");
|
||||
state::set_profile(&cfg, "away").unwrap();
|
||||
|
||||
let acted = bread_events::handle_command(&command_event(
|
||||
"bread.command.crumbs.set_profile",
|
||||
serde_json::json!({ "profile": "bogus" }),
|
||||
));
|
||||
|
||||
assert!(!acted);
|
||||
assert_eq!(
|
||||
State::load("away").profile,
|
||||
"away",
|
||||
"a rejected set_profile must not touch state"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_profile_command_rejects_missing_profile_field() {
|
||||
let _env = EnvSandbox::new();
|
||||
let cfg = Config::load().expect("fresh config");
|
||||
state::set_profile(&cfg, "away").unwrap();
|
||||
|
||||
let acted = bread_events::handle_command(&command_event(
|
||||
"bread.command.crumbs.set_profile",
|
||||
serde_json::json!({}),
|
||||
));
|
||||
|
||||
assert!(!acted);
|
||||
assert_eq!(State::load("away").profile, "away");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_command_ignores_unrecognized_verb() {
|
||||
let _env = EnvSandbox::new();
|
||||
let cfg = Config::load().expect("fresh config");
|
||||
state::set_profile(&cfg, "away").unwrap();
|
||||
|
||||
let acted = bread_events::handle_command(&command_event(
|
||||
"bread.command.crumbs.pin",
|
||||
serde_json::json!({}),
|
||||
));
|
||||
|
||||
assert!(!acted);
|
||||
assert_eq!(
|
||||
State::load("away").profile,
|
||||
"away",
|
||||
"an unrecognized verb must not touch state"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_command_ignores_events_outside_its_own_command_namespace() {
|
||||
let _env = EnvSandbox::new();
|
||||
let cfg = Config::load().expect("fresh config");
|
||||
state::set_profile(&cfg, "away").unwrap();
|
||||
|
||||
assert!(!bread_events::handle_command(&command_event(
|
||||
"bread.command.clip.clear",
|
||||
serde_json::json!({}),
|
||||
)));
|
||||
assert!(!bread_events::handle_command(&command_event(
|
||||
"bread.crumbs.profile.changed",
|
||||
serde_json::json!({ "from": "away", "to": "home" }),
|
||||
)));
|
||||
assert_eq!(State::load("away").profile, "away");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue