Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions .github/workflows/perf-light.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: perf-light

on:
pull_request: {}
Comment on lines +3 to +4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Optional style fix for consistency.

The pull_request: {} syntax is valid but pull_request: (no empty dict) is more conventional for workflows that trigger on all PR events.

Apply this diff for consistency:

-  pull_request: {}
+  pull_request:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
pull_request: {}
on:
pull_request:
🧰 Tools
🪛 YAMLlint (1.37.1)

[warning] 3-3: truthy value should be one of [false, true]

(truthy)

🤖 Prompt for AI Agents
.github/workflows/perf-light.yml around lines 3 to 4: replace the unconventional
empty-dict trigger syntax `pull_request: {}` with the more conventional
`pull_request:` (i.e., remove the `{}`) so the workflow uses the standard form
that triggers on all pull request events.


jobs:
k6:
runs-on: ubuntu-latest
timeout-minutes: 15
concurrency:
group: perf-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v4

- name: Install k6
run: |
sudo apt-get update
sudo apt-get install -y k6

- name: Start stack
run: |
cd paform
docker compose --env-file .env.development -f docker-compose.dev.yml up -d --build
Comment on lines +21 to +24

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Avoid cd into non-existent repo directory

Each step in the new perf-light workflow starts with cd paform, but after actions/checkout the runner is already inside the repository root (…/paform). There is no nested paform/paform folder, so the cd command exits with status 1 and, because steps run with set -e, the job fails before the stack is started or any tests execute. Dropping the extra cd (or setting working-directory) is required for the workflow to run.

Useful? React with 👍 / 👎.


- name: Wait for API
run: |
cd paform
for i in {1..60}; do curl -sf http://localhost:8000/healthcheck && break || sleep 2; done

- name: Wait for Frontend
run: |
for i in {1..60}; do curl -sf http://localhost:3000/models/manifest.json && break || sleep 2; done
Comment on lines +21 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding failure handling for healthchecks.

The healthcheck loops retry 60 times with 2-second intervals (2 minutes total), but silently succeed if the service never responds. Consider failing explicitly if the loop exhausts retries.

Apply this diff to fail explicitly:

       - name: Wait for API
         run: |
           cd paform
-          for i in {1..60}; do curl -sf http://localhost:8000/healthcheck && break || sleep 2; done
+          for i in {1..60}; do 
+            curl -sf http://localhost:8000/healthcheck && break || sleep 2
+            if [ $i -eq 60 ]; then echo "API healthcheck failed after 2m"; exit 1; fi
+          done
 
       - name: Wait for Frontend
         run: |
-          for i in {1..60}; do curl -sf http://localhost:3000/models/manifest.json && break || sleep 2; done
+          for i in {1..60}; do 
+            curl -sf http://localhost:3000/models/manifest.json && break || sleep 2
+            if [ $i -eq 60 ]; then echo "Frontend manifest check failed after 2m"; exit 1; fi
+          done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Start stack
run: |
cd paform
docker compose --env-file .env.development -f docker-compose.dev.yml up -d --build
- name: Wait for API
run: |
cd paform
for i in {1..60}; do curl -sf http://localhost:8000/healthcheck && break || sleep 2; done
- name: Wait for Frontend
run: |
for i in {1..60}; do curl -sf http://localhost:3000/models/manifest.json && break || sleep 2; done
- name: Wait for API
run: |
cd paform
for i in {1..60}; do
curl -sf http://localhost:8000/healthcheck && break || sleep 2
if [ $i -eq 60 ]; then echo "API healthcheck failed after 2m"; exit 1; fi
done
- name: Wait for Frontend
run: |
for i in {1..60}; do
curl -sf http://localhost:3000/models/manifest.json && break || sleep 2
if [ $i -eq 60 ]; then echo "Frontend manifest check failed after 2m"; exit 1; fi
done
🤖 Prompt for AI Agents
.github/workflows/perf-light.yml lines 21-33: the two healthcheck loops
currently retry but silently continue if the service never becomes healthy;
modify each block so that after the retry loop finishes without success it
prints a clear error message and exits non-zero (e.g., echo "Service X did not
become ready" and exit 1), ensuring the workflow step fails when retries are
exhausted.


- name: Seed backend for perf
env:
BASE_URL: http://localhost:8000
run: |
cd paform
python - <<'PY'
import json
import os
import urllib.error
import urllib.request

BASE_URL = os.environ.get("BASE_URL", "http://localhost:8000")

def post(path: str, payload: dict) -> dict:
req = urllib.request.Request(
f"{BASE_URL}{path}",
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", "ignore")
raise SystemExit(f"Seed request failed ({exc.code}): {detail}")

material = post(
"/api/materials/",
{"name": "Walnut", "texture_url": None, "cost_per_sq_ft": 12.5},
)
material_id = material.get("id")
if not material_id:
raise SystemExit("Material creation failed; missing id")

post(
"/api/modules/",
{
"name": "Base600",
"width": 600.0,
"height": 720.0,
"depth": 580.0,
"base_price": 100.0,
"material_id": material_id,
},
)
PY

- name: Run k6 light profile
env:
BASE_URL: http://localhost:8000
FRONTEND_BASE_URL: http://localhost:3000
run: |
cd paform
k6 run --summary-export k6-summary.json tests/perf/k6-quote-cnc.js

- name: Upload k6 summary
if: always()
uses: actions/upload-artifact@v4
with:
name: k6-summary
path: paform/k6-summary.json

- name: Shutdown stack
if: always()
run: |
cd paform
docker compose --env-file .env.development -f docker-compose.dev.yml down
37 changes: 37 additions & 0 deletions backend/tests/test_error_envelopes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

from fastapi.testclient import TestClient

from api.main import app


client = TestClient(app)


def _is_error_shape(payload: dict) -> bool:
if not isinstance(payload, dict):
return False
if payload.get('ok') is not False:
return False
error = payload.get('error')
return isinstance(error, dict) and 'code' in error and 'message' in error


def test_quote_invalid_payload_envelope() -> None:
response = client.post(
'/api/quote/generate',
data='not-json',
headers={'Content-Type': 'application/json'},
)
assert response.status_code in (400, 422)
assert _is_error_shape(response.json())


def test_cnc_invalid_payload_envelope() -> None:
response = client.post(
'/api/cnc/export',
data='not-json',
headers={'Content-Type': 'application/json'},
)
assert response.status_code in (400, 422)
assert _is_error_shape(response.json())
Loading
Loading