Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@ openspace = [
"local_server/config.json",
"local_server/README.md",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
56 changes: 56 additions & 0 deletions tests/cloud/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import importlib.util
import io
import zipfile
from pathlib import Path

import pytest


def _load_module():
module_path = Path(__file__).resolve().parents[2] / "openspace" / "cloud" / "client.py"
spec = importlib.util.spec_from_file_location("openspace_cloud_client_test", module_path)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(module)
return module


_client = _load_module()
CloudError = _client.CloudError
OpenSpaceClient = _client.OpenSpaceClient


def _zip_bytes(files: dict[str, str]) -> bytes:
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, mode="w") as zf:
for name, content in files.items():
zf.writestr(name, content)
return buffer.getvalue()


def test_extract_zip_skips_path_traversal_entries(tmp_path: Path):
zip_data = _zip_bytes(
{
"SKILL.md": "name: demo",
"../escape.txt": "nope",
"/absolute.txt": "nope",
"nested/file.txt": "ok",
}
)

extracted = OpenSpaceClient._extract_zip(zip_data, tmp_path)

assert extracted == ["SKILL.md", "nested/file.txt"]
assert (tmp_path / "SKILL.md").read_text(encoding="utf-8") == "name: demo"
assert (tmp_path / "nested" / "file.txt").read_text(encoding="utf-8") == "ok"


def test_validate_origin_parents_enforces_fixed_origin():
with pytest.raises(CloudError, match="exactly 1 parent_skill_id"):
OpenSpaceClient._validate_origin_parents("fixed", [])

OpenSpaceClient._validate_origin_parents("fixed", ["parent-1"])


def test_unified_diff_returns_none_when_snapshots_match():
assert OpenSpaceClient._unified_diff({"a.txt": "same\n"}, {"a.txt": "same\n"}) is None
42 changes: 42 additions & 0 deletions tests/config/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import importlib.util
from pathlib import Path


def _load_module():
module_path = Path(__file__).resolve().parents[2] / "openspace" / "config" / "utils.py"
spec = importlib.util.spec_from_file_location("openspace_config_utils_test", module_path)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(module)
return module


_utils = _load_module()
get_config_value = _utils.get_config_value
load_json_file = _utils.load_json_file
save_json_file = _utils.save_json_file


def test_pytest_scaffold_uses_tests_directory(pytestconfig):
assert pytestconfig.getini("testpaths") == ["tests"]


def test_get_config_value_supports_dict_and_object():
config_dict = {"value": 42}

class ConfigObject:
value = 42

assert get_config_value(config_dict, "value") == 42
assert get_config_value(ConfigObject(), "value") == 42
assert get_config_value(config_dict, "missing", "fallback") == "fallback"


def test_save_and_load_json_round_trip(tmp_path: Path):
payload = {"name": "openspace", "nested": {"enabled": True}}
target = tmp_path / "nested" / "config.json"

save_json_file(payload, target)

assert target.exists()
assert load_json_file(target) == payload
85 changes: 85 additions & 0 deletions tests/host_detection/test_nanobot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import importlib.util
from pathlib import Path


def _load_module():
module_path = Path(__file__).resolve().parents[2] / "openspace" / "host_detection" / "nanobot.py"
spec = importlib.util.spec_from_file_location("openspace_nanobot_test", module_path)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(module)
return module


_nanobot = _load_module()
match_provider = _nanobot.match_provider


def test_match_provider_prefers_forced_provider():
providers = {
"openai": {"apiKey": "oa-key", "apiBase": "https://openai.example/v1"},
"minimax": {"apiKey": "mini-key", "apiBase": "https://minimax.example/v1"},
}

result = match_provider(providers, model="anything", forced_provider="minimax")

assert result == {
"api_key": "mini-key",
"api_base": "https://minimax.example/v1",
}


def test_read_nanobot_mcp_env_returns_openspace_env(tmp_path: Path, monkeypatch):
config_path = tmp_path / "config.json"
config_path.write_text(
"""
{
"tools": {
"mcpServers": {
"openspace": {
"env": {
"OPENSPACE_MODEL": "openrouter/anthropic/claude"
}
}
}
}
}
""".strip(),
encoding="utf-8",
)
monkeypatch.setattr(_nanobot, "NANOBOT_CONFIG_PATH", config_path)

assert _nanobot.read_nanobot_mcp_env() == {"OPENSPACE_MODEL": "openrouter/anthropic/claude"}


def test_try_read_nanobot_config_extracts_model_and_provider(tmp_path: Path, monkeypatch):
config_path = tmp_path / "config.json"
config_path.write_text(
"""
{
"providers": {
"minimax": {
"apiKey": "mini-key",
"apiBase": "https://minimax.example/v1"
}
},
"agents": {
"defaults": {
"model": "minimax/text-01",
"provider": "minimax"
}
}
}
""".strip(),
encoding="utf-8",
)
monkeypatch.setattr(_nanobot, "NANOBOT_CONFIG_PATH", config_path)

result = _nanobot.try_read_nanobot_config("")

assert result == {
"api_key": "mini-key",
"api_base": "https://minimax.example/v1",
"_model": "minimax/text-01",
"_forced_provider": "minimax",
}