import { PERMISSION_CATALOG } from '../src/auth/permissions/permission-catalog'; import { PermissionCatalogSyncService } from '../src/auth/permissions/permission-catalog-sync.service'; import { PrismaService } from '../src/prisma/prisma.service'; const SYSTEM_ID = 'hive-system-zander-liebig-10'; const PART_PREFIX = 'hive-part-zl10-'; const PART_SLUGS = [ 'floor', 'floor-insert', 'box-10', 'box-empty', 'divider-board', 'queen-excluder', 'feeder', 'cover-abdeckfix', 'cover-lotus', 'propolis-screen', 'inner-lid', 'insulation', 'roof', 'evaporator', 'frame-feeder', ] as const; interface StateField { key: string; kind: string; feedType?: string; [extra: string]: unknown; } /** * The Liebig-Zander start catalog arrives with the migration (fixed ids) and * `hive-systems:manage` joins ADMIN through the boot-time catalog sync. * Checked against the migrated test database. */ describe('Hive catalog seed (e2e)', () => { let prisma: PrismaService; beforeAll(() => { prisma = new PrismaService(); }); afterAll(async () => { await prisma.$disconnect(); }); async function partType(slug: string) { return prisma.hivePartType.findUniqueOrThrow({ where: { id: `${PART_PREFIX}${slug}` }, }); } it('seeds the Zander-Liebig system with its geometry and template', async () => { const system = await prisma.hiveSystem.findUniqueOrThrow({ where: { id: SYSTEM_ID }, }); expect(system).toMatchObject({ name: 'Zander nach Dr. Liebig, 10 Rähmchen', frameWidthMm: 477, frameHeightMm: 220, footprintLMm: 525, footprintWMm: 430, active: true, }); }); it('seeds 15 active part types for the system under fixed ids', async () => { const types = await prisma.hivePartType.findMany({ where: { systemId: SYSTEM_ID, active: true }, orderBy: { id: 'asc' }, }); expect(types.map((t) => t.id)).toEqual( PART_SLUGS.map((slug) => `${PART_PREFIX}${slug}`).sort(), ); }); it('describes the 10-frame box with 10 slots and 230 mm height', async () => { const box = await partType('box-10'); expect(box).toMatchObject({ kind: 'BOX', boxDepth: 'FULL', frameSlots: 10, heightMm: 230, lengthMm: 525, widthMm: 430, }); expect((await partType('box-empty')).frameSlots).toBe(0); }); it('gives the feeder two level fields with a feed type each', async () => { const feeder = await partType('feeder'); const fields = feeder.stateSchema as unknown as StateField[]; expect(fields).toHaveLength(2); expect(fields.every((f) => f.kind === 'level')).toBe(true); expect(fields.map((f) => [f.key, f.feedType])).toEqual([ ['syrupLiters', 'SUGAR_WATER'], ['fondantKg', 'FEED_PASTE'], ]); }); it('seeds the frame feeder over two frame slots with a 2 l syrup level', async () => { const pocket = await partType('frame-feeder'); const fields = pocket.stateSchema as unknown as StateField[]; expect(pocket).toMatchObject({ kind: 'FRAME_FEEDER', slotWidth: 2, frameSlots: null, lengthMm: 477, widthMm: 70, heightMm: 220, }); expect(fields).toEqual([ { key: 'syrupLiters', label: 'Flüssigfutter', kind: 'level', unit: 'l', max: 2, step: 0.25, feedType: 'SUGAR_WATER', }, ]); }); it('gives the other stateful parts their choice and level fields', async () => { const keysOf = async (slug: string) => ((await partType(slug)).stateSchema as unknown as StateField[]).map( (f) => f.key, ); expect(await keysOf('floor')).toEqual(['entrance', 'varroaTray']); expect(await keysOf('divider-board')).toEqual(['bungHoles']); expect(await keysOf('inner-lid')).toEqual(['feedHole']); expect(await keysOf('evaporator')).toEqual(['acidMl']); expect(await keysOf('roof')).toEqual([]); }); it('stores the system template without ids, with 10 foundation frames', async () => { const system = await prisma.hiveSystem.findUniqueOrThrow({ where: { id: SYSTEM_ID }, }); const template = system.template as unknown as { schemaVersion: number; stack: { id?: string; partTypeId: string; state: Record; frames?: { slot: number; type: string }[]; }[]; }; expect(template.schemaVersion).toBe(1); expect(template.stack.map((p) => p.partTypeId)).toEqual([ `${PART_PREFIX}floor`, `${PART_PREFIX}box-10`, `${PART_PREFIX}cover-abdeckfix`, `${PART_PREFIX}inner-lid`, `${PART_PREFIX}insulation`, `${PART_PREFIX}roof`, ]); for (const part of template.stack) { expect(part.id).toBeUndefined(); expect(part.state).toEqual({}); } const frames = template.stack[1].frames ?? []; expect(frames).toHaveLength(10); expect(frames.map((f) => f.slot)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); expect(frames.every((f) => f.type === 'FOUNDATION')).toBe(true); }); it('lists hive-systems:manage in the permission catalog', () => { const entry = PERMISSION_CATALOG.find( (e) => e.key === 'hive-systems:manage', ); expect(entry).toMatchObject({ resource: 'hive-systems', action: 'manage', labelDe: 'Pflegen', groupDe: 'Beutensysteme', }); }); it('grants hive-systems:manage to ADMIN after the catalog sync', async () => { await new PermissionCatalogSyncService(prisma).onModuleInit(); const grant = await prisma.rolePermission.findFirst({ where: { role: { key: 'ADMIN' }, permission: { key: 'hive-systems:manage' }, }, }); expect(grant).not.toBeNull(); }); });