fix(presentation): brakujacy token przy /chart/prompt i /chart/horoscope
build / build (push) Successful in 59s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m57s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m52s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 30s
Testy / Kontrola składni wszystkich warstw (push) Successful in 18s
build / build (push) Successful in 59s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m57s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m52s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 30s
Testy / Kontrola składni wszystkich warstw (push) Successful in 18s
Blad z mergu: _auth_headers() dodano na galezi hardeningu, ktora odbila sie od mastera ZANIM powstaly metody prompt() i horoscope() (LOG-29/30, LOG-31). Git zmergowal obie zmiany czysto — byly w roznych liniach — ale semantycznie nowe metody wyszly bez tokenu i dostawaly 401 przy wlaczonej ochronie. Skutek dla uzytkownika: przyciski „Generuj prompt (AI)" i „Napisz horoskop" nie dzialaly po wdrozeniu INTERNAL_TOKEN, mimo ze reszta aplikacji dzialala. - naprawione oba wywolania, - nowy test strukturalny (AST): KAZDE wyjscie HTTP w dol musi niesc headers=. Test jednej metody by tego nie zlapal — regula musi byc pilnowana calosciowo. Straznik zweryfikowany sabotazem: po usunieciu naglowka test pada ze wskazaniem konkretnej linii; po przywroceniu 15/15 przechodzi. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #14.
This commit is contained in:
@@ -78,7 +78,7 @@ class LogicClient:
|
|||||||
if from_date and to_date:
|
if from_date and to_date:
|
||||||
payload["from_date"], payload["to_date"] = from_date, to_date
|
payload["from_date"], payload["to_date"] = from_date, to_date
|
||||||
with httpx.Client(timeout=max(settings.http_timeout, 60.0)) as client:
|
with httpx.Client(timeout=max(settings.http_timeout, 60.0)) as client:
|
||||||
r = client.post(f"{self.base_url}/chart/prompt", json=payload)
|
r = client.post(f"{self.base_url}/chart/prompt", json=payload, headers=_auth_headers())
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ class LogicClient:
|
|||||||
if from_date and to_date:
|
if from_date and to_date:
|
||||||
payload["from_date"], payload["to_date"] = from_date, to_date
|
payload["from_date"], payload["to_date"] = from_date, to_date
|
||||||
with httpx.Client(timeout=max(settings.http_timeout, 300.0)) as client:
|
with httpx.Client(timeout=max(settings.http_timeout, 300.0)) as client:
|
||||||
r = client.post(f"{self.base_url}/chart/horoscope", json=payload)
|
r = client.post(f"{self.base_url}/chart/horoscope", json=payload, headers=_auth_headers())
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"""Niezmiennik: KAŻDE wyjście HTTP w dół niesie token międzywarstwowy (LOG-32).
|
||||||
|
|
||||||
|
Powód istnienia tego testu: token dodano do klienta na gałęzi, która odbiła się od
|
||||||
|
mastera zanim powstały metody `prompt()` i `horoscope()`. Git zmergował obie zmiany
|
||||||
|
czysto (różne linie), ale nowe metody wyszły BEZ tokenu — i dostawały 401 dopiero na
|
||||||
|
produkcji. Zwykły test jednej metody by tego nie złapał, więc sprawdzamy regułę
|
||||||
|
strukturalnie: nie ma wywołania bez `headers=`.
|
||||||
|
"""
|
||||||
|
import ast
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
CLIENT = pathlib.Path(__file__).resolve().parents[1] / "app" / "clients" / "logic_client.py"
|
||||||
|
|
||||||
|
|
||||||
|
def _http_calls(path: pathlib.Path) -> list[tuple[str, int, bool]]:
|
||||||
|
"""(nazwa_metody_http, linia, czy_ma_headers) dla każdego client.post/get."""
|
||||||
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||||
|
out = []
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Attribute):
|
||||||
|
continue
|
||||||
|
if node.func.attr not in ("post", "get", "put", "patch", "delete"):
|
||||||
|
continue
|
||||||
|
if not (isinstance(node.func.value, ast.Name) and node.func.value.id == "client"):
|
||||||
|
continue
|
||||||
|
has_headers = any(kw.arg == "headers" for kw in node.keywords)
|
||||||
|
out.append((node.func.attr, node.lineno, has_headers))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_module_exists():
|
||||||
|
assert CLIENT.is_file()
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_outbound_call_sends_auth_header():
|
||||||
|
calls = _http_calls(CLIENT)
|
||||||
|
assert calls, "nie znaleziono żadnego wywołania HTTP — test przestał cokolwiek pilnować"
|
||||||
|
missing = [f"{CLIENT.name}:{line} client.{verb}()" for verb, line, ok in calls if not ok]
|
||||||
|
assert not missing, (
|
||||||
|
"Wywołania w dół bez tokenu międzywarstwowego (dostaną 401 przy włączonej "
|
||||||
|
"ochronie): " + ", ".join(missing)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_headers_helper_is_lazy():
|
||||||
|
"""Token czytany przy wywołaniu, nie przy imporcie — inaczej pod wystartowałby
|
||||||
|
z pustym tokenem, gdyby zmienna pojawiła się później."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
from app.clients.logic_client import _auth_headers
|
||||||
|
|
||||||
|
old = os.environ.get("INTERNAL_TOKEN")
|
||||||
|
try:
|
||||||
|
os.environ["INTERNAL_TOKEN"] = "abc"
|
||||||
|
assert _auth_headers() == {"X-Astrololo-Token": "abc"}
|
||||||
|
os.environ.pop("INTERNAL_TOKEN")
|
||||||
|
assert _auth_headers() == {} # ochrona wyłączona = brak nagłówka
|
||||||
|
finally:
|
||||||
|
if old is not None:
|
||||||
|
os.environ["INTERNAL_TOKEN"] = old
|
||||||
|
else:
|
||||||
|
os.environ.pop("INTERNAL_TOKEN", None)
|
||||||
Reference in New Issue
Block a user