use crate::boundary::codec::{decode, encode};
use crate::boundary::config::ConfigFile;
use crate::boundary::config::Read;
use crate::boundary::{Action, Gesture, Query};
use crate::config_edit::branch::edit::EditOrigin;
use crate::model_pick::{Effort, Tuning};
use serde_json::json;
fn rt(gesture: Gesture) {
let encoded = encode(&gesture);
assert_eq!(decode(&encoded), Ok(gesture.clone()), "via {encoded}");
}
fn applying(file: ConfigFile) -> Gesture {
Gesture::Act(Action::ApplyConfig {
file,
text: "models:\n gpt-5.4:\n provider: codex\n".to_owned(),
})
}
fn brazen() -> ConfigFile {
ConfigFile::Brazen {
workspace: "ws".to_owned(),
}
}
fn providers() -> Query {
Query::Config(Read::Providers {
workspace: "ws".to_owned(),
})
}
fn branch(origin: EditOrigin) -> ConfigFile {
ConfigFile::Branch {
workspace: "ws".to_owned(),
lineage: "default".to_owned(),
origin,
path: "providers.yaml".to_owned(),
}
}
pub(crate) fn surface() -> Vec<Gesture> {
let mut out = Vec::new();
for level in [
Some(Effort::Low),
Some(Effort::Medium),
Some(Effort::High),
None,
] {
out.push(Gesture::Act(Action::Tune(Tuning::Effort {
workspace: "ws".to_owned(),
role: "worker".to_owned(),
level,
})));
}
for on in [true, false] {
out.push(Gesture::Act(Action::Tune(Tuning::Priority {
workspace: "ws".to_owned(),
role: "compactor".to_owned(),
on,
})));
}
for file in [
brazen(),
ConfigFile::LitanyModels,
ConfigFile::Cadence,
ConfigFile::LitanyWorkflow {
name: "review".to_owned(),
},
branch(EditOrigin::Advance),
branch(EditOrigin::Fork {
source: "base".to_owned(),
}),
branch(EditOrigin::Orphan),
] {
out.push(applying(file));
}
for branch in ["balls/tasks", "balls/agents/corp"] {
out.push(Gesture::Act(Action::SetMarks {
workspace: "ws".to_owned(),
branch: branch.to_owned(),
}));
}
out.push(Gesture::Act(Action::PickModel {
workspace: "ws".to_owned(),
role: "worker".to_owned(),
provider: "codex".to_owned(),
model: "gpt-5.4".to_owned(),
}));
for file in [
brazen(),
ConfigFile::LitanyModels,
ConfigFile::Cadence,
ConfigFile::LitanyWorkflow {
name: "review".to_owned(),
},
] {
out.push(Gesture::Ask(Query::Config(Read::File { file })));
}
out.push(Gesture::Ask(Query::Config(Read::Marks {
workspace: "ws".to_owned(),
})));
out.push(Gesture::Ask(providers()));
out.push(Gesture::Ask(Query::Config(Read::Roles {
workspace: "ws".to_owned(),
})));
out
}
#[test]
fn every_config_gesture_round_trips() {
for gesture in surface() {
rt(gesture);
}
}
#[test]
fn the_config_envelope_names_its_target_and_carries_the_text_whole() {
let text = "[providers.codex]\nauth = \"none\"\n";
let encoded = encode(&Gesture::Act(Action::ApplyConfig {
file: brazen(),
text: text.to_owned(),
}));
assert_eq!(encoded["op"], "config");
assert_eq!(encoded["target"]["file"], "brazen");
assert_eq!(encoded["text"], text);
}
#[test]
fn a_wall_scoped_envelope_without_its_workspace_is_refused() {
for value in [
json!({ "op": "providers" }),
json!({ "op": "config", "target": { "file": "brazen" } }),
json!({ "op": "config", "target": { "file": "brazen" }, "text": "x" }),
] {
let err = decode(&value).unwrap_err();
assert!(err.contains("workspace"), "{value}: {err}");
}
}
#[test]
fn a_field_left_out_reads_instead_of_writing() {
let read = encode(&Gesture::Ask(Query::Config(Read::File { file: brazen() })));
assert_eq!(read["op"], "config");
assert!(read.get("text").is_none(), "{read}");
let marks = encode(&Gesture::Ask(Query::Config(Read::Marks {
workspace: "ws".to_owned(),
})));
assert_eq!(marks["op"], "marks");
assert!(marks.get("branch").is_none(), "{marks}");
}
#[test]
fn a_marks_envelope_names_the_workspace_and_the_branch() {
let encoded = encode(&Gesture::Act(Action::SetMarks {
workspace: "ws".to_owned(),
branch: "balls/agents/corp".to_owned(),
}));
assert_eq!(encoded["op"], "marks");
assert_eq!(encoded["workspace"], "ws");
assert_eq!(encoded["branch"], "balls/agents/corp");
}
#[test]
fn malformed_config_envelopes_refuse_with_a_reason() {
let cases = [
(json!({"op": "config", "text": "x"}), "missing target"),
(
json!({"op": "config", "target": "brazen", "text": "x"}),
"not an object",
),
(
json!({"op": "config", "target": {"file": "enhance"}, "text": "x"}),
"unknown target file",
),
(
json!({"op": "config", "target": {"file": "litany-workflow"}, "text": "x"}),
"\"name\"",
),
(
json!({"op": "config", "target": {"file": "branch", "workspace": "/ws",
"lineage": "d", "path": "p", "origin": "rebase"}, "text": "x"}),
"unknown origin",
),
(
json!({"op": "config", "target": {"file": "branch", "workspace": "/ws",
"lineage": "d", "path": "p", "origin": "fork"}, "text": "x"}),
"\"source\"",
),
(
json!({"op": "marks", "workspace": "/ws", "branch": "two words"}),
"one word",
),
(
json!({"op": "marks", "workspace": "/ws", "branch": "balls/config"}),
"landing branch",
),
(
json!({"op": "model", "workspace": "/ws", "role": "worker", "provider": "codex"}),
"\"model\"",
),
];
for (envelope, needle) in cases {
let err = decode(&envelope).unwrap_err();
assert!(err.contains(needle), "{envelope} refused with {err:?}");
}
}