1// Offline tests of the FinData client logic (no network): chunking, rate limiting, offline refusal, IPC page decoding.2#include <catch2/catch_test_macros.hpp>3#include <catch2/matchers/catch_matchers_floating_point.hpp>4#include "raid/data/findata.hpp"5#include "raid/data/io.hpp"6#include "raid/core/error.hpp"7#include <chrono>8using namespace raid; using namespace raid::findata;910TEST_CASE("Date chunking respects the API caps", "[findata]") {11 auto c = chunk_dates(date_from_iso("2024-01-01"), date_from_iso("2024-03-15"), 31);12 REQUIRE(c.size() == 3); CHECK(c[0].first.iso() == "2024-01-01"); CHECK(c[0].second.iso() == "2024-01-31"); CHECK(c[1].first.iso() == "2024-02-01"); CHECK(c[2].second.iso() == "2024-03-15");13 CHECK(chunk_dates(date_from_iso("2024-01-01"), date_from_iso("2024-01-01"), 31).size() == 1);14 CHECK(max_days_per_call(Interval::m1) == 31); CHECK(max_days_per_call(Interval::m5) == 186); CHECK(max_days_per_call(Interval::d1) == 3650);15 CHECK_THROWS_AS(chunk_dates(date_from_iso("2024-02-01"), date_from_iso("2024-01-01"), 31), RaidError);16}17TEST_CASE("Token bucket enforces the rate", "[findata]") {18 TokenBucket b(50.0, 2.0); const auto t0 = std::chrono::steady_clock::now();19 for (int i = 0; i < 12; ++i) b.acquire();20 const double s = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();21 CHECK(s >= 0.18); CHECK(s < 1.0); // 12 requests, burst 2, 50/s → ≥ 0.2 s22}23TEST_CASE("Offline mode refuses network and missing keys are reported", "[findata]") {24 Config c; c.offline = true; c.api_key = ""; c.cache_dir = "/tmp/raid-test-cache"; Client cl(c);25 CHECK(cl.key_fingerprint() == "none"); CHECK_THROWS_AS(cl.health(), RaidError);26 Config c2; c2.api_key = ""; c2.offline = false; CHECK_THROWS_AS(Client(c2), RaidError);27}28#ifdef RAID_WITH_ARROW29TEST_CASE("Synthetic Arrow IPC page decodes with schema metadata", "[findata][arrow]") {30 auto pb = data::read_bars_arrow_ipc(std::string(RAID_SOURCE_DIR) + "/datasets/fixtures/synthetic_bars_page.arrow");31 REQUIRE(pb.series.count("SYN1")); CHECK(pb.metadata["findata.timestamp_convention"] == "bar_open"); CHECK(pb.rows_read == 391); CHECK(pb.series["SYN1"].n_sessions() == 1);32 auto pq = data::read_bars_parquet(std::string(RAID_SOURCE_DIR) + "/datasets/fixtures/synthetic_bars_page.parquet");33 CHECK(pq.rows_read == 391); CHECK(pq.series["SYN1"].close[0] == pb.series["SYN1"].close[0]);34}35#endif36