# SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc """Tests for journal configuration utilities.""" import json import pytest from solstone.think.utils import CorruptConfigError, get_config, journal_is_active from tests.helpers.journal_config import seed_journal_config @pytest.fixture def config_journal(tmp_path): """Create a temporary journal with config.""" config_dir = tmp_path / "config" config_dir.mkdir() config_data = { "identity": { "name": "Test User", "preferred": "Tester", "bio": "a software engineer and tester", "pronouns": { "subject": "they", "object": "them", "possessive": "their", "reflexive": "themselves", }, "aliases": ["test", "tester"], "email_addresses": ["test@example.com"], "timezone": "America/New_York", } } config_file = config_dir / "journal.json" with open(config_file, "w") as f: json.dump(config_data, f, indent=2) f.write("\n") return tmp_path def test_get_config_default_structure(tmp_path, monkeypatch): """Test get_config returns default structure when file doesn't exist.""" monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) config = get_config() assert "identity" in config assert config["identity"]["name"] == "" assert config["identity"]["preferred"] == "" assert config["identity"]["pronouns"] == { "subject": "", "object": "", "possessive": "", "reflexive": "", } assert config["identity"]["aliases"] == [] assert config["identity"]["email_addresses"] == [] assert config["identity"]["timezone"] == "" assert config["identity"]["bio"] == "" # Describe defaults assert "describe" in config assert isinstance(config["describe"]["redact"], list) assert len(config["describe"]["redact"]) > 0 def test_get_config_default_is_deep_copy(tmp_path, monkeypatch): """Test that modifying returned defaults doesn't affect future calls.""" monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) config1 = get_config() config1["identity"]["name"] = "Modified" config1["describe"]["redact"].append("extra rule") config2 = get_config() assert config2["identity"]["name"] == "" assert "extra rule" not in config2["describe"]["redact"] def test_get_config_loads_existing(config_journal, monkeypatch): """Test get_config loads existing configuration.""" monkeypatch.setenv("SOLSTONE_JOURNAL", str(config_journal)) config = get_config() assert config["identity"]["name"] == "Test User" assert config["identity"]["preferred"] == "Tester" assert config["identity"]["pronouns"] == { "subject": "they", "object": "them", "possessive": "their", "reflexive": "themselves", } assert config["identity"]["aliases"] == ["test", "tester"] assert config["identity"]["email_addresses"] == ["test@example.com"] assert config["identity"]["timezone"] == "America/New_York" assert config["identity"]["bio"] == "a software engineer and tester" def test_get_config_existing_is_master(tmp_path, monkeypatch): """Test that existing journal.json is returned as-is without merging defaults.""" monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) # Create config with only a name - no other identity fields, no describe config_dir = tmp_path / "config" config_dir.mkdir() partial_config = { "identity": { "name": "Partial User", } } config_file = config_dir / "journal.json" with open(config_file, "w") as f: json.dump(partial_config, f) config = get_config() # User's value is preserved assert config["identity"]["name"] == "Partial User" # Missing fields are NOT filled from defaults - journal.json is master assert "preferred" not in config["identity"] assert "describe" not in config def test_get_config_empty_journal(tmp_path, monkeypatch): """Test get_config returns defaults with an empty journal directory.""" monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) config = get_config() assert "identity" in config assert config["identity"]["name"] == "" def test_get_config_raises_on_invalid_json(tmp_path, monkeypatch): """Test get_config raises when present journal.json is invalid.""" monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) # Create config with invalid JSON config_dir = tmp_path / "config" config_dir.mkdir() config_file = config_dir / "journal.json" with open(config_file, "w") as f: f.write("{ invalid json }") with pytest.raises(CorruptConfigError) as exc_info: get_config() message = str(exc_info.value) assert str(config_file) in message assert "NOT changed" in message def test_get_config_with_fixtures(monkeypatch): """Test get_config with tests/fixtures/journal path.""" # Set SOLSTONE_JOURNAL to fixtures monkeypatch.setenv("SOLSTONE_JOURNAL", "tests/fixtures/journal") config = get_config() # Fixtures has journal.json - returned as-is assert "identity" in config assert isinstance(config["identity"]["name"], str) assert isinstance(config["identity"]["preferred"], str) assert isinstance(config["identity"]["pronouns"], dict) assert "subject" in config["identity"]["pronouns"] assert "object" in config["identity"]["pronouns"] assert "possessive" in config["identity"]["pronouns"] assert "reflexive" in config["identity"]["pronouns"] assert isinstance(config["identity"]["aliases"], list) assert isinstance(config["identity"]["email_addresses"], list) assert isinstance(config["identity"]["timezone"], str) assert isinstance(config["identity"]["bio"], str) @pytest.mark.parametrize( ("config", "expected"), [ ({"setup": {"completed_at": 1}}, True), ({"setup": {"completed_at": 1.5}}, True), ({"setup": {"completed_at": 0}}, False), ({"setup": {"completed_at": -1}}, False), ({"setup": {"completed_at": None}}, False), ({"setup": {"completed_at": True}}, False), ({"setup": {"completed_at": False}}, False), ({"setup": {"completed_at": {}}}, False), ({"setup": {"completed_at": []}}, False), ({}, False), ({"setup": {"completed_at": "foo"}}, False), ({"identity": {"name": "Active User"}}, False), ({"identity": {"name": ""}, "setup": {"completed_at": 1}}, True), ], ) def test_journal_is_active_from_config(tmp_path, config, expected): seed_journal_config(config, tmp_path) assert journal_is_active(tmp_path) is expected def test_journal_is_active_with_fixtures(monkeypatch): monkeypatch.setenv("SOLSTONE_JOURNAL", "tests/fixtures/journal") assert journal_is_active("tests/fixtures/journal") is True def test_journal_is_active_false_for_empty_dir(tmp_path): assert journal_is_active(tmp_path) is False def test_journal_is_active_false_without_config(tmp_path): (tmp_path / "config").mkdir() assert journal_is_active(tmp_path) is False def test_journal_is_active_false_for_malformed_json(tmp_path): config_dir = tmp_path / "config" config_dir.mkdir() (config_dir / "journal.json").write_text("{bad json", encoding="utf-8") assert journal_is_active(tmp_path) is False def test_journal_is_active_false_for_path_that_is_not_directory(tmp_path): file_path = tmp_path / "journal.json" file_path.write_text("{}", encoding="utf-8") assert journal_is_active(file_path) is False def test_journal_is_active_false_for_absent_path(tmp_path): assert journal_is_active(tmp_path / "missing") is False