import argparse from unittest.mock import MagicMock from prowler.providers.m365.lib.arguments import arguments class TestM365Arguments: def setup_method(self): """Setup mock ArgumentParser for testing""" self.mock_parser = MagicMock() self.mock_subparsers = MagicMock() self.mock_m365_parser = MagicMock() self.mock_auth_group = MagicMock() self.mock_auth_modes_group = MagicMock() self.mock_regions_group = MagicMock() # Setup the mock chain self.mock_parser.add_subparsers.return_value = self.mock_subparsers self.mock_subparsers.add_parser.return_value = self.mock_m365_parser self.mock_m365_parser.add_argument_group.side_effect = [ self.mock_auth_group, self.mock_regions_group, ] self.mock_auth_group.add_mutually_exclusive_group.return_value = ( self.mock_auth_modes_group ) def test_init_parser_creates_subparser(self): """Test that init_parser creates the M365 subparser correctly""" # Create a mock object that has the necessary attributes mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() # Call init_parser arguments.init_parser(mock_m365_args) # Verify subparser was created self.mock_subparsers.add_parser.assert_called_once_with( "m365", parents=[mock_m365_args.common_providers_parser], help="M365 Provider", ) def test_init_parser_creates_argument_groups(self): """Test that init_parser creates the correct argument groups""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Verify argument groups were created assert self.mock_m365_parser.add_argument_group.call_count == 2 calls = self.mock_m365_parser.add_argument_group.call_args_list assert calls[0][0][0] == "Authentication Modes" assert calls[1][0][0] == "Regions" def test_init_parser_creates_mutually_exclusive_auth_group(self): """Test that init_parser creates mutually exclusive authentication group""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Verify mutually exclusive group was created for authentication modes self.mock_auth_group.add_mutually_exclusive_group.assert_called_once() def test_init_parser_adds_authentication_arguments(self): """Test that init_parser adds all authentication arguments""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Verify authentication arguments were added to the mutually exclusive group assert self.mock_auth_modes_group.add_argument.call_count == 5 # Check that all authentication arguments are present calls = self.mock_auth_modes_group.add_argument.call_args_list auth_args = [call[0][0] for call in calls] assert "--az-cli-auth" in auth_args assert "--env-auth" in auth_args assert "--sp-env-auth" in auth_args assert "--browser-auth" in auth_args assert "--certificate-auth" in auth_args def test_init_parser_adds_non_exclusive_arguments(self): """Test that init_parser adds non-exclusive arguments directly to parser""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Verify non-exclusive arguments were added to main parser assert self.mock_m365_parser.add_argument.call_count == 3 # Check that non-exclusive arguments are present calls = self.mock_m365_parser.add_argument.call_args_list non_exclusive_args = [call[0][0] for call in calls] assert "--tenant-id" in non_exclusive_args assert "--init-modules" in non_exclusive_args assert "--certificate-path" in non_exclusive_args def test_init_parser_adds_region_arguments(self): """Test that init_parser adds region arguments""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Verify region arguments were added to regions group assert self.mock_regions_group.add_argument.call_count == 1 # Check that region argument is present calls = self.mock_regions_group.add_argument.call_args_list region_args = [call[0][0] for call in calls] assert "--region" in region_args def test_az_cli_auth_argument_configuration(self): """Test that az-cli-auth argument is configured correctly""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Find the az-cli-auth argument call calls = self.mock_auth_modes_group.add_argument.call_args_list az_cli_call = None for call in calls: if call[0][0] == "--az-cli-auth": az_cli_call = call break assert az_cli_call is not None # Check argument configuration kwargs = az_cli_call[1] assert kwargs["action"] == "store_true" assert "Azure CLI authentication" in kwargs["help"] def test_sp_env_auth_argument_configuration(self): """Test that sp-env-auth argument is configured correctly""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Find the sp-env-auth argument call calls = self.mock_auth_modes_group.add_argument.call_args_list sp_env_call = None for call in calls: if call[0][0] == "--sp-env-auth": sp_env_call = call break assert sp_env_call is not None # Check argument configuration kwargs = sp_env_call[1] assert kwargs["action"] == "store_true" assert "Service Principal environment variables" in kwargs["help"] def test_browser_auth_argument_configuration(self): """Test that browser-auth argument is configured correctly""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Find the browser-auth argument call calls = self.mock_auth_modes_group.add_argument.call_args_list browser_auth_call = None for call in calls: if call[0][0] == "--browser-auth": browser_auth_call = call break assert browser_auth_call is not None # Check argument configuration kwargs = browser_auth_call[1] assert kwargs["action"] == "store_true" assert "Azure interactive browser authentication" in kwargs["help"] def test_certificate_auth_argument_configuration(self): """Test that certificate-auth argument is configured correctly""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Find the certificate-auth argument call calls = self.mock_auth_modes_group.add_argument.call_args_list cert_auth_call = None for call in calls: if call[0][0] == "--certificate-auth": cert_auth_call = call break assert cert_auth_call is not None # Check argument configuration kwargs = cert_auth_call[1] assert kwargs["action"] == "store_true" assert "Certificate authentication" in kwargs["help"] def test_tenant_id_argument_configuration(self): """Test that tenant-id argument is configured correctly""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Find the tenant-id argument call calls = self.mock_m365_parser.add_argument.call_args_list tenant_id_call = None for call in calls: if call[0][0] == "--tenant-id": tenant_id_call = call break assert tenant_id_call is not None # Check argument configuration kwargs = tenant_id_call[1] assert kwargs["nargs"] == "?" assert kwargs["default"] is None assert "Microsoft 365 Tenant ID" in kwargs["help"] assert "--browser-auth" in kwargs["help"] def test_init_modules_argument_configuration(self): """Test that init-modules argument is configured correctly""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Find the init-modules argument call calls = self.mock_m365_parser.add_argument.call_args_list init_modules_call = None for call in calls: if call[0][0] == "--init-modules": init_modules_call = call break assert init_modules_call is not None # Check argument configuration kwargs = init_modules_call[1] assert kwargs["action"] == "store_true" assert "Initialize Microsoft 365 PowerShell modules" in kwargs["help"] def test_region_argument_configuration(self): """Test that region argument is configured correctly""" mock_m365_args = MagicMock() mock_m365_args.subparsers = self.mock_subparsers mock_m365_args.common_providers_parser = MagicMock() arguments.init_parser(mock_m365_args) # Find the region argument call calls = self.mock_regions_group.add_argument.call_args_list region_call = None for call in calls: if call[0][0] == "--region": region_call = call break assert region_call is not None # Check argument configuration kwargs = region_call[1] assert kwargs["nargs"] == "?" assert kwargs["default"] == "M365Global" assert kwargs["choices"] == [ "M365Global", "M365China", "M365USGovernment", ] assert "Microsoft 365 region" in kwargs["help"] assert "M365Global" in kwargs["help"] class TestM365ArgumentsIntegration: def test_real_argument_parsing_az_cli_auth(self): """Test parsing arguments with Azure CLI authentication""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) # Create a mock object that mimics the structure used by the init_parser function mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with Azure CLI auth args = parser.parse_args(["m365", "--az-cli-auth"]) assert args.az_cli_auth is True assert not hasattr(args, "env_auth") assert args.sp_env_auth is False assert args.browser_auth is False assert args.certificate_auth is False def test_real_argument_parsing_sp_env_auth(self): """Test parsing arguments with service principal environment authentication""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with service principal environment auth args = parser.parse_args(["m365", "--sp-env-auth"]) assert args.az_cli_auth is False assert not hasattr(args, "env_auth") assert args.sp_env_auth is True assert args.browser_auth is False assert args.certificate_auth is False def test_real_argument_parsing_browser_auth_with_tenant_id(self): """Test parsing arguments with browser authentication and tenant ID""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with browser auth and tenant ID args = parser.parse_args( [ "m365", "--browser-auth", "--tenant-id", "12345678-1234-5678-abcd-123456789012", ] ) assert args.az_cli_auth is False assert not hasattr(args, "env_auth") assert args.sp_env_auth is False assert args.browser_auth is True assert args.certificate_auth is False assert args.tenant_id == "12345678-1234-5678-abcd-123456789012" def test_real_argument_parsing_certificate_auth(self): """Test parsing arguments with certificate authentication""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with certificate auth args = parser.parse_args(["m365", "--certificate-auth"]) assert args.az_cli_auth is False assert not hasattr(args, "env_auth") assert args.sp_env_auth is False assert args.browser_auth is False assert args.certificate_auth is True def test_real_argument_parsing_with_init_modules(self): """Test parsing arguments with init modules flag""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with init modules args = parser.parse_args(["m365", "--az-cli-auth", "--init-modules"]) assert args.az_cli_auth is True assert args.init_modules is True def test_real_argument_parsing_with_different_regions(self): """Test parsing arguments with different region options""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Test M365Global (default) args = parser.parse_args(["m365", "--az-cli-auth"]) assert args.region == "M365Global" # Test M365China args = parser.parse_args(["m365", "--az-cli-auth", "--region", "M365China"]) assert args.region == "M365China" # Test M365USGovernment args = parser.parse_args( ["m365", "--az-cli-auth", "--region", "M365USGovernment"] ) assert args.region == "M365USGovernment" def test_real_argument_parsing_no_authentication_defaults(self): """Test parsing arguments without any authentication flags (should have defaults)""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments without explicit auth (defaults should apply) args = parser.parse_args(["m365"]) assert args.az_cli_auth is False assert not hasattr(args, "env_auth") assert args.sp_env_auth is False assert args.browser_auth is False assert args.certificate_auth is False assert args.tenant_id is None assert args.init_modules is False assert args.region == "M365Global" def test_real_argument_parsing_complete_configuration(self): """Test parsing arguments with all non-exclusive options""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with complete configuration args = parser.parse_args( [ "m365", "--browser-auth", "--tenant-id", "12345678-1234-5678-abcd-123456789012", "--init-modules", "--region", "M365USGovernment", ] ) assert args.browser_auth is True assert args.tenant_id == "12345678-1234-5678-abcd-123456789012" assert args.init_modules is True assert args.region == "M365USGovernment" def test_mutually_exclusive_authentication_enforcement(self): """Test that authentication methods are mutually exclusive""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # This should raise SystemExit due to mutually exclusive group try: parser.parse_args(["m365", "--az-cli-auth", "--sp-env-auth"]) assert False, "Expected SystemExit due to mutually exclusive arguments" except SystemExit: # This is expected pass def test_tenant_id_without_arguments(self): """Test that tenant-id can be specified without an argument (optional value)""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with tenant-id but no value (should be None due to nargs="?") args = parser.parse_args(["m365", "--az-cli-auth", "--tenant-id"]) assert args.tenant_id is None def test_certificate_path_argument_configuration(self): """Test that certificate_path argument is properly configured""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with certificate-path args = parser.parse_args( ["m365", "--certificate-auth", "--certificate-path", "/path/to/cert.pem"] ) assert args.certificate_auth is True assert args.certificate_path == "/path/to/cert.pem" def test_certificate_path_without_value(self): """Test certificate_path argument without value (should be None due to nargs='?')""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse arguments with certificate-path but no value args = parser.parse_args(["m365", "--certificate-auth", "--certificate-path"]) assert args.certificate_auth is True assert args.certificate_path is None def test_certificate_auth_with_certificate_path_integration(self): """Test certificate authentication with certificate path integration""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() common_parser = argparse.ArgumentParser(add_help=False) mock_m365_args = MagicMock() mock_m365_args.subparsers = subparsers mock_m365_args.common_providers_parser = common_parser arguments.init_parser(mock_m365_args) # Parse complete certificate authentication arguments args = parser.parse_args( [ "m365", "--certificate-auth", "--certificate-path", "/home/user/cert.pem", "--tenant-id", "12345678-1234-1234-1234-123456789012", ] ) assert args.certificate_auth is True assert args.certificate_path == "/home/user/cert.pem" assert args.tenant_id == "12345678-1234-1234-1234-123456789012" assert args.az_cli_auth is False assert not hasattr(args, "env_auth") assert args.sp_env_auth is False assert args.browser_auth is False