# -*- coding: utf-8 -*- """Local mock test for subscription endpoints (subscribe/unsubscribe/tick).""" import sys, io, os, json, time, threading, urllib.request, urllib.error if sys.stdout is None: sys.stdout = io.StringIO() if sys.stderr is None: sys.stderr = io.StringIO() RESULT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sub_test.txt") SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src") sys.path.insert(0, SRC) def log(msg): with open(RESULT, "a") as f: f.write(msg + "\n") import bridge_util, bridge_http_server, bridge_ws_server, bridge_subscription # ---- Fake QMT ContextInfo ---- class FakeCtx: def __init__(self): self.subs = {} # handle -> codes self._next_handle = 1 self.pushed = [] # records of (handle, data) pushed def subscribe_whole_quote(self, codes, callback=None): handle = self._next_handle self._next_handle += 1 self.subs[handle] = list(codes) # remember callback so tests can trigger pushes self.callback = callback return handle def unsubscribe_quote(self, handle): self.subs.pop(handle, None) def get_full_tick(self, codes): return {c: {"lastPrice": 10.0, "volume": 100} for c in codes} fake = FakeCtx() bridge_util.CTX = fake bridge_util.ACCOUNT = "TEST" bridge_http_server.PORT = 18690 bridge_http_server.TOKEN = "" bridge_http_server.start_server() time.sleep(0.6) BASE = "http://127.0.0.1:18690" def post(path, obj): data = json.dumps(obj).encode("utf-8") req = urllib.request.Request(BASE + path, data=data, method="POST", headers={"Content-Type": "application/json"}) try: with urllib.request.urlopen(req, timeout=5) as r: return r.status, json.loads(r.read().decode()) except urllib.error.HTTPError as e: try: return e.code, json.loads(e.read().decode()) except Exception: return e.code, None def get(path): try: with urllib.request.urlopen(BASE + path, timeout=5) as r: return r.status, json.loads(r.read().decode()) except urllib.error.HTTPError as e: try: return e.code, json.loads(e.read().decode()) except Exception: return e.code, None # ---- Tests ---- # 1. subscribe whole st, body = post("/data/subscribe", {"type": "whole", "codes": ["SH", "SZ"]}) log("1. subscribe whole -> HTTP %d body=%s" % (st, json.dumps(body, ensure_ascii=False))) sub_id = body.get("sub_id") if body else None assert st == 200 and sub_id is not None, "subscribe failed" assert fake.subs, "QMT subscribe not created" log(" QMT subs: %s" % fake.subs) # 2. simulate QMT push -> should broadcast via ws (no ws client, no crash) if fake.callback: fake.callback({"600000.SH": {"lastPrice": 10.1}}) log("2. simulated QMT push OK (no crash)") # 3. subscribe invalid type st, body = post("/data/subscribe", {"type": "bogus", "codes": ["SH"]}) log("3. subscribe bogus type -> HTTP %d body=%s" % (st, json.dumps(body, ensure_ascii=False))) assert st == 400, "bogus type should be 400" # 4. subscribe missing codes st, body = post("/data/subscribe", {"type": "whole"}) log("4. subscribe no codes -> HTTP %d body=%s" % (st, json.dumps(body, ensure_ascii=False))) assert st == 400, "missing codes should be 400" # 5. unsubscribe st, body = post("/data/unsubscribe", {"sub_id": sub_id}) log("5. unsubscribe -> HTTP %d body=%s" % (st, json.dumps(body, ensure_ascii=False))) assert st == 200, "unsubscribe failed" assert not fake.subs, "QMT sub should be removed" # 6. unsubscribe unknown st, body = post("/data/unsubscribe", {"sub_id": 999999}) log("6. unsubscribe unknown -> HTTP %d body=%s" % (st, json.dumps(body, ensure_ascii=False))) assert st == 404, "unknown sub should be 404" # 7. GET /data/tick st, body = get("/data/tick?codes=600000.SH,000001.SZ") log("7. tick -> HTTP %d body=%s" % (st, json.dumps(body, ensure_ascii=False))) assert st == 200 and "600000.SH" in body.get("data", {}), "tick failed" # 8. tick missing codes st, body = get("/data/tick") log("8. tick no codes -> HTTP %d body=%s" % (st, json.dumps(body, ensure_ascii=False))) assert st == 400, "tick no codes should be 400" bridge_http_server.stop_server() log("=== done ===")