修复了一些错误 更新了AI.md(给ai看的)
This commit is contained in:
165
oss/tests/conftest.py
Normal file
165
oss/tests/conftest.py
Normal file
@@ -0,0 +1,165 @@
|
||||
Pytest configuration and shared fixtures
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def temp_data_dir():
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
store_dir = Path(temp_dir) / "store"
|
||||
store_dir.mkdir()
|
||||
|
||||
(store_dir / "@{NebulaShell}").mkdir()
|
||||
(store_dir / "@{Falck}").mkdir()
|
||||
|
||||
yield str(store_dir)
|
||||
|
||||
import shutil
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config(temp_data_dir, temp_store_dir):
|
||||
from oss.config.config import _global_config
|
||||
original_config = _global_config
|
||||
_global_config = None
|
||||
|
||||
yield
|
||||
|
||||
_global_config = original_config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plugin_dir(temp_store_dir):
|
||||
from oss.plugin.types import Plugin
|
||||
|
||||
class TestPlugin(Plugin):
|
||||
def __init__(self):
|
||||
self.name = "test-plugin"
|
||||
self.version = "1.0.0"
|
||||
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def New():
|
||||
return TestPlugin()
|
||||
{
|
||||
"metadata": {
|
||||
"name": "test-plugin",
|
||||
"version": "1.0.0",
|
||||
"author": "Test Author",
|
||||
"description": "A test plugin"
|
||||
},
|
||||
"config": {
|
||||
"args": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"permissions": []
|
||||
}
|
||||
plugin_dir = Path(sample_plugin_dir)
|
||||
|
||||
pl_dir = plugin_dir / "PL"
|
||||
pl_dir.mkdir()
|
||||
|
||||
pl_main = pl_dir / "main.py"
|
||||
with open(pl_main, 'w') as f:
|
||||
f.write(
|
||||
import sys
|
||||
import types
|
||||
from typing import Any, Optional, Dict
|
||||
|
||||
from oss.plugin.types import Plugin, register_plugin_type
|
||||
|
||||
class Log:
|
||||
@classmethod
|
||||
def info(cls, tag: str, msg: str): print(f"[{tag}] {msg}")
|
||||
@classmethod
|
||||
def warn(cls, tag: str, msg: str): print(f"[{tag}] ⚠ {msg}")
|
||||
@classmethod
|
||||
def error(cls, tag: str, msg: str): print(f"[{tag}] ✗ {msg}")
|
||||
@classmethod
|
||||
def ok(cls, tag: str, msg: str): print(f"[{tag}] {msg}")
|
||||
|
||||
class PluginInfo:
|
||||
def __init__(self):
|
||||
self.name: str = ""
|
||||
self.version: str = ""
|
||||
self.author: str = ""
|
||||
self.description: str = ""
|
||||
self.readme: str = ""
|
||||
self.config: dict[str, Any] = {}
|
||||
self.extensions: dict[str, Any] = {}
|
||||
self.lifecycle: Any = None
|
||||
self.capabilities: set[str] = set()
|
||||
self.dependencies: list[str] = []
|
||||
|
||||
class PluginManager:
|
||||
def __init__(self):
|
||||
self.plugins: dict = {}
|
||||
self.lifecycle_plugin = None
|
||||
self._dependency_plugin = None
|
||||
self._signature_verifier = None
|
||||
|
||||
def load_all(self, store_dir: str = "store"):
|
||||
pass
|
||||
|
||||
def init_and_start_all(self):
|
||||
pass
|
||||
|
||||
def stop_all(self):
|
||||
pass
|
||||
|
||||
class PluginLoaderPlugin(Plugin):
|
||||
def __init__(self):
|
||||
self.manager = PluginManager()
|
||||
self._loaded = False
|
||||
self._started = False
|
||||
|
||||
def init(self, deps: dict = None):
|
||||
if self._loaded: return
|
||||
self._loaded = True
|
||||
Log.info("plugin-loader", "开始加载插件...")
|
||||
self.manager.load_all()
|
||||
|
||||
def start(self):
|
||||
if self._started: return
|
||||
self._started = True
|
||||
Log.info("plugin-loader", "启动插件...")
|
||||
self.manager.init_and_start_all()
|
||||
|
||||
def stop(self):
|
||||
Log.info("plugin-loader", "停止插件...")
|
||||
self.manager.stop_all()
|
||||
|
||||
register_plugin_type("PluginManager", PluginManager)
|
||||
register_plugin_type("PluginInfo", PluginInfo)
|
||||
|
||||
def New():
|
||||
return PluginLoaderPlugin()
|
||||
pass
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
for item in items:
|
||||
if "plugin_loader" in item.nodeid or "plugin_dir" in item.nodeid:
|
||||
item.add_marker(pytest.mark.plugin)
|
||||
|
||||
if "integration" in item.nodeid:
|
||||
item.add_marker(pytest.mark.integration)
|
||||
|
||||
if "slow" in item.nodeid:
|
||||
item.add_marker(pytest.mark.slow)
|
||||
141
oss/tests/test_config.py
Normal file
141
oss/tests/test_config.py
Normal file
@@ -0,0 +1,141 @@
|
||||
Tests for Configuration Management
|
||||
|
||||
import os
|
||||
import json
|
||||
import tempfile
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from oss.config import Config, get_config, init_config
|
||||
|
||||
|
||||
class TestConfig:
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
config_file = os.path.join(temp_dir, "config.json")
|
||||
|
||||
config_data = {
|
||||
"HTTP_API_PORT": 9000,
|
||||
"HTTP_TCP_PORT": 9002,
|
||||
"HOST": "127.0.0.1",
|
||||
"DATA_DIR": "./test_data",
|
||||
"STORE_DIR": "./test_store",
|
||||
"LOG_LEVEL": "DEBUG",
|
||||
"PERMISSION_CHECK": False,
|
||||
"MAX_WORKERS": 8,
|
||||
"API_KEY": "test-key",
|
||||
"CORS_ALLOWED_ORIGINS": ["http://localhost:8080"]
|
||||
}
|
||||
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(config_data, f)
|
||||
|
||||
yield config_file
|
||||
|
||||
os.remove(config_file)
|
||||
os.rmdir(temp_dir)
|
||||
|
||||
def test_config_initialization_defaults(self):
|
||||
config = Config(temp_config_file)
|
||||
|
||||
assert config.get("HTTP_API_PORT") == 9000
|
||||
assert config.get("HTTP_TCP_PORT") == 9002
|
||||
assert config.get("HOST") == "127.0.0.1"
|
||||
assert config.get("DATA_DIR") == "./test_data"
|
||||
assert config.get("STORE_DIR") == "./test_store"
|
||||
assert config.get("LOG_LEVEL") == "DEBUG"
|
||||
assert config.get("PERMISSION_CHECK") is False
|
||||
assert config.get("MAX_WORKERS") == 8
|
||||
assert config.get("API_KEY") == "test-key"
|
||||
assert config.get("CORS_ALLOWED_ORIGINS") == ["http://localhost:8080"]
|
||||
|
||||
def test_config_load_from_nonexistent_file(self):
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
config_file = os.path.join(temp_dir, "invalid_config.json")
|
||||
|
||||
with open(config_file, 'w') as f:
|
||||
f.write("{ invalid json")
|
||||
|
||||
config = Config(config_file)
|
||||
|
||||
assert config.get("HTTP_API_PORT") == 8080
|
||||
|
||||
os.remove(config_file)
|
||||
os.rmdir(temp_dir)
|
||||
|
||||
def test_config_load_from_env(self):
|
||||
os.environ["HTTP_API_PORT"] = "7000"
|
||||
os.environ["HOST"] = "192.168.1.1"
|
||||
|
||||
try:
|
||||
config = Config(temp_config_file)
|
||||
|
||||
assert config.get("HTTP_TCP_PORT") == 9002
|
||||
assert config.get("DATA_DIR") == "./test_data"
|
||||
|
||||
assert config.get("HTTP_API_PORT") == 7000
|
||||
assert config.get("HOST") == "192.168.1.1"
|
||||
finally:
|
||||
for key in ["HTTP_API_PORT", "HOST"]:
|
||||
if key in os.environ:
|
||||
del os.environ[key]
|
||||
|
||||
def test_config_env_type_conversion(self):
|
||||
os.environ["HTTP_API_PORT"] = "not_a_number"
|
||||
os.environ["PERMISSION_CHECK"] = "not_a_boolean"
|
||||
|
||||
try:
|
||||
config = Config()
|
||||
|
||||
assert config.get("HTTP_API_PORT") == 8080
|
||||
assert config.get("PERMISSION_CHECK") is True
|
||||
finally:
|
||||
for key in ["HTTP_API_PORT", "PERMISSION_CHECK"]:
|
||||
if key in os.environ:
|
||||
del os.environ[key]
|
||||
|
||||
def test_config_get_with_default(self):
|
||||
config = Config()
|
||||
|
||||
config.set("HTTP_API_PORT", 9000)
|
||||
assert config.get("HTTP_API_PORT") == 9000
|
||||
|
||||
config.set("NONEXISTENT_KEY", "value")
|
||||
assert config.get("NONEXISTENT_KEY") is None
|
||||
|
||||
def test_config_all(self):
|
||||
config = Config()
|
||||
|
||||
assert isinstance(config.http_api_port, int)
|
||||
assert isinstance(config.http_tcp_port, int)
|
||||
assert isinstance(config.host, str)
|
||||
assert isinstance(config.data_dir, Path)
|
||||
assert isinstance(config.store_dir, Path)
|
||||
assert isinstance(config.log_level, str)
|
||||
assert isinstance(config.permission_check, bool)
|
||||
|
||||
assert config.http_api_port == 8080
|
||||
assert config.http_tcp_port == 8082
|
||||
assert config.host == "0.0.0.0"
|
||||
assert config.data_dir == Path("./data")
|
||||
assert config.store_dir == Path("./store")
|
||||
assert config.log_level == "INFO"
|
||||
assert config.permission_check is True
|
||||
|
||||
|
||||
class TestGlobalConfig:
|
||||
config1 = get_config()
|
||||
config2 = get_config()
|
||||
|
||||
assert config1 is config2
|
||||
|
||||
def test_init_config(self):
|
||||
config = init_config(temp_config_file)
|
||||
|
||||
assert isinstance(config, Config)
|
||||
assert config.get("HTTP_API_PORT") == 9000
|
||||
|
||||
assert config is get_config()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
32
oss/tests/test_fixes.py
Normal file
32
oss/tests/test_fixes.py
Normal file
@@ -0,0 +1,32 @@
|
||||
Simple test to verify our fixes
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from oss.config import Config
|
||||
from oss.logger.logger import Logger
|
||||
|
||||
|
||||
def test_cors_fix():
|
||||
config = Config()
|
||||
|
||||
assert config.get("LOG_FILE") == ""
|
||||
assert config.get("LOG_MAX_SIZE") == 10485760 assert config.get("LOG_BACKUP_COUNT") == 5
|
||||
|
||||
os.environ["LOG_FILE"] = "/tmp/test.log"
|
||||
os.environ["LOG_MAX_SIZE"] = "20971520" os.environ["LOG_BACKUP_COUNT"] = "10"
|
||||
|
||||
config = Config()
|
||||
|
||||
assert config.get("LOG_FILE") == "/tmp/test.log"
|
||||
assert config.get("LOG_MAX_SIZE") == 20971520
|
||||
assert config.get("LOG_BACKUP_COUNT") == 10
|
||||
|
||||
for key in ["LOG_FILE", "LOG_MAX_SIZE", "LOG_BACKUP_COUNT"]:
|
||||
if key in os.environ:
|
||||
del os.environ[key]
|
||||
|
||||
|
||||
def test_logger_functionality():
|
||||
137
oss/tests/test_http_api.py
Normal file
137
oss/tests/test_http_api.py
Normal file
@@ -0,0 +1,137 @@
|
||||
Tests for HTTP API
|
||||
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from oss.config import get_config
|
||||
from oss.logger.logger import Log
|
||||
from store.@{NebulaShell}.http-api.server import HttpServer, Request, Response
|
||||
from store.@{NebulaShell}.http-api.middleware import MiddlewareChain, CorsMiddleware, AuthMiddleware, LoggerMiddleware
|
||||
|
||||
|
||||
class TestRequest:
|
||||
req = Request("GET", "/test", {"Content-Type": "application/json"}, '{"test": true}')
|
||||
|
||||
assert req.method == "GET"
|
||||
assert req.path == "/test"
|
||||
assert req.headers == {"Content-Type": "application/json"}
|
||||
assert req.body == '{"test": true}'
|
||||
assert req.path_params == {}
|
||||
|
||||
|
||||
class TestResponse:
|
||||
resp = Response()
|
||||
|
||||
assert resp.status == 200
|
||||
assert resp.headers == {}
|
||||
assert resp.body == ""
|
||||
|
||||
def test_response_initialization_with_params(self):
|
||||
|
||||
@pytest.fixture
|
||||
def mock_router(self):
|
||||
return MiddlewareChain()
|
||||
|
||||
def test_http_server_initialization(self, mock_router, middleware_chain):
|
||||
server = HttpServer(mock_router, middleware_chain, host="127.0.0.1", port=9000)
|
||||
|
||||
assert server.host == "127.0.0.1"
|
||||
assert server.port == 9000
|
||||
|
||||
@patch('store.@{NebulaShell}.http-api.server.HTTPServer')
|
||||
def test_http_server_start(self, mock_http_server, mock_router, middleware_chain):
|
||||
server = HttpServer(mock_router, middleware_chain)
|
||||
|
||||
mock_server_instance = Mock()
|
||||
server._server = mock_server_instance
|
||||
|
||||
server.stop()
|
||||
|
||||
mock_server_instance.shutdown.assert_called_once()
|
||||
|
||||
|
||||
class TestMiddleware:
|
||||
from store.@{NebulaShell}.http-api.middleware import Middleware
|
||||
|
||||
class TestMiddleware(Middleware):
|
||||
def process(self, ctx, next_fn):
|
||||
return next_fn()
|
||||
|
||||
middleware = TestMiddleware()
|
||||
ctx = {}
|
||||
next_fn = Mock(return_value=None)
|
||||
|
||||
result = middleware.process(ctx, next_fn)
|
||||
|
||||
next_fn.assert_called_once()
|
||||
assert result is None
|
||||
|
||||
def test_cors_middleware_process(self):
|
||||
middleware = AuthMiddleware()
|
||||
ctx = {"request": Request("GET", "/api/test", {}, "")}
|
||||
next_fn = Mock(return_value=None)
|
||||
|
||||
with patch('store.@{NebulaShell}.http-api.middleware.get_config') as mock_get_config:
|
||||
mock_get_config.return_value.get.return_value = ""
|
||||
|
||||
result = middleware.process(ctx, next_fn)
|
||||
|
||||
next_fn.assert_called_once()
|
||||
assert result is None
|
||||
|
||||
def test_auth_middleware_process_public_path(self):
|
||||
middleware = AuthMiddleware()
|
||||
ctx = {"request": Request("GET", "/api/test", {"Authorization": "Bearer test-key"}, "")}
|
||||
next_fn = Mock(return_value=None)
|
||||
|
||||
with patch('store.@{NebulaShell}.http-api.middleware.get_config') as mock_get_config:
|
||||
mock_get_config.return_value.get.return_value = "test-key"
|
||||
|
||||
result = middleware.process(ctx, next_fn)
|
||||
|
||||
next_fn.assert_called_once()
|
||||
assert result is None
|
||||
|
||||
def test_auth_middleware_process_with_invalid_token(self):
|
||||
middleware = LoggerMiddleware()
|
||||
ctx = {"request": Request("GET", "/api/test", {}, "")}
|
||||
next_fn = Mock(return_value=None)
|
||||
|
||||
with patch.object(Log, 'info') as mock_log:
|
||||
result = middleware.process(ctx, next_fn)
|
||||
|
||||
next_fn.assert_called_once()
|
||||
mock_log.assert_called_once_with("http-api", "GET /api/test")
|
||||
assert result is None
|
||||
|
||||
def test_logger_middleware_process_silent_path(self):
|
||||
|
||||
def test_middleware_chain_initialization(self):
|
||||
chain = MiddlewareChain()
|
||||
initial_count = len(chain.middlewares)
|
||||
|
||||
mock_middleware = Mock()
|
||||
chain.add(mock_middleware)
|
||||
|
||||
assert len(chain.middlewares) == initial_count + 1
|
||||
assert chain.middlewares[-1] is mock_middleware
|
||||
|
||||
def test_middleware_chain_run(self):
|
||||
chain = MiddlewareChain()
|
||||
ctx = {}
|
||||
|
||||
response = Response(status=401, body='{"error": "Unauthorized"}')
|
||||
chain.middlewares[0].process = Mock(return_value=response)
|
||||
|
||||
result = chain.run(ctx)
|
||||
|
||||
chain.middlewares[0].process.assert_called_once()
|
||||
for middleware in chain.middlewares[1:]:
|
||||
middleware.process.assert_not_called()
|
||||
|
||||
assert result is response
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
104
oss/tests/test_logger.py
Normal file
104
oss/tests/test_logger.py
Normal file
@@ -0,0 +1,104 @@
|
||||
Tests for Logger
|
||||
|
||||
import logging
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import patch, Mock
|
||||
from io import StringIO
|
||||
|
||||
from oss.logger.logger import Logger
|
||||
|
||||
|
||||
class TestLogger:
|
||||
return Logger("test")
|
||||
|
||||
def test_logger_initialization(self):
|
||||
logger = Logger("test")
|
||||
|
||||
with patch.object(logger.logger, 'info') as mock_info:
|
||||
logger.info("Test message")
|
||||
|
||||
mock_info.assert_called_once_with("Test message")
|
||||
|
||||
def test_logger_warn(self):
|
||||
logger = Logger("test")
|
||||
|
||||
with patch.object(logger.logger, 'error') as mock_error:
|
||||
logger.error("Test error")
|
||||
|
||||
mock_error.assert_called_once_with("Test error")
|
||||
|
||||
def test_logger_debug(self):
|
||||
logger = Logger("test")
|
||||
|
||||
with patch.object(logger.logger, 'info') as mock_info:
|
||||
logger.info("Test message", "TAG")
|
||||
|
||||
mock_info.assert_called_once_with("[TAG] Test message")
|
||||
|
||||
def test_logger_warn_with_tag(self):
|
||||
logger = Logger("test")
|
||||
|
||||
with patch.object(logger.logger, 'error') as mock_error:
|
||||
logger.error("Test error", "TAG")
|
||||
|
||||
mock_error.assert_called_once_with("[TAG] Test error")
|
||||
|
||||
def test_logger_debug_with_tag(self):
|
||||
logger = Logger("test")
|
||||
|
||||
format_str = logger._get_log_format()
|
||||
|
||||
assert "%(asctime)s" in format_str
|
||||
assert "%(name)s" in format_str
|
||||
assert "%(levelname)s" in format_str
|
||||
assert "%(message)s" in format_str
|
||||
|
||||
def test_get_log_format_json(self):
|
||||
os.environ["LOG_FORMAT"] = "json"
|
||||
|
||||
try:
|
||||
logger = Logger("test")
|
||||
format_str = logger._get_log_format()
|
||||
|
||||
assert "%(asctime)s" in format_str
|
||||
assert "%(name)s" in format_str
|
||||
assert "%(levelname)s" in format_str
|
||||
assert "%(message)s" in format_str
|
||||
finally:
|
||||
if "LOG_FORMAT" in os.environ:
|
||||
del os.environ["LOG_FORMAT"]
|
||||
|
||||
def test_logger_json_format(self):
|
||||
|
||||
def test_logger_output(self):
|
||||
log_capture = StringIO()
|
||||
|
||||
logger = logging.getLogger("test_json")
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
handler = logging.StreamHandler(log_capture)
|
||||
formatter = logging.Formatter(
|
||||
'{"time": "%(asctime)s", "name": "%(name)s", "level": "%(levelname)s", "message": "%(message)s"}'
|
||||
)
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
logger.info("Test JSON message")
|
||||
|
||||
log_output = log_capture.getvalue().strip()
|
||||
assert log_output.startswith("{")
|
||||
assert log_output.endswith("}")
|
||||
assert "test_json" in log_output
|
||||
assert "INFO" in log_output
|
||||
assert "Test JSON message" in log_output
|
||||
|
||||
try:
|
||||
import json
|
||||
json.loads(log_output)
|
||||
except json.JSONDecodeError:
|
||||
pytest.fail("Log output is not valid JSON")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Tests for Node.js Adapter Plugin
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -9,11 +7,9 @@ import tempfile
|
||||
import shutil
|
||||
import pytest
|
||||
|
||||
# Add the plugin directory to path
|
||||
PLUGIN_DIR = os.path.join(os.path.dirname(__file__), '..', 'store', '@{NebulaShell}', 'nodejs-adapter')
|
||||
sys.path.insert(0, PLUGIN_DIR)
|
||||
|
||||
# Import after path update
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location("nodejs_adapter_main", os.path.join(PLUGIN_DIR, "main.py"))
|
||||
main_module = importlib.util.module_from_spec(spec)
|
||||
@@ -22,76 +18,23 @@ NodeJSAdapter = main_module.NodeJSAdapter
|
||||
|
||||
|
||||
class TestNodeJSAdapter:
|
||||
"""Test suite for NodeJSAdapter class"""
|
||||
|
||||
@pytest.fixture
|
||||
def adapter(self):
|
||||
"""Create a fresh adapter instance"""
|
||||
return NodeJSAdapter()
|
||||
|
||||
@pytest.fixture
|
||||
def temp_plugin_dir(self):
|
||||
"""Create a temporary plugin directory structure"""
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
pkg_dir = os.path.join(temp_dir, 'pkg')
|
||||
os.makedirs(pkg_dir)
|
||||
|
||||
# Create a minimal package.json
|
||||
package_json = {
|
||||
"name": "test-plugin",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"test": "echo 'test passed'"
|
||||
}
|
||||
}
|
||||
with open(os.path.join(pkg_dir, 'package.json'), 'w') as f:
|
||||
json.dump(package_json, f)
|
||||
|
||||
yield temp_dir
|
||||
|
||||
# Cleanup
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
def test_adapter_initialization(self, adapter):
|
||||
"""Test that adapter initializes correctly"""
|
||||
assert adapter.name == "nodejs-adapter"
|
||||
assert adapter.version == "1.0.0"
|
||||
assert "Node.js" in adapter.description
|
||||
|
||||
def test_get_capabilities(self, adapter):
|
||||
"""Test capabilities reporting"""
|
||||
caps = adapter.get_capabilities()
|
||||
|
||||
assert 'available' in caps
|
||||
assert 'npm_available' in caps
|
||||
assert 'versions' in caps
|
||||
assert 'features' in caps
|
||||
assert isinstance(caps['features'], list)
|
||||
|
||||
def test_check_versions(self, adapter):
|
||||
"""Test version checking"""
|
||||
versions = adapter.check_versions()
|
||||
|
||||
# Should return dict with node and/or npm keys
|
||||
assert isinstance(versions, dict)
|
||||
# At least one should be present if runtime exists
|
||||
if adapter.node_path:
|
||||
assert 'node' in versions
|
||||
assert not versions['node'].startswith('Error')
|
||||
|
||||
def test_execute_in_context_missing_dir(self, adapter):
|
||||
"""Test execution with non-existent directory"""
|
||||
if not adapter.node_path:
|
||||
pytest.skip("Node.js not available")
|
||||
|
||||
result = adapter.execute_in_context('/nonexistent/path', ['--version'])
|
||||
|
||||
assert result['success'] is False
|
||||
assert 'error' in result
|
||||
assert 'not found' in result['error'].lower()
|
||||
|
||||
def test_execute_in_context_node_version(self, adapter, temp_plugin_dir):
|
||||
"""Test executing node --version in context"""
|
||||
if not adapter.node_path:
|
||||
pytest.skip("Node.js not available")
|
||||
|
||||
@@ -100,24 +43,9 @@ class TestNodeJSAdapter:
|
||||
assert result['success'] is True
|
||||
assert 'cwd' in result
|
||||
assert result['cwd'].endswith('pkg')
|
||||
# Version should start with v
|
||||
assert result['stdout'].strip().startswith('v')
|
||||
|
||||
def test_execute_in_context_npm_version(self, adapter, temp_plugin_dir):
|
||||
"""Test executing npm --version in context"""
|
||||
if not adapter.npm_path:
|
||||
pytest.skip("npm not available")
|
||||
|
||||
result = adapter.execute_in_context(temp_plugin_dir, ['--version'], is_npm=True)
|
||||
|
||||
assert result['success'] is True
|
||||
assert 'cwd' in result
|
||||
assert result['cwd'].endswith('pkg')
|
||||
# Version should be numeric (possibly with dots)
|
||||
assert len(result['stdout'].strip()) > 0
|
||||
|
||||
def test_install_dependencies_empty(self, adapter, temp_plugin_dir):
|
||||
"""Test installing dependencies (empty, just reads package.json)"""
|
||||
if not adapter.npm_path:
|
||||
pytest.skip("npm not available")
|
||||
|
||||
@@ -128,21 +56,9 @@ class TestNodeJSAdapter:
|
||||
assert result['cwd'].endswith('pkg')
|
||||
|
||||
def test_run_script_test(self, adapter, temp_plugin_dir):
|
||||
"""Test running a custom npm script"""
|
||||
if not adapter.npm_path:
|
||||
pytest.skip("npm not available")
|
||||
|
||||
result = adapter.run_script(temp_plugin_dir, 'test')
|
||||
|
||||
assert result['success'] is True
|
||||
assert 'test passed' in result['stdout']
|
||||
|
||||
def test_run_file(self, adapter, temp_plugin_dir):
|
||||
"""Test running a JavaScript file"""
|
||||
if not adapter.node_path:
|
||||
pytest.skip("Node.js not available")
|
||||
|
||||
# Create a simple JS file
|
||||
js_file = os.path.join(temp_plugin_dir, 'pkg', 'hello.js')
|
||||
with open(js_file, 'w') as f:
|
||||
f.write("console.log('Hello from Node.js');")
|
||||
@@ -153,50 +69,8 @@ class TestNodeJSAdapter:
|
||||
assert 'Hello from Node.js' in result['stdout']
|
||||
|
||||
def test_init_project(self, adapter, temp_plugin_dir):
|
||||
"""Test initializing a new project"""
|
||||
if not adapter.npm_path:
|
||||
pytest.skip("npm not available")
|
||||
|
||||
# Create empty pkg dir for this test
|
||||
pkg_dir = os.path.join(temp_plugin_dir, 'pkg2')
|
||||
os.makedirs(pkg_dir)
|
||||
|
||||
# Create a minimal package.json first (npm init -y creates one)
|
||||
package_json = {"name": "temp", "version": "1.0.0"}
|
||||
with open(os.path.join(pkg_dir, 'package.json'), 'w') as f:
|
||||
json.dump(package_json, f)
|
||||
|
||||
# Manually test the logic since execute_in_context targets ./pkg by default
|
||||
pkg_json_path = os.path.join(pkg_dir, 'package.json')
|
||||
|
||||
# Simulate what init_project does
|
||||
data = {"name": "custom-test-project", "version": "1.0.0", "private": True}
|
||||
with open(pkg_json_path, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
# Verify
|
||||
with open(pkg_json_path, 'r') as f:
|
||||
pkg_data = json.load(f)
|
||||
assert pkg_data['name'] == 'custom-test-project'
|
||||
assert pkg_data['private'] is True
|
||||
|
||||
|
||||
class TestPluginLifecycle:
|
||||
"""Test plugin lifecycle hooks"""
|
||||
|
||||
def test_init_hook(self):
|
||||
"""Test init hook registers service"""
|
||||
init = main_module.init
|
||||
|
||||
context = {}
|
||||
result = init(context)
|
||||
|
||||
assert result['status'] == 'ready'
|
||||
assert 'nodejs-adapter' in context['services']
|
||||
assert 'runtime_available' in result
|
||||
|
||||
def test_start_hook(self):
|
||||
"""Test start hook"""
|
||||
start = main_module.start
|
||||
|
||||
context = {}
|
||||
@@ -205,16 +79,6 @@ class TestPluginLifecycle:
|
||||
assert result['status'] == 'active'
|
||||
|
||||
def test_stop_hook(self):
|
||||
"""Test stop hook"""
|
||||
stop = main_module.stop
|
||||
|
||||
context = {}
|
||||
result = stop(context)
|
||||
|
||||
assert result['status'] == 'inactive'
|
||||
|
||||
def test_get_info_hook(self):
|
||||
"""Test get_info hook"""
|
||||
init = main_module.init
|
||||
get_info = main_module.get_info
|
||||
|
||||
|
||||
73
oss/tests/test_plugin_manager.py
Normal file
73
oss/tests/test_plugin_manager.py
Normal file
@@ -0,0 +1,73 @@
|
||||
Tests for Plugin Manager
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from oss.plugin.manager import PluginManager
|
||||
from oss.plugin.loader import PluginLoader
|
||||
|
||||
|
||||
class TestPluginManager:
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
store_dir = Path(temp_dir) / "store"
|
||||
store_dir.mkdir()
|
||||
|
||||
plugin_loader_dir = store_dir / "@{NebulaShell}" / "plugin-loader"
|
||||
plugin_loader_dir.mkdir(parents=True)
|
||||
|
||||
main_py = plugin_loader_dir / "main.py"
|
||||
with open(main_py, 'w') as f:
|
||||
f.write(
|
||||
from oss.plugin.types import Plugin
|
||||
|
||||
class TestPlugin(Plugin):
|
||||
def __init__(self):
|
||||
self.name = "test-plugin"
|
||||
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def New():
|
||||
return TestPlugin()
|
||||
loader = PluginLoader()
|
||||
assert loader.loaded == {}
|
||||
assert loader._config is not None
|
||||
|
||||
def test_load_plugin_with_main_py(self, temp_plugin_dir):
|
||||
loader = PluginLoader()
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
plugin_dir = Path(temp_dir) / "empty-plugin"
|
||||
plugin_dir.mkdir()
|
||||
|
||||
result = loader._load_plugin("empty-plugin", plugin_dir)
|
||||
|
||||
assert result is None
|
||||
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
def test_load_plugin_without_new_function(self):
|
||||
loader = PluginLoader()
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
plugin_dir = Path(temp_dir) / "syntax-error-plugin"
|
||||
plugin_dir.mkdir()
|
||||
|
||||
main_py = plugin_dir / "main.py"
|
||||
with open(main_py, 'w') as f:
|
||||
f.write("def broken_function(\n
|
||||
result = loader._load_plugin("syntax-error-plugin", plugin_dir)
|
||||
|
||||
assert result is None
|
||||
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
Reference in New Issue
Block a user