feat: initial commit
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
# acme-app (fixture)
|
||||
|
||||
Minimalna aplikacja używająca `acme-sdk` w wersji **1.4.2**. Katalog `stubs/acme`
|
||||
zawiera atrapę SDK w wersji **2.1.0** - tylko nowe API.
|
||||
|
||||
Stan początkowy: `python3 -m pytest -q` jest **czerwony** (kod woła API z 1.x).
|
||||
Zadaniem pipeline'u agentowego jest doprowadzić go do zieleni, podnosząc przy okazji
|
||||
deklarację wersji w `pyproject.toml`.
|
||||
|
||||
Nie edytuj tego katalogu ręcznie - jest punktem odniesienia dla testów regresyjnych.
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Fixture nie ściąga nic z sieci: `acme` to lokalny stub udający SDK w wersji 2.x.
|
||||
|
||||
Dzięki temu weryfikacja jest czerwona przed migracją i zielona po niej,
|
||||
a cały przebieg pipeline'u da się odtworzyć offline - także na runnerze bez internetu.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).parent
|
||||
for extra in (ROOT / "src", ROOT / "stubs"):
|
||||
if str(extra) not in sys.path:
|
||||
sys.path.insert(0, str(extra))
|
||||
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "acme-app"
|
||||
version = "0.3.0"
|
||||
description = "Przykładowa aplikacja korzystająca z acme-sdk 1.x - fixture dla pipeline'u agentowego"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"acme-sdk==1.4.2",
|
||||
"httpx>=0.27",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Przykładowa aplikacja korzystająca z acme-sdk."""
|
||||
|
||||
__all__ = ["notifier"]
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Wysyłka powiadomień do klientów przez acme-sdk."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from acme import Client
|
||||
from acme.errors import AcmeError
|
||||
|
||||
WELCOME_BODY = "Witamy w serwisie. Twoje konto jest aktywne."
|
||||
|
||||
|
||||
def send_welcome(email: str, api_key: str, endpoint: str) -> str:
|
||||
"""Wysyła powiadomienie powitalne i zwraca identyfikator wiadomości."""
|
||||
client = Client(api_key=api_key, endpoint=endpoint)
|
||||
result = client.send(to=email, body=WELCOME_BODY)
|
||||
client.close()
|
||||
return result["id"]
|
||||
|
||||
|
||||
def send_reminder(email: str, api_key: str, endpoint: str, body: str) -> str | None:
|
||||
"""Wysyła przypomnienie. Zwraca None, gdy dostawca odrzucił wiadomość."""
|
||||
client = Client(api_key=api_key, endpoint=endpoint)
|
||||
try:
|
||||
result = client.send(to=email, body=body)
|
||||
except AcmeError:
|
||||
return None
|
||||
client.close()
|
||||
return result["id"]
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Atrapa acme-sdk 2.1.0 - wyłącznie API z wersji 2.x.
|
||||
|
||||
Odpowiada SDK dostarczanemu przez dostawcę: klasa `Client` i metoda `send()` z 1.x
|
||||
zostały usunięte, więc kod sprzed migracji nie zaimportuje się ani nie zadziała.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .errors import AcmeError
|
||||
|
||||
__version__ = "2.1.0"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
id: str
|
||||
recipient: str
|
||||
content: str
|
||||
|
||||
|
||||
class _Messages:
|
||||
def __init__(self, client: "AcmeClient") -> None:
|
||||
self._client = client
|
||||
|
||||
def create(self, recipient: str, content: str) -> Message:
|
||||
if recipient.startswith("odrzuc@"):
|
||||
raise AcmeError("recipient rejected by provider")
|
||||
return Message(id=f"msg_{abs(hash((recipient, content))) % 10**8:08d}", recipient=recipient, content=content)
|
||||
|
||||
|
||||
class AcmeClient:
|
||||
"""Klient 2.x. Zasoby zwalniane automatycznie - brak metody close()."""
|
||||
|
||||
def __init__(self, api_key: str, base_url: str, timeout: float = 10.0) -> None:
|
||||
if not api_key:
|
||||
raise AcmeError("api_key is required")
|
||||
self.api_key = api_key
|
||||
self.base_url = base_url
|
||||
self.timeout = timeout
|
||||
self.messages = _Messages(self)
|
||||
|
||||
def __enter__(self) -> "AcmeClient":
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_info: object) -> None:
|
||||
return None
|
||||
|
||||
|
||||
__all__ = ["AcmeClient", "AcmeError", "Message", "__version__"]
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Hierarchia błędów acme-sdk - niezmieniona między 1.x a 2.x."""
|
||||
|
||||
|
||||
class AcmeError(Exception):
|
||||
"""Błąd zwrócony przez dostawcę."""
|
||||
@@ -0,0 +1,15 @@
|
||||
from acme_app.notifier import send_reminder, send_welcome
|
||||
|
||||
|
||||
def test_send_welcome_zwraca_identyfikator():
|
||||
message_id = send_welcome("jan@example.com", api_key="k-1", endpoint="https://acme.internal")
|
||||
assert message_id.startswith("msg_")
|
||||
|
||||
|
||||
def test_send_reminder_zwraca_identyfikator():
|
||||
message_id = send_reminder("jan@example.com", api_key="k-1", endpoint="https://acme.internal", body="Przypomnienie")
|
||||
assert message_id.startswith("msg_")
|
||||
|
||||
|
||||
def test_send_reminder_zwraca_none_przy_bledzie_dostawcy():
|
||||
assert send_reminder("odrzuc@example.com", api_key="k-1", endpoint="https://acme.internal", body="x") is None
|
||||
Reference in New Issue
Block a user