mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-12 22:42:13 +02:00
I've added comprehensive unit and integration tests for the recently implemented Slack connector features. This includes tests for backend logic (SlackHistory, indexing tasks, API routes) and frontend components and hooks.
Backend Tests (`surfsense_backend`):
- `test_slack_history.py`:
- Tests for `SlackHistory` class methods including `get_all_channels`,
`get_conversation_history`, timestamp conversions, and message
formatting. Covers various API response scenarios, pagination,
and error handling.
- `test_connectors_indexing_tasks.py`:
- Unit tests for `index_slack_messages` task.
- Scenarios include initial indexing, periodic indexing, on-demand
re-indexing (targeted channels, force re-index, custom dates),
channel filtering, and error handling.
- Mocks `SlackHistory` and database interactions.
- `test_search_source_connectors_routes.py`:
- Tests for `/slack/{connector_id}/discover-channels` endpoint.
- Tests for `/slack/{connector_id}/reindex-channels` endpoint,
verifying parameter passing to the background task.
- Tests for the main `/search-source-connectors/{connector_id}/index`
endpoint for Slack connectors, ensuring the `force_full_reindex`
flag is handled.
Frontend Tests (`surfsense_web`):
- `components/editConnector/EditSlackConnectorConfigForm.test.tsx`:
- Tests rendering of all Slack configuration fields.
- Verifies `onConfigChange` callback functionality.
- Tests conditional rendering of periodic indexing settings.
- Checks disabled state of the form.
- `app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.test.tsx`:
- Tests for the "Channel Management" tab specific to Slack connectors.
- Verifies UI for discovering channels (mocking API calls).
- Tests selection of channels and updating the connector configuration.
- Tests UI for triggering on-demand re-indexing with various options.
- `hooks/useSearchSourceConnectors.test.ts`:
- Unit tests for the `discoverSlackChannels` function, mocking `fetch`.
- Unit tests for the `reindexSlackChannels` function, mocking `fetch`
and verifying correct request body construction.
- Includes testing of the `fetchWithAuth` helper function.
This commit is contained in:
parent
d1f11d4fbb
commit
4bf09b8bde
6 changed files with 1804 additions and 387 deletions
427
surfsense_backend/app/tasks/test_connectors_indexing_tasks.py
Normal file
427
surfsense_backend/app/tasks/test_connectors_indexing_tasks.py
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch, call
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from app.tasks.connectors_indexing_tasks import index_slack_messages
|
||||
from app.db import SearchSourceConnector, SearchSourceConnectorType, Document, DocumentType, Chunk
|
||||
from app.schemas.search_source_connector import SearchSourceConnectorCreate # For creating test connector data
|
||||
from app.connectors.slack_history import SlackHistory # To mock its methods
|
||||
|
||||
# Mock global config object if it's accessed directly
|
||||
# If it's passed around, then mock where it's used/passed.
|
||||
# For this example, assuming app.config.config is used.
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_app_config():
|
||||
mock_llm = MagicMock()
|
||||
mock_llm.ainvoke = AsyncMock(return_value=MagicMock(content="Test Summary"))
|
||||
|
||||
mock_embedding_model = MagicMock()
|
||||
mock_embedding_model.embed = MagicMock(return_value=[0.1, 0.2, 0.3])
|
||||
|
||||
mock_chunker = MagicMock()
|
||||
# Simulate chunker returning list of objects with a 'text' attribute
|
||||
mock_chunker.chunk = MagicMock(return_value=[MagicMock(text="chunk1"), MagicMock(text="chunk2")])
|
||||
|
||||
with patch('app.config.config.long_context_llm_instance', mock_llm), \
|
||||
patch('app.config.config.embedding_model_instance', mock_embedding_model), \
|
||||
patch('app.config.config.chunker_instance', mock_chunker), \
|
||||
patch('app.config.config.SUMMARY_PROMPT_TEMPLATE', MagicMock()): # Assuming this is a simple object
|
||||
yield
|
||||
|
||||
@pytest.fixture
|
||||
def mock_session():
|
||||
session = AsyncMock()
|
||||
session.execute = AsyncMock()
|
||||
session.scalars = MagicMock(return_value=MagicMock(first=MagicMock(return_value=None), all=MagicMock(return_value=[])))
|
||||
session.commit = AsyncMock()
|
||||
session.rollback = AsyncMock()
|
||||
session.add = MagicMock()
|
||||
session.refresh = AsyncMock()
|
||||
return session
|
||||
|
||||
@pytest.fixture
|
||||
def base_slack_connector():
|
||||
return SearchSourceConnector(
|
||||
id=1,
|
||||
user_id="test_user_1",
|
||||
search_space_id=1,
|
||||
name="Test Slack Connector",
|
||||
connector_type=SearchSourceConnectorType.SLACK_CONNECTOR,
|
||||
is_indexable=True,
|
||||
config={
|
||||
"SLACK_BOT_TOKEN": "xoxb-dummy-token",
|
||||
"slack_initial_indexing_days": 30,
|
||||
"slack_initial_max_messages_per_channel": 100,
|
||||
"slack_max_messages_per_channel_periodic": 50,
|
||||
"slack_membership_filter_type": "all_member_channels",
|
||||
"slack_selected_channel_ids": [],
|
||||
"slack_periodic_indexing_enabled": True, # New field from previous task
|
||||
"slack_periodic_indexing_frequency": "daily" # New field
|
||||
},
|
||||
last_indexed_at=None,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def mock_slack_history_instance():
|
||||
instance = AsyncMock(spec=SlackHistory)
|
||||
instance.get_all_channels = AsyncMock(return_value=[])
|
||||
instance.get_conversation_history = AsyncMock(return_value=[])
|
||||
# format_message should return a dict, not an AsyncMock
|
||||
instance.format_message = MagicMock(return_value={
|
||||
"user_id": "U123", "text": "Formatted message", "datetime": "2023-01-01 10:00:00 UTC"
|
||||
})
|
||||
return instance
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_connector_not_found(mock_session):
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = None
|
||||
count, error = await index_slack_messages(mock_session, 999, 1)
|
||||
assert count == 0
|
||||
assert "Connector with ID 999 not found" in error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_connector_not_slack_type(mock_session, base_slack_connector):
|
||||
base_slack_connector.connector_type = SearchSourceConnectorType.NOTION_CONNECTOR
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
count, error = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 0
|
||||
assert "is not a Slack connector" in error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_slack_token_missing(mock_session, base_slack_connector):
|
||||
base_slack_connector.config = {} # No token
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
count, error = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 0
|
||||
assert "Slack token not found" in error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_no_channels_returned_from_api(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
mock_slack_history_instance.get_all_channels = AsyncMock(return_value=[])
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
|
||||
count, error = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 0
|
||||
assert error == "No channels to index after filtering." # This is the specific message when no channels are left
|
||||
# Verify last_indexed_at is updated because the task "ran" successfully even if no channels
|
||||
assert base_slack_connector.last_indexed_at is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_all_member_channels_filter(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
mock_slack_history_instance.get_all_channels.return_value = [
|
||||
{"id": "C1", "name": "Channel1", "is_member": True, "is_private": False},
|
||||
{"id": "C2", "name": "Channel2", "is_member": True, "is_private": False}, # Bot is member
|
||||
]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [
|
||||
{"ts": "1", "user": "U1", "text": "msg1"},
|
||||
]
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
base_slack_connector.config["slack_membership_filter_type"] = "all_member_channels"
|
||||
|
||||
count, _ = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 2 # Two documents created, one for each channel
|
||||
assert mock_slack_history_instance.get_conversation_history.call_count == 2
|
||||
mock_slack_history_instance.get_conversation_history.assert_any_call(channel_id="C1", limit=100, oldest=mock_slack_history_instance.convert_date_to_timestamp.return_value, latest=mock_slack_history_instance.convert_date_to_timestamp.return_value)
|
||||
mock_slack_history_instance.get_conversation_history.assert_any_call(channel_id="C2", limit=100, oldest=mock_slack_history_instance.convert_date_to_timestamp.return_value, latest=mock_slack_history_instance.convert_date_to_timestamp.return_value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_selected_member_channels_filter(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
mock_slack_history_instance.get_all_channels.return_value = [
|
||||
{"id": "C1", "name": "Channel1", "is_member": True, "is_private": False},
|
||||
{"id": "C2", "name": "Channel2", "is_member": True, "is_private": False},
|
||||
{"id": "C3", "name": "Channel3", "is_member": True, "is_private": False},
|
||||
]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [{"ts": "1", "user": "U1", "text": "msg1"}]
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
base_slack_connector.config["slack_membership_filter_type"] = "selected_member_channels"
|
||||
base_slack_connector.config["slack_selected_channel_ids"] = ["C1", "C3"]
|
||||
|
||||
count, _ = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 2
|
||||
assert mock_slack_history_instance.get_conversation_history.call_count == 2
|
||||
mock_slack_history_instance.get_conversation_history.assert_any_call(channel_id="C1", limit=100, oldest=mock_slack_history_instance.convert_date_to_timestamp.return_value, latest=mock_slack_history_instance.convert_date_to_timestamp.return_value)
|
||||
mock_slack_history_instance.get_conversation_history.assert_any_call(channel_id="C3", limit=100, oldest=mock_slack_history_instance.convert_date_to_timestamp.return_value, latest=mock_slack_history_instance.convert_date_to_timestamp.return_value)
|
||||
# C2 should not have been called
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_target_channel_ids_interaction(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
mock_slack_history_instance.get_all_channels.return_value = [
|
||||
{"id": "C1", "name": "Channel1", "is_member": True, "is_private": False},
|
||||
{"id": "C2", "name": "Channel2", "is_member": True, "is_private": False}, # Configured but not targeted
|
||||
{"id": "C3", "name": "Channel3", "is_member": True, "is_private": False}, # Targeted
|
||||
]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [{"ts": "1", "user": "U1", "text": "msg1"}]
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
base_slack_connector.config["slack_membership_filter_type"] = "all_member_channels" # All are initially valid
|
||||
|
||||
count, _ = await index_slack_messages(mock_session, 1, 1, target_channel_ids=["C1", "C3"])
|
||||
assert count == 2 # Only C1 and C3
|
||||
mock_slack_history_instance.get_conversation_history.assert_any_call(channel_id="C1", limit=base_slack_connector.config["slack_max_messages_per_channel_periodic"], oldest=mock_slack_history_instance.convert_date_to_timestamp.return_value, latest=mock_slack_history_instance.convert_date_to_timestamp.return_value)
|
||||
mock_slack_history_instance.get_conversation_history.assert_any_call(channel_id="C3", limit=base_slack_connector.config["slack_max_messages_per_channel_periodic"], oldest=mock_slack_history_instance.convert_date_to_timestamp.return_value, latest=mock_slack_history_instance.convert_date_to_timestamp.return_value)
|
||||
# Ensure C2 was not called for history
|
||||
calls = mock_slack_history_instance.get_conversation_history.call_args_list
|
||||
for c in calls:
|
||||
assert c.kwargs['channel_id'] != "C2"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_initial_indexing_days_logic(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
base_slack_connector.last_indexed_at = None # Ensure initial run
|
||||
base_slack_connector.config["slack_initial_indexing_days"] = 7
|
||||
|
||||
# Mock convert_date_to_timestamp to check its input
|
||||
mock_converter = MagicMock()
|
||||
# Make it return a fixed value so we can check the call to get_conversation_history
|
||||
fixed_ts = str(int((datetime.now(timezone.utc) - timedelta(days=7)).timestamp()))
|
||||
mock_converter.return_value = fixed_ts
|
||||
# Patch the static method on the class, not the instance
|
||||
with patch.object(SlackHistory, 'convert_date_to_timestamp', mock_converter):
|
||||
mock_slack_history_instance.get_all_channels.return_value = [{"id": "C1", "name": "Chan1", "is_member": True}]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [{"ts": "1"}]
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
|
||||
await index_slack_messages(mock_session, 1, 1)
|
||||
|
||||
# Check if convert_date_to_timestamp was called correctly for the start date
|
||||
# It's called internally for both oldest and latest. We are interested in the 'oldest' logic.
|
||||
# The actual call to SlackHistory.convert_date_to_timestamp for 'oldest' happens inside index_slack_messages.
|
||||
# We can assert that get_conversation_history was called with the 'fixed_ts'
|
||||
|
||||
latest_ts_for_call = str(int((datetime.now(timezone.utc) + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).timestamp()))
|
||||
|
||||
mock_slack_history_instance.get_conversation_history.assert_called_once_with(
|
||||
channel_id="C1",
|
||||
limit=base_slack_connector.config["slack_initial_max_messages_per_channel"],
|
||||
oldest=fixed_ts, # This is the key check
|
||||
latest=latest_ts_for_call
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_initial_indexing_all_time(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
base_slack_connector.last_indexed_at = None
|
||||
base_slack_connector.config["slack_initial_indexing_days"] = -1
|
||||
mock_slack_history_instance.get_all_channels.return_value = [{"id": "C1", "name": "Chan1", "is_member": True}]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [{"ts": "1"}]
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
|
||||
await index_slack_messages(mock_session, 1, 1)
|
||||
latest_ts_for_call = str(int((datetime.now(timezone.utc) + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).timestamp()))
|
||||
mock_slack_history_instance.get_conversation_history.assert_called_once_with(
|
||||
channel_id="C1",
|
||||
limit=base_slack_connector.config["slack_initial_max_messages_per_channel"],
|
||||
oldest="0", # All time
|
||||
latest=latest_ts_for_call
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_periodic_indexing_logic(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
last_indexed_time = datetime.now(timezone.utc) - timedelta(days=5)
|
||||
base_slack_connector.last_indexed_at = last_indexed_time
|
||||
|
||||
mock_slack_history_instance.get_all_channels.return_value = [{"id": "C1", "name": "Chan1", "is_member": True}]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [{"ts": "1"}]
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
|
||||
await index_slack_messages(mock_session, 1, 1)
|
||||
|
||||
expected_oldest_ts = str(int(last_indexed_time.timestamp()))
|
||||
latest_ts_for_call = str(int((datetime.now(timezone.utc) + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).timestamp()))
|
||||
|
||||
mock_slack_history_instance.get_conversation_history.assert_called_once_with(
|
||||
channel_id="C1",
|
||||
limit=base_slack_connector.config["slack_max_messages_per_channel_periodic"],
|
||||
oldest=expected_oldest_ts,
|
||||
latest=latest_ts_for_call
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_reindex_force_all_with_dates(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
mock_slack_history_instance.get_all_channels.return_value = [{"id": "C1", "name": "Chan1", "is_member": True}]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [{"ts": "1"}]
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
|
||||
start_date_str = "2023-03-01"
|
||||
latest_date_str = "2023-03-10"
|
||||
expected_oldest = SlackHistory.convert_date_to_timestamp(start_date_str)
|
||||
expected_latest = SlackHistory.convert_date_to_timestamp(latest_date_str, is_latest=True)
|
||||
|
||||
await index_slack_messages(mock_session, 1, 1,
|
||||
target_channel_ids=["C1"],
|
||||
force_reindex_all_messages=True,
|
||||
reindex_start_date_str=start_date_str,
|
||||
reindex_latest_date_str=latest_date_str)
|
||||
|
||||
mock_slack_history_instance.get_conversation_history.assert_called_once_with(
|
||||
channel_id="C1",
|
||||
limit=base_slack_connector.config["slack_initial_max_messages_per_channel"], # Uses initial limit
|
||||
oldest=expected_oldest,
|
||||
latest=expected_latest
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_document_creation(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
mock_slack_history_instance.get_all_channels.return_value = [{"id": "C1", "name": "TestChannel", "is_member": True}]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [
|
||||
{"ts": "1", "user": "U1", "text": "Hello"},
|
||||
{"ts": "2", "user": "U2", "text": "World", "subtype": "bot_message"}, # Should be skipped
|
||||
]
|
||||
# Configure format_message to return what's needed by the task
|
||||
mock_slack_history_instance.format_message.side_effect = lambda msg, include_user_info=False: {
|
||||
"user_id": msg["user"], "text": msg["text"], "datetime": "time", "subtype": msg.get("subtype")
|
||||
}
|
||||
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
# Simulate no existing document for this channel
|
||||
mock_session.execute.return_value.scalars.return_value.all.return_value = []
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector # For connector fetch
|
||||
|
||||
count, _ = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 1
|
||||
|
||||
# Check Document creation (session.add was called with a Document instance)
|
||||
added_object = mock_session.add.call_args[0][0]
|
||||
assert isinstance(added_object, Document)
|
||||
assert added_object.title == "Slack - TestChannel"
|
||||
assert added_object.document_type == DocumentType.SLACK_CONNECTOR
|
||||
assert added_object.document_metadata["channel_id"] == "C1"
|
||||
assert added_object.document_metadata["message_count"] == 1 # Only one valid message
|
||||
assert added_object.content == "Test Summary" # From mock_app_config
|
||||
assert len(added_object.chunks) == 2 # From mock_app_config chunker
|
||||
|
||||
# Check that bot message was skipped via format_message calls
|
||||
format_calls = mock_slack_history_instance.format_message.call_args_list
|
||||
assert len(format_calls) == 1 # Only called for the non-bot message
|
||||
assert format_calls[0].args[0]['text'] == "Hello"
|
||||
|
||||
assert mock_session.commit.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_document_update(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
mock_slack_history_instance.get_all_channels.return_value = [{"id": "C1", "name": "TestChannel", "is_member": True}]
|
||||
mock_slack_history_instance.get_conversation_history.return_value = [{"ts": "1", "user": "U1", "text": "Updated Content"}]
|
||||
mock_slack_history_instance.format_message.return_value = {"user_id": "U1", "text": "Updated Content", "datetime":"time"}
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
|
||||
existing_doc = Document(id=101, search_space_id=1, title="Old Title", document_type=DocumentType.SLACK_CONNECTOR, document_metadata={"channel_id": "C1"})
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
# Simulate existing document for this channel_id
|
||||
mock_session.execute.return_value.scalars.return_value.all.return_value = [existing_doc]
|
||||
|
||||
count, _ = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 1 # 0 new, 1 updated
|
||||
|
||||
assert existing_doc.content == "Test Summary" # Updated content from summary
|
||||
assert existing_doc.document_metadata["message_count"] == 1
|
||||
# Check that session.delete was called for old chunks (implicitly via relationship or explicit call)
|
||||
# This depends on ORM setup or if task explicitly deletes.
|
||||
# For this test, we assume the task handles deleting old chunks if needed.
|
||||
# The provided task code does execute a delete statement for chunks.
|
||||
delete_chunk_call = None
|
||||
for call_item in mock_session.execute.call_args_list:
|
||||
if "DELETE" in str(call_item.args[0]): # Check if a DELETE statement was executed
|
||||
delete_chunk_call = call_item
|
||||
break
|
||||
assert delete_chunk_call is not None
|
||||
|
||||
# Verify new chunks are added
|
||||
num_new_chunks_added = 0
|
||||
for call_item in mock_session.add.call_args_list:
|
||||
if isinstance(call_item.args[0], Chunk):
|
||||
num_new_chunks_added +=1
|
||||
assert call_item.args[0].document_id == existing_doc.id # Associated with existing doc
|
||||
assert num_new_chunks_added == 2 # From mock_app_config chunker
|
||||
|
||||
assert mock_session.commit.call_count == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_last_indexed_at_update_logic(mock_session, base_slack_connector):
|
||||
# Scenario 1: update_last_indexed = True, documents processed
|
||||
mock_slack_history_instance_s1 = MagicMock(spec=SlackHistory)
|
||||
mock_slack_history_instance_s1.get_all_channels = AsyncMock(return_value=[{"id": "C1", "name": "Chan1", "is_member": True}])
|
||||
mock_slack_history_instance_s1.get_conversation_history = AsyncMock(return_value=[{"ts": "1", "user": "U1", "text": "msg"}])
|
||||
mock_slack_history_instance_s1.format_message = MagicMock(return_value={"user_id":"U1", "text":"msg", "datetime":"t"})
|
||||
|
||||
with patch('app.tasks.connectors_indexing_tasks.SlackHistory', return_value=mock_slack_history_instance_s1):
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
base_slack_connector.last_indexed_at = None # Reset for this test part
|
||||
await index_slack_messages(mock_session, 1, 1, update_last_indexed=True)
|
||||
assert base_slack_connector.last_indexed_at is not None
|
||||
original_last_indexed_at = base_slack_connector.last_indexed_at
|
||||
|
||||
# Scenario 2: update_last_indexed = False
|
||||
mock_slack_history_instance_s2 = MagicMock(spec=SlackHistory)
|
||||
mock_slack_history_instance_s2.get_all_channels = AsyncMock(return_value=[{"id": "C1", "name": "Chan1", "is_member": True}])
|
||||
mock_slack_history_instance_s2.get_conversation_history = AsyncMock(return_value=[{"ts": "1", "user": "U1", "text": "msg"}])
|
||||
mock_slack_history_instance_s2.format_message = MagicMock(return_value={"user_id":"U1", "text":"msg", "datetime":"t"})
|
||||
|
||||
with patch('app.tasks.connectors_indexing_tasks.SlackHistory', return_value=mock_slack_history_instance_s2):
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
# last_indexed_at should remain as it was from scenario 1
|
||||
await index_slack_messages(mock_session, 1, 1, update_last_indexed=False)
|
||||
assert base_slack_connector.last_indexed_at == original_last_indexed_at
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('app.tasks.connectors_indexing_tasks.SlackHistory')
|
||||
async def test_error_get_all_channels_exception(MockSlackHistory, mock_session, base_slack_connector, mock_slack_history_instance):
|
||||
mock_slack_history_instance.get_all_channels.side_effect = Exception("Failed to get channels")
|
||||
MockSlackHistory.return_value = mock_slack_history_instance
|
||||
mock_session.execute.return_value.scalars.return_value.first.return_value = base_slack_connector
|
||||
|
||||
count, error = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 0
|
||||
assert "Failed to get Slack channels: Failed to get channels" in error
|
||||
assert mock_session.rollback.call_count == 1 # Should rollback on general exception
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_db_error_on_commit(mock_session, base_slack_connector):
|
||||
# This test needs to allow the initial connector fetch to succeed, then fail on commit
|
||||
mock_connector_fetch_result = MagicMock()
|
||||
mock_connector_fetch_result.scalars.return_value.first.return_value = base_slack_connector
|
||||
|
||||
mock_docs_fetch_result = MagicMock()
|
||||
mock_docs_fetch_result.scalars.return_value.all.return_value = [] # No existing docs
|
||||
|
||||
# Configure side_effect for session.execute
|
||||
mock_session.execute.side_effect = [
|
||||
mock_connector_fetch_result, # First call for getting connector
|
||||
mock_docs_fetch_result # Second call for getting existing_docs
|
||||
]
|
||||
|
||||
mock_session.commit.side_effect = SQLAlchemyError("DB Commit Failed")
|
||||
|
||||
mock_slack_history_instance_db_error = MagicMock(spec=SlackHistory)
|
||||
mock_slack_history_instance_db_error.get_all_channels = AsyncMock(return_value=[{"id": "C1", "name": "Chan1", "is_member": True}])
|
||||
mock_slack_history_instance_db_error.get_conversation_history = AsyncMock(return_value=[{"ts": "1", "user":"U1", "text":"msg"}])
|
||||
mock_slack_history_instance_db_error.format_message = MagicMock(return_value={"user_id":"U1", "text":"msg", "datetime":"t"})
|
||||
|
||||
with patch('app.tasks.connectors_indexing_tasks.SlackHistory', return_value=mock_slack_history_instance_db_error):
|
||||
count, error = await index_slack_messages(mock_session, 1, 1)
|
||||
assert count == 0
|
||||
assert "Database error: DB Commit Failed" in error
|
||||
assert mock_session.rollback.call_count == 1 # Ensure rollback on SQLAlchemyError
|
||||
Loading…
Add table
Add a link
Reference in a new issue