191 lines
6.2 KiB
Python
191 lines
6.2 KiB
Python
"""Tests for core history loading and parsing."""
|
|
|
|
import os
|
|
import tempfile
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
|
|
from shellhist.core import HistoryEntry, HistoryStore, HistoryLoader
|
|
|
|
|
|
class TestHistoryEntry:
|
|
"""Test HistoryEntry dataclass."""
|
|
|
|
def test_create_entry(self):
|
|
"""Test creating a basic history entry."""
|
|
entry = HistoryEntry(command="git status")
|
|
assert entry.command == "git status"
|
|
assert entry.timestamp is None
|
|
assert entry.line_number == 0
|
|
assert entry.shell_type == "unknown"
|
|
|
|
def test_entry_with_timestamp(self):
|
|
"""Test creating an entry with timestamp."""
|
|
ts = datetime.now()
|
|
entry = HistoryEntry(command="ls -la", timestamp=ts)
|
|
assert entry.timestamp == ts
|
|
|
|
def test_entry_equality(self):
|
|
"""Test entry equality based on command."""
|
|
entry1 = HistoryEntry(command="git status")
|
|
entry2 = HistoryEntry(command="git status")
|
|
entry3 = HistoryEntry(command="git log")
|
|
|
|
assert entry1 == entry2
|
|
assert entry1 != entry3
|
|
|
|
def test_entry_hash(self):
|
|
"""Test entry hash for use in sets/dicts."""
|
|
entry1 = HistoryEntry(command="git status")
|
|
entry2 = HistoryEntry(command="git status")
|
|
|
|
entry_set = {entry1, entry2}
|
|
assert len(entry_set) == 1
|
|
|
|
|
|
class TestHistoryStore:
|
|
"""Test HistoryStore class."""
|
|
|
|
def test_empty_store(self):
|
|
"""Test creating an empty store."""
|
|
store = HistoryStore()
|
|
assert len(store.entries) == 0
|
|
assert len(store.command_frequency) == 0
|
|
|
|
def test_add_entry(self):
|
|
"""Test adding entries to the store."""
|
|
store = HistoryStore()
|
|
entry = HistoryEntry(command="git status")
|
|
|
|
store.add_entry(entry)
|
|
|
|
assert len(store.entries) == 1
|
|
assert store.get_frequency("git status") == 1
|
|
|
|
def test_frequency_tracking(self):
|
|
"""Test command frequency tracking."""
|
|
store = HistoryStore()
|
|
|
|
for _ in range(3):
|
|
store.add_entry(HistoryEntry(command="git status"))
|
|
|
|
store.add_entry(HistoryEntry(command="git log"))
|
|
|
|
assert store.get_frequency("git status") == 3
|
|
assert store.get_frequency("git log") == 1
|
|
assert store.get_frequency("unknown") == 0
|
|
|
|
def test_most_frequent(self):
|
|
"""Test getting most frequent commands."""
|
|
store = HistoryStore()
|
|
|
|
store.add_entry(HistoryEntry(command="git status"))
|
|
store.add_entry(HistoryEntry(command="git status"))
|
|
store.add_entry(HistoryEntry(command="git status"))
|
|
store.add_entry(HistoryEntry(command="git log"))
|
|
store.add_entry(HistoryEntry(command="ls"))
|
|
|
|
most_frequent = store.get_most_frequent(limit=2)
|
|
|
|
assert most_frequent[0] == ("git status", 3)
|
|
assert most_frequent[1] == ("git log", 1)
|
|
|
|
def test_get_unique_commands(self):
|
|
"""Test getting unique commands."""
|
|
store = HistoryStore()
|
|
|
|
store.add_entry(HistoryEntry(command="git status"))
|
|
store.add_entry(HistoryEntry(command="git status"))
|
|
store.add_entry(HistoryEntry(command="git log"))
|
|
|
|
unique = store.get_unique_commands()
|
|
|
|
assert len(unique) == 2
|
|
assert "git status" in unique
|
|
assert "git log" in unique
|
|
|
|
|
|
class TestHistoryLoader:
|
|
"""Test HistoryLoader class."""
|
|
|
|
def test_load_bash_history(self):
|
|
"""Test parsing bash history format."""
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='_bash_history', delete=False) as f:
|
|
f.write("git status\n")
|
|
f.write("git log --oneline\n")
|
|
f.write("ls -la\n")
|
|
f.write("npm install\n")
|
|
temp_path = f.name
|
|
|
|
try:
|
|
loader = HistoryLoader(history_path=temp_path)
|
|
store = loader.load()
|
|
|
|
assert len(store.entries) == 4
|
|
assert store.get_unique_commands() == [
|
|
"git status",
|
|
"git log --oneline",
|
|
"ls -la",
|
|
"npm install",
|
|
]
|
|
finally:
|
|
os.unlink(temp_path)
|
|
|
|
def test_load_bash_history_with_timestamps(self):
|
|
"""Test parsing bash history with timestamps."""
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='_bash_history', delete=False) as f:
|
|
f.write("#1700000000\n")
|
|
f.write("git status\n")
|
|
f.write("#1700000001\n")
|
|
f.write("git log\n")
|
|
temp_path = f.name
|
|
|
|
try:
|
|
loader = HistoryLoader(history_path=temp_path)
|
|
store = loader.load()
|
|
|
|
assert len(store.entries) == 2
|
|
assert store.entries[0].timestamp is not None
|
|
finally:
|
|
os.unlink(temp_path)
|
|
|
|
def test_load_zsh_history(self):
|
|
"""Test parsing zsh history format."""
|
|
import os
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='_zsh_history', delete=False) as f:
|
|
f.write(": 1700000000:0;git status\n")
|
|
f.write(": 1700000001:0;git log --oneline\n")
|
|
f.write("ls -la\n")
|
|
temp_path = f.name
|
|
|
|
try:
|
|
os.environ["SHELL"] = "/bin/zsh"
|
|
loader = HistoryLoader(history_path=temp_path)
|
|
store = loader.load()
|
|
|
|
assert len(store.entries) == 3
|
|
assert store.entries[0].timestamp is not None
|
|
del os.environ["SHELL"]
|
|
finally:
|
|
os.unlink(temp_path)
|
|
|
|
def test_file_not_found(self):
|
|
"""Test error handling for missing file."""
|
|
loader = HistoryLoader(history_path="/nonexistent/path")
|
|
|
|
with pytest.raises(FileNotFoundError):
|
|
loader.load()
|
|
|
|
def test_from_file_convenience_method(self):
|
|
"""Test the from_file class method."""
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='_history', delete=False) as f:
|
|
f.write("echo test\n")
|
|
temp_path = f.name
|
|
|
|
try:
|
|
store = HistoryLoader.from_file(temp_path)
|
|
assert len(store.entries) == 1
|
|
finally:
|
|
os.unlink(temp_path)
|