"""ECR-009: page/API smoke without requiring live provider during assert.""" from __future__ import annotations import os import sys import pytest # Ensure repo root + web on path like app.py _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _WEB = os.path.join(_ROOT, "web") for p in (_ROOT, _WEB): if p not in sys.path: sys.path.insert(0, p) os.environ.setdefault("CRYPTO_WYCKOFF_DISABLE", "1") @pytest.fixture() def client(): from app import create_app app = create_app() app.config["TESTING"] = True with app.test_client() as c: yield c def test_wyckoff_crypto_page_ok(client): resp = client.get("/wyckoff_crypto") assert resp.status_code == 200 assert b"Crypto Wyckoff Screener" in resp.data assert b"fCombo" in resp.data assert b"chartCanvas" in resp.data def test_wyckoff_crypto_meta_ok(client): resp = client.get("/api/wyckoff_crypto/meta") assert resp.status_code == 200 data = resp.get_json() assert "engine_version" in data assert data.get("combo", {}).get("id") == "h8_4_1" assert data["combo"]["low"] == "1h" ids = {c["id"] for c in data.get("combos") or []} assert "h8_4_1" in ids and "d_w_m" in ids def test_wyckoff_crypto_scan_ok(client): resp = client.get("/api/wyckoff_crypto/scan?limit=5&combo_id=h8_4_1") assert resp.status_code == 200 data = resp.get_json() assert "rows" in data assert data.get("combo", {}).get("id") == "h8_4_1" def test_wyckoff_crypto_klines_bad_request(client): resp = client.get("/api/wyckoff_crypto/klines") assert resp.status_code == 400 def test_wyckoff_crypto_klines_ok(client): resp = client.get( "/api/wyckoff_crypto/klines?symbol=BTC/USDT:USDT&tf=1h&limit=10&combo_id=h8_4_1" ) assert resp.status_code == 200 data = resp.get_json() assert "items" in data assert data.get("tf") == "1h" assert data.get("intraday") is True if data["items"]: assert "datetime" in data["items"][0] assert "ts" in data["items"][0] assert "T" in data["items"][0]["datetime"] assert "+08:00" in data["items"][0]["datetime"] def test_wyckoff_crypto_klines_bad_limit_ok(client): resp = client.get( "/api/wyckoff_crypto/klines?symbol=BTC/USDT:USDT&tf=1h&limit=abc&combo_id=h8_4_1" ) assert resp.status_code == 200 def test_wyckoff_crypto_overlay_ok(client): resp = client.get( "/api/wyckoff_crypto/overlay?symbol=BTC/USDT:USDT&tf=1h&bars=60&combo_id=h8_4_1" ) assert resp.status_code == 200 data = resp.get_json() assert "phases" in data assert "events" in data def test_combos_add_and_list(client, tmp_path, monkeypatch): from crypto_wyckoff import combos as cm monkeypatch.setattr(cm, "_COMBOS_FILE", tmp_path / "combos.json") monkeypatch.setattr(cm, "_cache", None) resp = client.get("/api/wyckoff_crypto/combos") assert resp.status_code == 200 assert len(resp.get_json()["combos"]) >= 2 bad = client.post( "/api/wyckoff_crypto/combos", json={"high": "1h", "mid": "4h", "low": "8h"}, ) assert bad.status_code == 400 ok = client.post( "/api/wyckoff_crypto/combos", json={"high": "12h", "mid": "4h", "low": "1h", "label": "12h/4h/1h"}, ) assert ok.status_code == 200 cid = ok.get_json()["combo"]["id"] assert cid == "12h_4h_1h" deleted = client.delete(f"/api/wyckoff_crypto/combos/{cid}") assert deleted.status_code == 200 builtin = client.delete("/api/wyckoff_crypto/combos/h8_4_1") assert builtin.status_code == 400