403Webshell
Server IP : 3.147.158.171  /  Your IP : 216.73.216.216
Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5
System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.21
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /tsai/www/html/api.turmansolutions.ai/tests/unit/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/www/html/api.turmansolutions.ai/tests/unit/test_character_studio_tools.py
"""Unit tests for the Character Studio agent tools.

These drive the async tools synchronously via asyncio.run (no pytest-asyncio in
this suite) and mock the HTTP helpers / article service so nothing touches Drupal,
WordPress, or the network. The key guarantee under test: the test-article tool
generates an *unpublished draft*.
"""

import asyncio
from unittest.mock import AsyncMock, patch

from app.appTypes import (
    ArticleCreationResult,
    ArticleResponse,
    LLModel,
    LLMProvider,
    SiteConfig,
)
from app.services.character_studio_tools import CharacterStudioToolset, _slugify


def make_site() -> SiteConfig:
    return SiteConfig(
        cb_host="chat.foo.test", api_host="http://api.foo.test", wp_host="http://foo.test",
        wp_dir="foo", drupal_host="http://drupal.test", drupal_api="/jsonapi",
        slug="foo", drupal_uid="user-uuid", drupal_target_id=5,
    )


def make_char(**over) -> dict:
    base = {
        "slug": "nova", "name": "Nova", "email": "nova@example.com",
        "description": "You are Nova.", "enabled": True, "date": "2026-07-10",
        "researchCurrentEvents": False,
    }
    base.update(over)
    return base


class FakeResp:
    def __init__(self, status=200, payload=None, text=""):
        self.status_code = status
        self._payload = payload or {}
        self.text = text

    def json(self):
        return self._payload


def test_slugify():
    assert _slugify("Nova Sparks!") == "nova-sparks"
    assert _slugify("  Multi   Word  ") == "multi-word"


def test_generate_test_article_uses_draft():
    ts = CharacterStudioToolset(make_site())
    ts._fetch_character = AsyncMock(return_value=make_char())

    fake_result = ArticleCreationResult(
        article=ArticleResponse(title="The Title", body="Body markdown", category="News", tags=[]),
        post_id=42,
        actual_model=LLModel(provider=LLMProvider.CLAUDE, name="claude-sonnet-4-6"),
        permalink="http://foo.test/?p=42",
    )

    with patch(
        "app.services.character_studio_tools.article_creation_service.create_article",
        new=AsyncMock(return_value=fake_result),
    ) as mock_create:
        out = asyncio.run(ts.generate_test_article("nova"))

    kwargs = mock_create.call_args.kwargs
    assert kwargs["post_status"] == "draft"
    assert kwargs["generate_featured_image"] is False
    assert kwargs["generate_secondary_image"] is False
    assert kwargs["enable_fact_checking"] is False
    assert kwargs["enable_corrections"] is False

    assert len(ts.frames) == 1
    frame = ts.frames[0]
    assert frame["type"] == "article_preview"
    assert frame["title"] == "The Title"
    assert frame["postId"] == 42
    assert "DRAFT" in out


def test_generate_test_article_missing_character():
    ts = CharacterStudioToolset(make_site())
    ts._fetch_character = AsyncMock(return_value=None)
    out = asyncio.run(ts.generate_test_article("ghost"))
    assert "No character found" in out
    assert ts.frames == []


def test_create_character_emits_frame_and_registers_author():
    ts = CharacterStudioToolset(make_site())
    ts._send = AsyncMock(return_value=FakeResp(200, {"data": {"data": {"id": "node-uuid-1"}}}))
    ts._fetch_character = AsyncMock(return_value=make_char())

    out = asyncio.run(ts.create_character(name="Nova", description="You are Nova, a science writer."))

    # POST /cms/character/ then POST /user/register/
    called_paths = [c.args[1] for c in ts._send.call_args_list]
    assert "/cms/character/" in called_paths
    assert "/user/register/" in called_paths

    assert any(f["type"] == "character_updated" for f in ts.frames)
    assert "Created character 'Nova'" in out


def test_create_character_requires_search_query_when_research():
    ts = CharacterStudioToolset(make_site())
    ts._send = AsyncMock()
    out = asyncio.run(ts.create_character(name="Nova", description="x", research=True))
    assert "search_query" in out
    ts._send.assert_not_called()


def test_update_character_appends_description():
    ts = CharacterStudioToolset(make_site())
    ts._fetch_character = AsyncMock(return_value=make_char())
    ts._send = AsyncMock(return_value=FakeResp(200, {"data": make_char(description="merged")}))

    asyncio.run(ts.update_character(slug="nova", append_description="Cite primary sources."))

    body = ts._send.call_args.args[2]  # (method, path, body)
    sent_description = body["character"]["description"]
    assert sent_description.startswith("You are Nova.")
    assert "Cite primary sources." in sent_description
    assert any(f["type"] == "character_updated" for f in ts.frames)

Youez - 2016 - github.com/yon3zu
LinuXploit