File size: 5,203 Bytes
e29625a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import pytest
import yaml
import json
from unittest.mock import patch

# Import the Settings class specifically to allow for instance management in tests
from blossomtune_gradio.settings import Settings


@pytest.fixture(autouse=True)
def reset_settings_singleton():
    """
    Fixture to automatically reset the Settings singleton before each test.
    This allows creating a new, clean instance for each test scenario by
    directly resetting the internal `_instance` variable.
    """
    Settings._instance = None


@pytest.fixture
def valid_config_files(tmp_path):
    """Creates a valid config and schema file in a temporary directory."""
    config_dir = tmp_path / "config"
    config_dir.mkdir()

    config_content = {
        "ui": {
            "welcome_message_md": "Hello, {{ name }}!",
            "error_message_md": "An error occurred.",
        }
    }
    schema_content = {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "type": "object",
        "properties": {
            "ui": {
                "type": "object",
                "properties": {
                    "welcome_message_md": {"type": "string"},
                    "error_message_md": {"type": "string"},
                },
                "required": ["welcome_message_md", "error_message_md"],
            }
        },
        "required": ["ui"],
    }

    config_path = config_dir / "blossomtune.yaml"
    schema_path = config_dir / "blossomtune.schema.json"

    with open(config_path, "w") as f:
        yaml.dump(config_content, f)
    with open(schema_path, "w") as f:
        json.dump(schema_content, f)

    return str(config_path), str(schema_path)


def test_load_valid_config(valid_config_files):
    """Tests successful loading of a valid configuration."""
    config_path, schema_path = valid_config_files
    settings = Settings(config_path=config_path, schema_path=schema_path)

    assert "welcome_message_md" in settings.templates
    assert "error_message_md" in settings.templates

    rendered_text = settings.get_text("welcome_message_md", name="World")
    assert rendered_text == "Hello, World!"


def test_missing_config_file(tmp_path, capsys):
    """Tests handling of a missing configuration file and captures stdout."""
    schema_path = tmp_path / "schema.json"
    schema_path.touch()  # Create an empty schema for the test
    settings = Settings(config_path="nonexistent.yaml", schema_path=str(schema_path))

    assert not settings.templates

    # Check that an error was printed to the console
    captured = capsys.readouterr()
    assert "Error: Configuration file not found" in captured.out


def test_invalid_yaml_file(tmp_path, capsys):
    """Tests handling of a syntactically incorrect YAML file."""
    config_path = tmp_path / "invalid.yaml"
    schema_path = tmp_path / "schema.json"
    schema_path.touch()

    with open(config_path, "w") as f:
        f.write("ui: { welcome: 'Hello'")  # Malformed YAML

    settings = Settings(config_path=str(config_path), schema_path=str(schema_path))
    assert not settings.templates
    captured = capsys.readouterr()
    assert "Error parsing YAML file" in captured.out


def test_schema_validation_failure(tmp_path, capsys):
    """Tests that validation fails if the config doesn't match the schema."""
    config_dir = tmp_path / "config"
    config_dir.mkdir()

    # Config is missing the required 'error_message'
    config_content = {"ui": {"welcome_message_md": "Hello!"}}
    schema_content = {
        "type": "object",
        "properties": {"ui": {"type": "object", "required": ["error_message_md"]}},
    }

    config_path = config_dir / "blossomtune.yaml"
    schema_path = config_dir / "blossomtune.schema.json"

    with open(config_path, "w") as f:
        yaml.dump(config_content, f)
    with open(schema_path, "w") as f:
        json.dump(schema_content, f)

    settings = Settings(config_path=str(config_path), schema_path=str(schema_path))
    assert not settings.templates
    captured = capsys.readouterr()
    assert "Error: YAML configuration is invalid" in captured.out


@patch("blossomtune_gradio.config.BLOSSOMTUNE_CONFIG")
def test_load_from_env_variable(mock_config_env, valid_config_files):
    """Tests loading the config path from an environment variable."""
    config_path, schema_path = valid_config_files
    mock_config_env = config_path

    # By patching the config module, the Settings constructor will pick it up
    with patch("blossomtune_gradio.settings.cfg.BLOSSOMTUNE_CONFIG", mock_config_env):
        settings = Settings(schema_path=schema_path)

    assert settings.config_path == config_path
    rendered_text = settings.get_text("welcome_message_md", name="From Env")
    assert rendered_text == "Hello, From Env!"


def test_singleton_pattern(valid_config_files):
    """Tests that the same instance of Settings is always returned."""
    config_path, schema_path = valid_config_files
    s1 = Settings(config_path=config_path, schema_path=schema_path)
    s2 = Settings()  # Should return the same instance as s1

    assert s1 is s2
    # Verify s2 is configured, proving it's the same instance
    assert "welcome_message_md" in s2.templates