blob: a9c4ffd62555e1697674f808eda59f7c3a5b18d4 [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
import json
import zipfile
from unittest.mock import patch
import pytest
from superset_extensions_cli.cli import app
from tests.utils import assert_file_exists
# Bundle Command Tests
@pytest.mark.cli
@patch("superset_extensions_cli.cli.build")
def test_bundle_command_creates_zip_with_default_name(
mock_build, cli_runner, isolated_filesystem, extension_setup_for_bundling
):
"""Test bundle command creates zip file with default name."""
# Mock the build command to do nothing (we'll set up dist manually)
mock_build.return_value = None
# Setup extension for bundling (this creates the dist structure)
extension_setup_for_bundling(isolated_filesystem)
result = cli_runner.invoke(app, ["bundle"])
assert result.exit_code == 0
assert "✅ Bundle created: test_extension-1.0.0.supx" in result.output
# Verify zip file was created
zip_path = isolated_filesystem / "test_extension-1.0.0.supx"
assert_file_exists(zip_path)
# Verify zip contents
with zipfile.ZipFile(zip_path, "r") as zipf:
file_list = zipf.namelist()
assert "manifest.json" in file_list
assert "frontend/dist/remoteEntry.abc123.js" in file_list
assert "frontend/dist/main.js" in file_list
assert "backend/src/test_extension/__init__.py" in file_list
@pytest.mark.cli
@patch("superset_extensions_cli.cli.build")
def test_bundle_command_with_custom_output_filename(
mock_build, cli_runner, isolated_filesystem, extension_setup_for_bundling
):
"""Test bundle command with custom output filename."""
# Mock the build command
mock_build.return_value = None
extension_setup_for_bundling(isolated_filesystem)
custom_name = "my_custom_bundle.supx"
result = cli_runner.invoke(app, ["bundle", "--output", custom_name])
assert result.exit_code == 0
assert f"✅ Bundle created: {custom_name}" in result.output
# Verify custom-named zip file was created
zip_path = isolated_filesystem / custom_name
assert_file_exists(zip_path)
@pytest.mark.cli
@patch("superset_extensions_cli.cli.build")
def test_bundle_command_with_output_directory(
mock_build, cli_runner, isolated_filesystem, extension_setup_for_bundling
):
"""Test bundle command with output directory."""
# Mock the build command
mock_build.return_value = None
extension_setup_for_bundling(isolated_filesystem)
# Create output directory
output_dir = isolated_filesystem / "output"
output_dir.mkdir()
result = cli_runner.invoke(app, ["bundle", "--output", str(output_dir)])
assert result.exit_code == 0
# Verify zip file was created in output directory
expected_path = output_dir / "test_extension-1.0.0.supx"
assert_file_exists(expected_path)
assert f"✅ Bundle created: {expected_path}" in result.output
@pytest.mark.cli
@patch("superset_extensions_cli.cli.build")
def test_bundle_command_fails_without_manifest(
mock_build, cli_runner, isolated_filesystem
):
"""Test bundle command fails when manifest.json doesn't exist."""
# Mock build to succeed but not create manifest
mock_build.return_value = None
# Create empty dist directory
(isolated_filesystem / "dist").mkdir()
result = cli_runner.invoke(app, ["bundle"])
assert result.exit_code == 1
assert "dist/manifest.json not found" in result.output
@pytest.mark.cli
@patch("superset_extensions_cli.cli.build")
def test_bundle_command_handles_zip_creation_error(
mock_build, cli_runner, isolated_filesystem, extension_setup_for_bundling
):
"""Test bundle command handles zip file creation errors."""
# Mock the build command
mock_build.return_value = None
extension_setup_for_bundling(isolated_filesystem)
# Try to bundle to an invalid location (directory that doesn't exist)
invalid_path = isolated_filesystem / "nonexistent" / "bundle.supx"
with patch("zipfile.ZipFile", side_effect=OSError("Permission denied")):
result = cli_runner.invoke(app, ["bundle", "--output", str(invalid_path)])
assert result.exit_code == 1
assert "Failed to create bundle" in result.output
@pytest.mark.cli
@patch("superset_extensions_cli.cli.build")
def test_bundle_includes_all_files_recursively(
mock_build, cli_runner, isolated_filesystem
):
"""Test that bundle includes all files from dist directory recursively."""
# Mock the build command
mock_build.return_value = None
# Create complex dist structure
dist_dir = isolated_filesystem / "dist"
dist_dir.mkdir(parents=True)
# Manifest
manifest = {
"id": "complex_extension",
"name": "Complex Extension",
"version": "2.1.0",
"permissions": [],
}
(dist_dir / "manifest.json").write_text(json.dumps(manifest))
# Frontend files with nested structure
frontend_dir = dist_dir / "frontend" / "dist"
frontend_dir.mkdir(parents=True)
(frontend_dir / "remoteEntry.xyz789.js").write_text("// entry")
assets_dir = frontend_dir / "assets"
assets_dir.mkdir()
(assets_dir / "style.css").write_text("/* css */")
(assets_dir / "image.png").write_bytes(b"fake image data")
# Backend files with nested structure
backend_dir = dist_dir / "backend" / "src" / "complex_extension"
backend_dir.mkdir(parents=True)
(backend_dir / "__init__.py").write_text("# init")
(backend_dir / "core.py").write_text("# core")
utils_dir = backend_dir / "utils"
utils_dir.mkdir()
(utils_dir / "helpers.py").write_text("# helpers")
result = cli_runner.invoke(app, ["bundle"])
assert result.exit_code == 0
# Verify zip file and contents
zip_path = isolated_filesystem / "complex_extension-2.1.0.supx"
assert_file_exists(zip_path)
with zipfile.ZipFile(zip_path, "r") as zipf:
file_list = set(zipf.namelist())
# Verify all files are included
expected_files = {
"manifest.json",
"frontend/dist/remoteEntry.xyz789.js",
"frontend/dist/assets/style.css",
"frontend/dist/assets/image.png",
"backend/src/complex_extension/__init__.py",
"backend/src/complex_extension/core.py",
"backend/src/complex_extension/utils/helpers.py",
}
assert expected_files.issubset(file_list), (
f"Missing files: {expected_files - file_list}"
)
@pytest.mark.cli
@patch("superset_extensions_cli.cli.build")
def test_bundle_command_short_option(
mock_build, cli_runner, isolated_filesystem, extension_setup_for_bundling
):
"""Test bundle command with short -o option."""
# Mock the build command
mock_build.return_value = None
extension_setup_for_bundling(isolated_filesystem)
result = cli_runner.invoke(app, ["bundle", "-o", "short_option.supx"])
assert result.exit_code == 0
assert "✅ Bundle created: short_option.supx" in result.output
assert_file_exists(isolated_filesystem / "short_option.supx")
@pytest.mark.cli
@pytest.mark.parametrize("output_option", ["--output", "-o"])
@patch("superset_extensions_cli.cli.build")
def test_bundle_command_output_options(
mock_build,
output_option,
cli_runner,
isolated_filesystem,
extension_setup_for_bundling,
):
"""Test bundle command with both long and short output options."""
# Mock the build command
mock_build.return_value = None
extension_setup_for_bundling(isolated_filesystem)
filename = f"test_{output_option.replace('-', '')}.supx"
result = cli_runner.invoke(app, ["bundle", output_option, filename])
assert result.exit_code == 0
assert f"✅ Bundle created: {filename}" in result.output
assert_file_exists(isolated_filesystem / filename)