* fix: improving write paths and write coordinator enqueue logic * perf(metadata): batch write-path updates * perf(metadata): profile write-path batching * fix(build): gate stacktrace diagnostics * fix(write-path): harden update and shutdown edges Signed-off-by: trvon <git@trevon.dev> * fix(metadata): address review nits Signed-off-by: trvon <git@trevon.dev> --------- Signed-off-by: trvon <git@trevon.dev>
209 lines
7.6 KiB
C++
209 lines
7.6 KiB
C++
// CLI Update Command tests
|
|
// Catch2 migration from GTest (yams-3s4 / yams-cli)
|
|
//
|
|
// Tests for update command basic functionality.
|
|
// Note: Full mock-based tests require GMock or trompeloeil which complicates
|
|
// the migration. This file validates basic command structure.
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <yams/cli/commands/update_command.h>
|
|
#include <yams/metadata/connection_pool.h>
|
|
#include <yams/metadata/document_metadata.h>
|
|
#include <yams/metadata/metadata_repository.h>
|
|
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <sstream>
|
|
|
|
using namespace yams;
|
|
using namespace yams::cli;
|
|
using namespace yams::metadata;
|
|
|
|
namespace {
|
|
|
|
struct UpdateCommandFixture {
|
|
std::filesystem::path testDir;
|
|
std::shared_ptr<ConnectionPool> pool;
|
|
std::shared_ptr<MetadataRepository> repo;
|
|
|
|
UpdateCommandFixture() {
|
|
// Set up test database in temp directory
|
|
testDir = std::filesystem::temp_directory_path() / "update_command_catch2_test";
|
|
std::filesystem::create_directories(testDir);
|
|
|
|
// Create in-memory database for basic tests
|
|
ConnectionPoolConfig poolCfg;
|
|
poolCfg.minConnections = 1;
|
|
poolCfg.maxConnections = 2;
|
|
|
|
pool = std::make_shared<ConnectionPool>((testDir / "test.db").string(), poolCfg);
|
|
auto initRes = pool->initialize();
|
|
REQUIRE(initRes.has_value());
|
|
|
|
repo = std::make_shared<MetadataRepository>(*pool);
|
|
}
|
|
|
|
~UpdateCommandFixture() {
|
|
repo.reset();
|
|
pool.reset();
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(testDir, ec);
|
|
}
|
|
|
|
int64_t insertTestDocument(const std::string& hash, const std::string& fileName) const {
|
|
DocumentInfo docInfo;
|
|
docInfo.sha256Hash = hash;
|
|
docInfo.fileName = fileName;
|
|
docInfo.filePath = (testDir / fileName).string();
|
|
docInfo.fileExtension = std::filesystem::path(fileName).extension().string();
|
|
docInfo.fileSize = 1024;
|
|
docInfo.mimeType = "text/plain";
|
|
docInfo.createdTime =
|
|
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
|
|
docInfo.modifiedTime = docInfo.createdTime;
|
|
docInfo.indexedTime = docInfo.createdTime;
|
|
|
|
auto insertRes = repo->insertDocument(docInfo);
|
|
REQUIRE(insertRes.has_value());
|
|
return insertRes.value();
|
|
}
|
|
};
|
|
|
|
class CaptureStdout {
|
|
public:
|
|
CaptureStdout() : oldCout_(std::cout.rdbuf(buffer_.rdbuf())) {}
|
|
~CaptureStdout() { std::cout.rdbuf(oldCout_); }
|
|
|
|
std::string str() const { return buffer_.str(); }
|
|
|
|
CaptureStdout(const CaptureStdout&) = delete;
|
|
CaptureStdout& operator=(const CaptureStdout&) = delete;
|
|
|
|
private:
|
|
std::ostringstream buffer_;
|
|
std::streambuf* oldCout_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("UpdateCommand - basic instantiation with real objects", "[cli][update][catch2]") {
|
|
UpdateCommandFixture fixture;
|
|
|
|
// Create command with real (not mock) objects
|
|
// Note: UpdateCommand constructor may require specific interface pointers
|
|
// This test validates the command can be created without crashing
|
|
SUCCEED();
|
|
}
|
|
|
|
TEST_CASE("UpdateCommand - command structure verification", "[cli][update][catch2]") {
|
|
// Verify the UpdateCommand type exists and has expected interface
|
|
// This is a compile-time check primarily
|
|
static_assert(std::is_class_v<UpdateCommand>, "UpdateCommand should be a class");
|
|
SUCCEED();
|
|
}
|
|
|
|
TEST_CASE("UpdateCommand - execute updates metadata by hash", "[cli][update][catch2]") {
|
|
UpdateCommandFixture fixture;
|
|
const auto docId = fixture.insertTestDocument(
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "update-test.txt");
|
|
|
|
UpdateCommand command(fixture.repo, nullptr);
|
|
command.setHash("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
|
|
command.setKey("author");
|
|
command.setValue("Alice");
|
|
|
|
CaptureStdout capture;
|
|
const auto result = command.execute();
|
|
|
|
REQUIRE(result.has_value());
|
|
CHECK((capture.str().find("Document metadata updated successfully!") != std::string::npos));
|
|
|
|
auto metadata = fixture.repo->getMetadata(docId, "author");
|
|
REQUIRE(metadata.has_value());
|
|
REQUIRE(metadata.value().has_value());
|
|
CHECK((metadata.value()->asString() == "Alice"));
|
|
}
|
|
|
|
TEST_CASE("UpdateCommand - parseArguments captures key and value pairs", "[cli][update][catch2]") {
|
|
UpdateCommandFixture fixture;
|
|
fixture.insertTestDocument("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd",
|
|
"parse-test.txt");
|
|
|
|
UpdateCommand command(fixture.repo, nullptr);
|
|
command.parseArguments({"--hash",
|
|
"abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd",
|
|
"--key", "category", "--value", "notes"});
|
|
|
|
const auto result = command.execute();
|
|
REQUIRE(result.has_value());
|
|
|
|
auto doc = fixture.repo->getDocumentByHash(
|
|
"abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd");
|
|
REQUIRE(doc.has_value());
|
|
REQUIRE(doc.value().has_value());
|
|
|
|
auto metadata = fixture.repo->getMetadata(doc.value()->id, "category");
|
|
REQUIRE(metadata.has_value());
|
|
REQUIRE(metadata.value().has_value());
|
|
CHECK((metadata.value()->asString() == "notes"));
|
|
}
|
|
|
|
TEST_CASE("UpdateCommand - parseArguments ignores missing value after key",
|
|
"[cli][update][catch2]") {
|
|
UpdateCommandFixture fixture;
|
|
fixture.insertTestDocument("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
|
"missing-value.txt");
|
|
|
|
UpdateCommand command(fixture.repo, nullptr);
|
|
command.parseArguments({"--hash",
|
|
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
|
"--key", "category", "--value"});
|
|
|
|
CaptureStdout capture;
|
|
const auto result = command.execute();
|
|
REQUIRE(result.has_value());
|
|
CHECK((capture.str().find("Metadata updated: 0") != std::string::npos));
|
|
}
|
|
|
|
TEST_CASE("UpdateCommand - local path batches multiple metadata and tag updates",
|
|
"[cli][update][catch2]") {
|
|
UpdateCommandFixture fixture;
|
|
const auto docId = fixture.insertTestDocument(
|
|
"feedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedface", "batch-test.txt");
|
|
|
|
UpdateCommand command(fixture.repo, nullptr);
|
|
command.parseArguments(
|
|
{"--hash", "feedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedface", "--metadata",
|
|
"author=Alice", "--metadata", "category=notes", "--metadata", "topic=systems",
|
|
"--metadata", "invalidpair", "--tags", "fresh,cli", "--remove-tags", "old"});
|
|
|
|
CaptureStdout capture;
|
|
const auto result = command.execute();
|
|
REQUIRE(result.has_value());
|
|
CHECK((capture.str().find("Metadata updated: 4") != std::string::npos));
|
|
CHECK((capture.str().find("Metadata failed: 1") != std::string::npos));
|
|
|
|
auto author = fixture.repo->getMetadata(docId, "author");
|
|
REQUIRE(author.has_value());
|
|
REQUIRE(author.value().has_value());
|
|
CHECK((author.value()->asString() == "Alice"));
|
|
|
|
auto category = fixture.repo->getMetadata(docId, "category");
|
|
REQUIRE(category.has_value());
|
|
REQUIRE(category.value().has_value());
|
|
CHECK((category.value()->asString() == "notes"));
|
|
|
|
auto topic = fixture.repo->getMetadata(docId, "topic");
|
|
REQUIRE(topic.has_value());
|
|
REQUIRE(topic.value().has_value());
|
|
CHECK((topic.value()->asString() == "systems"));
|
|
|
|
auto tags = fixture.repo->getMetadata(docId, "tags");
|
|
REQUIRE(tags.has_value());
|
|
REQUIRE(tags.value().has_value());
|
|
CHECK((tags.value()->asString() == "fresh,cli,-old"));
|
|
}
|