# 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 pathlib import Path import pytest from superset_extensions_cli.cli import app from tests.utils import ( assert_directory_exists, assert_directory_structure, assert_file_exists, assert_file_structure, assert_json_content, create_test_extension_structure, load_json_file, ) # Init Command Tests @pytest.mark.cli def test_init_creates_extension_with_both_frontend_and_backend( cli_runner, isolated_filesystem, cli_input_both ): """Test that init creates a complete extension with both frontend and backend.""" result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0, f"Command failed with output: {result.output}" assert ( "🎉 Extension Test Extension (ID: test_extension) initialized" in result.output ) # Verify directory structure extension_path = isolated_filesystem / "test_extension" assert_directory_exists(extension_path, "main extension directory") expected_structure = create_test_extension_structure( isolated_filesystem, "test_extension", include_frontend=True, include_backend=True, ) # Check directories assert_directory_structure(extension_path, expected_structure["expected_dirs"]) # Check files assert_file_structure(extension_path, expected_structure["expected_files"]) @pytest.mark.cli def test_init_creates_extension_with_frontend_only( cli_runner, isolated_filesystem, cli_input_frontend_only ): """Test that init creates extension with only frontend components.""" result = cli_runner.invoke(app, ["init"], input=cli_input_frontend_only) assert result.exit_code == 0, f"Command failed with output: {result.output}" extension_path = isolated_filesystem / "test_extension" assert_directory_exists(extension_path) # Should have frontend directory and package.json assert_directory_exists(extension_path / "frontend") assert_file_exists(extension_path / "frontend" / "package.json") # Should NOT have backend directory backend_path = extension_path / "backend" assert not backend_path.exists(), ( "Backend directory should not exist for frontend-only extension" ) @pytest.mark.cli def test_init_creates_extension_with_backend_only( cli_runner, isolated_filesystem, cli_input_backend_only ): """Test that init creates extension with only backend components.""" result = cli_runner.invoke(app, ["init"], input=cli_input_backend_only) assert result.exit_code == 0, f"Command failed with output: {result.output}" extension_path = isolated_filesystem / "test_extension" assert_directory_exists(extension_path) # Should have backend directory and pyproject.toml assert_directory_exists(extension_path / "backend") assert_file_exists(extension_path / "backend" / "pyproject.toml") # Should NOT have frontend directory frontend_path = extension_path / "frontend" assert not frontend_path.exists(), ( "Frontend directory should not exist for backend-only extension" ) @pytest.mark.cli def test_init_creates_extension_with_neither_frontend_nor_backend( cli_runner, isolated_filesystem, cli_input_neither ): """Test that init creates minimal extension with neither frontend nor backend.""" result = cli_runner.invoke(app, ["init"], input=cli_input_neither) assert result.exit_code == 0, f"Command failed with output: {result.output}" extension_path = isolated_filesystem / "test_extension" assert_directory_exists(extension_path) # Should only have extension.json assert_file_exists(extension_path / "extension.json") # Should NOT have frontend or backend directories assert not (extension_path / "frontend").exists() assert not (extension_path / "backend").exists() @pytest.mark.cli @pytest.mark.parametrize( "invalid_name,expected_error", [ ("test-extension", "must be alphanumeric"), ("test extension", "must be alphanumeric"), ("test.extension", "must be alphanumeric"), ("test@extension", "must be alphanumeric"), ("", "must be alphanumeric"), ], ) def test_init_validates_extension_name( cli_runner, isolated_filesystem, invalid_name, expected_error ): """Test that init validates extension names according to regex pattern.""" cli_input = f"{invalid_name}\n0.1.0\nApache-2.0\ny\ny\n" result = cli_runner.invoke(app, ["init"], input=cli_input) assert result.exit_code == 1, ( f"Expected command to fail for invalid name '{invalid_name}'" ) assert expected_error in result.output @pytest.mark.cli def test_init_accepts_numeric_extension_name(cli_runner, isolated_filesystem): """Test that init accepts numeric extension ids like '123'.""" cli_input = "123\n123\n0.1.0\nApache-2.0\ny\ny\n" result = cli_runner.invoke(app, ["init"], input=cli_input) assert result.exit_code == 0, f"Numeric id '123' should be valid: {result.output}" assert Path("123").exists(), "Directory for '123' should be created" @pytest.mark.cli @pytest.mark.parametrize( "valid_id", ["test123", "TestExtension", "test_extension_123", "MyExt_1"] ) def test_init_with_valid_alphanumeric_names(cli_runner, valid_id): """Test that init accepts various valid alphanumeric names.""" with cli_runner.isolated_filesystem(): cli_input = f"{valid_id}\nTest Extension\n0.1.0\nApache-2.0\ny\ny\n" result = cli_runner.invoke(app, ["init"], input=cli_input) assert result.exit_code == 0, ( f"Valid name '{valid_id}' was rejected: {result.output}" ) assert Path(valid_id).exists(), f"Directory for '{valid_id}' was not created" @pytest.mark.cli def test_init_fails_when_directory_already_exists( cli_runner, isolated_filesystem, cli_input_both ): """Test that init fails gracefully when target directory already exists.""" # Create the directory first existing_dir = isolated_filesystem / "test_extension" existing_dir.mkdir() result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 1, "Command should fail when directory already exists" assert "already exists" in result.output @pytest.mark.cli def test_extension_json_content_is_correct( cli_runner, isolated_filesystem, cli_input_both ): """Test that the generated extension.json has the correct content.""" result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0 extension_path = isolated_filesystem / "test_extension" extension_json_path = extension_path / "extension.json" # Verify the JSON structure and values assert_json_content( extension_json_path, { "id": "test_extension", "name": "Test Extension", "version": "0.1.0", "license": "Apache-2.0", "permissions": [], }, ) # Load and verify more complex nested structures content = load_json_file(extension_json_path) # Verify frontend section exists and has correct structure assert "frontend" in content frontend = content["frontend"] assert "contributions" in frontend assert "moduleFederation" in frontend assert frontend["contributions"] == {"commands": [], "views": [], "menus": []} assert frontend["moduleFederation"] == {"exposes": ["./index"]} # Verify backend section exists and has correct structure assert "backend" in content backend = content["backend"] assert "entryPoints" in backend assert "files" in backend assert backend["entryPoints"] == ["test_extension.entrypoint"] assert backend["files"] == ["backend/src/test_extension/**/*.py"] @pytest.mark.cli def test_frontend_package_json_content_is_correct( cli_runner, isolated_filesystem, cli_input_both ): """Test that the generated frontend/package.json has the correct content.""" result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0 extension_path = isolated_filesystem / "test_extension" package_json_path = extension_path / "frontend" / "package.json" # Verify the package.json structure and values assert_json_content( package_json_path, { "name": "test_extension", "version": "0.1.0", "license": "Apache-2.0", }, ) # Verify more complex structures content = load_json_file(package_json_path) assert "scripts" in content assert "build" in content["scripts"] assert "peerDependencies" in content assert "@apache-superset/core" in content["peerDependencies"] @pytest.mark.cli def test_backend_pyproject_toml_is_created( cli_runner, isolated_filesystem, cli_input_both ): """Test that the generated backend/pyproject.toml file is created.""" result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0 extension_path = isolated_filesystem / "test_extension" pyproject_path = extension_path / "backend" / "pyproject.toml" assert_file_exists(pyproject_path, "backend pyproject.toml") # Basic content verification (without parsing TOML for now) content = pyproject_path.read_text() assert "test_extension" in content assert "0.1.0" in content assert "Apache-2.0" in content @pytest.mark.cli def test_init_command_output_messages(cli_runner, isolated_filesystem, cli_input_both): """Test that init command produces expected output messages.""" result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0 output = result.output # Check for expected success messages assert "✅ Created extension.json" in output assert "✅ Created frontend folder structure" in output assert "✅ Created backend folder structure" in output assert "🎉 Extension Test Extension (ID: test_extension) initialized" in output @pytest.mark.cli def test_init_with_custom_version_and_license(cli_runner, isolated_filesystem): """Test init with custom version and license parameters.""" cli_input = "my_extension\nMy Extension\n2.1.0\nMIT\ny\nn\n" result = cli_runner.invoke(app, ["init"], input=cli_input) assert result.exit_code == 0 extension_path = isolated_filesystem / "my_extension" extension_json_path = extension_path / "extension.json" assert_json_content( extension_json_path, { "id": "my_extension", "name": "My Extension", "version": "2.1.0", "license": "MIT", }, ) @pytest.mark.integration @pytest.mark.cli def test_full_init_workflow_integration(cli_runner, isolated_filesystem): """Integration test for the complete init workflow.""" # Test the complete flow with realistic user input cli_input = "awesome_charts\nAwesome Charts\n1.0.0\nApache-2.0\ny\ny\n" result = cli_runner.invoke(app, ["init"], input=cli_input) # Verify success assert result.exit_code == 0 # Verify complete directory structure extension_path = isolated_filesystem / "awesome_charts" expected_structure = create_test_extension_structure( isolated_filesystem, "awesome_charts", include_frontend=True, include_backend=True, ) # Comprehensive structure verification assert_directory_structure(extension_path, expected_structure["expected_dirs"]) assert_file_structure(extension_path, expected_structure["expected_files"]) # Verify all generated files have correct content extension_json = load_json_file(extension_path / "extension.json") assert extension_json["id"] == "awesome_charts" assert extension_json["name"] == "Awesome Charts" assert extension_json["version"] == "1.0.0" assert extension_json["license"] == "Apache-2.0" package_json = load_json_file(extension_path / "frontend" / "package.json") assert package_json["name"] == "awesome_charts" pyproject_content = (extension_path / "backend" / "pyproject.toml").read_text() assert "awesome_charts" in pyproject_content