use super::*;
#[test]
fn read_card_projects_single_running_success_failure_and_selectors() {
let running = read_card_state(
"read-running",
ActivityStatus::Running,
serde_json::json!({"path":"src/lib.rs:raw:2-3"}),
serde_json::json!({}),
"",
);
let running = &project_cards(&running)[0];
assert_eq!(running.role, TranscriptCardRole::Read);
assert_eq!(running.title, "Read");
assert_eq!(running.body, "src/lib.rs:raw:2-3");
let success = read_card_state(
"read-success",
ActivityStatus::Success,
serde_json::json!({"path":"skill://rust-dev:3+"}),
serde_json::json!({"hashline_seen_lines":[3,4],"truncated":true}),
"secret file content",
);
let success = &project_cards(&success)[0];
assert_eq!(success.title, "Read • truncated");
assert_eq!(success.body, "skill://rust-dev:3+\n2 lines");
assert!(!success.body.contains("secret file content"));
let failed = read_card_state(
"read-failed",
ActivityStatus::Failed,
serde_json::json!({"path":"docs/missing.md"}),
serde_json::json!({}),
"No such file or directory\nbacktrace omitted",
);
let failed = &project_cards(&failed)[0];
assert_eq!(failed.title, "Read");
assert_eq!(failed.body, "docs/missing.md\nNo such file or directory");
}
#[test]
fn read_card_projects_multi_all_partial_and_all_failed_in_request_order() {
let all = read_card_state(
"read-all",
ActivityStatus::Success,
serde_json::json!({"paths":["src/lib.rs", "https://example.com:raw", "issue://42"]}),
serde_json::json!({
"files":3,
"succeeded":3,
"failed":0,
"results":[
read_result("src/lib.rs", true, None),
{
"requested_path":"https://example.com:raw",
"success":true,
"truncated":true
},
read_result("issue://42", true, None)
]
}),
"unshown content",
);
let all = &project_cards(&all)[0];
assert_eq!(all.title, "Read • 3 resources");
assert_eq!(
all.body,
"✓ src/lib.rs\n△ https://example.com:raw\n result truncated\n✓ issue://42"
);
let partial = read_card_state(
"read-partial",
ActivityStatus::Success,
serde_json::json!({"paths":["src/lib.rs", "docs/missing.md", "pr://7"]}),
serde_json::json!({
"files":3,
"succeeded":2,
"failed":1,
"aggregate_truncated":true,
"results":[
read_result("src/lib.rs", true, None),
read_result("docs/missing.md", false, Some("No such file or directory")),
read_result("pr://7", true, None)
]
}),
"unshown content",
);
let partial = &project_cards(&partial)[0];
assert_eq!(partial.title, "Read • 2/3 resources");
assert_eq!(
partial.body,
"✓ src/lib.rs\n✗ docs/missing.md\n No such file or directory\n✓ pr://7"
);
let omitted = read_card_state(
"read-omitted",
ActivityStatus::Success,
serde_json::json!({"paths":["src/lib.rs", "docs/later.md"]}),
serde_json::json!({
"files":2,
"succeeded":1,
"failed":0,
"omitted":1,
"results":[
read_result("src/lib.rs", true, None),
read_result(
"docs/later.md",
false,
Some("aggregate read output limit exceeded")
)
]
}),
"--- READ OMITTED ---\nERROR: aggregate read output limit",
);
let omitted = &project_cards(&omitted)[0];
assert_eq!(omitted.title, "Read • 1/2 resources");
assert!(omitted.body.contains("✗ docs/later.md"));
let failed = read_card_state(
"read-none",
ActivityStatus::Failed,
serde_json::json!({"paths":["missing-a", "missing-b"]}),
serde_json::json!({
"files":2,
"succeeded":0,
"failed":2,
"results":[
read_result("missing-a", false, Some("first failure")),
read_result("missing-b", false, Some("second failure"))
]
}),
"",
);
let failed = &project_cards(&failed)[0];
assert_eq!(failed.title, "Read • 2 resources");
assert!(failed.body.contains("✗ missing-a\n first failure"));
assert!(failed.body.contains("✗ missing-b\n second failure"));
}
#[test]
fn read_card_treats_one_element_paths_as_single_resource() {
let state = read_card_state(
"read-one-paths",
ActivityStatus::Success,
serde_json::json!({"paths":["session://abc:2"]}),
serde_json::json!({
"files":1,
"succeeded":1,
"failed":0,
"results":[{
"requested_path":"session://abc:2",
"success":true,
"hashline_seen_lines":[2]
}]
}),
"selected session line",
);
let card = &project_cards(&state)[0];
assert_eq!(card.title, "Read");
assert_eq!(card.body, "session://abc:2\n1 lines");
}
#[test]
fn read_card_preserves_targets_errors_and_copy_text_except_display_controls() {
let state = read_card_state(
"read-safe",
ActivityStatus::Failed,
serde_json::json!({
"path":"https://user:secret@example.com/file?token=literal\u{1b}[31m:raw:2-4\nforged\trow\u{1b}]0;hidden\u{7}"
}),
serde_json::json!({}),
"authorization: Bearer useful-error\u{1b}[2J\nraw file contents",
);
let card = &project_cards(&state)[0];
let target = "https://user:secret@example.com/file?token=literal:raw:2-4forgedrow";
assert_eq!(
card.body,
format!("{target}\nauthorization: Bearer useful-error")
);
assert!(!card.body.contains('\u{1b}'));
assert!(!card.body.contains('\u{7}'));
assert!(!card.body.contains("hidden"));
assert!(!card.body.contains("raw file contents"));
for width in [1, 2, 12] {
let copied = visual_text_for_width(&state, width);
assert!(copied.contains("Read"), "width {width}: {copied:?}");
assert!(copied.contains(target), "width {width}: {copied:?}");
assert!(
copied.contains("authorization: Bearer useful-error"),
"width {width}: {copied:?}"
);
assert!(!copied.contains("raw file contents"));
assert!(!copied.contains("STATUS:"));
assert!(!copied.contains("OUTPUT:"));
assert!(!copied.contains('\u{1b}'));
}
}
#[test]
fn read_card_preserves_multi_resource_errors_except_display_controls() {
let state = read_card_state(
"read-multi-safe",
ActivityStatus::Success,
serde_json::json!({"paths":["ok?api_key=literal", "missing?token=literal\nforged\trow\u{1b}[31m"]}),
serde_json::json!({
"files":2,
"succeeded":1,
"failed":1,
"results":[
read_result("ok?api_key=literal", true, None),
read_result(
"missing?token=literal\nforged\trow\u{1b}[31m",
false,
Some("authorization: Bearer useful-error\u{1b}]0;hidden\u{7}")
)
]
}),
"unshown content",
);
let card = &project_cards(&state)[0];
assert_eq!(card.title, "Read • 1/2 resources");
assert_eq!(
card.body,
"✓ ok?api_key=literal\n✗ missing?token=literalforgedrow\n authorization: Bearer useful-error"
);
}