improve signed reads, crypto hardening, and scraper transport
This commit is contained in:
@@ -155,7 +155,7 @@ def test_adoption_error_codes_used_by_callers_are_all_defined():
|
||||
# These are the codes referenced from app/api/positions.py +
|
||||
# app/services/telegram_bot.py. Update both sites if you add one.
|
||||
promised_codes = {
|
||||
"no_subscription", "no_hl_key", "paper_mode",
|
||||
"no_subscription", "no_hl_key", "paper_mode", "macro_disabled",
|
||||
"key_decrypt_failed", "hl_read_failed",
|
||||
"bad_mode", "circuit_breaker",
|
||||
"already_adopted", "concurrency_cap",
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
"""enc:v2 envelope encryption (H4): roundtrip, legacy v1 compat, format checks."""
|
||||
import base64
|
||||
import hashlib
|
||||
|
||||
import pytest
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
from app.services import crypto
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _kek(monkeypatch):
|
||||
monkeypatch.setattr(crypto.settings, "encryption_key", "k" * 64)
|
||||
# Reset module caches so each test derives from the patched KEK.
|
||||
crypto._fernet_v1 = None
|
||||
crypto._fernet_v2_cache.clear()
|
||||
crypto._encrypt_salt = None
|
||||
yield
|
||||
crypto._fernet_v1 = None
|
||||
crypto._fernet_v2_cache.clear()
|
||||
crypto._encrypt_salt = None
|
||||
|
||||
|
||||
def test_v2_roundtrip():
|
||||
blob = crypto.encrypt_api_key("0x" + "ab" * 32)
|
||||
assert blob.startswith(crypto.ENC_PREFIX_V2)
|
||||
assert crypto.decrypt_api_key(blob) == "0x" + "ab" * 32
|
||||
assert crypto.is_current_format(blob)
|
||||
# Fits the hl_api_key String(256) column.
|
||||
assert len(blob) <= 256
|
||||
|
||||
|
||||
def test_v1_blob_still_decrypts():
|
||||
# Build a v1 blob exactly the way the legacy code did.
|
||||
digest = hashlib.sha256(("k" * 64).encode()).digest()
|
||||
f = Fernet(base64.urlsafe_b64encode(digest))
|
||||
blob = crypto.ENC_PREFIX_V1 + f.encrypt(b"secret-key").decode()
|
||||
assert not crypto.is_current_format(blob)
|
||||
assert crypto.decrypt_api_key(blob) == "secret-key"
|
||||
|
||||
|
||||
def test_wrong_kek_raises(monkeypatch):
|
||||
blob = crypto.encrypt_api_key("topsecret")
|
||||
monkeypatch.setattr(crypto.settings, "encryption_key", "x" * 64)
|
||||
crypto._fernet_v2_cache.clear()
|
||||
with pytest.raises(RuntimeError):
|
||||
crypto.decrypt_api_key(blob)
|
||||
|
||||
|
||||
def test_malformed_v2_raises():
|
||||
with pytest.raises(RuntimeError):
|
||||
crypto.decrypt_api_key(crypto.ENC_PREFIX_V2 + "no-salt-separator")
|
||||
|
||||
|
||||
def test_plaintext_refused_in_production(monkeypatch):
|
||||
monkeypatch.setattr(crypto.settings, "environment", "production")
|
||||
with pytest.raises(RuntimeError):
|
||||
crypto.decrypt_api_key("raw-plaintext-key")
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Unit tests for kol_analysis._derive_tier — the non-Twitter tier derivation
|
||||
that lets blog/substack/podcast posts get the same trade_signal/directional/
|
||||
noise tiers (and SIGNAL/VIEW badges + "Signals only" filter) as Twitter posts.
|
||||
"""
|
||||
|
||||
from app.services.kol_analysis import _derive_tier
|
||||
|
||||
|
||||
def _tk(action="mention", conviction=0.0):
|
||||
return {"action": action, "conviction": conviction}
|
||||
|
||||
|
||||
def test_no_tickers_is_noise():
|
||||
assert _derive_tier([], 0.0) == "noise"
|
||||
|
||||
|
||||
def test_only_mentions_is_noise():
|
||||
assert _derive_tier([_tk("mention", 0.9)], 0.1) == "noise"
|
||||
|
||||
|
||||
def test_high_conviction_directional_is_trade_signal():
|
||||
assert _derive_tier([_tk("buy", 0.7)], 0.0) == "trade_signal"
|
||||
|
||||
|
||||
def test_strong_divergence_is_trade_signal_even_without_ticker():
|
||||
assert _derive_tier([], 0.8) == "trade_signal"
|
||||
|
||||
|
||||
def test_low_conviction_directional_is_directional():
|
||||
assert _derive_tier([_tk("bullish", 0.4)], 0.0) == "directional"
|
||||
|
||||
|
||||
def test_moderate_divergence_is_directional():
|
||||
assert _derive_tier([_tk("mention", 0.0)], 0.55) == "directional"
|
||||
|
||||
|
||||
def test_boundary_conviction_0_6_is_trade_signal():
|
||||
assert _derive_tier([_tk("sell", 0.6)], 0.0) == "trade_signal"
|
||||
@@ -97,10 +97,10 @@ async def test_open_positions_requires_signed_wallet_read(monkeypatch):
|
||||
monkeypatch.setattr(positions, "verify_signed_request_any", fake_verify)
|
||||
db = _Db([[]])
|
||||
|
||||
from app.services.signed_request import SignedReadCreds
|
||||
result = await positions.get_open_positions(
|
||||
wallet="0xABC",
|
||||
ts=123,
|
||||
sig="0xsig",
|
||||
creds=SignedReadCreds(ts=123, sig="0xsig"),
|
||||
db=db,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user