"""Tests for the Thicket Zulip bot.""" import pytest from thicket.bots.test_bot import ( BotTester, MockBotHandler, create_test_entry, create_test_message, ) from thicket.bots.thicket_bot import ThicketBotHandler class TestThicketBot: """Test suite for ThicketBotHandler.""" def setup_method(self) -> None: """Set up test environment.""" self.bot = ThicketBotHandler() self.handler = MockBotHandler() def test_usage(self) -> None: """Test bot usage message.""" usage = self.bot.usage() assert "Thicket Feed Bot" in usage assert "@thicket status" in usage assert "@thicket config" in usage def test_help_command(self) -> None: """Test help command response.""" message = create_test_message("@thicket help") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "Thicket Feed Bot" in response def test_status_command_unconfigured(self) -> None: """Test status command when bot is not configured.""" message = create_test_message("@thicket status") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "Not configured" in response assert "Stream:" in response assert "Topic:" in response def test_config_stream_command(self) -> None: """Test setting stream configuration.""" message = create_test_message("@thicket config stream general") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "Stream set to: **general**" in response assert self.bot.stream_name == "general" def test_config_topic_command(self) -> None: """Test setting topic configuration.""" message = create_test_message("@thicket config topic 'Feed Updates'") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "Topic set to:" in response and "Feed Updates" in response assert self.bot.topic_name == "'Feed Updates'" def test_config_interval_command(self) -> None: """Test setting sync interval.""" message = create_test_message("@thicket config interval 600") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "Sync interval set to: **600s**" in response assert self.bot.sync_interval == 600 def test_config_interval_too_small(self) -> None: """Test setting sync interval that's too small.""" message = create_test_message("@thicket config interval 30") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "must be at least 60 seconds" in response assert self.bot.sync_interval != 30 def test_config_path_nonexistent(self) -> None: """Test setting config path that doesn't exist.""" message = create_test_message("@thicket config path /nonexistent/config.yaml") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "Config file not found" in response def test_unknown_command(self) -> None: """Test unknown command handling.""" message = create_test_message("@thicket unknown") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "Unknown command: unknown" in response def test_config_persistence(self) -> None: """Test that configuration is persisted.""" # Set some config self.bot.stream_name = "test-stream" self.bot.topic_name = "test-topic" self.bot.sync_interval = 600 # Save config self.bot._save_bot_config(self.handler) # Create new bot instance new_bot = ThicketBotHandler() new_bot._load_bot_config(self.handler) # Check config was loaded assert new_bot.stream_name == "test-stream" assert new_bot.topic_name == "test-topic" assert new_bot.sync_interval == 600 def test_posted_entries_persistence(self) -> None: """Test that posted entries are persisted.""" # Add some entries self.bot.posted_entries = {"user1:entry1", "user2:entry2"} # Save entries self.bot._save_posted_entries(self.handler) # Create new bot instance new_bot = ThicketBotHandler() new_bot._load_posted_entries(self.handler) # Check entries were loaded assert new_bot.posted_entries == {"user1:entry1", "user2:entry2"} def test_mention_detection(self) -> None: """Test bot mention detection.""" assert self.bot._is_mentioned("@Thicket Bot help", self.handler) assert self.bot._is_mentioned("@thicket status", self.handler) assert not self.bot._is_mentioned("regular message", self.handler) def test_mention_cleaning(self) -> None: """Test cleaning mentions from messages.""" cleaned = self.bot._clean_mention("@Thicket Bot status", self.handler) assert cleaned == "status" cleaned = self.bot._clean_mention("@thicket help", self.handler) assert cleaned == "help" def test_sync_now_uninitialized(self) -> None: """Test sync now command when not initialized.""" message = create_test_message("@thicket sync now") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "not initialized" in response.lower() def test_debug_mode_initialization(self) -> None: """Test debug mode initialization.""" import os # Mock environment variable os.environ["THICKET_DEBUG_USER"] = "testuser" try: bot = ThicketBotHandler() # Simulate initialize call bot.debug_user = os.getenv("THICKET_DEBUG_USER") assert bot.debug_user == "testuser" assert bot.debug_zulip_user_id is None # Not validated yet finally: # Clean up if "THICKET_DEBUG_USER" in os.environ: del os.environ["THICKET_DEBUG_USER"] def test_debug_mode_status(self) -> None: """Test status command in debug mode.""" self.bot.debug_user = "testuser" self.bot.debug_zulip_user_id = "test.user" message = create_test_message("@thicket status") self.bot.handle_message(message, self.handler) assert len(self.handler.sent_messages) == 1 response = self.handler.sent_messages[0]["content"] assert "**Debug Mode:** ENABLED" in response assert "**Debug User:** testuser" in response assert "**Debug Zulip ID:** test.user" in response def test_debug_mode_check_initialization(self) -> None: """Test initialization check in debug mode.""" from unittest.mock import Mock # Setup mock git store and config self.bot.git_store = Mock() self.bot.config = Mock() self.bot.debug_user = "testuser" self.bot.debug_zulip_user_id = "test.user" message = create_test_message("@thicket sync now") # Should pass with debug mode properly set up result = self.bot._check_initialization(message, self.handler) assert result is True # Should fail if debug_zulip_user_id is missing self.bot.debug_zulip_user_id = None result = self.bot._check_initialization(message, self.handler) assert result is False assert len(self.handler.sent_messages) == 1 assert ( "Debug mode validation failed" in self.handler.sent_messages[0]["content"] ) def test_debug_mode_dm_posting(self) -> None: """Test that debug mode posts DMs instead of stream messages.""" from unittest.mock import Mock # Setup bot in debug mode self.bot.debug_user = "testuser" self.bot.debug_zulip_user_id = "test.user@example.com" self.bot.git_store = Mock() # Create a test entry entry = create_test_entry() # Mock the handler config self.handler.config_info = { "full_name": "Thicket Bot", "email": "thicket-bot@example.com", "site": "https://example.zulipchat.com", } # Mock git store user mock_user = Mock() mock_user.get_zulip_mention.return_value = "author.user" self.bot.git_store.get_user.return_value = mock_user # Post entry self.bot._post_entry_to_zulip(entry, self.handler, "testauthor") # Check that a DM was sent assert len(self.handler.sent_messages) == 1 message = self.handler.sent_messages[0] # Verify it's a DM assert message["type"] == "private" assert message["to"] == ["test.user@example.com"] assert "DEBUG:" in message["content"] assert entry.title in message["content"] assert "@**author.user** posted:" in message["content"] class TestBotTester: """Test the bot testing utilities.""" def test_bot_tester_basic(self) -> None: """Test basic bot tester functionality.""" tester = BotTester() # Test help command responses = tester.send_command("help") assert len(responses) == 1 assert "Thicket Feed Bot" in tester.get_last_response_content() def test_bot_tester_config(self) -> None: """Test bot tester configuration.""" tester = BotTester() # Configure stream tester.send_command("config stream general") tester.assert_response_contains("Stream set to") # Configure topic tester.send_command("config topic test") tester.assert_response_contains("Topic set to") def test_assert_response_contains(self) -> None: """Test response assertion helper.""" tester = BotTester() # Send command tester.send_command("help") # This should pass tester.assert_response_contains("Thicket Feed Bot") # This should fail with pytest.raises(AssertionError): tester.assert_response_contains("nonexistent text")