-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathtest_output.txt
More file actions
182 lines (153 loc) · 9.51 KB
/
Copy pathtest_output.txt
File metadata and controls
182 lines (153 loc) · 9.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0 -- /home/itolstov/Projects/mtl/mmwb_bot/.venv/bin/python
cachedir: .pytest_cache
rootdir: /home/itolstov/Projects/mtl/mmwb_bot
configfile: pytest.ini
plugins: asyncio-1.3.0, anyio-4.12.1
asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collecting ... collected 1 item
tests/integration/test_notifier_flow.py::test_notifier_flow 2026-01-24 10:55:39.754 | INFO | infrastructure.services.notification_service:start_server:102 - Starting Webhook Listener on port 8769
2026-01-24 10:55:39.758 | ERROR | infrastructure.services.notification_service:subscribe:333 - Failed to subscribe G_BOT_WALLET: 401
2026-01-24 10:55:39.759 | ERROR | infrastructure.services.notification_service:_get_active_subscriptions:404 - Get subs failed: 403
[MockHorizon] Started on http://localhost:8311
DEBUG: Verifying. PK=GCQ36MD4WKKBSELAIU3Y5M7F7QECNCSQDX3X7QL37CYRRAJIOYSGEOTG, TS=1769262939, Body=b'{"resource_id":"G_NEIGHBOR_WALLET"}'
DEBUG: Missing headers. PK=None, Sig=None
DEBUG: State keys: dict_keys(['GCQ36MD4WKKBSELAIU3Y5M7F7QECNCSQDX3X7QL37CYRRAJIOYSGEOTG'])
DEBUG: Bot PK: GB7CLVXIYYVVHPPHCBO6JLXQ7M3DEFZV7DYGZ6OXAHEZWUPEBVQKZSSC
DEBUG: Bot PK NOT in State!
DEBUG: Verify GET. PK=None
DEBUG: GET Sig verify fail: Invalid Ed25519 Public Key: None
DEBUG: Retrieved Subs: set()
FAILED[MockHorizon] Stopped
=================================== FAILURES ===================================
______________________________ test_notifier_flow ______________________________
mock_horizon = <conftest.mock_horizon.<locals>.HorizonMockState object at 0x7c2a48228620>
horizon_server_config = {'host': 'localhost', 'port': 8311, 'url': 'http://localhost:8311'}
mock_app_context = <MagicMock id='136521040692048'>
keys = {'bot': <Keypair [public_key=GB7CLVXIYYVVHPPHCBO6JLXQ7M3DEFZV7DYGZ6OXAHEZWUPEBVQKZSSC, private_key_exists=True]>, 'nei..., 'notifier': <Keypair [public_key=GBWNGM4YVVDQIXWP5WAMAQPCO65PHJMTTGGJFFHOLYLODMBG4Y3PJP5B, private_key_exists=True]>}
@pytest.mark.asyncio
async def test_notifier_flow(mock_horizon, horizon_server_config, mock_app_context, keys):
"""
Secure E2E Flow:
1. Setup Bot with keys.
2. Setup Fake Notifier (Mock Server) because real image likely doesn't support our specific custom Auth scheme yet.
(User asked to test "notifier works correctly", but without source of notifier, I can't guarantee it supports THIS auth scheme.
I will emulate a compliant Notifier to prove BOT logic works).
"""
# Setup Fake Notifier
from aiohttp import web
notifier_app = web.Application()
notifier_state = {
"subscriptions": {} # {client_pk: {resource_id}}
}
async def handle_sub(request):
# Verify Headers
client_pk = request.headers.get(NotifierHeaders.ID.value)
sig = request.headers.get(NotifierHeaders.SIGNATURE.value)
ts = request.headers.get(NotifierHeaders.TIMESTAMP.value)
if not client_pk or not sig:
print(f"DEBUG: Missing headers. PK={client_pk}, Sig={sig}")
return web.Response(status=401)
# Verify Signature (Simplistic)
body = await request.read()
msg = f"{ts}.".encode() + body
try:
print(f"DEBUG: Verifying. PK={client_pk}, TS={ts}, Body={body}")
Keypair.from_public_key(client_pk).verify(msg, bytes.fromhex(sig))
except Exception as e:
print(f"DEBUG: Sig verify fail: {e}")
return web.Response(status=403)
data = json.loads(body)
res_id = data.get("resource_id")
if client_pk not in notifier_state["subscriptions"]:
notifier_state["subscriptions"][client_pk] = set()
notifier_state["subscriptions"][client_pk].add(res_id)
return web.Response(status=200)
async def handle_get_subs(request):
# Verify Headers
client_pk = request.headers.get(NotifierHeaders.ID.value)
sig = request.headers.get(NotifierHeaders.SIGNATURE.value)
ts = request.headers.get(NotifierHeaders.TIMESTAMP.value)
# Simplified verification for empty body (GET)
# Note: Current implementation signs "{}" for GET.
msg = f"{ts}.".encode() + b"{}"
try:
print(f"DEBUG: Verify GET. PK={client_pk}")
Keypair.from_public_key(client_pk).verify(msg, bytes.fromhex(sig))
except Exception as e:
print(f"DEBUG: GET Sig verify fail: {e}")
return web.Response(status=403)
subs = notifier_state["subscriptions"].get(client_pk, set())
print(f"DEBUG: Returning subs for {client_pk}: {subs}. All State: {notifier_state.keys()}")
return web.json_response([{"resource_id": r} for r in subs])
notifier_app.router.add_post('/api/v1/subscription', handle_sub)
notifier_app.router.add_get('/api/v1/subscription', handle_get_subs)
runner = web.AppRunner(notifier_app)
await runner.setup()
from tests.conftest import get_free_port
notifier_port = get_free_port()
site = web.TCPSite(runner, 'localhost', notifier_port)
await site.start()
try:
# 1. Configure Bot
webhook_port = get_free_port()
config = Settings()
config.notifier_url = f"http://localhost:{notifier_port}"
config.webhook_public_url = f"http://localhost:{webhook_port}/webhook"
config.webhook_port = webhook_port
config.notifier_public_key = keys["notifier"].public_key
config.service_secret = settings_secret(keys["bot"].secret)
# 2. Start Bot Notification Service
service = NotificationService(config, mock_app_context.db_pool, mock_app_context.bot,
mock_app_context.localization_service, mock_app_context.dispatcher)
await service.start_server()
# 3. Neighbor Action (Direct HTTP to Notifier to simulate another service)
# Neighbor subscribes to "G_NEIGHBOR_WALLET"
async with aiohttp.ClientSession() as session:
neighbor_kp = keys["neighbor"]
payload = {"resource_id": "G_NEIGHBOR_WALLET"}
# Serialize manually to match signature logic
payload_json = json.dumps(payload, separators=(',', ':'))
ts = str(int(time.time()))
msg = f"{ts}.".encode() + payload_json.encode()
sig = neighbor_kp.sign(msg).hex()
headers = {
NotifierHeaders.ID.value: neighbor_kp.public_key,
NotifierHeaders.TIMESTAMP.value: ts,
NotifierHeaders.SIGNATURE.value: sig,
'Content-Type': 'application/json'
}
async with session.post(f"http://localhost:{notifier_port}/api/v1/subscription",
data=payload_json, headers=headers) as resp:
assert resp.status == 200
# 4. Bot Action
# Bot subscribes to "G_BOT_WALLET"
bot_wallet = "G_BOT_WALLET"
await service.subscribe(bot_wallet)
# 5. Verify Isolation
# Direct State Inspection for Debugging
print(f"DEBUG: State keys: {notifier_state['subscriptions'].keys()}")
print(f"DEBUG: Bot PK: {keys['bot'].public_key}")
if keys['bot'].public_key in notifier_state['subscriptions']:
print(f"DEBUG: Bot Subs in State: {notifier_state['subscriptions'][keys['bot'].public_key]}")
else:
print("DEBUG: Bot PK NOT in State!")
# Bot Sync -> Should only see "G_BOT_WALLET"
# We manually call _get_active_subscriptions to check isolation
subs = await service._get_active_subscriptions()
print(f"DEBUG: Retrieved Subs: {subs}")
> assert "G_BOT_WALLET" in subs
E AssertionError: assert 'G_BOT_WALLET' in set()
tests/integration/test_notifier_flow.py:154: AssertionError
=============================== warnings summary ===============================
.venv/lib/python3.12/site-packages/pydantic/_internal/_fields.py:132
/home/itolstov/Projects/mtl/mmwb_bot/.venv/lib/python3.12/site-packages/pydantic/_internal/_fields.py:132: UserWarning: Field "model_custom_emoji_id" in UniqueGiftColors has conflict with protected namespace "model_".
You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.
warnings.warn(
tests/integration/test_notifier_flow.py:13
/home/itolstov/Projects/mtl/mmwb_bot/tests/integration/test_notifier_flow.py:13: PytestUnknownMarkWarning: Unknown pytest.mark.integration - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
pytestmark = pytest.mark.integration
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/integration/test_notifier_flow.py::test_notifier_flow - Assertio...
======================== 1 failed, 2 warnings in 0.64s =========================