Release Notes: dev-12 #14
50
.github/workflows/deploy.yml
vendored
50
.github/workflows/deploy.yml
vendored
@@ -173,6 +173,56 @@ jobs:
|
||||
🔗 View details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Get PR body from merged PR
|
||||
if: job.status == 'success' && github.event_name == 'push'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "🔍 Searching for merged PR associated with commit ${{ github.sha }}..."
|
||||
|
||||
# Находим последний мерженный PR для main ветки по merge commit SHA
|
||||
COMMIT_SHA="${{ github.sha }}"
|
||||
PR_NUMBER=$(gh pr list --state merged --base main --limit 10 --json number,mergeCommit --jq ".[] | select(.mergeCommit.oid == \"$COMMIT_SHA\") | .number" | head -1)
|
||||
|
||||
# Если не нашли по merge commit, ищем последний мерженный PR
|
||||
if [ -z "$PR_NUMBER" ]; then
|
||||
echo "⚠️ PR not found by merge commit, trying to get latest merged PR..."
|
||||
PR_NUMBER=$(gh pr list --state merged --base main --limit 1 --json number --jq '.[0].number')
|
||||
fi
|
||||
|
||||
if [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != "null" ]; then
|
||||
echo "✅ Found PR #$PR_NUMBER"
|
||||
PR_BODY=$(gh pr view $PR_NUMBER --json body --jq '.body // ""')
|
||||
|
||||
if [ -n "$PR_BODY" ] && [ "$PR_BODY" != "null" ]; then
|
||||
echo "PR_BODY<<EOF" >> $GITHUB_ENV
|
||||
echo "$PR_BODY" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
|
||||
echo "✅ PR body extracted successfully"
|
||||
else
|
||||
echo "⚠️ PR body is empty"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ No merged PR found for this commit"
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
- name: Send PR body to important logs
|
||||
if: job.status == 'success' && github.event_name == 'push' && env.PR_BODY != ''
|
||||
uses: appleboy/telegram-action@v1.0.0
|
||||
with:
|
||||
to: ${{ secrets.IMPORTANT_LOGS_CHAT }}
|
||||
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
message: |
|
||||
📋 Pull Request Description (PR #${{ env.PR_NUMBER }}):
|
||||
|
||||
${{ env.PR_BODY }}
|
||||
|
||||
🔗 PR: ${{ github.server_url }}/${{ github.repository }}/pull/${{ env.PR_NUMBER }}
|
||||
📝 Commit: ${{ github.sha }}
|
||||
continue-on-error: true
|
||||
|
||||
rollback:
|
||||
runs-on: ubuntu-latest
|
||||
name: Rollback to Previous Version
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Репозиторий для работы с миграциями базы данных."""
|
||||
import aiosqlite
|
||||
|
||||
from database.base import DatabaseConnection
|
||||
|
||||
|
||||
|
||||
@@ -7,17 +7,12 @@
|
||||
- ScoringManager - объединение всех сервисов скоринга
|
||||
"""
|
||||
|
||||
from .base import ScoringResult, ScoringServiceProtocol, CombinedScore
|
||||
from .exceptions import (
|
||||
ScoringError,
|
||||
ModelNotLoadedError,
|
||||
VectorStoreError,
|
||||
DeepSeekAPIError,
|
||||
InsufficientExamplesError,
|
||||
TextTooShortError,
|
||||
)
|
||||
from .rag_client import RagApiClient
|
||||
from .base import CombinedScore, ScoringResult, ScoringServiceProtocol
|
||||
from .deepseek_service import DeepSeekService
|
||||
from .exceptions import (DeepSeekAPIError, InsufficientExamplesError,
|
||||
ModelNotLoadedError, ScoringError, TextTooShortError,
|
||||
VectorStoreError)
|
||||
from .rag_client import RagApiClient
|
||||
from .scoring_manager import ScoringManager
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Protocol, Dict, Any
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional, Protocol
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -6,12 +6,11 @@ DeepSeek API сервис для скоринга постов.
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Optional, List
|
||||
from typing import List, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from helper_bot.utils.metrics import track_errors, track_time
|
||||
from logs.custom_logger import logger
|
||||
from helper_bot.utils.metrics import track_time, track_errors
|
||||
|
||||
from .base import ScoringResult
|
||||
from .exceptions import DeepSeekAPIError, ScoringError, TextTooShortError
|
||||
|
||||
@@ -4,13 +4,15 @@ HTTP клиент для взаимодействия с внешним RAG се
|
||||
Использует REST API для получения скоров и отправки примеров.
|
||||
"""
|
||||
|
||||
from typing import Optional, Dict, Any
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from helper_bot.utils.metrics import track_errors, track_time
|
||||
from logs.custom_logger import logger
|
||||
from helper_bot.utils.metrics import track_time, track_errors
|
||||
|
||||
from .base import ScoringResult
|
||||
from .exceptions import ScoringError, InsufficientExamplesError, TextTooShortError
|
||||
from .exceptions import (InsufficientExamplesError, ScoringError,
|
||||
TextTooShortError)
|
||||
|
||||
|
||||
class RagApiClient:
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
from helper_bot.utils.metrics import track_errors, track_time
|
||||
from logs.custom_logger import logger
|
||||
from helper_bot.utils.metrics import track_time, track_errors
|
||||
|
||||
from .base import CombinedScore, ScoringResult
|
||||
from .rag_client import RagApiClient
|
||||
from .deepseek_service import DeepSeekService
|
||||
from .exceptions import ScoringError, InsufficientExamplesError, TextTooShortError
|
||||
from .exceptions import (InsufficientExamplesError, ScoringError,
|
||||
TextTooShortError)
|
||||
from .rag_client import RagApiClient
|
||||
|
||||
|
||||
class ScoringManager:
|
||||
|
||||
@@ -130,11 +130,8 @@ class BaseDependencyFactory:
|
||||
|
||||
Вызывается лениво при первом обращении к get_scoring_manager().
|
||||
"""
|
||||
from helper_bot.services.scoring import (
|
||||
ScoringManager,
|
||||
RagApiClient,
|
||||
DeepSeekService,
|
||||
)
|
||||
from helper_bot.services.scoring import (DeepSeekService, RagApiClient,
|
||||
ScoringManager)
|
||||
|
||||
scoring_config = self.settings['Scoring']
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ class TestKeyboards:
|
||||
|
||||
assert isinstance(keyboard, ReplyKeyboardMarkup)
|
||||
assert keyboard.keyboard is not None
|
||||
assert len(keyboard.keyboard) == 2 # Две строки
|
||||
assert len(keyboard.keyboard) == 3 # Три строки
|
||||
|
||||
# Проверяем первую строку (3 кнопки)
|
||||
first_row = keyboard.keyboard[0]
|
||||
@@ -124,7 +124,12 @@ class TestKeyboards:
|
||||
second_row = keyboard.keyboard[1]
|
||||
assert len(second_row) == 2
|
||||
assert second_row[0].text == "Разбан (список)"
|
||||
assert second_row[1].text == "Вернуться в бота"
|
||||
assert second_row[1].text == "📊 ML Статистика"
|
||||
|
||||
# Проверяем третью строку (1 кнопка)
|
||||
third_row = keyboard.keyboard[2]
|
||||
assert len(third_row) == 1
|
||||
assert third_row[0].text == "Вернуться в бота"
|
||||
|
||||
def test_get_reply_keyboard_for_post(self):
|
||||
"""Тест клавиатуры для постов"""
|
||||
|
||||
@@ -3,16 +3,14 @@
|
||||
"""
|
||||
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
# Импорты для тестирования базовых классов
|
||||
from helper_bot.services.scoring.base import ScoringResult, CombinedScore
|
||||
from helper_bot.services.scoring.exceptions import (
|
||||
ScoringError,
|
||||
InsufficientExamplesError,
|
||||
TextTooShortError,
|
||||
)
|
||||
from helper_bot.services.scoring.base import CombinedScore, ScoringResult
|
||||
from helper_bot.services.scoring.exceptions import (InsufficientExamplesError,
|
||||
ScoringError,
|
||||
TextTooShortError)
|
||||
|
||||
|
||||
class TestScoringResult:
|
||||
@@ -215,7 +213,8 @@ class TestDeepSeekService:
|
||||
@pytest.fixture
|
||||
def deepseek_service(self):
|
||||
"""Создает DeepSeekService для тестов."""
|
||||
from helper_bot.services.scoring.deepseek_service import DeepSeekService
|
||||
from helper_bot.services.scoring.deepseek_service import \
|
||||
DeepSeekService
|
||||
return DeepSeekService(
|
||||
api_key="test_key",
|
||||
enabled=True,
|
||||
@@ -224,7 +223,8 @@ class TestDeepSeekService:
|
||||
|
||||
def test_service_disabled_without_key(self):
|
||||
"""Тест отключения сервиса без API ключа."""
|
||||
from helper_bot.services.scoring.deepseek_service import DeepSeekService
|
||||
from helper_bot.services.scoring.deepseek_service import \
|
||||
DeepSeekService
|
||||
service = DeepSeekService(api_key=None, enabled=True)
|
||||
|
||||
assert service.is_enabled is False
|
||||
@@ -255,7 +255,8 @@ class TestDeepSeekService:
|
||||
@pytest.mark.asyncio
|
||||
async def test_calculate_score_disabled(self):
|
||||
"""Тест расчета скора при отключенном сервисе."""
|
||||
from helper_bot.services.scoring.deepseek_service import DeepSeekService
|
||||
from helper_bot.services.scoring.deepseek_service import \
|
||||
DeepSeekService
|
||||
service = DeepSeekService(api_key=None, enabled=False)
|
||||
|
||||
with pytest.raises(ScoringError):
|
||||
@@ -281,6 +282,8 @@ class TestScoringManager:
|
||||
source="rag",
|
||||
model="rubert",
|
||||
))
|
||||
mock.add_positive_example = AsyncMock()
|
||||
mock.add_negative_example = AsyncMock()
|
||||
return mock
|
||||
|
||||
@pytest.fixture
|
||||
@@ -293,6 +296,8 @@ class TestScoringManager:
|
||||
source="deepseek",
|
||||
model="deepseek-chat",
|
||||
))
|
||||
mock.add_positive_example = AsyncMock()
|
||||
mock.add_negative_example = AsyncMock()
|
||||
return mock
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -301,7 +306,7 @@ class TestScoringManager:
|
||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||
|
||||
manager = ScoringManager(
|
||||
rag_service=mock_rag_service,
|
||||
rag_client=mock_rag_service,
|
||||
deepseek_service=mock_deepseek_service,
|
||||
)
|
||||
|
||||
@@ -317,7 +322,7 @@ class TestScoringManager:
|
||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||
|
||||
manager = ScoringManager(
|
||||
rag_service=mock_rag_service,
|
||||
rag_client=mock_rag_service,
|
||||
deepseek_service=None,
|
||||
)
|
||||
|
||||
@@ -331,7 +336,7 @@ class TestScoringManager:
|
||||
"""Тест скоринга пустого текста."""
|
||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||
|
||||
manager = ScoringManager(rag_service=mock_rag_service)
|
||||
manager = ScoringManager(rag_client=mock_rag_service)
|
||||
|
||||
result = await manager.score_post("")
|
||||
|
||||
@@ -347,7 +352,7 @@ class TestScoringManager:
|
||||
mock_rag_service.calculate_score = AsyncMock(side_effect=Exception("Test error"))
|
||||
|
||||
manager = ScoringManager(
|
||||
rag_service=mock_rag_service,
|
||||
rag_client=mock_rag_service,
|
||||
deepseek_service=mock_deepseek_service,
|
||||
)
|
||||
|
||||
@@ -365,7 +370,7 @@ class TestScoringManager:
|
||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||
|
||||
manager = ScoringManager(
|
||||
rag_service=mock_rag_service,
|
||||
rag_client=mock_rag_service,
|
||||
deepseek_service=mock_deepseek_service,
|
||||
)
|
||||
|
||||
@@ -380,7 +385,7 @@ class TestScoringManager:
|
||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||
|
||||
manager = ScoringManager(
|
||||
rag_service=mock_rag_service,
|
||||
rag_client=mock_rag_service,
|
||||
deepseek_service=mock_deepseek_service,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user