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.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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]
|
||||
Reference in New Issue
Block a user