#include "vdm/net/probe.hpp" #include #include #include #include "testserver_fixture.hpp" #include "vtest.hpp" using namespace vdm; using namespace vdm::net; using vdm::testing::TestServer; namespace { Result run_probe(Prober &p, const std::string &url) { std::promise> prom; auto fut = prom.get_future(); ProbeRequest req; req.url = url; req.overall_timeout_ms = 15000; p.probe(std::move(req), [&](Result r) { prom.set_value(std::move(r)); }); if (fut.wait_for(std::chrono::seconds(25)) != std::future_status::ready) return Err{Error::timeout, "probe test wait"}; return fut.get(); } const std::string kEuro = "\xE2\x82\xAC"; } // namespace VT_TEST(probe_plain_resumable_file) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(4); auto r = run_probe(p, srv.url("/plain/file/2M")); VT_REQUIRE(r.has_value()); const auto &pr = r.value(); VT_CHECK_EQ(pr.http_status, 206); // proven via the ranged GET VT_CHECK(pr.accept_ranges); VT_CHECK(pr.resumable); // 206 + ETag validator VT_REQUIRE(pr.total_size.has_value()); VT_CHECK_EQ(*pr.total_size, 2u * 1024 * 1024); VT_CHECK(!pr.etag.empty()); VT_CHECK_EQ(pr.filename_from_url, std::string("2M")); VT_CHECK(!pr.requires_auth); } VT_TEST(probe_no_range_server_is_not_resumable) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(4); auto r = run_probe(p, srv.url("/no-range/file/1M")); VT_REQUIRE(r.has_value()); const auto &pr = r.value(); VT_CHECK(!pr.resumable); // no 206 ever VT_CHECK(!pr.accept_ranges); VT_REQUIRE(pr.total_size.has_value()); VT_CHECK_EQ(*pr.total_size, 1u * 1024 * 1024); } VT_TEST(probe_lying_accept_ranges_still_not_resumable) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(4); // advertises Accept-Ranges: bytes but never returns 206 auto r = run_probe(p, srv.url("/lies-about-accept-ranges/file/1M")); VT_REQUIRE(r.has_value()); const auto &pr = r.value(); VT_CHECK(pr.accept_ranges); // it advertised VT_CHECK(!pr.resumable); // ...but never proved it -> resume is off } VT_TEST(probe_reads_utf8_content_disposition) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(4); auto r = run_probe(p, srv.url("/utf8-content-disposition/file/8K")); VT_REQUIRE(r.has_value()); VT_CHECK_EQ(r.value().filename_from_disposition, kEuro + " rates.pdf"); VT_CHECK_EQ(suggest_filename(r.value()), kEuro + " rates.pdf"); } VT_TEST(probe_reads_legacy_content_disposition) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(4); auto r = run_probe(p, srv.url("/legacy-content-disposition/file/8K")); VT_REQUIRE(r.has_value()); VT_CHECK(!r.value().filename_from_disposition.empty()); // decoded, not the raw =?...?= VT_CHECK(r.value().filename_from_disposition.find("=?") == std::string::npos); } VT_TEST(probe_follows_redirect_and_reports_effective_url) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(4); auto r = run_probe(p, srv.url("/redirect-chain/file/64K")); VT_REQUIRE(r.has_value()); const auto &pr = r.value(); VT_CHECK(pr.effective_url != srv.url("/redirect-chain/file/64K")); VT_REQUIRE(pr.redirect_chain.size() >= 2); VT_CHECK_EQ(pr.redirect_chain.front(), srv.url("/redirect-chain/file/64K")); VT_CHECK_EQ(pr.redirect_chain.back(), pr.effective_url); } VT_TEST(probe_401_is_requires_auth_not_error) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(4); auto r = run_probe(p, srv.url("/401-basic/file/64K")); VT_REQUIRE(r.has_value()); // success result... VT_CHECK(r.value().requires_auth); // ...flagged for the credential dialog } VT_TEST(probe_404_is_an_error) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(4); auto r = run_probe(p, srv.url("/plain/nope")); VT_REQUIRE(!r.has_value()); VT_CHECK_EQ(r.error().code, Error::not_found); } VT_TEST(probe_dead_host_is_connect_failed) { Prober p(4); std::promise> prom; auto fut = prom.get_future(); ProbeRequest req; req.url = "http://127.0.0.1:1/x"; req.connect_timeout_ms = 2000; p.probe(std::move(req), [&](Result r) { prom.set_value(std::move(r)); }); VT_REQUIRE(fut.wait_for(std::chrono::seconds(10)) == std::future_status::ready); auto r = fut.get(); VT_REQUIRE(!r.has_value()); VT_CHECK_EQ(r.error().code, Error::connect_failed); } VT_TEST(probe_pool_serialises_excess_requests) { TestServer srv; VT_REQUIRE(srv.available()); Prober p(2); // only 2 at a time constexpr int kN = 8; std::vector>> futs; std::vector>> proms(kN); for (int i = 0; i < kN; ++i) { futs.push_back(proms[i].get_future()); ProbeRequest req; req.url = srv.url("/plain/file/4K"); p.probe(std::move(req), [pr = &proms[i]](Result r) { pr->set_value(std::move(r)); }); } for (auto &f : futs) { VT_REQUIRE(f.wait_for(std::chrono::seconds(30)) == std::future_status::ready); VT_CHECK(f.get().has_value()); } }