From ea137943fc63a27f23cbe54d5509baf1f49b99df Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 24 Dec 2025 15:45:14 +0000 Subject: [PATCH] test: Add tests for access lists, proxy host creation, and config defaults --- tests/test_client.py | 113 +++++++++++++++++++++++++++++++++++++++++++ tests/test_config.py | 91 ++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 tests/test_config.py diff --git a/tests/test_client.py b/tests/test_client.py index 05364af..508ed41 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -35,6 +35,53 @@ def mock_proxy_hosts(): ] +@pytest.fixture +def mock_access_lists(): + """Mock access lists response.""" + return [ + { + "id": 1, + "created_on": "2024-01-01T00:00:00Z", + "modified_on": "2024-01-01T00:00:00Z", + "owner_user_id": 1, + "name": "Admin Only", + "satisfy_any": False, + "pass_auth": True, + }, + { + "id": 2, + "created_on": "2024-01-02T00:00:00Z", + "modified_on": "2024-01-02T00:00:00Z", + "owner_user_id": 1, + "name": "Internal Network", + "satisfy_any": True, + "pass_auth": False, + }, + ] + + +@pytest.fixture +def mock_created_proxy_host(): + """Mock response for created proxy host.""" + return { + "id": 42, + "created_on": "2024-01-15T10:00:00Z", + "modified_on": "2024-01-15T10:00:00Z", + "owner_user_id": 1, + "domain_names": ["newapp.example.com"], + "forward_host": "10.0.0.50", + "forward_port": 3000, + "forward_scheme": "http", + "enabled": True, + "ssl_forced": True, + "certificate_id": 24, + "block_exploits": True, + "allow_websocket_upgrade": True, + "access_list_id": 0, + "advanced_config": "", + } + + class TestNpmClientAuth: """Test authentication logic.""" @@ -114,3 +161,69 @@ class TestNpmClientEndpoints: assert hosts[0].id == 1 assert hosts[0].domain_names == ["example.com"] assert hosts[0].forward_host == "192.168.1.100" + + @pytest.mark.asyncio + async def test_get_access_lists(self, httpx_mock, mock_token_response, mock_access_lists): + """Test fetching access lists.""" + httpx_mock.add_response( + method="POST", + url="http://localhost:81/api/tokens", + json=mock_token_response, + ) + httpx_mock.add_response( + method="GET", + url="http://localhost:81/api/nginx/access-lists", + json=mock_access_lists, + ) + + async with NpmClient( + base_url="http://localhost:81/api", + identity="test@test.com", + secret="password", + ) as client: + access_lists = await client.get_access_lists() + + assert len(access_lists) == 2 + assert access_lists[0].id == 1 + assert access_lists[0].name == "Admin Only" + assert access_lists[0].pass_auth is True + assert access_lists[1].id == 2 + assert access_lists[1].name == "Internal Network" + assert access_lists[1].satisfy_any is True + + @pytest.mark.asyncio + async def test_create_proxy_host( + self, httpx_mock, mock_token_response, mock_created_proxy_host + ): + """Test creating a proxy host.""" + httpx_mock.add_response( + method="POST", + url="http://localhost:81/api/tokens", + json=mock_token_response, + ) + httpx_mock.add_response( + method="POST", + url="http://localhost:81/api/nginx/proxy-hosts", + json=mock_created_proxy_host, + status_code=201, + ) + + async with NpmClient( + base_url="http://localhost:81/api", + identity="test@test.com", + secret="password", + ) as client: + host = await client.create_proxy_host( + domain_names=["newapp.example.com"], + forward_host="10.0.0.50", + forward_port=3000, + certificate_id=24, + ssl_forced=True, + ) + + assert host.id == 42 + assert host.domain_names == ["newapp.example.com"] + assert host.forward_host == "10.0.0.50" + assert host.forward_port == 3000 + assert host.ssl_forced is True + assert host.certificate_id == 24 diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..ff5c04e --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,91 @@ +"""Tests for configuration handling.""" + +import pytest +from pydantic_settings.exceptions import SettingsError + +from npm_mcp.config import DEFAULT_PROXY_SETTINGS, Settings + + +class TestProxyDefaults: + """Test NPM_PROXY_DEFAULTS parsing and merging.""" + + def test_default_proxy_settings(self): + """Test that default settings are correct.""" + settings = Settings(identity="test", secret="test") + defaults = settings.get_proxy_defaults() + + assert defaults["forward_scheme"] == "http" + assert defaults["certificate_id"] == 0 + assert defaults["ssl_forced"] is True + assert defaults["block_exploits"] is True + assert defaults["allow_websocket_upgrade"] is True + assert defaults["access_list_id"] == 0 + assert defaults["advanced_config"] == "" + + def test_proxy_defaults_json_parsing(self, monkeypatch): + """Test parsing JSON string from environment variable.""" + monkeypatch.setenv("NPM_IDENTITY", "test") + monkeypatch.setenv("NPM_SECRET", "test") + monkeypatch.setenv("NPM_PROXY_DEFAULTS", '{"certificate_id": 24, "ssl_forced": false}') + + settings = Settings() + defaults = settings.get_proxy_defaults() + + # Overridden values + assert defaults["certificate_id"] == 24 + assert defaults["ssl_forced"] is False + + # Default values preserved + assert defaults["forward_scheme"] == "http" + assert defaults["block_exploits"] is True + + def test_proxy_defaults_dict_passthrough(self): + """Test that dict values pass through correctly.""" + settings = Settings( + identity="test", + secret="test", + proxy_defaults={"certificate_id": 18, "access_list_id": 5}, + ) + defaults = settings.get_proxy_defaults() + + assert defaults["certificate_id"] == 18 + assert defaults["access_list_id"] == 5 + + def test_proxy_defaults_empty_env_raises(self, monkeypatch): + """Test that empty string env var raises SettingsError.""" + monkeypatch.setenv("NPM_IDENTITY", "test") + monkeypatch.setenv("NPM_SECRET", "test") + monkeypatch.setenv("NPM_PROXY_DEFAULTS", "") + + # pydantic-settings tries to JSON decode empty string and fails + with pytest.raises(SettingsError): + Settings() + + def test_proxy_defaults_invalid_json_raises(self, monkeypatch): + """Test that invalid JSON raises SettingsError.""" + monkeypatch.setenv("NPM_IDENTITY", "test") + monkeypatch.setenv("NPM_SECRET", "test") + monkeypatch.setenv("NPM_PROXY_DEFAULTS", "{not valid json}") + + # pydantic-settings tries to JSON decode and fails + with pytest.raises(SettingsError): + Settings() + + def test_proxy_defaults_merges_not_replaces(self): + """Test that user defaults merge with base defaults.""" + settings = Settings( + identity="test", + secret="test", + proxy_defaults={"certificate_id": 24}, + ) + defaults = settings.get_proxy_defaults() + + # All keys should be present + assert set(defaults.keys()) == set(DEFAULT_PROXY_SETTINGS.keys()) + + # User value applied + assert defaults["certificate_id"] == 24 + + # Other defaults preserved + assert defaults["ssl_forced"] is True + assert defaults["block_exploits"] is True