Files
superset2/superset-extensions-cli/tests/test_templates.py

368 lines
12 KiB
Python

# 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
from pathlib import Path
import pytest
from jinja2 import Environment, FileSystemLoader
@pytest.fixture
def templates_dir():
"""Get the templates directory path."""
return (
Path(__file__).parent.parent / "src" / "superset_extensions_cli" / "templates"
)
@pytest.fixture
def jinja_env(templates_dir):
"""Create a Jinja2 environment for testing templates."""
return Environment(loader=FileSystemLoader(templates_dir))
@pytest.fixture
def template_context():
"""Default template context for testing."""
return {
"publisher": "test-org",
"name": "test-extension",
"display_name": "Test Extension",
"id": "test-org.test-extension",
"npm_name": "@test-org/test-extension",
"mf_name": "testOrg_testExtension",
"backend_package": "test_org-test_extension",
"backend_path": "superset_extensions.test_org.test_extension",
"backend_entry": "superset_extensions.test_org.test_extension.entrypoint",
"version": "0.1.0",
"license": "Apache-2.0",
"include_frontend": True,
"include_backend": True,
}
# Extension JSON Template Tests
@pytest.mark.unit
def test_extension_json_template_renders_with_both_frontend_and_backend(
jinja_env, template_context
):
"""Test extension.json template renders correctly with both frontend and backend."""
template = jinja_env.get_template("extension.json.j2")
rendered = template.render(template_context)
# Parse the rendered JSON to ensure it's valid
parsed = json.loads(rendered)
# Verify basic fields
assert parsed["publisher"] == "test-org"
assert parsed["name"] == "test-extension"
assert parsed["displayName"] == "Test Extension"
assert parsed["version"] == "0.1.0"
assert parsed["license"] == "Apache-2.0"
assert parsed["permissions"] == []
# Verify frontend section is not present (contributions are code-first)
assert "frontend" not in parsed
# Verify no backend section in extension.json (moved to pyproject.toml)
assert "backend" not in parsed
@pytest.mark.unit
@pytest.mark.parametrize(
"include_frontend,include_backend,expected_sections",
[
(True, False, []),
(False, True, []),
(False, False, []),
],
)
def test_extension_json_template_renders_with_different_configurations(
jinja_env, template_context, include_frontend, include_backend, expected_sections
):
"""Test extension.json template renders correctly with different configurations."""
template_context["include_frontend"] = include_frontend
template_context["include_backend"] = include_backend
template = jinja_env.get_template("extension.json.j2")
rendered = template.render(template_context)
parsed = json.loads(rendered)
# Check for expected sections
for section in expected_sections:
assert section in parsed, f"Expected section '{section}' not found"
# Check that unexpected sections are not present
all_sections = ["frontend", "backend"]
for section in all_sections:
if section not in expected_sections:
assert section not in parsed, f"Unexpected section '{section}' found"
# Frontend Package JSON Template Tests
@pytest.mark.unit
def test_frontend_package_json_template_renders_correctly(jinja_env, template_context):
"""Test frontend/package.json template renders correctly."""
template = jinja_env.get_template("frontend/package.json.j2")
rendered = template.render(template_context)
parsed = json.loads(rendered)
# Verify basic package info
assert parsed["name"] == "@test-org/test-extension"
assert parsed["version"] == "0.1.0"
assert parsed["license"] == "Apache-2.0"
assert parsed["private"] is True
# Verify scripts section
assert "scripts" in parsed
scripts = parsed["scripts"]
assert "start" in scripts
assert "build" in scripts
assert "webpack" in scripts["build"]
# Verify dependencies
assert "peerDependencies" in parsed
peer_deps = parsed["peerDependencies"]
assert "@apache-superset/core" in peer_deps
assert "react" in peer_deps
assert "react-dom" in peer_deps
# Verify dev dependencies
assert "devDependencies" in parsed
dev_deps = parsed["devDependencies"]
assert "webpack" in dev_deps
assert "typescript" in dev_deps
# Backend Pyproject TOML Template Tests
@pytest.mark.unit
def test_backend_pyproject_toml_template_renders_correctly(jinja_env, template_context):
"""Test backend/pyproject.toml template renders correctly."""
template = jinja_env.get_template("backend/pyproject.toml.j2")
rendered = template.render(template_context)
# Basic content verification (without full TOML parsing)
assert "test_org-test_extension" in rendered
assert "0.1.0" in rendered
assert "Apache-2.0" in rendered
# Template Rendering with Different Parameters Tests
@pytest.mark.unit
@pytest.mark.parametrize(
"publisher,technical_name,display_name",
[
("test-org", "simple-extension", "Simple Extension"),
("acme", "my-extension-123", "My Extension 123"),
("company", "complex-extension-name-123", "Complex Extension Name 123"),
("pub", "ext", "Ext"),
],
)
def test_template_rendering_with_different_ids(
jinja_env, publisher, technical_name, display_name
):
"""Test templates render correctly with various publisher/name combinations."""
from superset_extensions_cli.utils import (
get_module_federation_name,
kebab_to_snake_case,
)
publisher_snake = kebab_to_snake_case(publisher)
name_snake = kebab_to_snake_case(technical_name)
context = {
"publisher": publisher,
"name": technical_name,
"display_name": display_name,
"id": f"{publisher}.{technical_name}",
"npm_name": f"@{publisher}/{technical_name}",
"mf_name": get_module_federation_name(publisher, technical_name),
"backend_package": f"{publisher_snake}-{name_snake}",
"backend_path": f"superset_extensions.{publisher_snake}.{name_snake}",
"backend_entry": f"superset_extensions.{publisher_snake}.{name_snake}.entrypoint",
"version": "1.0.0",
"license": "MIT",
"include_frontend": True,
"include_backend": True,
}
# Test extension.json template
template = jinja_env.get_template("extension.json.j2")
rendered = template.render(context)
parsed = json.loads(rendered)
assert parsed["publisher"] == publisher
assert parsed["name"] == technical_name
assert parsed["displayName"] == display_name
assert "backend" not in parsed
# Test package.json template
template = jinja_env.get_template("frontend/package.json.j2")
rendered = template.render(context)
parsed = json.loads(rendered)
assert parsed["name"] == f"@{publisher}/{technical_name}"
# Test pyproject.toml template
template = jinja_env.get_template("backend/pyproject.toml.j2")
rendered = template.render(context)
assert f"{publisher_snake}-{name_snake}" in rendered
@pytest.mark.unit
@pytest.mark.parametrize("version", ["0.1.0", "1.0.0", "2.1.3-alpha", "10.20.30"])
def test_template_rendering_with_different_versions(jinja_env, version):
"""Test templates render correctly with various version formats."""
context = {
"publisher": "test-pub",
"name": "test-ext",
"display_name": "Test Extension",
"id": "test-pub.test-ext",
"npm_name": "@test-pub/test-ext",
"mf_name": "testPub_testExt",
"version": version,
"license": "Apache-2.0",
"include_frontend": True,
"include_backend": False,
}
template = jinja_env.get_template("extension.json.j2")
rendered = template.render(context)
parsed = json.loads(rendered)
assert parsed["version"] == version
@pytest.mark.unit
@pytest.mark.parametrize(
"license_type",
[
"Apache-2.0",
"MIT",
"BSD-3-Clause",
"GPL-3.0",
"Custom License",
],
)
def test_template_rendering_with_different_licenses(jinja_env, license_type):
"""Test templates render correctly with various license types."""
context = {
"publisher": "test-pub",
"name": "test-ext",
"display_name": "Test Extension",
"id": "test-pub.test-ext",
"npm_name": "@test-pub/test-ext",
"mf_name": "testPub_testExt",
"backend_package": "test_pub-test_ext",
"backend_path": "superset_extensions.test_pub.test_ext",
"backend_entry": "superset_extensions.test_pub.test_ext.entrypoint",
"version": "1.0.0",
"license": license_type,
"include_frontend": True,
"include_backend": True,
}
# Test extension.json template
template = jinja_env.get_template("extension.json.j2")
rendered = template.render(context)
parsed = json.loads(rendered)
assert parsed["license"] == license_type
# Test package.json template
template = jinja_env.get_template("frontend/package.json.j2")
rendered = template.render(context)
parsed = json.loads(rendered)
assert parsed["license"] == license_type
# Template Validation Tests
@pytest.mark.unit
@pytest.mark.parametrize(
"template_name", ["extension.json.j2", "frontend/package.json.j2"]
)
def test_templates_produce_valid_json(jinja_env, template_context, template_name):
"""Test that all JSON templates produce valid JSON output."""
template = jinja_env.get_template(template_name)
rendered = template.render(template_context)
# This will raise an exception if the JSON is invalid
try:
json.loads(rendered)
except json.JSONDecodeError as e:
pytest.fail(f"Template {template_name} produced invalid JSON: {e}")
@pytest.mark.unit
def test_template_whitespace_handling(jinja_env, template_context):
"""Test that templates handle whitespace correctly and produce clean output."""
template = jinja_env.get_template("extension.json.j2")
rendered = template.render(template_context)
# Should not have excessive empty lines
lines = rendered.split("\n")
empty_line_count = sum(1 for line in lines if line.strip() == "")
# Some empty lines are OK for formatting, but not excessive
assert empty_line_count < len(lines) / 2, (
"Too many empty lines in rendered template"
)
# Should be properly formatted JSON
parsed = json.loads(rendered)
# Re-serialize to check it's valid structure
json.dumps(parsed, indent=2)
@pytest.mark.unit
def test_template_context_edge_cases(jinja_env):
"""Test template rendering with edge case contexts."""
# Test with minimal context
minimal_context = {
"publisher": "min",
"name": "minimal",
"display_name": "Minimal",
"id": "min.minimal",
"npm_name": "@min/minimal",
"mf_name": "min_minimal",
"backend_package": "min-minimal",
"backend_path": "superset_extensions.min.minimal",
"backend_entry": "superset_extensions.min.minimal.entrypoint",
"version": "1.0.0",
"license": "MIT",
"include_frontend": False,
"include_backend": False,
}
template = jinja_env.get_template("extension.json.j2")
rendered = template.render(minimal_context)
parsed = json.loads(rendered)
# Should still be valid JSON with basic fields
assert parsed["publisher"] == "min"
assert parsed["name"] == "minimal"
assert parsed["displayName"] == "Minimal"
assert "frontend" not in parsed
assert "backend" not in parsed