blob: e3f7e6a139dce3cd7ee41429f534bb9ea540b1ac [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
from unittest.mock import Mock, patch
import pytest
from superset_extensions_cli.cli import app, validate_npm
# Validate Command Tests
@pytest.mark.cli
def test_validate_command_success(cli_runner):
"""Test validate command succeeds when npm is available and valid."""
with patch("superset_extensions_cli.cli.validate_npm") as mock_validate:
result = cli_runner.invoke(app, ["validate"])
assert result.exit_code == 0
assert "✅ Validation successful" in result.output
mock_validate.assert_called_once()
@pytest.mark.cli
def test_validate_command_calls_npm_validation(cli_runner):
"""Test that validate command calls the npm validation function."""
with patch("superset_extensions_cli.cli.validate_npm") as mock_validate:
cli_runner.invoke(app, ["validate"])
mock_validate.assert_called_once()
# Validate NPM Function Tests
@pytest.mark.unit
@patch("shutil.which")
def test_validate_npm_fails_when_npm_not_on_path(mock_which):
"""Test validate_npm fails when npm is not on PATH."""
mock_which.return_value = None
with pytest.raises(SystemExit) as exc_info:
validate_npm()
assert exc_info.value.code == 1
mock_which.assert_called_once_with("npm")
@pytest.mark.unit
@patch("shutil.which")
@patch("subprocess.run")
def test_validate_npm_fails_when_npm_command_fails(mock_run, mock_which):
"""Test validate_npm fails when npm -v command fails."""
mock_which.return_value = "/usr/bin/npm"
mock_run.return_value = Mock(returncode=1, stderr="Command failed")
with pytest.raises(SystemExit) as exc_info:
validate_npm()
assert exc_info.value.code == 1
@pytest.mark.unit
@patch("shutil.which")
@patch("subprocess.run")
def test_validate_npm_fails_when_version_too_low(mock_run, mock_which):
"""Test validate_npm fails when npm version is below minimum."""
mock_which.return_value = "/usr/bin/npm"
mock_run.return_value = Mock(returncode=0, stdout="9.0.0\n", stderr="")
with pytest.raises(SystemExit) as exc_info:
validate_npm()
assert exc_info.value.code == 1
@pytest.mark.unit
@pytest.mark.parametrize(
"npm_version",
[
"10.8.2", # Exact minimum version
"11.0.0", # Higher version
"10.9.0-alpha.1", # Pre-release version higher than minimum
],
)
@patch("shutil.which")
@patch("subprocess.run")
def test_validate_npm_succeeds_with_valid_versions(mock_run, mock_which, npm_version):
"""Test validate_npm succeeds when npm version is valid."""
mock_which.return_value = "/usr/bin/npm"
mock_run.return_value = Mock(returncode=0, stdout=f"{npm_version}\n", stderr="")
# Should not raise SystemExit
validate_npm()
@pytest.mark.unit
@pytest.mark.parametrize(
"npm_version,should_pass",
[
("10.8.2", True), # Exact minimum version
("10.8.1", False), # Slightly lower version
("10.9.0-alpha.1", True), # Pre-release version higher than minimum
("9.9.9", False), # Much lower version
("11.0.0", True), # Much higher version
],
)
@patch("shutil.which")
@patch("subprocess.run")
def test_validate_npm_version_comparison_edge_cases(
mock_run, mock_which, npm_version, should_pass
):
"""Test npm version comparison with edge cases."""
mock_which.return_value = "/usr/bin/npm"
mock_run.return_value = Mock(returncode=0, stdout=f"{npm_version}\n", stderr="")
if should_pass:
# Should not raise SystemExit
validate_npm()
else:
with pytest.raises(SystemExit):
validate_npm()
@pytest.mark.unit
@patch("shutil.which")
@patch("subprocess.run")
def test_validate_npm_handles_file_not_found_exception(mock_run, mock_which):
"""Test validate_npm handles FileNotFoundError gracefully."""
mock_which.return_value = "/usr/bin/npm"
mock_run.side_effect = FileNotFoundError("Test error")
with pytest.raises(SystemExit) as exc_info:
validate_npm()
assert exc_info.value.code == 1
@pytest.mark.unit
@pytest.mark.parametrize(
"exception_type",
[
OSError,
PermissionError,
],
)
@patch("shutil.which")
@patch("subprocess.run")
def test_validate_npm_does_not_catch_other_subprocess_exceptions(
mock_run, mock_which, exception_type
):
"""
Test validate_npm does not catch OSError and PermissionError (they propagate up).
"""
mock_which.return_value = "/usr/bin/npm"
mock_run.side_effect = exception_type("Test error")
# These exceptions should propagate up, not be caught
with pytest.raises(exception_type):
validate_npm()
@pytest.mark.unit
@patch("shutil.which")
@patch("subprocess.run")
def test_validate_npm_with_malformed_version_output_raises_error(mock_run, mock_which):
"""Test validate_npm raises ValueError with malformed version output."""
mock_which.return_value = "/usr/bin/npm"
mock_run.return_value = Mock(returncode=0, stdout="not-a-version\n", stderr="")
# semver.compare will raise ValueError for malformed version
with pytest.raises(ValueError):
validate_npm()
@pytest.mark.unit
@patch("shutil.which")
@patch("subprocess.run")
def test_validate_npm_with_empty_version_output_raises_error(mock_run, mock_which):
"""Test validate_npm raises ValueError with empty version output."""
mock_which.return_value = "/usr/bin/npm"
mock_run.return_value = Mock(returncode=0, stdout="", stderr="")
# semver.compare will raise ValueError for empty version
with pytest.raises(ValueError):
validate_npm()