/** * scaffold-frontend-auth / generate.test — the PermissionGuard contract. * A PAGE-level guard must render a `permission-denied` marker on denial (a stable * signal for UAT + a clearer UX), while an INLINE guard stays silent (returns the * fallback), so a denied row/toolbar affordance simply disappears. */ import { describe, it, expect } from 'vitest'; import { generate } from '../generate.js'; function guardSource(): string { const files = generate({ projectPath: '/tmp/web', appCode: 'crm', force: false }); const guard = files.find((f) => f.path.endsWith('components/auth/PermissionGuard.tsx')); if (!guard) throw new Error('PermissionGuard.tsx not generated'); return guard.content; } describe('PermissionGuard', () => { const content = guardSource(); it('exposes a `page` prop', () => { expect(content).toMatch(/page\?:\s*boolean/); expect(content).toMatch(/page = false/); }); it('renders the permission-denied marker only for a page-level denial', () => { expect(content).toContain('data-testid="permission-denied"'); // The marker sits behind the `page` branch, not the default deny path. expect(content).toMatch(/if \(page\)/); }); it('still returns the fallback for inline (non-page) denials', () => { expect(content).toMatch(/return fallback;/); }); }); describe('emitted surface', () => { const paths = generate({ projectPath: '/tmp/web', appCode: 'crm', force: false }).map((f) => f.path); it('emits exactly the two auth seam files', () => { expect(paths).toEqual([ 'web/crm-web/src/business/auth/useAuth.ts', 'web/crm-web/src/components/auth/PermissionGuard.tsx', ]); }); it('never re-emits the platform-admin placeholder', () => { // Removed together with the aggregator's fallback block (it shipped up to CLI 5.12): // registered on an `administration.*` key WINS over the SDK's generic ModuleHomePage, // so the placeholder blanked the admin landing pages it was meant to protect. expect(paths.some((p) => p.includes('AdminModuleFallback'))).toBe(false); }); });