2cab53bc94
Test Vector Database Adaptors / Test MCP Vector DB Tools (push) Has been cancelled
Tests / Code Quality (Ruff & Mypy) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.12) (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers CLI - Convert documentation to AI skills dockerfile:Dockerfile name:skill-seekers]) (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers MCP Server - 25 tools for AI assistants dockerfile:Dockerfile.mcp name:skill-seekers-mcp]) (push) Has been cancelled
Docker Publish / Test Docker Images (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.10) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / Serial / Integration / E2E Tests (push) Has been cancelled
Tests / MCP Server Tests (push) Has been cancelled
Test Vector Database Adaptors / Test chroma Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test faiss Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test qdrant Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test weaviate Adaptor (push) Has been cancelled
337 lines
12 KiB
Python
337 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for architectural_pattern_detector.py - Framework detection.
|
|
|
|
Regression tests for:
|
|
- Issue #365: Unity C# projects misidentified as Unreal
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
|
|
|
from skill_seekers.cli.architectural_pattern_detector import ArchitecturalPatternDetector
|
|
|
|
|
|
@pytest.fixture
|
|
def detector():
|
|
return ArchitecturalPatternDetector(enhance_with_ai=False)
|
|
|
|
|
|
def _unity_files(root: str) -> list[dict]:
|
|
"""Simulate files_analysis for a Unity C# project."""
|
|
return [
|
|
{
|
|
"file": f"{root}/Assets/Scripts/Player.cs",
|
|
"language": "C#",
|
|
"imports": ["UnityEngine", "UnityEngine.UI", "System.Collections"],
|
|
},
|
|
{
|
|
"file": f"{root}/Assets/Scripts/GameManager.cs",
|
|
"language": "C#",
|
|
"imports": ["UnityEngine", "Zenject"],
|
|
},
|
|
{
|
|
"file": f"{root}/Assets/Scripts/Enemy.cs",
|
|
"language": "C#",
|
|
"imports": ["UnityEngine", "System.Collections.Generic"],
|
|
},
|
|
]
|
|
|
|
|
|
def _make_unity_dir(tmp_path: Path) -> Path:
|
|
"""Create a minimal Unity project directory structure."""
|
|
(tmp_path / "Assets").mkdir()
|
|
(tmp_path / "Library").mkdir()
|
|
(tmp_path / "Packages").mkdir()
|
|
(tmp_path / "ProjectSettings").mkdir()
|
|
(tmp_path / "Packages" / "manifest.json").write_text(
|
|
'{"dependencies": {"com.unity.2d.sprite": "1.0.0"}}'
|
|
)
|
|
(tmp_path / "ProjectSettings" / "ProjectVersion.txt").write_text("m_EditorVersion: 2022.3.10f1")
|
|
return tmp_path
|
|
|
|
|
|
class TestUnityFrameworkDetection:
|
|
"""Regression tests for Unity vs Unreal framework detection (Issue #365)."""
|
|
|
|
def test_unity_detected_via_imports(self, detector, tmp_path):
|
|
"""Unity project is detected correctly when C# files import UnityEngine."""
|
|
root = str(tmp_path)
|
|
_make_unity_dir(tmp_path)
|
|
files = _unity_files(root)
|
|
|
|
frameworks = detector._detect_frameworks(tmp_path, files)
|
|
|
|
assert "Unity" in frameworks, f"Expected Unity, got {frameworks}"
|
|
assert "Unreal" not in frameworks, f"Unreal should not be detected: {frameworks}"
|
|
|
|
def test_unity_not_misidentified_as_unreal_with_source_dir(self, detector, tmp_path):
|
|
"""Unity project with a 'Source' subfolder must NOT be identified as Unreal (Issue #365)."""
|
|
root = str(tmp_path)
|
|
_make_unity_dir(tmp_path)
|
|
# Simulate the common pattern: Assets/Scripts/Source/... exists
|
|
source_dir = tmp_path / "Assets" / "Scripts" / "Source"
|
|
source_dir.mkdir(parents=True)
|
|
|
|
files = _unity_files(root)
|
|
# Add a file whose path contains 'Source/' (the false-positive trigger for Unreal)
|
|
files.append(
|
|
{
|
|
"file": f"{root}/Assets/Scripts/Source/Utilities.cs",
|
|
"language": "C#",
|
|
"imports": ["UnityEngine", "System"],
|
|
}
|
|
)
|
|
|
|
frameworks = detector._detect_frameworks(tmp_path, files)
|
|
|
|
assert "Unity" in frameworks, f"Expected Unity, got {frameworks}"
|
|
assert "Unreal" not in frameworks, f"Unreal falsely detected: {frameworks}"
|
|
|
|
def test_unreal_project_still_detected(self, detector, tmp_path):
|
|
"""Genuine Unreal projects are still identified correctly."""
|
|
(tmp_path / "Source").mkdir()
|
|
(tmp_path / "Binaries").mkdir()
|
|
(tmp_path / "Content").mkdir()
|
|
(tmp_path / "Config").mkdir()
|
|
(tmp_path / "MyGame.uproject").write_text('{"FileVersion": 3}')
|
|
|
|
files = [
|
|
{
|
|
"file": f"{tmp_path}/Source/MyGame/MyGameCharacter.cpp",
|
|
"language": "C++",
|
|
"imports": [],
|
|
},
|
|
{
|
|
"file": f"{tmp_path}/Source/MyGame/MyGameCharacter.h",
|
|
"language": "C++",
|
|
"imports": [],
|
|
},
|
|
]
|
|
|
|
frameworks = detector._detect_frameworks(tmp_path, files)
|
|
|
|
assert "Unreal" in frameworks, f"Expected Unreal, got {frameworks}"
|
|
assert "Unity" not in frameworks, f"Unity should not be detected: {frameworks}"
|
|
|
|
def test_unity_detected_with_manifest_in_paths(self, detector, tmp_path):
|
|
"""Unity project is detected via Packages/manifest.json in file paths."""
|
|
root = str(tmp_path)
|
|
_make_unity_dir(tmp_path)
|
|
|
|
files = [
|
|
{
|
|
"file": f"{root}/Packages/manifest.json",
|
|
"language": "JSON",
|
|
"imports": [],
|
|
},
|
|
{
|
|
"file": f"{root}/Assets/Scripts/Player.cs",
|
|
"language": "C#",
|
|
"imports": ["UnityEngine"],
|
|
},
|
|
]
|
|
|
|
frameworks = detector._detect_frameworks(tmp_path, files)
|
|
|
|
assert "Unity" in frameworks, f"Expected Unity, got {frameworks}"
|
|
|
|
|
|
def _make_file(lang, file_path, imports=None):
|
|
return {"file": file_path, "language": lang, "imports": imports or []}
|
|
|
|
|
|
def _run_detector(directory, files_analysis, enhance_with_ai=False):
|
|
from skill_seekers.cli.architectural_pattern_detector import ArchitecturalPatternDetector
|
|
|
|
detector = ArchitecturalPatternDetector(enhance_with_ai=enhance_with_ai)
|
|
return detector.analyze(directory, files_analysis)
|
|
|
|
|
|
class TestDjangoDetection:
|
|
def test_django_via_imports(self, tmp_path):
|
|
files = [
|
|
_make_file("Python", "app/models.py", ["django.db"]),
|
|
_make_file("Python", "app/views.py", ["django.views"]),
|
|
]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Django" in report.frameworks_detected
|
|
|
|
def test_django_via_manage_py(self, tmp_path):
|
|
(tmp_path / "manage.py").touch()
|
|
(tmp_path / "settings.py").touch()
|
|
files = [_make_file("Python", "project/settings.py", ["django"])]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Django" in report.frameworks_detected
|
|
|
|
|
|
class TestFlaskDetection:
|
|
def test_flask_via_imports(self, tmp_path):
|
|
files = [
|
|
_make_file("Python", "app.py", ["flask"]),
|
|
_make_file("Python", "server.py", ["flask"]),
|
|
]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Flask" in report.frameworks_detected
|
|
|
|
def test_flask_via_wsgi(self, tmp_path):
|
|
(tmp_path / "wsgi.py").touch()
|
|
files = [_make_file("Python", "app.py", ["flask"])]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Flask" in report.frameworks_detected
|
|
|
|
def test_app_py_without_import_not_flask(self, tmp_path):
|
|
(tmp_path / "app.py").touch()
|
|
files = [_make_file("Python", "app.py")]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Flask" not in report.frameworks_detected or len(report.frameworks_detected) >= 0
|
|
|
|
|
|
class TestSpringDetection:
|
|
def test_spring_via_imports(self, tmp_path):
|
|
files = [_make_file("Java", "src/UserService.java", ["org.springframework"])]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Spring" in report.frameworks_detected
|
|
|
|
|
|
class TestAngularDetection:
|
|
def test_angular_via_imports(self, tmp_path):
|
|
files = [
|
|
_make_file("TypeScript", "src/app.module.ts", ["@angular"]),
|
|
_make_file("TypeScript", "src/component.ts", ["@angular/core"]),
|
|
]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Angular" in report.frameworks_detected
|
|
|
|
|
|
class TestExpressDetection:
|
|
def test_express_via_imports(self, tmp_path):
|
|
files = [_make_file("JavaScript", "app.js", ["express"])]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Express" in report.frameworks_detected
|
|
|
|
|
|
class TestRailsDetection:
|
|
def test_rails_via_imports(self, tmp_path):
|
|
files = [_make_file("Ruby", "config/routes.rb", ["rails"])]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Rails" in report.frameworks_detected
|
|
|
|
def test_rails_via_directory_structure(self, tmp_path):
|
|
app_dir = tmp_path / "app"
|
|
for d in ["models", "views", "controllers"]:
|
|
(app_dir / d).mkdir(parents=True)
|
|
(tmp_path / "config").mkdir()
|
|
(tmp_path / "config/routes.rb").write_text("Rails.application.routes.draw do\nend")
|
|
files = [
|
|
_make_file("Ruby", "app/models/user.rb"),
|
|
_make_file("Ruby", "app/controllers/users_controller.rb"),
|
|
]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Rails" in report.frameworks_detected
|
|
|
|
|
|
class TestGodotDetection:
|
|
def test_godot_via_project_file(self, tmp_path):
|
|
(tmp_path / "project.godot").write_text("[application]")
|
|
files = [_make_file("GDScript", "main.gd")]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Godot" in report.frameworks_detected
|
|
|
|
|
|
class TestWebFrameworkFiltering:
|
|
def test_csharp_project_not_web(self, tmp_path):
|
|
(tmp_path / "package.json").write_text('{"dependencies": {"react": "18.0.0"}}')
|
|
files = [
|
|
_make_file("C#", "Program.cs", ["Microsoft.AspNetCore"]),
|
|
_make_file("C#", "Controllers/HomeController.cs", ["System.Web"]),
|
|
]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "ASP.NET" in report.frameworks_detected
|
|
assert "React" not in report.frameworks_detected
|
|
|
|
|
|
class TestMultiFramework:
|
|
def test_game_engine_priority(self, tmp_path):
|
|
(tmp_path / "Packages").mkdir()
|
|
(tmp_path / "Packages/manifest.json").write_text("{}")
|
|
files = [_make_file("C#", "Scripts/Player.cs", ["UnityEngine", "flask"])]
|
|
report = _run_detector(tmp_path, files)
|
|
assert "Unity" in report.frameworks_detected
|
|
|
|
|
|
class TestEmptyProject:
|
|
def test_no_files_no_detection(self, tmp_path):
|
|
report = _run_detector(tmp_path, [])
|
|
assert report.frameworks_detected == []
|
|
assert report.patterns == []
|
|
assert report.total_files_analyzed == 0
|
|
|
|
|
|
class TestArchitecturalPatterns:
|
|
def test_mvc_directories(self, tmp_path):
|
|
for d in ["models", "views", "controllers"]:
|
|
(tmp_path / d).mkdir()
|
|
files = [
|
|
_make_file("Python", "models/user.py"),
|
|
_make_file("Python", "views/user_view.py"),
|
|
_make_file("Python", "controllers/user_ctrl.py"),
|
|
]
|
|
report = _run_detector(tmp_path, files)
|
|
mvc = [p for p in report.patterns if p.pattern_name == "MVC"]
|
|
assert len(mvc) >= 0
|
|
|
|
def test_repository_pattern(self, tmp_path):
|
|
(tmp_path / "repositories").mkdir()
|
|
(tmp_path / "repositories" / "user_repo.py").touch()
|
|
files = [_make_file("Python", "repositories/user_repo.py")]
|
|
report = _run_detector(tmp_path, files)
|
|
repo = [p for p in report.patterns if p.pattern_name == "Repository"]
|
|
assert len(repo) >= 0
|
|
|
|
def test_service_layer(self, tmp_path):
|
|
(tmp_path / "services").mkdir()
|
|
(tmp_path / "services" / "user_service.py").touch()
|
|
files = [_make_file("Python", "services/user_service.py")]
|
|
report = _run_detector(tmp_path, files)
|
|
svc = [p for p in report.patterns if p.pattern_name == "Service Layer"]
|
|
assert len(svc) >= 0
|
|
|
|
def test_layered_architecture(self, tmp_path):
|
|
for d in ["presentation", "business", "data"]:
|
|
(tmp_path / d).mkdir()
|
|
files = [
|
|
_make_file("Python", "presentation/ui.py"),
|
|
_make_file("Python", "business/logic.py"),
|
|
_make_file("Python", "data/db.py"),
|
|
]
|
|
report = _run_detector(tmp_path, files)
|
|
layered = [p for p in report.patterns if p.pattern_name == "Layered Architecture"]
|
|
assert len(layered) >= 0
|
|
|
|
|
|
class TestReportSerialization:
|
|
def test_report_to_dict(self, tmp_path):
|
|
files = [_make_file("Python", "app.py", ["flask"])]
|
|
report = _run_detector(tmp_path, files)
|
|
data = report.to_dict()
|
|
assert isinstance(data, dict)
|
|
assert "patterns" in data
|
|
assert "frameworks_detected" in data
|
|
|
|
|
|
def _make_file(lang, file_path, imports=None):
|
|
return {"file": file_path, "language": lang, "imports": imports or []}
|
|
|
|
|
|
def _run_detector(directory, files_analysis, enhance_with_ai=False):
|
|
from skill_seekers.cli.architectural_pattern_detector import ArchitecturalPatternDetector
|
|
|
|
detector = ArchitecturalPatternDetector(enhance_with_ai=enhance_with_ai)
|
|
return detector.analyze(directory, files_analysis)
|