// 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 #include #include #include #include #include #include #include #include #include #include using namespace yams; using namespace yams::cli; using namespace yams::metadata; namespace { struct UpdateCommandFixture { std::filesystem::path testDir; std::shared_ptr pool; std::shared_ptr 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((testDir / "test.db").string(), poolCfg); auto initRes = pool->initialize(); REQUIRE(initRes.has_value()); repo = std::make_shared(*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::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 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")); }