dclient/tests/test_config.py
2026-02-26 21:19:32 -08:00

251 lines
8.4 KiB
Python

"""Tests for the v2 config system: config.json format, instance resolution, database resolution."""
from dclient.cli import (
_load_config,
_save_config,
_resolve_instance,
_resolve_database,
_resolve_token,
)
import json
import pathlib
import pytest
# -- Config loading/saving --
def test_load_config_empty(tmpdir):
"""Loading config when no file exists returns empty defaults."""
config_file = pathlib.Path(tmpdir) / "config.json"
config = _load_config(config_file)
assert config == {"default_instance": None, "instances": {}}
def test_load_config_existing(tmpdir):
"""Loading config reads the JSON file."""
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": "prod",
"instances": {
"prod": {
"url": "https://myapp.datasette.cloud",
"default_database": "main",
}
},
}
)
)
config = _load_config(config_file)
assert config["default_instance"] == "prod"
assert config["instances"]["prod"]["url"] == "https://myapp.datasette.cloud"
assert config["instances"]["prod"]["default_database"] == "main"
def test_save_config(tmpdir):
"""Saving config writes JSON to disk."""
config_file = pathlib.Path(tmpdir) / "config.json"
config = {
"default_instance": "local",
"instances": {
"local": {
"url": "http://localhost:8001",
"default_database": None,
}
},
}
_save_config(config_file, config)
assert json.loads(config_file.read_text()) == config
# -- Instance resolution --
def test_resolve_instance_from_flag(tmpdir):
"""An explicit -i flag with a URL is used directly."""
config_file = pathlib.Path(tmpdir) / "config.json"
url = _resolve_instance("https://example.com", config_file)
assert url == "https://example.com"
def test_resolve_instance_from_flag_alias(tmpdir):
"""An explicit -i flag with an alias name resolves via config."""
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": None,
"instances": {
"prod": {
"url": "https://myapp.datasette.cloud",
"default_database": None,
}
},
}
)
)
url = _resolve_instance("prod", config_file)
assert url == "https://myapp.datasette.cloud"
def test_resolve_instance_from_config_default(tmpdir):
"""When no -i flag, uses config.default_instance."""
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": "prod",
"instances": {
"prod": {
"url": "https://myapp.datasette.cloud",
"default_database": None,
}
},
}
)
)
url = _resolve_instance(None, config_file)
assert url == "https://myapp.datasette.cloud"
def test_resolve_instance_from_env(tmpdir, monkeypatch):
"""When no -i flag and no config default, falls back to DATASETTE_URL."""
config_file = pathlib.Path(tmpdir) / "config.json"
monkeypatch.setenv("DATASETTE_URL", "https://env.example.com")
url = _resolve_instance(None, config_file)
assert url == "https://env.example.com"
def test_resolve_instance_from_env_strips_trailing_slash(tmpdir, monkeypatch):
"""DATASETTE_URL trailing slash is stripped."""
config_file = pathlib.Path(tmpdir) / "config.json"
monkeypatch.setenv("DATASETTE_URL", "https://env.example.com/")
url = _resolve_instance(None, config_file)
assert url == "https://env.example.com"
def test_resolve_instance_error(tmpdir, monkeypatch):
"""When nothing is configured, raises an error."""
config_file = pathlib.Path(tmpdir) / "config.json"
monkeypatch.delenv("DATASETTE_URL", raising=False)
with pytest.raises(Exception, match="No instance specified"):
_resolve_instance(None, config_file)
def test_resolve_instance_unknown_alias(tmpdir):
"""An -i flag with an unknown alias name raises an error."""
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(json.dumps({"default_instance": None, "instances": {}}))
with pytest.raises(Exception, match="Unknown instance"):
_resolve_instance("nonexistent", config_file)
# -- Database resolution (optional -d flag mode) --
def test_resolve_database_from_flag(tmpdir):
"""An explicit -d flag is used directly."""
config_file = pathlib.Path(tmpdir) / "config.json"
db = _resolve_database("mydb", None, config_file)
assert db == "mydb"
def test_resolve_database_from_instance_default(tmpdir):
"""When no -d flag, uses the instance's default_database from config."""
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": "prod",
"instances": {
"prod": {
"url": "https://myapp.datasette.cloud",
"default_database": "main",
}
},
}
)
)
db = _resolve_database(None, "prod", config_file)
assert db == "main"
def test_resolve_database_from_env(tmpdir, monkeypatch):
"""When no -d flag and no instance default, falls back to DATASETTE_DATABASE."""
config_file = pathlib.Path(tmpdir) / "config.json"
monkeypatch.setenv("DATASETTE_DATABASE", "envdb")
db = _resolve_database(None, None, config_file)
assert db == "envdb"
def test_resolve_database_error(tmpdir, monkeypatch):
"""When nothing is configured, raises an error."""
config_file = pathlib.Path(tmpdir) / "config.json"
monkeypatch.delenv("DATASETTE_DATABASE", raising=False)
with pytest.raises(Exception, match="No database specified"):
_resolve_database(None, None, config_file)
# -- Token resolution --
def test_resolve_token_from_flag(tmpdir):
"""An explicit --token flag is used directly."""
auth_file = pathlib.Path(tmpdir) / "auth.json"
config_file = pathlib.Path(tmpdir) / "config.json"
token = _resolve_token(
"explicit-token", "https://example.com", auth_file, config_file
)
assert token == "explicit-token"
def test_resolve_token_from_auth_by_alias(tmpdir):
"""Auth token looked up by alias name."""
auth_file = pathlib.Path(tmpdir) / "auth.json"
auth_file.write_text(json.dumps({"prod": "tok123"}))
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": None,
"instances": {
"prod": {
"url": "https://myapp.datasette.cloud",
"default_database": None,
}
},
}
)
)
token = _resolve_token(
None, "https://myapp.datasette.cloud", auth_file, config_file
)
assert token == "tok123"
def test_resolve_token_from_auth_by_url_fallback(tmpdir):
"""Auth token falls back to URL prefix matching when no alias match."""
auth_file = pathlib.Path(tmpdir) / "auth.json"
auth_file.write_text(json.dumps({"https://example.com": "url-tok"}))
config_file = pathlib.Path(tmpdir) / "config.json"
token = _resolve_token(None, "https://example.com/db", auth_file, config_file)
assert token == "url-tok"
def test_resolve_token_from_env(tmpdir, monkeypatch):
"""Falls back to DATASETTE_TOKEN env var."""
auth_file = pathlib.Path(tmpdir) / "auth.json"
config_file = pathlib.Path(tmpdir) / "config.json"
monkeypatch.setenv("DATASETTE_TOKEN", "env-tok")
token = _resolve_token(None, "https://example.com", auth_file, config_file)
assert token == "env-tok"
def test_resolve_token_none(tmpdir, monkeypatch):
"""Returns None when nothing is configured."""
auth_file = pathlib.Path(tmpdir) / "auth.json"
config_file = pathlib.Path(tmpdir) / "config.json"
monkeypatch.delenv("DATASETTE_TOKEN", raising=False)
token = _resolve_token(None, "https://example.com", auth_file, config_file)
assert token is None