Contributing

Testing

Externa uses Pest 4 with the Laravel and browser plugins. Feature tests refresh the database; prefer focused suites that mirror the domain folders under tests/Feature.

Layout

tests/
  Pest.php              # binds TestCase + RefreshDatabase; grantAiPermissions()
  TestCase.php
  Unit/                 # no RefreshDatabase by default
  Feature/
    Admin/              # users, groups, roles, files, activity
    Ai/                 # assistant, tools, webhook, attachments
    Auth/               # Fortify / verification flows
    Authorization/      # permission sync, super-admin gate, resolver
    Collections/        # collections, fields, items, normalizer
    Settings/           # account / profile settings
    DashboardTest.php
    NotificationsTest.php
    CreateSuperAdminSeederTest.php

  Browser/              # pest-plugin-browser (Playwright)
    FileManagerBrowserTest.php

Configured in tests/Pest.php:

SuiteBaseTraits
FeatureTests\TestCaseRefreshDatabase
BrowserTests\TestCaseRefreshDatabase
UnitTests\TestCase(none)

Domain folders (what lives where)

FolderCovers
Feature/AdminUser/group/role/permission management, file manager JSON, zip/duplicate async, thumbnails, activity log UI
Feature/ApiPublic CMS API (/api/v1) — public role grants, API keys, IP allowlist, per-action deny/allow
Feature/AiChat, attachments, tool permission matrix, import remote JSON, sync/webhook phases, multi-role smoke
Feature/CollectionsCollection CRUD, item data normalizer, management flows
Feature/AuthLogin, registration, verification, 2FA-related behaviour
Feature/Authorizationpermissions:sync, effective permissions, Gate::before super-admin
Feature/SettingsProfile / password / appearance settings pages
BrowserReal browser interactions (file manager drive-like UX)

Browser plugin notes

Dependencies: pestphp/pest-plugin-browser and playwright (npm).

  • Browser tests live under tests/Browser/, not Feature/.
  • They use the same factories/seeders patterns as feature tests (e.g. seed PermissionSeeder, grant a disposable role with exact permission names).
  • Require a working Playwright install (npx playwright install if browsers are missing).
  • Slower than HTTP feature tests — keep them for interactions that need real DOM/Inertia behaviour (drag/select, panels), not for every CRUD path.

Example helper pattern (see FileManagerBrowserTest.php): create a unique role per test, syncPermissions, assign to the user, then drive the UI.

How to run tests

From externa-core:

# Full gate used in CI-style scripts (clears config, Pint --test, then Pest)
composer test

# Direct Pest / Artisan
php artisan test
php artisan test --filter=FileManagerTest
php artisan test tests/Feature/Ai
php artisan test tests/Browser

# Frontend static checks (also part of composer ci:check)
npm run lint:check
npm run format:check
npm run types:check

composer test runs:

  1. php artisan config:clear
  2. pint --parallel --test
  3. php artisan test

PHP version

The app targets PHP 8.3+ (installation docs often recommend 8.4). Run tests with the same CLI PHP your Herd/FPM stack uses so platform checks match Composer.

Helpers

grantAiPermissions(User $user, array $permissions): User in tests/Pest.php creates a unique role, syncs the given permission names, and assigns it — safe for parallel/isolation-sensitive AI tests.

What to cover for new features

ChangeMinimum tests
New permissionSync includes it; route/FormRequest 403 without it and 200/allowed with it; UI gate optional via browser or Inertia assert
New admin/files routeFeature test for happy path + permission denial; async path asserts job dispatched / worker-friendly outcome
New collections behaviourFeature test for store/update/validation; normalizer cases for new field shapes
New field typeNormalizer + validation rules; create field via HTTP or AI tool; UI smoke if non-trivial
New AI toolPermission denied string; success JSON with "ok": true; register presence in AppAssistant for a user who has the gate permissions
Webhook / importToken rejection (empty/wrong); successful import summary; throttle not required in unit tests
Seeder / config defaultsAssert super-admin email/password from config/super_admin.php when touching seeders
Browser-only UXOne Browser test for the interaction that HTTP tests cannot express

Prefer one focused Pest file next to the domain folder over a giant catch-all. Reuse factories and seed PermissionSeeder (or full DatabaseSeeder) instead of hand-inserting permission rows.

  • Contributing — lint/format before push
  • Extending Externa — end-to-end checklists that include tests
  • Operations — queue/scheduler needed when testing async jobs locally (composer run dev or a separate queue:listen)
  • Post-v1 polish smoke — optional signed-off Pest/Playwright checklist (internal)
Previous
Extending Externa