288 lines
10 KiB
Python
288 lines
10 KiB
Python
"""Tests for CLI options using Click's testing utilities."""
|
|
|
|
import os
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from nextcloud_mcp_server.cli import run
|
|
|
|
|
|
@pytest.fixture
|
|
def runner():
|
|
"""Create a Click CLI runner."""
|
|
return CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def clean_env(monkeypatch):
|
|
"""Clean environment variables before each test."""
|
|
env_vars = [
|
|
"NEXTCLOUD_HOST",
|
|
"NEXTCLOUD_USERNAME",
|
|
"NEXTCLOUD_PASSWORD",
|
|
"NEXTCLOUD_OIDC_CLIENT_ID",
|
|
"NEXTCLOUD_OIDC_CLIENT_SECRET",
|
|
"NEXTCLOUD_OIDC_SCOPES",
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE",
|
|
"NEXTCLOUD_MCP_SERVER_URL",
|
|
"NEXTCLOUD_PUBLIC_ISSUER_URL",
|
|
]
|
|
for var in env_vars:
|
|
monkeypatch.delenv(var, raising=False)
|
|
|
|
|
|
def test_help_message_displays_all_options(runner):
|
|
"""Test that help message includes all new CLI options."""
|
|
result = runner.invoke(run, ["--help"])
|
|
assert result.exit_code == 0
|
|
|
|
# Check for new options
|
|
assert "--nextcloud-host" in result.output
|
|
assert "--nextcloud-username" in result.output
|
|
assert "--nextcloud-password" in result.output
|
|
assert "--oauth-scopes" in result.output
|
|
assert "--oauth-token-type" in result.output
|
|
assert "--public-issuer-url" in result.output
|
|
|
|
# Check for existing options
|
|
assert "--oauth-client-id" in result.output
|
|
assert "--oauth-client-secret" in result.output
|
|
assert "--mcp-server-url" in result.output
|
|
|
|
|
|
def test_token_type_accepts_valid_values(runner, clean_env):
|
|
"""Test that --oauth-token-type accepts bearer and jwt (case insensitive)."""
|
|
# Test lowercase bearer
|
|
result = runner.invoke(run, ["--oauth-token-type", "bearer", "--help"])
|
|
assert result.exit_code == 0
|
|
|
|
# Test lowercase jwt
|
|
result = runner.invoke(run, ["--oauth-token-type", "jwt", "--help"])
|
|
assert result.exit_code == 0
|
|
|
|
# Test uppercase (should work with case_sensitive=False)
|
|
result = runner.invoke(run, ["--oauth-token-type", "Bearer", "--help"])
|
|
assert result.exit_code == 0
|
|
|
|
result = runner.invoke(run, ["--oauth-token-type", "JWT", "--help"])
|
|
assert result.exit_code == 0
|
|
|
|
|
|
def test_token_type_rejects_invalid_values(runner, clean_env):
|
|
"""Test that --oauth-token-type rejects invalid values."""
|
|
result = runner.invoke(run, ["--oauth-token-type", "invalid"])
|
|
assert result.exit_code != 0
|
|
assert "Invalid value" in result.output
|
|
|
|
|
|
def test_cli_options_set_environment_variables(runner, clean_env, monkeypatch):
|
|
"""Test that CLI options set environment variables correctly."""
|
|
# We need to mock the actual server startup to avoid connection errors
|
|
# Store the env vars that get set
|
|
captured_env = {}
|
|
|
|
def mock_get_app(*args, **kwargs):
|
|
# Capture environment variables after they're set by CLI
|
|
captured_env.update(
|
|
{
|
|
"NEXTCLOUD_HOST": os.environ.get("NEXTCLOUD_HOST"),
|
|
"NEXTCLOUD_USERNAME": os.environ.get("NEXTCLOUD_USERNAME"),
|
|
"NEXTCLOUD_PASSWORD": os.environ.get("NEXTCLOUD_PASSWORD"),
|
|
"NEXTCLOUD_OIDC_SCOPES": os.environ.get("NEXTCLOUD_OIDC_SCOPES"),
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE": os.environ.get(
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE"
|
|
),
|
|
"NEXTCLOUD_PUBLIC_ISSUER_URL": os.environ.get(
|
|
"NEXTCLOUD_PUBLIC_ISSUER_URL"
|
|
),
|
|
"NEXTCLOUD_MCP_SERVER_URL": os.environ.get("NEXTCLOUD_MCP_SERVER_URL"),
|
|
}
|
|
)
|
|
# Raise an exception to stop execution before uvicorn.run
|
|
raise SystemExit(0)
|
|
|
|
# Patch get_app to capture env vars
|
|
monkeypatch.setattr("nextcloud_mcp_server.cli.get_app", mock_get_app)
|
|
|
|
_ = runner.invoke(
|
|
run,
|
|
[
|
|
"--nextcloud-host",
|
|
"https://test.example.com",
|
|
"--nextcloud-username",
|
|
"testuser",
|
|
"--nextcloud-password",
|
|
"testpass",
|
|
"--oauth-scopes",
|
|
"openid nc:read",
|
|
"--oauth-token-type",
|
|
"jwt",
|
|
"--public-issuer-url",
|
|
"https://public.example.com",
|
|
"--mcp-server-url",
|
|
"http://test:8000",
|
|
],
|
|
)
|
|
|
|
# Verify environment variables were set
|
|
assert captured_env["NEXTCLOUD_HOST"] == "https://test.example.com"
|
|
assert captured_env["NEXTCLOUD_USERNAME"] == "testuser"
|
|
assert captured_env["NEXTCLOUD_PASSWORD"] == "testpass"
|
|
assert captured_env["NEXTCLOUD_OIDC_SCOPES"] == "openid nc:read"
|
|
assert captured_env["NEXTCLOUD_OIDC_TOKEN_TYPE"] == "jwt"
|
|
assert captured_env["NEXTCLOUD_PUBLIC_ISSUER_URL"] == "https://public.example.com"
|
|
assert captured_env["NEXTCLOUD_MCP_SERVER_URL"] == "http://test:8000"
|
|
|
|
|
|
def test_cli_options_override_environment_variables(runner, monkeypatch):
|
|
"""Test that CLI options override environment variables."""
|
|
# Set environment variables
|
|
monkeypatch.setenv("NEXTCLOUD_HOST", "https://from-env.example.com")
|
|
monkeypatch.setenv("NEXTCLOUD_USERNAME", "envuser")
|
|
monkeypatch.setenv("NEXTCLOUD_OIDC_SCOPES", "openid")
|
|
monkeypatch.setenv("NEXTCLOUD_OIDC_TOKEN_TYPE", "bearer")
|
|
|
|
captured_env = {}
|
|
|
|
def mock_get_app(*args, **kwargs):
|
|
captured_env.update(
|
|
{
|
|
"NEXTCLOUD_HOST": os.environ.get("NEXTCLOUD_HOST"),
|
|
"NEXTCLOUD_USERNAME": os.environ.get("NEXTCLOUD_USERNAME"),
|
|
"NEXTCLOUD_OIDC_SCOPES": os.environ.get("NEXTCLOUD_OIDC_SCOPES"),
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE": os.environ.get(
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE"
|
|
),
|
|
}
|
|
)
|
|
raise SystemExit(0)
|
|
|
|
monkeypatch.setattr("nextcloud_mcp_server.cli.get_app", mock_get_app)
|
|
|
|
# Provide CLI options that should override env vars
|
|
_ = runner.invoke(
|
|
run,
|
|
[
|
|
"--nextcloud-host",
|
|
"https://from-cli.example.com",
|
|
"--nextcloud-username",
|
|
"cliuser",
|
|
"--oauth-scopes",
|
|
"openid nc:write",
|
|
"--oauth-token-type",
|
|
"jwt",
|
|
],
|
|
)
|
|
|
|
# Verify CLI options overrode env vars
|
|
assert captured_env["NEXTCLOUD_HOST"] == "https://from-cli.example.com"
|
|
assert captured_env["NEXTCLOUD_USERNAME"] == "cliuser"
|
|
assert captured_env["NEXTCLOUD_OIDC_SCOPES"] == "openid nc:write"
|
|
assert captured_env["NEXTCLOUD_OIDC_TOKEN_TYPE"] == "jwt"
|
|
|
|
|
|
def test_environment_variables_used_when_cli_not_provided(runner, monkeypatch):
|
|
"""Test that environment variables are used when CLI options not provided."""
|
|
# Set environment variables
|
|
monkeypatch.setenv("NEXTCLOUD_HOST", "https://from-env.example.com")
|
|
monkeypatch.setenv("NEXTCLOUD_USERNAME", "envuser")
|
|
monkeypatch.setenv("NEXTCLOUD_PASSWORD", "envpass")
|
|
monkeypatch.setenv("NEXTCLOUD_OIDC_SCOPES", "openid email")
|
|
monkeypatch.setenv("NEXTCLOUD_OIDC_TOKEN_TYPE", "jwt")
|
|
monkeypatch.setenv("NEXTCLOUD_PUBLIC_ISSUER_URL", "https://public-env.example.com")
|
|
|
|
captured_env = {}
|
|
|
|
def mock_get_app(*args, **kwargs):
|
|
captured_env.update(
|
|
{
|
|
"NEXTCLOUD_HOST": os.environ.get("NEXTCLOUD_HOST"),
|
|
"NEXTCLOUD_USERNAME": os.environ.get("NEXTCLOUD_USERNAME"),
|
|
"NEXTCLOUD_PASSWORD": os.environ.get("NEXTCLOUD_PASSWORD"),
|
|
"NEXTCLOUD_OIDC_SCOPES": os.environ.get("NEXTCLOUD_OIDC_SCOPES"),
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE": os.environ.get(
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE"
|
|
),
|
|
"NEXTCLOUD_PUBLIC_ISSUER_URL": os.environ.get(
|
|
"NEXTCLOUD_PUBLIC_ISSUER_URL"
|
|
),
|
|
}
|
|
)
|
|
raise SystemExit(0)
|
|
|
|
monkeypatch.setattr("nextcloud_mcp_server.cli.get_app", mock_get_app)
|
|
|
|
# Don't provide any CLI options - should use env vars
|
|
_ = runner.invoke(run, [])
|
|
|
|
# Verify env vars were used
|
|
assert captured_env["NEXTCLOUD_HOST"] == "https://from-env.example.com"
|
|
assert captured_env["NEXTCLOUD_USERNAME"] == "envuser"
|
|
assert captured_env["NEXTCLOUD_PASSWORD"] == "envpass"
|
|
assert captured_env["NEXTCLOUD_OIDC_SCOPES"] == "openid email"
|
|
assert captured_env["NEXTCLOUD_OIDC_TOKEN_TYPE"] == "jwt"
|
|
assert (
|
|
captured_env["NEXTCLOUD_PUBLIC_ISSUER_URL"] == "https://public-env.example.com"
|
|
)
|
|
|
|
|
|
def test_default_values(runner, clean_env, monkeypatch):
|
|
"""Test that default values are used when neither CLI nor env vars provided."""
|
|
captured_env = {}
|
|
|
|
def mock_get_app(*args, **kwargs):
|
|
captured_env.update(
|
|
{
|
|
"NEXTCLOUD_OIDC_SCOPES": os.environ.get("NEXTCLOUD_OIDC_SCOPES"),
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE": os.environ.get(
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE"
|
|
),
|
|
"NEXTCLOUD_MCP_SERVER_URL": os.environ.get("NEXTCLOUD_MCP_SERVER_URL"),
|
|
}
|
|
)
|
|
raise SystemExit(0)
|
|
|
|
monkeypatch.setattr("nextcloud_mcp_server.cli.get_app", mock_get_app)
|
|
|
|
# Don't provide CLI options or env vars - should use defaults
|
|
_ = runner.invoke(run, [])
|
|
|
|
# Verify default values
|
|
assert captured_env["NEXTCLOUD_OIDC_SCOPES"] == (
|
|
"openid profile email "
|
|
"notes:read notes:write "
|
|
"calendar:read calendar:write "
|
|
"todo:read todo:write "
|
|
"contacts:read contacts:write "
|
|
"cookbook:read cookbook:write "
|
|
"deck:read deck:write "
|
|
"tables:read tables:write "
|
|
"files:read files:write "
|
|
"sharing:read sharing:write"
|
|
)
|
|
assert captured_env["NEXTCLOUD_OIDC_TOKEN_TYPE"] == "bearer"
|
|
assert captured_env["NEXTCLOUD_MCP_SERVER_URL"] == "http://localhost:8000"
|
|
|
|
|
|
def test_oauth_token_type_case_normalization(runner, clean_env, monkeypatch):
|
|
"""Test that token type is normalized correctly regardless of input case."""
|
|
captured_env = {}
|
|
|
|
def mock_get_app(*args, **kwargs):
|
|
captured_env["NEXTCLOUD_OIDC_TOKEN_TYPE"] = os.environ.get(
|
|
"NEXTCLOUD_OIDC_TOKEN_TYPE"
|
|
)
|
|
raise SystemExit(0)
|
|
|
|
monkeypatch.setattr("nextcloud_mcp_server.cli.get_app", mock_get_app)
|
|
|
|
# Test uppercase JWT
|
|
runner.invoke(run, ["--oauth-token-type", "JWT"])
|
|
assert captured_env["NEXTCLOUD_OIDC_TOKEN_TYPE"] in ["JWT", "jwt"]
|
|
|
|
# Test mixed case Bearer
|
|
captured_env.clear()
|
|
runner.invoke(run, ["--oauth-token-type", "Bearer"])
|
|
assert captured_env["NEXTCLOUD_OIDC_TOKEN_TYPE"] in ["Bearer", "bearer"]
|