Add pytest suite and Gitea Actions CI/CD pipeline
CI / build-and-scan (pull_request) Successful in 56s
CI / build-and-scan (pull_request) Successful in 56s
83 tests (94% coverage of src/py) covering the builder pipeline (gallery sync, HTML/CSS generation, image processing, full site build) and the Flask webui (routes, uploads, theme/font management). Dockerfile gains a `test` stage (pytest) between the wheel builder and the prod image, and pins the alpine base to a full patch tag so Renovate can classify updates. CI workflow builds, smoke-tests, runs the suite, scans with Trivy, and publishes/releases on merge to main, following the same pipeline already running on adguard-cidre.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import io
|
||||
|
||||
from src.py.webui.upload import allowed_file, save_uploaded_file
|
||||
|
||||
|
||||
def test_allowed_file_accepts_known_extensions():
|
||||
for name in ["a.jpg", "a.JPEG", "a.png", "a.webp"]:
|
||||
assert allowed_file(name) is True
|
||||
|
||||
|
||||
def test_allowed_file_rejects_unknown_or_missing_extension():
|
||||
assert allowed_file("a.gif") is False
|
||||
assert allowed_file("noextension") is False
|
||||
|
||||
|
||||
def test_save_uploaded_file_sanitizes_filename(tmp_path):
|
||||
class FakeFile:
|
||||
filename = "../../evil.jpg"
|
||||
|
||||
def save(self, path):
|
||||
self.saved_path = path
|
||||
|
||||
folder = tmp_path / "gallery"
|
||||
fake_file = FakeFile()
|
||||
|
||||
result = save_uploaded_file(fake_file, folder)
|
||||
|
||||
assert result == "evil.jpg"
|
||||
assert fake_file.saved_path == folder / "evil.jpg"
|
||||
assert folder.exists()
|
||||
|
||||
|
||||
def test_upload_photo_invalid_section(client):
|
||||
resp = client.post(
|
||||
"/api/invalid/upload",
|
||||
data={"files": (io.BytesIO(b"data"), "a.jpg")},
|
||||
content_type="multipart/form-data",
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
def test_upload_photo_no_files_key(client):
|
||||
resp = client.post("/api/gallery/upload", data={}, content_type="multipart/form-data")
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
def test_upload_photo_skips_disallowed_extensions(client):
|
||||
resp = client.post(
|
||||
"/api/gallery/upload",
|
||||
data={"files": (io.BytesIO(b"data"), "notes.txt")},
|
||||
content_type="multipart/form-data",
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert "No valid files uploaded" in resp.get_json()["error"]
|
||||
|
||||
|
||||
def test_upload_photo_saves_valid_files_and_updates_gallery(client, app_env):
|
||||
resp = client.post(
|
||||
"/api/gallery/upload",
|
||||
data={"files": (io.BytesIO(b"fake-jpg-bytes"), "photo.jpg")},
|
||||
content_type="multipart/form-data",
|
||||
)
|
||||
body = resp.get_json()
|
||||
assert body["status"] == "ok"
|
||||
assert body["uploaded"] == ["photo.jpg"]
|
||||
assert (app_env / "config" / "photos" / "gallery" / "photo.jpg").exists()
|
||||
|
||||
gallery = client.get("/api/gallery").get_json()
|
||||
assert gallery[0]["src"] == "gallery/photo.jpg"
|
||||
|
||||
|
||||
def test_upload_photo_hero_section(client, app_env):
|
||||
resp = client.post(
|
||||
"/api/hero/upload",
|
||||
data={"files": (io.BytesIO(b"fake-jpg-bytes"), "photo.jpg")},
|
||||
content_type="multipart/form-data",
|
||||
)
|
||||
assert resp.get_json()["status"] == "ok"
|
||||
assert (app_env / "config" / "photos" / "hero" / "photo.jpg").exists()
|
||||
|
||||
hero = client.get("/api/hero").get_json()
|
||||
assert hero[0]["src"] == "hero/photo.jpg"
|
||||
Reference in New Issue
Block a user