"""Tests for garage.py — Click CLI commands.""" import json import sqlite3 from unittest.mock import patch, MagicMock import pytest from click.testing import CliRunner from garage import cli @pytest.fixture def runner(): return CliRunner() def test_add_and_list(runner): result = runner.invoke(cli, ["add", "Test Prompt", "-c", "This is test content"]) assert result.exit_code == 0 assert "Added prompt" in result.output result = runner.invoke(cli, ["list"]) assert result.exit_code == 0 assert "Test Prompt" in result.output def test_search(runner): runner.invoke(cli, ["add", "Python Tips", "-c", "Use list comprehensions"]) result = runner.invoke(cli, ["search", "python"]) assert result.exit_code == 0 assert "Python Tips" in result.output def test_show(runner): runner.invoke(cli, ["add", "Show Test", "-c", "Detailed content here", "-t", "test,demo"]) result = runner.invoke(cli, ["show", "1"]) assert result.exit_code == 0 assert "Show Test" in result.output assert "test,demo" in result.output def test_star(runner): runner.invoke(cli, ["add", "Star Test", "-c", "content"]) result = runner.invoke(cli, ["star", "1"]) assert result.exit_code == 0 assert "starred" in result.output def test_rate(runner): runner.invoke(cli, ["add", "Rate Test", "-c", "content"]) result = runner.invoke(cli, ["rate", "1", "4"]) assert result.exit_code == 0 assert "****" in result.output def test_stats(runner): runner.invoke(cli, ["add", "A", "-c", "aaa", "-t", "python"]) runner.invoke(cli, ["add", "B", "-c", "bbb", "-t", "coding"]) result = runner.invoke(cli, ["stats"]) assert result.exit_code == 0 assert "Total: 2" in result.output def test_export_json(runner): runner.invoke(cli, ["add", "Export", "-c", "export test"]) result = runner.invoke(cli, ["export"]) assert result.exit_code == 0 data = json.loads(result.output) assert len(data) == 1 assert data[0]["title"] == "Export" def test_export_md(runner): runner.invoke(cli, ["add", "MD Export", "-c", "markdown test"]) result = runner.invoke(cli, ["export", "--format", "md"]) assert result.exit_code == 0 assert "## MD Export" in result.output def test_delete(runner): runner.invoke(cli, ["add", "Del", "-c", "delete me"]) result = runner.invoke(cli, ["delete", "1", "--yes"]) assert result.exit_code == 0 assert "deleted" in result.output def test_list_no_prompts(runner): result = runner.invoke(cli, ["list"]) assert result.exit_code == 0 assert "No prompts found" in result.output def test_import_codex_command(runner, tmp_path): history = tmp_path / "history.jsonl" history.write_text(json.dumps({"text": "This Codex prompt should be imported through the CLI"}) + "\n") result = runner.invoke(cli, ["import-codex", str(history)]) assert result.exit_code == 0 assert "Scanned: 1" in result.output assert "Imported: 1" in result.output def test_import_hermes_command(runner, tmp_path): db_path = tmp_path / "state.db" conn = sqlite3.connect(db_path) conn.executescript( """ CREATE TABLE sessions ( id TEXT PRIMARY KEY, source TEXT NOT NULL, model TEXT ); CREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT, timestamp REAL NOT NULL ); """ ) conn.execute( "INSERT INTO sessions (id, source, model) VALUES (?, ?, ?)", ("s1", "cli", "MiniMax-M2.7"), ) conn.execute( "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)", ("s1", "user", "This Hermes prompt should be imported through the CLI", 1), ) conn.commit() conn.close() result = runner.invoke(cli, ["import-hermes", str(db_path)]) assert result.exit_code == 0 assert "Scanned: 1" in result.output assert "Imported: 1" in result.output def test_show_nonexistent(runner): result = runner.invoke(cli, ["show", "999"]) assert result.exit_code == 0 assert "not found" in result.output # --- use / copy / random / pick --- def test_use_copies_and_increments(runner): """use command copies to clipboard and increments use_count.""" runner.invoke(cli, ["add", "Use Test", "-c", "use content"]) with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock(returncode=0) result = runner.invoke(cli, ["use", "1"]) assert result.exit_code == 0 mock_run.assert_called_once_with(["wl-copy", "use content"], check=True) assert "Copied #1 to clipboard." in result.output assert "use_count: 1" in result.output def test_use_nonexistent(runner): result = runner.invoke(cli, ["use", "999"]) assert result.exit_code == 0 assert "not found" in result.output def test_copy_copies_without_increment(runner): """copy command copies to clipboard but does NOT increment use_count.""" runner.invoke(cli, ["add", "Copy Test", "-c", "copy content"]) with patch("subprocess.run") as mock_run, \ patch("garage.increment_use") as mock_inc: mock_run.return_value = MagicMock(returncode=0) result = runner.invoke(cli, ["copy", "1"]) assert result.exit_code == 0 mock_run.assert_called_once_with(["wl-copy", "copy content"], check=True) mock_inc.assert_not_called() assert "Copied #1 to clipboard." in result.output def test_copy_nonexistent(runner): result = runner.invoke(cli, ["copy", "999"]) assert result.exit_code == 0 assert "not found" in result.output def test_random_shows_prompt(runner): """random command picks and displays a prompt.""" runner.invoke(cli, ["add", "Random Prompt", "-c", "random content"]) result = runner.invoke(cli, ["random"]) assert result.exit_code == 0 assert "Random Prompt" in result.output assert "random content" in result.output def test_random_no_prompts(runner): result = runner.invoke(cli, ["random"]) assert result.exit_code == 0 assert "No prompts found." in result.output def test_random_with_copy_flag(runner): """random --copy also invokes wl-copy.""" runner.invoke(cli, ["add", "Rand Copy", "-c", "rand copy content"]) with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock(returncode=0) result = runner.invoke(cli, ["random", "--copy"]) assert result.exit_code == 0 mock_run.assert_called_once_with(["wl-copy", "rand copy content"], check=True) assert "Copied #" in result.output def test_pick_selects_and_copies(runner): """pick command shows numbered list, reads choice, copies and increments.""" runner.invoke(cli, ["add", "Pick Prompt", "-c", "pick content"]) with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock(returncode=0) # Simulate user typing "1\n" result = runner.invoke(cli, ["pick", "Pick"], input="1\n") assert result.exit_code == 0 mock_run.assert_called_once_with(["wl-copy", "pick content"], check=True) assert "Copied #1 to clipboard." in result.output def test_pick_no_matches(runner): result = runner.invoke(cli, ["pick", "zzznomatch"]) assert result.exit_code == 0 assert "No matches." in result.output def test_pick_invalid_choice(runner): runner.invoke(cli, ["add", "Pick2", "-c", "content2"]) with patch("subprocess.run"): result = runner.invoke(cli, ["pick", "Pick2"], input="99\n") assert result.exit_code == 0 assert "Invalid choice." in result.output # --- browse / tag / untag / top / auto-tag --- def test_browse_shows_and_stars(runner): """browse shows list and stars selected entries.""" runner.invoke(cli, ["add", "Browse A", "-c", "content a"]) runner.invoke(cli, ["add", "Browse B", "-c", "content b"]) # user selects entry 1 to star result = runner.invoke(cli, ["browse"], input="1\n") assert result.exit_code == 0 assert "Browse A" in result.output assert "Browse B" in result.output assert "Toggled star on" in result.output def test_browse_quit(runner): """browse quits cleanly when user enters 'q'.""" runner.invoke(cli, ["add", "Quit Browse", "-c", "content"]) result = runner.invoke(cli, ["browse"], input="q\n") assert result.exit_code == 0 assert "Toggled" not in result.output def test_browse_no_prompts(runner): result = runner.invoke(cli, ["browse"]) assert result.exit_code == 0 assert "No prompts found." in result.output def test_tag_adds_tags(runner): runner.invoke(cli, ["add", "Tag Test", "-c", "content"]) result = runner.invoke(cli, ["tag", "1", "python,testing"]) assert result.exit_code == 0 assert "Tagged #1" in result.output assert "python" in result.output assert "testing" in result.output def test_tag_appends_without_duplicates(runner): runner.invoke(cli, ["add", "Tag Dup", "-c", "content", "-t", "python"]) result = runner.invoke(cli, ["tag", "1", "python,docker"]) assert result.exit_code == 0 # python should appear only once in tag list output_tags_part = result.output.split("[")[1].split("]")[0] tag_list = [t.strip() for t in output_tags_part.split(",")] assert tag_list.count("python") == 1 assert "docker" in tag_list def test_tag_nonexistent(runner): result = runner.invoke(cli, ["tag", "999", "python"]) assert result.exit_code == 0 assert "not found" in result.output def test_untag_removes_tag(runner): runner.invoke(cli, ["add", "Untag Test", "-c", "content", "-t", "python,docker"]) result = runner.invoke(cli, ["untag", "1", "docker"]) assert result.exit_code == 0 assert "removed 'docker'" in result.output def test_untag_missing_tag(runner): runner.invoke(cli, ["add", "Untag Miss", "-c", "content", "-t", "python"]) result = runner.invoke(cli, ["untag", "1", "docker"]) assert result.exit_code == 0 assert "not found on #1" in result.output def test_untag_nonexistent_prompt(runner): result = runner.invoke(cli, ["untag", "999", "python"]) assert result.exit_code == 0 assert "not found" in result.output def test_top_shows_most_used(runner): """top shows prompts sorted by use_count.""" runner.invoke(cli, ["add", "Top Prompt", "-c", "top content"]) with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock(returncode=0) runner.invoke(cli, ["use", "1"]) runner.invoke(cli, ["use", "1"]) result = runner.invoke(cli, ["top"]) assert result.exit_code == 0 assert "Top Prompt" in result.output assert "used 2x" in result.output def test_top_no_used_prompts(runner): runner.invoke(cli, ["add", "Unused", "-c", "content"]) result = runner.invoke(cli, ["top"]) assert result.exit_code == 0 assert "No used prompts found." in result.output def test_auto_tag_dry_run(runner): """auto-tag --dry-run shows what would be tagged without applying.""" runner.invoke(cli, ["add", "Python Guide", "-c", "Write python code with pytest"]) result = runner.invoke(cli, ["auto-tag", "--dry-run"]) assert result.exit_code == 0 assert "would add" in result.output assert "python" in result.output # verify tags were NOT applied — show still reports Tags: - show = runner.invoke(cli, ["show", "1"]) assert "Tags: -" in show.output def test_auto_tag_applies(runner): """auto-tag without --dry-run actually updates tags.""" runner.invoke(cli, ["add", "Docker Setup", "-c", "Use docker to containerize"]) result = runner.invoke(cli, ["auto-tag"]) assert result.exit_code == 0 assert "tagged" in result.output assert "docker" in result.output # verify tags were applied show = runner.invoke(cli, ["show", "1"]) assert "docker" in show.output def test_auto_tag_skips_already_tagged(runner): """auto-tag skips prompts that already have tags.""" runner.invoke(cli, ["add", "Already Tagged", "-c", "python script", "-t", "existing"]) result = runner.invoke(cli, ["auto-tag"]) assert result.exit_code == 0 # should report 0 tagged (already has tag, not in untagged list) assert "Tagged 0 prompts" in result.output def test_auto_tag_summary_counts(runner): """auto-tag prints correct summary.""" runner.invoke(cli, ["add", "Python Doc", "-c", "python code"]) runner.invoke(cli, ["add", "No Keywords", "-c", "write a poem"]) result = runner.invoke(cli, ["auto-tag"]) assert result.exit_code == 0 # 1 matched + 1 no-match assert "Tagged 1 prompts, skipped 1 (no matches)" in result.output