121 lines
4.4 KiB
Rust
121 lines
4.4 KiB
Rust
//! Favorites, notes and personal tags (issue #39).
|
|
|
|
mod common;
|
|
|
|
use agent_manager::commands::annotations_cmd;
|
|
use agent_manager::commands::search_cmd;
|
|
use agent_manager::events::{self, EventKind};
|
|
use agent_manager::state::{Annotation, InstalledEntry};
|
|
|
|
fn install(app: &agent_manager::app::App, name: &str) {
|
|
let entry = InstalledEntry {
|
|
name: name.to_string(),
|
|
version: Some("1.0.0".to_string()),
|
|
method: "npm".to_string(),
|
|
install_dir: format!("/tmp/{name}"),
|
|
bins: vec![],
|
|
run: name.to_string(),
|
|
installed_at: "2026-08-01T00:00:00Z".to_string(),
|
|
updated_at: None,
|
|
pid: None,
|
|
started_at: None,
|
|
};
|
|
app.state.set(&entry).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn favorite_persists_in_state_and_survives_reload() {
|
|
let app = common::test_app("fav", &["list"]);
|
|
install(&app, "claude-code");
|
|
let code = annotations_cmd::run_favorite(&app, "claude-code").unwrap();
|
|
assert_eq!(code, 0);
|
|
assert!(annotations_cmd::is_favorite(&app, "claude-code"));
|
|
|
|
// A fresh StateStore over the same file sees the star (restart).
|
|
let store = agent_manager::state::StateStore::new(app.state.path().to_path_buf());
|
|
let ann = store.annotation("claude-code").unwrap();
|
|
assert!(ann.favorite, "favorite must survive a reload");
|
|
|
|
let code = annotations_cmd::run_unfavorite(&app, "claude-code").unwrap();
|
|
assert_eq!(code, 0);
|
|
assert!(!annotations_cmd::is_favorite(&app, "claude-code"));
|
|
}
|
|
|
|
#[test]
|
|
fn favorite_is_journalized() {
|
|
let app = common::test_app("favlog", &["list"]);
|
|
install(&app, "claude-code");
|
|
annotations_cmd::run_favorite(&app, "claude-code").unwrap();
|
|
let evs = events::read_events(&app.events_dir(), 10);
|
|
assert!(
|
|
evs.iter().any(|e| e.kind == EventKind::Annotate && e.agent.as_deref() == Some("claude-code")),
|
|
"an annotate event must be journaled: {evs:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn note_is_stored_and_shown() {
|
|
let app = common::test_app("note", &["list"]);
|
|
install(&app, "claude-code");
|
|
let code = annotations_cmd::run_note(
|
|
&app,
|
|
"claude-code",
|
|
&["use".to_string(), "the".to_string(), "sonnet".to_string(), "model".to_string()],
|
|
)
|
|
.unwrap();
|
|
assert_eq!(code, 0);
|
|
let ann = app.state.annotation("claude-code").unwrap();
|
|
assert_eq!(ann.note.as_deref(), Some("use the sonnet model"));
|
|
// Showing the note is a no-error operation too.
|
|
assert_eq!(annotations_cmd::run_note(&app, "claude-code", &[]).unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn tags_are_filterable_in_search() {
|
|
let app = common::test_app("tag", &["list"]);
|
|
install(&app, "claude-code");
|
|
install(&app, "gemini-cli");
|
|
annotations_cmd::run_tag(&app, "claude-code", &["python".to_string(), "Frontend".to_string()]).unwrap();
|
|
annotations_cmd::run_tag(&app, "gemini-cli", &["python".to_string()]).unwrap();
|
|
|
|
// Personal tags are lowercased and de-duplicated.
|
|
let ann: Annotation = app.state.annotation("claude-code").unwrap();
|
|
assert_eq!(ann.tags, vec!["frontend", "python"]);
|
|
|
|
// search --tag python returns both tagged agents.
|
|
assert_eq!(
|
|
annotations_cmd::agents_with_tag(&app, "python"),
|
|
vec!["claude-code".to_string(), "gemini-cli".to_string()]
|
|
);
|
|
let code = search_cmd::run(&app, Some(""), None, Some("python")).unwrap();
|
|
assert_eq!(code, 0);
|
|
|
|
// A tag matching nothing is reported cleanly.
|
|
let code = search_cmd::run(&app, Some(""), None, Some("nope")).unwrap();
|
|
assert_eq!(code, 0);
|
|
|
|
// untag removes one tag; tags command lists the rest.
|
|
annotations_cmd::run_untag(&app, "claude-code", "frontend").unwrap();
|
|
let ann = app.state.annotation("claude-code").unwrap();
|
|
assert_eq!(ann.tags, vec!["python"]);
|
|
let code = annotations_cmd::run_tags(&app, None).unwrap();
|
|
assert_eq!(code, 0);
|
|
let code = annotations_cmd::run_tags(&app, Some("claude-code")).unwrap();
|
|
assert_eq!(code, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn tag_requires_at_least_one_word() {
|
|
let app = common::test_app("tagempty", &["list"]);
|
|
install(&app, "claude-code");
|
|
let err = annotations_cmd::run_tag(&app, "claude-code", &[]).unwrap_err().to_string();
|
|
assert!(err.contains("usage"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_agent_is_rejected() {
|
|
let app = common::test_app("favbad", &["list"]);
|
|
let err = annotations_cmd::run_favorite(&app, "does-not-exist").unwrap_err().to_string();
|
|
assert!(err.contains("unknown agent"), "{err}");
|
|
}
|