forked from 1b-agent/opencadstudio-upstream
fix(commands): deny list during BEDIT
Refuse LAYOUT, VPORTS, MVIEW, WBLOCK, PURGE, RENAME, REFEDIT, REFEDIT_BEGIN and PLOT from the command line while a block editor session is open, matching the reference product's "<CMD> command not allowed in block editor" behaviour (spec section 8, based on the V7 hands-on check). XREF, NEW, OPEN and SAVEAS stay allowed. The service-level RENAME guard stays as defense in depth.
This commit is contained in:
1 parent
43cca2094b
commit
4143a01ff4
3 files changed
+139
-3
No files matched your search
@@ -1318,6 +1318,115 @@ mod bedit_characterization_tests {
|
|||||||
assert!(app.tabs[tab].block_edits.is_empty());
|
assert!(app.tabs[tab].block_edits.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// V7 (reference check): while a block editor session is open this
|
||||||
|
/// command set is refused outright with "not allowed in block editor";
|
||||||
|
/// the refusal pushes no undo entry and starts no command.
|
||||||
|
#[test]
|
||||||
|
fn locked_deny_list_refuses_while_session_open() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
let (insert, _) = setup_block_and_insert(&mut app);
|
||||||
|
let tab = app.active_tab;
|
||||||
|
let _ = app.dispatch_command(&format!("BEDIT_BEGIN:{}", insert.value()));
|
||||||
|
assert!(
|
||||||
|
app.tabs[tab].active_block_edit.is_some(),
|
||||||
|
"setup: BEDIT_BEGIN should open a session"
|
||||||
|
);
|
||||||
|
let undo_depth = app.tabs[tab].history.undo_stack.len();
|
||||||
|
for verb in [
|
||||||
|
"LAYOUT",
|
||||||
|
"VPORTS",
|
||||||
|
"MVIEW",
|
||||||
|
"WBLOCK",
|
||||||
|
"PURGE",
|
||||||
|
"RENAME",
|
||||||
|
"REFEDIT",
|
||||||
|
"REFEDIT_BEGIN",
|
||||||
|
"PLOT",
|
||||||
|
] {
|
||||||
|
let revision = app.command_line.error_revision;
|
||||||
|
let _ = app.dispatch_command(verb);
|
||||||
|
assert_eq!(
|
||||||
|
app.command_line.error_revision,
|
||||||
|
revision.wrapping_add(1),
|
||||||
|
"{verb} must refuse with a command-line error"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
app.command_line
|
||||||
|
.last_error
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.contains("command not allowed in block editor"),
|
||||||
|
"{verb}: got {:?}",
|
||||||
|
app.command_line.last_error
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
app.tabs[tab].active_block_edit.is_some(),
|
||||||
|
"{verb} must keep the session open"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
app.tabs[tab].active_cmd.is_none(),
|
||||||
|
"{verb} must not install a command"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.tabs[tab].history.undo_stack.len(),
|
||||||
|
undo_depth,
|
||||||
|
"{verb} must push no undo entry"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spec 8: a rejected command must not cancel a running command — the
|
||||||
|
/// deny hook sits before the running-command teardown.
|
||||||
|
#[test]
|
||||||
|
fn locked_deny_list_does_not_cancel_a_running_command() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
let (insert, _) = setup_block_and_insert(&mut app);
|
||||||
|
let tab = app.active_tab;
|
||||||
|
let _ = app.dispatch_command(&format!("BEDIT_BEGIN:{}", insert.value()));
|
||||||
|
assert!(app.tabs[tab].active_block_edit.is_some());
|
||||||
|
let _ = app.dispatch_command("LINE");
|
||||||
|
assert_eq!(
|
||||||
|
app.tabs[tab].active_cmd.as_deref().map(|cmd| cmd.name()),
|
||||||
|
Some("LINE"),
|
||||||
|
"setup: LINE should be running inside the session"
|
||||||
|
);
|
||||||
|
let _ = app.dispatch_command("LAYOUT");
|
||||||
|
assert!(app
|
||||||
|
.command_line
|
||||||
|
.last_error
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.contains("command not allowed in block editor"));
|
||||||
|
assert_eq!(
|
||||||
|
app.tabs[tab].active_cmd.as_deref().map(|cmd| cmd.name()),
|
||||||
|
Some("LINE"),
|
||||||
|
"the refused command must leave the running LINE untouched"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// V7: the reference keeps XREF, NEW, OPEN, SAVEAS and BEDIT available
|
||||||
|
/// inside the editor — the deny list must not touch them.
|
||||||
|
#[test]
|
||||||
|
fn locked_deny_list_leaves_v7_allowed_commands_alone() {
|
||||||
|
for verb in ["XREF", "NEW", "OPEN", "SAVEAS", "BEDIT"] {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
let (insert, _) = setup_block_and_insert(&mut app);
|
||||||
|
let tab = app.active_tab;
|
||||||
|
let _ = app.dispatch_command(&format!("BEDIT_BEGIN:{}", insert.value()));
|
||||||
|
assert!(app.tabs[tab].active_block_edit.is_some());
|
||||||
|
let _ = app.dispatch_command(verb);
|
||||||
|
assert!(
|
||||||
|
!app.command_line
|
||||||
|
.last_error
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.contains("not allowed in block editor"),
|
||||||
|
"{verb} is available in the reference session and must not be denied: {:?}",
|
||||||
|
app.command_line.last_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- documenting: today's behavior, expected to flip in Phase 1 ----
|
// ---- documenting: today's behavior, expected to flip in Phase 1 ----
|
||||||
|
|
||||||
/// Discard removes local before-images before drawing undo resumes.
|
/// Discard removes local before-images before drawing undo resumes.
|
||||||
|
|||||||
@@ -209,6 +209,30 @@ impl OpenCADStudio {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// V7 (reference check): inside a block editor session a set of
|
||||||
|
// commands is refused outright. The hook sits after alias resolution
|
||||||
|
// and before the transparent split and the running-command teardown,
|
||||||
|
// so a rejected command leaves whatever is running untouched.
|
||||||
|
if self.tabs[i].active_block_edit.is_some() {
|
||||||
|
let name = cmd.split([' ', ':']).next().unwrap_or(cmd);
|
||||||
|
if matches!(
|
||||||
|
name,
|
||||||
|
"LAYOUT"
|
||||||
|
| "VPORTS"
|
||||||
|
| "MVIEW"
|
||||||
|
| "WBLOCK"
|
||||||
|
| "PURGE"
|
||||||
|
| "RENAME"
|
||||||
|
| "REFEDIT"
|
||||||
|
| "REFEDIT_BEGIN"
|
||||||
|
| "PLOT"
|
||||||
|
) {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::tf!("{} command not allowed in block editor", name).as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
}
|
||||||
if is_spacemouse_command(cmd) {
|
if is_spacemouse_command(cmd) {
|
||||||
return self.run_action(cmd);
|
return self.run_action(cmd);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -626,6 +626,9 @@ fn locked_rename_refuses_anonymous_and_layout_records_with_the_shared_text() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A rename refuses a block with an open BEDIT session, whatever the new name.
|
/// A rename refuses a block with an open BEDIT session, whatever the new name.
|
||||||
|
/// V7: the RENAME verb itself is refused at the command choke point while a
|
||||||
|
/// session is open; the block service keeps its own refusal as defense in
|
||||||
|
/// depth for the non-command paths (control op, palette).
|
||||||
#[test]
|
#[test]
|
||||||
fn locked_rename_refuses_a_block_with_an_open_bedit_session() {
|
fn locked_rename_refuses_a_block_with_an_open_bedit_session() {
|
||||||
let mut app = app();
|
let mut app = app();
|
||||||
@@ -639,9 +642,9 @@ fn locked_rename_refuses_a_block_with_an_open_bedit_session() {
|
|||||||
assert_eq!(undo_entry_count(&app), before);
|
assert_eq!(undo_entry_count(&app), before);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors(&app),
|
errors(&app),
|
||||||
vec![command_line_error(&format!(
|
vec![command_line_error(
|
||||||
"block \"{name}\" is open in the block editor; close the editor first"
|
"RENAME command not allowed in block editor"
|
||||||
))]
|
)]
|
||||||
);
|
);
|
||||||
let _ = app.dispatch_command("BEDIT_DISCARD");
|
let _ = app.dispatch_command("BEDIT_DISCARD");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user