diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be17f77..bf656e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,13 +23,17 @@ jobs: - name: "Install dependencies" run: | - pip3 install flake8 Flask==1.1.2 + pip3 install flake8 Flask==1.1.2 pytest pip3 install -e . - name: "Lint code base" run: | flake8 . + - name: "Run pytest suite" + run: | + python -m pytest tests/ + - name: "Run test suite" run: | cd tests/example_app diff --git a/.gitignore b/.gitignore index cee8217..9ab436d 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,6 @@ coverage.xml *.pot # End of https://www.gitignore.io/api/python,osx + +env/ +.vscode/ diff --git a/flask_static_digest/__init__.py b/flask_static_digest/__init__.py index 6e13b22..6bfbd8b 100644 --- a/flask_static_digest/__init__.py +++ b/flask_static_digest/__init__.py @@ -1,8 +1,7 @@ import json import os -from urllib.parse import urljoin - +from urllib.parse import urljoin, urlparse from flask import url_for as flask_url_for @@ -25,7 +24,8 @@ def init_app(self, app): app.config.setdefault("FLASK_STATIC_DIGEST_GZIP_FILES", True) app.config.setdefault("FLASK_STATIC_DIGEST_HOST_URL", None) - self.host_url = app.config.get("FLASK_STATIC_DIGEST_HOST_URL") + self.host_url, self.prefix = parse_digest_host_url( + app.config.get("FLASK_STATIC_DIGEST_HOST_URL")) self.static_url_path = app.static_url_path self.manifest_path = os.path.join(app.static_folder, @@ -46,8 +46,10 @@ def _load_manifest(self, app): return manifest_dict def _prepend_host_url(self, host, filename): - return urljoin(self.host_url, - "/".join([self.static_url_path, filename])) + parts = list( # removing empty strings + filter(None, [self.prefix, self.static_url_path, filename])) + + return urljoin(self.host_url, "/".join(parts)) def static_url_for(self, endpoint, **values): """ @@ -84,3 +86,28 @@ def static_url_for(self, endpoint, **values): merged_values.get("filename")) else: return flask_url_for(endpoint, **merged_values) + + +def parse_digest_host_url(host_url_prefix): + """ + Detect if host_url_prefix contains a path element and returns a tuple with + the elements (host_part, path_part), for example for + + host_url_prefix = "https://cdn.example.com/myapp/some/path/" + + This function should return: + ('https://cdn.example.com', "/myapp/some/path/") + + :param host_url_prefix: CDN like URL prefix + :type host_url_prefix: str + :return: tuple with the elements (host_part, path_part). + """ + scheme, netloc, path, _, _, _ = urlparse(host_url_prefix) + + if path and path.startswith("/"): + path = path[1:] + + if path and path.endswith("/"): + path = path[:-1] + + return (f"{scheme}://{netloc}", path) diff --git a/tests/test_parse_digest_host_url.py b/tests/test_parse_digest_host_url.py new file mode 100644 index 0000000..9471d46 --- /dev/null +++ b/tests/test_parse_digest_host_url.py @@ -0,0 +1,39 @@ +from flask_static_digest import parse_digest_host_url + +import pytest as pt + + +@pt.mark.parametrize( + "digest_host_url, expected", [ + ( + "https://cdn.example.com", + ("https://cdn.example.com", "") + ), + ( + "https://cdn.example.com/", + ("https://cdn.example.com", "") + ), + ( + "https://cdn.example.com/myapp", + ("https://cdn.example.com", "myapp") + ), + ( + "https://cdn.example.com/myapp/", + ("https://cdn.example.com", "myapp") + ), + ( + "https://cdn.example.com/myapp/anotherdir", + ("https://cdn.example.com", "myapp/anotherdir") + ), + ( + "https://cdn.example.com/myapp/anotherdir/", + ("https://cdn.example.com", "myapp/anotherdir") + ), + ] +) +def test_parse_function(digest_host_url, expected): + assert parse_digest_host_url(digest_host_url) == expected + + +def test_joined_urls(): + pass diff --git a/tests/test_static_url_for.py b/tests/test_static_url_for.py new file mode 100644 index 0000000..24db86e --- /dev/null +++ b/tests/test_static_url_for.py @@ -0,0 +1,82 @@ +from flask import Flask +from flask_static_digest import FlaskStaticDigest +import pytest as pt +import json + + +fake_manifest = { + "hey.png": "hey-HASH.png", + "images/hey.png": "images/hey-HASH.png", + "images/card/hey.png": "images/card/hey-HASH.png" +} + + +def make_app(tmp_path, host_url, static_url_path): + appdir = tmp_path / 'fakeappdir' + appdir.mkdir() + + staticdir = appdir / 'static' + staticdir.mkdir() + + manifest_f = staticdir / 'cache_manifest.json' + manifest_f.write_text(json.dumps(fake_manifest)) + + app = Flask( + __name__, root_path=appdir, static_folder=staticdir, + static_url_path=static_url_path) + app.config['TESTING'] = True + app.config['FLASK_STATIC_DIGEST_HOST_URL'] = host_url + + with app.app_context(): + return app + + +@pt.mark.parametrize( + "host_url, static_url_path, file, expected", [ + ( + "https://cdn.example.com", None, "hey.png", + "https://cdn.example.com/static/hey-HASH.png" + ), + ( + "https://cdn.example.com", None, "images/hey.png", + "https://cdn.example.com/static/images/hey-HASH.png" + ), + ( + "https://cdn.example.com", "/static/something/", "hey.png", + "https://cdn.example.com/static/something/hey-HASH.png" + ), + ( + pt.param( + "https://cdn.example.com", "static/something/", "hey.png", + "https://cdn.example.com/static/something/hey-HASH.png", + marks=pt.mark.xfail(raises=ValueError) + ) + ), + ( + "https://cdn.example.com/myapp", None, "images/hey.png", + "https://cdn.example.com/myapp/static/images/hey-HASH.png" + ), + ( + "https://cdn.example.com/myapp", '/static', "images/hey.png", + "https://cdn.example.com/myapp/static/images/hey-HASH.png" + ), + ( + "https://cdn.example.com/myapp", "/static/something/", "hey.png", + "https://cdn.example.com/myapp/static/something/hey-HASH.png" + ), + ( + "https://cdn.example.com/myapp/anotherdir", None, "images/hey.png", + "https://cdn.example.com/myapp/anotherdir" + "/static/images/hey-HASH.png" + ), + (None, None, "hey.png", "/static/hey-HASH.png"), + (None, '/mystatic/url', "hey.png", "/mystatic/url/hey-HASH.png"), + ] +) +def test_get_url(tmp_path, host_url, static_url_path, file, expected): + app = make_app(tmp_path, host_url, static_url_path) + ext = FlaskStaticDigest(app=app) + + ret = ext.static_url_for('static', filename=file) + + assert ret == expected