|
4 | 4 | import tempfile
|
5 | 5 | from unittest.mock import patch
|
6 | 6 |
|
| 7 | +import pytest |
| 8 | + |
7 | 9 | from src.config.config import (
|
8 | 10 | ConfigManager,
|
9 | 11 | CorsConfig,
|
@@ -365,6 +367,197 @@ def test_get_provider_not_exists(self):
|
365 | 367 | provider = config_manager.get_provider("nonexistent_provider")
|
366 | 368 | assert provider is None
|
367 | 369 |
|
| 370 | + @patch.dict( |
| 371 | + os.environ, |
| 372 | + { |
| 373 | + "GITHUB_CLIENT_ID": "github_test_id", |
| 374 | + "GITHUB_CLIENT_SECRET": "github_test_secret", |
| 375 | + "REDIS_HOST": "redis.test.com", |
| 376 | + "REDIS_PORT": "6380", |
| 377 | + "VAULT_TOKEN": "vault_test_token", |
| 378 | + }, |
| 379 | + ) |
| 380 | + def test_env_var_substitution_required_vars(self): |
| 381 | + """Test environment variable substitution for required variables.""" |
| 382 | + yaml_content = """ |
| 383 | +host: "127.0.0.1" |
| 384 | +port: 8080 |
| 385 | +issuer: "http://localhost:8080" |
| 386 | +session_secret: "test-secret" |
| 387 | +
|
| 388 | +storage: |
| 389 | + type: "redis" |
| 390 | + redis: |
| 391 | + host: "${REDIS_HOST}" |
| 392 | + port: "${REDIS_PORT}" |
| 393 | + vault: |
| 394 | + token: "${VAULT_TOKEN}" |
| 395 | +
|
| 396 | +oauth_providers: |
| 397 | + github: |
| 398 | + client_id: "${GITHUB_CLIENT_ID}" |
| 399 | + client_secret: "${GITHUB_CLIENT_SECRET}" |
| 400 | + scopes: ["user:email"] |
| 401 | +
|
| 402 | +mcp_services: |
| 403 | + calculator: |
| 404 | + name: "Calculator Service" |
| 405 | + url: "http://localhost:3001/mcp" |
| 406 | + oauth_provider: "github" |
| 407 | + auth_required: true |
| 408 | +""" |
| 409 | + |
| 410 | + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: |
| 411 | + f.write(yaml_content) |
| 412 | + config_path = f.name |
| 413 | + |
| 414 | + try: |
| 415 | + config_manager = ConfigManager(config_path) |
| 416 | + config = config_manager.load_config() |
| 417 | + |
| 418 | + # Check that environment variables were substituted |
| 419 | + github_provider = config.oauth_providers["github"] |
| 420 | + assert github_provider.client_id == "github_test_id" |
| 421 | + assert github_provider.client_secret == "github_test_secret" |
| 422 | + |
| 423 | + # Check storage config |
| 424 | + assert config.storage.redis.host == "redis.test.com" |
| 425 | + assert config.storage.redis.port == 6380 # Should be converted to int |
| 426 | + assert config.storage.vault.token == "vault_test_token" |
| 427 | + |
| 428 | + finally: |
| 429 | + os.unlink(config_path) |
| 430 | + |
| 431 | + @patch.dict(os.environ, {"REDIS_HOST": "redis.prod.com"}, clear=False) |
| 432 | + def test_env_var_substitution_with_defaults(self): |
| 433 | + """Test environment variable substitution with default values.""" |
| 434 | + yaml_content = """ |
| 435 | +host: "127.0.0.1" |
| 436 | +port: 8080 |
| 437 | +
|
| 438 | +storage: |
| 439 | + type: "redis" |
| 440 | + redis: |
| 441 | + host: "${REDIS_HOST:-localhost}" |
| 442 | + port: "${REDIS_PORT:-6379}" |
| 443 | + password: "${REDIS_PASSWORD:-}" |
| 444 | + db: "${REDIS_DB:-0}" |
| 445 | +
|
| 446 | +oauth_providers: |
| 447 | + github: |
| 448 | + client_id: "${GITHUB_CLIENT_ID:-default_client_id}" |
| 449 | + client_secret: "${GITHUB_CLIENT_SECRET:-default_secret}" |
| 450 | +""" |
| 451 | + |
| 452 | + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: |
| 453 | + f.write(yaml_content) |
| 454 | + config_path = f.name |
| 455 | + |
| 456 | + try: |
| 457 | + config_manager = ConfigManager(config_path) |
| 458 | + config = config_manager.load_config() |
| 459 | + |
| 460 | + # Check that environment variables were used |
| 461 | + assert config.storage.redis.host == "redis.prod.com" # From env |
| 462 | + assert config.storage.redis.port == 6379 # Default value, converted to int |
| 463 | + assert config.storage.redis.password == "" # Default empty string |
| 464 | + assert config.storage.redis.db == 0 # Default value |
| 465 | + |
| 466 | + # Check OAuth provider defaults |
| 467 | + github_provider = config.oauth_providers["github"] |
| 468 | + assert github_provider.client_id == "default_client_id" |
| 469 | + assert github_provider.client_secret == "default_secret" |
| 470 | + |
| 471 | + finally: |
| 472 | + os.unlink(config_path) |
| 473 | + |
| 474 | + def test_env_var_substitution_missing_required(self): |
| 475 | + """Test that missing required environment variables raise an error.""" |
| 476 | + yaml_content = """ |
| 477 | +oauth_providers: |
| 478 | + github: |
| 479 | + client_id: "${MISSING_CLIENT_ID}" |
| 480 | + client_secret: "static_secret" |
| 481 | +""" |
| 482 | + |
| 483 | + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: |
| 484 | + f.write(yaml_content) |
| 485 | + config_path = f.name |
| 486 | + |
| 487 | + try: |
| 488 | + config_manager = ConfigManager(config_path) |
| 489 | + |
| 490 | + with pytest.raises( |
| 491 | + ValueError, |
| 492 | + match="Environment variable 'MISSING_CLIENT_ID' is required but not set", |
| 493 | + ): |
| 494 | + config_manager.load_config() |
| 495 | + |
| 496 | + finally: |
| 497 | + os.unlink(config_path) |
| 498 | + |
| 499 | + def test_env_var_substitution_mixed_formats(self): |
| 500 | + """Test mixed environment variable formats in the same config.""" |
| 501 | + yaml_content = """ |
| 502 | +host: "${HOST:-0.0.0.0}" |
| 503 | +port: "${PORT}" |
| 504 | +issuer: "${ISSUER:-http://localhost:8080}" |
| 505 | +session_secret: "${SESSION_SECRET}" |
| 506 | +debug: "${DEBUG:-false}" |
| 507 | +""" |
| 508 | + |
| 509 | + with patch.dict(os.environ, {"PORT": "9000", "SESSION_SECRET": "secret123"}): |
| 510 | + with tempfile.NamedTemporaryFile( |
| 511 | + mode="w", suffix=".yaml", delete=False |
| 512 | + ) as f: |
| 513 | + f.write(yaml_content) |
| 514 | + config_path = f.name |
| 515 | + |
| 516 | + try: |
| 517 | + config_manager = ConfigManager(config_path) |
| 518 | + config = config_manager.load_config() |
| 519 | + |
| 520 | + # Check mixed substitution results |
| 521 | + assert config.host == "0.0.0.0" # Default value |
| 522 | + assert config.port == 9000 # From environment |
| 523 | + assert config.issuer == "http://localhost:8080" # Default value |
| 524 | + assert config.session_secret == "secret123" # From environment |
| 525 | + assert config.debug is False # Default value converted to boolean |
| 526 | + |
| 527 | + finally: |
| 528 | + os.unlink(config_path) |
| 529 | + |
| 530 | + def test_substitute_env_vars_method_direct(self): |
| 531 | + """Test the _substitute_env_vars method directly.""" |
| 532 | + config_manager = ConfigManager() |
| 533 | + |
| 534 | + # Test required variable |
| 535 | + with patch.dict(os.environ, {"TEST_VAR": "test_value"}): |
| 536 | + result = config_manager._substitute_env_vars("value: ${TEST_VAR}") |
| 537 | + assert result == "value: test_value" |
| 538 | + |
| 539 | + # Test variable with default |
| 540 | + result = config_manager._substitute_env_vars("value: ${MISSING_VAR:-default}") |
| 541 | + assert result == "value: default" |
| 542 | + |
| 543 | + # Test missing required variable |
| 544 | + with pytest.raises( |
| 545 | + ValueError, |
| 546 | + match="Environment variable 'MISSING_REQUIRED' is required but not set", |
| 547 | + ): |
| 548 | + config_manager._substitute_env_vars("value: ${MISSING_REQUIRED}") |
| 549 | + |
| 550 | + # Test multiple variables |
| 551 | + with patch.dict(os.environ, {"VAR1": "value1", "VAR2": "value2"}): |
| 552 | + result = config_manager._substitute_env_vars("${VAR1} and ${VAR2}") |
| 553 | + assert result == "value1 and value2" |
| 554 | + |
| 555 | + # Test variables with complex defaults |
| 556 | + result = config_manager._substitute_env_vars( |
| 557 | + "${VAR:-default with spaces and : colons}" |
| 558 | + ) |
| 559 | + assert result == "default with spaces and : colons" |
| 560 | + |
368 | 561 |
|
369 | 562 | class TestDataClasses:
|
370 | 563 | """Test cases for configuration data classes."""
|
|
0 commit comments