60222a7d91
## Summary
Fixes **TRU-128** ("Jeg kan ikke fakturere") — a customer in
#afdelingsansvarlige could not invoice because the customer-facing
invoice PUT endpoint returned a misleading 400 error.
## Root cause
`PUT /collected-invoices` in
`services/nginx/app/routes/userInvoicesRoute.php` had two related bugs:
1. **Misleading error message** — the 'both fields missing' guard
errored with
`'Missing required parameters: po_number, closed_at'`, which reads as
if BOTH fields are required. The actual condition (`&&`) only fires
when neither is set, so only one is required. Customers who tried
different combinations kept getting the same error and concluded the
system was broken.
2. **Inconsistent `closed_at` clearing** — the 'forbidden closed_at for
non-superusers' guard fired for ANY present `closed_at` key,
including `null` and `""`. That blocked customers from CLEARING a
previously-set `closed_at`, even though the handler further down
already nulls the field when it receives an empty value.
## Fix
- Reword the missing-fields error to state the actual contract:
*"At least one of po_number or closed_at must be provided"*.
- Narrow the forbidden guard to *non-empty* `closed_at`, so customers
can still pass `null` / `""` to clear a previously-set value.
The clear-on-null/empty logic further down in the handler is unchanged
— the guard now matches it.
## Test
`tests/Unit/Invoicing/UserCollectedInvoiceUpdateRouteValidationTest.php`
- Locks in the new error message.
- Locks in the new `$closed_at_is_non_empty` guard shape with the
`if (self::isParametersSet(['closed_at'])) { ... }` pre-check.
- Locks in the regression: the previous 'any present closed_at -> 403'
pattern is explicitly asserted to be absent.
## Files changed
- `services/nginx/app/routes/userInvoicesRoute.php`
-
`services/nginx/app/tests/Unit/Invoicing/UserCollectedInvoiceUpdateRouteValidationTest.php`
## Refs
- TRU-128
- Slack: #afdelingsansvarlige (kunde-rapport)
---------
Co-authored-by: OpenClaw Backend Agent <agent@openclaw.ai>
Co-authored-by: Jeppe B <jeppe@copenhagentruckwash.io>
Co-authored-by: jeppemaxclaw[bot] <bot@jeppemaxclaw.local>
Co-authored-by: Bugfix Subagent <bugfix-subagent@openclaw.local>
81 lines
2.2 KiB
Bash
81 lines
2.2 KiB
Bash
#!/usr/bin/env bash
|
|
# Generic smoke test for any deployed app.
|
|
#
|
|
# Usage: ./scripts/smoke-test.sh [base_url]
|
|
# Default: https://staging.truckwash.io
|
|
#
|
|
# Required env vars (set by GitHub Action):
|
|
# SMOKE_BASE_URL - base URL to test (default: https://staging.truckwash.io)
|
|
#
|
|
# Optional env vars:
|
|
# SMOKE_TOKEN - bearer token for authenticated checks
|
|
# SMOKE_TIMEOUT - curl timeout in seconds (default: 10)
|
|
#
|
|
# Exits 0 on all-pass, 1 on any failure.
|
|
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${SMOKE_BASE_URL:-${1:-https://staging.truckwash.io}}"
|
|
TIMEOUT="${SMOKE_TIMEOUT:-10}"
|
|
|
|
# Color codes
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
FAIL=0
|
|
|
|
check() {
|
|
local name="$1"
|
|
local url="$2"
|
|
local expected="${3:-200}"
|
|
local method="${4:-GET}"
|
|
|
|
local status
|
|
status=$(curl -s -o /dev/null -w "%{http_code}" -X "$method" --max-time "$TIMEOUT" "$url" || echo "000")
|
|
|
|
if [[ "$status" =~ ^($expected)$ ]] || [[ "$expected" == "2xx" && "$status" =~ ^2 ]]; then
|
|
echo -e " ${GREEN}✓${NC} $name ($status) — $url"
|
|
else
|
|
echo -e " ${RED}✗${NC} $name (expected $expected, got $status) — $url"
|
|
FAIL=1
|
|
fi
|
|
}
|
|
|
|
echo "Smoke test against $BASE_URL"
|
|
echo " (timeout ${TIMEOUT}s per check)"
|
|
echo
|
|
|
|
# === Health endpoints (universal) ===
|
|
check "health check" "$BASE_URL/healthz" "2xx"
|
|
check "ping" "$BASE_URL/api/ping" "2xx"
|
|
|
|
# === Authentication (should NOT 500) ===
|
|
check "login page" "$BASE_URL/login" "2xx"
|
|
|
|
# === Public endpoints (api repo) ===
|
|
check "customer list (public schema)" "$BASE_URL/api/customer" "2xx"
|
|
check "kundeoprettelse form" "$BASE_URL/kundeoprettelse" "2xx"
|
|
|
|
# === Public endpoints (pleno-vue) ===
|
|
check "self-serve program picker" "$BASE_URL/self-serve/program" "2xx"
|
|
check "vehicle step" "$BASE_URL/self-serve/vehicle" "2xx"
|
|
|
|
# === Custom 404 should not 500 ===
|
|
check "404 page" "$BASE_URL/this-route-does-not-exist" "404"
|
|
|
|
# === Optional authenticated check ===
|
|
if [ -n "${SMOKE_TOKEN:-}" ]; then
|
|
check "auth check" "$BASE_URL/api/me" "2xx"
|
|
fi
|
|
|
|
echo
|
|
if [ "$FAIL" -eq 0 ]; then
|
|
echo -e "${GREEN}✓ All smoke tests passed${NC}"
|
|
exit 0
|
|
else
|
|
echo -e "${RED}✗ Some smoke tests failed${NC}"
|
|
exit 1
|
|
fi
|