"""Tests for soup chat command.""" import json from pathlib import Path from typer.testing import CliRunner from soup_cli.cli import app from soup_cli.commands.chat import _detect_base_model runner = CliRunner() def test_chat_missing_model_path(): """Chat with nonexistent path should fail.""" result = runner.invoke(app, ["chat", "--model", "/nonexistent/path"]) assert result.exit_code == 1 def test_detect_base_model_valid(tmp_path: Path): """Should read base_model_name_or_path from adapter_config.json.""" config_path = tmp_path / "adapter_config.json" config_path.write_text(json.dumps({ "base_model_name_or_path": "meta-llama/Llama-3.1-8B-Instruct", "r": 64, "lora_alpha": 16, })) result = _detect_base_model(config_path) assert result == "meta-llama/Llama-3.1-8B-Instruct" def test_detect_base_model_missing_key(tmp_path: Path): """Should return None if base_model_name_or_path is missing.""" config_path = tmp_path / "adapter_config.json" config_path.write_text(json.dumps({"r": 64})) result = _detect_base_model(config_path) assert result is None def test_detect_base_model_invalid_json(tmp_path: Path): """Should return None for malformed JSON.""" config_path = tmp_path / "adapter_config.json" config_path.write_text("not valid json {{{") result = _detect_base_model(config_path) assert result is None def test_detect_base_model_missing_file(tmp_path: Path): """Should return None for nonexistent file.""" config_path = tmp_path / "nonexistent.json" result = _detect_base_model(config_path) assert result is None def test_chat_adapter_without_base_model(tmp_path: Path): """Chat with adapter that has no base model info should fail.""" # Create fake adapter directory with adapter_config.json but no base model adapter_dir = tmp_path / "adapter" adapter_dir.mkdir() config = adapter_dir / "adapter_config.json" config.write_text(json.dumps({"r": 64})) result = runner.invoke(app, ["chat", "--model", str(adapter_dir)]) assert result.exit_code == 1 assert "Cannot detect base model" in result.output def test_chat_non_adapter_directory(tmp_path: Path): """Chat with directory that has no adapter_config.json skips base model detection.""" model_dir = tmp_path / "model" model_dir.mkdir() # No adapter_config.json → not an adapter, will try to load directly # This will fail because there's no actual model, but it should get past validation result = runner.invoke(app, ["chat", "--model", str(model_dir)]) # Should fail during model loading, not during validation assert result.exit_code == 1