2025-03-08 23:44:34 +08:00
|
|
|
/**
|
|
|
|
* @file test_mcp_resource.cpp
|
|
|
|
* @brief Test MCP resource related functionality
|
|
|
|
*
|
|
|
|
* This file contains unit tests for the MCP resource module, based on the 2024-11-05 specification.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "mcp_resource.h"
|
2025-03-10 03:24:54 +08:00
|
|
|
#include "mcp_server.h"
|
|
|
|
#include "mcp_client.h"
|
|
|
|
#include "base64.hpp"
|
2025-03-08 23:44:34 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
#include <string>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <fstream>
|
|
|
|
#include <iostream>
|
2025-03-10 03:24:54 +08:00
|
|
|
#include <thread>
|
|
|
|
#include <chrono>
|
2025-03-08 23:44:34 +08:00
|
|
|
|
|
|
|
// Create a test directory and file
|
|
|
|
class ResourceTest : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
void SetUp() override {
|
|
|
|
// Create test directory
|
|
|
|
test_dir = std::filesystem::temp_directory_path() / "mcp_test";
|
|
|
|
std::filesystem::create_directories(test_dir);
|
|
|
|
|
|
|
|
// Create test file
|
|
|
|
test_file = test_dir / "test.txt";
|
|
|
|
std::ofstream file(test_file);
|
|
|
|
file << "Test file content";
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
// Output test directory and file path for debugging
|
|
|
|
std::cout << "Test directory: " << test_dir << std::endl;
|
|
|
|
std::cout << "Test file: " << test_file << std::endl;
|
|
|
|
|
|
|
|
// Ensure file exists
|
|
|
|
ASSERT_TRUE(std::filesystem::exists(test_file)) << "Test file was not created successfully";
|
|
|
|
}
|
|
|
|
|
|
|
|
void TearDown() override {
|
|
|
|
// Clean up test directory
|
|
|
|
std::filesystem::remove_all(test_dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::filesystem::path test_dir;
|
|
|
|
std::filesystem::path test_file;
|
|
|
|
};
|
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Test text resource
|
|
|
|
TEST(McpResourceTest, TextResourceTest) {
|
|
|
|
// Create text resource
|
|
|
|
mcp::text_resource resource("test://example.txt", "example.txt", "text/plain", "Example text resource");
|
|
|
|
|
|
|
|
// Test metadata
|
|
|
|
mcp::json metadata = resource.get_metadata();
|
|
|
|
EXPECT_EQ(metadata["uri"], "test://example.txt");
|
|
|
|
EXPECT_EQ(metadata["name"], "example.txt");
|
|
|
|
EXPECT_EQ(metadata["mimeType"], "text/plain");
|
|
|
|
EXPECT_EQ(metadata["description"], "Example text resource");
|
|
|
|
|
|
|
|
// Test URI
|
|
|
|
EXPECT_EQ(resource.get_uri(), "test://example.txt");
|
|
|
|
|
|
|
|
// Test setting and getting text
|
|
|
|
std::string test_content = "This is a test content";
|
|
|
|
resource.set_text(test_content);
|
|
|
|
EXPECT_EQ(resource.get_text(), test_content);
|
|
|
|
|
|
|
|
// Test read
|
|
|
|
mcp::json content = resource.read();
|
|
|
|
EXPECT_EQ(content["uri"], "test://example.txt");
|
|
|
|
EXPECT_EQ(content["mimeType"], "text/plain");
|
|
|
|
EXPECT_EQ(content["text"], test_content);
|
|
|
|
|
|
|
|
// Test modification
|
|
|
|
EXPECT_FALSE(resource.is_modified());
|
|
|
|
resource.set_text("New content");
|
|
|
|
EXPECT_TRUE(resource.is_modified());
|
|
|
|
|
|
|
|
// Test read after modification
|
|
|
|
content = resource.read();
|
|
|
|
EXPECT_EQ(content["text"], "New content");
|
|
|
|
EXPECT_FALSE(resource.is_modified());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test binary resource
|
|
|
|
TEST(McpResourceTest, BinaryResourceTest) {
|
|
|
|
// Create binary resource
|
|
|
|
mcp::binary_resource resource("test://example.bin", "example.bin", "application/octet-stream", "Example binary resource");
|
|
|
|
|
|
|
|
// Test metadata
|
|
|
|
mcp::json metadata = resource.get_metadata();
|
|
|
|
EXPECT_EQ(metadata["uri"], "test://example.bin");
|
|
|
|
EXPECT_EQ(metadata["name"], "example.bin");
|
|
|
|
EXPECT_EQ(metadata["mimeType"], "application/octet-stream");
|
|
|
|
EXPECT_EQ(metadata["description"], "Example binary resource");
|
|
|
|
|
|
|
|
// Test URI
|
|
|
|
EXPECT_EQ(resource.get_uri(), "test://example.bin");
|
|
|
|
|
|
|
|
// Test setting and getting binary data
|
|
|
|
std::vector<uint8_t> test_data = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello" in ASCII
|
|
|
|
resource.set_data(test_data.data(), test_data.size());
|
|
|
|
const auto& data = resource.get_data();
|
|
|
|
EXPECT_EQ(data.size(), test_data.size());
|
|
|
|
EXPECT_TRUE(std::equal(data.begin(), data.end(), test_data.begin()));
|
|
|
|
|
|
|
|
// Test read
|
|
|
|
mcp::json content = resource.read();
|
|
|
|
EXPECT_EQ(content["uri"], "test://example.bin");
|
|
|
|
EXPECT_EQ(content["mimeType"], "application/octet-stream");
|
|
|
|
|
|
|
|
// Base64 encode the data for comparison
|
|
|
|
std::string base64_data = base64::encode(reinterpret_cast<const char*>(test_data.data()), test_data.size());
|
|
|
|
EXPECT_EQ(content["blob"], base64_data);
|
|
|
|
|
|
|
|
// Test modification
|
|
|
|
EXPECT_FALSE(resource.is_modified());
|
|
|
|
std::vector<uint8_t> new_data = {0x57, 0x6F, 0x72, 0x6C, 0x64}; // "World" in ASCII
|
|
|
|
resource.set_data(new_data.data(), new_data.size());
|
|
|
|
EXPECT_TRUE(resource.is_modified());
|
|
|
|
|
|
|
|
// Test read after modification
|
|
|
|
content = resource.read();
|
|
|
|
std::string new_base64_data = base64::encode(reinterpret_cast<const char*>(new_data.data()), new_data.size());
|
|
|
|
EXPECT_EQ(content["blob"], new_base64_data);
|
|
|
|
EXPECT_FALSE(resource.is_modified());
|
|
|
|
}
|
|
|
|
|
2025-03-08 23:44:34 +08:00
|
|
|
// Test file resource
|
|
|
|
TEST_F(ResourceTest, FileResourceTest) {
|
|
|
|
// Create file resource
|
2025-03-10 03:24:54 +08:00
|
|
|
mcp::file_resource resource(test_file.string());
|
2025-03-08 23:44:34 +08:00
|
|
|
|
|
|
|
// Test metadata
|
|
|
|
mcp::json metadata = resource.get_metadata();
|
2025-03-10 03:24:54 +08:00
|
|
|
EXPECT_EQ(metadata["uri"], "file://" + test_file.string());
|
|
|
|
EXPECT_EQ(metadata["name"], test_file.filename().string());
|
|
|
|
EXPECT_EQ(metadata["mimeType"], "text/plain");
|
2025-03-08 23:44:34 +08:00
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Test URI
|
|
|
|
EXPECT_EQ(resource.get_uri(), "file://" + test_file.string());
|
2025-03-08 23:44:34 +08:00
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Test read
|
|
|
|
mcp::json content = resource.read();
|
|
|
|
EXPECT_EQ(content["uri"], "file://" + test_file.string());
|
|
|
|
EXPECT_EQ(content["mimeType"], "text/plain");
|
|
|
|
EXPECT_EQ(content["text"], "Test file content");
|
2025-03-08 23:44:34 +08:00
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Test modification detection
|
|
|
|
EXPECT_FALSE(resource.is_modified());
|
2025-03-08 23:44:34 +08:00
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Modify file
|
|
|
|
{
|
|
|
|
std::ofstream file(test_file);
|
|
|
|
file << "Modified content";
|
|
|
|
file.close();
|
2025-03-08 23:44:34 +08:00
|
|
|
}
|
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Test modification detection after file change
|
|
|
|
EXPECT_TRUE(resource.is_modified());
|
2025-03-08 23:44:34 +08:00
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Test read after modification
|
|
|
|
content = resource.read();
|
|
|
|
EXPECT_EQ(content["text"], "Modified content");
|
|
|
|
EXPECT_FALSE(resource.is_modified());
|
2025-03-08 23:44:34 +08:00
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Test file deletion detection
|
|
|
|
std::filesystem::remove(test_file);
|
|
|
|
EXPECT_TRUE(resource.is_modified());
|
2025-03-08 23:44:34 +08:00
|
|
|
}
|
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
// Test resource manager
|
|
|
|
TEST_F(ResourceTest, ResourceManagerTest) {
|
|
|
|
// Get resource manager instance
|
|
|
|
mcp::resource_manager& manager = mcp::resource_manager::instance();
|
|
|
|
|
|
|
|
// Create resources
|
|
|
|
auto text_res = std::make_shared<mcp::text_resource>("test://text.txt", "text.txt", "text/plain", "Text resource");
|
|
|
|
auto binary_res = std::make_shared<mcp::binary_resource>("test://binary.bin", "binary.bin", "application/octet-stream", "Binary resource");
|
|
|
|
auto file_res = std::make_shared<mcp::file_resource>(test_file.string());
|
|
|
|
|
|
|
|
// Set content
|
|
|
|
text_res->set_text("Text resource content");
|
|
|
|
std::vector<uint8_t> binary_data = {0x42, 0x69, 0x6E, 0x61, 0x72, 0x79}; // "Binary" in ASCII
|
|
|
|
binary_res->set_data(binary_data.data(), binary_data.size());
|
|
|
|
|
|
|
|
// Register resources
|
|
|
|
manager.register_resource(text_res);
|
|
|
|
manager.register_resource(binary_res);
|
|
|
|
manager.register_resource(file_res);
|
|
|
|
|
|
|
|
// Test list resources
|
|
|
|
mcp::json resources_list = manager.list_resources();
|
|
|
|
EXPECT_EQ(resources_list["resources"].size(), 3);
|
|
|
|
|
|
|
|
// Test get resource
|
|
|
|
auto retrieved_text_res = manager.get_resource("test://text.txt");
|
|
|
|
ASSERT_NE(retrieved_text_res, nullptr);
|
|
|
|
EXPECT_EQ(retrieved_text_res->get_uri(), "test://text.txt");
|
|
|
|
|
|
|
|
auto retrieved_binary_res = manager.get_resource("test://binary.bin");
|
|
|
|
ASSERT_NE(retrieved_binary_res, nullptr);
|
|
|
|
EXPECT_EQ(retrieved_binary_res->get_uri(), "test://binary.bin");
|
|
|
|
|
|
|
|
auto retrieved_file_res = manager.get_resource("file://" + test_file.string());
|
|
|
|
ASSERT_NE(retrieved_file_res, nullptr);
|
|
|
|
EXPECT_EQ(retrieved_file_res->get_uri(), "file://" + test_file.string());
|
|
|
|
|
|
|
|
// Test unregister resource
|
|
|
|
EXPECT_TRUE(manager.unregister_resource("test://text.txt"));
|
|
|
|
EXPECT_EQ(manager.get_resource("test://text.txt"), nullptr);
|
|
|
|
|
|
|
|
// Test resources list after unregister
|
|
|
|
resources_list = manager.list_resources();
|
|
|
|
EXPECT_EQ(resources_list["resources"].size(), 2);
|
|
|
|
|
|
|
|
// Test subscription
|
|
|
|
bool notification_received = false;
|
|
|
|
std::string notification_uri;
|
|
|
|
|
|
|
|
int subscription_id = manager.subscribe("test://binary.bin", [&](const std::string& uri) {
|
|
|
|
notification_received = true;
|
|
|
|
notification_uri = uri;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Modify resource and notify
|
|
|
|
binary_res->set_data(binary_data.data(), binary_data.size()); // This sets modified flag
|
|
|
|
manager.notify_resource_changed("test://binary.bin");
|
|
|
|
|
|
|
|
// Check notification
|
|
|
|
EXPECT_TRUE(notification_received);
|
|
|
|
EXPECT_EQ(notification_uri, "test://binary.bin");
|
|
|
|
|
|
|
|
// Test unsubscribe
|
|
|
|
EXPECT_TRUE(manager.unsubscribe(subscription_id));
|
|
|
|
|
|
|
|
// Reset notification flags
|
|
|
|
notification_received = false;
|
|
|
|
notification_uri = "";
|
|
|
|
|
|
|
|
// Modify and notify again
|
|
|
|
binary_res->set_data(binary_data.data(), binary_data.size());
|
|
|
|
manager.notify_resource_changed("test://binary.bin");
|
|
|
|
|
|
|
|
// Check no notification after unsubscribe
|
|
|
|
EXPECT_FALSE(notification_received);
|
|
|
|
EXPECT_EQ(notification_uri, "");
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
manager.unregister_resource("test://binary.bin");
|
|
|
|
manager.unregister_resource("file://" + test_file.string());
|
2025-03-08 23:44:34 +08:00
|
|
|
}
|
|
|
|
|
2025-03-10 03:24:54 +08:00
|
|
|
int main(int argc, char **argv) {
|
|
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
return RUN_ALL_TESTS();
|
2025-03-08 23:44:34 +08:00
|
|
|
}
|