""" Tests for the OSC 8 URL-linkification helper used by :class:`omnigent_ui_sdk.terminal.TerminalHost`. Covers: - bare URLs in plain text get wrapped - already-linkified content is preserved verbatim (idempotent) - trailing punctuation is excluded from the link region - URLs inside parentheses / quotes / angle brackets terminate cleanly before the delimiter - composes with surrounding ANSI SGR styling (Rich-rendered content is the typical input) - empty / no-URL strings pass through unchanged Why this gets a focused unit test rather than only being covered by the higher-level ``TerminalHost`` tests: the regex and the trailing-punctuation peel are subtle, and a regression silently downgrades clickable-URL rendering for every agent that emits links. A dedicated test file makes the contract unambiguous and catches a subtle regex break in one shot. """ from __future__ import annotations from omnigent_ui_sdk.terminal._linkify import linkify_ansi # OSC 8 escape components — duplicated here from the module # (deliberately) so the test fails loudly if the module's # escape format drifts. The contract this test pins is the # byte-level wire format, so spelling it out in the test is # load-bearing. _OSC_OPEN = "\x1b]8;;" _OSC_CLOSE = "\x1b\\" def _wrap(url: str) -> str: """ Build the expected OSC 8 byte sequence for ``url``. Helper to keep individual assertions readable; the byte soup is the same per-URL pattern repeated. :param url: The URL to wrap, e.g. ``"https://example.com"``. :returns: The OSC 8 byte sequence the linkifier should emit for this URL. """ return f"{_OSC_OPEN}{url}{_OSC_CLOSE}{url}{_OSC_OPEN}{_OSC_CLOSE}" def test_linkify_wraps_bare_https_url() -> None: """ A plain HTTPS URL in prose gets wrapped in OSC 8 escapes. Regression target: the URL regex stops matching ``https://`` schemes, or the OSC 8 envelope shape changes. """ out = linkify_ansi("See https://example.com here") assert out == f"See {_wrap('https://example.com')} here" def test_linkify_wraps_bare_http_url() -> None: """ HTTP URLs are linkified too (not HTTPS-only). Some legacy services still serve HTTP; the agent's tool output may surface them. """ out = linkify_ansi("Open http://localhost:8080/foo and click") assert out == f"Open {_wrap('http://localhost:8080/foo')} and click" def test_linkify_strips_trailing_period() -> None: """ Sentence-final punctuation isn't part of the URL. "Visit https://example.com." should produce a link over ``https://example.com`` followed by a literal period, not a link over ``https://example.com.`` (which would 404). """ out = linkify_ansi("Visit https://example.com.") assert out == f"Visit {_wrap('https://example.com')}." def test_linkify_strips_trailing_comma_between_urls() -> None: """ A list of URLs separated by ``, `` produces independent links — neither URL absorbs the comma. """ out = linkify_ansi("URLs: https://a.com, https://b.com") assert out == f"URLs: {_wrap('https://a.com')}, {_wrap('https://b.com')}" def test_linkify_stops_at_closing_paren() -> None: """ A URL inside parentheses ends at the close-paren — "(see https://example.com)" wraps the URL but not the closing ``)``. Trade-off: legitimate URLs containing parens (e.g. Wikipedia disambiguation pages) lose their close-paren in the linked region. Acceptable: the sentential-paren case is overwhelmingly more common in agent text than paren-bearing URLs. """ out = linkify_ansi("(see https://example.com)") assert out == f"(see {_wrap('https://example.com')})" def test_linkify_handles_query_string() -> None: """ Query strings (``?key=val&other=val``) are part of the URL and stay inside the link. """ url = "https://example.com/path?q=1&r=2" out = linkify_ansi(f"go to {url}") assert out == f"go to {_wrap(url)}" def test_linkify_is_idempotent_on_existing_osc_8_block() -> None: """ A URL already wrapped in OSC 8 escapes is left UNTOUCHED — we don't re-wrap and produce malformed nested escapes. Regression target: the OSC 8 block recognizer breaks (e.g. closer pattern stops matching) so the regex falls through to the bare-URL branch and double-wraps. The double-wrap failure mode is visually catastrophic in supporting terminals (the link target becomes garbled escape bytes) so this is the main idempotence assertion. """ already = f"see {_wrap('https://x.com')} end" out = linkify_ansi(already) assert out == already def test_linkify_handles_mixed_already_and_bare() -> None: """ Strings containing both already-linkified URLs and bare URLs only wrap the bare ones; the existing OSC 8 blocks pass through verbatim. """ bare = "https://a.com" already = _wrap("https://b.com") bare2 = "https://c.com" inp = f"ab {bare} cd {already} ef {bare2}" out = linkify_ansi(inp) assert out == f"ab {_wrap(bare)} cd {already} ef {_wrap(bare2)}" def test_linkify_composes_with_sgr_styling() -> None: """ Rich-rendered content typically contains SGR color codes around or near URLs. The linkifier's OSC 8 wrapper sits OUTSIDE those — the URL text stays styled, and the wrapper bytes don't break the SGR sequence. Concretely: a Rich-rendered ``[bold]See [/bold]https://x.com`` yields ``\\x1b[1mSee \\x1b[0mhttps://x.com``; after linkification the URL gets wrapped but the bold prefix is untouched. """ inp = "\x1b[1mSee \x1b[0mhttps://example.com" out = linkify_ansi(inp) assert out == f"\x1b[1mSee \x1b[0m{_wrap('https://example.com')}" def test_linkify_url_followed_by_trailing_sgr_reset() -> None: """ A URL immediately followed by an SGR reset (``\\x1b[0m``) — exactly what Rich emits for a styled/underlined autolink (``\\x1b[4;34m\\x1b[0m``) — must NOT swallow the reset into the URL. Regression target (the "0m before the URL" bug): ``_URL`` did not exclude ESC, so it matched ``https://example.com\\x1b[0m`` and embedded the reset INSIDE the OSC 8 link target (``\\x1b]8;;https://example.com\\x1b[0m\\x1b\\``) — a malformed hyperlink that terminals render by leaking ``0m`` as visible text before the URL. The URL must end at the ESC, leaving the reset OUTSIDE the link envelope. """ inp = "open \x1b[4;38;5;33mhttps://example.com\x1b[0m done" out = linkify_ansi(inp) # URL wrapped cleanly; the styling prefix and the trailing reset are # untouched and sit OUTSIDE the OSC 8 envelope. assert out == f"open \x1b[4;38;5;33m{_wrap('https://example.com')}\x1b[0m done" # The link target must be the bare URL — no escape bytes embedded in it. assert f"{_OSC_OPEN}https://example.com{_OSC_CLOSE}" in out assert f"{_OSC_OPEN}https://example.com\x1b[0m" not in out def test_linkify_no_urls_passes_through_unchanged() -> None: """ Strings without any URLs are returned byte-identical. No mutation, no spurious bytes inserted. """ inp = "This text has no links, just words. Period." assert linkify_ansi(inp) == inp def test_linkify_empty_string() -> None: """Empty input → empty output. Trivial but guards against edge-case regressions in the regex/sub fast path.""" assert linkify_ansi("") == "" def test_linkify_url_with_multiple_trailing_punct() -> None: """ Multiple trailing punctuation chars (``!?``, ``...``) all stay outside the link. Edge case worth pinning: URL endings like ``foo.com!?`` — the linker peels ALL trailing-punct chars in a loop, not just one. """ out = linkify_ansi("Wow https://example.com!?") assert out == f"Wow {_wrap('https://example.com')}!?" def test_linkify_url_at_end_of_string() -> None: """A URL that's the last thing in the string — no trailing text, no terminator. Common for tool output that prints a URL on its own line.""" out = linkify_ansi("https://example.com") assert out == _wrap("https://example.com") def test_linkify_skips_naked_domains() -> None: """ Naked domain references like ``example.com`` (no scheme) are NOT linkified. We require ``http(s)://`` to avoid false positives on things like ``agent.py`` or version strings (``1.2.3``). """ inp = "see example.com or agent.py:42" assert linkify_ansi(inp) == inp def test_linkify_idempotent_on_rich_osc_8_with_sgr_display_text() -> None: """ Rich's Markdown renderer wraps ``[text](url)`` links in OSC 8 with SGR color/underline codes INSIDE the display text, e.g.:: \\x1b]8;id=123;https://x.com\\x1b\\\\ \\x1b[4;34mtext\\x1b[0m \\x1b]8;;\\x1b\\\\ The display region between OSC 8 opener and closer contains ``\\x1b[...m`` SGR sequences. ``linkify_ansi`` must recognize this as a pre-existing OSC 8 block and leave it untouched. Regression target (2026-05-13): the ``_OSC_8_BLOCK`` regex used ``[^\\x1b]*`` for display text, which rejected any ``\\x1b`` — so Rich's styled blocks went unrecognized, the bare-URL branch fired, and the URL got double-wrapped, producing corrupted escape sequences that broke clickable links in every terminal. """ # Simulate Rich's actual output: OSC 8 with id param, SGR-styled text. rich_osc_8 = ( "\x1b]8;id=12345;https://example.com/docs\x1b\\\x1b[4;34mthe docs\x1b[0m\x1b]8;;\x1b\\" ) inp = f"See {rich_osc_8} for more" out = linkify_ansi(inp) assert out == inp, ( f"Rich's OSC 8 block with SGR display text was mutated by " f"linkify_ansi (double-wrap regression). " f"Input: {inp!r}, output: {out!r}" ) def test_linkify_mixed_rich_osc_8_and_bare_url() -> None: """ When a string contains both a Rich-emitted OSC 8 block (with SGR display text) AND a bare URL, ``linkify_ansi`` must wrap the bare URL while leaving the Rich block untouched. """ rich_block = "\x1b]8;id=1;https://example.com\x1b\\\x1b[4;34mlinked text\x1b[0m\x1b]8;;\x1b\\" inp = f"See {rich_block} and also https://bare.example.com here" out = linkify_ansi(inp) # Rich block preserved verbatim. assert rich_block in out, f"Rich OSC 8 block was mutated. Output: {out!r}" # Bare URL wrapped. assert _wrap("https://bare.example.com") in out, f"Bare URL was not wrapped. Output: {out!r}"