diff --git a/docs/src/content/docs/local-testing/testing-harness.mdx b/docs/src/content/docs/local-testing/testing-harness.mdx index 5ed7ccce..ae5f5dc2 100644 --- a/docs/src/content/docs/local-testing/testing-harness.mdx +++ b/docs/src/content/docs/local-testing/testing-harness.mdx @@ -51,10 +51,49 @@ The resolved handle is a `DeployedStack`: - `outputs` — the built outputs, keyed by lexicon. What was deployed, available for template-level assertions. - `entities` — the discovered entities, keyed by name. - `env` — the environment this deploy targeted. The teardown key. +- `assertLive(name, options?)`, observation-backed assertions against the live stack. Covered below. - `destroy()` — the marker-scoped sweep of that environment, in-process. The same operation as [`chant lifecycle teardown --yes`](/chant/guide/reconciling-lifecycle/). `destroy()` throws a `TeardownIncompleteError` when any candidate failed to delete or the plan had holes (something chant may own could not be read). An environment that cannot be called clean is a test failure, never a silent leak. It is safe to call again: teardown is stateless, and a second call over a clean environment plans nothing. +## Asserting the deploy is live + +`entities.has("taskQueue")` and the `outputs` map both check what was declared and built, not what is actually running. `assertLive` is the other half, calling `describeResources()` against exactly one named entity and reporting back with either a resolved value or a thrown error. Reach for it whenever a test needs to know the live stack, not just the plan that was sent to it. + +```typescript +assertLive(name: string, options?: { status?: string }): Promise +``` + +The call takes two forms. + +```typescript +// exists, marker-verified +const bucket = await stack.assertLive("dataBucket"); + +// exists, marker-verified, and reports this status +const queue = await stack.assertLive("taskQueue", { status: "CREATE_COMPLETE" }); +``` + +On success it resolves to the entity's `ResourceMetadata`. On failure it throws rather than returning a falsy result, for three distinct reasons. + +- The entity is observed absent. `describeResources` looked, and the environment reported no such resource, so `assertLive` throws `LiveAssertionError`. +- The entity is not observed at all, an unmapped kind, a failed call, missing credentials. That counts as neither a pass nor an absence, the same tri-state [the observation contract](/chant/lexicon-authoring/observation-contract/#the-observation-tri-state-absent-is-not-the-same-as-unread) draws for `lifecycle plan`, so `assertLive` throws `UnobservedAssertionError`, a distinct type so a suite can catch could-not-tell apart from a confirmed miss. +- A same-named resource is observed, but it carries another deploy's `{ stack, env }` marker, or no marker at all where the lexicon's ownership channel says one should be present, so `assertLive` throws `LiveAssertionError` naming what it found. + +Both error types are exported from `@intentius/chant/testing`: + +```typescript +import { LiveAssertionError, UnobservedAssertionError } from "@intentius/chant/testing"; +``` + +Endpoint resolution is identical to `deployStack` and `destroy()`: ambient endpoint variables win, then the target environment's declared `endpoint`, then the real cloud. See [Emulator or real cloud](#emulator-or-real-cloud) below. + +`assertLive` checks existence and status only. Property-level assertions, comparing a live resource's actual configuration against what was declared, wait on deep observation (chant #1014); `status` is as deep as v1 goes. + + + ## What the project must declare Two config requirements, both about identity: