-
Notifications
You must be signed in to change notification settings - Fork 42
Enhance state management and logging functionality #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
NiveditJain
merged 5 commits into
exospherehost:main
from
NiveditJain:230-improve-process-of-bulk-state-creation-in-terms-of-fanout-apis
Aug 19, 2025
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
cbb4319
Enhance state management and logging functionality
NiveditJain a95fc1f
Update state-manager/app/controller/executed_state.py
NiveditJain 740ca8d
fixed tests
NiveditJain a9445e3
Merge branch '230-improve-process-of-bulk-state-creation-in-terms-of-…
NiveditJain 582975f
added more tests
NiveditJain File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| import pytest | ||
| from unittest.mock import AsyncMock, MagicMock, patch | ||
| from beanie import PydanticObjectId | ||
|
|
||
| from app.tasks.create_next_state import create_next_state | ||
| from app.models.db.state import State | ||
| from app.models.db.graph_template_model import GraphTemplate | ||
| from app.models.db.registered_node import RegisteredNode | ||
| from app.models.graph_template_validation_status import GraphTemplateValidationStatus | ||
| from app.models.state_status_enum import StateStatusEnum | ||
|
|
||
|
|
||
| class TestCreateNextState: | ||
| """Test cases for create_next_state function""" | ||
|
|
||
| @pytest.fixture | ||
| def mock_state(self): | ||
| """Create a mock state object""" | ||
| state = MagicMock(spec=State) | ||
| state.id = PydanticObjectId() | ||
| state.identifier = "test_node" | ||
| state.namespace_name = "test_namespace" | ||
| state.graph_name = "test_graph" | ||
| state.run_id = "test_run_id" | ||
| state.status = StateStatusEnum.EXECUTED | ||
| state.inputs = {"input1": "value1"} | ||
| state.outputs = {"output1": "result1"} | ||
| state.error = None | ||
| state.parents = {"parent_node": PydanticObjectId()} | ||
| state.save = AsyncMock() | ||
| return state | ||
|
|
||
| @pytest.fixture | ||
| def mock_graph_template(self): | ||
| """Create a mock graph template""" | ||
| template = MagicMock(spec=GraphTemplate) | ||
| template.validation_status = GraphTemplateValidationStatus.VALID | ||
| template.get_node_by_identifier = MagicMock() | ||
| return template | ||
|
|
||
| @pytest.fixture | ||
| def mock_registered_node(self): | ||
| """Create a mock registered node""" | ||
| node = MagicMock(spec=RegisteredNode) | ||
| node.inputs_schema = { | ||
| "type": "object", | ||
| "properties": { | ||
| "field1": {"type": "string"}, | ||
| "field2": {"type": "string"} | ||
| } | ||
| } | ||
| return node | ||
|
|
||
| @patch('app.tasks.create_next_state.GraphTemplate') | ||
| async def test_create_next_state_none_id(self, mock_graph_template_class): | ||
| """Test create_next_state with state having None id""" | ||
| # Arrange | ||
| state_with_none_id = MagicMock() | ||
| state_with_none_id.id = None | ||
|
|
||
| # Act & Assert | ||
| with pytest.raises(ValueError, match="State is not valid"): | ||
| await create_next_state(state_with_none_id) | ||
|
|
||
| @patch('app.tasks.create_next_state.GraphTemplate') | ||
| @patch('app.tasks.create_next_state.asyncio.sleep') | ||
| async def test_create_next_state_wait_for_validation( | ||
| self, | ||
| mock_sleep, | ||
| mock_graph_template_class, | ||
| mock_state, | ||
| mock_graph_template | ||
| ): | ||
| """Test waiting for graph template to become valid""" | ||
| # Arrange | ||
| # First call returns invalid template, second call returns valid | ||
| invalid_template = MagicMock() | ||
| invalid_template.validation_status = GraphTemplateValidationStatus.INVALID | ||
|
|
||
| mock_graph_template_class.find_one = AsyncMock(side_effect=[invalid_template, mock_graph_template]) | ||
|
|
||
| # Mock node template with no next nodes | ||
| node_template = MagicMock() | ||
| node_template.next_nodes = None | ||
| mock_graph_template.get_node_by_identifier.return_value = node_template | ||
|
|
||
| # Act | ||
| await create_next_state(mock_state) | ||
|
|
||
| # Assert | ||
| assert mock_graph_template_class.find_one.call_count == 2 | ||
| mock_sleep.assert_called_once_with(1) | ||
| assert mock_state.status == StateStatusEnum.SUCCESS | ||
|
|
||
| @patch('app.tasks.create_next_state.GraphTemplate') | ||
| async def test_create_next_state_no_next_nodes( | ||
| self, | ||
| mock_graph_template_class, | ||
| mock_state, | ||
| mock_graph_template | ||
| ): | ||
| """Test when there are no next nodes""" | ||
| # Arrange | ||
| mock_graph_template_class.find_one = AsyncMock(return_value=mock_graph_template) | ||
|
|
||
| node_template = MagicMock() | ||
| node_template.next_nodes = None | ||
| mock_graph_template.get_node_by_identifier.return_value = node_template | ||
|
|
||
| # Act | ||
| await create_next_state(mock_state) | ||
|
|
||
| # Assert | ||
| assert mock_state.status == StateStatusEnum.SUCCESS | ||
|
|
||
| @patch('app.tasks.create_next_state.GraphTemplate') | ||
| async def test_create_next_state_general_exception( | ||
| self, | ||
| mock_graph_template_class, | ||
| mock_state | ||
| ): | ||
| """Test general exception handling""" | ||
| # Arrange | ||
| mock_graph_template_class.find_one = AsyncMock(side_effect=Exception("General error")) | ||
|
|
||
| # Act | ||
| await create_next_state(mock_state) | ||
|
|
||
| # Assert | ||
| assert mock_state.status == StateStatusEnum.ERRORED | ||
| assert mock_state.error == "General error" | ||
| mock_state.save.assert_called_once() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import logging | ||
| from unittest.mock import patch | ||
| from app.singletons.logs_manager import LogsManager | ||
|
|
||
|
|
||
| class TestLogsManager: | ||
| """Test cases for LogsManager singleton""" | ||
|
|
||
| def setup_method(self): | ||
| """Reset the singleton and logging before each test""" | ||
| # Clear the singleton instance | ||
| if hasattr(LogsManager, '_instance'): | ||
| delattr(LogsManager, '_instance') | ||
|
|
||
| # Reset logging level to INFO | ||
| logging.getLogger().setLevel(logging.INFO) | ||
|
|
||
| def teardown_method(self): | ||
| """Clean up after each test""" | ||
| # Clear the singleton instance | ||
| if hasattr(LogsManager, '_instance'): | ||
| delattr(LogsManager, '_instance') | ||
|
|
||
| @patch('app.singletons.logs_manager.sys.argv', ['python', 'run.py', '--mode', 'production']) | ||
| def test_logs_manager_production_mode_command_line(self): | ||
| """Test LogsManager sets INFO level in production mode via command line""" | ||
| # Check that the logging level is set to INFO in production mode | ||
| root_logger = logging.getLogger() | ||
| assert root_logger.level == logging.INFO | ||
|
|
||
| @patch('app.singletons.logs_manager.sys.argv', ['python', 'run.py', '--mode']) | ||
| def test_logs_manager_invalid_command_line_format(self): | ||
| """Test LogsManager handles invalid command line format gracefully""" | ||
| # Should default to INFO level when command line format is invalid | ||
| root_logger = logging.getLogger() | ||
| assert root_logger.level == logging.INFO | ||
|
|
||
| @patch('app.singletons.logs_manager.sys.argv', ['python', 'run.py', '--mode', 'invalid']) | ||
| def test_logs_manager_invalid_mode_command_line(self): | ||
| """Test LogsManager handles invalid mode in command line""" | ||
| # Should default to INFO level when mode is invalid | ||
| root_logger = logging.getLogger() | ||
| assert root_logger.level == logging.INFO | ||
|
|
||
| def test_logs_manager_singleton_pattern(self): | ||
| """Test LogsManager follows singleton pattern""" | ||
| logs_manager1 = LogsManager() | ||
| logs_manager2 = LogsManager() | ||
|
|
||
| # Both instances should be the same object | ||
| assert logs_manager1 is logs_manager2 | ||
|
|
||
| def test_get_logger_returns_structlog_logger(self): | ||
| """Test get_logger returns a structlog logger""" | ||
| logs_manager = LogsManager() | ||
| logger = logs_manager.get_logger() | ||
|
|
||
| # Should return a structlog logger | ||
| assert logger is not None | ||
| # Check that it's a structlog logger by checking for structlog-specific attributes | ||
| assert hasattr(logger, 'info') | ||
| assert hasattr(logger, 'error') | ||
| assert hasattr(logger, 'warning') |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.