from contextlib import contextmanager from scrapers import lookintobitcoin from scoring import engine def test_hash_ribbon_signal_requires_a_current_truthy_marker(): named_but_inactive = {"name": "Buy Signal", "y": [1, None, None]} active = {"name": "Buy Signal", "y": [None, 0, 1]} assert lookintobitcoin._trace_signal_is_active(named_but_inactive) is False assert lookintobitcoin._trace_signal_is_active(active) is True def test_lth_supply_trace_selection_never_falls_back_to_price(): traces = [ {"name": "BTC Price", "y": [60000, 61000]}, {"name": "Long-Term Holder Supply", "y": [14_000_000, 14_100_000]}, ] assert lookintobitcoin._find_lth_supply_trace(traces)["name"] == "Long-Term Holder Supply" assert lookintobitcoin._find_lth_supply_trace(traces[:1]) is None def test_vdd_derived_return_is_labeled_as_momentum_not_raw_multiple(): result = engine.score_all({"vdd_multiple": {"value": 0.12}}) vdd = next(metric for metric in result["metrics"] if metric["key"] == "vdd_multiple") assert vdd["name"] == "VDD 30-Period Momentum" assert vdd["transform"] == "30_period_return" def test_scrape_all_reuses_one_browser_page(monkeypatch): page = object() seen_pages = [] @contextmanager def fake_browser_page(): yield page def fake_scrape_chart(_path, timeout=25000, page=None): seen_pages.append(page) return [{"name": "metric", "y": [1.0]}] monkeypatch.setattr(lookintobitcoin, "CHARTS", { "first": {"path": "/first", "traces": ["metric"]}, "second": {"path": "/second", "traces": ["metric"]}, }) monkeypatch.setattr(lookintobitcoin, "browser_page", fake_browser_page) monkeypatch.setattr(lookintobitcoin, "scrape_chart", fake_scrape_chart) result = lookintobitcoin.scrape_all() assert seen_pages == [page, page] assert result["first"]["value"] == 1.0 assert result["second"]["value"] == 1.0