Skip to content

Render Given/When/Then into Cratis specifications - #36

Closed
woksin wants to merge 1 commit into
mainfrom
feat/render-specifications
Closed

Render Given/When/Then into Cratis specifications#36
woksin wants to merge 1 commit into
mainfrom
feat/render-specifications

Conversation

@woksin

@woksin woksin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Added

  • A Screenplay specification exercising its slice's command renders as a Cratis spec — a CommandScenario<T> in the when_ folder layout, wrapped in #if DEBUG. Appended events are asserted against the event source the command's identifier names, narrowed to the values the specification states; a rejection asserts both that the command failed and that it failed as validation. (Render Given/When/Then into Cratis Specifications #14)

Changed

A specification exercising the slice's own command renders as a
CommandScenario<T> spec - one file each, in the when_ folder layout the
conventions use, wrapped in #if DEBUG. Appended events assert against
the event source the command's identifier names; a rejection asserts
both that the command failed and that it failed as validation, because
ShouldNotBeSuccessful alone cannot tell a rejection from an exception.

A specification that cannot be rendered faithfully is not rendered at
all and says why. The interesting case is 'given': CommandScenario does
support seeding prior events, so the gap is in the document rather than
the target - a given event names no event source, and it is frequently
not the command's own. A specification asserting that a duplicate
invoice number is rejected seeds an InvoiceRegistered for a different
invoice than the one it registers, and seeding it against the command's
id would assert something else entirely.
@woksin woksin added the minor label Aug 13, 2026
@woksin

woksin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Reviewer context.

minor, not major — nothing public changes shape. Specifications render through a new SpecificationRenderer that CratisRenderer calls alongside the slice renderer, rather than by widening ISliceRenderer.Render to return many files. That kept it additive; the return-type change would have been a break for no gain.

#14's premise is stale, and that changed the design. The issue records "structural gap: CommandScenario has no Given/Events". It has since gained scenario.Given.ForEventSource(id).Events(…) and .ReadModel<T>(…) in Cratis.Arc.Chronicle.Testing. So the reason given is not rendered is not the target — it is the document: a given event names no event source, and it is frequently not the command's own. RejectingAnInvoiceWhoseNumberIsAlreadyTaken in invoicing.play seeds an InvoiceRegistered for a different invoice than the one it registers, precisely because the point is a collision. Seeding it against the command's id would produce a spec that passes for the wrong reason, which is worse than not having it. Rendering given needs identity in the language, not a heuristic here.

Measured on invoicing.play — the 17-slice document with every construct:

rendered, and compiling 1 (RejectingAnInvoiceWithNoLines)
declined, with the reason on stderr 3 — two seed prior state, one states read model state

Before this, UnrenderedConstructs reported all four as "no specs are rendered for the generated application" and that was the whole of it.

Verified by compiling, not by reading. should_render_specs_that_compile renders the slice, its concept and two specs — an appended-event one and a rejection one — and compiles the four files together against the real Cratis.Arc.Testing and Cratis.Arc.Chronicle.Testing assemblies. That is what caught the API shapes: ShouldHaveAppendedEvent<TCommand, TEvent> is an extension in Cratis.Arc.Chronicle.Testing.Commands, not on the scenario, and the two testing packages had to join the spec-only reference set.

Bite proven. Reverting the given rule fails should_decline_a_specification_that_seeds_prior_state; dropping the value predicate fails should_assert_the_appended_event_against_its_event_source. Solution-wide: 421 specs green, Debug and Release both zero warnings.

A spec defended the old behavior and its premise moved rather than its assertion — for_UnrenderedConstructs asserted specifications appear in the dropped list. They no longer belong there; it now asserts they do not, with a comment saying why.

Values are type-directed, because a document writes a uuid as text: "9c858901-…" into a Uuid concept renders as Guid.Parse("9c858901-…"), an enum member as the member, and a literal no conversion reaches is reported rather than forced. A command property the specification says nothing about is constructed as a missing value and reported — worth knowing when the rendered spec later fails.

Not verified: no rendered spec has been executed. It compiles against the real testing assemblies; whether CommandScenario then behaves as the document intends is the next question, and needs a rendered application that builds — which is what the remaining 15 errors on invoicing.play are about.

Conflicts with #35 in Rendering.Cratis.csproj and Directory.Packages.props — both add package references. Trivial to resolve; #35 should land first as the base.

@woksin

woksin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Folded into #35 — same files, same review, one release. The specification rendering is unchanged; #35 now carries both it and the packaging split, relabelled minor after measuring the blast radius on the only consumer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant