Skip to content
Closed
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
70 changes: 69 additions & 1 deletion autobot-frontend/src/__tests__/nav-items-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,24 @@
* Sub-routes (children) are intentionally excluded — only the parent appears
* in main nav. Redirects and dynamic-param routes (`:sessionId`, `:pathMatch`)
* are also excluded.
*
* #16933: `hideInNav: true` short-circuits the rule above at `isHiddenByMeta`
* — by design, since that flag means "reached some other way", not "has no
* way". For most hideInNav routes that other way is real (login, onboarding,
* an LLC sub-tab, a dev-only page). For `/admin/*` routes specifically it is
* supposed to mean "listed in `adminMenuItems` instead of the main nav" —
* every sibling admin route already follows that pattern — but nothing
* checked it, so `/admin/pricing` and `/admin/mcp-servers` (#16825) shipped
* with the flag and no matching entry, reachable only by typing the URL.
* The second describe block below is that missing check, scoped to
* `/admin/*` rather than every hideInNav route, since the wider set has
* genuine other exposure paths this file was never meant to enumerate.
*/

import { describe, it, expect } from 'vitest'
import type { RouteRecordRaw } from 'vue-router'
import { routes } from '@/router'
import { navItems, profileMenuItems, filterByFeatureFlag } from '@/config/navItems'
import { navItems, profileMenuItems, adminMenuItems, filterByFeatureFlag } from '@/config/navItems'

/**
* Routes that are intentionally NOT in main nav.
Expand Down Expand Up @@ -180,3 +192,59 @@ describe('navItems coverage (#6499)', () => {
expect(allowlistSize).toBeLessThan(15)
})
})

describe('adminMenuItems coverage (#16933)', () => {
/** Top-level routes under /admin/ that carry hideInNav: true. */
function hiddenAdminRoutes(): RouteRecordRaw[] {
return topLevelRoutes().filter(
(r) => typeof r.path === 'string' && r.path.startsWith('/admin/') && isHiddenByMeta(r),
)
}

it('every hideInNav /admin/* route has an adminMenuItems entry or is allowlisted', () => {
const adminPaths = new Set(adminMenuItems.map((n) => n.to))
const missing = hiddenAdminRoutes()
.map((r) => r.path as string)
.filter((p) => !(p in INTENTIONALLY_HIDDEN) && !adminPaths.has(p))

if (missing.length > 0) {
throw new Error(
`The following /admin/* routes carry hideInNav: true but have no adminMenuItems ` +
`entry and are not in INTENTIONALLY_HIDDEN:\n` +
missing.map((p) => ` - ${p}`).join('\n') +
`\n\nhideInNav: true does not mean "unreachable is fine" for an /admin/* route — ` +
`it means "listed in adminMenuItems instead of the main nav" (#16933). Add an ` +
`entry to src/config/navItems.ts's adminMenuItems, or add the route to ` +
`INTENTIONALLY_HIDDEN above with a justification.`,
)
}

expect(missing).toEqual([])
})

it('every adminMenuItems entry corresponds to a real route', () => {
const allPaths = allRoutePaths()
const orphans = adminMenuItems.filter((n) => !allPaths.has(n.to)).map((n) => n.to)

expect(orphans).toEqual([])
})

it('sanity: is checking a non-empty, real set of admin routes (mutation proof, #16933 AC4)', () => {
// Not a fabricated route: at least the pre-existing sibling admin pages
// must be present, so a change that broke discovery of /admin/* routes
// entirely (not just missed one entry) would also be caught here.
const hidden = hiddenAdminRoutes().map((r) => r.path)
expect(hidden).toEqual(expect.arrayContaining(['/admin/sandbox', '/admin/pricing', '/admin/mcp-servers']))

// The actual mutation proof: drop a real adminMenuItems entry and confirm
// the coverage check above would have caught it -- reusing the same
// matching logic the real test uses, not a hand-rolled re-implementation
// that could drift from it.
const withoutPricing = adminMenuItems.filter((n) => n.to !== '/admin/pricing')
const adminPathsWithoutPricing = new Set(withoutPricing.map((n) => n.to))
const missingWithoutPricing = hidden.filter(
(p) => !(p in INTENTIONALLY_HIDDEN) && !adminPathsWithoutPricing.has(p),
)
expect(missingWithoutPricing).toEqual(['/admin/pricing'])
})
})
7 changes: 7 additions & 0 deletions autobot-frontend/src/config/navItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,11 @@ export const adminMenuItems: NavItem[] = [
{ to: '/admin/provider-fallback', labelKey: 'nav.providerFallback', icon: 'M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4', iconViewBox: '0 0 24 24', iconStroke: true },
// Issue #12162 (#12102/#11506 T1 Stage 1): Advanced Control — takeover approval queue
{ to: '/admin/advanced-control', labelKey: 'nav.advancedControl', icon: 'M7 11.5V14m0-2.5v-6a1.5 1.5 0 113 0m-3 6a1.5 1.5 0 00-3 0v2a7.5 7.5 0 0015 0v-5a1.5 1.5 0 00-3 0m-6-3V11m0-5.5v-1a1.5 1.5 0 013 0v1m0 0V11m0-5.5a1.5 1.5 0 013 0v3m0 0V11', iconStroke: true },
// Issue #16825, #16933: live model pricing admin panel -- shipped with
// hideInNav: true but no adminMenuItems entry, so it was unreachable
// except by typing the URL directly.
{ to: '/admin/pricing', labelKey: 'nav.adminPricing', icon: 'M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z', iconViewBox: '0 0 24 24', iconStroke: true },
// Issue #16825, #16933: external MCP server admin panel -- same gap as
// the pricing panel above, same fix.
{ to: '/admin/mcp-servers', labelKey: 'nav.adminMcpServers', icon: 'M21.75 17.25v-.228a4.5 4.5 0 00-.12-1.03l-2.268-9.64a3.375 3.375 0 00-3.285-2.602H7.923a3.375 3.375 0 00-3.285 2.602l-2.268 9.64a4.5 4.5 0 00-.12 1.03v.228m19.5 0a3 3 0 01-3 3H5.25a3 3 0 01-3-3m19.5 0a3 3 0 00-3-3H5.25a3 3 0 00-3 3', iconViewBox: '0 0 24 24', iconStroke: true },
];
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -7858,6 +7858,8 @@
"systemHealth": "صحة النظام",
"providerFallback": "احتياطي المزود",
"advancedControl": "التحكم المتقدم",
"adminPricing": "تسعير النماذج",
"adminMcpServers": "خوادم MCP",
"llcRoles": "الأدوار"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -7858,6 +7858,8 @@
"systemHealth": "Systemzustand",
"providerFallback": "Anbieter-Fallback",
"advancedControl": "Erweiterte Steuerung",
"adminPricing": "Modellpreise",
"adminMcpServers": "MCP-Server",
"llcRoles": "Rollen"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7865,6 +7865,8 @@
"systemHealth": "System Health",
"providerFallback": "Provider Fallback",
"advancedControl": "Advanced Control",
"adminPricing": "Model Pricing",
"adminMcpServers": "MCP Servers",
"sharedLinks": "Shared Chat Links",
"companyOs": "Company OS",
"llcDashboard": "Dashboard",
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -7858,6 +7858,8 @@
"systemHealth": "Salud del sistema",
"providerFallback": "Respaldo de proveedor",
"advancedControl": "Control avanzado",
"adminPricing": "Precios de modelos",
"adminMcpServers": "Servidores MCP",
"llcRoles": "Roles"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@
"systemHealth": "سلامت سیستم",
"providerFallback": "جایگزینی ارائه‌دهنده",
"advancedControl": "کنترل پیشرفته",
"adminPricing": "قیمت‌گذاری مدل‌ها",
"adminMcpServers": "سرورهای MCP",
"llcRoles": "نقش‌ها"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -7858,6 +7858,8 @@
"systemHealth": "Santé du système",
"providerFallback": "Basculement de fournisseur",
"advancedControl": "Contrôle avancé",
"adminPricing": "Tarification des modèles",
"adminMcpServers": "Serveurs MCP",
"llcRoles": "Rôles"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/he.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@
"systemHealth": "בריאות המערכת",
"providerFallback": "גיבוי ספק",
"advancedControl": "בקרה מתקדמת",
"adminPricing": "תמחור דגמים",
"adminMcpServers": "שרתוני MCP",
"llcRoles": "תפקידים"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/lv.json
Original file line number Diff line number Diff line change
Expand Up @@ -7858,6 +7858,8 @@
"systemHealth": "Sistēmas veselība",
"providerFallback": "Nodrošinātāja rezerve",
"advancedControl": "Papildu vadība",
"adminPricing": "Modeļu cenas",
"adminMcpServers": "MCP serveri",
"llcRoles": "Lomas"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -7858,6 +7858,8 @@
"systemHealth": "Kondycja systemu",
"providerFallback": "Przełączanie dostawcy",
"advancedControl": "Sterowanie zaawansowane",
"adminPricing": "Ceny modeli",
"adminMcpServers": "Serwery MCP",
"llcRoles": "Role"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -7858,6 +7858,8 @@
"systemHealth": "Saúde do sistema",
"providerFallback": "Fallback de provedor",
"advancedControl": "Controle avançado",
"adminPricing": "Preços de modelos",
"adminMcpServers": "Servidores MCP",
"llcRoles": "Funções"
},
"llcCompanyStatus": {
Expand Down
2 changes: 2 additions & 0 deletions autobot-frontend/src/i18n/locales/ur.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@
"systemHealth": "سسٹم کی صحت",
"providerFallback": "فراہم کنندہ فال بیک",
"advancedControl": "ایڈوانسڈ کنٹرول",
"adminPricing": "ماڈل قیمت",
"adminMcpServers": "MCP سرورز",
"llcRoles": "کردار"
},
"llcCompanyStatus": {
Expand Down
7 changes: 7 additions & 0 deletions changelog/unreleased/16933-admin-menu-entries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
type: fix
scope: frontend
issue: 16933
pr: 0000
---
`/admin/pricing` and `/admin/mcp-servers` (#16825) merged with `hideInNav: true` but no matching entry in `navItems.ts`'s `adminMenuItems` — in this codebase `hideInNav: true` means "listed in the admin menu instead of the main nav", not "hidden", so both screens were reachable only by typing the URL directly. Added the missing `adminMenuItems` entries (with translations across all 11 locales) and extended `nav-items-coverage.test.ts` to check every `hideInNav` `/admin/*` route against `adminMenuItems`, which the existing coverage test structurally could not do — it short-circuits on `hideInNav: true` before reaching any allowlist or membership check. Proved by a test that removes a real `adminMenuItems` entry and confirms the new check would have caught it.
Loading