194 lines
6.2 KiB
Python
194 lines
6.2 KiB
Python
import pytest
|
|
|
|
from traderai.agent import OllamaAgent
|
|
from traderai.memory import MemoryStore
|
|
from traderai.scheduler import WakeScheduler
|
|
|
|
|
|
class FakeUEXNotifications:
|
|
def __init__(self):
|
|
self.calls = 0
|
|
|
|
async def get_user_notifications(self):
|
|
self.calls += 1
|
|
return {
|
|
"status": "ok",
|
|
"notifications": [
|
|
{
|
|
"id": 10,
|
|
"message": "A buyer replied to your listing.",
|
|
"redir": "/marketplace/negotiations/abc",
|
|
"code": "negotiation_reply",
|
|
"date_added": 123,
|
|
"date_read": 0,
|
|
},
|
|
{
|
|
"id": 11,
|
|
"message": "Already read.",
|
|
"date_added": 122,
|
|
"date_read": 123,
|
|
},
|
|
],
|
|
}
|
|
|
|
|
|
class FailingUEXNotifications:
|
|
async def get_user_notifications(self):
|
|
raise RuntimeError("bad token")
|
|
|
|
|
|
class FakeWakeAgent:
|
|
async def generate_wake_response(self, wake_message):
|
|
return f"Wake output: {wake_message}"
|
|
|
|
|
|
class ListingWakeTools:
|
|
schemas = [
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "search_marketplace_listings",
|
|
"description": "Search active UEX marketplace listings.",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
},
|
|
}
|
|
]
|
|
|
|
def __init__(self):
|
|
self.calls = []
|
|
self.pending_actions = {}
|
|
|
|
async def execute(self, name, arguments):
|
|
self.calls.append((name, arguments))
|
|
return {
|
|
"count": 2,
|
|
"listings": [
|
|
{
|
|
"id": 100,
|
|
"title": "Wikelo Favor",
|
|
"operation": "sell",
|
|
"price": 500_000_000,
|
|
"currency": "UEC",
|
|
"in_stock": 9,
|
|
"advertiser": "pilot_a",
|
|
},
|
|
{
|
|
"id": 101,
|
|
"title": "Wikelo Favor stack",
|
|
"operation": "sell",
|
|
"price": 1_000_000_000,
|
|
"currency": "UEC",
|
|
"in_stock": 5,
|
|
"advertiser": "pilot_b",
|
|
},
|
|
],
|
|
}
|
|
|
|
|
|
class ListingWakeAgent(OllamaAgent):
|
|
def __init__(self, memory):
|
|
self.listing_tools = ListingWakeTools()
|
|
super().__init__("http://127.0.0.1:1", "missing-model", self.listing_tools, memory=memory)
|
|
self.responses = [
|
|
{
|
|
"message": {
|
|
"role": "assistant",
|
|
"content": "",
|
|
"tool_calls": [
|
|
{
|
|
"function": {
|
|
"name": "search_marketplace_listings",
|
|
"arguments": {
|
|
"query": "Wikelo Favor",
|
|
"operation": "sell",
|
|
"limit": 5,
|
|
},
|
|
}
|
|
}
|
|
],
|
|
}
|
|
},
|
|
{
|
|
"message": {
|
|
"role": "assistant",
|
|
"content": (
|
|
"Listing check complete: found 2 active Wikelo Favor sell listings. "
|
|
"Cheapest listing is 500,000,000 UEC with 9 in stock; the next listing is "
|
|
"1,000,000,000 UEC. Suggested next action: price near 500,000,000 UEC "
|
|
"if you want to move yours quickly."
|
|
),
|
|
}
|
|
},
|
|
]
|
|
|
|
async def ensure_available(self):
|
|
return None
|
|
|
|
async def _ollama_chat(self, *args, **kwargs):
|
|
return self.responses.pop(0)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_poll_uex_notifications_adds_unread_once(tmp_path):
|
|
memory = MemoryStore(str(tmp_path / "memory.sqlite3"))
|
|
scheduler = WakeScheduler(memory)
|
|
scheduler.bind_uex_notifications(FakeUEXNotifications())
|
|
|
|
first = await scheduler.poll_uex_notifications()
|
|
second = await scheduler.poll_uex_notifications()
|
|
outbox = memory.inspect()["outbox"]
|
|
|
|
assert len(first) == 1
|
|
assert second == []
|
|
assert len(outbox) == 1
|
|
assert "A buyer replied to your listing." in outbox[0]["content"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_poll_uex_notifications_reports_failures_to_outbox(tmp_path):
|
|
memory = MemoryStore(str(tmp_path / "memory.sqlite3"))
|
|
scheduler = WakeScheduler(memory)
|
|
scheduler.bind_uex_notifications(FailingUEXNotifications())
|
|
|
|
result = await scheduler.poll_uex_notifications()
|
|
|
|
assert result == []
|
|
assert "bad token" in memory.inspect()["outbox"][0]["content"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wake_job_writes_agent_output_to_outbox_and_disables_one_shot(tmp_path):
|
|
memory = MemoryStore(str(tmp_path / "memory.sqlite3"))
|
|
scheduler = WakeScheduler(memory)
|
|
scheduler.bind_agent(FakeWakeAgent())
|
|
memory.add_job("wake-test", "check notifications", "date", "2099-01-01T00:00:00+00:00")
|
|
|
|
await scheduler._run_job("wake-test", "check notifications")
|
|
snapshot = memory.inspect()
|
|
|
|
assert "Wake output:" in snapshot["outbox"][0]["content"]
|
|
assert snapshot["scheduled_jobs"][0]["enabled"] == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wake_job_checks_listings_and_writes_analysis_to_outbox(tmp_path):
|
|
memory = MemoryStore(str(tmp_path / "memory.sqlite3"))
|
|
scheduler = WakeScheduler(memory)
|
|
agent = ListingWakeAgent(memory)
|
|
scheduler.bind_agent(agent)
|
|
memory.add_job("wake-listings", "check Wikelo Favor listings and analyze the market", "date", "2099-01-01T00:00:00+00:00")
|
|
|
|
await scheduler._run_job("wake-listings", "check Wikelo Favor listings and analyze the market")
|
|
snapshot = memory.inspect()
|
|
content = snapshot["outbox"][0]["content"]
|
|
|
|
assert agent.listing_tools.calls == [
|
|
(
|
|
"search_marketplace_listings",
|
|
{"query": "Wikelo Favor", "operation": "sell", "limit": 5},
|
|
)
|
|
]
|
|
assert "Listing check complete" in content
|
|
assert "500,000,000 UEC" in content
|
|
assert "Suggested next action" in content
|