Skip to content

Commit

Permalink
Merge pull request #141 from piercefreeman/feature/hash-static-files
Browse files Browse the repository at this point in the history
Automatically add sha to static artifacts
  • Loading branch information
piercefreeman authored Nov 26, 2024
2 parents ba51252 + c4408c6 commit 1260d0b
Show file tree
Hide file tree
Showing 15 changed files with 408 additions and 41 deletions.
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

0 comments on commit 1260d0b

Please sign in to comment.