# `tests/conformance/`

L1 application-shape scenario tests for the platform runtime defined in
`backend/internal/features/automation` (and, in the future, other engines).

Each subdirectory is one L1 pack's scenario set, in its own Go module. The
L1 application vocabulary each pack needs (`Lead`, `Account`, `Issue`,
`Project`, …) **never enters the `backend/` source tree** — a load-bearing
constraint of the L0/L1 split (see top-level `CLAUDE.md` "Coding Red
Lines" and "Strong vs transitional reading").

> **Note on this document.** The L1 names below (`Lead`, `Account`,
> `Issue`, `Project`, …) are illustrative examples of what each pack's
> tests would use — they show the convention concretely. This convention
> document itself is application-neutral mechanism; nothing in it makes
> the platform aware of any specific L1 pack.

## Layout

```
tests/conformance/
├── README.md                      ← this file
├── sf-crm/                        ← Salesforce-CRM-shaped scenarios
│   ├── go.mod                     ← own module, replace → ../../../backend
│   ├── go.sum
│   ├── README.md
│   └── *_test.go                  ← uses Lead, Account, Contact, …
├── jira-like/                     ← (future) Jira-like-shaped scenarios
│   ├── go.mod
│   ├── go.sum
│   └── *_test.go                  ← uses Issue, Project, Sprint, …
└── …
```

## Cross-module path

External scenario tests import the L0 bridge package
`github.com/nexuscrm/backend/pkg/automationtesting` (added by PR #275),
which sits in the backend module but **outside `internal/`** so external
modules can import it. Scenario tests **must not** import
`backend/internal/...` directly: Go's `internal/` rule is enforced at the
module boundary, and a `replace` directive does not bypass it.

```
tests/conformance/<pack>/         own module
    └── imports → backend/pkg/automationtesting   ← public bridge (L0)
                      └── imports → backend/internal/features/automation   ← runtime
```

## Why a separate Go module per pack

Three constraints have to hold simultaneously:

1. **L1 application vocabulary may not appear in `backend/`** — the
   platform binary must remain L0-pure, or the strong reading of the
   metadata-driven boundary breaks (CLAUDE.md "Strong vs transitional
   reading").
2. **Scenarios need to drive `automation.FlowExecutor` programmatically** —
   black-box-only is too coarse for some internal branches.
3. **Go's `internal/` rule** restricts `backend/internal/...` imports to
   the `backend` module.

A test file at `tests/conformance/sf-crm/lead_insert_test.go` containing
`"Lead"` strings satisfies (1) because the path is outside `backend/`. It
satisfies (2) and (3) by going through the bridge package.

The cost is a per-pack `go.mod` plus a `replace` directive pinning the
`backend` and `shared` modules to the local checkout:

```
module nexuscrm-tests/<pack>

go 1.25

require (
    github.com/nexuscrm/backend v0.0.0
    github.com/nexuscrm/shared v0.0.0
)

replace github.com/nexuscrm/backend => ../../../backend
replace github.com/nexuscrm/shared => ../../../shared
```

This is the explicit price of the L0/L1 split for tests that need internal
runtime affordances. Scenarios that can run end-to-end through public APIs
should still go to `tests/integration/` — that path is cheaper, more
realistic, and was reaffirmed by the PR #173 → #196 revert as the
preferred shape for "conformance"-level coverage.

## Running a pack's scenarios

```bash
cd tests/conformance/<pack>
go test ./...
```

Each pack's `README.md` may add pack-specific instructions (fixtures,
env vars, pre-installed packs).

## Adding a new pack

1. Create `tests/conformance/<pack>/`.
2. Add `go.mod`, `go.sum`, `README.md`, and a first `*_test.go`.
3. Use `backend/pkg/automationtesting` for generic rigs, stubs, builders,
   and snapshot comparison. Pass the SObject name (and any other L1
   facts) in as a parameter.
4. The pack's tests are the only place application vocabulary appears.

If the helper you need does not yet exist in
`backend/pkg/automationtesting`, add it there in a separate PR (with
tests) before importing it from a pack module. Do not vendor / inline /
fork helper code into the pack module.

## Anti-patterns

| ❌ Don't | ✅ Do |
|---|---|
| Put `Lead`, `Account`, etc. in `backend/pkg/automationtesting/...` | Pass them as parameters from `tests/conformance/sf-crm/...` |
| Import `backend/internal/...` directly from a pack module | Always go through `backend/pkg/automationtesting` |
| Reach for unexported flow-runtime fields via `_test.go` in `package automation` | Add a generic `automation.With...` Option, then a `RigOption` in `automationtesting`, then use that from external tests |
| Cross-pack helpers that hardcode multi-pack vocab | Per-pack helpers; if a pattern recurs, parameterize and lift to `automationtesting` |
| Replace `tests/integration/<feature>/` with `tests/conformance/<pack>/` | They serve different purposes — integration is black-box end-to-end; conformance scenarios drive runtime branches via the bridge |
| Author a "conformance baseline matrix" document with mostly-FAIL cells caused by stubbed dependencies | That was the PR #173 / #196 cautionary path; snapshot tests pass or fail, period |
