elsa-core/test/unit/Elsa.UserTasks.Persistence.ConformanceTests
Sipke Schoorstra fec3d561d9
fix(user-tasks): include tags in EF and VNext safe search (#8146)
* fix(user-tasks): include tags in EF and VNext safe search

Safe search must cover tags per the persistence contract. InMemory already
matched tags; EF inspected only title/summary/reference/task type, and VNext
had the same gap. Search TagsJson with a bounded contains so tag-only hits
are visible without a new table.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(user-tasks): make EF tag search case-insensitive and tag-scoped

SQLite Contains on TagsJson is case-sensitive and can match JSON array
syntax between tags. Lowercase the payload for tag contains, and skip
tag matching when the query itself contains JSON punctuation, matching
InMemory/VNext individual-tag semantics.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(user-tasks): keep tag punctuation searchable on EF

The previous JSON-structure guard skipped all tag matching when the
query contained brackets or quotes, so a tag like review[urgent] was
invisible on EF while InMemory and VNext found it. Only skip the tag
path when the query spans the serialized tag delimiter.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(user-tasks): match EF tags per element, not JSON text

Map Tags as a primitive collection on the existing TagsJson column so
safe search uses Any(tag.Contains) with ToLower. That is the same
per-tag, case-insensitive semantics as InMemory/VNext: punctuation
inside a tag stays searchable, and text that only spans JSON array
syntax cannot match.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-09-14 07:27:31 +02:00
..
Faults test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
Infrastructure test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
Providers test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
ConformanceCoverageTests.cs test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
Elsa.UserTasks.Persistence.ConformanceTests.csproj test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
ProviderConformanceSuites.cs test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
README.md test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
UserTaskConformanceTestBase.cs fix(user-tasks): align query sort and cursor contract across providers (#8144) 2026-09-14 06:43:03 +02:00
UserTaskFaultInjectionConformanceTests.cs feat(user-tasks)!: migrate endpoints to structured permissions (#7999) 2026-08-27 12:06:52 +02:00
UserTaskGuestSessionConformanceTests.cs test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
UserTaskInvitationOutboxConformanceTests.cs test(user-tasks): add a persistence conformance suite with fault injection (#7986) 2026-08-25 04:36:18 +02:00
UserTaskRepositoryConformanceTests.cs fix(user-tasks): include tags in EF and VNext safe search (#8146) 2026-09-14 07:27:31 +02:00

User Tasks persistence conformance suite

One suite, run unchanged against every implementation of the User Tasks persistence contracts. It exists because the User Tasks build shipped four P1 defects that a single-threaded, happy-path, in-memory test suite could not see — every one of them was found by injecting a failure against a real store.

What it covers

Contract Suite Implementations
IUserTaskRepository UserTaskRepositoryConformanceTests InMemory, EF Core, VNext
IUserTaskGuestSessionIssuer UserTaskGuestSessionConformanceTests InMemory, EF Core
IUserTaskInvitationOutbox UserTaskInvitationOutboxConformanceTests InMemory, EF Core
The services above them UserTaskFaultInjectionConformanceTests InMemory, EF Core

UserTaskFaultInjectionConformanceTests runs the real DefaultUserTaskManager and DefaultUserTaskInvitationService against a real store and breaks the seams between them with the decorators in Faults/. A cross-store operation must either commit fully or leave the caller able to retry, and the retry must converge.

Providers

Availability is resolved once per run by ConformanceProviders, and nothing is ever skipped quietly:

  • Always run: in-memory, EF Core over SQLite, VNext over the SQLite document store.
  • Opt in with a connection string: SQL Server, PostgreSQL, Oracle. Each test reports as skipped, with the reason, when the variable is unset — not as passed.
  • Not coverable: MySQL. Pomelo.EntityFrameworkCore.MySql 9.0.0 caps Microsoft.EntityFrameworkCore.Relational at 9.0.x while this repository targets 10.0.9, so Elsa.UserTasks.Persistence.EFCore.MySql cannot be referenced from a test project at all (NU1107).
ELSA_USERTASKS_TEST_POSTGRES="Host=localhost;Database=elsa_conformance;Username=elsa;Password=elsa" dotnet test test/unit/Elsa.UserTasks.Persistence.ConformanceTests

The remaining variables are ELSA_USERTASKS_TEST_SQLSERVER and ELSA_USERTASKS_TEST_ORACLE.

Point them at a disposable database. The suite migrates the schema and isolates each test with its own tenant, but it never drops the database — that is deliberate, so it can never delete something an operator cared about.

ConformanceCoverageTests always runs. It fails if a provider that must run is unreachable, fails if a variable is set but empty (configured on the CI job, gating nothing), and writes the full matrix to the test output and to user-task-conformance-coverage.md in the output directory.

Adding a provider

  1. Add a ConformanceProvider entry to ConformanceProviders.All.
  2. Add a fixture under Providers/.
  3. Add a collection and one concrete class per contract in ProviderConformanceSuites.cs, each carrying [ConformanceProvider(...)].

A conformance class without [ConformanceProvider] is skipped with a wiring error rather than counted as coverage — the suite refuses to guess which provider a class exercised.