refactor: improve imports, fix datetime usage, and reorganize cross-chain services
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
- Added logger initialization to EventRouter in events.py - Fixed datetime.timedelta references to use timedelta directly in security_hardening.py - Fixed StateTransition timestamp default_factory to use lambda in state.py - Fixed StateValidator.validate_transitions to only check source states exist - Moved cross_chain_bridge_enhanced.py to cross_chain/bridge_enhanced.py - Updated import paths in global_marketplace
This commit is contained in:
403
tests/test_feature_flags.py
Normal file
403
tests/test_feature_flags.py
Normal file
@@ -0,0 +1,403 @@
|
||||
"""
|
||||
Tests for feature flags utilities
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from aitbc.feature_flags import (
|
||||
FeatureFlag,
|
||||
FeatureFlagManager,
|
||||
get_feature_flag_manager,
|
||||
is_feature_enabled,
|
||||
)
|
||||
|
||||
|
||||
class TestFeatureFlag:
|
||||
"""Tests for FeatureFlag dataclass"""
|
||||
|
||||
def test_feature_flag_creation(self):
|
||||
"""Test FeatureFlag dataclass creation"""
|
||||
flag = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
rollout_percentage=50.0
|
||||
)
|
||||
assert flag.name == "test_feature"
|
||||
assert flag.enabled is True
|
||||
assert flag.description == "Test feature"
|
||||
assert flag.rollout_percentage == 50.0
|
||||
|
||||
def test_feature_flag_with_whitelist(self):
|
||||
"""Test FeatureFlag with whitelisted users"""
|
||||
flag = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
whitelisted_users={"user1", "user2"}
|
||||
)
|
||||
assert flag.whitelisted_users == {"user1", "user2"}
|
||||
|
||||
def test_feature_flag_with_blacklist(self):
|
||||
"""Test FeatureFlag with blacklisted users"""
|
||||
flag = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
blacklisted_users={"user3"}
|
||||
)
|
||||
assert flag.blacklisted_users == {"user3"}
|
||||
|
||||
def test_feature_flag_with_enabled_since(self):
|
||||
"""Test FeatureFlag with enabled_since timestamp"""
|
||||
now = datetime.now()
|
||||
flag = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
enabled_since=now
|
||||
)
|
||||
assert flag.enabled_since == now
|
||||
|
||||
|
||||
class TestFeatureFlagManager:
|
||||
"""Tests for FeatureFlagManager"""
|
||||
|
||||
def test_initialization_without_config_file(self, tmp_path):
|
||||
"""Test initialization without config file"""
|
||||
manager = FeatureFlagManager(config_file=tmp_path / "nonexistent.json")
|
||||
assert manager._flags == {}
|
||||
assert manager.config_file == tmp_path / "nonexistent.json"
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_load_flags_from_file(self, mock_logger, tmp_path):
|
||||
"""Test loading flags from configuration file"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
config_data = {
|
||||
"test_feature": {
|
||||
"enabled": True,
|
||||
"description": "Test feature",
|
||||
"rollout_percentage": 50.0,
|
||||
"whitelisted_users": ["user1"],
|
||||
"blacklisted_users": ["user2"],
|
||||
"enabled_since": "2024-01-01T00:00:00"
|
||||
}
|
||||
}
|
||||
config_file.write_text(json.dumps(config_data))
|
||||
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
|
||||
assert "test_feature" in manager._flags
|
||||
assert manager._flags["test_feature"].enabled is True
|
||||
assert manager._flags["test_feature"].description == "Test feature"
|
||||
assert manager._flags["test_feature"].rollout_percentage == 50.0
|
||||
mock_logger.info.assert_called_once()
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_load_flags_file_not_found(self, mock_logger, tmp_path):
|
||||
"""Test loading flags when file doesn't exist"""
|
||||
manager = FeatureFlagManager(config_file=tmp_path / "nonexistent.json")
|
||||
mock_logger.info.assert_called_once()
|
||||
assert "No feature flags file found" in mock_logger.info.call_args[0][0]
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_load_flags_invalid_json(self, mock_logger, tmp_path):
|
||||
"""Test loading flags with invalid JSON"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
config_file.write_text("invalid json")
|
||||
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
mock_logger.error.assert_called_once()
|
||||
assert "Failed to load feature flags" in mock_logger.error.call_args[0][0]
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_save_flags(self, mock_logger, tmp_path):
|
||||
"""Test saving flags to configuration file"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature"
|
||||
)
|
||||
|
||||
manager.save_flags()
|
||||
|
||||
assert config_file.exists()
|
||||
with open(config_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
assert "test_feature" in data
|
||||
assert data["test_feature"]["enabled"] is True
|
||||
# Check that save was logged (may have other log calls from initialization)
|
||||
assert any("Saved" in str(call) for call in mock_logger.info.call_args_list)
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_is_enabled_flag_not_found(self, mock_logger):
|
||||
"""Test is_enabled when flag not found"""
|
||||
manager = FeatureFlagManager()
|
||||
result = manager.is_enabled("nonexistent_feature")
|
||||
assert result is False
|
||||
mock_logger.warning.assert_called_once()
|
||||
|
||||
def test_is_enabled_globally_disabled(self):
|
||||
"""Test is_enabled when flag is globally disabled"""
|
||||
manager = FeatureFlagManager()
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=False,
|
||||
description="Test feature"
|
||||
)
|
||||
result = manager.is_enabled("test_feature")
|
||||
assert result is False
|
||||
|
||||
def test_is_enabled_globally_enabled(self):
|
||||
"""Test is_enabled when flag is globally enabled"""
|
||||
manager = FeatureFlagManager()
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature"
|
||||
)
|
||||
result = manager.is_enabled("test_feature")
|
||||
assert result is True
|
||||
|
||||
def test_is_enabled_user_blacklisted(self):
|
||||
"""Test is_enabled when user is blacklisted"""
|
||||
manager = FeatureFlagManager()
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
blacklisted_users={"user1"}
|
||||
)
|
||||
result = manager.is_enabled("test_feature", user_id="user1")
|
||||
assert result is False
|
||||
|
||||
def test_is_enabled_user_whitelisted(self):
|
||||
"""Test is_enabled when user is whitelisted"""
|
||||
manager = FeatureFlagManager()
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
whitelisted_users={"user1"}
|
||||
)
|
||||
result = manager.is_enabled("test_feature", user_id="user1")
|
||||
assert result is True
|
||||
|
||||
def test_is_enabled_percentage_rollout_included(self):
|
||||
"""Test is_enabled with percentage-based rollout - user included"""
|
||||
manager = FeatureFlagManager()
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
rollout_percentage=50.0
|
||||
)
|
||||
result = manager.is_enabled("test_feature", user_hash=25)
|
||||
assert result is True # 25 % 100 = 25 < 50
|
||||
|
||||
def test_is_enabled_percentage_rollout_excluded(self):
|
||||
"""Test is_enabled with percentage-based rollout - user excluded"""
|
||||
manager = FeatureFlagManager()
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
rollout_percentage=50.0
|
||||
)
|
||||
result = manager.is_enabled("test_feature", user_hash=75)
|
||||
assert result is False # 75 % 100 = 75 >= 50
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_enable_feature_new_flag(self, mock_logger, tmp_path):
|
||||
"""Test enable_feature for new flag"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
|
||||
manager.enable_feature("new_feature", rollout_percentage=75.0)
|
||||
|
||||
assert "new_feature" in manager._flags
|
||||
assert manager._flags["new_feature"].enabled is True
|
||||
assert manager._flags["new_feature"].rollout_percentage == 75.0
|
||||
assert manager._flags["new_feature"].enabled_since is not None
|
||||
# Check that enable was logged
|
||||
assert any("Enabled" in str(call) for call in mock_logger.info.call_args_list)
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_enable_feature_existing_flag(self, mock_logger, tmp_path):
|
||||
"""Test enable_feature for existing flag"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
manager._flags["existing_feature"] = FeatureFlag(
|
||||
name="existing_feature",
|
||||
enabled=False,
|
||||
description="Existing feature"
|
||||
)
|
||||
|
||||
manager.enable_feature("existing_feature", rollout_percentage=50.0)
|
||||
|
||||
assert manager._flags["existing_feature"].enabled is True
|
||||
assert manager._flags["existing_feature"].rollout_percentage == 50.0
|
||||
# Check that enable was logged
|
||||
assert any("Enabled" in str(call) for call in mock_logger.info.call_args_list)
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_disable_feature(self, mock_logger, tmp_path):
|
||||
"""Test disable_feature"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature"
|
||||
)
|
||||
|
||||
manager.disable_feature("test_feature")
|
||||
|
||||
assert manager._flags["test_feature"].enabled is False
|
||||
# Check that disable was logged
|
||||
assert any("Disabled" in str(call) for call in mock_logger.info.call_args_list)
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_add_whitelisted_user_new_flag(self, mock_logger, tmp_path):
|
||||
"""Test add_whitelisted_user for new flag"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
|
||||
manager.add_whitelisted_user("new_feature", "user1")
|
||||
|
||||
assert "new_feature" in manager._flags
|
||||
assert "user1" in manager._flags["new_feature"].whitelisted_users
|
||||
# Check that add was logged
|
||||
assert any("whitelist" in str(call) for call in mock_logger.info.call_args_list)
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_add_whitelisted_user_existing_flag(self, mock_logger, tmp_path):
|
||||
"""Test add_whitelisted_user for existing flag"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
whitelisted_users=set()
|
||||
)
|
||||
|
||||
manager.add_whitelisted_user("test_feature", "user1")
|
||||
|
||||
assert "user1" in manager._flags["test_feature"].whitelisted_users
|
||||
# Check that add was logged
|
||||
assert any("whitelist" in str(call) for call in mock_logger.info.call_args_list)
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_add_blacklisted_user_new_flag(self, mock_logger, tmp_path):
|
||||
"""Test add_blacklisted_user for new flag"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
|
||||
manager.add_blacklisted_user("new_feature", "user1")
|
||||
|
||||
assert "new_feature" in manager._flags
|
||||
assert "user1" in manager._flags["new_feature"].blacklisted_users
|
||||
# Check that add was logged
|
||||
assert any("blacklist" in str(call) for call in mock_logger.info.call_args_list)
|
||||
|
||||
@patch('aitbc.feature_flags.logger')
|
||||
def test_add_blacklisted_user_existing_flag(self, mock_logger, tmp_path):
|
||||
"""Test add_blacklisted_user for existing flag"""
|
||||
config_file = tmp_path / "feature_flags.json"
|
||||
manager = FeatureFlagManager(config_file=config_file)
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature",
|
||||
blacklisted_users=set()
|
||||
)
|
||||
|
||||
manager.add_blacklisted_user("test_feature", "user1")
|
||||
|
||||
assert "user1" in manager._flags["test_feature"].blacklisted_users
|
||||
# Check that add was logged
|
||||
assert any("blacklist" in str(call) for call in mock_logger.info.call_args_list)
|
||||
|
||||
def test_get_all_flags(self):
|
||||
"""Test get_all_flags"""
|
||||
manager = FeatureFlagManager()
|
||||
manager._flags["feature1"] = FeatureFlag(
|
||||
name="feature1",
|
||||
enabled=True,
|
||||
description="Feature 1"
|
||||
)
|
||||
manager._flags["feature2"] = FeatureFlag(
|
||||
name="feature2",
|
||||
enabled=False,
|
||||
description="Feature 2"
|
||||
)
|
||||
|
||||
flags = manager.get_all_flags()
|
||||
|
||||
assert len(flags) == 2
|
||||
assert "feature1" in flags
|
||||
assert "feature2" in flags
|
||||
|
||||
def test_get_flag_status_found(self):
|
||||
"""Test get_flag_status when flag exists"""
|
||||
manager = FeatureFlagManager()
|
||||
flag = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature"
|
||||
)
|
||||
manager._flags["test_feature"] = flag
|
||||
|
||||
result = manager.get_flag_status("test_feature")
|
||||
|
||||
assert result == flag
|
||||
|
||||
def test_get_flag_status_not_found(self):
|
||||
"""Test get_flag_status when flag doesn't exist"""
|
||||
manager = FeatureFlagManager()
|
||||
|
||||
result = manager.get_flag_status("nonexistent_feature")
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestGlobalFunctions:
|
||||
"""Tests for global feature flag functions"""
|
||||
|
||||
def test_get_feature_flag_manager_singleton(self):
|
||||
"""Test get_feature_flag_manager returns singleton"""
|
||||
manager1 = get_feature_flag_manager()
|
||||
manager2 = get_feature_flag_manager()
|
||||
|
||||
assert manager1 is manager2
|
||||
|
||||
def test_get_feature_flag_manager_with_config(self, tmp_path):
|
||||
"""Test get_feature_flag_manager with custom config"""
|
||||
# Reset global manager first
|
||||
import aitbc.feature_flags as ff_module
|
||||
ff_module._global_feature_flag_manager = None
|
||||
|
||||
manager = get_feature_flag_manager(config_file=tmp_path / "custom.json")
|
||||
|
||||
assert manager.config_file == tmp_path / "custom.json"
|
||||
|
||||
def test_is_feature_enabled_global(self):
|
||||
"""Test is_feature_enabled global function"""
|
||||
manager = get_feature_flag_manager()
|
||||
manager._flags["test_feature"] = FeatureFlag(
|
||||
name="test_feature",
|
||||
enabled=True,
|
||||
description="Test feature"
|
||||
)
|
||||
|
||||
result = is_feature_enabled("test_feature")
|
||||
|
||||
assert result is True
|
||||
Reference in New Issue
Block a user