* fix(daemon): avoid socket cancel on stop Signal active sockets with shutdown only so pending I/O wakes without entering Boost.Asio's reactor cancel path during teardown. * fix(metadata): open read pools read-only * ci: fix coverage report generation * fix(metadata): tag unscoped pool holders * test(daemon): avoid ipc teardown takeover * ci: skip plugin coverage objects * fix(daemon): repair completed FTS5 ghosts * build(vector): guard unusable FAISS headers * ci: use clang gcov for coverage * test(daemon): stabilize arm CI flakes * Harden search and compression numeric config * fix(daemon): reconcile orphan vector embeddings * fix: address vector cleanup review comments * chore(main): release 0.16.1 * Harden search ranking divisors * Harden snippet distance and command registration * test(storage): add readiness coverage gate Document the distributed sync readiness surface and add direct adapter, refcount, and GC failure-path coverage. * test(storage): cover strict resolver fallback * fix(search): tighten retrieval ranking Honor component weights in default RRF, over-fetch vector chunks before document aggregation, and persist topology centroids for sparse-guided routing. * fix(search): route weak queries through topology * fix(search): improve rerank and weak queries Wire provider-backed cross reranking, add direct Simeon candidates for weak lexical pools, and report stage-level retrieval metrics. * fix(storage): honor explicit plugin names Add readiness coverage for factory, URL backend, and compressed storage failure paths. * fix(storage): avoid remote size listing * fix(api): rollback failed content adds * test(storage): cover engine readiness * test(storage): cover compressed readiness * fix(core): harden delete and recovery paths Route prune and local delete fallback through DocumentService so content, metadata, vectors, and KG cleanup stay consistent. Treat transient SQLite quick_check lock failures as ResourceBusy, refresh IPC activity on streaming writes, and preserve reranker config in tuner seeding. * test(storage): cover URL backend readiness * fix(api): harden content rollback * feat(storage): add list seam, URL listing, and write-edge coverage * feat(api): reconcile ambiguous remote manifest writes * feat(storage): add deep compressed scan validation * test(storage): add plugin loader error path coverage * fix(gc): prune reference rows after collection * test(api,storage): add health, compact, gc, and verify edge coverage * docs: update storage-readiness coverage to 71.8% * test(metadata): add connection pool shutdown-while-leased coverage * test(metadata): add tree builder snapshot root stability coverage * test(vector): add before-init and after-close operation rejection coverage * test(metadata): add database post-close, open-failure, and cache-clear coverage * test(integrity): add P2P wrong-hash rejection + batch matrix resolution * docs: audit and resolve all matrix rows — 18 fully covered * test(metadata): add failed-migration and rollback coverage * test(storage): add getBlockSize-invalid-key, empty-verify, PUT/DELETE retry coverage * feat(storage): add file-open and rename failure hooks + ContentStore retrieve edge coverage * docs: add broad-surface coverage with per-subsystem breakdown * test(integrity): add chunk validator manifest validation coverage * test(vector): add batch insert, delete, and update operation coverage * docs: update integrity subsystem coverage to 46.4% * docs: consolidate coverage report — clean matrix, remove boilerplate * docs: update coverage with complete-suite numbers — 59.8% broad, 74.3% narrow * test(storage): add URLBackend HEAD exists with 2xx coverage * bump submodules * fix: address PR review — curl_slist leak, ec ordering, CAS rollback, production hooks --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
161 lines
5.5 KiB
C++
161 lines
5.5 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <yams/crypto/hasher.h>
|
|
#include <yams/integrity/chunk_validator.h>
|
|
#include <yams/manifest/manifest_manager.h>
|
|
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace yams::integrity;
|
|
|
|
namespace {
|
|
|
|
std::vector<std::byte> toBytes(const std::string& s) {
|
|
std::vector<std::byte> out(s.size());
|
|
std::memcpy(out.data(), s.data(), s.size());
|
|
return out;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("ChunkValidator single chunk validation", "[integrity][chunk-validator]") {
|
|
ChunkValidationConfig cfg;
|
|
cfg.maxParallelValidations = 4;
|
|
|
|
ChunkValidator validator(cfg);
|
|
|
|
SECTION("validates chunk with correct hash") {
|
|
auto hasher = yams::crypto::createSHA256Hasher();
|
|
std::string data = "test chunk data";
|
|
auto bytes = toBytes(data);
|
|
std::string hash = hasher->hash(std::span<const std::byte>(bytes.data(), bytes.size()));
|
|
|
|
auto result =
|
|
validator.validateChunk(std::span<const std::byte>(bytes.data(), bytes.size()), hash);
|
|
|
|
CHECK(result.isValid);
|
|
CHECK(result.errorMessage.empty());
|
|
}
|
|
|
|
SECTION("detects chunk with incorrect hash") {
|
|
std::string data = "test chunk data";
|
|
auto bytes = toBytes(data);
|
|
|
|
auto result = validator.validateChunk(
|
|
std::span<const std::byte>(bytes.data(), bytes.size()), "bad-hash");
|
|
|
|
CHECK_FALSE(result.isValid);
|
|
CHECK_FALSE(result.errorMessage.empty());
|
|
}
|
|
|
|
SECTION("validates empty chunk") {
|
|
auto hasher = yams::crypto::createSHA256Hasher();
|
|
std::vector<std::byte> emptyBytes;
|
|
std::string hash = hasher->hash(std::span<const std::byte>(emptyBytes.data(), 0));
|
|
|
|
auto result =
|
|
validator.validateChunk(std::span<const std::byte>(emptyBytes.data(), 0), hash);
|
|
|
|
CHECK(result.isValid);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ChunkValidator configuration", "[integrity][chunk-validator]") {
|
|
ChunkValidationConfig cfg;
|
|
cfg.maxParallelValidations = 8;
|
|
cfg.failOnFirstError = true;
|
|
|
|
ChunkValidator validator(cfg);
|
|
|
|
SECTION("getConfig returns current configuration") {
|
|
const auto& currentCfg = validator.getConfig();
|
|
CHECK(currentCfg.maxParallelValidations == 8);
|
|
CHECK(currentCfg.failOnFirstError);
|
|
}
|
|
|
|
SECTION("setConfig updates configuration") {
|
|
ChunkValidationConfig newCfg;
|
|
newCfg.maxParallelValidations = 2;
|
|
newCfg.failOnFirstError = false;
|
|
|
|
validator.setConfig(newCfg);
|
|
|
|
const auto& currentCfg = validator.getConfig();
|
|
CHECK(currentCfg.maxParallelValidations == 2);
|
|
CHECK_FALSE(currentCfg.failOnFirstError);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ChunkValidator validates manifest with valid chunks", "[integrity][chunk-validator]") {
|
|
ChunkValidationConfig cfg;
|
|
cfg.maxParallelValidations = 4;
|
|
ChunkValidator validator(cfg);
|
|
|
|
auto hasher = yams::crypto::createSHA256Hasher();
|
|
std::string data1 = "chunk one data";
|
|
std::string data2 = "chunk two is longer";
|
|
auto bytes1 = toBytes(data1);
|
|
auto bytes2 = toBytes(data2);
|
|
|
|
std::string hash1 = hasher->hash(std::span<const std::byte>(bytes1.data(), bytes1.size()));
|
|
std::string hash2 = hasher->hash(std::span<const std::byte>(bytes2.data(), bytes2.size()));
|
|
|
|
yams::manifest::Manifest manifest;
|
|
manifest.fileHash = hasher->hash(std::span<const std::byte>(bytes1.data(), bytes1.size()));
|
|
manifest.fileSize = data1.size() + data2.size();
|
|
manifest.chunks.push_back(
|
|
{.hash = hash1, .offset = 0, .size = static_cast<uint32_t>(data1.size())});
|
|
manifest.chunks.push_back({.hash = hash2,
|
|
.offset = static_cast<uint64_t>(data1.size()),
|
|
.size = static_cast<uint32_t>(data2.size())});
|
|
|
|
auto chunkProvider = [&](const std::string& hash) -> yams::Result<std::vector<std::byte>> {
|
|
if (hash == hash1)
|
|
return bytes1;
|
|
if (hash == hash2)
|
|
return bytes2;
|
|
return yams::Error{yams::ErrorCode::ChunkNotFound, "no such chunk"};
|
|
};
|
|
|
|
auto report = validator.validateManifest(manifest, chunkProvider);
|
|
CHECK(report.overallSuccess);
|
|
CHECK(report.validChunks == 2);
|
|
CHECK(report.invalidChunks == 0);
|
|
CHECK(report.failures.empty());
|
|
}
|
|
|
|
TEST_CASE("ChunkValidator detects invalid chunk in manifest", "[integrity][chunk-validator]") {
|
|
ChunkValidationConfig cfg;
|
|
cfg.failOnFirstError = true;
|
|
ChunkValidator validator(cfg);
|
|
|
|
auto hasher = yams::crypto::createSHA256Hasher();
|
|
std::string goodData = "good chunk";
|
|
std::string badData = "bad chunk data";
|
|
auto goodBytes = toBytes(goodData);
|
|
auto badBytes = toBytes(badData);
|
|
|
|
std::string goodHash =
|
|
hasher->hash(std::span<const std::byte>(goodBytes.data(), goodBytes.size()));
|
|
|
|
yams::manifest::Manifest manifest;
|
|
manifest.fileHash = goodHash;
|
|
manifest.fileSize = goodData.size() + badData.size();
|
|
manifest.chunks.push_back(
|
|
{.hash = goodHash, .offset = 0, .size = static_cast<uint32_t>(goodData.size())});
|
|
manifest.chunks.push_back({.hash = "wrong-hash-for-bad-chunk",
|
|
.offset = static_cast<uint64_t>(goodData.size()),
|
|
.size = static_cast<uint32_t>(badData.size())});
|
|
|
|
auto chunkProvider = [&](const std::string& hash) -> yams::Result<std::vector<std::byte>> {
|
|
if (hash == goodHash)
|
|
return goodBytes;
|
|
return badBytes;
|
|
};
|
|
|
|
auto report = validator.validateManifest(manifest, chunkProvider);
|
|
CHECK_FALSE(report.overallSuccess);
|
|
CHECK(report.invalidChunks > 0);
|
|
}
|