command-center-chat / test_robinhood.py
Simzy's picture
Deploy PR #13: Robinhood refresh wait for newer snapshot (11e93ece)
2b25951 verified
Raw History Blame Contribute Delete
11.1 kB
import json
import os
import unittest
import uuid
from fastapi.testclient import TestClient
from main import RATE, app, refresh_file, snapshot_file
SAMPLE = {
"updatedAt": "2026-09-28T15:04:00Z",
"account": {"label": "", "last4": "999988886740", "accountNumber": "1234999988886740"},
"totalValue": 118532.25,
"equityValue": 175464.27,
"cryptoValue": 26130.89,
"cash": -83062.91,
"currency": "USD",
"positions": [
{
"symbol": "pltr",
"quantity": 149.998,
"avgCost": 142.02,
"price": 0,
"marketValue": 0,
"dayChangePct": None,
}
],
}
class RobinhoodBridgeTest(unittest.TestCase):
def setUp(self) -> None:
self._env = {
"ROBINHOOD_BRIDGE_SECRET": os.environ.get("ROBINHOOD_BRIDGE_SECRET"),
"CHAT_BRIDGE_SECRET": os.environ.get("CHAT_BRIDGE_SECRET"),
}
os.environ.pop("ROBINHOOD_BRIDGE_SECRET", None)
os.environ["CHAT_BRIDGE_SECRET"] = "chat-secret-value"
RATE.clear()
self._wipe_files()
self.client = TestClient(app)
def tearDown(self) -> None:
self.client.close()
self._wipe_files()
for key, value in self._env.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
def _wipe_files(self) -> None:
for path in (snapshot_file(), refresh_file()):
if path.exists():
path.unlink()
tmp = path.with_suffix(path.suffix + ".tmp")
if tmp.exists():
tmp.unlink()
def test_get_empty_snapshot_is_public(self) -> None:
res = self.client.get("/api/robinhood")
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json(), {"snapshot": None, "refreshPending": False})
self.assertEqual(res.headers.get("cache-control"), "no-store")
def test_post_requires_bearer_and_masks_account(self) -> None:
denied = self.client.post("/api/robinhood", json=SAMPLE)
self.assertEqual(denied.status_code, 401)
wrong = self.client.post(
"/api/robinhood",
json=SAMPLE,
headers={"Authorization": "Bearer nope"},
)
self.assertEqual(wrong.status_code, 401)
ok = self.client.post(
"/api/robinhood",
json=SAMPLE,
headers={"Authorization": "Bearer chat-secret-value"},
)
self.assertEqual(ok.status_code, 200)
self.assertEqual(ok.json()["updatedAt"], "2026-09-28T15:04:00Z")
got = self.client.get("/api/robinhood")
snapshot = got.json()["snapshot"]
self.assertEqual(snapshot["account"], {"label": "Individual", "last4": "6740"})
self.assertNotIn("accountNumber", snapshot["account"])
self.assertNotIn("1234999988886740", got.text)
self.assertEqual(snapshot["totalValue"], 118532.25)
self.assertEqual(snapshot["positions"][0]["symbol"], "PLTR")
self.assertEqual(snapshot["positions"][0]["price"], 0)
def test_post_replaces_snapshot(self) -> None:
headers = {"Authorization": "Bearer chat-secret-value"}
self.client.post("/api/robinhood", json=SAMPLE, headers=headers)
replaced = dict(SAMPLE)
replaced["totalValue"] = 10
replaced["positions"] = []
res = self.client.post("/api/robinhood", json=replaced, headers=headers)
self.assertEqual(res.status_code, 200)
snapshot = self.client.get("/api/robinhood").json()["snapshot"]
self.assertEqual(snapshot["totalValue"], 10)
self.assertEqual(snapshot["positions"], [])
def test_dedicated_secret_replaces_chat_secret(self) -> None:
os.environ["ROBINHOOD_BRIDGE_SECRET"] = "rh-secret-value"
chat = self.client.post(
"/api/robinhood",
json=SAMPLE,
headers={"Authorization": "Bearer chat-secret-value"},
)
self.assertEqual(chat.status_code, 401)
ok = self.client.post(
"/api/robinhood",
json=SAMPLE,
headers={"Authorization": "Bearer rh-secret-value"},
)
self.assertEqual(ok.status_code, 200)
def test_missing_secret_is_not_open(self) -> None:
os.environ.pop("ROBINHOOD_BRIDGE_SECRET", None)
os.environ.pop("CHAT_BRIDGE_SECRET", None)
res = self.client.post("/api/robinhood", json=SAMPLE, headers={"Authorization": "Bearer anything"})
self.assertEqual(res.status_code, 500)
def test_chat_reply_still_uses_chat_secret(self) -> None:
os.environ["ROBINHOOD_BRIDGE_SECRET"] = "rh-secret-value"
body = {
"sessionId": str(uuid.UUID("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")),
"clientMsgId": "cmt_test",
"botId": "chief",
"text": "ok",
"status": "final",
}
denied = self.client.post(
"/api/chat/reply",
json=body,
headers={"Authorization": "Bearer rh-secret-value"},
)
self.assertEqual(denied.status_code, 401)
ok = self.client.post(
"/api/chat/reply",
json=body,
headers={"Authorization": "Bearer chat-secret-value"},
)
self.assertEqual(ok.status_code, 200)
polled = self.client.get(f"/api/chat?sessionId={body['sessionId']}")
self.assertEqual(polled.status_code, 200)
self.assertEqual(polled.json()["messages"][-1]["text"], "ok")
def test_refresh_request_is_public_and_rate_limited_per_ip(self) -> None:
os.environ.pop("ROBINHOOD_BRIDGE_SECRET", None)
os.environ.pop("CHAT_BRIDGE_SECRET", None)
first = self.client.post("/api/robinhood/refresh", headers={"X-Forwarded-For": "203.0.113.10"})
self.assertEqual(first.status_code, 200)
body = first.json()
self.assertTrue(body["ok"])
self.assertTrue(body["refreshPending"])
self.assertRegex(body["requestedAt"], r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$")
self.assertEqual(first.headers.get("cache-control"), "no-store")
stored = json.loads(refresh_file().read_text(encoding="utf-8"))
self.assertEqual(stored, {"requestedAt": body["requestedAt"]})
again = self.client.post("/api/robinhood/refresh", headers={"X-Forwarded-For": "203.0.113.10"})
self.assertEqual(again.status_code, 429)
other = self.client.post("/robinhood/refresh", headers={"X-Forwarded-For": "203.0.113.11"})
self.assertEqual(other.status_code, 200)
def test_refresh_pending_until_snapshot_is_newer(self) -> None:
headers = {"Authorization": "Bearer chat-secret-value"}
old = dict(SAMPLE)
old["updatedAt"] = "2020-01-01T00:00:00Z"
self.client.post("/api/robinhood", json=old, headers=headers)
asked = self.client.post("/api/robinhood/refresh", headers={"X-Forwarded-For": "203.0.113.12"})
requested_at = asked.json()["requestedAt"]
got = self.client.get("/api/robinhood")
body = got.json()
self.assertEqual(got.headers.get("cache-control"), "no-store")
self.assertTrue(body["refreshPending"])
self.assertEqual(body["refreshRequestedAt"], requested_at)
self.assertEqual(body["snapshot"]["updatedAt"], "2020-01-01T00:00:00Z")
pending = self.client.get("/api/robinhood/pending-refresh")
self.assertEqual(pending.status_code, 200)
self.assertEqual(
pending.json(),
{"refreshPending": True, "refreshRequestedAt": requested_at},
)
alias = self.client.get("/robinhood/pending-refresh")
self.assertEqual(alias.json()["refreshPending"], True)
def test_pending_flag_is_false_when_snapshot_is_already_newer(self) -> None:
headers = {"Authorization": "Bearer chat-secret-value"}
future = dict(SAMPLE)
future["updatedAt"] = "2099-01-01T00:00:00Z"
self.client.post("/api/robinhood", json=future, headers=headers)
self.client.post("/api/robinhood/refresh", headers={"X-Forwarded-For": "203.0.113.13"})
body = self.client.get("/api/robinhood").json()
self.assertFalse(body["refreshPending"])
self.assertNotIn("refreshRequestedAt", body)
self.assertTrue(refresh_file().is_file())
self.assertEqual(self.client.get("/api/robinhood/pending-refresh").json(), {"refreshPending": False})
def test_snapshot_post_clears_pending_refresh(self) -> None:
self.client.post("/api/robinhood/refresh", headers={"X-Forwarded-For": "203.0.113.14"})
self.assertTrue(refresh_file().is_file())
denied = self.client.post("/api/robinhood", json=SAMPLE)
self.assertEqual(denied.status_code, 401)
self.assertTrue(self.client.get("/api/robinhood").json()["refreshPending"])
res = self.client.post(
"/api/robinhood",
json=SAMPLE,
headers={"Authorization": "Bearer chat-secret-value"},
)
self.assertEqual(res.status_code, 200)
self.assertFalse(refresh_file().exists())
body = self.client.get("/api/robinhood").json()
self.assertFalse(body["refreshPending"])
self.assertNotIn("refreshRequestedAt", body)
self.assertEqual(self.client.get("/robinhood/pending-refresh").json(), {"refreshPending": False})
def test_delete_refresh_requires_snapshot_bearer(self) -> None:
self.client.post("/api/robinhood/refresh", headers={"X-Forwarded-For": "203.0.113.15"})
denied = self.client.delete("/api/robinhood/refresh")
self.assertEqual(denied.status_code, 401)
self.assertTrue(refresh_file().is_file())
os.environ["ROBINHOOD_BRIDGE_SECRET"] = "rh-secret-value"
chat = self.client.delete(
"/api/robinhood/refresh",
headers={"Authorization": "Bearer chat-secret-value"},
)
self.assertEqual(chat.status_code, 401)
self.assertTrue(refresh_file().is_file())
ok = self.client.delete(
"/robinhood/refresh",
headers={"Authorization": "Bearer rh-secret-value"},
)
self.assertEqual(ok.status_code, 200)
self.assertEqual(ok.json(), {"ok": True, "refreshPending": False})
self.assertFalse(refresh_file().exists())
self.assertFalse(self.client.get("/api/robinhood").json()["refreshPending"])
def test_refresh_cors_allows_phone_origin(self) -> None:
res = self.client.options(
"/api/robinhood/refresh",
headers={
"Origin": "https://simzy420.github.io",
"Access-Control-Request-Method": "POST",
},
)
self.assertIn(res.status_code, (200, 204))
self.assertEqual(res.headers.get("access-control-allow-origin"), "https://simzy420.github.io")
allow = res.headers.get("access-control-allow-methods", "")
self.assertIn("POST", allow)
self.assertIn("DELETE", allow)
if __name__ == "__main__":
unittest.main()