Files
publicpaste/tests/test_provider_parsing.py
T
尘曲 fbde4d6aca Add public paste CLI and skill
Provide a zero-dependency Python tool that races multiple public paste providers and documents safe AI-tool usage through a project skill.
2026-05-12 13:14:05 +08:00

117 lines
4.3 KiB
Python

from __future__ import annotations
import time
import pytest
from publicpaste.cli import main, read_input
from publicpaste.core import PasteError, paste_text
from publicpaste.providers import (
DpasteProvider,
PasteCentosProvider,
PasteProvider,
PasteRsProvider,
ProviderError,
ProviderResult,
SnippetHostProvider,
TermbinProvider,
ZeroXZeroProvider,
)
@pytest.mark.parametrize(
("parser", "body", "headers", "expected"),
[
(PasteRsProvider.parse_response, "https://paste.rs/abc\n", {}, "https://paste.rs/abc"),
(DpasteProvider.parse_response, b"https://dpaste.org/ABC123\n", {}, "https://dpaste.org/ABC123"),
(ZeroXZeroProvider.parse_response, "https://0x0.st/abcd.txt\n", {}, "https://0x0.st/abcd.txt"),
(SnippetHostProvider.parse_response, "", {"Location": "/abc123"}, "https://snippet.host/abc123"),
(
SnippetHostProvider.parse_response,
'<a href="https://snippet.host/xyz">snippet</a>',
{},
"https://snippet.host/xyz",
),
(
PasteCentosProvider.parse_response,
'{"url":"https://paste.centos.org/view/42"}',
{},
"https://paste.centos.org/view/42",
),
(PasteCentosProvider.parse_response, '{"id": 42}', {}, "https://paste.centos.org/view/42"),
(TermbinProvider.parse_response, "https://termbin.com/abcd\n", {}, "https://termbin.com/abcd"),
],
)
def test_provider_parse_response(parser, body: bytes | str, headers: dict[str, str], expected: str) -> None:
assert parser(body, headers) == expected
def test_provider_parse_response_rejects_missing_url() -> None:
with pytest.raises(ProviderError):
PasteRsProvider.parse_response("not a url")
class FakeProvider(PasteProvider):
name = "fake"
def __init__(self, url: str | None = None, delay: float = 0.0, error: str | None = None) -> None:
self.url = url
self.delay = delay
self.error = error
def paste(self, text: str, *, timeout: float) -> ProviderResult:
if self.delay:
time.sleep(self.delay)
if self.error:
raise ProviderError(self.error)
assert self.url is not None
return ProviderResult(provider=self.name, url=self.url)
def test_paste_text_returns_first_success() -> None:
slow = FakeProvider("https://paste.rs/slow", delay=0.2)
fast = FakeProvider("https://paste.rs/fast", delay=0.01)
result = paste_text("hello", providers=[slow, fast], overall_timeout=1.0)
assert result.url == "https://paste.rs/fast"
assert result.provider == "fake"
def test_paste_text_aggregates_all_failures() -> None:
with pytest.raises(PasteError) as exc_info:
paste_text(
"hello",
providers=[FakeProvider(error="boom"), FakeProvider(error="nope")],
overall_timeout=1.0,
)
assert "all providers failed" in str(exc_info.value)
assert exc_info.value.errors == {"fake": "nope"} or exc_info.value.errors == {"fake": "boom"}
def test_cli_reads_positional_text(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
def fake_paste_text(text: str, **kwargs):
assert text == "hello"
return type("Result", (), {"url": "https://paste.rs/abc", "provider": "paste.rs"})()
monkeypatch.setattr("publicpaste.cli.paste_text", fake_paste_text)
assert main(["hello"]) == 0
assert capsys.readouterr().out == "https://paste.rs/abc\n"
def test_cli_json_from_stdin(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
def fake_paste_text(text: str, **kwargs):
assert text == "from stdin"
return type("Result", (), {"url": "https://paste.rs/abc", "provider": "paste.rs"})()
monkeypatch.setattr("publicpaste.cli.paste_text", fake_paste_text)
monkeypatch.setattr("sys.stdin", type("Stdin", (), {"read": lambda self: "from stdin"})())
assert main(["--json"]) == 0
assert '"provider": "paste.rs"' in capsys.readouterr().out
def test_read_input_rejects_text_and_file(tmp_path) -> None:
path = tmp_path / "a.txt"
path.write_text("file", encoding="utf-8")
args = type("Args", (), {"text": "hello", "file": path})()
with pytest.raises(ValueError):
read_input(args, stdin=None) # type: ignore[arg-type]