@cratis/pi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/package.json +24 -0
- package/skills/cratis-application-react-specifications/LICENSE +2 -0
- package/skills/cratis-application-react-specifications/SKILL.md +192 -0
- package/skills/cratis-application-slice-specifications/LICENSE +2 -0
- package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
- package/skills/cratis-arc-command/LICENSE +2 -0
- package/skills/cratis-arc-command/SKILL.md +286 -0
- package/skills/cratis-arc-command/references/command-result.md +111 -0
- package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
- package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
- package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
- package/skills/cratis-arc-command-execution/LICENSE +2 -0
- package/skills/cratis-arc-command-execution/SKILL.md +148 -0
- package/skills/cratis-arc-command-validation/LICENSE +2 -0
- package/skills/cratis-arc-command-validation/SKILL.md +230 -0
- package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
- package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
- package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
- package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
- package/skills/cratis-arc-query-paging/LICENSE +2 -0
- package/skills/cratis-arc-query-paging/SKILL.md +173 -0
- package/skills/cratis-arc-react-page/LICENSE +2 -0
- package/skills/cratis-arc-react-page/SKILL.md +291 -0
- package/skills/cratis-arc-react-page/references/data-page.md +125 -0
- package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
- package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
- package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
- package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
- package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
- package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
- package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
- package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
- package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
- package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
- package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
- package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
- package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
- package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
- package/skills/cratis-chronicle-compliance/LICENSE +2 -0
- package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
- package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
- package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
- package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
- package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
- package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
- package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
- package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
- package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
- package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
- package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
- package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
- package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
- package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
- package/skills/cratis-chronicle-projection/LICENSE +2 -0
- package/skills/cratis-chronicle-projection/SKILL.md +162 -0
- package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
- package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
- package/skills/cratis-chronicle-reactor/LICENSE +2 -0
- package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
- package/skills/cratis-chronicle-read-model/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
- package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
- package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
- package/skills/cratis-chronicle-reducer/LICENSE +2 -0
- package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
- package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
- package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
- package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
- package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
- package/skills/cratis-code-review/LICENSE +2 -0
- package/skills/cratis-code-review/SKILL.md +177 -0
- package/skills/cratis-components-accessibility/LICENSE +2 -0
- package/skills/cratis-components-accessibility/SKILL.md +176 -0
- package/skills/cratis-components-schema-editor/LICENSE +2 -0
- package/skills/cratis-components-schema-editor/SKILL.md +238 -0
- package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
- package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
- package/skills/cratis-components-styling/LICENSE +2 -0
- package/skills/cratis-components-styling/SKILL.md +221 -0
- package/skills/cratis-components-toolbar/LICENSE +2 -0
- package/skills/cratis-components-toolbar/SKILL.md +259 -0
- package/skills/cratis-documentation-writing/LICENSE +2 -0
- package/skills/cratis-documentation-writing/SKILL.md +121 -0
- package/skills/cratis-event-model-diagram/LICENSE +2 -0
- package/skills/cratis-event-model-diagram/SKILL.md +142 -0
- package/skills/cratis-fundamentals-concept/LICENSE +2 -0
- package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
- package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
- package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
- package/skills/cratis-governed-release-methodology/LICENSE +2 -0
- package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
- package/skills/cratis-lens-browser-extension/LICENSE +2 -0
- package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
- package/skills/cratis-performance-review/LICENSE +2 -0
- package/skills/cratis-performance-review/SKILL.md +139 -0
- package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
- package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
- package/skills/cratis-security-review/LICENSE +2 -0
- package/skills/cratis-security-review/SKILL.md +141 -0
- package/skills/cratis-specification-by-example/LICENSE +2 -0
- package/skills/cratis-specification-by-example/SKILL.md +188 -0
- package/skills/cratis-specifications-csharp/LICENSE +2 -0
- package/skills/cratis-specifications-csharp/SKILL.md +230 -0
- package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
- package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
- package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
- package/skills/cratis-specifications-typescript/LICENSE +2 -0
- package/skills/cratis-specifications-typescript/SKILL.md +202 -0
- package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
- package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
- package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
- package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
- package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) Cratis. All rights reserved.
|
|
3
|
+
Licensed under the MIT license. See LICENSE in this package for full license information.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# @cratis/pi
|
|
7
|
+
|
|
8
|
+
Passive AI skills for the Cratis ecosystem: the whole public skill set the
|
|
9
|
+
Cratis marketplace installs deliver, wrapped as a Pi npm package. Fundamentals,
|
|
10
|
+
Chronicle, Arc, Components, specifications, reviews, and more arrive as
|
|
11
|
+
passive markdown skills — no hooks, no executable code, no MCP server.
|
|
12
|
+
|
|
13
|
+
This package was previously published as `@cratis/ai-fundamentals`; that name
|
|
14
|
+
is deprecated in favor of `@cratis/pi`.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Install globally:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pi install npm:@cratis/pi
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Install it for one trusted project:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pi install -l npm:@cratis/pi
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Try it for one Pi run without changing settings:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pi -e npm:@cratis/pi
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Update or remove
|
|
37
|
+
|
|
38
|
+
Update to the latest published release with `pi install npm:@cratis/pi`.
|
|
39
|
+
Remove the package with `pi remove npm:@cratis/pi`.
|
|
40
|
+
|
|
41
|
+
## Status
|
|
42
|
+
|
|
43
|
+
This is a supported stable release. Review skill instructions before use.
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cratis/pi",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "The whole public Cratis AI skill set for building event-sourced and CQRS applications, delivered as a passive Pi package",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/Cratis/AI"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://cratis.io/ai",
|
|
12
|
+
"files": [
|
|
13
|
+
"skills"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"cratis"
|
|
18
|
+
],
|
|
19
|
+
"pi": {
|
|
20
|
+
"skills": [
|
|
21
|
+
"./skills"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-application-react-specifications
|
|
3
|
+
description: Write specifications for the React and TypeScript surface of a Cratis application slice — view models, helpers, command orchestration, and narrow component behavior — using Vitest with Mocha-style describe/it, Sinon, and the Chai should interface. Use when adding or changing frontend behavior in an application that consumes Cratis. Do not use for backend scenarios or for specifications inside a Cratis framework package.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cratis application React specifications
|
|
8
|
+
|
|
9
|
+
Frontend specifications are specifications, not tests. The `for_ / when_ / and_`
|
|
10
|
+
shape is deliberately the same BDD language as the backend, so one subject, one
|
|
11
|
+
context, clear setup, and small assertions read as sentences.
|
|
12
|
+
|
|
13
|
+
This is the **application** frontend peer of the backend slice specification
|
|
14
|
+
skill. Specifications for a Cratis framework package (`@cratis/*`) follow that
|
|
15
|
+
repository's own TypeScript specification conventions instead.
|
|
16
|
+
|
|
17
|
+
## Stack
|
|
18
|
+
|
|
19
|
+
- **Vitest** for the runner, with Mocha-style `describe` / `beforeEach` / `it`.
|
|
20
|
+
- **Sinon** for stubs and fakes (`sinon.stub()`, `sinon.createStubInstance()`).
|
|
21
|
+
- **Chai** with the **`.should` fluent interface** — never `expect()`.
|
|
22
|
+
`result.should.equal(expected)` reads as a sentence.
|
|
23
|
+
|
|
24
|
+
## Placement and naming
|
|
25
|
+
|
|
26
|
+
Specifications live next to the unit they specify, in snake_case so the path
|
|
27
|
+
reads as a scenario sentence:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
<Slice>/
|
|
31
|
+
<Subject>.ts
|
|
32
|
+
for_<Subject>/
|
|
33
|
+
when_<context>/
|
|
34
|
+
and_<extra_context>.ts
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Shape | Use for |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `for_<Subject>/when_<context>/and_<extra>.ts` | the default — view models, branching helpers, component specs |
|
|
40
|
+
| `for_<Subject>/when_<context>.ts` | a tiny pure helper with no useful extra context |
|
|
41
|
+
|
|
42
|
+
`describe('when ...')` names the scenario, not the class. `it('should ...')`
|
|
43
|
+
uses spaces, not underscores, and asserts one observable outcome. Do not add
|
|
44
|
+
`.test.ts` or `.spec.ts` files unless the project's configuration changes
|
|
45
|
+
deliberately.
|
|
46
|
+
|
|
47
|
+
## What to specify
|
|
48
|
+
|
|
49
|
+
Behavior that can regress without TypeScript catching it:
|
|
50
|
+
|
|
51
|
+
- **View models** — the primary target. State transitions, computed getters,
|
|
52
|
+
filtering, sorting, selection, wizard steps, validation state, and
|
|
53
|
+
command-value derivation.
|
|
54
|
+
- **Command orchestration outside a command dialog** — the unauthorized,
|
|
55
|
+
invalid, exception, and success branches.
|
|
56
|
+
- **Pure helpers** — parsing, formatting, grouping, boundary cases.
|
|
57
|
+
- **Component rendering** only when the markup, a disabled state, or wrapper
|
|
58
|
+
behavior is genuinely the point.
|
|
59
|
+
|
|
60
|
+
Do **not** specify generated Cratis proxies, PrimeReact or framework internals,
|
|
61
|
+
CSS pixel-perfection, snapshots, or trivial presentational pass-throughs.
|
|
62
|
+
|
|
63
|
+
## View-model specification — the default shape
|
|
64
|
+
|
|
65
|
+
A view model is a plain class: constructible directly, with no React hooks, no
|
|
66
|
+
direct `window`, `localStorage`, timer, or network access (inject the Cratis
|
|
67
|
+
abstraction instead), and derived values as getters.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { describe, beforeEach, it } from 'vitest';
|
|
71
|
+
import { CandidatesViewModel } from '../../CandidatesViewModel';
|
|
72
|
+
|
|
73
|
+
describe('when filtering by search text', () => {
|
|
74
|
+
let viewModel: CandidatesViewModel;
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
viewModel = new CandidatesViewModel();
|
|
78
|
+
viewModel.setSearch('senior');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should keep only matching candidates', () =>
|
|
82
|
+
viewModel.filteredItems.should.have.lengthOf(1));
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`beforeEach` does the arrange and act once for the context; each `it` asserts
|
|
87
|
+
one outcome. Pass small typed fakes or `sinon.stub()` instances for
|
|
88
|
+
dependencies, and do not build a shared harness until a second specification
|
|
89
|
+
actually reuses it.
|
|
90
|
+
|
|
91
|
+
A view model that cannot be constructed without React is doing too much or
|
|
92
|
+
depending on the wrong abstraction — that is a design signal, not a testing
|
|
93
|
+
problem.
|
|
94
|
+
|
|
95
|
+
## Command-result branches
|
|
96
|
+
|
|
97
|
+
When a command runs outside a command dialog, each granular flag is a branch
|
|
98
|
+
worth specifying: `isAuthorized`, `isValid` with its `validationResults`,
|
|
99
|
+
`hasExceptions`, and the success path. Stub the command and return a result
|
|
100
|
+
object per branch rather than reaching for the network.
|
|
101
|
+
|
|
102
|
+
## Component specification — when rendering is the point
|
|
103
|
+
|
|
104
|
+
The environment is `node`. For behavior that does not need browser events,
|
|
105
|
+
render server-side markup and mock only the external component boundary:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import React from 'react';
|
|
109
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
110
|
+
import { describe, beforeEach, it, vi } from 'vitest';
|
|
111
|
+
|
|
112
|
+
vi.mock('primereact/dialog', () => ({
|
|
113
|
+
Dialog: (props: { footer?: React.ReactNode; children?: React.ReactNode }) =>
|
|
114
|
+
React.createElement('div', null, props.footer, props.children),
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
describe('when rendered while busy', () => {
|
|
118
|
+
let html: string;
|
|
119
|
+
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
html = renderToStaticMarkup(React.createElement(MyDialog, { isBusy: true }));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should disable the confirm button', () => html.should.include('disabled'));
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Prefer typed mock props over `any`. Add jsdom or Testing Library only when the
|
|
129
|
+
behavior truly requires DOM events — do not make it the default. Avoid
|
|
130
|
+
snapshots; they hide behavior and make refactors noisy.
|
|
131
|
+
|
|
132
|
+
Cratis Components mount inside `CratisComponentsProvider`, so a specification
|
|
133
|
+
that renders one for real must provide it, and jsdom needs a `ResizeObserver`
|
|
134
|
+
stub for the table and split-pane components.
|
|
135
|
+
|
|
136
|
+
## Async and time
|
|
137
|
+
|
|
138
|
+
Use `vi.useFakeTimers()` for time-dependent behavior and restore with
|
|
139
|
+
`vi.useRealTimers()` in `afterEach`. Never depend on the real current date,
|
|
140
|
+
random values, the network, or browser storage. For suspense-query
|
|
141
|
+
specifications, clear the Arc query caches in teardown with
|
|
142
|
+
`clearSuspenseQueryCache()` and `clearSuspenseObservableQueryCache()` from
|
|
143
|
+
`@cratis/arc.react/queries`.
|
|
144
|
+
|
|
145
|
+
## Stories are not specifications
|
|
146
|
+
|
|
147
|
+
Storybook covers visual states, documentation, and manual verification; Vitest
|
|
148
|
+
covers behavior. A component with non-trivial logic usually needs both. A story
|
|
149
|
+
file is colocated with its component, uses
|
|
150
|
+
`satisfies Meta<typeof Component>` rather than a type annotation, and carries
|
|
151
|
+
`tags: ['autodocs']`. Write stories for reusable components — shared primitives
|
|
152
|
+
and slice-internal components reused across slices — not for live slice pages
|
|
153
|
+
wired to Arc query and command hooks; exercise those in the running app.
|
|
154
|
+
|
|
155
|
+
An interaction (`play:`) function belongs on stateful components only, and it is
|
|
156
|
+
documentation and QA rather than the enforced gate. The enforced behavioral gate
|
|
157
|
+
is the Vitest specification.
|
|
158
|
+
|
|
159
|
+
## The engineering bar these specifications serve
|
|
160
|
+
|
|
161
|
+
- No placeholder or dead UI ships for behavior the code actually implements.
|
|
162
|
+
- Components stay small and single-responsibility; a `// Section` comment inside
|
|
163
|
+
a component means that section is its own component.
|
|
164
|
+
- State logic lives in a view model or a tested state module, not in the
|
|
165
|
+
component — extract as soon as there are three or more `useState` calls, any
|
|
166
|
+
state-synchronizing `useEffect`, or derived values.
|
|
167
|
+
- Reuse an existing Cratis Components wrapper or shared primitive before adding
|
|
168
|
+
a new component.
|
|
169
|
+
- `unknown` or a real type, never `any`.
|
|
170
|
+
- After editing a `.tsx`, read past its final closing brace to confirm no stale
|
|
171
|
+
return block or unreachable code remains.
|
|
172
|
+
|
|
173
|
+
## Gate
|
|
174
|
+
|
|
175
|
+
Run the project's frontend test gate (typically `yarn test`) plus lint and the
|
|
176
|
+
TypeScript build. Specifications complement lint and build; they do not replace
|
|
177
|
+
them.
|
|
178
|
+
|
|
179
|
+
## Verify
|
|
180
|
+
|
|
181
|
+
- Files sit in `for_<Subject>/when_<context>/` beside the unit, in snake_case.
|
|
182
|
+
- `describe` names the scenario; `it` descriptions start with "should" and use
|
|
183
|
+
spaces.
|
|
184
|
+
- Assertions use the Chai `.should` interface, never `expect()`.
|
|
185
|
+
- Each `it` asserts one observable outcome.
|
|
186
|
+
- View models are constructed directly, with stubs for dependencies and no
|
|
187
|
+
React involved.
|
|
188
|
+
- No specification covers a generated proxy, a framework internal, or a
|
|
189
|
+
snapshot.
|
|
190
|
+
- Fake timers are restored in `afterEach` and query caches are cleared for
|
|
191
|
+
suspense specifications.
|
|
192
|
+
- Lint, the frontend test gate, and the TypeScript build all pass.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-application-slice-specifications
|
|
3
|
+
description: Specify the backend behavior of an event-sourced Cratis application slice with the in-process scenario family — CommandScenario, EventScenario, ReadModelScenario and ReactorScenario — including what to cover for each slice type and where the specification files live. Use when adding or changing backend behavior in an application built on Cratis. Do not use for framework library specifications and do not use for frontend behavior.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cratis application slice specifications
|
|
8
|
+
|
|
9
|
+
A slice is specified from the outside, through the pipeline it actually runs on.
|
|
10
|
+
The scenario family exercises the real Arc and Chronicle code paths in-process —
|
|
11
|
+
no HTTP, no fixture, no server — so a specification says what the slice does
|
|
12
|
+
rather than what its classes do.
|
|
13
|
+
|
|
14
|
+
This skill decides **which scenario** and **what to cover**. The C# mechanics
|
|
15
|
+
(`Establish`/`Because`, substitutes, contexts) live in
|
|
16
|
+
`cratis-specifications-csharp`.
|
|
17
|
+
|
|
18
|
+
## Verified product sources
|
|
19
|
+
|
|
20
|
+
| Package | Version | Purpose |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `Cratis.Arc.Testing` | `22.10.4` | `CommandScenario<TCommand>` and the `CommandResult` assertions |
|
|
23
|
+
| `Cratis.Arc.Chronicle.Testing` | `22.10.4` | `Given`, `EventLog`, `AppendedEvents` and the appended-event assertions on a command scenario |
|
|
24
|
+
| `Cratis.Chronicle.Testing` | `16.45.2` | `EventScenario`, `ReadModelScenario<T>`, `ReactorScenario<T>` |
|
|
25
|
+
| `Cratis.Specifications.XUnit` | `4.x` | The `Specification` base and the `ShouldXxx` assertions |
|
|
26
|
+
|
|
27
|
+
`Cratis.Testing` is the meta-package that brings `Cratis.Arc.Testing` and
|
|
28
|
+
`Cratis.Arc.Chronicle.Testing` together. Reverify against the owning product
|
|
29
|
+
repository before claiming support for another version.
|
|
30
|
+
|
|
31
|
+
## Route near misses
|
|
32
|
+
|
|
33
|
+
- The repository builds a Cratis **library** rather than an application: use
|
|
34
|
+
`cratis-specifications-csharp` and stay on the plain `Specification` base.
|
|
35
|
+
- React, TypeScript, or a view model: use
|
|
36
|
+
`cratis-application-react-specifications`.
|
|
37
|
+
- The subject is append semantics or a constraint on its own: use
|
|
38
|
+
`cratis-chronicle-event-specifications`.
|
|
39
|
+
- The subject is a projection or reducer on its own: use
|
|
40
|
+
`cratis-chronicle-read-model-specifications`.
|
|
41
|
+
- The question is what the slice *should do*: settle the behavior first. A
|
|
42
|
+
specification records a decision; it does not make one.
|
|
43
|
+
|
|
44
|
+
## Step 1 — Place the files
|
|
45
|
+
|
|
46
|
+
Specifications live **in the slice folder**, beside the `.cs` they specify, each
|
|
47
|
+
file wrapped in `#if DEBUG … #endif` so specification code compiles only in
|
|
48
|
+
Debug.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
<Feature>/<Slice>/
|
|
52
|
+
├── <Slice>.cs
|
|
53
|
+
└── when_<verb_phrase>/
|
|
54
|
+
├── and_<happy_scenario>.cs
|
|
55
|
+
└── and_<failure_scenario>.cs
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- Folder: `when_<verb_phrase>` — `when_registering`.
|
|
59
|
+
- File: `and_<condition>.cs` — `and_name_is_unique.cs`, `and_name_already_exists.cs`.
|
|
60
|
+
- Method: `should_<expected_result>`.
|
|
61
|
+
|
|
62
|
+
A behavior covering several subjects groups under
|
|
63
|
+
`<Slice>/for_<Subject>/when_<behavior>/`.
|
|
64
|
+
|
|
65
|
+
## Step 2 — Pick the scenario by slice type
|
|
66
|
+
|
|
67
|
+
| Slice type | Scenario | Skill |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| State Change — a command appends events | `CommandScenario<TCommand>` | this one |
|
|
70
|
+
| Append semantics, a constraint | `EventScenario` | `cratis-chronicle-event-specifications` |
|
|
71
|
+
| State View — a projection or reducer | `ReadModelScenario<TReadModel>` | `cratis-chronicle-read-model-specifications` |
|
|
72
|
+
| Automation or Translation — a reactor | `ReactorScenario<TReactor>` | `cratis-specifications-csharp` |
|
|
73
|
+
| A host, transport, or real-infrastructure boundary | Out-of-process integration | `cratis-specifications-csharp` |
|
|
74
|
+
|
|
75
|
+
## Step 3 — Cover every outcome of a State Change slice
|
|
76
|
+
|
|
77
|
+
One specification class for **each** of:
|
|
78
|
+
|
|
79
|
+
1. **The happy path** — the command succeeds and the expected event is appended.
|
|
80
|
+
2. **Each validation failure** — one `and_` class per `CommandValidator` or
|
|
81
|
+
`ConceptValidator` rule.
|
|
82
|
+
3. **Each business-rule rejection** — one `and_` class per condition in
|
|
83
|
+
`Handle()` that inspects a read model.
|
|
84
|
+
4. **Each constraint violation** — one class per constraint, written with
|
|
85
|
+
`EventScenario`, because a constraint is enforced at the append and not by
|
|
86
|
+
the command.
|
|
87
|
+
|
|
88
|
+
## Step 4 — `CommandScenario<TCommand>`
|
|
89
|
+
|
|
90
|
+
It runs authorization, the validators, `Provide()` and `Handle()` in-process and
|
|
91
|
+
exposes what was appended.
|
|
92
|
+
|
|
93
|
+
```csharp
|
|
94
|
+
#if DEBUG
|
|
95
|
+
namespace MyApp.Projects.Registration.when_registering;
|
|
96
|
+
|
|
97
|
+
public class and_name_is_unique : Specification
|
|
98
|
+
{
|
|
99
|
+
readonly CommandScenario<RegisterProject> _scenario = new();
|
|
100
|
+
readonly ProjectId _id = ProjectId.New();
|
|
101
|
+
CommandResult _result;
|
|
102
|
+
|
|
103
|
+
async Task Because() => _result = await _scenario.Execute(new RegisterProject(_id, "My Project"));
|
|
104
|
+
|
|
105
|
+
[Fact] void should_succeed() => _result.ShouldBeSuccessful();
|
|
106
|
+
[Fact] async Task should_have_appended_the_registered_event() =>
|
|
107
|
+
await _scenario.ShouldHaveAppendedEvent<RegisterProject, ProjectRegistered>(_id, e => e.Name == "My Project");
|
|
108
|
+
}
|
|
109
|
+
#endif
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The scenario's own surface is `Services`, `Context`, `Execute(command)` and
|
|
113
|
+
`Validate(command)` — `Validate` runs the filters without the handler.
|
|
114
|
+
Everything else is an extension method.
|
|
115
|
+
|
|
116
|
+
## Step 5 — Seed prior state through `Given`
|
|
117
|
+
|
|
118
|
+
With `Cratis.Arc.Chronicle.Testing` referenced, a command scenario has a
|
|
119
|
+
Chronicle-backed `Given` builder, plus `EventScenario`, `EventLog`,
|
|
120
|
+
`EventSequence` and `AppendedEvents`.
|
|
121
|
+
|
|
122
|
+
```csharp
|
|
123
|
+
// Seed the events the world already contains. A read model the command injects
|
|
124
|
+
// for this event source is materialized from them by its own projection or
|
|
125
|
+
// reducer — no read-model type is named.
|
|
126
|
+
_scenario.Given.ForEventSource(_cartId).Events(new ItemAddedToCart(itemId));
|
|
127
|
+
|
|
128
|
+
// Or pin a specific read-model value directly, when the events are beside the point.
|
|
129
|
+
_scenario.Given.ForEventSource(_cartId).ReadModel(new CartSummary { ItemCount = 3 });
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Prefer seeding **events**: it exercises the projection that production relies on,
|
|
133
|
+
so a specification cannot pass against a read-model shape the projection never
|
|
134
|
+
produces. Pin an instance only when the events would be noise.
|
|
135
|
+
|
|
136
|
+
Both builder methods return `void`; there is nothing to await.
|
|
137
|
+
|
|
138
|
+
Validator and `Provide()` dependencies are registered in `_scenario.Services`;
|
|
139
|
+
the concrete validator itself is discovered automatically.
|
|
140
|
+
|
|
141
|
+
## Step 6 — Assert twice on every unhappy path
|
|
142
|
+
|
|
143
|
+
```csharp
|
|
144
|
+
[Fact] void should_not_succeed() => _result.ShouldNotBeSuccessful();
|
|
145
|
+
[Fact] void should_have_validation_errors() => _result.ShouldHaveValidationErrors();
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`ShouldNotBeSuccessful()` alone cannot distinguish a validation rejection from an
|
|
149
|
+
unhandled exception, so both facts are required. **Never assert on a message
|
|
150
|
+
string** — it is presentation text.
|
|
151
|
+
|
|
152
|
+
`CommandResult` assertions, from `Cratis.Arc.Testing.Commands`, throwing
|
|
153
|
+
`CommandResultAssertionException`:
|
|
154
|
+
|
|
155
|
+
| Assertion | Says |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `ShouldBeSuccessful()` / `ShouldNotBeSuccessful()` | Authorized, valid, no exceptions — or not |
|
|
158
|
+
| `ShouldBeValid()` | Validation only; it checks neither authorization nor exceptions |
|
|
159
|
+
| `ShouldHaveValidationErrors()` | At least one validation error |
|
|
160
|
+
| `ShouldHaveValidationErrorFor(message)` | A validation error with that **message** |
|
|
161
|
+
| `ShouldHaveValidationErrorBecauseOf(reason)` | A validation error with that `ValidationResultReason` |
|
|
162
|
+
| `ShouldBeAuthorized()` / `ShouldNotBeAuthorized()` | The authorization outcome |
|
|
163
|
+
| `ShouldHaveExceptions()` / `ShouldNotHaveExceptions()` | Whether the handler threw |
|
|
164
|
+
| `ShouldHaveConstraintViolationFor(name)` | A constraint violation surfaced on the command result |
|
|
165
|
+
|
|
166
|
+
`ShouldHaveValidationErrorBecauseOf(reason)` is the message-free way to say
|
|
167
|
+
*which kind* of rejection happened — `ValidationResultReason.ConstraintViolation`,
|
|
168
|
+
`ConcurrencyViolation`, `ValidatorFailed`, `Rule`. Prefer it over
|
|
169
|
+
`ShouldHaveValidationErrorFor(message)`, which pins presentation text.
|
|
170
|
+
|
|
171
|
+
Authorization failures are different in kind: an unauthorized result carries
|
|
172
|
+
**no** validation errors, so assert `ShouldNotBeAuthorized()`. Adding an
|
|
173
|
+
authorization attribute to an existing command therefore breaks both its
|
|
174
|
+
happy-path and its validation-failure specifications — switch the assertions
|
|
175
|
+
rather than patching around them.
|
|
176
|
+
|
|
177
|
+
## Step 7 — Assert what was appended
|
|
178
|
+
|
|
179
|
+
The appended-event assertions come from `Cratis.Arc.Chronicle.Testing` and are
|
|
180
|
+
keyed by **command and event type**. They return `Task`, so the fact is
|
|
181
|
+
`async Task`, and they throw `EventSequenceAssertionException`.
|
|
182
|
+
|
|
183
|
+
- `ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId)`
|
|
184
|
+
- `ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId, Func<TEvent, bool> predicate)`
|
|
185
|
+
- `ShouldHaveTailSequenceNumber<TCommand>(sequenceNumber)`
|
|
186
|
+
|
|
187
|
+
⚠️ On a command scenario the second argument is a **predicate**
|
|
188
|
+
(`Func<TEvent, bool>`), not an assertion callback. The `Action<TEvent>` validator
|
|
189
|
+
overloads exist only on the `IEventSequence` extensions in
|
|
190
|
+
`Cratis.Chronicle.Testing`, which is a different receiver.
|
|
191
|
+
|
|
192
|
+
Sequence numbers are **zero-based**: the first event is `0`, and the tail after a
|
|
193
|
+
single append is `0`, never `1`.
|
|
194
|
+
|
|
195
|
+
## What breaks
|
|
196
|
+
|
|
197
|
+
- **A specification passes alone and fails in the suite.** A hard-coded value
|
|
198
|
+
collided with another specification's uniqueness claim. Use a per-specification
|
|
199
|
+
value — a fresh `Guid`, or `Guid.NewGuid().ToString("N")[..9]`. Adding
|
|
200
|
+
`[Collection(…)]` is the wrong fix here.
|
|
201
|
+
- **`Given` does not resolve.** `Cratis.Arc.Chronicle.Testing` (or the
|
|
202
|
+
`Cratis.Testing` meta-package) is not referenced. It is an extension member,
|
|
203
|
+
not part of `CommandScenario` itself.
|
|
204
|
+
- **A DCB rejection specification is green for the wrong reason.** The read model
|
|
205
|
+
was never seeded, so the handler saw a default or `null` instance and rejected
|
|
206
|
+
on that instead of on the rule. Seed, then confirm the specification fails when
|
|
207
|
+
the seeding is removed.
|
|
208
|
+
- **The command scenario reports success but nothing was appended.** The
|
|
209
|
+
assertion is on `CommandResult` only; add a
|
|
210
|
+
`ShouldHaveAppendedEvent<TCommand, TEvent>` fact.
|
|
211
|
+
- **Injected-validator branches flip with test ordering.** The command-scenario
|
|
212
|
+
pipeline can cache enough state that a validator branch depends on execution
|
|
213
|
+
order under parallel xUnit runs. Use `CommandScenario` for the valid path, test
|
|
214
|
+
rejected state variants by instantiating the validator directly, and only then
|
|
215
|
+
put that command's specifications in a small `[Collection]`.
|
|
216
|
+
|
|
217
|
+
## How it is proven
|
|
218
|
+
|
|
219
|
+
`dotnet build -c Debug` — it compiles the `#if DEBUG` specification code — then
|
|
220
|
+
`dotnet test`, both clean, with a specification for the happy path and one per
|
|
221
|
+
rejection. Before trusting a rejection specification green, remove the condition
|
|
222
|
+
that causes the rejection once and confirm it goes red.
|