| |
|
|
| """ |
| Unit tests for main.py CLI functions. |
| Tests for parse_cli_args(), resolve_server_config(), and print_startup_banner(). |
| """ |
|
|
| import pytest |
| import argparse |
| import sys |
| from unittest.mock import patch, MagicMock |
| from io import StringIO |
|
|
|
|
| class TestParseCliArgs: |
| """Tests for parse_cli_args() function.""" |
| |
| def test_default_values_are_none(self): |
| """ |
| What it does: Verifies that default values for host and port are None. |
| Purpose: Ensure that None indicates "use env or default" in priority resolution. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with no arguments...") |
| with patch.object(sys, 'argv', ['main.py']): |
| args = parse_cli_args() |
| |
| print(f"args.host: {args.host}") |
| print(f"args.port: {args.port}") |
| print(f"Comparing: Expected host=None, port=None") |
| assert args.host is None |
| assert args.port is None |
| |
| def test_port_argument_long_form(self): |
| """ |
| What it does: Verifies that --port argument is parsed correctly. |
| Purpose: Ensure long form --port works. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with --port 9000...") |
| with patch.object(sys, 'argv', ['main.py', '--port', '9000']): |
| args = parse_cli_args() |
| |
| print(f"args.port: {args.port}") |
| print(f"Comparing: Expected 9000, Got {args.port}") |
| assert args.port == 9000 |
| |
| def test_port_argument_short_form(self): |
| """ |
| What it does: Verifies that -p argument is parsed correctly. |
| Purpose: Ensure short form -p works. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with -p 8080...") |
| with patch.object(sys, 'argv', ['main.py', '-p', '8080']): |
| args = parse_cli_args() |
| |
| print(f"args.port: {args.port}") |
| print(f"Comparing: Expected 8080, Got {args.port}") |
| assert args.port == 8080 |
| |
| def test_host_argument_long_form(self): |
| """ |
| What it does: Verifies that --host argument is parsed correctly. |
| Purpose: Ensure long form --host works. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with --host 127.0.0.1...") |
| with patch.object(sys, 'argv', ['main.py', '--host', '127.0.0.1']): |
| args = parse_cli_args() |
| |
| print(f"args.host: {args.host}") |
| print(f"Comparing: Expected '127.0.0.1', Got '{args.host}'") |
| assert args.host == "127.0.0.1" |
| |
| def test_host_argument_short_form(self): |
| """ |
| What it does: Verifies that -H argument is parsed correctly. |
| Purpose: Ensure short form -H works. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with -H 192.168.1.1...") |
| with patch.object(sys, 'argv', ['main.py', '-H', '192.168.1.1']): |
| args = parse_cli_args() |
| |
| print(f"args.host: {args.host}") |
| print(f"Comparing: Expected '192.168.1.1', Got '{args.host}'") |
| assert args.host == "192.168.1.1" |
| |
| def test_both_arguments_together(self): |
| """ |
| What it does: Verifies that both --host and --port can be used together. |
| Purpose: Ensure both arguments work simultaneously. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with --host 0.0.0.0 --port 3000...") |
| with patch.object(sys, 'argv', ['main.py', '--host', '0.0.0.0', '--port', '3000']): |
| args = parse_cli_args() |
| |
| print(f"args.host: {args.host}") |
| print(f"args.port: {args.port}") |
| assert args.host == "0.0.0.0" |
| assert args.port == 3000 |
| |
| def test_short_forms_together(self): |
| """ |
| What it does: Verifies that both -H and -p can be used together. |
| Purpose: Ensure short forms work simultaneously. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with -H 127.0.0.1 -p 5000...") |
| with patch.object(sys, 'argv', ['main.py', '-H', '127.0.0.1', '-p', '5000']): |
| args = parse_cli_args() |
| |
| print(f"args.host: {args.host}") |
| print(f"args.port: {args.port}") |
| assert args.host == "127.0.0.1" |
| assert args.port == 5000 |
|
|
|
|
| class TestResolveServerConfig: |
| """Tests for resolve_server_config() function - priority hierarchy.""" |
| |
| def test_cli_args_take_priority_over_env(self): |
| """ |
| What it does: Verifies that CLI arguments have highest priority. |
| Purpose: Ensure CLI args override environment variables. |
| """ |
| print("Setup: Importing resolve_server_config...") |
| from main import resolve_server_config |
| |
| print("Setup: Creating args with host=127.0.0.1, port=9000...") |
| args = argparse.Namespace(host="127.0.0.1", port=9000) |
| |
| print("Action: Calling resolve_server_config with CLI args...") |
| |
| with patch('main.SERVER_HOST', '0.0.0.0'), \ |
| patch('main.SERVER_PORT', 8000), \ |
| patch('main.DEFAULT_SERVER_HOST', '0.0.0.0'), \ |
| patch('main.DEFAULT_SERVER_PORT', 8000): |
| host, port = resolve_server_config(args) |
| |
| print(f"Resolved host: {host}") |
| print(f"Resolved port: {port}") |
| print(f"Comparing: Expected ('127.0.0.1', 9000)") |
| assert host == "127.0.0.1" |
| assert port == 9000 |
| |
| def test_env_vars_take_priority_over_defaults(self): |
| """ |
| What it does: Verifies that env vars have priority over defaults. |
| Purpose: Ensure env vars are used when CLI args are not provided. |
| """ |
| print("Setup: Importing resolve_server_config...") |
| from main import resolve_server_config |
| |
| print("Setup: Creating args with host=None, port=None (no CLI args)...") |
| args = argparse.Namespace(host=None, port=None) |
| |
| print("Action: Calling resolve_server_config with env vars set...") |
| |
| with patch('main.SERVER_HOST', '192.168.1.100'), \ |
| patch('main.SERVER_PORT', 3000), \ |
| patch('main.DEFAULT_SERVER_HOST', '0.0.0.0'), \ |
| patch('main.DEFAULT_SERVER_PORT', 8000): |
| host, port = resolve_server_config(args) |
| |
| print(f"Resolved host: {host}") |
| print(f"Resolved port: {port}") |
| print(f"Comparing: Expected ('192.168.1.100', 3000)") |
| assert host == "192.168.1.100" |
| assert port == 3000 |
| |
| def test_defaults_used_when_nothing_set(self): |
| """ |
| What it does: Verifies that defaults are used when nothing else is set. |
| Purpose: Ensure default values work correctly. |
| """ |
| print("Setup: Importing resolve_server_config...") |
| from main import resolve_server_config |
| |
| print("Setup: Creating args with host=None, port=None...") |
| args = argparse.Namespace(host=None, port=None) |
| |
| print("Action: Calling resolve_server_config with defaults...") |
| |
| with patch('main.SERVER_HOST', '0.0.0.0'), \ |
| patch('main.SERVER_PORT', 8000), \ |
| patch('main.DEFAULT_SERVER_HOST', '0.0.0.0'), \ |
| patch('main.DEFAULT_SERVER_PORT', 8000): |
| host, port = resolve_server_config(args) |
| |
| print(f"Resolved host: {host}") |
| print(f"Resolved port: {port}") |
| print(f"Comparing: Expected ('0.0.0.0', 8000)") |
| assert host == "0.0.0.0" |
| assert port == 8000 |
| |
| def test_cli_host_only_env_port(self): |
| """ |
| What it does: Verifies mixed priority - CLI host with env port. |
| Purpose: Ensure each argument is resolved independently. |
| """ |
| print("Setup: Importing resolve_server_config...") |
| from main import resolve_server_config |
| |
| print("Setup: Creating args with host='127.0.0.1', port=None...") |
| args = argparse.Namespace(host="127.0.0.1", port=None) |
| |
| print("Action: Calling resolve_server_config...") |
| with patch('main.SERVER_HOST', '0.0.0.0'), \ |
| patch('main.SERVER_PORT', 9000), \ |
| patch('main.DEFAULT_SERVER_HOST', '0.0.0.0'), \ |
| patch('main.DEFAULT_SERVER_PORT', 8000): |
| host, port = resolve_server_config(args) |
| |
| print(f"Resolved host: {host}") |
| print(f"Resolved port: {port}") |
| print(f"Comparing: Expected ('127.0.0.1', 9000)") |
| assert host == "127.0.0.1" |
| assert port == 9000 |
| |
| def test_cli_port_only_env_host(self): |
| """ |
| What it does: Verifies mixed priority - CLI port with env host. |
| Purpose: Ensure each argument is resolved independently. |
| """ |
| print("Setup: Importing resolve_server_config...") |
| from main import resolve_server_config |
| |
| print("Setup: Creating args with host=None, port=5000...") |
| args = argparse.Namespace(host=None, port=5000) |
| |
| print("Action: Calling resolve_server_config...") |
| with patch('main.SERVER_HOST', '192.168.1.1'), \ |
| patch('main.SERVER_PORT', 8000), \ |
| patch('main.DEFAULT_SERVER_HOST', '0.0.0.0'), \ |
| patch('main.DEFAULT_SERVER_PORT', 8000): |
| host, port = resolve_server_config(args) |
| |
| print(f"Resolved host: {host}") |
| print(f"Resolved port: {port}") |
| print(f"Comparing: Expected ('192.168.1.1', 5000)") |
| assert host == "192.168.1.1" |
| assert port == 5000 |
|
|
|
|
| class TestPrintStartupBanner: |
| """Tests for print_startup_banner() function.""" |
| |
| def test_banner_contains_url(self, capsys): |
| """ |
| What it does: Verifies that banner contains the server URL. |
| Purpose: Ensure URL is displayed to user. |
| """ |
| print("Setup: Importing print_startup_banner...") |
| from main import print_startup_banner |
| |
| print("Action: Calling print_startup_banner('0.0.0.0', 8000)...") |
| print_startup_banner("0.0.0.0", 8000) |
| |
| captured = capsys.readouterr() |
| print(f"Captured output length: {len(captured.out)}") |
| |
| |
| assert "localhost:8000" in captured.out or "8000" in captured.out |
| |
| def test_banner_contains_custom_port(self, capsys): |
| """ |
| What it does: Verifies that banner shows custom port. |
| Purpose: Ensure custom port is displayed correctly. |
| """ |
| print("Setup: Importing print_startup_banner...") |
| from main import print_startup_banner |
| |
| print("Action: Calling print_startup_banner('127.0.0.1', 9000)...") |
| print_startup_banner("127.0.0.1", 9000) |
| |
| captured = capsys.readouterr() |
| print(f"Captured output contains '9000': {'9000' in captured.out}") |
| |
| assert "9000" in captured.out |
| |
| def test_banner_contains_docs_url(self, capsys): |
| """ |
| What it does: Verifies that banner contains API docs URL. |
| Purpose: Ensure /docs endpoint is mentioned. |
| """ |
| print("Setup: Importing print_startup_banner...") |
| from main import print_startup_banner |
| |
| print("Action: Calling print_startup_banner('0.0.0.0', 8000)...") |
| print_startup_banner("0.0.0.0", 8000) |
| |
| captured = capsys.readouterr() |
| print(f"Captured output contains '/docs': {'/docs' in captured.out}") |
| |
| assert "/docs" in captured.out |
| |
| def test_banner_contains_health_url(self, capsys): |
| """ |
| What it does: Verifies that banner contains health check URL. |
| Purpose: Ensure /health endpoint is mentioned. |
| """ |
| print("Setup: Importing print_startup_banner...") |
| from main import print_startup_banner |
| |
| print("Action: Calling print_startup_banner('0.0.0.0', 8000)...") |
| print_startup_banner("0.0.0.0", 8000) |
| |
| captured = capsys.readouterr() |
| print(f"Captured output contains '/health': {'/health' in captured.out}") |
| |
| assert "/health" in captured.out |
|
|
|
|
| class TestCliHelp: |
| """Tests for CLI help output.""" |
| |
| def test_help_shows_port_option(self): |
| """ |
| What it does: Verifies that --help shows port option. |
| Purpose: Ensure help is informative. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with --help...") |
| with patch.object(sys, 'argv', ['main.py', '--help']): |
| with pytest.raises(SystemExit) as exc_info: |
| parse_cli_args() |
| |
| print(f"Exit code: {exc_info.value.code}") |
| |
| assert exc_info.value.code == 0 |
| |
| def test_help_shows_host_option(self, capsys): |
| """ |
| What it does: Verifies that --help output contains host option. |
| Purpose: Ensure host option is documented. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with --help...") |
| with patch.object(sys, 'argv', ['main.py', '--help']): |
| with pytest.raises(SystemExit): |
| parse_cli_args() |
| |
| captured = capsys.readouterr() |
| print(f"Help output contains '--host': {'--host' in captured.out}") |
| print(f"Help output contains '-H': {'-H' in captured.out}") |
| |
| assert "--host" in captured.out |
| assert "-H" in captured.out |
|
|
|
|
| class TestCliVersion: |
| """Tests for CLI version output.""" |
| |
| def test_version_flag_exits_with_zero(self): |
| """ |
| What it does: Verifies that --version exits with code 0. |
| Purpose: Ensure version flag works correctly. |
| """ |
| print("Setup: Importing parse_cli_args...") |
| from main import parse_cli_args |
| |
| print("Action: Calling parse_cli_args with --version...") |
| with patch.object(sys, 'argv', ['main.py', '--version']): |
| with pytest.raises(SystemExit) as exc_info: |
| parse_cli_args() |
| |
| print(f"Exit code: {exc_info.value.code}") |
| assert exc_info.value.code == 0 |
| |
| def test_version_shows_app_version(self, capsys): |
| """ |
| What it does: Verifies that --version shows application version. |
| Purpose: Ensure version is displayed. |
| """ |
| print("Setup: Importing parse_cli_args and APP_VERSION...") |
| from main import parse_cli_args |
| from kiro.config import APP_VERSION |
| |
| print("Action: Calling parse_cli_args with --version...") |
| with patch.object(sys, 'argv', ['main.py', '--version']): |
| with pytest.raises(SystemExit): |
| parse_cli_args() |
| |
| captured = capsys.readouterr() |
| print(f"Version output: {captured.out}") |
| print(f"APP_VERSION: {APP_VERSION}") |
| |
| assert APP_VERSION in captured.out |