Explorar o código

fix(docker): copy gcode_viewer assets into the production image (issue #1218)

  The embedded GCode viewer's static assets (gcode_viewer/) were never
  copied into the production Docker image, so /gcode-viewer/ returned a
  bare FastAPI 404 ({"detail":"Not Found"}) and 3D Preview broke for every
  Docker user since the viewer landed in 0.2.4b1. The Vite production
  build doesn't stage the directory either — the dev server serves it via
  a configureServer middleware that's dev-only.

  Dockerfile now copies gcode_viewer/ alongside the React build output.

  Defence in depth: main.py logs an ERROR at startup when
  _gcode_viewer_dir/index.html is missing so future packaging gaps surface
  in docker logs and the support bundle instead of as silent runtime 404s.

  The existing integration test accepted 404 unconditionally
  (assert response.status_code in (200, 404)) so CI never caught the
  missing files. Add test_gcode_viewer_index_served_when_assets_present
  which skips when the directory is intentionally absent (unit-test envs)
  but asserts 200 + non-empty HTML body when the assets do exist on disk —
  so a broken COPY fails CI loudly rather than shipping a broken image.
maziggy hai 3 semanas
pai
achega
a3e09891d1
Modificáronse 4 ficheiros con 49 adicións e 0 borrados
  1. 0 0
      CHANGELOG.md
  2. 9 0
      Dockerfile
  3. 13 0
      backend/app/main.py
  4. 27 0
      backend/tests/integration/test_gcode_viewer.py

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
CHANGELOG.md


+ 9 - 0
Dockerfile

@@ -67,6 +67,15 @@ COPY .git/HEAD ./.git/HEAD
 # Copy built frontend from builder stage
 COPY --from=frontend-builder /app/static ./static
 
+# Copy embedded GCode viewer static assets (PrettyGCode + Bambuddy adapter).
+# Served by the explicit @app.get("/gcode-viewer/{...}") routes in main.py,
+# which resolve files under (static_dir.parent / "gcode_viewer") = /app/gcode_viewer/.
+# Without this COPY the routes return a bare 404 at request time and the 3D
+# Preview iframe shows {"detail":"Not Found"} (see #1218). The directory is
+# vendored third-party JS — the Vite build does NOT stage it into static/,
+# the dev server serves it via a configureServer middleware that's dev-only.
+COPY gcode_viewer/ ./gcode_viewer/
+
 # Create data directories. Ownership is normalised at startup by the
 # entrypoint (chowns to PUID:PGID and drops privileges via gosu before
 # exec'ing the app), so we don't need a chmod 777 hack here — that was

+ 13 - 0
backend/app/main.py

@@ -5180,6 +5180,19 @@ async def serve_sw_register():
 # to the /{full_path:path} catch-all in some Starlette versions).
 _gcode_viewer_dir = (app_settings.static_dir.parent / "gcode_viewer").resolve()
 
+# Surface packaging gaps at startup instead of as silent runtime 404s. If the
+# directory is missing the explicit @app.get("/gcode-viewer/...") routes below
+# return bare HTTPException(404) which renders as {"detail":"Not Found"} in
+# the 3D Preview iframe (#1218) — easy to miss in normal operation, easy to
+# spot if the operator scans the startup log or a support bundle.
+if not (_gcode_viewer_dir / "index.html").is_file():
+    logging.getLogger(__name__).error(
+        "Embedded GCode viewer assets missing at %s — /gcode-viewer/ will return 404 "
+        "and 3D Preview will fail. This indicates a packaging bug; the gcode_viewer/ "
+        "directory must be present alongside static/.",
+        _gcode_viewer_dir,
+    )
+
 
 def _gcode_viewer_response(rel: str) -> FileResponse:
     from fastapi import HTTPException as _HTTPException

+ 27 - 0
backend/tests/integration/test_gcode_viewer.py

@@ -41,6 +41,33 @@ class TestGCodeViewerRouteOrdering:
         # If a body came back it must NOT be the React SPA shell.
         assert b'<div id="root">' not in response.content
 
+    @pytest.mark.asyncio
+    @pytest.mark.integration
+    async def test_gcode_viewer_index_served_when_assets_present(self, async_client: AsyncClient):
+        """GET /gcode-viewer/ must return the PrettyGCode index when the
+        vendored assets are on disk.
+
+        Regression guard for #1218: the production Dockerfile has to copy
+        ``gcode_viewer/`` into the image alongside ``static/``. The previous
+        ``test_gcode_viewer_index_does_not_fall_through_to_spa`` accepted
+        404 unconditionally so a missing COPY never failed CI. This test
+        only runs when the directory is actually present (so it stays a
+        no-op in unit-test environments where the assets are intentionally
+        absent), but when it does run it asserts 200 + a non-empty HTML
+        body so a future packaging regression fails loudly.
+        """
+        from backend.app.main import _gcode_viewer_dir
+
+        index = _gcode_viewer_dir / "index.html"
+        if not index.is_file():
+            pytest.skip(f"gcode_viewer/index.html not present at {index} — skipping packaging assertion")
+        response = await async_client.get("/gcode-viewer/")
+        assert response.status_code == 200, (
+            f"gcode_viewer/index.html exists at {index} but /gcode-viewer/ returned "
+            f"{response.status_code} — route or response wiring is broken."
+        )
+        assert b"PrettyGCode" in response.content or b"<!doctype html>" in response.content.lower()
+
     @pytest.mark.asyncio
     @pytest.mark.integration
     async def test_gcode_viewer_no_trailing_slash_falls_through_to_spa(self, async_client: AsyncClient):

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio