mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-30 14:52:36 +00:00
Compare commits
3 Commits
devin/1752
...
devin/1752
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f36d48d57e | ||
|
|
3eea890409 | ||
|
|
a670b2b35e |
@@ -311,20 +311,47 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="AWS Bedrock">
|
||||
Amazon Bedrock supports two authentication methods:
|
||||
|
||||
**Method 1: IAM Role Authentication (Recommended for Production)**
|
||||
```toml Code
|
||||
AWS_ACCESS_KEY_ID=<your-access-key>
|
||||
AWS_SECRET_ACCESS_KEY=<your-secret-key>
|
||||
AWS_DEFAULT_REGION=<your-region>
|
||||
```
|
||||
|
||||
**Method 2: API Key Authentication (Recommended for Development)**
|
||||
```toml Code
|
||||
AWS_BEARER_TOKEN_BEDROCK=<your-api-key>
|
||||
AWS_DEFAULT_REGION=<your-region>
|
||||
```
|
||||
|
||||
Example usage in your CrewAI project:
|
||||
```python Code
|
||||
# Method 1: IAM Role Authentication (uses AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY)
|
||||
# Set environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION
|
||||
llm = LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
)
|
||||
|
||||
# Method 2: API Key Authentication (uses AWS_BEARER_TOKEN_BEDROCK)
|
||||
# Set environment variables: AWS_BEARER_TOKEN_BEDROCK, AWS_DEFAULT_REGION
|
||||
llm = LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
)
|
||||
```
|
||||
|
||||
Before using Amazon Bedrock, make sure you have boto3 installed in your environment
|
||||
**Requirements:**
|
||||
- Before using Amazon Bedrock, make sure you have boto3 v1.393+ installed in your environment
|
||||
- For API key authentication, you can generate a 30-day API key from the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/)
|
||||
- For production applications, use IAM roles or temporary credentials instead of long-term API keys
|
||||
|
||||
**Security Best Practices:**
|
||||
- API keys expire after 30 days and should be rotated regularly
|
||||
- Use IAM roles for production environments for better security
|
||||
- Store API keys securely and never commit them to version control
|
||||
- Monitor API usage and set up alerts for unusual activity
|
||||
- Consider using temporary credentials for enhanced security
|
||||
|
||||
[Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/models-regions.html) is a managed service that provides access to multiple foundation models from top AI companies through a unified API, enabling secure and responsible AI application development.
|
||||
|
||||
|
||||
@@ -309,20 +309,47 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="AWS Bedrock">
|
||||
O Amazon Bedrock suporta dois métodos de autenticação:
|
||||
|
||||
**Método 1: Autenticação por Função IAM (Recomendado para Produção)**
|
||||
```toml Code
|
||||
AWS_ACCESS_KEY_ID=<your-access-key>
|
||||
AWS_SECRET_ACCESS_KEY=<your-secret-key>
|
||||
AWS_DEFAULT_REGION=<your-region>
|
||||
```
|
||||
|
||||
**Método 2: Autenticação por Chave de API (Recomendado para Desenvolvimento)**
|
||||
```toml Code
|
||||
AWS_BEARER_TOKEN_BEDROCK=<your-api-key>
|
||||
AWS_DEFAULT_REGION=<your-region>
|
||||
```
|
||||
|
||||
Exemplo de uso em seu projeto CrewAI:
|
||||
```python Code
|
||||
# Método 1: Autenticação por Função IAM (usa AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY)
|
||||
# Configure as variáveis de ambiente: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION
|
||||
llm = LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
)
|
||||
|
||||
# Método 2: Autenticação por Chave de API (usa AWS_BEARER_TOKEN_BEDROCK)
|
||||
# Configure as variáveis de ambiente: AWS_BEARER_TOKEN_BEDROCK, AWS_DEFAULT_REGION
|
||||
llm = LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
)
|
||||
```
|
||||
|
||||
Antes de usar o Amazon Bedrock, certifique-se de ter o boto3 instalado em seu ambiente
|
||||
**Requisitos:**
|
||||
- Antes de usar o Amazon Bedrock, certifique-se de ter o boto3 v1.393+ instalado em seu ambiente
|
||||
- Para autenticação por chave de API, você pode gerar uma chave de 30 dias no [console do Amazon Bedrock](https://console.aws.amazon.com/bedrock/)
|
||||
- Para aplicações de produção, use funções IAM ou credenciais temporárias em vez de chaves de API de longo prazo
|
||||
|
||||
**Melhores Práticas de Segurança:**
|
||||
- Chaves de API expiram após 30 dias e devem ser rotacionadas regularmente
|
||||
- Use funções IAM para ambientes de produção para melhor segurança
|
||||
- Armazene chaves de API com segurança e nunca as confirme no controle de versão
|
||||
- Monitore o uso da API e configure alertas para atividades incomuns
|
||||
- Considere usar credenciais temporárias para segurança aprimorada
|
||||
|
||||
[Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/models-regions.html) é um serviço gerenciado que fornece acesso a múltiplos modelos fundamentais dos principais provedores de IA através de uma API unificada, permitindo o desenvolvimento seguro e responsável de aplicações de IA.
|
||||
|
||||
@@ -881,4 +908,4 @@ Saiba como obter o máximo da configuração do seu LLM:
|
||||
llm = LLM(model="openai/gpt-4o") # 128K tokens
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Tabs>
|
||||
|
||||
@@ -47,7 +47,7 @@ Documentation = "https://docs.crewai.com"
|
||||
Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = ["crewai-tools~=0.51.0"]
|
||||
tools = ["crewai-tools~=0.49.0"]
|
||||
embeddings = [
|
||||
"tiktoken~=0.8.0"
|
||||
]
|
||||
|
||||
@@ -54,7 +54,7 @@ def _track_install_async():
|
||||
|
||||
_track_install_async()
|
||||
|
||||
__version__ = "0.141.0"
|
||||
__version__ = "0.140.0"
|
||||
__all__ = [
|
||||
"Agent",
|
||||
"Crew",
|
||||
|
||||
@@ -5,4 +5,4 @@ AUTH0_AUDIENCE = "https://crewai.us.auth0.com/api/v2/"
|
||||
|
||||
WORKOS_DOMAIN = "login.crewai.com"
|
||||
WORKOS_CLI_CONNECT_APP_ID = "client_01JYT06R59SP0NXYGD994NFXXX"
|
||||
WORKOS_ENVIRONMENT_ID = "client_01JNJQWBJ4SPFN3SWJM5T7BDG8"
|
||||
WORKOS_ENVIRONMENT_ID = "client_01JNJQWB4HG8T5980R5VHP057C"
|
||||
|
||||
@@ -30,9 +30,6 @@ def validate_jwt_token(
|
||||
jwk_client = PyJWKClient(jwks_url)
|
||||
signing_key = jwk_client.get_signing_key_from_jwt(jwt_token)
|
||||
|
||||
_unverified_decoded_token = jwt.decode(
|
||||
jwt_token, options={"verify_signature": False}
|
||||
)
|
||||
decoded_token = jwt.decode(
|
||||
jwt_token,
|
||||
signing_key.key,
|
||||
@@ -52,15 +49,9 @@ def validate_jwt_token(
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise Exception("Token has expired.")
|
||||
except jwt.InvalidAudienceError:
|
||||
actual_audience = _unverified_decoded_token.get("aud", "[no audience found]")
|
||||
raise Exception(
|
||||
f"Invalid token audience. Got: '{actual_audience}'. Expected: '{audience}'"
|
||||
)
|
||||
raise Exception(f"Invalid token audience. Expected: '{audience}'")
|
||||
except jwt.InvalidIssuerError:
|
||||
actual_issuer = _unverified_decoded_token.get("iss", "[no issuer found]")
|
||||
raise Exception(
|
||||
f"Invalid token issuer. Got: '{actual_issuer}'. Expected: '{issuer}'"
|
||||
)
|
||||
raise Exception(f"Invalid token issuer. Expected: '{issuer}'")
|
||||
except jwt.MissingRequiredClaimError as e:
|
||||
raise Exception(f"Token is missing required claims: {str(e)}")
|
||||
except jwt.exceptions.PyJWKClientError as e:
|
||||
|
||||
@@ -62,6 +62,10 @@ ENV_VARS = {
|
||||
"prompt": "Enter your AWS Region Name (press Enter to skip)",
|
||||
"key_name": "AWS_REGION_NAME",
|
||||
},
|
||||
{
|
||||
"prompt": "Enter your AWS Bedrock API Key (30-day key from AWS console, press Enter to skip)",
|
||||
"key_name": "AWS_BEARER_TOKEN_BEDROCK",
|
||||
},
|
||||
],
|
||||
"azure": [
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.141.0,<1.0.0"
|
||||
"crewai[tools]>=0.140.0,<1.0.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.141.0,<1.0.0",
|
||||
"crewai[tools]>=0.140.0,<1.0.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.141.0"
|
||||
"crewai[tools]>=0.140.0"
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
|
||||
@@ -120,7 +120,6 @@ class EventListener(BaseEventListener):
|
||||
"completed",
|
||||
final_string_output,
|
||||
)
|
||||
self.formatter.stop_live()
|
||||
|
||||
@crewai_event_bus.on(CrewKickoffFailedEvent)
|
||||
def on_crew_failed(source, event: CrewKickoffFailedEvent):
|
||||
@@ -263,7 +262,6 @@ class EventListener(BaseEventListener):
|
||||
self.formatter.update_flow_status(
|
||||
self.formatter.current_flow_tree, event.flow_name, source.flow_id
|
||||
)
|
||||
self.formatter.stop_live()
|
||||
|
||||
@crewai_event_bus.on(MethodExecutionStartedEvent)
|
||||
def on_method_execution_started(source, event: MethodExecutionStartedEvent):
|
||||
|
||||
@@ -1753,9 +1753,3 @@ class ConsoleFormatter:
|
||||
Attempts=f"{retry_count + 1}",
|
||||
)
|
||||
self.print_panel(content, "🛡️ Guardrail Failed", "red")
|
||||
|
||||
def stop_live(self) -> None:
|
||||
"""Stop and clear any active Live session to restore normal terminal output."""
|
||||
if self._live:
|
||||
self._live.stop()
|
||||
self._live = None
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestValidateToken(unittest.TestCase):
|
||||
audience="app_id_xxxx",
|
||||
)
|
||||
|
||||
mock_jwt.decode.assert_called_with(
|
||||
mock_jwt.decode.assert_called_once_with(
|
||||
"aaaaa.bbbbbb.cccccc",
|
||||
"mock_signing_key",
|
||||
algorithms=["RS256"],
|
||||
|
||||
177
tests/test_bedrock_authentication.py
Normal file
177
tests/test_bedrock_authentication.py
Normal file
@@ -0,0 +1,177 @@
|
||||
import os
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from crewai import LLM
|
||||
|
||||
|
||||
class TestBedrockAuthentication:
|
||||
"""Test AWS Bedrock authentication methods."""
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_ACCESS_KEY_ID': 'test-key-id',
|
||||
'AWS_SECRET_ACCESS_KEY': 'test-secret-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_iam_authentication(self, mock_completion):
|
||||
"""Test Bedrock with IAM role authentication."""
|
||||
mock_completion.return_value = MagicMock()
|
||||
mock_completion.return_value.choices = [MagicMock()]
|
||||
mock_completion.return_value.choices[0].message.content = "Test response"
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
result = llm.call("test message")
|
||||
|
||||
mock_completion.assert_called_once()
|
||||
assert result == "Test response"
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_BEARER_TOKEN_BEDROCK': 'test-api-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_api_key_authentication(self, mock_completion):
|
||||
"""Test Bedrock with API key authentication."""
|
||||
mock_completion.return_value = MagicMock()
|
||||
mock_completion.return_value.choices = [MagicMock()]
|
||||
mock_completion.return_value.choices[0].message.content = "Test response"
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
result = llm.call("test message")
|
||||
|
||||
mock_completion.assert_called_once()
|
||||
assert result == "Test response"
|
||||
|
||||
def test_bedrock_missing_credentials(self):
|
||||
"""Test Bedrock fails gracefully with missing credentials."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
assert llm.model == "bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_BEARER_TOKEN_BEDROCK': 'test-api-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_api_key_with_streaming(self, mock_completion):
|
||||
"""Test Bedrock API key authentication with streaming."""
|
||||
mock_completion.return_value = iter([
|
||||
MagicMock(choices=[MagicMock(delta=MagicMock(content="Test"))]),
|
||||
MagicMock(choices=[MagicMock(delta=MagicMock(content=" response"))])
|
||||
])
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
result = list(llm.stream("test message"))
|
||||
|
||||
mock_completion.assert_called_once()
|
||||
assert len(result) == 2
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_ACCESS_KEY_ID': 'test-key-id',
|
||||
'AWS_SECRET_ACCESS_KEY': 'test-secret-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_iam_with_custom_parameters(self, mock_completion):
|
||||
"""Test Bedrock IAM authentication with custom parameters."""
|
||||
mock_completion.return_value = MagicMock()
|
||||
mock_completion.return_value.choices = [MagicMock()]
|
||||
mock_completion.return_value.choices[0].message.content = "Test response"
|
||||
|
||||
llm = LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
temperature=0.7,
|
||||
max_tokens=100
|
||||
)
|
||||
result = llm.call("test message")
|
||||
|
||||
mock_completion.assert_called_once()
|
||||
call_args = mock_completion.call_args
|
||||
assert call_args[1]['temperature'] == 0.7
|
||||
assert call_args[1]['max_tokens'] == 100
|
||||
assert result == "Test response"
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_BEARER_TOKEN_BEDROCK': 'test-api-key',
|
||||
'AWS_DEFAULT_REGION': 'us-west-2'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_api_key_different_region(self, mock_completion):
|
||||
"""Test Bedrock API key authentication with different region."""
|
||||
mock_completion.return_value = MagicMock()
|
||||
mock_completion.return_value.choices = [MagicMock()]
|
||||
mock_completion.return_value.choices[0].message.content = "Test response"
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
result = llm.call("test message")
|
||||
|
||||
mock_completion.assert_called_once()
|
||||
assert result == "Test response"
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_BEARER_TOKEN_BEDROCK': 'test-api-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_timeout_handling(self, mock_completion):
|
||||
"""Test Bedrock API timeout handling."""
|
||||
mock_completion.side_effect = TimeoutError("Request timed out")
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
with pytest.raises(TimeoutError, match="Request timed out"):
|
||||
llm.call("test message")
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_BEARER_TOKEN_BEDROCK': 'test-api-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_rate_limit_handling(self, mock_completion):
|
||||
"""Test Bedrock API rate limit handling."""
|
||||
mock_completion.side_effect = Exception("Rate limit exceeded")
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
with pytest.raises(Exception, match="Rate limit exceeded"):
|
||||
llm.call("test message")
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_BEARER_TOKEN_BEDROCK': 'invalid-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_invalid_api_key(self, mock_completion):
|
||||
"""Test Bedrock with invalid API key."""
|
||||
mock_completion.side_effect = Exception("Invalid API key")
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
with pytest.raises(Exception, match="Invalid API key"):
|
||||
llm.call("test message")
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_ACCESS_KEY_ID': 'test-key-id',
|
||||
'AWS_SECRET_ACCESS_KEY': 'test-secret-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_connection_error(self, mock_completion):
|
||||
"""Test Bedrock with connection error."""
|
||||
mock_completion.side_effect = ConnectionError("Connection failed")
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
with pytest.raises(ConnectionError, match="Connection failed"):
|
||||
llm.call("test message")
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
'AWS_BEARER_TOKEN_BEDROCK': 'test-api-key',
|
||||
'AWS_DEFAULT_REGION': 'us-east-1'
|
||||
})
|
||||
@patch('litellm.completion')
|
||||
def test_bedrock_api_key_with_retry_scenario(self, mock_completion):
|
||||
"""Test Bedrock API key authentication with retry scenario."""
|
||||
mock_completion.side_effect = [
|
||||
Exception("Temporary error"),
|
||||
MagicMock(choices=[MagicMock(message=MagicMock(content="Success after retry"))])
|
||||
]
|
||||
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
with pytest.raises(Exception, match="Temporary error"):
|
||||
llm.call("test message")
|
||||
@@ -1,54 +0,0 @@
|
||||
from unittest.mock import patch
|
||||
from rich.tree import Tree
|
||||
from crewai.utilities.events.utils.console_formatter import ConsoleFormatter
|
||||
from crewai.utilities.events.event_listener import EventListener
|
||||
|
||||
|
||||
class TestRichLiveCleanup:
|
||||
"""Test that Rich Live sessions are properly cleaned up after CrewAI operations."""
|
||||
|
||||
def test_logging_works_after_tree_rendering(self):
|
||||
"""Test that logging output appears after tree rendering with proper cleanup."""
|
||||
formatter = ConsoleFormatter()
|
||||
|
||||
tree = Tree("Test Flow")
|
||||
formatter.print(tree)
|
||||
|
||||
assert formatter._live is not None
|
||||
|
||||
formatter.stop_live()
|
||||
|
||||
assert formatter._live is None
|
||||
|
||||
with patch.object(formatter.console, 'print') as mock_print:
|
||||
formatter.print("This should appear immediately")
|
||||
mock_print.assert_called_once_with("This should appear immediately")
|
||||
|
||||
def test_event_listener_cleanup_integration(self):
|
||||
"""Test that EventListener properly cleans up Live sessions."""
|
||||
event_listener = EventListener()
|
||||
formatter = event_listener.formatter
|
||||
|
||||
tree = Tree("Test Crew")
|
||||
formatter.print(tree)
|
||||
assert formatter._live is not None
|
||||
|
||||
formatter.stop_live()
|
||||
assert formatter._live is None
|
||||
|
||||
def test_stop_live_restores_normal_output(self):
|
||||
"""Test that stop_live properly restores normal console output behavior."""
|
||||
formatter = ConsoleFormatter()
|
||||
|
||||
tree = Tree("Test Tree")
|
||||
formatter.print(tree)
|
||||
|
||||
assert formatter._live is not None
|
||||
|
||||
formatter.stop_live()
|
||||
|
||||
assert formatter._live is None
|
||||
|
||||
with patch.object(formatter.console, 'print') as mock_print:
|
||||
formatter.print("Normal output")
|
||||
mock_print.assert_called_once_with("Normal output")
|
||||
@@ -114,38 +114,3 @@ class TestConsoleFormatterPauseResume:
|
||||
|
||||
assert hasattr(formatter, '_live_paused')
|
||||
assert not formatter._live_paused
|
||||
|
||||
def test_stop_live_with_active_session(self):
|
||||
"""Test stopping Live session when one is active."""
|
||||
formatter = ConsoleFormatter()
|
||||
|
||||
mock_live = MagicMock(spec=Live)
|
||||
formatter._live = mock_live
|
||||
|
||||
formatter.stop_live()
|
||||
|
||||
mock_live.stop.assert_called_once()
|
||||
assert formatter._live is None
|
||||
|
||||
def test_stop_live_with_no_session(self):
|
||||
"""Test stopping Live session when none exists."""
|
||||
formatter = ConsoleFormatter()
|
||||
|
||||
formatter._live = None
|
||||
|
||||
formatter.stop_live()
|
||||
|
||||
assert formatter._live is None
|
||||
|
||||
def test_stop_live_multiple_calls(self):
|
||||
"""Test multiple calls to stop_live are safe."""
|
||||
formatter = ConsoleFormatter()
|
||||
|
||||
mock_live = MagicMock(spec=Live)
|
||||
formatter._live = mock_live
|
||||
|
||||
formatter.stop_live()
|
||||
formatter.stop_live()
|
||||
|
||||
mock_live.stop.assert_called_once()
|
||||
assert formatter._live is None
|
||||
|
||||
Reference in New Issue
Block a user