yams/tests/unit/search/hotzone_manager_catch2_test.cpp
trvon 77cab7f6e9
fix(ci): stabilize PR checks
Signed-off-by: trvon <me@trevon.dev>
2026-05-21 16:18:08 -04:00

118 lines
3.7 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright 2026 YAMS Contributors
#include <catch2/catch_test_macros.hpp>
#include <yams/search/hotzone_manager.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <string>
namespace {
std::filesystem::path makeTempPath(const std::string& suffix) {
const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
return std::filesystem::temp_directory_path() /
("yams-hotzone-" + std::to_string(stamp) + suffix);
}
} // namespace
TEST_CASE("HotzoneManager records boosts and ignores invalid input",
"[unit][search][hotzone][catch2]") {
yams::search::HotzoneConfig cfg;
cfg.max_boost_factor = 1.5;
yams::search::HotzoneManager manager(cfg);
CHECK(manager.size() == 0);
manager.record("", 2.0);
manager.record("doc-a", 0.0);
manager.record("doc-a", -1.0);
CHECK(manager.size() == 0);
manager.record("doc-a", 1.0);
CHECK(manager.size() == 1);
CHECK(manager.getBoost("") == 1.0);
CHECK(manager.getBoost("missing") == 1.0);
const double boost = manager.getBoost("doc-a");
CHECK(boost > 1.0);
CHECK(boost <= cfg.max_boost_factor);
yams::search::HotzoneConfig mergeCfg;
mergeCfg.max_boost_factor = 3.0;
yams::search::HotzoneManager merging(mergeCfg);
merging.record("doc-b", 0.25);
const double firstBoost = merging.getBoost("doc-b");
merging.record("doc-b", 0.25);
CHECK(merging.getBoost("doc-b") > firstBoost);
}
TEST_CASE("HotzoneManager decays entries and supports immediate decay",
"[unit][search][hotzone][catch2]") {
yams::search::HotzoneConfig cfg;
cfg.half_life_hours = 1.0;
yams::search::HotzoneManager manager(cfg);
manager.record("doc-a", 1.0);
const double initial = manager.getBoost("doc-a");
const auto decayAt = std::chrono::system_clock::now() + std::chrono::hours(2);
manager.decaySweep(decayAt);
CHECK(manager.getBoost("doc-a", decayAt) < initial);
cfg.half_life_hours = 0.0;
manager.setConfig(cfg);
manager.decaySweep(decayAt + std::chrono::hours(1));
CHECK(manager.size() == 0);
}
TEST_CASE("HotzoneManager saves, loads, and rejects invalid files",
"[unit][search][hotzone][catch2]") {
const auto path = makeTempPath(".json");
const auto missingPath = makeTempPath("-missing.json");
const auto invalidPath = makeTempPath("-invalid.json");
const auto invalidSchemaPath = makeTempPath("-invalid-schema.json");
const auto partialEntriesPath = makeTempPath("-partial-entries.json");
yams::search::HotzoneManager empty;
CHECK(empty.save(path));
CHECK_FALSE(std::filesystem::exists(path));
yams::search::HotzoneManager manager;
manager.record("doc-a", 1.0);
REQUIRE(manager.save(path));
REQUIRE(std::filesystem::exists(path));
yams::search::HotzoneManager loaded;
REQUIRE(loaded.load(path));
CHECK(loaded.size() == 1);
CHECK(loaded.getBoost("doc-a") > 1.0);
CHECK_FALSE(loaded.load(missingPath));
{
std::ofstream ofs(invalidPath);
ofs << "not-json";
}
CHECK_FALSE(loaded.load(invalidPath));
{
std::ofstream ofs(invalidSchemaPath);
ofs << R"({"version":1})";
}
CHECK_FALSE(loaded.load(invalidSchemaPath));
{
std::ofstream ofs(partialEntriesPath);
ofs << R"({"version":1,"entries":[{"key":"bad"},{"key":"doc-b","score":1.0,"updated_ms":0}]})";
}
REQUIRE(loaded.load(partialEntriesPath));
CHECK(loaded.size() == 1);
std::filesystem::remove(path);
std::filesystem::remove(invalidPath);
std::filesystem::remove(invalidSchemaPath);
std::filesystem::remove(partialEntriesPath);
}