use crate::view::{DiffItem, NodeId};
use super::Workspace;
use super::id_index::NodeAddress;
impl Workspace {
pub fn rule_set_id_at(&self, rule_set: usize) -> Option<NodeId> {
self.ids.id_for(NodeAddress::RuleSet { rule_set })
}
pub fn rule_id_at(&self, rule_set: usize, rule: usize) -> Option<NodeId> {
self.ids.id_for(NodeAddress::Rule { rule_set, rule })
}
pub fn respond_id_at(&self, rule_set: usize, rule: usize) -> Option<NodeId> {
self.ids.id_for(NodeAddress::Respond { rule_set, rule })
}
pub fn describe(&self, id: NodeId) -> Option<String> {
let rule_set_label = |rule_set: usize| -> String {
self.config
.service
.rule_sets
.get(rule_set)
.map(|rs| rs.file_path.clone())
.unwrap_or_else(|| format!("rule set #{rule_set}"))
};
let addr = self.ids.lookup(id)?;
Some(match addr {
NodeAddress::Root => "root config".to_owned(),
NodeAddress::RuleSet { rule_set } => {
format!("rule set `{}`", rule_set_label(rule_set))
}
NodeAddress::Rule { rule_set, rule } => {
format!("rule set `{}`, rule #{rule}", rule_set_label(rule_set))
}
NodeAddress::Respond { rule_set, rule } => {
format!(
"rule set `{}`, rule #{rule} respond",
rule_set_label(rule_set)
)
}
NodeAddress::Middleware { middleware } => format!("middleware #{middleware}"),
NodeAddress::FallbackRespondDir => "fallback_respond_dir".to_owned(),
NodeAddress::HeaderCondition {
rule_set,
rule,
header_name,
} => format!(
"rule set `{}`, rule #{rule}, header `{header_name}`",
rule_set_label(rule_set)
),
NodeAddress::BodyCondition {
rule_set,
rule,
path,
} => format!(
"rule set `{}`, rule #{rule}, body `{path}`",
rule_set_label(rule_set)
),
})
}
pub fn preview_changes(&self) -> Vec<DiffItem> {
self.compute_diff_summary()
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crate::{
Workspace,
view::{EditCommand, RespondPayload, RulePayload},
};
fn workspace_with_two_rules() -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let rs_toml = concat!(
"[[rules]]\n",
"when.request.url_path = \"/a\"\n",
"respond = { text = \"a\" }\n",
"\n",
"[[rules]]\n",
"when.request.url_path = \"/b\"\n",
"respond = { text = \"b\" }\n",
);
let rs_path = dir.path().join("apimock-rule-set.toml");
std::fs::write(&rs_path, rs_toml).unwrap();
let root_toml =
"[service]\nrule_sets = [\"apimock-rule-set.toml\"]\nfallback_respond_dir = \".\"\n";
let root_path = dir.path().join("apimock.toml");
std::fs::write(&root_path, root_toml).unwrap();
(dir, root_path)
}
#[test]
fn rule_set_id_at_resolves_a_freshly_loaded_rule_set() {
let (_dir, root) = workspace_with_two_rules();
let ws = Workspace::load(root).expect("load");
assert!(ws.rule_set_id_at(0).is_some());
assert!(ws.rule_set_id_at(1).is_none(), "only one rule set exists");
}
#[test]
fn rule_id_at_resolves_a_rule_seeded_at_load_not_just_one_added_this_session() {
let (_dir, root) = workspace_with_two_rules();
let ws = Workspace::load(root).expect("load");
assert!(ws.rule_id_at(0, 0).is_some());
assert!(ws.rule_id_at(0, 1).is_some());
assert!(
ws.rule_id_at(0, 2).is_none(),
"out-of-range rule index must resolve to None, not panic"
);
}
#[test]
fn describe_never_contains_a_uuid_shaped_substring() {
let (_dir, root) = workspace_with_two_rules();
let mut ws = Workspace::load(root).expect("load");
let parent = ws.rule_set_id_at(0).expect("rule set 0 exists");
let result = ws
.apply(EditCommand::AddRule {
parent,
rule: RulePayload {
url_path: Some("/new".to_owned()),
url_path_op: None,
method: None,
priority: None,
headers: None,
body: None,
respond: RespondPayload {
text: Some("ok".to_owned()),
..Default::default()
},
},
})
.expect("apply");
for id in result.changed_nodes {
let label = ws.describe(id).expect("every changed node describes");
assert!(
!looks_like_a_uuid(&label),
"describe() must never render a UUID: {label}"
);
}
}
fn looks_like_a_uuid(s: &str) -> bool {
let groups: Vec<&str> = s.split('-').collect();
groups.len() >= 5
&& groups.windows(5).any(|w| {
w.iter()
.all(|g| !g.is_empty() && g.chars().all(|c| c.is_ascii_hexdigit()))
})
}
#[test]
fn preview_changes_matches_what_save_would_report() {
let (_dir, root) = workspace_with_two_rules();
let mut ws = Workspace::load(root).expect("load");
let parent = ws.rule_set_id_at(0).expect("rule set 0 exists");
ws.apply(EditCommand::AddRule {
parent,
rule: RulePayload {
url_path: Some("/preview".to_owned()),
url_path_op: None,
method: None,
priority: None,
headers: None,
body: None,
respond: RespondPayload {
text: Some("ok".to_owned()),
..Default::default()
},
},
})
.expect("apply");
let preview = ws.preview_changes();
assert!(!preview.is_empty(), "an applied add should show in preview");
let save = ws.save().expect("save");
assert_eq!(
preview.len(),
save.diff_summary.len(),
"preview_changes must match what save() then actually reports"
);
}
}