yams/tests/integration/smoke/integrity_auto_repair_smoke_catch2_test.cpp

247 lines
8.8 KiB
C++

#include <CLI/CLI.hpp>
#include <catch2/catch_test_macros.hpp>
#include <yams/crypto/hasher.h>
#include <yams/integrity/repair_manager.h>
#include <yams/integrity/verifier.h>
#include <yams/storage/reference_counter.h>
#include <yams/storage/storage_engine.h>
#include <cstddef>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <span>
#include <string>
#include <string_view>
#include <vector>
using namespace yams;
using namespace yams::integrity;
namespace {
std::vector<std::byte> makeBytes(std::string_view payload) {
std::vector<std::byte> bytes(payload.size());
std::memcpy(bytes.data(), payload.data(), payload.size());
return bytes;
}
} // namespace
TEST_CASE("IntegrationSmoke.IntegrityAutoRepair_FromBackupDir", "[smoke][integrationsmoke]") {
namespace fs = std::filesystem;
// Prepare a temp working area
fs::path temp = fs::temp_directory_path() / "yams-int-repair-smoke";
std::error_code ec;
fs::remove_all(temp, ec);
fs::create_directories(temp / "objects", ec);
fs::create_directories(temp / "backup", ec);
// Create storage engine
storage::StorageEngine storage({.basePath = temp / "objects"});
// Create ref counter
storage::ReferenceCounter::Config rcfg;
rcfg.databasePath = temp / "refs.db";
auto refCounter = storage::createReferenceCounter(rcfg);
// Prepare a block payload and its hash
const std::string payload = "auto-repair";
std::vector<std::byte> bytes = makeBytes(payload);
auto hasher = crypto::createSHA256Hasher();
hasher->init();
hasher->update(std::span<const std::byte>(bytes.data(), bytes.size()));
auto hash = hasher->finalize();
// Simulate that primary storage is missing the block, but backup dir has it
{
std::ofstream out(temp / "backup" / hash, std::ios::binary | std::ios::trunc);
out.write(payload.data(), static_cast<std::streamsize>(payload.size()));
}
// Build verifier with auto repair
VerificationConfig vcfg;
vcfg.enableAutoRepair = true;
vcfg.maxRepairAttempts = 1;
IntegrityVerifier verifier(storage, static_cast<storage::ReferenceCounter&>(*refCounter), vcfg);
// Wire RepairManager with a backup fetcher to our backup dir
RepairManagerConfig rmCfg;
rmCfg.backupFetcher = [&](const std::string& requested) -> Result<std::vector<std::byte>> {
fs::path p = temp / "backup" / requested;
if (!fs::exists(p))
return Error{ErrorCode::NotFound, "no backup"};
std::ifstream in(p, std::ios::binary);
std::vector<char> buf((std::istreambuf_iterator<char>(in)), {});
std::vector<std::byte> out(buf.size());
std::memcpy(out.data(), buf.data(), buf.size());
return out;
};
auto rm = makeRepairManager(storage, rmCfg);
verifier.setRepairManager(rm);
// Verify the block: should be repaired
auto result = verifier.verifyBlock(hash);
CHECK(result.status == VerificationStatus::Repaired);
// Confirm block is now stored
auto retrieved = storage.retrieve(hash);
REQUIRE(retrieved.has_value());
std::string restored(reinterpret_cast<const char*>(retrieved.value().data()),
retrieved.value().size());
CHECK(restored == payload);
fs::remove_all(temp, ec);
}
TEST_CASE("IntegrationSmoke.IntegrityAutoRepair_FromP2PDir", "[smoke][integrationsmoke]") {
namespace fs = std::filesystem;
fs::path temp = fs::temp_directory_path() / "yams-int-repair-p2p-smoke";
std::error_code ec;
fs::remove_all(temp, ec);
fs::create_directories(temp / "objects", ec);
fs::create_directories(temp / "p2p", ec);
storage::StorageEngine storage({.basePath = temp / "objects"});
storage::ReferenceCounter::Config rcfg;
rcfg.databasePath = temp / "refs.db";
auto refCounter = storage::createReferenceCounter(rcfg);
const std::string payload = "auto-repair-p2p";
std::vector<std::byte> bytes = makeBytes(payload);
auto hasher = crypto::createSHA256Hasher();
hasher->init();
hasher->update(std::span<const std::byte>(bytes.data(), bytes.size()));
auto hash = hasher->finalize();
{
std::ofstream out(temp / "p2p" / hash, std::ios::binary | std::ios::trunc);
out.write(payload.data(), static_cast<std::streamsize>(payload.size()));
}
VerificationConfig vcfg;
vcfg.enableAutoRepair = true;
IntegrityVerifier verifier(storage, static_cast<storage::ReferenceCounter&>(*refCounter), vcfg);
RepairManagerConfig rmCfg;
rmCfg.p2pFetcher = [&](const std::string& requested) -> Result<std::vector<std::byte>> {
fs::path p = temp / "p2p" / requested;
if (!fs::exists(p))
return Error{ErrorCode::NotFound, "no p2p"};
std::ifstream in(p, std::ios::binary);
std::vector<char> buf((std::istreambuf_iterator<char>(in)), {});
std::vector<std::byte> out(buf.size());
std::memcpy(out.data(), buf.data(), buf.size());
return out;
};
verifier.setRepairManager(makeRepairManager(storage, rmCfg));
auto result = verifier.verifyBlock(hash);
CHECK(result.status == VerificationStatus::Repaired);
auto retrieved = storage.retrieve(hash);
REQUIRE(retrieved.has_value());
std::string restored(reinterpret_cast<const char*>(retrieved.value().data()),
retrieved.value().size());
CHECK(restored == payload);
fs::remove_all(temp, ec);
}
TEST_CASE("IntegrationSmoke.VerifyCLIRepairsFromBackupFlag", "[smoke][integrationsmoke]") {
namespace fs = std::filesystem;
fs::path temp = fs::temp_directory_path() / "yams-verify-cli-repair-smoke";
std::error_code ec;
fs::remove_all(temp, ec);
fs::create_directories(temp / "backup", ec);
fs::create_directories(temp / "blocks", ec);
const std::string payload = "cli-repair-backup";
std::vector<std::byte> bytes = makeBytes(payload);
auto hasher = crypto::createSHA256Hasher();
hasher->init();
hasher->update(std::span<const std::byte>(bytes.data(), bytes.size()));
const std::string hash = hasher->finalize();
{
std::ofstream out(temp / "backup" / hash, std::ios::binary | std::ios::trunc);
REQUIRE(out.is_open());
out.write(payload.data(), static_cast<std::streamsize>(payload.size()));
REQUIRE(out.good());
}
CLI::App verifyApp{"verify"};
bool attemptRepair = false;
std::string hashArg;
std::string storageArg;
std::string backupArg;
verifyApp.add_option("--hash", hashArg)->required();
verifyApp.add_flag("--repair", attemptRepair);
verifyApp.add_option("--repair-backup-dir", backupArg)->required();
verifyApp.add_option("--storage", storageArg)->required();
std::vector<std::string> args = {
"verify",
"--hash",
hash,
"--repair",
"--repair-backup-dir",
(temp / "backup").string(),
"--storage",
temp.string(),
};
std::vector<char*> argv;
argv.reserve(args.size());
for (auto& arg : args) {
argv.push_back(arg.data());
}
int argc = static_cast<int>(argv.size());
REQUIRE_NOTHROW(verifyApp.parse(argc, argv.data()));
REQUIRE(attemptRepair);
REQUIRE(hashArg == hash);
fs::path storageRoot = fs::path(storageArg);
fs::path backupRoot = fs::path(backupArg);
storage::StorageEngine storageEngine({.basePath = storageRoot / "blocks"});
storage::ReferenceCounter::Config rcfg;
rcfg.databasePath = storageRoot / "refs.db";
auto refCounterPtr = storage::createReferenceCounter(rcfg);
VerificationConfig vcfg;
vcfg.enableAutoRepair = attemptRepair;
vcfg.maxRepairAttempts = 1;
IntegrityVerifier verifier(storageEngine,
static_cast<storage::ReferenceCounter&>(*refCounterPtr), vcfg);
RepairManagerConfig rmCfg;
rmCfg.backupFetcher =
[backupRoot](const std::string& requested) -> Result<std::vector<std::byte>> {
fs::path p = backupRoot / requested;
if (!fs::exists(p))
return Error{ErrorCode::NotFound, "backup not found"};
std::ifstream in(p, std::ios::binary);
if (!in)
return Error{ErrorCode::IOError, "open failed"};
std::vector<char> buf((std::istreambuf_iterator<char>(in)), {});
std::vector<std::byte> out(buf.size());
std::memcpy(out.data(), buf.data(), buf.size());
return out;
};
verifier.setRepairManager(makeRepairManager(storageEngine, rmCfg));
auto result = verifier.verifyBlock(hashArg);
CHECK(result.status == VerificationStatus::Repaired);
auto retrieved = storageEngine.retrieve(hashArg);
REQUIRE(retrieved.has_value());
std::string restored(reinterpret_cast<const char*>(retrieved.value().data()),
retrieved.value().size());
CHECK(restored == payload);
fs::remove_all(temp, ec);
}