1 line
19 KiB
JSON
1 line
19 KiB
JSON
{"repo": "central-university-it-prod/2026-final-command-team-17-backend-v2", "files": {"sentiment/tests/test_tasks.py": {"5b5be6780a1b1853f0b4e623a82de1e8955b9cda": {"t": 1785747502.431992, "n": 17884, "h": "bffccdbb1fd47038", "c": "import unittest\nfrom unittest.mock import patch\n\nimport httpx\n\nfrom sentiment_worker.tasks import (\n _parse_feelings,\n _parse_risk_hype,\n _call_deepseek,\n _call_deepseek_theme_metrics,\n _call_deepseek_theme_summary,\n analyze_news,\n analyze_theme,\n summarize_theme,\n generate_theme_title,\n generate_news_title,\n BACKEND_TASK_NAME,\n BACKEND_SET_THEME_METRICS,\n BACKEND_SET_THEME_SUMMARY,\n BACKEND_SET_THEME_TITLE,\n BACKEND_SET_NEWS_TITLE,\n BACKEND_QUEUE_NAME,\n THEME_SUMMARY_MAX_LEN,\n THEME_TITLE_MAX_LEN,\n NEWS_TITLE_MAX_LEN,\n)\n\n\nclass TestParseFeelings(unittest.TestCase):\n def test_positive_returned_as_is(self):\n self.assertEqual(_parse_feelings(\"positive\"), \"positive\")\n self.assertEqual(_parse_feelings(\"Positive\"), \"positive\")\n self.assertEqual(_parse_feelings(\"The sentiment is positive.\"), \"positive\")\n\n def test_negative_returned_as_is(self):\n self.assertEqual(_parse_feelings(\"negative\"), \"negative\")\n self.assertEqual(_parse_feelings(\"NEGATIVE\"), \"negative\")\n\n def test_neutral_returned_as_is(self):\n self.assertEqual(_parse_feelings(\"neutral\"), \"neutral\")\n self.assertEqual(_parse_feelings(\"Neutral\"), \"neutral\")\n\n def test_empty_or_unknown_returns_neutral(self):\n self.assertEqual(_parse_feelings(\"\"), \"neutral\")\n self.assertEqual(_parse_feelings(\" \"), \"neutral\")\n self.assertEqual(_parse_feelings(\"happy\"), \"neutral\")\n self.assertEqual(_parse_feelings(\"mixed\"), \"neutral\")\n\n\nclass TestCallDeepSeek(unittest.TestCase):\n def test_no_api_key_returns_neutral(self):\n def _getenv(k, d=None):\n if k == \"DEEPSEEK_API_KEY\":\n return None\n return d if d is not None else (\"https://api.deepseek.com\" if k == \"DEEPSEEK_API_BASE\" else \"deepseek-chat\" if k == \"DEEPSEEK_MODEL\" else \"\")\n with patch(\"sentiment_worker.tasks.getenv\", side_effect=_getenv):\n result = _call_deepseek(\"Title\", \"Summary\")\n self.assertEqual(result, \"neutral\")\n\n def test_api_returns_positive_sentiment(self):\n with patch(\"sentiment_worker.tasks.getenv\") as getenv_mock:\n getenv_mock.side_effect = lambda k, d=None: \"sk-fake\" if k == \"DEEPSEEK_API_KEY\" else (d if d is not None else (\"https://api.deepseek.com\" if k == \"DEEPSEEK_API_BASE\" else \"deepseek-chat\" if k == \"DEEPSEEK_MODEL\" else \"\"))\n with patch(\"sentiment_worker.tasks.httpx.Client\") as client_mock:\n resp = client_mock.return_value.__enter__.return_value.post.return_value\n resp.raise_for_status = unittest.mock.MagicMock()\n resp.json.return_value = {\n \"choices\": [{\"message\": {\"content\": \"positive\"}}],\n }\n result = _call_deepseek(\"Good news\", \"Everything is great\")\n self.assertEqual(result, \"positive\")\n\n def test_api_returns_negative_sentiment(self):\n with patch(\"sentiment_worker.tasks.getenv\") as getenv_mock:\n getenv_mock.side_effect = lambda k, d=None: \"sk-fake\" if k == \"DEEPSEEK_API_KEY\" else (d if d is not None else (\"https://api.deepseek.com\" if k == \"DEEPSEEK_API_BASE\" else \"deepseek-chat\" if k == \"DEEPSEEK_MODEL\" else \"\"))\n with patch(\"sentiment_worker.tasks.httpx.Client\") as client_mock:\n resp = client_mock.return_value.__enter__.return_value.post.return_value\n resp.raise_for_status = unittest.mock.MagicMock()\n resp.json.return_value = {\n \"choices\": [{\"message\": {\"content\": \"negative\"}}],\n }\n result = _call_deepseek(\"Bad event\", \"Disaster\")\n self.assertEqual(result, \"negative\")\n\n def test_api_error_returns_neutral(self):\n def _getenv(k, d=None):\n if k == \"DEEPSEEK_API_KEY\":\n return \"sk-fake\"\n return d if d is not None else (\"https://api.deepseek.com\" if k == \"DEEPSEEK_API_BASE\" else \"deepseek-chat\" if k == \"DEEPSEEK_MODEL\" else \"\")\n with patch(\"sentiment_worker.tasks.getenv\", side_effect=_getenv):\n with patch(\"sentiment_worker.tasks.httpx.Client\") as client_mock:\n client_mock.return_value.__enter__.return_value.post.side_effect = httpx.HTTPStatusError(\n \"err\", request=unittest.mock.MagicMock(), response=unittest.mock.MagicMock()\n )\n result = _call_deepseek(\"Title\", \"Summary\")\n self.assertEqual(result, \"neutral\")\n\n\nclass TestAnalyzeNewsSendsCorrectPayload(unittest.TestCase):\n def test_positive_sentiment_sends_positive_feelings(self):\n with patch(\"sentiment_worker.tasks._call_deepseek\", return_value=\"positive\"), patch(\n \"sentiment_worker.tasks.sentiment_app.send_task\"\n ) as send_mock:\n analyze_news(\n project_id=\"proj-1\",\n news_id=\"news-1\",\n title=\"Great success\",\n summary=\"Everyone is happy\",\n )\n send_mock.assert_called_once()\n self.assertEqual(send_mock.call_args.args[0], BACKEND_TASK_NAME)\n self.assertEqual(send_mock.call_args.kwargs[\"queue\"], BACKEND_QUEUE_NAME)\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(kwargs[\"project_id\"], \"proj-1\")\n self.assertEqual(kwargs[\"news_id\"], \"news-1\")\n self.assertEqual(kwargs[\"feelings\"], \"positive\")\n\n def test_neutral_sentiment_sends_neutral_feelings(self):\n with patch(\"sentiment_worker.tasks._call_deepseek\", return_value=\"neutral\"), patch(\n \"sentiment_worker.tasks.sentiment_app.send_task\"\n ) as send_mock:\n analyze_news(\n project_id=\"p\",\n news_id=\"n\",\n title=\"Title\",\n summary=\"Summary\",\n )\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(kwargs[\"feelings\"], \"neutral\")\n\n def test_negative_sentiment_sends_negative_feelings(self):\n with patch(\"sentiment_worker.tasks._call_deepseek\", return_value=\"negative\"), patch(\n \"sentiment_worker.tasks.sentiment_app.send_task\"\n ) as send_mock:\n analyze_news(\n project_id=\"p\",\n news_id=\"n\",\n title=\"Crisis\",\n summary=\"Losses\",\n )\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(kwargs[\"feelings\"], \"negative\")\n\n def test_send_task_exception_does_not_raise(self):\n with patch(\"sentiment_worker.tasks._call_deepseek\", return_value=\"positive\"), patch(\n \"sentiment_worker.tasks.sentiment_app.send_task\"\n ) as send_mock:\n send_mock.side_effect = RuntimeError(\"backend unavailable\")\n analyze_news(\n project_id=\"p\",\n news_id=\"n\",\n title=\"T\",\n summary=\"S\",\n )\n send_mock.assert_called_once()\n\n\nclass TestParseRiskHype(unittest.TestCase):\n def test_parses_risk_and_hype(self):\n self.assertEqual(_parse_risk_hype(\"risk: 0.3 hype: 0.8\"), (0.3, 0.8))\n self.assertEqual(_parse_risk_hype(\"risk: 0.0 hype: 1.0\"), (0.0, 1.0))\n self.assertEqual(_parse_risk_hype(\"risk: 1.0 hype: 0.5\"), (1.0, 0.5))\n self.assertEqual(_parse_risk_hype(\"Risk: 0.7 Hype: 0.2\"), (0.7, 0.2))\n\n def test_clamps_to_zero_one(self):\n self.assertEqual(_parse_risk_hype(\"risk: 1.5 hype: -0.1\"), (1.0, 0.0))\n\n def test_empty_or_invalid_returns_zero_zero(self):\n self.assertEqual(_parse_risk_hype(\"\"), (0.0, 0.0))\n self.assertEqual(_parse_risk_hype(\"no numbers here\"), (0.0, 0.0))\n self.assertEqual(_parse_risk_hype(\"risk: hype:\"), (0.0, 0.0))\n\n\nclass TestAnalyzeThemeSendsCorrectPayload(unittest.TestCase):\n def test_analyze_theme_sends_set_theme_metrics(self):\n with patch(\n \"sentiment_worker.tasks._call_deepseek_theme_metrics\",\n return_value=(0.4, 0.9),\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n analyze_theme(\n project_id=\"proj-1\",\n theme_id=\"theme-uuid\",\n title=\"Topic\",\n summary=\"Summary\",\n news_snippets=[{\"title\": \"N1\", \"summary\": \"S1\"}],\n )\n send_mock.assert_called_once()\n self.assertEqual(send_mock.call_args.args[0], BACKEND_SET_THEME_METRICS)\n self.assertEqual(send_mock.call_args.kwargs[\"queue\"], BACKEND_QUEUE_NAME)\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(kwargs[\"project_id\"], \"proj-1\")\n self.assertEqual(kwargs[\"theme_id\"], \"theme-uuid\")\n self.assertEqual(kwargs[\"risk\"], 0.4)\n self.assertEqual(kwargs[\"hype\"], 0.9)\n\n def test_analyze_theme_send_exception_does_not_raise(self):\n with patch(\n \"sentiment_worker.tasks._call_deepseek_theme_metrics\",\n return_value=(0.0, 0.0),\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n send_mock.side_effect = RuntimeError(\"backend down\")\n analyze_theme(\n project_id=\"p\",\n theme_id=\"t\",\n title=\"T\",\n summary=\"S\",\n )\n send_mock.assert_called_once()\n\n\nclass TestCallDeepSeekThemeSummary(unittest.TestCase):\n def test_no_api_key_returns_current_summary_truncated(self):\n def _getenv(k, d=None):\n if k == \"DEEPSEEK_API_KEY\":\n return None\n return d if d is not None else (\"https://api.deepseek.com\" if k == \"DEEPSEEK_API_BASE\" else \"deepseek-chat\" if k == \"DEEPSEEK_MODEL\" else \"\")\n with patch(\"sentiment_worker.tasks.getenv\", side_effect=_getenv):\n result = _call_deepseek_theme_summary(\"Title\", \"Current summary here\", [])\n self.assertEqual(result, \"Current summary here\")\n\n def test_no_api_key_empty_summary_returns_empty(self):\n def _getenv(k, d=None):\n if k == \"DEEPSEEK_API_KEY\":\n return None\n return d if d is not None else (\"https://api.deepseek.com\" if k == \"DEEPSEEK_API_BASE\" else \"deepseek-chat\" if k == \"DEEPSEEK_MODEL\" else \"\")\n with patch(\"sentiment_worker.tasks.getenv\", side_effect=_getenv):\n result = _call_deepseek_theme_summary(\"Title\", \"\", None)\n self.assertEqual(result, \"\")\n\n def test_api_returns_summary_truncated_to_max_len(self):\n long_content = \"x\" * (THEME_SUMMARY_MAX_LEN + 100)\n with patch(\"sentiment_worker.tasks.getenv\") as getenv_mock:\n getenv_mock.side_effect = lambda k, d=None: \"sk-fake\" if k == \"DEEPSEEK_API_KEY\" else (d if d is not None else (\"https://api.deepseek.com\" if k == \"DEEPSEEK_API_BASE\" else \"deepseek-chat\" if k == \"DEEPSEEK_MODEL\" else \"\"))\n with patch(\"sentiment_worker.tasks.httpx.Client\") as client_mock:\n resp = client_mock.return_value.__enter__.return_value.post.return_value\n resp.raise_for_status = unittest.mock.MagicMock()\n resp.json.return_value = {\n \"choices\": [{\"message\": {\"content\": long_content}}],\n }\n result = _call_deepseek_theme_summary(\"Topic\", \"Old\", [{\"title\": \"N\", \"summary\": \"S\"}])\n self.assertEqual(len(result), THEME_SUMMARY_MAX_LEN)\n self.assertEqual(result, long_content[:THEME_SUMMARY_MAX_LEN])\n\n def test_api_error_returns_current_summary_truncated(self):\n def _getenv(k, d=None):\n if k == \"DEEPSEEK_API_KEY\":\n return \"sk-fake\"\n return d if d is not None else (\"https://api.deepseek.com\" if k == \"DEEPSEEK_API_BASE\" else \"deepseek-chat\" if k == \"DEEPSEEK_MODEL\" else \"\")\n with patch(\"sentiment_worker.tasks.getenv\", side_effect=_getenv):\n with patch(\"sentiment_worker.tasks.httpx.Client\") as client_mock:\n client_mock.return_value.__enter__.return_value.post.side_effect = httpx.HTTPStatusError(\n \"err\", request=unittest.mock.MagicMock(), response=unittest.mock.MagicMock()\n )\n result = _call_deepseek_theme_summary(\"T\", \"Fallback summary\", [])\n self.assertEqual(result, \"Fallback summary\")\n\n\nclass TestSummarizeThemeSendsCorrectPayload(unittest.TestCase):\n def test_summarize_theme_sends_set_theme_summary(self):\n with patch(\n \"sentiment_worker.tasks._call_deepseek_theme_summary\",\n return_value=\"New summary from AI\",\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n summarize_theme(\n project_id=\"proj-1\",\n theme_id=\"theme-uuid\",\n title=\"Topic\",\n summary=\"Old summary\",\n news_snippets=[{\"title\": \"N1\", \"summary\": \"S1\"}],\n )\n send_mock.assert_called_once()\n self.assertEqual(send_mock.call_args.args[0], BACKEND_SET_THEME_SUMMARY)\n self.assertEqual(send_mock.call_args.kwargs[\"queue\"], BACKEND_QUEUE_NAME)\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(kwargs[\"project_id\"], \"proj-1\")\n self.assertEqual(kwargs[\"theme_id\"], \"theme-uuid\")\n self.assertEqual(kwargs[\"summary\"], \"New summary from AI\")\n\n def test_summarize_theme_truncates_summary_to_max_len(self):\n long_summary = \"a\" * (THEME_SUMMARY_MAX_LEN + 50)\n with patch(\n \"sentiment_worker.tasks._call_deepseek_theme_summary\",\n return_value=long_summary,\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n summarize_theme(\n project_id=\"p\",\n theme_id=\"t\",\n title=\"T\",\n summary=\"\",\n news_snippets=[],\n )\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(len(kwargs[\"summary\"]), THEME_SUMMARY_MAX_LEN)\n\n def test_summarize_theme_send_exception_does_not_raise(self):\n with patch(\n \"sentiment_worker.tasks._call_deepseek_theme_summary\",\n return_value=\"S\",\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n send_mock.side_effect = RuntimeError(\"backend down\")\n summarize_theme(\n project_id=\"p\",\n theme_id=\"t\",\n title=\"T\",\n summary=\"S\",\n )\n send_mock.assert_called_once()\n\n\nclass TestGenerateThemeTitleSendsCorrectPayload(unittest.TestCase):\n def test_generate_theme_title_sends_set_theme_title(self):\n with patch(\n \"sentiment_worker.tasks._call_deepseek_theme_title\",\n return_value=\"Сгенерированное название темы\",\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n generate_theme_title(\n project_id=\"proj-1\",\n theme_id=\"theme-uuid\",\n title=\"Topic\",\n summary=\"Old summary\",\n news_snippets=[{\"title\": \"N1\", \"summary\": \"S1\"}],\n )\n send_mock.assert_called_once()\n self.assertEqual(send_mock.call_args.args[0], BACKEND_SET_THEME_TITLE)\n self.assertEqual(send_mock.call_args.kwargs[\"queue\"], BACKEND_QUEUE_NAME)\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(kwargs[\"project_id\"], \"proj-1\")\n self.assertEqual(kwargs[\"theme_id\"], \"theme-uuid\")\n self.assertEqual(kwargs[\"title\"], \"Сгенерированное название темы\")\n\n def test_generate_theme_title_truncates_title_to_max_len(self):\n long_title = \"a\" * (THEME_TITLE_MAX_LEN + 50)\n with patch(\n \"sentiment_worker.tasks._call_deepseek_theme_title\",\n return_value=long_title,\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n generate_theme_title(\n project_id=\"p\",\n theme_id=\"t\",\n title=\"T\",\n summary=\"\",\n news_snippets=[],\n )\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(len(kwargs[\"title\"]), THEME_TITLE_MAX_LEN)\n\n\nclass TestGenerateNewsTitleSendsCorrectPayload(unittest.TestCase):\n def test_generate_news_title_sends_set_news_title(self):\n with patch(\n \"sentiment_worker.tasks._call_deepseek_news_title\",\n return_value=\"Сгенерированный заголовок новости\",\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n generate_news_title(\n project_id=\"proj-1\",\n news_id=\"news-uuid\",\n title=\"First 256 chars...\",\n summary=\"Full Telegram post text here\",\n )\n send_mock.assert_called_once()\n self.assertEqual(send_mock.call_args.args[0], BACKEND_SET_NEWS_TITLE)\n self.assertEqual(send_mock.call_args.kwargs[\"queue\"], BACKEND_QUEUE_NAME)\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(kwargs[\"project_id\"], \"proj-1\")\n self.assertEqual(kwargs[\"news_id\"], \"news-uuid\")\n self.assertEqual(kwargs[\"title\"], \"Сгенерированный заголовок новости\")\n\n def test_generate_news_title_truncates_title_to_max_len(self):\n long_title = \"a\" * (NEWS_TITLE_MAX_LEN + 50)\n with patch(\n \"sentiment_worker.tasks._call_deepseek_news_title\",\n return_value=long_title,\n ), patch(\"sentiment_worker.tasks.sentiment_app.send_task\") as send_mock:\n generate_news_title(\n project_id=\"p\",\n news_id=\"n\",\n title=\"\",\n summary=\"Post text\",\n )\n kwargs = send_mock.call_args.kwargs[\"kwargs\"]\n self.assertEqual(len(kwargs[\"title\"]), NEWS_TITLE_MAX_LEN)\n"}}}} |