VLAN group form silently drops negative VLAN ID with no error feedback #5

Closed
opened 2026-07-31 06:07:57 -07:00 by jwed · 3 comments
Owner

Steps

  1. Logged in as superadmin@playwright.test.
  2. Navigated to /app/network/portal (Unifi > Captive Portal & Auth > VLAN Groups).
  3. Filled the "Add VLAN Group" inline form: Name = QA 测试 🎉 emoji unicode, VLAN ID = -5 (set directly via JS on the spinbutton, bypassing the client-side min attribute — a determined user can do this too via devtools or a raw request), Description blank.
  4. Clicked "Add VLAN Group".

Expected

Either the group is created (if negative VLAN IDs are meant to be allowed), or a validation error is shown to the user (banner/inline field error) explaining why it was rejected.

Actual

  • Network tab shows POST /app/network/portal/vlan-groups → 302 (a normal Inertia "redirect back" response, not an error status).
  • After the redirect, the page's Inertia props.errors object is {} (empty) and props.vlanGroups is still [] — so the server silently did not create the group, but also returned no validation error at all.
  • The form just re-renders with the same values still filled in and no banner/inline error message anywhere on the page.
  • Confirmed this isn't a general/env issue: immediately retrying with the same name (ASCII, no unicode) but a valid positive VLAN ID (231) succeeded instantly and the group appeared in the list. So the unicode name isn't the problem — the negative VLAN ID specifically is being rejected server-side with zero user-facing feedback.

This is the same class of bug as the already-known dashboard-oncall#3/#4 "silent validation/rejection" pattern (a Vue page never surfacing $page.props.errors) — worth checking whether central-unifi's VLAN group form/controller has the same gap.

Persona

superadmin@playwright.test

URL

https://central-dev.vancs.org/app/network/portal

Notes

No console errors were logged. Cleaned up: the one successful test group ("QA unicode test 29b", VLAN 231) created while isolating the repro was deleted afterward. Nothing was left in a bad state — the negative-VLAN-ID attempt never actually persisted anything.


Filed automatically by the Claude-driven QA sweep (/opt/qa-agent). The marker below dedupes repeat reports — do not edit it.

## Steps 1. Logged in as `superadmin@playwright.test`. 2. Navigated to `/app/network/portal` (Unifi > Captive Portal & Auth > VLAN Groups). 3. Filled the "Add VLAN Group" inline form: Name = `QA 测试 🎉 emoji unicode`, VLAN ID = `-5` (set directly via JS on the spinbutton, bypassing the client-side `min` attribute — a determined user can do this too via devtools or a raw request), Description blank. 4. Clicked "Add VLAN Group". ## Expected Either the group is created (if negative VLAN IDs are meant to be allowed), or a validation error is shown to the user (banner/inline field error) explaining why it was rejected. ## Actual - Network tab shows `POST /app/network/portal/vlan-groups` → `302` (a normal Inertia "redirect back" response, not an error status). - After the redirect, the page's Inertia `props.errors` object is `{}` (empty) and `props.vlanGroups` is still `[]` — so the server silently did **not** create the group, but also returned no validation error at all. - The form just re-renders with the same values still filled in and no banner/inline error message anywhere on the page. - Confirmed this isn't a general/env issue: immediately retrying with the same name (ASCII, no unicode) but a valid positive VLAN ID (`231`) succeeded instantly and the group appeared in the list. So the unicode name isn't the problem — the negative VLAN ID specifically is being rejected server-side with zero user-facing feedback. This is the same class of bug as the already-known dashboard-oncall#3/#4 "silent validation/rejection" pattern (a Vue page never surfacing `$page.props.errors`) — worth checking whether central-unifi's VLAN group form/controller has the same gap. ## Persona superadmin@playwright.test ## URL https://central-dev.vancs.org/app/network/portal ## Notes No console errors were logged. Cleaned up: the one successful test group ("QA unicode test 29b", VLAN 231) created while isolating the repro was deleted afterward. Nothing was left in a bad state — the negative-VLAN-ID attempt never actually persisted anything. --- _Filed automatically by the Claude-driven QA sweep (`/opt/qa-agent`). The marker below dedupes repeat reports — do not edit it._ <!-- claude-qa-fingerprint: 418b6088b7bf -->
jwed added the e2e-failure label 2026-07-31 06:07:57 -07:00
Author
Owner

Still occurring as of the 2026-07-31 regression-plan sweep (32nd run).

Repro: /app/network/portal (superadmin), Add VLAN Group form. Set VLAN ID to -5
via direct value assignment (bypassing the spinbutton's client-side min), name
"QA Recheck Neg". Submit. Result: no new VLAN group appears in the list, no
banner/inline error is shown anywhere on the page, and the console has zero
errors — the form just silently re-renders with the entered values still in
place. Confirmed the negative value is the trigger by retrying the same name
with a valid positive VLAN ID immediately after, which succeeded.

Persona: superadmin@playwright.test

Still occurring as of the 2026-07-31 regression-plan sweep (32nd run). Repro: `/app/network/portal` (superadmin), Add VLAN Group form. Set VLAN ID to `-5` via direct value assignment (bypassing the spinbutton's client-side min), name "QA Recheck Neg". Submit. Result: no new VLAN group appears in the list, no banner/inline error is shown anywhere on the page, and the console has zero errors — the form just silently re-renders with the entered values still in place. Confirmed the negative value is the trigger by retrying the same name with a valid positive VLAN ID immediately after, which succeeded. Persona: superadmin@playwright.test
Author
Owner

Still occurring as of the 42nd regression run (2026-08-01). Repro: on /app/network/portal, submitted the Add VLAN Group form with name "QA neg probe" and VLAN ID -5 (set via direct JS value assignment to bypass the spinbutton's client-side min). Result: no group created, no error banner/inline message shown, form just re-displays with the entered values. Same silent-rejection pattern as before. No cleanup needed (nothing was created).

Still occurring as of the 42nd regression run (2026-08-01). Repro: on `/app/network/portal`, submitted the Add VLAN Group form with name "QA neg probe" and VLAN ID `-5` (set via direct JS value assignment to bypass the spinbutton's client-side `min`). Result: no group created, no error banner/inline message shown, form just re-displays with the entered values. Same silent-rejection pattern as before. No cleanup needed (nothing was created).
Author
Owner

Fixed in dashboard-shell 8882024 (the page) and central-unifi b0287f1 / v1.13.4 (a sibling field).

Root cause

The server was never at fault. vlan_id has been required|integer|min:1|max:4094 all along in VlanGroupController::store/update, so -5 was correctly refused — which is why you saw a 302 and no row.

What was missing was any way to find that out. Unifi/Portal.vue submitted through the bare router.post/router.put with plain ref() state. The bare router has no errors bag, and the page did not reference errors even once, on any of its four forms. So the rejection had nowhere to land. Your call that this is the same class as dashboard-oncall#3/#4 was right.

(On props.errors being {} — the errors are shared by Inertia's base middleware and do arrive; useForm is what binds them to a form and keeps them, and there was no useForm here.)

Fix, applied to the class rather than the field

All four forms on the page — add VLAN group, edit VLAN group, add mapping, add known MAC — now use useForm() and render <p class="form-error"> under every field that can be rejected, not just the one reported. The two VLAN pickers put the message under the whole select/custom-input toggle so it shows on either branch. Submit buttons get the standard processing state. Delete/disconnect stay on the bare router deliberately: no field, nothing to report.

Sibling numeric fields — one was a 500 waiting to happen

Checked all of them against the columns that store them:

  • vlan_id — unsignedSmallInteger, bounded 1–4094. Safe.
  • max_devices — unsignedTinyInteger, bounded 1–50. Safe.
  • session_minutes — unsignedInteger (ceiling 4294967295) validated integer|min:1 with no maximum. Anything larger reached the INSERT and raised SQLSTATE 22003: an unhandled 500, the same shape as central-oncall#7. Now max:525600.

Verified in the dev browser

At /app/network/portal, driving your repro exactly — -5 set on the spinbutton via JS to bypass the client-side min:

  • before: POST 302, empty errors, nothing on screen;
  • after: "The vlan id field must be at least 1." under the field, no group created, and VLAN 231 still saves and lists normally;
  • oversized session length now reads "The session minutes field must not be greater than 525600." instead of 500ing.

Test data cleaned up; nothing left in the dev DB.

Tests

dashboard-shell tests/Feature/Unifi/VlanGroupValidationTest.php — 8 tests: server-side bounds on all three forms (including the edit path), the session_minutes overflow, and an invariant over Portal.vue that every form is a useForm() whose errors are rendered and that no user-entered state is submitted through the bare router. The two real defects failed before the fix; whole tests/Feature/Unifi directory is green (21 tests).

Fixed in dashboard-shell 8882024 (the page) and central-unifi b0287f1 / v1.13.4 (a sibling field). ## Root cause The server was never at fault. `vlan_id` has been `required|integer|min:1|max:4094` all along in `VlanGroupController::store`/`update`, so `-5` was correctly refused — which is why you saw a 302 and no row. What was missing was any way to find that out. `Unifi/Portal.vue` submitted through the bare `router.post`/`router.put` with plain `ref()` state. The bare router has no errors bag, and the page did not reference errors even once, on any of its four forms. So the rejection had nowhere to land. Your call that this is the same class as dashboard-oncall#3/#4 was right. (On `props.errors` being `{}` — the errors *are* shared by Inertia's base middleware and do arrive; `useForm` is what binds them to a form and keeps them, and there was no `useForm` here.) ## Fix, applied to the class rather than the field All four forms on the page — add VLAN group, edit VLAN group, add mapping, add known MAC — now use `useForm()` and render `<p class="form-error">` under **every** field that can be rejected, not just the one reported. The two VLAN pickers put the message under the whole select/custom-input toggle so it shows on either branch. Submit buttons get the standard processing state. Delete/disconnect stay on the bare router deliberately: no field, nothing to report. ## Sibling numeric fields — one was a 500 waiting to happen Checked all of them against the columns that store them: - `vlan_id` — `unsignedSmallInteger`, bounded 1–4094. Safe. - `max_devices` — `unsignedTinyInteger`, bounded 1–50. Safe. - `session_minutes` — `unsignedInteger` (ceiling 4294967295) validated `integer|min:1` with **no maximum**. Anything larger reached the INSERT and raised SQLSTATE 22003: an unhandled 500, the same shape as central-oncall#7. Now `max:525600`. ## Verified in the dev browser At `/app/network/portal`, driving your repro exactly — `-5` set on the spinbutton via JS to bypass the client-side `min`: - before: POST 302, empty errors, nothing on screen; - after: **"The vlan id field must be at least 1."** under the field, no group created, and VLAN 231 still saves and lists normally; - oversized session length now reads **"The session minutes field must not be greater than 525600."** instead of 500ing. Test data cleaned up; nothing left in the dev DB. ## Tests `dashboard-shell tests/Feature/Unifi/VlanGroupValidationTest.php` — 8 tests: server-side bounds on all three forms (including the edit path), the `session_minutes` overflow, and an invariant over Portal.vue that every form is a `useForm()` whose errors are rendered and that no user-entered state is submitted through the bare router. The two real defects failed before the fix; whole `tests/Feature/Unifi` directory is green (21 tests).
jwed closed this issue 2026-08-01 14:05:52 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jwed/central-unifi#5