Testing and quality
Use layered automated testing and deployment gates to keep a configurable platform safe to evolve.
FormPlatform Testing Guide
1. Current test architecture
FormPlatform uses layered verification:
| Layer | Location | Purpose | Dependencies |
|---|---|---|---|
| Frontend unit/contracts | `src/FormPlatform.Host/ClientApp/src/**/*.test.js` | pure runtime rules, public component contracts, regressions | Node.js, Vitest |
| Static checks | projects/scripts | JSON/XML/JS/C# structure, links, formatting, manifests | none/local SDK |
| PostgreSQL integration | `tests/FormPlatform.PostgreSql.Tests` | real ORM, transaction, reference, migration/checksum behavior | Docker |
| SQL Server integration | `tests/FormPlatform.SqlServer.Tests` | real SQL Server ORM, generated-key/paging, migration/checksum behavior | Docker |
| MySQL integration | `tests/FormPlatform.MySql.Tests` | real MySQL `binary(16)` UUID v7 CRUD/filter behavior | Docker |
| Playwright E2E | `tests/e2e` | browser, auth, routing, forms, modules, error UX | running Host, Chromium, test account |
| All-module build | `eng/build-all.ps1` | Host/SDK/Sample/Todo/ResourceBooking compatibility | Node, .NET, sibling repositories |
| Manual acceptance | browser/database/logs | Designer, printing, payment/OIDC, environment flows | target environment |
GitHub CI now enforces eight gates: frontend Vitest, build and stage every module, SDK/module compatibility, PostgreSQL Testcontainers, SQL Server Testcontainers, MySQL Testcontainers, the real Host against isolated PostgreSQL for Playwright, and a final release-runtime integrity gate. The final gate depends on every earlier gate.
2. Prerequisites
Use .NET 10 SDK and Node.js 22. PostgreSQL, SQL Server, and MySQL integration tests require Docker. E2E requires a separately running FormPlatform, and ResourceBooking tests require that module and client extension.
Never point automation at development or production data. Testcontainers creates a temporary PostgreSQL. The E2E Host also needs a dedicated database and secrets.
3. PostgreSQL integration tests
dotnet test tests/FormPlatform.PostgreSql.Tests/FormPlatform.PostgreSql.Tests.csproj -c Release
`PostgreSqlFixture` starts `postgres:17-alpine` and implements `IDatabaseConnectionFactory`. Tests in the xUnit collection share a container, so isolate with unique rows/tables or cleanup.
Current coverage includes dynamic repository/provider behavior and migration once-only/checksum/Core-Commerce schema behavior.
3.1 Add ORM tests
Use real metadata, repository, dialect, and UnitOfWork rather than mocked SQL. Project explicit fields and assert that unrequested attributes are absent. Use unique IDs/names and never rely on test order.
3.2 SQL Server integration tests
dotnet test tests/FormPlatform.SqlServer.Tests/FormPlatform.SqlServer.Tests.csproj -c Release
`SqlServerFixture` starts an isolated SQL Server 2022 container. It verifies the SQL Server dialect's CRUD/filter/offset-paging/generated-key path and applies the complete Core and Commerce migration chain twice, including checksum protection. This is deliberately independent from a locally installed SQL Server.
3.3 Add migration tests
Verify empty-database execution, idempotent second execution, history module/id/checksum, checksum mismatch failure, and a new migration following old history. PostgreSQL and SQL Server migration chains are automated. MySQL currently exercises the native `binary(16)` UUID v7 repository path; add module-migration cases there whenever MySQL-specific DDL is introduced or changed.
3.4 MySQL integration tests
dotnet test tests/FormPlatform.MySql.Tests/FormPlatform.MySql.Tests.csproj -c Release
`MySqlFixture` starts MySQL 8.4 and explicitly uses MySqlConnector's `GuidFormat=Binary16`. The tests create a real `binary(16)` primary key, insert an application-generated UUID v7, then verify read normalization, equality filtering, prefix text search through `BIN_TO_UUID`, update, and delete. This catches byte-order or parameter-binding regressions that cannot be detected by SQL string snapshots.
3.5 Testcontainers troubleshooting
Run `docker info`; inspect proxy/registry settings for image failures; allow first-run download time; avoid fixed port 5432; and prevent collisions because the fixture shares one container.
4. Release runtime integrity gate
After every earlier CI gate passes, the staged runtime is checked by `eng/validate-release-runtime.ps1`:
- Host, SDK, contracts, appsettings, SPA asset references, module manifests, and module entry assemblies must exist.
- `appsettings.json` must not contain non-empty connection strings, passwords, secrets, encryption keys, API keys, or private keys. Deployment secrets belong in environment variables, User Secrets, or the external deployment secrets document.
- `DataAccess:IncludeSqlParameterValues` must remain `false`.
- The existing binary module validator runs again, and `release-manifest.json` records SHA-256 hashes for every delivered file.
Run the same gate after `eng/prepare-ci-runtime.ps1` before manually publishing an artifact:
./eng/validate-release-runtime.ps1 -RuntimeDirectory .ci/runtime -WriteManifest
The resulting `formplatform-release-candidate` CI artifact is a tested, integrity-listed candidate; release approval should require the `release-readiness` job in branch protection.
5. Playwright E2E
cd tests/e2e
npm install --no-audit --no-fund
npm run install:browsers
Prepare a dedicated Host/database, apply migrations, create a role-appropriate test user, deploy tested modules, build the client, and start the Host.
$env:FORMPLATFORM_E2E_BASE_URL='http://localhost:5080'
$env:FORMPLATFORM_E2E_USER='e2e-admin'
$env:FORMPLATFORM_E2E_PASSWORD='use-a-secret-value'
cd tests/e2e
npm test
Debug with `npm run test:ui`, `npx playwright test specs/core.spec.js --headed --debug`, and `npx playwright show-trace ...`.
Current core flows cover public standalone Designer, validated client-extension discovery, authenticated Form Center, direct/refresh module routing, and preserving a mounted form after a server 409. Login tests may skip locally when credentials are absent, but missing CI credentials fail the run.
5.1 Stable E2E rules
Prefer roles, labels, and test IDs over Tailwind classes or DOM hierarchy. Mock only a boundary outside the test objective. A 409 route mock is valid for error UX; it is invalid for a real persistence test. Each test creates/cleans its own data.
Critical coverage includes Designer/Preview/Viewer, all identity modes, CRUD/failure preservation, pagination draft/final state, boolean/hidden fields, DataGrid editing/sort/export, AsyncSelect/Tree references, module direct routes, i18n/toasts/field errors, print exclusion, and Survey upload/download authorization.
6. All-module build
Expected layout:
workspace/FormPlatform
workspace/Todo
workspace/ResourceBooking
Run:
./eng/build-all.ps1 -Configuration Release
The script builds ClientApp, Host/SDK, Sample, Todo, and ResourceBooking client/server. Missing modules/manifests fail instead of being skipped. `-SkipClientBuild` provides a faster server compatibility check; path parameters can override sibling locations.
GitHub variables are `FORMPLATFORM_TODO_REPOSITORY` and `FORMPLATFORM_RESOURCE_BOOKING_REPOSITORY`; private repositories use a read-only `FORMPLATFORM_MODULES_TOKEN`.
`eng/prepare-ci-runtime.ps1` stages the Host, Vue assets, Sample, Todo, and ResourceBooking under `.ci/runtime`. Then run:
./eng/validate-module-compatibility.ps1 -RuntimeDirectory .ci/runtime
The Host's `--validate-modules` mode loads the real packages and validates manifest schema/name, module/assembly version equality, SDK/Platform ranges, SDK reference compatibility, and absence of obsolete Host references. It neither connects to a database nor starts HTTP. Playwright uses a `postgres:17-alpine` service and saves Host logs, reports, traces, and screenshots; all provider integration jobs save TRX results. Require the build, compatibility, frontend, three database, Playwright, and release-readiness jobs in branch protection.
7. Client unit, contract, and static testing
ClientApp uses Vitest for substitutions, Action Context isolation, validation/cache/debounce, Pagination, Checkbox/Radio, DataGrid models, and exhaustive built-in contracts:
cd src/FormPlatform.Host/ClientApp
npm run test:unit
npm run test:unit:watch
See Client Component Contracts. Unit tests protect pure/runtime rules; Playwright remains preferred for real control interaction. The production build validates Vue templates, dynamic imports, and bundling.
Static checks include `node --check` for plain JS, JSON/XML parsing, `git diff --check`, Markdown relative-link checking, and manifest version/schema checking. Vue SFCs require Vite or a Vue parser; `node --check` is insufficient.
8. API/error contract tests
Assert status, problem content type, and stable fields: `code`, `messageKey`, `fallback`, `fieldErrors`, `status`, and `traceId`. A 500 must not reveal SQL, connection strings, secrets, or stack traces. Empty 401/403/404 responses should receive the same platform envelope. Client tests ensure field errors remain attached to the active editor/FormReader.
9. Manual acceptance
Environment-dependent checks remain necessary for OIDC callbacks, SMTP, PayPal/Stripe/WeChat sandboxes, print/PDF, browser local date/time, IIS/Nginx forwarding, large files, production database permissions, and backup/restore.
Minimum release smoke: clean startup with no migration/module errors, login/logout, Form Center, Designer, create/update a regular record, submit one Survey, query a DataGrid, open a module route, and inspect logs for unhandled failures.
10. Test data and security
Credentials for shared or persistent test environments are CI secrets, not spec literals. The fixed password in the E2E workflow belongs only to its per-job disposable database and must never be reused. Do not use personal data. Use payment/OIDC sandboxes. Failure traces may contain form content, so limit artifact access and retention. Keep sensitive SQL parameter logging disabled.
11. Strategy template
For each feature decide whether it needs a unit rule test, PostgreSQL integration, migration history test, API contract assertion, Playwright interaction, provider-specific migration run, allow/deny matrix, or external-service/manual smoke. A bug fix starts with a reproducer; after it passes, remove obsolete workarounds.
12. Recommended CI stages
1. Frontend Vitest and component contracts.
2. Client production build plus Host/SDK/all-module build.
3. Stage the deployable runtime and validate every SDK/module package through the Host.
4. PostgreSQL integration.
5. SQL Server migration/ORM integration.
6. MySQL native UUID repository integration.
7. Isolated PostgreSQL/Host plus Playwright core.
8. Preserve runtime, TRX, Host log, and failure traces.
9. Release runtime integrity gate and SHA-256 manifest.
10. Staging external-service smoke, then production approval.
Preserve the earliest root-cause log. A successful build alone does not prove migrations, module loading, or browser flows.
---
SDK, modules, migrations, and testing
Third-party code must reference `FormPlatform.Sdk.dll` and `FormPlatform.Extension.Abstractions.dll`, never the closed Host assembly. The SDK source is owned by `src/FormPlatform.Sdk`; module lifecycle contracts are owned by `src/FormPlatform.Extension.Abstractions`; and the closed application host is `src/FormPlatform.Host`. The SDK exposes stable media, ORM, form, query, transaction, migration, endpoint, and extension contracts. A module package contains `module.json`, its entry assembly, dependencies, client assets, and documentation under `Modules/<module-name>/`.
Version compatibility
`module.json` declares the module name, entry type, module version, and minimum/maximum-exclusive SDK and platform ranges. At startup the Host validates these before calling module configuration. Compile every module against the target published SDK in CI; a successful Host build alone does not verify module compatibility.
Database migration rules
Modules register `IDatabaseMigrationModule`. Every released migration has an immutable name and checksum. The runner records applied migrations per data source and rejects edited history. To change an existing database, add a new migration; do not edit an applied migration or manually rewrite its checksum. Initializers are for idempotent system forms/seed records, not schema changes.
Within each data source, the runner always applies `FormPlatform.Core` before extension modules and then orders extensions by their stable module names. This guarantees that shared platform tables such as `app_users`, `app_departments`, media storage, and Survey infrastructure exist before a module creates foreign keys or other references to them. The rule is enforced by the Host runner, so an already compiled module does not need a new SDK interface implementation. A module must use the same data source as the Core table it references; cross-database foreign keys are not inferred or created by this ordering rule.
Tests
Run unit tests for client contract/runtime helpers, PostgreSQL and SQL Server integration tests for persistence/migrations, MySQL integration tests for its native `binary(16)` UUID path, and Playwright core-flow tests for browser behaviour. CI should gate format/static checks, Host build, SDK build, every module build, module-manifest validation, unit tests, all three database providers, and Playwright smoke flows.
Chinese edition: SDK_MODULES_MIGRATIONS_AND_TESTING.zh-CN.md.