Skip to content

test: enhance tests for conversation and persona management#5361

Open
whatevertogo wants to merge 4 commits intoAstrBotDevs:masterfrom
whatevertogo:test/conversation-persona
Open

test: enhance tests for conversation and persona management#5361
whatevertogo wants to merge 4 commits intoAstrBotDevs:masterfrom
whatevertogo:test/conversation-persona

Conversation

@whatevertogo
Copy link
Contributor

@whatevertogo whatevertogo commented Feb 23, 2026

Expand test coverage for conversation and persona management modules to improve reliability of conversation history and persona functionality.

Modifications / 改动点

  • Expanded tests/unit/test_conversation_mgr.py with additional test cases
  • Updated persona manager tests in tests/unit/test_persona_mgr.py

Test Coverage:

  • Conversation creation, retrieval, and deletion

  • Conversation session management

  • Persona creation, update, and deletion

  • Persona assignment and validation

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

# Verification: Conversation manager tests pass
$ pytest tests/unit/test_conversation_mgr.py -v
...
collected 18 items

tests/unit/test_conversation_mgr.py::TestConversationMgr::test_create_conversation PASSED
tests/unit/test_conversation_mgr.py::TestConversationMgr::test_retrieve_conversation PASSED
tests/unit/test_conversation_mgr.py::TestConversationMgr::test_delete_conversation PASSED
...

# Verification: Persona manager tests pass
$ pytest tests/unit/test_persona_mgr.py -v
...
collected 15 items

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了"验证步骤"和"运行截图"。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

扩展对会话和人物配置管理的单元测试覆盖范围,以验证核心行为、配置处理,以及在并发访问下的健壮性。

Tests:

  • 增加对 PersonaManager 的全面测试,覆盖初始化、CRUD 操作、文件夹管理、v3 人物数据转换、热重载行为以及排序顺序更新。
  • 增加对 ConversationManager 的广泛测试,覆盖会话生命周期、会话与会话 ID 的映射、删除流程及回调、历史记录更新、消息追加、v2 到 v1 的转换,以及并发读/写/切换场景。
Original summary in English

Summary by Sourcery

Expand unit test coverage for conversation and persona management to validate core behaviors, configuration handling, and robustness under concurrent access.

Tests:

  • Add comprehensive PersonaManager tests covering initialization, CRUD operations, folder management, v3 persona data conversion, hot reload behavior, and sort-order updates.
  • Add extensive ConversationManager tests covering conversation lifecycle, session mapping, deletion flows and callbacks, history updates, message appends, v2-to-v1 conversion, and concurrent read/write/switch scenarios.

- Expand conversation manager tests
- Update persona manager tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 23, 2026 00:58
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 23, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @whatevertogo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the test coverage for the conversation and persona management modules. The primary goal is to improve the reliability and stability of these core functionalities by adding extensive unit tests. These new tests validate various operations, including concurrent access patterns and dynamic updates, ensuring that conversation history and persona configurations behave as expected under diverse conditions.

Highlights

  • Expanded Conversation Management Tests: Introduced a new comprehensive test suite for the ConversationManager, covering core functionalities like creation, retrieval, deletion, and session management, as well as concurrent access scenarios.
  • Enhanced Persona Management Tests: Added a new extensive test file for the PersonaManager, validating persona lifecycle operations (create, update, delete), folder management, and critical hot reload capabilities.
  • Concurrent Operations Validation: Included specific tests to ensure the robustness and correct behavior of ConversationManager methods under concurrent access conditions, such as simultaneous reads, updates, and deletions.
  • Persona Hot Reload Testing: Implemented tests to verify that the PersonaManager correctly handles dynamic changes to personas, including additions, deletions, modifications, and folder reassignments, without requiring a full application restart.
Changelog
  • tests/unit/test_conversation_mgr.py
    • Added comprehensive unit tests for the ConversationManager, covering initialization, conversation lifecycle (create, retrieve, delete), session management, and concurrent access scenarios.
    • Included tests for new_conversation, switch_conversation, delete_conversation, get_conversation, update_conversation, and add_message_pair methods.
    • Validated concurrent access for various operations like creating, reading, updating, switching, and deleting conversations.
  • tests/unit/test_persona_mgr.py
    • Added comprehensive unit tests for the PersonaManager, covering initialization, persona lifecycle (create, retrieve, update, delete), and folder management.
    • Included tests for get_persona, create_persona, update_persona, delete_persona, get_all_personas, get_personas_by_folder, and move_persona_to_folder methods.
    • Validated folder management operations including create_folder, get_folder, get_folders, get_all_folders, update_folder, delete_folder, and get_folder_tree.
    • Implemented tests for batch_update_sort_order and get_v3_persona_data.
    • Added extensive tests for persona hot reload functionality, ensuring correct behavior when personas are added, deleted, modified, or moved between folders dynamically.
Activity
  • The pull request introduces two new test files, tests/unit/test_conversation_mgr.py and tests/unit/test_persona_mgr.py, significantly expanding test coverage for core management modules.
  • The author has verified that the new tests pass, as indicated by the provided pytest output in the PR description.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot
Copy link

dosubot bot commented Feb 23, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@dosubot dosubot bot added area:core The bug / feature is about astrbot's core, backend feature:persona The bug / feature is about astrbot AI persona system (system prompt) labels Feb 23, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并留下了一些高层次的反馈:

  • test_conversation_mgr.py 中,有很多测试会手动构造几乎相同的 ConversationV2 mock(每次都设置相同的属性);建议抽取一个共享的 fixture 或辅助工厂方法来构建这些 mocks,这样可以让测试保持 DRY,并且在模型变更时更容易统一更新。
  • 一些并发测试(例如 test_concurrent_add_message_pairtest_concurrent_read_conversationstest_concurrent_create_conversations)会启动数量相对较多、行为非常相似的任务;你可以考虑减少任务数量,或者用参数化的方式覆盖更小的一组用例,在仍然覆盖并发行为的前提下,更好地控制测试运行时间和潜在的不稳定性。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `test_conversation_mgr.py` there are many tests that manually construct almost identical `ConversationV2` mocks (setting the same attributes each time); consider extracting a shared fixture or helper factory to build these mocks to keep the tests DRY and easier to update if the model changes.
- Several of the concurrent tests (e.g. `test_concurrent_add_message_pair`, `test_concurrent_read_conversations`, `test_concurrent_create_conversations`) spin up relatively large numbers of tasks with very similar behavior; you might reduce the task counts or parameterize a smaller set of cases to keep test runtime and potential flakiness under better control while still exercising concurrency.

## Individual Comments

### Comment 1
<location> `tests/unit/test_conversation_mgr.py:22` </location>
<code_context>
+@pytest.fixture
+def mock_db():
</code_context>

<issue_to_address>
**suggestion (testing):** 为 get_conversations 和 get_filtered_conversations 的行为添加明确的测试

该 fixture 暴露了 `get_conversations``get_filtered_conversations`,但目前没有专门的单元测试来验证:

- `ConversationV2``ConversationManager.get_conversations()` 返回的公共结构的精确映射(顺序、字段名、时间戳)。
- `ConversationManager.get_filtered_conversations()` 中的分页、过滤条件,以及 `(items, total)` 的处理。

请添加一些小而确定性的测试,用 1–2 个 `ConversationV2` 实例来 stub 这些 DB 方法,并断言精确的返回结构,以及过滤条件和 `total` 是否被正确传递。

建议实现如下:

```python
    db.get_filtered_conversations = AsyncMock(return_value=([], 0))
    return db


@pytest.mark.asyncio
async def test_get_conversations_maps_conversation_v2_structure(mock_db):
    """Ensure ConversationManager.get_conversations maps ConversationV2 to the expected public structure."""
    # Arrange
    # NOTE: adjust fields here to match ConversationV2 and ConversationManager implementation.
    conv = ConversationV2(
        id="conv1",
        user_id="user1",
        title="First conversation",
        created_at=1234567890,
        updated_at=1234567999,
    )
    mock_db.get_conversations.return_value = [conv]

    manager = ConversationManager(db=mock_db)

    # Act
    result = await manager.get_conversations(user_id="user1")

    # Assert
    # Assert ordering and structure of the public response; update keys/values as needed.
    assert isinstance(result, list)
    assert len(result) == 1

    conv_public = result[0]
    # These keys should match the public API of ConversationManager.get_conversations
    assert conv_public["id"] == "conv1"
    assert conv_public["user_id"] == "user1"
    assert conv_public["title"] == "First conversation"
    assert conv_public["created_at"] == 1234567890
    assert conv_public["updated_at"] == 1234567999

    mock_db.get_conversations.assert_awaited_once_with(user_id="user1")


@pytest.mark.asyncio
async def test_get_filtered_conversations_pagination_filters_and_total(mock_db):
    """Ensure get_filtered_conversations forwards filters/pagination and maps items & total correctly."""
    # Arrange
    conv1 = ConversationV2(
        id="conv1",
        user_id="user1",
        title="First conversation",
        created_at=1,
        updated_at=2,
    )
    conv2 = ConversationV2(
        id="conv2",
        user_id="user1",
        title="Second conversation",
        created_at=3,
        updated_at=4,
    )
    db_items = [conv1, conv2]
    db_total = 10

    filters = {"archived": False, "search": "First"}
    limit = 2
    offset = 0

    mock_db.get_filtered_conversations.return_value = (db_items, db_total)

    manager = ConversationManager(db=mock_db)

    # Act
    items, total = await manager.get_filtered_conversations(
        user_id="user1",
        filters=filters,
        limit=limit,
        offset=offset,
    )

    # Assert: underlying DB called with the same filters/pagination
    mock_db.get_filtered_conversations.assert_awaited_once_with(
        user_id="user1",
        filters=filters,
        limit=limit,
        offset=offset,
    )

    # Assert: total is passed through
    assert total == db_total

    # Assert: items length, ordering, and structure are preserved/mapped correctly
    assert isinstance(items, list)
    assert [item["id"] for item in items] == ["conv1", "conv2"]
    assert items[0]["title"] == "First conversation"
    assert items[1]["title"] == "Second conversation"
    assert items[0]["created_at"] == 1
    assert items[1]["created_at"] == 3

```

这些测试基于以下假设:
1. `ConversationV2``__init__` 与使用到的关键字参数兼容(`id``user_id``title``created_at``updated_at`)。如果实际模型不同,请相应调整构造函数参数。
2. `ConversationManager.get_conversations` 返回 `List[Dict]`,其中每个 dict 都包含 `id``user_id``title``created_at``updated_at` 这些键。如果公共结构使用了不同的字段名,或包含额外字段,请更新断言以匹配真实的映射逻辑。
3. `ConversationManager.get_filtered_conversations` 的签名类似于:
   `async def get_filtered_conversations(self, user_id: str, filters: dict, limit: int, offset: int) -> Tuple[List[dict], int]`
   并调用 `db.get_filtered_conversations(user_id=user_id, filters=filters, limit=limit, offset=offset)`。如果签名或关键字参数名不同,请相应更新调用和 `assert_awaited_once_with`。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据反馈改进之后的 Review。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In test_conversation_mgr.py there are many tests that manually construct almost identical ConversationV2 mocks (setting the same attributes each time); consider extracting a shared fixture or helper factory to build these mocks to keep the tests DRY and easier to update if the model changes.
  • Several of the concurrent tests (e.g. test_concurrent_add_message_pair, test_concurrent_read_conversations, test_concurrent_create_conversations) spin up relatively large numbers of tasks with very similar behavior; you might reduce the task counts or parameterize a smaller set of cases to keep test runtime and potential flakiness under better control while still exercising concurrency.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `test_conversation_mgr.py` there are many tests that manually construct almost identical `ConversationV2` mocks (setting the same attributes each time); consider extracting a shared fixture or helper factory to build these mocks to keep the tests DRY and easier to update if the model changes.
- Several of the concurrent tests (e.g. `test_concurrent_add_message_pair`, `test_concurrent_read_conversations`, `test_concurrent_create_conversations`) spin up relatively large numbers of tasks with very similar behavior; you might reduce the task counts or parameterize a smaller set of cases to keep test runtime and potential flakiness under better control while still exercising concurrency.

## Individual Comments

### Comment 1
<location> `tests/unit/test_conversation_mgr.py:22` </location>
<code_context>
+@pytest.fixture
+def mock_db():
</code_context>

<issue_to_address>
**suggestion (testing):** Add explicit tests for get_conversations and get_filtered_conversations behavior

The fixture exposes `get_conversations` and `get_filtered_conversations`, but there are no focused unit tests that validate:

- The exact mapping from `ConversationV2` to the public structure in `ConversationManager.get_conversations()` (ordering, field names, timestamps).
- Pagination, filters, and `(items, total)` handling in `ConversationManager.get_filtered_conversations()`.

Please add small, deterministic tests that stub these DB methods with 1–2 `ConversationV2` instances and assert the precise returned structure and that filters and `total` are passed through correctly.

Suggested implementation:

```python
    db.get_filtered_conversations = AsyncMock(return_value=([], 0))
    return db


@pytest.mark.asyncio
async def test_get_conversations_maps_conversation_v2_structure(mock_db):
    """Ensure ConversationManager.get_conversations maps ConversationV2 to the expected public structure."""
    # Arrange
    # NOTE: adjust fields here to match ConversationV2 and ConversationManager implementation.
    conv = ConversationV2(
        id="conv1",
        user_id="user1",
        title="First conversation",
        created_at=1234567890,
        updated_at=1234567999,
    )
    mock_db.get_conversations.return_value = [conv]

    manager = ConversationManager(db=mock_db)

    # Act
    result = await manager.get_conversations(user_id="user1")

    # Assert
    # Assert ordering and structure of the public response; update keys/values as needed.
    assert isinstance(result, list)
    assert len(result) == 1

    conv_public = result[0]
    # These keys should match the public API of ConversationManager.get_conversations
    assert conv_public["id"] == "conv1"
    assert conv_public["user_id"] == "user1"
    assert conv_public["title"] == "First conversation"
    assert conv_public["created_at"] == 1234567890
    assert conv_public["updated_at"] == 1234567999

    mock_db.get_conversations.assert_awaited_once_with(user_id="user1")


@pytest.mark.asyncio
async def test_get_filtered_conversations_pagination_filters_and_total(mock_db):
    """Ensure get_filtered_conversations forwards filters/pagination and maps items & total correctly."""
    # Arrange
    conv1 = ConversationV2(
        id="conv1",
        user_id="user1",
        title="First conversation",
        created_at=1,
        updated_at=2,
    )
    conv2 = ConversationV2(
        id="conv2",
        user_id="user1",
        title="Second conversation",
        created_at=3,
        updated_at=4,
    )
    db_items = [conv1, conv2]
    db_total = 10

    filters = {"archived": False, "search": "First"}
    limit = 2
    offset = 0

    mock_db.get_filtered_conversations.return_value = (db_items, db_total)

    manager = ConversationManager(db=mock_db)

    # Act
    items, total = await manager.get_filtered_conversations(
        user_id="user1",
        filters=filters,
        limit=limit,
        offset=offset,
    )

    # Assert: underlying DB called with the same filters/pagination
    mock_db.get_filtered_conversations.assert_awaited_once_with(
        user_id="user1",
        filters=filters,
        limit=limit,
        offset=offset,
    )

    # Assert: total is passed through
    assert total == db_total

    # Assert: items length, ordering, and structure are preserved/mapped correctly
    assert isinstance(items, list)
    assert [item["id"] for item in items] == ["conv1", "conv2"]
    assert items[0]["title"] == "First conversation"
    assert items[1]["title"] == "Second conversation"
    assert items[0]["created_at"] == 1
    assert items[1]["created_at"] == 3

```

These tests assume:
1. `ConversationV2` has an `__init__` compatible with the keyword arguments used (`id`, `user_id`, `title`, `created_at`, `updated_at`). If the actual model differs, adjust the constructor arguments accordingly.
2. `ConversationManager.get_conversations` returns a `List[Dict]` where each dict has keys `id`, `user_id`, `title`, `created_at`, and `updated_at`. If the public structure uses different field names or includes additional fields, update the assertions to match the real mapping logic.
3. `ConversationManager.get_filtered_conversations` has a signature like:
   `async def get_filtered_conversations(self, user_id: str, filters: dict, limit: int, offset: int) -> Tuple[List[dict], int]`
   and calls `db.get_filtered_conversations(user_id=user_id, filters=filters, limit=limit, offset=offset)`. If the signature or keyword names differ, update the call and `assert_awaited_once_with` accordingly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

db.delete_conversations_by_user_id = AsyncMock()
db.update_conversation = AsyncMock()
db.get_conversations = AsyncMock(return_value=[])
db.get_filtered_conversations = AsyncMock(return_value=([], 0))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): 为 get_conversations 和 get_filtered_conversations 的行为添加明确的测试

该 fixture 暴露了 get_conversationsget_filtered_conversations,但目前没有专门的单元测试来验证:

  • ConversationV2ConversationManager.get_conversations() 返回的公共结构的精确映射(顺序、字段名、时间戳)。
  • ConversationManager.get_filtered_conversations() 中的分页、过滤条件,以及 (items, total) 的处理。

请添加一些小而确定性的测试,用 1–2 个 ConversationV2 实例来 stub 这些 DB 方法,并断言精确的返回结构,以及过滤条件和 total 是否被正确传递。

建议实现如下:

    db.get_filtered_conversations = AsyncMock(return_value=([], 0))
    return db


@pytest.mark.asyncio
async def test_get_conversations_maps_conversation_v2_structure(mock_db):
    """Ensure ConversationManager.get_conversations maps ConversationV2 to the expected public structure."""
    # Arrange
    # NOTE: adjust fields here to match ConversationV2 and ConversationManager implementation.
    conv = ConversationV2(
        id="conv1",
        user_id="user1",
        title="First conversation",
        created_at=1234567890,
        updated_at=1234567999,
    )
    mock_db.get_conversations.return_value = [conv]

    manager = ConversationManager(db=mock_db)

    # Act
    result = await manager.get_conversations(user_id="user1")

    # Assert
    # Assert ordering and structure of the public response; update keys/values as needed.
    assert isinstance(result, list)
    assert len(result) == 1

    conv_public = result[0]
    # These keys should match the public API of ConversationManager.get_conversations
    assert conv_public["id"] == "conv1"
    assert conv_public["user_id"] == "user1"
    assert conv_public["title"] == "First conversation"
    assert conv_public["created_at"] == 1234567890
    assert conv_public["updated_at"] == 1234567999

    mock_db.get_conversations.assert_awaited_once_with(user_id="user1")


@pytest.mark.asyncio
async def test_get_filtered_conversations_pagination_filters_and_total(mock_db):
    """Ensure get_filtered_conversations forwards filters/pagination and maps items & total correctly."""
    # Arrange
    conv1 = ConversationV2(
        id="conv1",
        user_id="user1",
        title="First conversation",
        created_at=1,
        updated_at=2,
    )
    conv2 = ConversationV2(
        id="conv2",
        user_id="user1",
        title="Second conversation",
        created_at=3,
        updated_at=4,
    )
    db_items = [conv1, conv2]
    db_total = 10

    filters = {"archived": False, "search": "First"}
    limit = 2
    offset = 0

    mock_db.get_filtered_conversations.return_value = (db_items, db_total)

    manager = ConversationManager(db=mock_db)

    # Act
    items, total = await manager.get_filtered_conversations(
        user_id="user1",
        filters=filters,
        limit=limit,
        offset=offset,
    )

    # Assert: underlying DB called with the same filters/pagination
    mock_db.get_filtered_conversations.assert_awaited_once_with(
        user_id="user1",
        filters=filters,
        limit=limit,
        offset=offset,
    )

    # Assert: total is passed through
    assert total == db_total

    # Assert: items length, ordering, and structure are preserved/mapped correctly
    assert isinstance(items, list)
    assert [item["id"] for item in items] == ["conv1", "conv2"]
    assert items[0]["title"] == "First conversation"
    assert items[1]["title"] == "Second conversation"
    assert items[0]["created_at"] == 1
    assert items[1]["created_at"] == 3

这些测试基于以下假设:

  1. ConversationV2__init__ 与使用到的关键字参数兼容(iduser_idtitlecreated_atupdated_at)。如果实际模型不同,请相应调整构造函数参数。
  2. ConversationManager.get_conversations 返回 List[Dict],其中每个 dict 都包含 iduser_idtitlecreated_atupdated_at 这些键。如果公共结构使用了不同的字段名,或包含额外字段,请更新断言以匹配真实的映射逻辑。
  3. ConversationManager.get_filtered_conversations 的签名类似于:
    async def get_filtered_conversations(self, user_id: str, filters: dict, limit: int, offset: int) -> Tuple[List[dict], int]
    并调用 db.get_filtered_conversations(user_id=user_id, filters=filters, limit=limit, offset=offset)。如果签名或关键字参数名不同,请相应更新调用和 assert_awaited_once_with
Original comment in English

suggestion (testing): Add explicit tests for get_conversations and get_filtered_conversations behavior

The fixture exposes get_conversations and get_filtered_conversations, but there are no focused unit tests that validate:

  • The exact mapping from ConversationV2 to the public structure in ConversationManager.get_conversations() (ordering, field names, timestamps).
  • Pagination, filters, and (items, total) handling in ConversationManager.get_filtered_conversations().

Please add small, deterministic tests that stub these DB methods with 1–2 ConversationV2 instances and assert the precise returned structure and that filters and total are passed through correctly.

Suggested implementation:

    db.get_filtered_conversations = AsyncMock(return_value=([], 0))
    return db


@pytest.mark.asyncio
async def test_get_conversations_maps_conversation_v2_structure(mock_db):
    """Ensure ConversationManager.get_conversations maps ConversationV2 to the expected public structure."""
    # Arrange
    # NOTE: adjust fields here to match ConversationV2 and ConversationManager implementation.
    conv = ConversationV2(
        id="conv1",
        user_id="user1",
        title="First conversation",
        created_at=1234567890,
        updated_at=1234567999,
    )
    mock_db.get_conversations.return_value = [conv]

    manager = ConversationManager(db=mock_db)

    # Act
    result = await manager.get_conversations(user_id="user1")

    # Assert
    # Assert ordering and structure of the public response; update keys/values as needed.
    assert isinstance(result, list)
    assert len(result) == 1

    conv_public = result[0]
    # These keys should match the public API of ConversationManager.get_conversations
    assert conv_public["id"] == "conv1"
    assert conv_public["user_id"] == "user1"
    assert conv_public["title"] == "First conversation"
    assert conv_public["created_at"] == 1234567890
    assert conv_public["updated_at"] == 1234567999

    mock_db.get_conversations.assert_awaited_once_with(user_id="user1")


@pytest.mark.asyncio
async def test_get_filtered_conversations_pagination_filters_and_total(mock_db):
    """Ensure get_filtered_conversations forwards filters/pagination and maps items & total correctly."""
    # Arrange
    conv1 = ConversationV2(
        id="conv1",
        user_id="user1",
        title="First conversation",
        created_at=1,
        updated_at=2,
    )
    conv2 = ConversationV2(
        id="conv2",
        user_id="user1",
        title="Second conversation",
        created_at=3,
        updated_at=4,
    )
    db_items = [conv1, conv2]
    db_total = 10

    filters = {"archived": False, "search": "First"}
    limit = 2
    offset = 0

    mock_db.get_filtered_conversations.return_value = (db_items, db_total)

    manager = ConversationManager(db=mock_db)

    # Act
    items, total = await manager.get_filtered_conversations(
        user_id="user1",
        filters=filters,
        limit=limit,
        offset=offset,
    )

    # Assert: underlying DB called with the same filters/pagination
    mock_db.get_filtered_conversations.assert_awaited_once_with(
        user_id="user1",
        filters=filters,
        limit=limit,
        offset=offset,
    )

    # Assert: total is passed through
    assert total == db_total

    # Assert: items length, ordering, and structure are preserved/mapped correctly
    assert isinstance(items, list)
    assert [item["id"] for item in items] == ["conv1", "conv2"]
    assert items[0]["title"] == "First conversation"
    assert items[1]["title"] == "Second conversation"
    assert items[0]["created_at"] == 1
    assert items[1]["created_at"] == 3

These tests assume:

  1. ConversationV2 has an __init__ compatible with the keyword arguments used (id, user_id, title, created_at, updated_at). If the actual model differs, adjust the constructor arguments accordingly.
  2. ConversationManager.get_conversations returns a List[Dict] where each dict has keys id, user_id, title, created_at, and updated_at. If the public structure uses different field names or includes additional fields, update the assertions to match the real mapping logic.
  3. ConversationManager.get_filtered_conversations has a signature like:
    async def get_filtered_conversations(self, user_id: str, filters: dict, limit: int, offset: int) -> Tuple[List[dict], int]
    and calls db.get_filtered_conversations(user_id=user_id, filters=filters, limit=limit, offset=offset). If the signature or keyword names differ, update the call and assert_awaited_once_with accordingly.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces comprehensive unit tests for ConversationManager and PersonaManager, significantly enhancing test coverage for conversation and persona management functionalities. The tests cover initialization, CRUD operations, session management, folder management, and concurrent access scenarios, which is a great improvement for the reliability and robustness of these modules. The addition of hot reload tests for persona management is also a valuable inclusion.

Comment on lines +109 to +110
with patch.object(persona_manager, "get_v3_persona_data"):
await persona_manager.initialize()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

Suggested change
with patch.object(persona_manager, "get_v3_persona_data"):
await persona_manager.initialize()
with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +127 to +128
with pytest.raises(RuntimeError, match="v3 conversion failed"):
await persona_manager.initialize()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

Suggested change
with pytest.raises(RuntimeError, match="v3 conversion failed"):
await persona_manager.initialize()
with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +199 to +200
with patch.object(persona_manager, "get_v3_persona_data"):
result = await persona_manager.create_persona(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

Suggested change
with patch.object(persona_manager, "get_v3_persona_data"):
result = await persona_manager.create_persona(
with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +243 to +244
result = await persona_manager.update_persona(
persona_id="test-persona",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

        with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +273 to +274
await persona_manager.delete_persona("test-persona")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

        with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +746 to +747
with patch.object(persona_manager, "get_v3_persona_data"):
await persona_manager.initialize()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

        with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +781 to +782
with patch.object(persona_manager, "get_v3_persona_data"):
await persona_manager.initialize()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

        with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +810 to +811

with patch.object(persona_manager, "get_v3_persona_data"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

        with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +896 to +897
with patch.object(persona_manager, "get_v3_persona_data"):
for i in range(5):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

        with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Comment on lines +928 to +929
with patch.object(persona_manager, "get_v3_persona_data"):
await persona_manager.initialize()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The patch.object context manager should ideally be used to mock the get_v3_persona_data method for the duration of the test, ensuring a clean state.

        with patch.object(persona_manager, "get_v3_persona_data") as mock_get_v3_persona_data:

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Expands unit test coverage for AstrBot’s conversation and persona management layers to improve confidence in conversation lifecycle behavior and persona/folder operations.

Changes:

  • Adds comprehensive unit tests for ConversationManager covering creation, switching, deletion, updates, history mutations, and concurrent access patterns.
  • Adds extensive unit tests for PersonaManager covering persona CRUD, folder CRUD/tree building, sort-order batch updates, and v3 persona data generation/hot-reload scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
tests/unit/test_conversation_mgr.py Adds unit tests for ConversationManager behaviors including session selection storage and concurrent operations.
tests/unit/test_persona_mgr.py Adds unit tests for PersonaManager behaviors including persona/folder management and v3 persona conversion logic.

Comment on lines +1 to +5
"""Tests for PersonaManager."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description’s verification output says pytest tests/unit/test_persona_mgr.py -v collected 15 tests, but this file currently defines many more test functions (over 40 test_... methods). Please re-run the verification step (or update the pasted output) so it reflects the current version of the test file and ensures everything is being collected/executed as expected.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +10
"""Tests for ConversationManager."""

import asyncio
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from astrbot.core.conversation_mgr import ConversationManager
from astrbot.core.db.po import ConversationV2

Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description’s verification output says pytest tests/unit/test_conversation_mgr.py -v collected 18 tests, but this file currently defines more test functions than that. Please refresh the verification output (or explain why some tests are not collected) to ensure CI/local runs are actually executing the full set of tests.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend feature:persona The bug / feature is about astrbot AI persona system (system prompt) size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants