feat: chat
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
import pytest
|
||||
|
||||
from traderai.memory import MemoryStore
|
||||
from traderai.negotiations import NegotiationSyncService, extract_negotiation_hash
|
||||
from traderai.tools import ToolRegistry
|
||||
|
||||
|
||||
class FakeNegotiationUEX:
|
||||
def __init__(self):
|
||||
self.list_calls = []
|
||||
self.message_calls = []
|
||||
self.posts = []
|
||||
|
||||
async def list_negotiations(self, id=None, id_listing=None, hash=None):
|
||||
self.list_calls.append({"id": id, "id_listing": id_listing, "hash": hash})
|
||||
data = [
|
||||
{
|
||||
"id": 11,
|
||||
"hash": "open-hash",
|
||||
"id_listing": 101,
|
||||
"listing_slug": "rgl-open",
|
||||
"listing_title": "RGL Set",
|
||||
"advertiser_username": "seller_a",
|
||||
"client_username": "pilot_hudson",
|
||||
"date_modified": 1_780_975_053,
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"hash": "closed-recent",
|
||||
"id_listing": 102,
|
||||
"listing_slug": "rgl-closed",
|
||||
"listing_title": "Closed Deal",
|
||||
"advertiser_username": "seller_b",
|
||||
"client_username": "pilot_hudson",
|
||||
"date_modified": 1_780_975_053,
|
||||
"date_closed": 1_780_975_054,
|
||||
},
|
||||
]
|
||||
if hash:
|
||||
data = [item for item in data if item["hash"] == hash]
|
||||
return {"status": "ok", "negotiations": data}
|
||||
|
||||
async def get_negotiation_messages(self, hash=None, id_negotiation=None):
|
||||
self.message_calls.append({"hash": hash, "id_negotiation": id_negotiation})
|
||||
return {
|
||||
"status": "ok",
|
||||
"messages": [
|
||||
{
|
||||
"id": 201,
|
||||
"negotiation_hash": hash,
|
||||
"user_username": "seller_a" if hash == "open-hash" else "seller_b",
|
||||
"user_name": "Seller",
|
||||
"message": "Still available.",
|
||||
"date_added": 1_780_975_053,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
async def send_negotiation_message(self, **payload):
|
||||
self.posts.append({"kind": "message", **payload})
|
||||
return {"status": "ok", "posted": self.posts[-1]}
|
||||
|
||||
async def close_negotiation(self, **payload):
|
||||
self.posts.append({"kind": "close", **payload})
|
||||
return {"status": "ok", "posted": self.posts[-1]}
|
||||
|
||||
|
||||
def test_extract_negotiation_hash_handles_uex_redirects():
|
||||
assert extract_negotiation_hash("https://uexcorp.space/marketplace/negotiate/hash/abc-123") == "abc-123"
|
||||
assert extract_negotiation_hash("/marketplace/negotiate/hash/def-456") == "def-456"
|
||||
assert extract_negotiation_hash("/marketplace/item/info/foo") is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_startup_sync_keeps_open_and_recent_threads(tmp_path):
|
||||
memory = MemoryStore(str(tmp_path / "memory.sqlite3"))
|
||||
memory.set_profile("uex_user", {"username": "pilot_hudson"})
|
||||
service = NegotiationSyncService(memory, FakeNegotiationUEX())
|
||||
|
||||
result = await service.startup_sync()
|
||||
negotiations = memory.list_negotiations(limit=10)
|
||||
|
||||
assert result["count"] == 2
|
||||
assert {item["hash"] for item in negotiations} == {"open-hash", "closed-recent"}
|
||||
detail = memory.get_negotiation("open-hash")
|
||||
assert detail["messages"][0]["body"] == "Still available."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_notification_refresh_targets_only_changed_negotiation(tmp_path):
|
||||
memory = MemoryStore(str(tmp_path / "memory.sqlite3"))
|
||||
memory.set_profile("uex_user", {"username": "pilot_hudson"})
|
||||
fake = FakeNegotiationUEX()
|
||||
service = NegotiationSyncService(memory, fake)
|
||||
await service.startup_sync()
|
||||
fake.message_calls.clear()
|
||||
|
||||
await service.handle_notifications(
|
||||
[
|
||||
{
|
||||
"id": 99,
|
||||
"message": "seller_a: ping",
|
||||
"redir": "https://uexcorp.space/marketplace/negotiate/hash/open-hash",
|
||||
"date_added": 1_780_975_060,
|
||||
"date_read": 0,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
assert fake.message_calls == [{"hash": "open-hash", "id_negotiation": None}]
|
||||
assert memory.get_negotiation("open-hash")["unread_count"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_manual_send_refreshes_local_thread(tmp_path):
|
||||
memory = MemoryStore(str(tmp_path / "memory.sqlite3"))
|
||||
memory.set_profile("uex_user", {"username": "pilot_hudson"})
|
||||
fake = FakeNegotiationUEX()
|
||||
service = NegotiationSyncService(memory, fake)
|
||||
await service.startup_sync()
|
||||
|
||||
result = await service.manual_send_message("open-hash", "I can buy tonight.")
|
||||
|
||||
assert result["posted"]["kind"] == "message"
|
||||
assert fake.message_calls[-1]["hash"] == "open-hash"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_draft_negotiation_close_creates_pending_action(tmp_path):
|
||||
memory = MemoryStore(str(tmp_path / "memory.sqlite3"))
|
||||
registry = ToolRegistry(FakeNegotiationUEX(), memory=memory)
|
||||
|
||||
result = await registry.draft_negotiation_close(
|
||||
hash="open-hash",
|
||||
deal_closed=True,
|
||||
deal_value=1_000_000,
|
||||
currency="UEC",
|
||||
clarity_rating=5,
|
||||
speed_rating=5,
|
||||
respect_rating=5,
|
||||
fairness_rating=4,
|
||||
comment="Smooth trade",
|
||||
)
|
||||
|
||||
pending = result["pending_action"]
|
||||
assert pending["endpoint"] == "marketplace_negotiations_close"
|
||||
assert pending["payload"]["deal_closed"] == 1
|
||||
assert pending["payload"]["is_production"] == 1
|
||||
Reference in New Issue
Block a user