Initial commit: MCP Hub Community Edition v3.0.0
Community edition generated from private repo via sync pipeline. Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with ~587 tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
270
tests/test_community_build.py
Normal file
270
tests/test_community_build.py
Normal file
@@ -0,0 +1,270 @@
|
||||
"""Tests for Community Build Sync Script."""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
# Import sync module from scripts
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts" / "community-build"))
|
||||
from sync import (
|
||||
SyncReport,
|
||||
apply_branding_transform,
|
||||
collect_files,
|
||||
parse_communityignore,
|
||||
scan_for_secrets,
|
||||
should_exclude,
|
||||
should_transform,
|
||||
sync,
|
||||
)
|
||||
|
||||
|
||||
class TestParseCommunityignore:
|
||||
"""Test .communityignore parsing."""
|
||||
|
||||
def test_parse_valid_file(self, tmp_path):
|
||||
ignore_file = tmp_path / ".communityignore"
|
||||
ignore_file.write_text("# comment\n\nfoo/\n*.pyc\nbar.txt\n")
|
||||
patterns = parse_communityignore(ignore_file)
|
||||
assert patterns == ["foo/", "*.pyc", "bar.txt"]
|
||||
|
||||
def test_parse_missing_file(self, tmp_path):
|
||||
patterns = parse_communityignore(tmp_path / "nonexistent")
|
||||
assert patterns == []
|
||||
|
||||
def test_skip_comments_and_empty(self, tmp_path):
|
||||
ignore_file = tmp_path / ".communityignore"
|
||||
ignore_file.write_text("# header\n\n# another\nreal_pattern\n\n")
|
||||
patterns = parse_communityignore(ignore_file)
|
||||
assert patterns == ["real_pattern"]
|
||||
|
||||
|
||||
class TestShouldExclude:
|
||||
"""Test file exclusion logic."""
|
||||
|
||||
def test_directory_pattern(self):
|
||||
assert should_exclude("docs/plans/file.md", ["docs/plans/"]) is True
|
||||
assert should_exclude("docs/other/file.md", ["docs/plans/"]) is False
|
||||
|
||||
def test_exact_match(self):
|
||||
assert should_exclude("MASTER_CONTEXT.md", ["MASTER_CONTEXT.md"]) is True
|
||||
assert should_exclude("README.md", ["MASTER_CONTEXT.md"]) is False
|
||||
|
||||
def test_glob_pattern(self):
|
||||
assert should_exclude("module.pyc", ["*.pyc"]) is True
|
||||
assert should_exclude("module.py", ["*.pyc"]) is False
|
||||
|
||||
def test_nested_directory(self):
|
||||
assert should_exclude("__pycache__/module.cpython-311.pyc", ["__pycache__/"]) is True
|
||||
|
||||
def test_no_patterns(self):
|
||||
assert should_exclude("any/file.txt", []) is False
|
||||
|
||||
def test_component_match(self):
|
||||
"""Pattern matching against directory components."""
|
||||
assert should_exclude("deep/__pycache__/file.pyc", ["__pycache__/"]) is True
|
||||
|
||||
|
||||
class TestScanForSecrets:
|
||||
"""Test secret detection."""
|
||||
|
||||
def test_no_secrets(self, tmp_path):
|
||||
f = tmp_path / "clean.py"
|
||||
f.write_text("x = 1\nprint('hello')\n")
|
||||
assert scan_for_secrets(f, "clean.py") == []
|
||||
|
||||
def test_detects_private_key(self, tmp_path):
|
||||
f = tmp_path / "bad.pem"
|
||||
f.write_text("-----BEGIN PRIVATE KEY-----\ndata\n-----END PRIVATE KEY-----\n")
|
||||
warnings = scan_for_secrets(f, "bad.pem")
|
||||
assert len(warnings) > 0
|
||||
|
||||
def test_allowlisted_file_skipped(self, tmp_path):
|
||||
f = tmp_path / "env.example"
|
||||
f.write_text('password = "super_secret_value"\n')
|
||||
assert scan_for_secrets(f, "env.example") == []
|
||||
|
||||
def test_detects_api_key_pattern(self, tmp_path):
|
||||
f = tmp_path / "config.py"
|
||||
f.write_text('api_key = "AKIAIOSFODNN7EXAMPLE12345"\n')
|
||||
warnings = scan_for_secrets(f, "config.py")
|
||||
assert len(warnings) > 0
|
||||
|
||||
|
||||
class TestSync:
|
||||
"""Test full sync operation."""
|
||||
|
||||
@pytest.fixture
|
||||
def source_tree(self, tmp_path):
|
||||
"""Create a mock source tree."""
|
||||
src = tmp_path / "source"
|
||||
src.mkdir()
|
||||
|
||||
# Create .communityignore
|
||||
(src / ".communityignore").write_text("secret/\n*.log\nINTERNAL.md\n.communityignore\n")
|
||||
|
||||
# Create files that should be copied
|
||||
(src / "README.md").write_text("# Public Readme")
|
||||
(src / "core").mkdir()
|
||||
(src / "core" / "auth.py").write_text("class AuthManager: pass")
|
||||
|
||||
# Create files that should be excluded
|
||||
(src / "secret").mkdir()
|
||||
(src / "secret" / "keys.json").write_text('{"key": "value"}')
|
||||
(src / "INTERNAL.md").write_text("Internal docs")
|
||||
(src / "app.log").write_text("log data")
|
||||
|
||||
return src
|
||||
|
||||
def test_dry_run(self, source_tree, tmp_path):
|
||||
output = tmp_path / "output"
|
||||
report = sync(source_tree, output, dry_run=True)
|
||||
|
||||
assert report.total_copied > 0
|
||||
assert report.total_excluded > 0
|
||||
assert not output.exists() # Nothing should be created
|
||||
|
||||
def test_actual_sync(self, source_tree, tmp_path):
|
||||
output = tmp_path / "output"
|
||||
report = sync(source_tree, output)
|
||||
|
||||
assert (output / "README.md").exists()
|
||||
assert (output / "core" / "auth.py").exists()
|
||||
assert not (output / "secret").exists()
|
||||
assert not (output / "INTERNAL.md").exists()
|
||||
assert not (output / "app.log").exists()
|
||||
|
||||
def test_communityignore_excluded(self, source_tree, tmp_path):
|
||||
output = tmp_path / "output"
|
||||
report = sync(source_tree, output)
|
||||
|
||||
# .communityignore itself should be excluded
|
||||
assert not (output / ".communityignore").exists()
|
||||
assert ".communityignore" in report.excluded
|
||||
|
||||
|
||||
class TestSyncReport:
|
||||
"""Test report generation."""
|
||||
|
||||
def test_summary_format(self):
|
||||
report = SyncReport(
|
||||
copied=["a.py", "b.py"],
|
||||
excluded=["c.log"],
|
||||
source_dir="/src",
|
||||
output_dir="/out",
|
||||
timestamp="2026-02-17 12:00:00 UTC",
|
||||
)
|
||||
summary = report.summary()
|
||||
assert "Files copied: **2**" in summary
|
||||
assert "Files excluded: **1**" in summary
|
||||
|
||||
def test_summary_with_warnings(self):
|
||||
report = SyncReport(
|
||||
copied=["a.py"],
|
||||
excluded=[],
|
||||
secret_warnings=["file.py: found secret"],
|
||||
source_dir="/src",
|
||||
output_dir="/out",
|
||||
timestamp="now",
|
||||
)
|
||||
summary = report.summary()
|
||||
assert "Secret Warnings" in summary
|
||||
|
||||
def test_summary_has_review_checklist(self):
|
||||
report = SyncReport(
|
||||
copied=["a.py"],
|
||||
excluded=[],
|
||||
source_dir="/src",
|
||||
output_dir="/out",
|
||||
timestamp="now",
|
||||
)
|
||||
summary = report.summary()
|
||||
assert "Pre-Publish Review Checklist" in summary
|
||||
assert "APPROVED FOR PUBLISH" in summary
|
||||
|
||||
|
||||
class TestBrandingTransform:
|
||||
"""Test branding transform (B.3)."""
|
||||
|
||||
def test_replaces_package_name(self):
|
||||
content = 'name = "coolify-mcp-hub"'
|
||||
transformed, changed = apply_branding_transform(content)
|
||||
assert changed is True
|
||||
assert 'name = "mcphub"' in transformed
|
||||
|
||||
def test_replaces_display_name(self):
|
||||
content = "Welcome to Coolify MCP Hub"
|
||||
transformed, changed = apply_branding_transform(content)
|
||||
assert "MCP Hub" in transformed
|
||||
assert "Coolify" not in transformed
|
||||
|
||||
def test_replaces_urls(self):
|
||||
content = "Homepage: https://airano.ir"
|
||||
transformed, changed = apply_branding_transform(content)
|
||||
assert "mcphub.dev" in transformed
|
||||
assert "airano.ir" not in transformed
|
||||
|
||||
def test_replaces_repo_urls(self):
|
||||
content = "https://gitea.airano.ir/dev/coolify-mcp-hub"
|
||||
transformed, changed = apply_branding_transform(content)
|
||||
assert "github.com/mcphub/mcphub" in transformed
|
||||
|
||||
def test_replaces_email(self):
|
||||
content = "Contact: gitea@airano.ir"
|
||||
transformed, changed = apply_branding_transform(content)
|
||||
assert "hello@mcphub.dev" in transformed
|
||||
|
||||
def test_strips_private_comments(self):
|
||||
content = "line1\n# PRIVATE: internal note\nline3\n"
|
||||
transformed, changed = apply_branding_transform(content)
|
||||
assert "PRIVATE" not in transformed
|
||||
assert "line1" in transformed
|
||||
assert "line3" in transformed
|
||||
|
||||
def test_no_changes_returns_false(self):
|
||||
content = "clean content with no markers"
|
||||
transformed, changed = apply_branding_transform(content)
|
||||
assert changed is False
|
||||
|
||||
def test_should_transform_pyproject(self):
|
||||
assert should_transform("pyproject.toml") is True
|
||||
|
||||
def test_should_transform_readme(self):
|
||||
assert should_transform("README.md") is True
|
||||
|
||||
def test_should_transform_docs(self):
|
||||
assert should_transform("docs/OAUTH_GUIDE.md") is True
|
||||
|
||||
def test_should_transform_python(self):
|
||||
assert should_transform("core/auth.py") is True
|
||||
assert should_transform("server.py") is True
|
||||
assert should_transform("plugins/wordpress/plugin.py") is True
|
||||
|
||||
def test_should_not_transform_binary(self):
|
||||
assert should_transform("core/data.bin") is False
|
||||
assert should_transform("images/logo.png") is False
|
||||
|
||||
def test_transform_applied_in_sync(self, tmp_path):
|
||||
"""Full sync should apply transforms to matching files."""
|
||||
src = tmp_path / "source"
|
||||
src.mkdir()
|
||||
(src / ".communityignore").write_text(".communityignore\n")
|
||||
(src / "README.md").write_text("Welcome to Coolify MCP Hub\nhttps://airano.ir\n")
|
||||
(src / "core").mkdir()
|
||||
(src / "core" / "auth.py").write_text("# coolify-mcp-hub internal")
|
||||
|
||||
output = tmp_path / "output"
|
||||
report = sync(src, output)
|
||||
|
||||
# README.md should be transformed
|
||||
readme = (output / "README.md").read_text()
|
||||
assert "MCP Hub" in readme
|
||||
assert "Coolify" not in readme
|
||||
assert "mcphub.dev" in readme
|
||||
assert "README.md" in report.transformed
|
||||
|
||||
# auth.py SHOULD be transformed (Python files now in TRANSFORM_GLOBS)
|
||||
auth = (output / "core" / "auth.py").read_text()
|
||||
assert "mcphub" in auth # transformed
|
||||
Reference in New Issue
Block a user