Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically add sha to static artifacts #141

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions mountaineer/__tests__/client_builder/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from mountaineer.actions import sideeffect
from mountaineer.app import AppController
from mountaineer.client_builder.builder import ClientBuilder
from mountaineer.client_builder.builder import APIBuilder
from mountaineer.controller import ControllerBase
from mountaineer.controller_layout import LayoutControllerBase
from mountaineer.render import RenderBase
Expand Down Expand Up @@ -60,30 +60,30 @@ def simple_app_controller(

@pytest.fixture
def builder(simple_app_controller: AppController):
return ClientBuilder(simple_app_controller)
return APIBuilder(simple_app_controller)


def test_generate_static_files(builder: ClientBuilder):
def test_generate_static_files(builder: APIBuilder):
builder.generate_static_files()


def test_generate_model_definitions(builder: ClientBuilder):
def test_generate_model_definitions(builder: APIBuilder):
builder.generate_model_definitions()


def test_generate_action_definitions(builder: ClientBuilder):
def test_generate_action_definitions(builder: APIBuilder):
builder.generate_action_definitions()


def test_generate_view_definitions(builder: ClientBuilder):
def test_generate_view_definitions(builder: APIBuilder):
builder.generate_link_shortcuts()


def test_generate_link_aggregator(builder: ClientBuilder):
def test_generate_link_aggregator(builder: APIBuilder):
builder.generate_link_aggregator()


def test_generate_link_aggregator_ignores_layout(builder: ClientBuilder):
def test_generate_link_aggregator_ignores_layout(builder: APIBuilder):
class ExampleLayout(LayoutControllerBase):
view_path = "/test.tsx"

Expand All @@ -100,12 +100,12 @@ async def render(self) -> None:
assert "ExampleDetailControllerGetLinks" in global_links


def test_generate_view_servers(builder: ClientBuilder):
def test_generate_view_servers(builder: APIBuilder):
builder.generate_view_servers()


@pytest.mark.parametrize("empty_links", [True, False])
def test_generate_index_file_ignores_empty(builder: ClientBuilder, empty_links: bool):
def test_generate_index_file_ignores_empty(builder: APIBuilder, empty_links: bool):
# Create some stub files. We simulate a case where the links file
# is created but empty
file_contents = "import React from 'react';\n"
Expand Down Expand Up @@ -138,13 +138,13 @@ def test_generate_index_file_ignores_empty(builder: ClientBuilder, empty_links:
]


def test_cache_is_outdated_no_cache(builder: ClientBuilder):
def test_cache_is_outdated_no_cache(builder: APIBuilder):
# No cache
builder.build_cache = None
assert builder.cache_is_outdated() is True


def test_cache_is_outdated_no_existing_data(builder: ClientBuilder, tmp_path: Path):
def test_cache_is_outdated_no_existing_data(builder: APIBuilder, tmp_path: Path):
builder.build_cache = tmp_path

assert builder.cache_is_outdated() is True
Expand All @@ -159,7 +159,7 @@ def test_cache_is_outdated_no_existing_data(builder: ClientBuilder, tmp_path: Pa


def test_cache_is_outdated_existing_data(
builder: ClientBuilder,
builder: APIBuilder,
tmp_path: Path,
home_controller: ExampleHomeController,
detail_controller: ExampleDetailController,
Expand Down Expand Up @@ -198,7 +198,7 @@ def test_cache_is_outdated_existing_data(


def test_cache_is_outdated_url_change(
builder: ClientBuilder,
builder: APIBuilder,
tmp_path: Path,
home_controller: ExampleHomeController,
detail_controller: ExampleDetailController,
Expand Down Expand Up @@ -243,7 +243,7 @@ def test_cache_is_outdated_url_change(


def test_validate_unique_paths_exact_definition(
builder: ClientBuilder,
builder: APIBuilder,
):
"""
Two controllers can't manage the same view path.
Expand All @@ -267,7 +267,7 @@ def render(self) -> None:


def test_validate_unique_paths_conflicting_layout(
builder: ClientBuilder,
builder: APIBuilder,
):
"""
Layouts need to be placed in their own directory. Even if the literal paths
Expand All @@ -291,7 +291,7 @@ def render(self) -> None:


def test_generate_controller_schema_sideeffect_required_attributes(
builder: ClientBuilder,
builder: APIBuilder,
):
"""
Ensure that we treat @sideeffect and @passthrough return models like
Expand Down
27 changes: 27 additions & 0 deletions mountaineer/__tests__/client_compiler/test_compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pathlib import Path

from mountaineer.app import AppController
from mountaineer.client_compiler.compile import ClientCompiler


def test_build_static_metadata(tmpdir: Path):
app = AppController(view_root=tmpdir)
compiler = ClientCompiler(app=app)

# Write test files to the view path to determine if we're able
# to parse the whole file tree
static_dir = compiler.view_root.get_managed_static_dir()

(static_dir / "test_css.css").write_text("CSS_TEXT")

(static_dir / "nested").mkdir(exist_ok=True)
(static_dir / "nested" / "test_nested.css").write_text("CSS_TEXT")

# File contents are the same - shas should be the same as well
metadata = compiler._build_static_metadata()
assert "test_css.css" in metadata.static_artifact_shas
assert "nested/test_nested.css" in metadata.static_artifact_shas
assert (
metadata.static_artifact_shas["test_css.css"]
== metadata.static_artifact_shas["nested/test_nested.css"]
)
206 changes: 205 additions & 1 deletion mountaineer/__tests__/test_render.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from mountaineer.client_compiler.build_metadata import BuildMetadata
from mountaineer.render import (
LinkAttribute,
MetaAttribute,
Expand Down Expand Up @@ -90,7 +91,159 @@
],
)
def test_build_header(metadata: Metadata, expected_tags: list[str]):
assert metadata.build_header() == expected_tags
assert metadata.build_header(build_metadata=None) == expected_tags


@pytest.mark.parametrize(
"metadata, build_metadata, expected_tags",
[
# Test static file with matching SHA
(
Metadata(
links=[
LinkAttribute(
rel="stylesheet",
href="/static/css/style.css",
add_static_sha=True,
)
]
),
BuildMetadata(static_artifact_shas={"css/style.css": "abc123"}),
['<link rel="stylesheet" href="/static/css/style.css?sha=abc123" />'],
),
# Test multiple static files with matching SHAs
(
Metadata(
links=[
LinkAttribute(
rel="stylesheet",
href="/static/css/style.css",
add_static_sha=True,
),
LinkAttribute(
rel="stylesheet",
href="/static/css/other.css",
add_static_sha=True,
),
]
),
BuildMetadata(
static_artifact_shas={
"css/style.css": "abc123",
"css/other.css": "def456",
}
),
[
'<link rel="stylesheet" href="/static/css/style.css?sha=abc123" />',
'<link rel="stylesheet" href="/static/css/other.css?sha=def456" />',
],
),
# Test static file without matching SHA (should not modify URL)
(
Metadata(
links=[
LinkAttribute(
rel="stylesheet",
href="/static/css/nonexistent.css",
add_static_sha=True,
)
]
),
BuildMetadata(static_artifact_shas={"css/style.css": "abc123"}),
['<link rel="stylesheet" href="/static/css/nonexistent.css" />'],
),
# Test mix of static and non-static files
(
Metadata(
links=[
LinkAttribute(
rel="stylesheet",
href="/static/css/style.css",
add_static_sha=True,
),
LinkAttribute(
rel="stylesheet", href="/css/external.css", add_static_sha=True
),
]
),
BuildMetadata(static_artifact_shas={"css/style.css": "abc123"}),
[
'<link rel="stylesheet" href="/static/css/style.css?sha=abc123" />',
'<link rel="stylesheet" href="/css/external.css" />',
],
),
# Test with add_static_sha=False
(
Metadata(
links=[
LinkAttribute(
rel="stylesheet",
href="/static/css/style.css",
add_static_sha=False,
)
]
),
BuildMetadata(static_artifact_shas={"css/style.css": "abc123"}),
['<link rel="stylesheet" href="/static/css/style.css" />'],
),
# Test with no build_metadata
(
Metadata(
links=[
LinkAttribute(
rel="stylesheet",
href="/static/css/style.css",
add_static_sha=True,
)
]
),
None,
['<link rel="stylesheet" href="/static/css/style.css" />'],
),
# Test with existing query parameters
(
Metadata(
links=[
LinkAttribute(
rel="stylesheet",
href="/static/css/style.css?v=1.0",
add_static_sha=True,
)
]
),
BuildMetadata(static_artifact_shas={"css/style.css": "abc123"}),
['<link rel="stylesheet" href="/static/css/style.css?v=1.0&sha=abc123" />'],
),
# Test with mixed attributes and SHA
(
Metadata(
links=[
LinkAttribute(
rel="stylesheet",
href="/static/css/style.css",
add_static_sha=True,
optional_attributes={
"media": "screen",
"crossorigin": "anonymous",
},
)
]
),
BuildMetadata(static_artifact_shas={"css/style.css": "abc123"}),
[
'<link rel="stylesheet" href="/static/css/style.css?sha=abc123" media="screen" crossorigin="anonymous" />'
],
),
],
)
def test_build_header_with_sha(
metadata: Metadata, build_metadata: BuildMetadata | None, expected_tags: list[str]
):
"""
Test the SHA addition logic for static files in the build_header method.

"""
assert metadata.build_header(build_metadata=build_metadata) == expected_tags


COMPLEX_METADATA = Metadata(
Expand Down Expand Up @@ -177,3 +330,54 @@ def test_merge_metadatas(metadatas: list[Metadata], expected_metadata: Metadata)
metadata = metadata.merge(other_metadata)

assert metadata == expected_metadata


@pytest.mark.parametrize(
"initial_url,new_sha,expected_url",
[
# No existing query parameters
("https://example.com/path", "abc123", "https://example.com/path?sha=abc123"),
# Has existing sha parameter
(
"https://example.com/path?sha=old123",
"new456",
"https://example.com/path?sha=new456",
),
# Has other query parameters but no sha
(
"https://example.com/path?param1=value1&param2=value2",
"def789",
"https://example.com/path?param1=value1&param2=value2&sha=def789",
),
# Has multiple query parameters including sha
(
"https://example.com/path?param1=value1&sha=old123&param2=value2",
"ghi012",
"https://example.com/path?param1=value1&sha=ghi012&param2=value2",
),
# URL with special characters
(
"https://example.com/path?param=special+value&sha=old",
"jkl345",
"https://example.com/path?param=special+value&sha=jkl345",
),
# URL with fragment
(
"https://example.com/path?param=value#fragment",
"mno678",
"https://example.com/path?param=value&sha=mno678#fragment",
),
# URL with empty query string
("https://example.com/path?", "pqr901", "https://example.com/path?sha=pqr901"),
# URL with port number
(
"https://example.com:8080/path?param=value",
"stu234",
"https://example.com:8080/path?param=value&sha=stu234",
),
],
)
def test_link_attribute_set_sha(initial_url, new_sha, expected_url):
link = LinkAttribute(rel="test", href=initial_url)
link.set_sha(new_sha)
assert link.href == expected_url
Loading