"""
Tests for BlackRiver GGUF Tools.
Run: python -m pytest test_app.py -v
"""
import os
import sys
import json
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Mock Gradio before importing app
mock_gr = MagicMock()
mock_gr.OAuthToken = MagicMock()
mock_gr.Progress = MagicMock()
mock_gr.Error = Exception
mock_gr.Interface = MagicMock()
mock_gr.Blocks = MagicMock()
mock_gr.Markdown = MagicMock()
mock_gr.HTML = MagicMock()
mock_gr.Dropdown = MagicMock()
mock_gr.Checkbox = MagicMock()
mock_gr.File = MagicMock()
mock_gr.Number = MagicMock()
mock_gr.Textbox = MagicMock()
mock_gr.Image = MagicMock()
mock_gr.Row = MagicMock()
mock_gr.Column = MagicMock()
mock_gr.Group = MagicMock()
mock_gr.LoginButton = MagicMock()
mock_gr.Button = MagicMock()
mock_gr.update = MagicMock()
mock_gr.themes = MagicMock()
mock_gr.themes.Base = MagicMock(return_value=MagicMock())
sys.modules['gradio'] = mock_gr
sys.modules['gradio_huggingfacehub_search'] = MagicMock()
sys.modules['huggingface_hub'] = MagicMock()
sys.modules['apscheduler'] = MagicMock()
sys.modules['apscheduler.schedulers'] = MagicMock()
sys.modules['apscheduler.schedulers.background'] = MagicMock()
# Now import app
import app
class TestDetectMultimodal:
def test_text_only_model(self, tmp_path):
config = {"architectures": ["LlamaForCausalLM"], "model_type": "llama"}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is False
assert "No multimodal indicators" in result["reason"]
def test_vision_model_vision_config(self, tmp_path):
config = {
"architectures": ["LlavaForConditionalGeneration"],
"vision_config": {"hidden_size": 768},
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
assert "vision_config present" in result["reason"]
def test_vision_model_image_token_ids(self, tmp_path):
config = {
"architectures": ["Gemma3ForConditionalGeneration"],
"image_token_id": 256000,
"vision_start_token_id": 255999,
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
def test_vision_model_text_config(self, tmp_path):
config = {
"architectures": ["Qwen2VLForConditionalGeneration"],
"text_config": {"vision_config": {"hidden_size": 1024}},
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
def test_vision_model_mm_projector(self, tmp_path):
config = {
"architectures": ["MiniCPMV"],
"mm_projector": "mlp2x_gelu",
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
def test_vision_model_mm_vision_tower(self, tmp_path):
config = {
"architectures": ["BailingMoe2ForConditionalGeneration"],
"mm_vision_tower": "openai/clip-vit-large-patch14-336",
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
def test_missing_config(self, tmp_path):
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is False
assert "No config.json" in result["reason"]
def test_invalid_config(self, tmp_path):
(tmp_path / "config.json").write_text("not json")
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is False
assert "Failed to parse" in result["reason"]
def test_preprocessor_detection(self, tmp_path):
config = {"architectures": ["LlavaForConditionalGeneration"]}
(tmp_path / "config.json").write_text(json.dumps(config))
pp = {"image_processor_type": "CLIPImageProcessor", "size": 336}
(tmp_path / "preprocessor_config.json").write_text(json.dumps(pp))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
def test_legacy_llava_detection(self, tmp_path):
config = {"architectures": ["LlavaLlamaForCausalLM"], "model_type": "llava"}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["method"] == "legacy"
assert result["legacy_key"] == "llava"
def test_legacy_minicpmv_detection(self, tmp_path):
config = {"architectures": ["MiniCPMVForCausalLM"], "model_type": "minicpmv"}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["method"] == "legacy"
assert result["legacy_key"] == "minicpmv"
def test_legacy_glmedge_detection(self, tmp_path):
config = {"architectures": ["GlmEdgeForCausalLM"], "model_type": "glm-edge"}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["method"] == "legacy"
assert result["legacy_key"] == "glmedge"
class TestSanitizeName:
def test_simple(self):
assert app._sanitize_name("hello-world") == "hello-world"
def test_spaces(self):
assert app._sanitize_name("hello world") == "hello_world"
def test_special_chars(self):
assert app._sanitize_name("model@v1.0!") == "model_v1.0_"
class TestEscape:
def test_html_chars(self):
result = app.escape("")
assert "