Complete Phase 1: policy kernel, REST service, and local smoke tooling
Implements QONTO-WP-0002 (policy-gated Qonto REST service with audit logging, rate limiting, and credential handling) and the ADHOC-2026-07-21 follow-up (fixture-backed local smoke mode, repo classification metadata). Marks QONTO-WP-0001/0002 and the ad-hoc workplan finished, and regenerates WORK-RECORDS.md and the ADHOC workplan's state_hub_workstream_id via fix-consistency. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
eef408bb19
commit
ca12843013
33 changed files with 2533 additions and 30 deletions
84
tests/test_qonto_client.py
Normal file
84
tests/test_qonto_client.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import httpx
|
||||
|
||||
from qonto_assistant.contracts import QontoCredentials
|
||||
from qonto_assistant.qonto_client import QontoClient
|
||||
|
||||
|
||||
class StubCredentialProvider:
|
||||
def __init__(self) -> None:
|
||||
self.invalidated = False
|
||||
|
||||
def get_credentials(self) -> QontoCredentials:
|
||||
return QontoCredentials(api_user="binky-user", api_key="top-secret")
|
||||
|
||||
def invalidate(self) -> None:
|
||||
self.invalidated = True
|
||||
|
||||
|
||||
def test_client_sends_legacy_api_key_header_and_params() -> None:
|
||||
provider = StubCredentialProvider()
|
||||
seen: dict[str, object] = {}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
seen["auth"] = request.headers["Authorization"]
|
||||
seen["path"] = request.url.path
|
||||
seen["params"] = dict(request.url.params)
|
||||
return httpx.Response(200, json={"transactions": []})
|
||||
|
||||
client = QontoClient(
|
||||
base_url="https://example.test",
|
||||
organization_path="/v2/organization",
|
||||
transactions_path="/v2/transactions",
|
||||
auth_mode="legacy_api_key",
|
||||
timeout_seconds=1,
|
||||
max_retries=0,
|
||||
credential_provider=provider,
|
||||
transport=httpx.MockTransport(handler),
|
||||
)
|
||||
|
||||
client.list_transactions(
|
||||
iban="DE1234567890",
|
||||
page=2,
|
||||
page_size=25,
|
||||
window_days=14,
|
||||
status="completed",
|
||||
side="debit",
|
||||
)
|
||||
|
||||
assert seen["auth"] == "binky-user:top-secret"
|
||||
assert seen["path"] == "/v2/transactions"
|
||||
assert seen["params"] == {
|
||||
"iban": "DE1234567890",
|
||||
"current_page": "2",
|
||||
"per_page": "25",
|
||||
"window_days": "14",
|
||||
"status": "completed",
|
||||
"side": "debit",
|
||||
}
|
||||
|
||||
|
||||
def test_client_invalidates_cached_credentials_on_auth_failure() -> None:
|
||||
provider = StubCredentialProvider()
|
||||
|
||||
def handler(_: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(401, json={"error": "unauthorized"})
|
||||
|
||||
client = QontoClient(
|
||||
base_url="https://example.test",
|
||||
organization_path="/v2/organization",
|
||||
transactions_path="/v2/transactions",
|
||||
auth_mode="legacy_api_key",
|
||||
timeout_seconds=1,
|
||||
max_retries=0,
|
||||
credential_provider=provider,
|
||||
transport=httpx.MockTransport(handler),
|
||||
)
|
||||
|
||||
try:
|
||||
client.get_organization()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
assert getattr(exc, "error_code", None) == "qonto_auth_failed"
|
||||
else:
|
||||
raise AssertionError("Expected Qonto auth failure")
|
||||
|
||||
assert provider.invalidated is True
|
||||
Loading…
Add table
Add a link
Reference in a new issue