yams/tests/unit/integrity/integrity_verifier_catch2_test.cpp

345 lines
12 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include "../../common/test_helpers_catch2.h"
#include <yams/crypto/hasher.h>
#include <yams/integrity/verifier.h>
#include <yams/storage/reference_counter.h>
#include <yams/storage/storage_engine.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <memory>
#include <span>
#include <string>
#include <vector>
using namespace yams;
using namespace yams::integrity;
using namespace yams::storage;
namespace {
struct IntegrityVerifierFixture {
yams::test::TempDirGuard tempDirGuard{"yams_integrity_verifier_"};
std::filesystem::path testDir{tempDirGuard.path()};
std::unique_ptr<StorageEngine> storageEngine;
std::unique_ptr<ReferenceCounter> refCounter;
std::unique_ptr<IntegrityVerifier> verifier;
std::unique_ptr<crypto::IContentHasher> hasher;
std::vector<std::byte> testDataBytes;
std::string testDataString;
std::string testHash;
IntegrityVerifierFixture() {
StorageConfig config{
.basePath = testDir, .shardDepth = 2, .mutexPoolSize = 128, .enableCompression = false};
storageEngine = std::make_unique<StorageEngine>(std::move(config));
ReferenceCounter::Config refConfig;
refConfig.databasePath = testDir / "refs.db";
refCounter = std::make_unique<ReferenceCounter>(refConfig);
VerificationConfig verifyConfig;
verifyConfig.maxConcurrentVerifications = 2;
verifyConfig.blocksPerSecond = 100;
verifyConfig.enableAutoRepair = false;
verifier = std::make_unique<IntegrityVerifier>(*storageEngine, *refCounter, verifyConfig);
testDataBytes = yams::test::TestVectors::getHelloWorldData();
testDataString = "Hello World";
hasher = crypto::createSHA256Hasher();
testHash = hasher->hash(testDataBytes);
}
~IntegrityVerifierFixture() {
if (verifier && verifier->isRunning()) {
verifier->stopBackgroundVerification();
}
verifier.reset();
refCounter.reset();
storageEngine.reset();
}
IntegrityVerifierFixture(const IntegrityVerifierFixture&) = delete;
IntegrityVerifierFixture& operator=(const IntegrityVerifierFixture&) = delete;
std::string storeBlock(const std::vector<std::byte>& data) {
auto dataHash = hasher->hash(data);
auto storeResult =
storageEngine->store(dataHash, std::span<const std::byte>(data.data(), data.size()));
REQUIRE(storeResult.has_value());
auto refResult = refCounter->increment(dataHash, data.size());
REQUIRE(refResult.has_value());
return dataHash;
}
void storeTestBlock() { storeBlock(testDataBytes); }
void corruptTestBlock() {
// Mirror StorageEngine::getObjectPath sharding (shardDepth=2).
auto blockPath = testDir / "objects" / testHash.substr(0, 2) / testHash.substr(2);
if (std::filesystem::exists(blockPath)) {
std::ofstream file(blockPath, std::ios::binary | std::ios::out);
if (file.is_open()) {
std::string corruptData = "CORRUPTED DATA";
file.write(corruptData.data(), corruptData.size());
file.close();
}
}
}
};
} // namespace
TEST_CASE_METHOD(IntegrityVerifierFixture, "ConstructorAndDestructor", "[integrity][verifier]") {
CHECK_FALSE(verifier->isRunning());
CHECK_FALSE(verifier->isPaused());
const auto& stats = verifier->getStatistics();
CHECK(stats.blocksVerifiedTotal.load() == 0);
CHECK(stats.verificationErrorsTotal.load() == 0);
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyValidBlock", "[integrity][verifier]") {
storeTestBlock();
auto result = verifier->verifyBlock(testHash);
CHECK(result.status == VerificationStatus::Passed);
CHECK(result.blockHash == testHash);
CHECK(result.blockSize == testDataBytes.size());
CHECK(result.errorDetails.empty());
CHECK(result.isSuccess());
const auto& stats = verifier->getStatistics();
CHECK(stats.blocksVerifiedTotal.load() == 1);
CHECK(stats.verificationErrorsTotal.load() == 0);
CHECK(stats.bytesVerifiedTotal.load() == testDataBytes.size());
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyMissingBlock", "[integrity][verifier]") {
std::string missingHash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
auto result = verifier->verifyBlock(missingHash);
CHECK(result.status == VerificationStatus::Missing);
CHECK(result.blockHash == missingHash);
CHECK(result.blockSize == 0);
CHECK_FALSE(result.errorDetails.empty());
CHECK_FALSE(result.isSuccess());
const auto& stats = verifier->getStatistics();
CHECK(stats.blocksVerifiedTotal.load() == 0);
CHECK(stats.verificationErrorsTotal.load() == 1);
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyCorruptedBlock", "[integrity][verifier]") {
storeTestBlock();
corruptTestBlock();
auto result = verifier->verifyBlock(testHash);
CHECK(result.status == VerificationStatus::Corrupted);
CHECK(result.blockHash == testHash);
CHECK(result.blockSize > 0);
CHECK_FALSE(result.errorDetails.empty());
CHECK_FALSE(result.isSuccess());
const auto& stats = verifier->getStatistics();
CHECK(stats.blocksVerifiedTotal.load() == 0);
CHECK(stats.verificationErrorsTotal.load() == 1);
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyMultipleBlocks", "[integrity][verifier]") {
std::vector<std::vector<std::byte>> testBlocks = {yams::test::TestVectors::getABCData(),
yams::test::TestVectors::getHelloWorldData()};
std::vector<std::string> hashes;
hashes.reserve(testBlocks.size());
for (const auto& data : testBlocks) {
hashes.push_back(storeBlock(data));
}
auto results = verifier->verifyBlocks(hashes);
REQUIRE(results.size() == testBlocks.size());
for (size_t i = 0; i < results.size(); ++i) {
CHECK(results[i].status == VerificationStatus::Passed);
CHECK(results[i].blockHash == hashes[i]);
CHECK(results[i].isSuccess());
}
const auto& stats = verifier->getStatistics();
CHECK(stats.blocksVerifiedTotal.load() == testBlocks.size());
CHECK(stats.verificationErrorsTotal.load() == 0);
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "BackgroundVerificationLifecycle",
"[integrity][verifier]") {
CHECK_FALSE(verifier->isRunning());
auto startResult = verifier->startBackgroundVerification();
CHECK(startResult.has_value());
CHECK(verifier->isRunning());
CHECK_FALSE(verifier->isPaused());
auto startAgainResult = verifier->startBackgroundVerification();
CHECK_FALSE(startAgainResult.has_value());
CHECK(startAgainResult.error() == ErrorCode::OperationInProgress);
auto stopResult = verifier->stopBackgroundVerification();
CHECK(stopResult.has_value());
CHECK_FALSE(verifier->isRunning());
auto stopAgainResult = verifier->stopBackgroundVerification();
CHECK(stopAgainResult.has_value());
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "PauseResumeVerification", "[integrity][verifier]") {
auto startResult = verifier->startBackgroundVerification();
CHECK(startResult.has_value());
CHECK(verifier->isRunning());
CHECK_FALSE(verifier->isPaused());
auto pauseResult = verifier->pauseVerification();
CHECK(pauseResult.has_value());
CHECK(verifier->isRunning());
CHECK(verifier->isPaused());
auto resumeResult = verifier->resumeVerification();
CHECK(resumeResult.has_value());
CHECK(verifier->isRunning());
CHECK_FALSE(verifier->isPaused());
verifier->stopBackgroundVerification();
auto pauseFailResult = verifier->pauseVerification();
CHECK_FALSE(pauseFailResult.has_value());
CHECK(pauseFailResult.error() == ErrorCode::InvalidOperation);
auto resumeFailResult = verifier->resumeVerification();
CHECK_FALSE(resumeFailResult.has_value());
CHECK(resumeFailResult.error() == ErrorCode::InvalidOperation);
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "StatisticsAndReporting", "[integrity][verifier]") {
storeTestBlock();
const auto& initialStats = verifier->getStatistics();
CHECK(initialStats.blocksVerifiedTotal.load() == 0);
verifier->verifyBlock(testHash);
const auto& stats = verifier->getStatistics();
CHECK(stats.blocksVerifiedTotal.load() == 1);
CHECK(stats.totalVerificationTime.count() >= 0);
CHECK(stats.getVerificationRate() >= 0.0);
auto report = verifier->generateReport(std::chrono::hours{1});
CHECK(report.blocksVerified == 1);
CHECK(report.blocksPassed == 1);
CHECK(report.blocksFailed == 0);
CHECK(report.totalBytes > 0);
CHECK(report.getSuccessRate() > 0.99);
CHECK(report.getErrorRate() < 0.01);
verifier->resetStatistics();
const auto& resetStats = verifier->getStatistics();
CHECK(resetStats.blocksVerifiedTotal.load() == 0);
CHECK(resetStats.verificationErrorsTotal.load() == 0);
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "RecentFailures", "[integrity][verifier]") {
storeTestBlock();
corruptTestBlock();
verifier->verifyBlock(testHash);
auto failures = verifier->getRecentFailures(10);
REQUIRE(failures.size() == 1);
CHECK(failures[0].status == VerificationStatus::Corrupted);
CHECK(failures[0].blockHash == testHash);
CHECK_FALSE(failures[0].isSuccess());
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "ConfigurationUpdate", "[integrity][verifier]") {
VerificationConfig newConfig;
newConfig.maxConcurrentVerifications = 8;
newConfig.blocksPerSecond = 500;
newConfig.enableAutoRepair = true;
CHECK_NOTHROW(verifier->updateConfig(newConfig));
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "CallbackFunctionality", "[integrity][verifier]") {
storeTestBlock();
bool progressCallbackCalled = false;
VerificationResult capturedResult;
verifier->setProgressCallback([&](const VerificationResult& result) {
progressCallbackCalled = true;
capturedResult = result;
});
bool alertCallbackCalled = false;
verifier->setAlertCallback(
[&](const IntegrityReport& /*report*/) { alertCallbackCalled = true; });
verifier->verifyBlock(testHash);
CHECK(progressCallbackCalled);
CHECK(capturedResult.blockHash == testHash);
CHECK(capturedResult.status == VerificationStatus::Passed);
(void)alertCallbackCalled;
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "SchedulingStrategyChange", "[integrity][verifier]") {
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::ByAge));
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::BySize));
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::ByFailures));
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::ByAccess));
CHECK_NOTHROW(verifier->setSchedulingStrategy(SchedulingStrategy::Balanced));
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyAllOperation", "[integrity][verifier]") {
storeTestBlock();
auto report = verifier->verifyAll();
CHECK(report.generatedAt.time_since_epoch().count() >= 0);
CHECK(report.duration.count() >= 0);
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyEmptyHash", "[integrity][verifier]") {
auto result = verifier->verifyBlock("");
CHECK(result.status != VerificationStatus::Passed);
CHECK_FALSE(result.isSuccess());
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "VerifyInvalidHash", "[integrity][verifier]") {
auto result = verifier->verifyBlock("invalid_hash");
CHECK(result.status != VerificationStatus::Passed);
CHECK_FALSE(result.isSuccess());
}
TEST_CASE_METHOD(IntegrityVerifierFixture, "MoveConstructorAndAssignment",
"[integrity][verifier]") {
VerificationConfig config;
auto verifier1 = std::make_unique<IntegrityVerifier>(*storageEngine, *refCounter, config);
auto verifier2 = std::move(*verifier1);
IntegrityVerifier verifier3(*storageEngine, *refCounter, config);
verifier3 = std::move(verifier2);
CHECK_FALSE(verifier3.isRunning());
}