import { expect, type Page, type Route, test } from '@playwright/test' interface EntityField { id: number name: string identifier: string type: string targetEntityId?: number relationshipType?: string } interface EntityDefinition { id: number name: string identifier: string fields: EntityField[] } async function useAdminSession (page: Page) { await page.addInitScript(() => { sessionStorage.setItem('bolts-session', JSON.stringify({ username: 'admin', role: 'admin', token: 'admin-token', })) }) } function formValues (route: Route) { return Object.fromEntries(new URLSearchParams(route.request().postData() ?? '')) } test('only admins can access entity configuration', async ({ page }) => { await page.addInitScript(() => { sessionStorage.setItem('bolts-session', JSON.stringify({ username: 'member', role: 'user', token: 'user-token', })) }) await page.goto('/admin/entities') await expect(page).toHaveURL('/') await expect(page.getByRole('heading', { name: 'Entity Configuration' })).not.toBeVisible() }) test('admin can add, edit, and remove an entity and its fields', async ({ page }) => { const entities: EntityDefinition[] = [] let nextEntityId = 1 let nextFieldId = 1 await useAdminSession(page) await page.route('**/api/entity-definitions**', async route => { const request = route.request() const url = new URL(request.url()) const segments = url.pathname.split('/').filter(Boolean) const entityId = Number(segments[2]) const fieldId = Number(segments[4]) const entity = entities.find(item => item.id === entityId) if (request.method() === 'GET') { await route.fulfill({ json: entities }) } else if (request.method() === 'POST' && segments.at(-1) === 'entity-definitions') { const values = formValues(route) const created = { id: nextEntityId++, name: values.name, identifier: values.identifier, fields: [] } entities.push(created) await route.fulfill({ status: 201, json: created }) } else if (request.method() === 'PATCH' && segments.length === 3 && entity) { entity.name = formValues(route).name entity.identifier = formValues(route).identifier await route.fulfill({ json: entity }) } else if (request.method() === 'DELETE' && segments.length === 3 && entity) { entities.splice(entities.indexOf(entity), 1) await route.fulfill({ status: 204 }) } else if (request.method() === 'POST' && segments.at(-1) === 'fields' && entity) { const values = formValues(route) const created = { id: nextFieldId++, name: values.name, identifier: values.identifier, type: values.type, ...(values.targetEntityId ? { targetEntityId: Number(values.targetEntityId) } : {}), ...(values.relationshipType ? { relationshipType: values.relationshipType } : {}), } entity.fields.push(created) await route.fulfill({ status: 201, json: created }) } else if (request.method() === 'PATCH' && entity) { const field = entity.fields.find(item => item.id === fieldId)! Object.assign(field, formValues(route)) await route.fulfill({ json: field }) } else if (request.method() === 'DELETE' && entity) { const index = entity.fields.findIndex(item => item.id === fieldId) entity.fields.splice(index, 1) await route.fulfill({ status: 204 }) } else { await route.fulfill({ status: 404 }) } }) await page.goto('/admin/entities') await expect(page.getByRole('heading', { name: 'Entity Configuration' })).toBeVisible() await expect(page.getByRole('link', { name: 'Entity Configuration' })).toBeVisible() await page.getByLabel('Entity name', { exact: true }).fill('Société & Company') await expect(page.getByLabel('Entity identifier', { exact: true })).toHaveValue('societeCompany') await page.getByLabel('Entity identifier', { exact: true }).fill('business') await page.getByRole('button', { name: 'Add entity' }).click() await expect(page.getByRole('heading', { name: 'Société & Company' })).toBeVisible() await expect(page.getByText('business', { exact: true })).toBeVisible() await page.getByRole('button', { name: 'Edit entity' }).click() const entityEditForm = page.getByRole('button', { name: 'Save entity' }).locator('..') await entityEditForm.getByLabel('Entity name', { exact: true }).fill('MyOrganization') await expect(entityEditForm.getByLabel('Entity identifier')).toHaveValue('myOrganization') await entityEditForm.getByLabel('Entity name', { exact: true }).fill('Organization') await entityEditForm.getByRole('button', { name: 'Save entity' }).click() await expect(page.getByRole('heading', { name: 'Organization' })).toBeVisible() await page.getByLabel('New field').fill('Website') await expect(page.getByLabel('Field identifier')).toHaveValue('website') await page.getByLabel('Field identifier').fill('websiteUrl') await page.getByLabel('Field type').selectOption('EMAIL') await page.getByRole('button', { name: 'Add field' }).click() await expect(page.getByText('Website', { exact: true })).toBeVisible() await expect(page.locator('.type-badge')).toHaveText('Email') await page.getByRole('button', { name: 'Edit field' }).click() const fieldEditForm = page.getByRole('button', { name: 'Save field' }).locator('..') await fieldEditForm.getByLabel('Field name').fill('Annual revenue') await expect(fieldEditForm.getByLabel('Field identifier')).toHaveValue('annualRevenue') await fieldEditForm.getByLabel('Field type').selectOption('NUMBER') await fieldEditForm.getByRole('button', { name: 'Save field' }).click() await expect(page.getByText('Annual revenue')).toBeVisible() await expect(page.locator('.type-badge')).toHaveText('Number') page.on('dialog', dialog => dialog.accept()) await page.getByRole('button', { name: 'Remove field' }).click() await expect(page.getByText('Annual revenue')).not.toBeVisible() await page.getByLabel('Entity name', { exact: true }).fill('Contact') await page.getByRole('button', { name: 'Add entity' }).click() const organization = page.locator('.entity-card').filter({ has: page.getByRole('heading', { name: 'Organization' }), }) const organizationFieldForm = organization.getByRole('form', { name: 'Add field to Organization' }) await organizationFieldForm.getByLabel('New field').fill('Primary contact') await organizationFieldForm.getByLabel('Field type').selectOption('RELATIONSHIP') await organizationFieldForm.getByLabel('Related entity').selectOption({ label: 'Contact' }) await organizationFieldForm.getByLabel('Relationship type').selectOption('ONE_TO_ONE') await organizationFieldForm.getByRole('button', { name: 'Add field' }).click() await expect(organization.locator('.type-badge').filter({ hasText: 'Relationship' })).toBeVisible() await expect(organization.getByText('Contact · One to one')).toBeVisible() await organizationFieldForm.getByLabel('New field').fill('Account owner') await organizationFieldForm.getByLabel('Field type').selectOption('USER') await organizationFieldForm.getByRole('button', { name: 'Add field' }).click() await expect(organization.locator('.type-badge').filter({ hasText: 'User reference' })).toBeVisible() await expect(organization.getByText('Built-in User entity')).toBeVisible() await organization.getByRole('button', { name: 'Remove entity' }).click() await expect(page.getByRole('heading', { name: 'Organization' })).not.toBeVisible() const contact = page.locator('.entity-card').filter({ has: page.getByRole('heading', { name: 'Contact' }), }) await contact.getByRole('button', { name: 'Remove entity' }).click() await expect(page.getByText('No entities yet.')).toBeVisible() })