192 lines
7.9 KiB
C++
192 lines
7.9 KiB
C++
|
|
// Step 620: Config auto-discovery (walk up from CWD) (12 tests)
|
||
|
|
|
||
|
|
#include "MCPProjectConfig.h"
|
||
|
|
|
||
|
|
#include <filesystem>
|
||
|
|
#include <fstream>
|
||
|
|
#include <iostream>
|
||
|
|
|
||
|
|
static int passed = 0, failed = 0;
|
||
|
|
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||
|
|
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||
|
|
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||
|
|
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||
|
|
|
||
|
|
static std::filesystem::path tempRoot() {
|
||
|
|
std::filesystem::path root = std::filesystem::temp_directory_path() / "whetstone_step620";
|
||
|
|
std::filesystem::remove_all(root);
|
||
|
|
std::filesystem::create_directories(root);
|
||
|
|
return root;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void writeFile(const std::filesystem::path& path, const std::string& content) {
|
||
|
|
std::ofstream out(path);
|
||
|
|
out << content;
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_from_exact_directory() {
|
||
|
|
TEST(discover_from_exact_directory);
|
||
|
|
auto root = tempRoot() / "exact";
|
||
|
|
std::filesystem::create_directories(root);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"defaultLanguage":"go"})");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(root.string());
|
||
|
|
CHECK(result.found, "config should be found");
|
||
|
|
CHECK(result.sourceWorkspaceRoot == root.string(), "source workspace mismatch");
|
||
|
|
CHECK(result.config.defaultLanguage == "go", "language mismatch");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_walks_to_parent() {
|
||
|
|
TEST(discover_walks_to_parent);
|
||
|
|
auto root = tempRoot() / "parent";
|
||
|
|
auto child = root / "src" / "feature";
|
||
|
|
std::filesystem::create_directories(child);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"defaultLanguage":"rust"})");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(child.string());
|
||
|
|
CHECK(result.found, "config should be found");
|
||
|
|
CHECK(result.sourceWorkspaceRoot == root.string(), "should resolve parent workspace");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_prefers_nearest_config() {
|
||
|
|
TEST(discover_prefers_nearest_config);
|
||
|
|
auto root = tempRoot() / "nearest";
|
||
|
|
auto mid = root / "apps";
|
||
|
|
auto leaf = mid / "api";
|
||
|
|
std::filesystem::create_directories(leaf);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"mcpWorkspaceAlias":"root"})");
|
||
|
|
writeFile(mid / ".whetstone.json", R"({"mcpWorkspaceAlias":"mid"})");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(leaf.string());
|
||
|
|
CHECK(result.found, "config should be found");
|
||
|
|
CHECK(result.sourceWorkspaceRoot == mid.string(), "should choose nearest parent");
|
||
|
|
CHECK(result.config.mcpWorkspaceAlias == "mid", "alias should come from nearest file");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_returns_not_found_when_missing() {
|
||
|
|
TEST(discover_returns_not_found_when_missing);
|
||
|
|
auto root = tempRoot() / "missing";
|
||
|
|
auto child = root / "sub";
|
||
|
|
std::filesystem::create_directories(child);
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(child.string());
|
||
|
|
CHECK(!result.found, "should not find config");
|
||
|
|
CHECK(result.error.empty(), "error should be empty");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_reports_invalid_start_directory() {
|
||
|
|
TEST(discover_reports_invalid_start_directory);
|
||
|
|
auto root = tempRoot() / "nope" / "does-not-exist";
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(root.string());
|
||
|
|
CHECK(!result.found, "should not find config");
|
||
|
|
CHECK(result.error == "start_dir_not_found", "wrong error");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_reports_parse_error_from_nearest_file() {
|
||
|
|
TEST(discover_reports_parse_error_from_nearest_file);
|
||
|
|
auto root = tempRoot() / "bad";
|
||
|
|
auto child = root / "src";
|
||
|
|
std::filesystem::create_directories(child);
|
||
|
|
writeFile(root / ".whetstone.json", "{bad");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(child.string());
|
||
|
|
CHECK(result.found, "file exists so found should be true");
|
||
|
|
CHECK(result.error == "config_json_parse_failed", "wrong error");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_parses_workspace_override() {
|
||
|
|
TEST(discover_parses_workspace_override);
|
||
|
|
auto root = tempRoot() / "workspace-override";
|
||
|
|
std::filesystem::create_directories(root);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"workspace":"/opt/project"})");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(root.string());
|
||
|
|
CHECK(result.found, "should find config");
|
||
|
|
CHECK(result.config.workspace == "/opt/project", "workspace override mismatch");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_parses_agent_role() {
|
||
|
|
TEST(discover_parses_agent_role);
|
||
|
|
auto root = tempRoot() / "role";
|
||
|
|
std::filesystem::create_directories(root);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"agentRole":"generator"})");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(root.string());
|
||
|
|
CHECK(result.found, "should find config");
|
||
|
|
CHECK(result.config.agentRole == "generator", "agent role mismatch");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_parses_workspace_alias() {
|
||
|
|
TEST(discover_parses_workspace_alias);
|
||
|
|
auto root = tempRoot() / "alias";
|
||
|
|
std::filesystem::create_directories(root);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"mcpWorkspaceAlias":"payments"})");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(root.string());
|
||
|
|
CHECK(result.found, "should find config");
|
||
|
|
CHECK(result.config.mcpWorkspaceAlias == "payments", "alias mismatch");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_with_no_argument_uses_current_path() {
|
||
|
|
TEST(discover_with_no_argument_uses_current_path);
|
||
|
|
auto root = tempRoot() / "cwd-default";
|
||
|
|
std::filesystem::create_directories(root);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"defaultLanguage":"cpp"})");
|
||
|
|
|
||
|
|
auto previous = std::filesystem::current_path();
|
||
|
|
std::filesystem::current_path(root);
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd();
|
||
|
|
std::filesystem::current_path(previous);
|
||
|
|
|
||
|
|
CHECK(result.found, "should find config from cwd");
|
||
|
|
CHECK(result.config.defaultLanguage == "cpp", "language mismatch");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_ignores_grandparent_when_parent_has_invalid_file() {
|
||
|
|
TEST(discover_ignores_grandparent_when_parent_has_invalid_file);
|
||
|
|
auto root = tempRoot() / "invalid-nearest";
|
||
|
|
auto parent = root / "service";
|
||
|
|
auto leaf = parent / "api";
|
||
|
|
std::filesystem::create_directories(leaf);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"defaultLanguage":"python"})");
|
||
|
|
writeFile(parent / ".whetstone.json", "{bad");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(leaf.string());
|
||
|
|
CHECK(result.found, "nearest file exists");
|
||
|
|
CHECK(result.error == "config_json_parse_failed", "should fail on nearest invalid file");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_discover_preserves_source_workspace_root_for_nested_start() {
|
||
|
|
TEST(discover_preserves_source_workspace_root_for_nested_start);
|
||
|
|
auto root = tempRoot() / "source-root";
|
||
|
|
auto nested = root / "a" / "b" / "c";
|
||
|
|
std::filesystem::create_directories(nested);
|
||
|
|
writeFile(root / ".whetstone.json", R"({"defaultLanguage":"typescript"})");
|
||
|
|
auto result = MCPProjectConfig::discoverFromCwd(nested.string());
|
||
|
|
CHECK(result.found, "should find config");
|
||
|
|
CHECK(result.sourceWorkspaceRoot == root.string(), "source root mismatch");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
std::cout << "Step 620: Config auto-discovery walk-up\n";
|
||
|
|
|
||
|
|
test_discover_from_exact_directory(); // 1
|
||
|
|
test_discover_walks_to_parent(); // 2
|
||
|
|
test_discover_prefers_nearest_config(); // 3
|
||
|
|
test_discover_returns_not_found_when_missing(); // 4
|
||
|
|
test_discover_reports_invalid_start_directory(); // 5
|
||
|
|
test_discover_reports_parse_error_from_nearest_file(); // 6
|
||
|
|
test_discover_parses_workspace_override(); // 7
|
||
|
|
test_discover_parses_agent_role(); // 8
|
||
|
|
test_discover_parses_workspace_alias(); // 9
|
||
|
|
test_discover_with_no_argument_uses_current_path(); // 10
|
||
|
|
test_discover_ignores_grandparent_when_parent_has_invalid_file();// 11
|
||
|
|
test_discover_preserves_source_workspace_root_for_nested_start();// 12
|
||
|
|
|
||
|
|
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||
|
|
return failed == 0 ? 0 : 1;
|
||
|
|
}
|