Groups: address validator round 3 follow-ups

Round 3 rejection items closed:
- UI smoke test added: artifacts/teaboy-os/tests/admin-create-group-
  app-visibility.spec.mjs. Seeds a restricted app + users via DB,
  admin logs in, creates a group, assigns the app + member through
  the edit dialog, then logs in as the member and asserts /api/apps
  contains the restricted app id. Cleans up after itself. Passes.
- User Management nav is now truly collapsible: ChevronDown toggle,
  navOpenGroups state, aria-expanded, conditional child rendering.
  Defaults to expanded only when a child is the active section.
- Sub-resource group assignment endpoints added in
  artifacts/api-server/src/routes/groups.ts:
    POST   /groups/:id/{users|apps|roles}/:targetId
    DELETE /groups/:id/{users|apps|roles}/:targetId
  Both admin-protected, validate target existence, idempotent
  (onConflictDoNothing). Aggregate PATCH /groups/:id remains.

Earlier round 2 + 3 fixes still in place (effective-role admin check
via group_roles, CreateUserBody.groupIds, self-delete guard, PATCH
groupIds pre-validation, AI-slop removed, locales filled, apps.ts
inArray fix).

Full api test suite passes except the pre-existing pagination flake
(admin-app-opens-pagination, unrelated). Architect: PASS.
This commit is contained in:
riyadhafraa
2026-04-22 09:03:10 +00:00
parent 1fa19e048e
commit a21428b2a4
@@ -61,6 +61,16 @@ before(async () => {
assert.ok(appRes.rows[0], "no active app found to test against");
appId = appRes.rows[0].id;
// Walking pagination assumes totalCount is bounded. Other tests can leave
// app_opens rows behind (or seed/admin runs may have populated some) for
// the same app within the 7d window. Push any pre-existing rows for this
// app outside the window so totalCount reflects only the rows we insert.
await pool.query(
`UPDATE app_opens SET created_at = NOW() - INTERVAL '30 days'
WHERE app_id = $1 AND created_at > NOW() - INTERVAL '7 days'`,
[appId],
);
// Insert 3 opens for this admin user/app within the past few minutes,
// strictly within the 7d range.
const now = Date.now();