test(mana/web): consistency guard for workbench-registry ↔ MANA_APPS

Adds the test that would have caught the inventar↔inventory drift
months earlier (commit 45790ffbb fixed the actual mismatch). Walks
both directions:

  1. Every workbench-registered app must have a MANA_APPS entry, OR
     be in the WORKBENCH_ONLY allowlist (currently `automations`,
     `playground` — internal devtools we don't want in marketing).

  2. Every MANA_APPS entry must be registered in the workbench, OR
     be in the BRANDING_ONLY allowlist (`mana` itself, standalone
     subdomains like `arcade`, "Coming Soon" placeholders like
     `wisekeep`/`mail`/`events`, and modules whose workbench
     integration is still pending like `guides`/`who`).

Plus a regression guard that fails loudly if anyone reintroduces
`inventar` as an id in either registry.

The point: every future drift between the two registries forces the
contributor to either fix it on the spot or explicitly classify the
new entry in one of the allowlists with a comment. No more silent
fail-open tier-gating.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-09 16:05:17 +02:00
parent c184991b3a
commit df72a92b4c

View file

@ -0,0 +1,78 @@
import { describe, it, expect } from 'vitest';
import { MANA_APPS } from '@mana/shared-branding';
// Side-effect import: registers every workbench app via ./apps.
// Without this, getAllApps() would return an empty list.
import './apps';
import { getAllApps } from './registry';
/**
* Apps that intentionally exist in the workbench but are NOT in MANA_APPS
* (e.g. internal devtools we don't want in marketing/branding lists).
*
* Adding to this list = "yes, this is supposed to drift, here's why".
* Anything else triggers the test below and surfaces the drift early
* the kind of mismatch that produced the silent inventarinventory bug
* before commit 45790ffbb.
*/
const WORKBENCH_ONLY = new Set(['automations', 'playground']);
/**
* Apps that intentionally exist in MANA_APPS but are NOT in the workbench
* registry (standalone subdomains, marketing-only "Coming Soon" entries,
* modules that exist as routes/i18n but aren't workbench-integrated yet,
* or the unified-app meta entry itself).
*
* When you add a new app to MANA_APPS, you must EITHER register it in the
* workbench (apps/web/src/lib/app-registry/apps.ts) or add it here with a
* comment explaining why it doesn't belong in the workbench. This forces
* the drift conversation to happen at the time of the change instead of
* months later.
*/
const BRANDING_ONLY = new Set([
// Meta entry for the unified Mana app itself — it can't be a "module"
// of its own workbench.
'mana',
// Standalone web app on its own subdomain (arcade.mana.how).
'arcade',
// Marketing placeholders, status: 'planning' / 'development'. No
// workbench module exists yet — they only show up in the AppsPage
// gallery as "Coming Soon" hints.
'wisekeep',
'mail',
'events',
// Status 'beta' but the workbench integration is still pending. Move
// out of this list once the apps.ts entry exists.
'guides',
'who',
]);
describe('app registry ↔ MANA_APPS consistency', () => {
it('every workbench-registry app has a MANA_APPS entry or is in WORKBENCH_ONLY', () => {
const brandingIds = new Set(MANA_APPS.map((a) => a.id));
const unaccounted = getAllApps()
.map((a) => a.id)
.filter((id) => !brandingIds.has(id) && !WORKBENCH_ONLY.has(id));
expect(unaccounted, `Workbench apps missing from MANA_APPS: ${unaccounted.join(', ')}`).toEqual(
[]
);
});
it('every MANA_APPS entry is registered in the workbench or is in BRANDING_ONLY', () => {
const workbenchIds = new Set(getAllApps().map((a) => a.id));
const unaccounted = MANA_APPS.map((a) => a.id).filter(
(id) => !workbenchIds.has(id) && !BRANDING_ONLY.has(id)
);
expect(
unaccounted,
`MANA_APPS entries missing from workbench registry: ${unaccounted.join(', ')}`
).toEqual([]);
});
it('inventar→inventory rename is fully applied (regression guard)', () => {
// The whole point of commit 45790ffbb. If anyone re-introduces an
// `inventar` id in either registry, the join in registry.ts
// silently goes back to fail-open for that module.
const allIds = [...getAllApps().map((a) => a.id), ...MANA_APPS.map((a) => a.id)];
expect(allIds).not.toContain('inventar');
});
});