@ -613,3 +613,256 @@ fn update_deps_replaces_list() {
assert_eq! ( deps . len ( ) , 1 , "should have exactly one dependency" ) ;
assert_eq! ( deps [ 0 ] , dep2 , "dependency should be dep2" ) ;
}
// ── --filter tests ────────────────────────────────────────────────────────────
/// `nbd list --filter type=bug` shows only bug tickets.
#[ test ]
fn list_filter_by_type ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "Bug ticket" , "--type" , "bug" ] ) ;
env . create ( & [ "--title" , "Task ticket" , "--type" , "task" ] ) ;
let output = env . run ( & [ "list" , "--filter" , "type=bug" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let arr = parsed . as_array ( ) . unwrap ( ) ;
assert_eq! ( arr . len ( ) , 1 , "only the bug ticket should appear" ) ;
assert_eq! ( arr [ 0 ] [ "ticket_type" ] , "bug" ) ;
}
/// `nbd list --filter status=in_progress` shows only in_progress tickets.
#[ test ]
fn list_filter_by_status ( ) {
let env = TestEnv ::new ( ) ;
let id = env . create ( & [ "--title" , "Active ticket" ] ) ;
env . create ( & [ "--title" , "Todo ticket" ] ) ;
env . run ( & [ "update" , & id , "--status" , "in_progress" ] ) ;
let output = env . run ( & [ "list" , "--filter" , "status=in_progress" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let arr = parsed . as_array ( ) . unwrap ( ) ;
assert_eq! ( arr . len ( ) , 1 , "only in_progress ticket should appear" ) ;
assert_eq! ( arr [ 0 ] [ "status" ] , "in_progress" ) ;
}
/// Same key repeated is ORed: `--filter status=todo --filter status=in_progress`
/// shows both todo and in_progress tickets.
#[ test ]
fn list_filter_same_key_ored ( ) {
let env = TestEnv ::new ( ) ;
let id = env . create ( & [ "--title" , "Active" ] ) ;
env . create ( & [ "--title" , "Todo" ] ) ;
let done_id = env . create ( & [ "--title" , "Done" ] ) ;
env . run ( & [ "update" , & id , "--status" , "in_progress" ] ) ;
env . run ( & [ "update" , & done_id , "--status" , "done" ] ) ;
let output = env . run ( & [
"list" ,
"--filter" ,
"status=todo" ,
"--filter" ,
"status=in_progress" ,
"--json" ,
] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let arr = parsed . as_array ( ) . unwrap ( ) ;
assert_eq! (
arr . len ( ) ,
2 ,
"todo and in_progress should appear; done excluded"
) ;
let statuses : Vec < & str > = arr . iter ( ) . map ( | v | v [ "status" ] . as_str ( ) . unwrap ( ) ) . collect ( ) ;
assert! ( statuses . contains ( & "todo" ) , "todo should be in results" ) ;
assert! (
statuses . contains ( & "in_progress" ) ,
"in_progress should be in results"
) ;
}
/// Different keys are ANDed: `--filter type=bug --filter status=todo` shows
/// only bug tickets with status todo.
#[ test ]
fn list_filter_different_keys_anded ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "Bug todo" , "--type" , "bug" ] ) ;
let bug_active = env . create ( & [ "--title" , "Bug active" , "--type" , "bug" ] ) ;
env . create ( & [ "--title" , "Task todo" , "--type" , "task" ] ) ;
env . run ( & [ "update" , & bug_active , "--status" , "in_progress" ] ) ;
let output = env . run ( & [
"list" ,
"--filter" ,
"type=bug" ,
"--filter" ,
"status=todo" ,
"--json" ,
] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let arr = parsed . as_array ( ) . unwrap ( ) ;
assert_eq! ( arr . len ( ) , 1 , "only bug+todo should appear" ) ;
assert_eq! ( arr [ 0 ] [ "title" ] , "Bug todo" ) ;
}
/// `nbd list --filter title=*login*` matches by glob on the title field.
#[ test ]
fn list_filter_title_glob ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "Fix login button" ] ) ;
env . create ( & [ "--title" , "Add rate limiting" ] ) ;
let output = env . run ( & [ "list" , "--filter" , "title=*login*" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let arr = parsed . as_array ( ) . unwrap ( ) ;
assert_eq! ( arr . len ( ) , 1 , "only the login ticket should match" ) ;
assert_eq! ( arr [ 0 ] [ "title" ] , "Fix login button" ) ;
}
/// `nbd list --filter status=*` wildcard matches all statuses.
#[ test ]
fn list_filter_status_wildcard ( ) {
let env = TestEnv ::new ( ) ;
let id = env . create ( & [ "--title" , "A" ] ) ;
env . create ( & [ "--title" , "B" ] ) ;
env . run ( & [ "update" , & id , "--status" , "done" ] ) ;
let output = env . run ( & [ "list" , "--filter" , "status=*" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let arr = parsed . as_array ( ) . unwrap ( ) ;
assert_eq! ( arr . len ( ) , 2 , "wildcard should match all statuses" ) ;
}
/// `nbd list --filter badformat` (no `=`) exits non-zero.
#[ test ]
fn list_filter_bad_format_exits_nonzero ( ) {
let env = TestEnv ::new ( ) ;
let output = env . run ( & [ "list" , "--filter" , "badformat" ] ) ;
assert! (
! output . status . success ( ) ,
"missing '=' in filter should exit non-zero"
) ;
}
/// `nbd list --filter unknown=foo` (unknown key) exits non-zero.
#[ test ]
fn list_filter_unknown_key_exits_nonzero ( ) {
let env = TestEnv ::new ( ) ;
let output = env . run ( & [ "list" , "--filter" , "colour=red" ] ) ;
assert! (
! output . status . success ( ) ,
"unknown filter key should exit non-zero"
) ;
let stderr = String ::from_utf8 ( output . stderr ) . unwrap ( ) ;
assert! (
stderr . contains ( "colour" ) ,
"error should mention the unknown key, got: {stderr}"
) ;
}
/// `nbd ready --filter type=bug` returns only ready bug tickets.
#[ test ]
fn ready_filter_by_type ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "Ready bug" , "--type" , "bug" ] ) ;
env . create ( & [ "--title" , "Ready task" , "--type" , "task" ] ) ;
let output = env . run ( & [ "ready" , "--filter" , "type=bug" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let arr = parsed . as_array ( ) . unwrap ( ) ;
assert_eq! ( arr . len ( ) , 1 , "only the bug ticket should appear in ready" ) ;
assert_eq! ( arr [ 0 ] [ "ticket_type" ] , "bug" ) ;
}
/// `nbd ready --filter priority=8` returns only ready tickets with priority 8.
#[ test ]
fn ready_filter_by_priority ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "High prio" , "--priority" , "8" ] ) ;
env . create ( & [ "--title" , "Normal prio" , "--priority" , "5" ] ) ;
let output = env . run ( & [ "ready" , "--filter" , "priority=8" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
let arr = parsed . as_array ( ) . unwrap ( ) ;
assert_eq! ( arr . len ( ) , 1 , "only priority-8 ticket should appear" ) ;
assert_eq! ( arr [ 0 ] [ "priority" ] , 8 ) ;
}
/// `nbd migrate --filter status=todo --dry-run --json` reports skipped count
/// for tickets that do not match the filter.
#[ test ]
fn migrate_filter_skipped_in_json ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "Todo ticket" ] ) ;
let id2 = env . create ( & [ "--title" , "Active ticket" ] ) ;
env . run ( & [ "update" , & id2 , "--status" , "in_progress" ] ) ;
let output = env . run ( & [ "migrate" , "--filter" , "status=todo" , "--dry-run" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value =
serde_json ::from_str ( & stdout ) . expect ( "migrate --json should produce valid JSON" ) ;
assert! (
parsed . get ( "skipped" ) . is_some ( ) ,
"JSON should have 'skipped' key"
) ;
assert_eq! (
parsed [ "skipped" ] , 1 ,
"one ticket (in_progress) should be skipped"
) ;
}
/// `nbd migrate --json` always includes a `skipped` key (even when zero).
#[ test ]
fn migrate_json_always_has_skipped_key ( ) {
let env = TestEnv ::new ( ) ;
env . create ( & [ "--title" , "Some ticket" ] ) ;
let output = env . run ( & [ "migrate" , "--json" ] ) ;
assert! ( output . status . success ( ) ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let parsed : serde_json ::Value = serde_json ::from_str ( & stdout ) . unwrap ( ) ;
assert! (
parsed . get ( "skipped" ) . is_some ( ) ,
"JSON should always contain 'skipped' key"
) ;
assert_eq! ( parsed [ "skipped" ] , 0 ) ;
}
/// `nbd ready --filter badformat` exits non-zero.
#[ test ]
fn ready_filter_bad_format_exits_nonzero ( ) {
let env = TestEnv ::new ( ) ;
let output = env . run ( & [ "ready" , "--filter" , "noequals" ] ) ;
assert! (
! output . status . success ( ) ,
"bad filter format on ready should exit non-zero"
) ;
}