c7d3ec57c3
Commitd2217531(google-labs-jules SSRF fix) catastrophically deleted 11,500+ lines across 100+ files while fixing an unrelated IMAP issue. Restored from d2217531^ (pre-bad-commit state): Deleted files (fully restored): - app/api/{automation,classification_rules,comments,sharing}.py - app/middleware/upload_rate_limit.py - app/tasks/{automation_tasks,classify_document}.py - app/utils/{automation_hooks,classification_rules}.py - docs/AppleAppStoreCompliance.md - frontend/input.css, package.json, package-lock.json, tailwind.config.js - frontend/static/js/{annotations,claim,comments,sharing}.js - frontend/templates/{admin_connections,file_annotations,file_summary}.html - tests/{test_api_files_comprehensive,test_auth_extended,test_sharing, test_comments,test_connections,test_imap_profiles,test_api_sessions, test_automation,test_classification_rules,test_api_advanced_filters, test_api_classification_rules,test_upload_rate_limit,test_api_dropbox, test_classify_document,test_comments_ui,test_upload_to_icloud, test_api_onedrive_comprehensive,test_frontend_build,test_sentry, test_diagnostic,test_database,test_views_dropbox,test_local_auth}.py Truncated files (content restored): - app/{auth,config,main,models,celery_worker,database}.py - app/api/{__init__,api_tokens,diagnostic,dropbox,files,google_drive, integrations,local_auth,mobile,onedrive,pipelines,qr_auth, settings,url_upload}.py - app/middleware/upload_rate_limit.py - app/tasks/upload_to_nextcloud.py - app/utils/{allowed_types,settings_service,settings_sync,user_scope,webhook}.py - app/views/{base,dropbox,files,google_drive,onedrive,settings}.py - docs/{API,AuthenticationSetup,ConfigurationGuide,DatabaseConfiguration, DeploymentGuide,DropboxSetup,GoogleDriveSetup,KubernetesDeployment, MobileApp,OneDriveSetup,ProductionReadiness,SentrySetup, SocialLoginSetup,UserGuide}.md - frontend/static/{js/upload.js,styles.css} - frontend/templates/{api_tokens,base,devices,dropbox,dropbox_callback, file_view,files,google_drive,onedrive,onedrive_callback, signup}.html - frontend/translations/en.json - migrations/env.py - tests/{conftest,test_api_integrations,test_api_mobile,test_api_settings, test_api_tokens,test_audit_logs,test_duplicates,test_imap_tasks, test_setup_wizard,test_views_files_comprehensive}.py Security fixes kept from post-d2217531 commits: - app/utils/network.py: DNS SSRF fail-secure fix (06b0fced) - app/utils/file_operations.py: path traversal fix (1018ea17) - tests/test_imap_tasks.py: re-applied 4 is_private_ip mock patches Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/51133dd8-9bec-41ab-aa10-3de753634187
146 lines
6.4 KiB
Python
146 lines
6.4 KiB
Python
"""Tests for frontend build configuration and Docker build consistency.
|
|
|
|
Validates that the frontend build toolchain (Tailwind CSS) is correctly
|
|
configured in package.json and that the Dockerfile installs all required
|
|
dependencies for the build step.
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
# Resolve the project root from the test file location
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
FRONTEND_DIR = PROJECT_ROOT / "frontend"
|
|
DOCKERFILE_PATH = PROJECT_ROOT / "Dockerfile"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFrontendPackageJson:
|
|
"""Validate frontend/package.json structure and scripts."""
|
|
|
|
def test_package_json_exists(self) -> None:
|
|
"""package.json must exist in the frontend directory."""
|
|
pkg_path = FRONTEND_DIR / "package.json"
|
|
assert pkg_path.exists(), "frontend/package.json not found"
|
|
|
|
def test_package_json_is_valid_json(self) -> None:
|
|
"""package.json must be parseable JSON."""
|
|
pkg_path = FRONTEND_DIR / "package.json"
|
|
data = json.loads(pkg_path.read_text(encoding="utf-8"))
|
|
assert isinstance(data, dict), "package.json must be a JSON object"
|
|
|
|
def test_build_script_defined(self) -> None:
|
|
"""A 'build' script must be defined in package.json."""
|
|
pkg_path = FRONTEND_DIR / "package.json"
|
|
data = json.loads(pkg_path.read_text(encoding="utf-8"))
|
|
scripts = data.get("scripts", {})
|
|
assert "build" in scripts, "Missing 'build' script in package.json"
|
|
|
|
def test_build_script_uses_tailwindcss(self) -> None:
|
|
"""The build script must invoke the tailwindcss CLI."""
|
|
pkg_path = FRONTEND_DIR / "package.json"
|
|
data = json.loads(pkg_path.read_text(encoding="utf-8"))
|
|
build_cmd = data["scripts"]["build"]
|
|
assert "tailwindcss" in build_cmd, f"Build script does not reference tailwindcss: {build_cmd}"
|
|
|
|
def test_tailwindcss_listed_as_dependency(self) -> None:
|
|
"""tailwindcss must be listed in dependencies or devDependencies."""
|
|
pkg_path = FRONTEND_DIR / "package.json"
|
|
data = json.loads(pkg_path.read_text(encoding="utf-8"))
|
|
deps = data.get("dependencies", {})
|
|
dev_deps = data.get("devDependencies", {})
|
|
all_deps = {**deps, **dev_deps}
|
|
assert "tailwindcss" in all_deps, "tailwindcss is not listed in dependencies or devDependencies"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFrontendBuildAssets:
|
|
"""Validate that required frontend build source files exist."""
|
|
|
|
def test_input_css_exists(self) -> None:
|
|
"""The Tailwind CSS input file must exist."""
|
|
input_css = FRONTEND_DIR / "input.css"
|
|
assert input_css.exists(), "frontend/input.css not found"
|
|
|
|
def test_input_css_has_tailwind_directives(self) -> None:
|
|
"""input.css must include Tailwind CSS directives."""
|
|
input_css = FRONTEND_DIR / "input.css"
|
|
content = input_css.read_text(encoding="utf-8")
|
|
assert "@tailwind base" in content, "Missing @tailwind base directive"
|
|
assert "@tailwind components" in content, "Missing @tailwind components directive"
|
|
assert "@tailwind utilities" in content, "Missing @tailwind utilities directive"
|
|
|
|
def test_tailwind_config_exists(self) -> None:
|
|
"""tailwind.config.js must exist in the frontend directory."""
|
|
config_path = FRONTEND_DIR / "tailwind.config.js"
|
|
assert config_path.exists(), "frontend/tailwind.config.js not found"
|
|
|
|
def test_package_lock_exists(self) -> None:
|
|
"""package-lock.json must exist for reproducible installs."""
|
|
lock_path = FRONTEND_DIR / "package-lock.json"
|
|
assert lock_path.exists(), "frontend/package-lock.json not found"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestDockerfileFrontendBuilder:
|
|
"""Validate the Dockerfile frontend-builder stage installs build dependencies."""
|
|
|
|
def test_dockerfile_exists(self) -> None:
|
|
"""Production Dockerfile must exist at the project root."""
|
|
assert DOCKERFILE_PATH.exists(), "Dockerfile not found at project root"
|
|
|
|
def test_dockerfile_has_frontend_builder_stage(self) -> None:
|
|
"""Dockerfile must define a frontend-builder stage."""
|
|
content = DOCKERFILE_PATH.read_text(encoding="utf-8")
|
|
assert "AS frontend-builder" in content, "Dockerfile does not define a frontend-builder stage"
|
|
|
|
def test_dockerfile_npm_ci_does_not_omit_dev(self) -> None:
|
|
"""npm ci must NOT use --omit=dev in the frontend-builder stage.
|
|
|
|
The tailwindcss CLI is a devDependency required at build time.
|
|
Using --omit=dev would skip installing it, causing the build to
|
|
fail with 'tailwindcss: not found'.
|
|
"""
|
|
content = DOCKERFILE_PATH.read_text(encoding="utf-8")
|
|
|
|
# Extract the frontend-builder stage content
|
|
# Look for the stage start and the next stage (or end of file)
|
|
stage_pattern = re.compile(
|
|
r"FROM\s+\S+\s+AS\s+frontend-builder\b(.*?)(?=FROM\s|\Z)",
|
|
re.DOTALL,
|
|
)
|
|
match = stage_pattern.search(content)
|
|
assert match is not None, "Could not find frontend-builder stage in Dockerfile"
|
|
|
|
stage_content = match.group(1)
|
|
assert "--omit=dev" not in stage_content, (
|
|
"Dockerfile frontend-builder stage uses 'npm ci --omit=dev' which "
|
|
"excludes tailwindcss (a devDependency) needed for the build step. "
|
|
"Use 'npm ci' instead to install all dependencies."
|
|
)
|
|
|
|
def test_dockerfile_runs_npm_build(self) -> None:
|
|
"""Dockerfile frontend-builder stage must run npm run build."""
|
|
content = DOCKERFILE_PATH.read_text(encoding="utf-8")
|
|
|
|
stage_pattern = re.compile(
|
|
r"FROM\s+\S+\s+AS\s+frontend-builder\b(.*?)(?=FROM\s|\Z)",
|
|
re.DOTALL,
|
|
)
|
|
match = stage_pattern.search(content)
|
|
assert match is not None, "Could not find frontend-builder stage in Dockerfile"
|
|
|
|
stage_content = match.group(1)
|
|
assert "npm run build" in stage_content, "Dockerfile frontend-builder stage does not run 'npm run build'"
|
|
|
|
def test_dockerfile_copies_compiled_css(self) -> None:
|
|
"""Dockerfile must copy the compiled styles.css from the frontend-builder stage."""
|
|
content = DOCKERFILE_PATH.read_text(encoding="utf-8")
|
|
assert "COPY --from=frontend-builder" in content, (
|
|
"Dockerfile does not copy assets from the frontend-builder stage"
|
|
)
|
|
assert "styles.css" in content, "Dockerfile does not reference the compiled styles.css"
|