Files
codex/codex-rs/tui/src/assistant_directives_tests.rs
T
Ian MacLeod f46671b14a Render assistant file citations as local links (#42650)
## What changed

- Convert `codex-file-citation` directives in assistant Markdown into local-file links while preserving paths with Markdown-significant characters, Unicode, Windows separators, and location suffixes.
- Apply citation rendering consistently to streaming output, finalized messages and plans, and resume-picker transcript previews.
- Keep directives literal in code, HTML, existing links, reference definitions, escaped text, and generic Markdown rendering.
- Bound repeated parsing work for malformed directive candidates without limiting valid citations.

## Testing

- Add coverage for citation parsing and rendering across finalized, streaming, and resume-preview output, including malformed input and path edge cases.

GitOrigin-RevId: 5a0bfb3fc7ac21b7e8846fd170a349fb2d2d5e85
2026-09-04 01:46:55 +00:00

137 lines
4.3 KiB
Rust

//! Shared directive grammar and caller-specific quote compatibility.
use super::AssistantDirective;
use super::QuoteEscaping;
use super::parse_assistant_directive;
use super::parse_assistant_directive_with_budget;
use pretty_assertions::assert_eq;
use std::borrow::Cow;
use std::collections::BTreeMap;
#[test]
fn parses_git_receipts_with_unquoted_attributes() {
let raw = r#"::git-create-pr{cwd="/repo\" branch="feature/report" isDraft=true}"#;
assert_eq!(
parse_assistant_directive(raw, QuoteEscaping::Literal),
Some(AssistantDirective {
name: "git-create-pr",
attributes: BTreeMap::from([
("branch", Cow::Borrowed("feature/report")),
("cwd", Cow::Borrowed(r"/repo\")),
("isDraft", Cow::Borrowed("true")),
]),
raw,
}),
);
}
#[test]
fn parses_triple_colon_comments_with_escaped_quotes_and_closing_braces() {
let raw = r#":::code-comment{title="Fix class" body="Keep \"px-${size}\" literal." file="C:\Users\me\app.ts" start=10 priority=2}"#;
let source = format!("{raw} remaining text");
assert_eq!(
parse_assistant_directive(&source, QuoteEscaping::Backslash),
Some(AssistantDirective {
name: "code-comment",
attributes: BTreeMap::from([
(
"body",
Cow::Owned(r#"Keep "px-${size}" literal."#.to_string())
),
("file", Cow::Borrowed(r"C:\Users\me\app.ts")),
("priority", Cow::Borrowed("2")),
("start", Cow::Borrowed("10")),
("title", Cow::Borrowed("Fix class")),
]),
raw,
}),
);
}
#[test]
fn preserves_artifact_metadata_and_single_quoted_values() {
let raw = ":artifact{path='Quarterly Report.xlsx' label='team\\'s report' sheet='Revenue' range='A1:D8'}";
assert_eq!(
parse_assistant_directive(raw, QuoteEscaping::Backslash),
Some(AssistantDirective {
name: "artifact",
attributes: BTreeMap::from([
("label", Cow::Owned("team's report".to_string())),
("path", Cow::Borrowed("Quarterly Report.xlsx")),
("range", Cow::Borrowed("A1:D8")),
("sheet", Cow::Borrowed("Revenue")),
]),
raw,
}),
);
}
#[test]
fn rejects_ambiguous_or_incomplete_directives() {
for source in [
r#"::git-push{cwd="/repo" cwd="/other"}"#,
r#"::git-push{cwd="/repo" branch="feature""#,
"::::code-comment{title=comment}",
":artifact{path=/tmp/a path=:artifact{path=/tmp/b}}",
":artifact{invalid:key=:artifact{path=/tmp/a}}",
":artifact{path=/tmp/a\nnext=value}",
] {
assert_eq!(
parse_assistant_directive(source, QuoteEscaping::Backslash),
None
);
}
}
#[test]
fn malformed_retries_exhaust_the_shared_scan_budget() {
let source = format!(
"codex-file-citation {} x bad}}",
":a{k=".repeat(/*n*/ 16_000)
);
let mut remaining = source.len() * 4;
let mut attempts = 0;
for (offset, _) in source.match_indices(':') {
if remaining == 0 {
break;
}
assert_eq!(
parse_assistant_directive_with_budget(
&source[offset..],
QuoteEscaping::Literal,
&mut remaining,
),
None,
);
attempts += 1;
}
assert!(attempts <= 8, "retried {attempts} long malformed values");
assert_eq!(remaining, 0);
}
#[test]
fn scan_budget_counts_inspected_values_not_the_unread_suffix() {
let tail = "x".repeat(/*n*/ 16_000);
let raw = ":artifact{path=report.xlsx}";
let source = format!("{raw}{tail}");
let mut remaining = 64;
assert_eq!(
parse_assistant_directive_with_budget(&source, QuoteEscaping::Literal, &mut remaining),
parse_assistant_directive(raw, QuoteEscaping::Literal),
);
for source in [
format!(":artifact{{path=\"{tail}"),
format!(":artifact{{path={tail}"),
] {
let mut remaining = 64;
assert_eq!(
parse_assistant_directive_with_budget(&source, QuoteEscaping::Literal, &mut remaining),
None,
);
assert_eq!(remaining, 0);
}
}