diff --git a/tests/test_codex_auth.py b/tests/test_codex_auth.py index 595971e..c71a520 100644 --- a/tests/test_codex_auth.py +++ b/tests/test_codex_auth.py @@ -140,7 +140,8 @@ class TestGetAvailableSourcesWithAuth(unittest.TestCase): "XAI_API_KEY": None, } result = env.get_available_sources(config) - self.assertEqual(result, "web") + # Reddit is available via public JSON fallback even without OpenAI auth + self.assertEqual(result, "reddit") class TestParseCodexStream(unittest.TestCase): diff --git a/tests/test_models.py b/tests/test_models.py index 0baa42b..1b862ec 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -84,7 +84,7 @@ class TestSelectXAIModel(unittest.TestCase): "fake-key", policy="latest" ) - self.assertEqual(result, "grok-4-latest") + self.assertEqual(result, "grok-4-1-fast") def test_stable_policy(self): # Clear cache first to avoid interference @@ -94,7 +94,7 @@ class TestSelectXAIModel(unittest.TestCase): "fake-key", policy="stable" ) - self.assertEqual(result, "grok-4") + self.assertEqual(result, "grok-4-1-fast") def test_pinned_policy(self): result = models.select_xai_model( @@ -125,10 +125,10 @@ class TestGetModels(unittest.TestCase): "XAI_API_KEY": "xai-test", } mock_openai = [{"id": "gpt-5.2", "created": 1704067200}] - mock_xai = [{"id": "grok-4-latest", "created": 1704067200}] + mock_xai = [{"id": "grok-4-1-fast", "created": 1704067200}] result = models.get_models(config, mock_openai, mock_xai) self.assertEqual(result["openai"], "gpt-5.2") - self.assertEqual(result["xai"], "grok-4-latest") + self.assertEqual(result["xai"], "grok-4-1-fast") if __name__ == "__main__": diff --git a/tests/test_openai_reddit.py b/tests/test_openai_reddit.py index 2748007..607be97 100644 --- a/tests/test_openai_reddit.py +++ b/tests/test_openai_reddit.py @@ -68,9 +68,9 @@ class TestModelFallbackOrder(unittest.TestCase): """Fallback list should include gpt-4o.""" self.assertIn("gpt-4o", MODEL_FALLBACK_ORDER) - def test_gpt4o_is_first(self): - """gpt-4o should be the first fallback option.""" - self.assertEqual(MODEL_FALLBACK_ORDER[0], "gpt-4o") + def test_gpt41_is_first(self): + """gpt-4.1 should be the first fallback option.""" + self.assertEqual(MODEL_FALLBACK_ORDER[0], "gpt-4.1") if __name__ == "__main__": diff --git a/tests/test_truthsocial.py b/tests/test_truthsocial.py index 2901e1e..e827eab 100644 --- a/tests/test_truthsocial.py +++ b/tests/test_truthsocial.py @@ -1,129 +1,133 @@ """Tests for Truth Social source module.""" -import pytest +import sys +import unittest +from pathlib import Path from unittest.mock import patch, MagicMock -from scripts.lib import truthsocial +sys.path.insert(0, str(Path(__file__).parent.parent / "scripts")) + +from lib import truthsocial -class TestStripHtml: +class TestStripHtml(unittest.TestCase): """Test HTML tag stripping.""" def test_basic_paragraph(self): - assert truthsocial._strip_html("
Hello world
") == "Hello world" + self.assertEqual(truthsocial._strip_html("Hello world
"), "Hello world") def test_br_tags(self): - assert truthsocial._strip_html("Line 1Hello world
") == "Hello world" + self.assertEqual(truthsocial._strip_html("Hello world
"), "Hello world") def test_empty_string(self): - assert truthsocial._strip_html("") == "" + self.assertEqual(truthsocial._strip_html(""), "") def test_no_tags(self): - assert truthsocial._strip_html("plain text") == "plain text" + self.assertEqual(truthsocial._strip_html("plain text"), "plain text") def test_entities_preserved(self): - assert truthsocial._strip_html("& test
") == "& test" + self.assertEqual(truthsocial._strip_html("& test
"), "& test") -class TestExtractCoreSubject: +class TestExtractCoreSubject(unittest.TestCase): """Test query preprocessing.""" def test_strips_question_prefix(self): - assert truthsocial._extract_core_subject("what are people saying about tariffs") == "tariffs" + self.assertEqual(truthsocial._extract_core_subject("what are people saying about tariffs"), "tariffs") def test_strips_noise_words(self): - assert truthsocial._extract_core_subject("latest trending crypto news") == "crypto" + self.assertEqual(truthsocial._extract_core_subject("latest trending crypto news"), "crypto") def test_preserves_core_topic(self): - assert truthsocial._extract_core_subject("tariffs") == "tariffs" + self.assertEqual(truthsocial._extract_core_subject("tariffs"), "tariffs") def test_strips_trailing_punctuation(self): - assert truthsocial._extract_core_subject("what is bitcoin?") == "bitcoin" + self.assertEqual(truthsocial._extract_core_subject("what is bitcoin?"), "bitcoin") -class TestParseDate: +class TestParseDate(unittest.TestCase): """Test date parsing from Mastodon status.""" def test_iso_date(self): - assert truthsocial._parse_date({"created_at": "2026-03-09T12:00:00.000Z"}) == "2026-03-09" + self.assertEqual(truthsocial._parse_date({"created_at": "2026-03-09T12:00:00.000Z"}), "2026-03-09") def test_missing_date(self): - assert truthsocial._parse_date({}) is None + self.assertIsNone(truthsocial._parse_date({})) def test_short_date(self): - assert truthsocial._parse_date({"created_at": "short"}) is None + self.assertIsNone(truthsocial._parse_date({"created_at": "short"})) def test_none_value(self): - assert truthsocial._parse_date({"created_at": None}) is None + self.assertIsNone(truthsocial._parse_date({"created_at": None})) -class TestDepthConfig: +class TestDepthConfig(unittest.TestCase): """Test depth configuration.""" def test_all_depths_exist(self): - assert "quick" in truthsocial.DEPTH_CONFIG - assert "default" in truthsocial.DEPTH_CONFIG - assert "deep" in truthsocial.DEPTH_CONFIG + self.assertIn("quick", truthsocial.DEPTH_CONFIG) + self.assertIn("default", truthsocial.DEPTH_CONFIG) + self.assertIn("deep", truthsocial.DEPTH_CONFIG) def test_depth_ordering(self): - assert truthsocial.DEPTH_CONFIG["quick"] < truthsocial.DEPTH_CONFIG["default"] - assert truthsocial.DEPTH_CONFIG["default"] < truthsocial.DEPTH_CONFIG["deep"] + self.assertLess(truthsocial.DEPTH_CONFIG["quick"], truthsocial.DEPTH_CONFIG["default"]) + self.assertLess(truthsocial.DEPTH_CONFIG["default"], truthsocial.DEPTH_CONFIG["deep"]) -class TestSearchTruthSocial: +class TestSearchTruthSocial(unittest.TestCase): """Test search function auth handling.""" def test_no_config_returns_error(self): result = truthsocial.search_truthsocial("test", "2026-02-09", "2026-03-09") - assert result["statuses"] == [] - assert "not configured" in result["error"] + self.assertEqual(result["statuses"], []) + self.assertIn("not configured", result["error"]) def test_empty_token_returns_error(self): result = truthsocial.search_truthsocial( "test", "2026-02-09", "2026-03-09", config={"TRUTHSOCIAL_TOKEN": ""}, ) - assert result["statuses"] == [] - assert "not configured" in result["error"] + self.assertEqual(result["statuses"], []) + self.assertIn("not configured", result["error"]) - @patch("scripts.lib.truthsocial.http.request") + @patch("lib.truthsocial.http.request") def test_401_returns_token_expired(self, mock_request): - from scripts.lib.http import HTTPError + from lib.http import HTTPError mock_request.side_effect = HTTPError("Unauthorized", status_code=401) result = truthsocial.search_truthsocial( "test", "2026-02-09", "2026-03-09", config={"TRUTHSOCIAL_TOKEN": "expired_token"}, ) - assert result["statuses"] == [] - assert "expired" in result["error"] + self.assertEqual(result["statuses"], []) + self.assertIn("expired", result["error"]) - @patch("scripts.lib.truthsocial.http.request") + @patch("lib.truthsocial.http.request") def test_403_returns_access_denied(self, mock_request): - from scripts.lib.http import HTTPError + from lib.http import HTTPError mock_request.side_effect = HTTPError("Forbidden", status_code=403) result = truthsocial.search_truthsocial( "test", "2026-02-09", "2026-03-09", config={"TRUTHSOCIAL_TOKEN": "blocked_token"}, ) - assert result["statuses"] == [] - assert "Cloudflare" in result["error"] + self.assertEqual(result["statuses"], []) + self.assertIn("Cloudflare", result["error"]) - @patch("scripts.lib.truthsocial.http.request") + @patch("lib.truthsocial.http.request") def test_429_returns_rate_limited(self, mock_request): - from scripts.lib.http import HTTPError + from lib.http import HTTPError mock_request.side_effect = HTTPError("Too Many Requests", status_code=429) result = truthsocial.search_truthsocial( "test", "2026-02-09", "2026-03-09", config={"TRUTHSOCIAL_TOKEN": "rate_limited_token"}, ) - assert result["statuses"] == [] - assert "rate limited" in result["error"] + self.assertEqual(result["statuses"], []) + self.assertIn("rate limited", result["error"]) - @patch("scripts.lib.truthsocial.http.request") + @patch("lib.truthsocial.http.request") def test_successful_search(self, mock_request): mock_request.return_value = { "statuses": [ @@ -142,13 +146,13 @@ class TestSearchTruthSocial: "tariffs", "2026-02-09", "2026-03-09", config={"TRUTHSOCIAL_TOKEN": "valid_token"}, ) - assert len(result["statuses"]) == 1 + self.assertEqual(len(result["statuses"]), 1) # Verify bearer token was passed call_args = mock_request.call_args - assert call_args[1]["headers"]["Authorization"] == "Bearer valid_token" + self.assertEqual(call_args[1]["headers"]["Authorization"], "Bearer valid_token") -class TestParseTruthSocialResponse: +class TestParseTruthSocialResponse(unittest.TestCase): """Test response parsing.""" def test_basic_post(self): @@ -166,21 +170,21 @@ class TestParseTruthSocialResponse: ] } items = truthsocial.parse_truthsocial_response(response) - assert len(items) == 1 + self.assertEqual(len(items), 1) item = items[0] - assert item["handle"] == "testuser" - assert item["display_name"] == "Test User" - assert item["text"] == "Hello from Truth Social" # HTML stripped - assert item["url"] == "https://truthsocial.com/@testuser/456" - assert item["date"] == "2026-03-09" - assert item["engagement"]["likes"] == 100 - assert item["engagement"]["reposts"] == 50 - assert item["engagement"]["replies"] == 25 - assert item["relevance"] > 0 + self.assertEqual(item["handle"], "testuser") + self.assertEqual(item["display_name"], "Test User") + self.assertEqual(item["text"], "Hello from Truth Social") + self.assertEqual(item["url"], "https://truthsocial.com/@testuser/456") + self.assertEqual(item["date"], "2026-03-09") + self.assertEqual(item["engagement"]["likes"], 100) + self.assertEqual(item["engagement"]["reposts"], 50) + self.assertEqual(item["engagement"]["replies"], 25) + self.assertGreater(item["relevance"], 0) def test_empty_response(self): items = truthsocial.parse_truthsocial_response({"statuses": []}) - assert items == [] + self.assertEqual(items, []) def test_missing_fields(self): response = { @@ -192,10 +196,10 @@ class TestParseTruthSocialResponse: ] } items = truthsocial.parse_truthsocial_response(response) - assert len(items) == 1 - assert items[0]["handle"] == "" - assert items[0]["text"] == "" - assert items[0]["engagement"]["likes"] == 0 + self.assertEqual(len(items), 1) + self.assertEqual(items[0]["handle"], "") + self.assertEqual(items[0]["text"], "") + self.assertEqual(items[0]["engagement"]["likes"], 0) def test_relevance_ordering(self): response = { @@ -206,8 +210,8 @@ class TestParseTruthSocialResponse: ] } items = truthsocial.parse_truthsocial_response(response) - assert items[0]["relevance"] >= items[1]["relevance"] - assert items[1]["relevance"] >= items[2]["relevance"] + self.assertGreaterEqual(items[0]["relevance"], items[1]["relevance"]) + self.assertGreaterEqual(items[1]["relevance"], items[2]["relevance"]) def test_html_stripping_in_parse(self): response = { @@ -222,5 +226,9 @@ class TestParseTruthSocialResponse: ] } items = truthsocial.parse_truthsocial_response(response) - assert "<" not in items[0]["text"] - assert ">" not in items[0]["text"] + self.assertNotIn("<", items[0]["text"]) + self.assertNotIn(">", items[0]["text"]) + + +if __name__ == "__main__": + unittest.main()