spec_forge 0.7.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +139 -9
- data/README.md +125 -203
- data/bin/spec_forge +1 -1
- data/flake.lock +76 -4
- data/flake.nix +5 -4
- data/lib/spec_forge/attribute/chainable.rb +6 -6
- data/lib/spec_forge/attribute/environment.rb +45 -0
- data/lib/spec_forge/attribute/factory.rb +26 -17
- data/lib/spec_forge/attribute/faker.rb +6 -1
- data/lib/spec_forge/attribute/generate.rb +114 -0
- data/lib/spec_forge/attribute/literal.rb +1 -14
- data/lib/spec_forge/attribute/matcher.rb +6 -2
- data/lib/spec_forge/attribute/parameterized.rb +20 -22
- data/lib/spec_forge/attribute/resolvable_array.rb +16 -16
- data/lib/spec_forge/attribute/resolvable_hash.rb +17 -16
- data/lib/spec_forge/attribute/resolvable_struct.rb +67 -0
- data/lib/spec_forge/attribute/template.rb +118 -0
- data/lib/spec_forge/attribute/transform.rb +14 -19
- data/lib/spec_forge/attribute/variable.rb +31 -31
- data/lib/spec_forge/attribute.rb +54 -100
- data/lib/spec_forge/blueprint.rb +27 -0
- data/lib/spec_forge/cli/docs/generate.rb +28 -8
- data/lib/spec_forge/cli/docs.rb +5 -2
- data/lib/spec_forge/cli/init.rb +4 -4
- data/lib/spec_forge/cli/new.rb +78 -27
- data/lib/spec_forge/cli/run.rb +84 -52
- data/lib/spec_forge/cli/serve.rb +6 -0
- data/lib/spec_forge/cli.rb +6 -14
- data/lib/spec_forge/configuration.rb +212 -78
- data/lib/spec_forge/documentation/{loader → builder}/cache.rb +26 -23
- data/lib/spec_forge/documentation/builder/compiler.rb +373 -0
- data/lib/spec_forge/documentation/builder/extractor.rb +75 -0
- data/lib/spec_forge/documentation/builder.rb +77 -329
- data/lib/spec_forge/documentation/document/operation.rb +4 -4
- data/lib/spec_forge/documentation/document.rb +0 -6
- data/lib/spec_forge/documentation/generator.rb +88 -0
- data/lib/spec_forge/documentation/{generators/openapi → openapi/v3_0}/error_formatter.rb +2 -2
- data/lib/spec_forge/documentation/openapi/v3_0/example.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +22 -6
- data/lib/spec_forge/documentation/openapi/v3_0/response.rb +29 -7
- data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +20 -2
- data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0.rb +116 -0
- data/lib/spec_forge/documentation/openapi.rb +40 -12
- data/lib/spec_forge/documentation.rb +1 -7
- data/lib/spec_forge/error.rb +215 -41
- data/lib/spec_forge/factory.rb +38 -18
- data/lib/spec_forge/forge/action.rb +41 -0
- data/lib/spec_forge/forge/actions/call.rb +33 -0
- data/lib/spec_forge/forge/actions/debug.rb +47 -0
- data/lib/spec_forge/forge/actions/expect.rb +44 -0
- data/lib/spec_forge/forge/actions/request.rb +65 -0
- data/lib/spec_forge/forge/actions/store.rb +31 -0
- data/lib/spec_forge/forge/callbacks.rb +80 -0
- data/lib/spec_forge/forge/context.rb +41 -0
- data/lib/spec_forge/forge/display.rb +503 -0
- data/lib/spec_forge/forge/hooks.rb +131 -0
- data/lib/spec_forge/forge/runner/array_io.rb +81 -0
- data/lib/spec_forge/forge/runner/content_validator.rb +92 -0
- data/lib/spec_forge/forge/runner/header_validator.rb +66 -0
- data/lib/spec_forge/forge/runner/reporter.rb +56 -0
- data/lib/spec_forge/forge/runner/schema_validator.rb +113 -0
- data/lib/spec_forge/forge/runner.rb +118 -0
- data/lib/spec_forge/forge/timer.rb +94 -0
- data/lib/spec_forge/forge/variables.rb +38 -0
- data/lib/spec_forge/forge.rb +207 -133
- data/lib/spec_forge/http/backend.rb +49 -143
- data/lib/spec_forge/http/client.rb +14 -17
- data/lib/spec_forge/http/request.rb +37 -84
- data/lib/spec_forge/http/verb.rb +4 -0
- data/lib/spec_forge/http.rb +0 -5
- data/lib/spec_forge/loader/filter.rb +85 -0
- data/lib/spec_forge/loader/step_processor.rb +282 -0
- data/lib/spec_forge/loader.rb +105 -220
- data/lib/spec_forge/normalizer/default.rb +1 -1
- data/lib/spec_forge/normalizer/structure.rb +140 -0
- data/lib/spec_forge/normalizer/transformers.rb +168 -0
- data/lib/spec_forge/normalizer/validators.rb +50 -8
- data/lib/spec_forge/normalizer.rb +76 -119
- data/lib/spec_forge/normalizers/callback.yml +38 -0
- data/lib/spec_forge/normalizers/configuration.yml +59 -9
- data/lib/spec_forge/normalizers/factory.yml +53 -2
- data/lib/spec_forge/normalizers/factory_reference.yml +63 -2
- data/lib/spec_forge/normalizers/json_schema.yml +79 -0
- data/lib/spec_forge/normalizers/step.yml +506 -0
- data/lib/spec_forge/step/call.rb +36 -0
- data/lib/spec_forge/step/expect.rb +110 -0
- data/lib/spec_forge/step/source.rb +22 -0
- data/lib/spec_forge/step.rb +129 -0
- data/lib/spec_forge/type.rb +115 -66
- data/lib/spec_forge/version.rb +1 -1
- data/lib/spec_forge.rb +44 -106
- data/lib/templates/forge_helper.rb.tt +43 -22
- data/lib/templates/new_blueprint.yml.tt +54 -0
- metadata +75 -44
- data/lib/spec_forge/attribute/global.rb +0 -96
- data/lib/spec_forge/attribute/store.rb +0 -65
- data/lib/spec_forge/backtrace_formatter.rb +0 -50
- data/lib/spec_forge/callbacks.rb +0 -88
- data/lib/spec_forge/context/callbacks.rb +0 -91
- data/lib/spec_forge/context/global.rb +0 -72
- data/lib/spec_forge/context/store.rb +0 -131
- data/lib/spec_forge/context/variables.rb +0 -91
- data/lib/spec_forge/context.rb +0 -36
- data/lib/spec_forge/core_ext/rspec.rb +0 -55
- data/lib/spec_forge/core_ext.rb +0 -5
- data/lib/spec_forge/documentation/generators/base.rb +0 -81
- data/lib/spec_forge/documentation/generators/openapi/base.rb +0 -100
- data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +0 -65
- data/lib/spec_forge/documentation/generators/openapi.rb +0 -59
- data/lib/spec_forge/documentation/generators.rb +0 -17
- data/lib/spec_forge/documentation/loader.rb +0 -159
- data/lib/spec_forge/documentation/openapi/base.rb +0 -33
- data/lib/spec_forge/filter.rb +0 -86
- data/lib/spec_forge/normalizer/definition.rb +0 -248
- data/lib/spec_forge/normalizers/_shared.yml +0 -74
- data/lib/spec_forge/normalizers/constraint.yml +0 -8
- data/lib/spec_forge/normalizers/expectation.yml +0 -47
- data/lib/spec_forge/normalizers/global_context.yml +0 -28
- data/lib/spec_forge/normalizers/spec.yml +0 -50
- data/lib/spec_forge/runner/adapter.rb +0 -183
- data/lib/spec_forge/runner/callbacks.rb +0 -246
- data/lib/spec_forge/runner/debug_proxy.rb +0 -213
- data/lib/spec_forge/runner/listener.rb +0 -54
- data/lib/spec_forge/runner/metadata.rb +0 -58
- data/lib/spec_forge/runner/state.rb +0 -98
- data/lib/spec_forge/runner.rb +0 -75
- data/lib/spec_forge/spec/expectation/constraint.rb +0 -127
- data/lib/spec_forge/spec/expectation.rb +0 -68
- data/lib/spec_forge/spec.rb +0 -68
- data/lib/templates/new_spec.yml.tt +0 -43
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# JSON Schema Structure Definition
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Defines the structure for advanced JSON validation using explicit schemas.
|
|
5
|
+
# Used when the simpler "shape" syntax isn't sufficient, particularly for:
|
|
6
|
+
# - Union types (field can be string OR integer)
|
|
7
|
+
# - Explicit pattern vs structure control on arrays
|
|
8
|
+
# - Verbose, self-documenting style preference
|
|
9
|
+
#
|
|
10
|
+
# Key concepts:
|
|
11
|
+
# - structure = positional (element N matches item N)
|
|
12
|
+
# - pattern = repeating (all elements match same schema)
|
|
13
|
+
#
|
|
14
|
+
# Example usage in blueprints:
|
|
15
|
+
# expect:
|
|
16
|
+
# - json:
|
|
17
|
+
# schema:
|
|
18
|
+
# type: array
|
|
19
|
+
# structure:
|
|
20
|
+
# - integer # First element must be integer
|
|
21
|
+
# - string # Second element must be string
|
|
22
|
+
# - hash # Third element must be hash
|
|
23
|
+
# =============================================================================
|
|
24
|
+
|
|
25
|
+
type:
|
|
26
|
+
type:
|
|
27
|
+
- array
|
|
28
|
+
- string
|
|
29
|
+
aliases: ["types"]
|
|
30
|
+
description: |-
|
|
31
|
+
The expected type of the JSON value. Can be a single type or an
|
|
32
|
+
array of allowed types for union types. Prefix with ? for nullable,
|
|
33
|
+
* for optional, or combine (*?).
|
|
34
|
+
examples:
|
|
35
|
+
- "array"
|
|
36
|
+
- "hash"
|
|
37
|
+
- "?string"
|
|
38
|
+
- "*?string"
|
|
39
|
+
- '["string", "integer"]'
|
|
40
|
+
|
|
41
|
+
structure:
|
|
42
|
+
type:
|
|
43
|
+
- hash
|
|
44
|
+
- array
|
|
45
|
+
default: null
|
|
46
|
+
description: |-
|
|
47
|
+
Defines the internal structure of the value. Behavior depends on type:
|
|
48
|
+
- For arrays: Positional/tuple (element N matches item N)
|
|
49
|
+
- For hashes: A hash mapping keys to their type definitions
|
|
50
|
+
Each element can be a type string or a nested schema definition.
|
|
51
|
+
examples:
|
|
52
|
+
- |-
|
|
53
|
+
structure:
|
|
54
|
+
- integer
|
|
55
|
+
- string
|
|
56
|
+
- hash
|
|
57
|
+
- |-
|
|
58
|
+
structure:
|
|
59
|
+
id: integer
|
|
60
|
+
name: string
|
|
61
|
+
bio:
|
|
62
|
+
type: string
|
|
63
|
+
optional: true
|
|
64
|
+
nullable: true
|
|
65
|
+
|
|
66
|
+
pattern:
|
|
67
|
+
type: hash
|
|
68
|
+
default: null
|
|
69
|
+
description: |-
|
|
70
|
+
Defines a repeating pattern for array elements. Use when all array
|
|
71
|
+
elements should match the same structure (alternative to positional
|
|
72
|
+
structure). Contains a nested schema definition.
|
|
73
|
+
examples:
|
|
74
|
+
- |-
|
|
75
|
+
pattern:
|
|
76
|
+
type: hash
|
|
77
|
+
structure:
|
|
78
|
+
id: integer
|
|
79
|
+
name: string
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Step Structure Definition
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Defines the structure for individual steps in a SpecForge blueprint.
|
|
5
|
+
# Steps are the fundamental building blocks of API test workflows.
|
|
6
|
+
#
|
|
7
|
+
# A step can perform various actions:
|
|
8
|
+
# - Send HTTP requests and validate responses
|
|
9
|
+
# - Store values for later use
|
|
10
|
+
# - Include other blueprint files
|
|
11
|
+
# - Execute callbacks
|
|
12
|
+
#
|
|
13
|
+
# Steps execute sequentially, with later steps able to reference values
|
|
14
|
+
# from earlier steps via the {{ variable }} syntax.
|
|
15
|
+
# =============================================================================
|
|
16
|
+
|
|
17
|
+
# -----------------------------------------------------------------------------
|
|
18
|
+
# Core Attributes
|
|
19
|
+
# -----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
name:
|
|
22
|
+
type: string
|
|
23
|
+
default: ""
|
|
24
|
+
description: |-
|
|
25
|
+
Human-readable identifier for the step, displayed in test output.
|
|
26
|
+
Helps identify which step failed and documents the test's purpose.
|
|
27
|
+
examples:
|
|
28
|
+
- "Create new user"
|
|
29
|
+
- "Login as admin"
|
|
30
|
+
- "Verify user was deleted"
|
|
31
|
+
|
|
32
|
+
debug:
|
|
33
|
+
type: boolean
|
|
34
|
+
aliases:
|
|
35
|
+
- pry
|
|
36
|
+
- breakpoint
|
|
37
|
+
default: false
|
|
38
|
+
description: |-
|
|
39
|
+
When true, triggers a breakpoint before executing this step.
|
|
40
|
+
Useful for interactive debugging. Configure the debug handler
|
|
41
|
+
in forge_helper.rb with config.on_debug { binding.pry }.
|
|
42
|
+
Note: Does NOT inherit to child steps.
|
|
43
|
+
examples:
|
|
44
|
+
- true
|
|
45
|
+
- false
|
|
46
|
+
|
|
47
|
+
tags:
|
|
48
|
+
type: array
|
|
49
|
+
default: []
|
|
50
|
+
structure:
|
|
51
|
+
type: string
|
|
52
|
+
description: |-
|
|
53
|
+
Labels for organizing and filtering steps. Tags cascade down through
|
|
54
|
+
nesting and are applied to included files. Use CLI flags like
|
|
55
|
+
--tags smoke or --skip-tags slow to filter execution.
|
|
56
|
+
examples:
|
|
57
|
+
- '["smoke", "auth"]'
|
|
58
|
+
- '["crud", "users", "fast"]'
|
|
59
|
+
- '["integration", "slow"]'
|
|
60
|
+
|
|
61
|
+
documentation:
|
|
62
|
+
type: boolean
|
|
63
|
+
default: true
|
|
64
|
+
description: |-
|
|
65
|
+
Controls whether this step is included in OpenAPI documentation generation.
|
|
66
|
+
Set to false to exclude a step from the generated API docs while still
|
|
67
|
+
executing the step during test runs.
|
|
68
|
+
examples:
|
|
69
|
+
- true
|
|
70
|
+
- false
|
|
71
|
+
|
|
72
|
+
# -----------------------------------------------------------------------------
|
|
73
|
+
# Request Configuration
|
|
74
|
+
# -----------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
request: &request_structure
|
|
77
|
+
type: hash
|
|
78
|
+
default: {}
|
|
79
|
+
description: |-
|
|
80
|
+
Defines the HTTP request to send. When present, the step becomes a
|
|
81
|
+
request step that sends an HTTP request and optionally validates
|
|
82
|
+
the response. Child steps inherit and can override parent request config.
|
|
83
|
+
structure:
|
|
84
|
+
base_url:
|
|
85
|
+
type: string
|
|
86
|
+
default: null
|
|
87
|
+
aliases:
|
|
88
|
+
- base_path
|
|
89
|
+
description: |-
|
|
90
|
+
The base URL for the request (e.g., "https://api.example.com").
|
|
91
|
+
Overrides the global base_url from configuration.
|
|
92
|
+
examples:
|
|
93
|
+
- "https://api.example.com"
|
|
94
|
+
- "http://localhost:3000"
|
|
95
|
+
|
|
96
|
+
url:
|
|
97
|
+
type: string
|
|
98
|
+
default: null
|
|
99
|
+
aliases:
|
|
100
|
+
- path
|
|
101
|
+
description: |-
|
|
102
|
+
The URL path for the request. Combined with base_url to form
|
|
103
|
+
the full request URL. Supports variable interpolation.
|
|
104
|
+
examples:
|
|
105
|
+
- "/users"
|
|
106
|
+
- "/posts/{{ post_id }}"
|
|
107
|
+
- "/api/v1/users/{{ user_id }}/comments"
|
|
108
|
+
|
|
109
|
+
http_verb:
|
|
110
|
+
type: string
|
|
111
|
+
default: null
|
|
112
|
+
aliases:
|
|
113
|
+
- method
|
|
114
|
+
- http_method
|
|
115
|
+
validator: http_verb
|
|
116
|
+
description: |-
|
|
117
|
+
The HTTP method for the request. If not specified, defaults to GET.
|
|
118
|
+
examples:
|
|
119
|
+
- "GET"
|
|
120
|
+
- "POST"
|
|
121
|
+
- "PUT"
|
|
122
|
+
- "PATCH"
|
|
123
|
+
- "DELETE"
|
|
124
|
+
|
|
125
|
+
headers:
|
|
126
|
+
type: hash
|
|
127
|
+
default: null
|
|
128
|
+
description: |-
|
|
129
|
+
HTTP headers to include with the request. Child steps merge with
|
|
130
|
+
parent headers (child wins on conflicts). Supports variable interpolation.
|
|
131
|
+
examples:
|
|
132
|
+
- |-
|
|
133
|
+
headers:
|
|
134
|
+
Authorization: "Bearer {{ auth_token }}"
|
|
135
|
+
Content-Type: "application/json"
|
|
136
|
+
- |-
|
|
137
|
+
headers:
|
|
138
|
+
X-API-Key: "{{ env.API_KEY }}"
|
|
139
|
+
|
|
140
|
+
query:
|
|
141
|
+
type:
|
|
142
|
+
- hash
|
|
143
|
+
- string
|
|
144
|
+
default: null
|
|
145
|
+
aliases:
|
|
146
|
+
- params
|
|
147
|
+
description: |-
|
|
148
|
+
Query parameters appended to the URL. Can be a hash or a raw
|
|
149
|
+
query string. Supports variable interpolation.
|
|
150
|
+
examples:
|
|
151
|
+
- |-
|
|
152
|
+
query:
|
|
153
|
+
page: 1
|
|
154
|
+
per_page: 25
|
|
155
|
+
- |-
|
|
156
|
+
params:
|
|
157
|
+
filter: active
|
|
158
|
+
sort: name
|
|
159
|
+
|
|
160
|
+
raw:
|
|
161
|
+
type: string
|
|
162
|
+
default: null
|
|
163
|
+
aliases:
|
|
164
|
+
- body
|
|
165
|
+
- data
|
|
166
|
+
description: |-
|
|
167
|
+
Raw request body as a string. Use for non-JSON payloads or when
|
|
168
|
+
you need exact control over the request body format.
|
|
169
|
+
examples:
|
|
170
|
+
- "<xml><user><name>Test</name></user></xml>"
|
|
171
|
+
- "plain text body"
|
|
172
|
+
|
|
173
|
+
json:
|
|
174
|
+
type: hash
|
|
175
|
+
default: null
|
|
176
|
+
aliases:
|
|
177
|
+
- hash
|
|
178
|
+
- array
|
|
179
|
+
description: |-
|
|
180
|
+
JSON request body. Automatically serialized and sets Content-Type
|
|
181
|
+
to application/json. Supports variable interpolation and data generation.
|
|
182
|
+
examples:
|
|
183
|
+
- |-
|
|
184
|
+
json:
|
|
185
|
+
name: "{{ faker.name.first_name }}"
|
|
186
|
+
email: "{{ faker.internet.email }}"
|
|
187
|
+
- |-
|
|
188
|
+
json:
|
|
189
|
+
ids: ["id1", "id2", "id3"]
|
|
190
|
+
|
|
191
|
+
# -----------------------------------------------------------------------------
|
|
192
|
+
# Expectations (Response Validation)
|
|
193
|
+
# -----------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
expect:
|
|
196
|
+
type: array
|
|
197
|
+
default: []
|
|
198
|
+
aliases: [expects]
|
|
199
|
+
description: |-
|
|
200
|
+
Array of expectations to validate against the response. Each expectation
|
|
201
|
+
is a separate test that runs independently. Validates status codes,
|
|
202
|
+
headers, and response body content/structure.
|
|
203
|
+
structure:
|
|
204
|
+
type: hash
|
|
205
|
+
default: {}
|
|
206
|
+
structure:
|
|
207
|
+
status:
|
|
208
|
+
type:
|
|
209
|
+
- string
|
|
210
|
+
- integer
|
|
211
|
+
description: |-
|
|
212
|
+
Expected HTTP status code. Can be an exact value or a matcher.
|
|
213
|
+
examples:
|
|
214
|
+
- 200
|
|
215
|
+
- 201
|
|
216
|
+
- "{{ kind_of.integer }}"
|
|
217
|
+
|
|
218
|
+
headers:
|
|
219
|
+
type: hash
|
|
220
|
+
default: {}
|
|
221
|
+
description: |-
|
|
222
|
+
Expected response headers. Keys are header names, values are
|
|
223
|
+
expected values or matchers. Case insensitive.
|
|
224
|
+
examples:
|
|
225
|
+
- |-
|
|
226
|
+
headers:
|
|
227
|
+
Content-Type: "application/json"
|
|
228
|
+
Location: "/users/{{ user_id }}"
|
|
229
|
+
|
|
230
|
+
raw:
|
|
231
|
+
type: string
|
|
232
|
+
default: null
|
|
233
|
+
aliases:
|
|
234
|
+
- body
|
|
235
|
+
- data
|
|
236
|
+
description: |-
|
|
237
|
+
Expected raw response body as a string. Use for non-JSON
|
|
238
|
+
responses or exact string matching.
|
|
239
|
+
|
|
240
|
+
json:
|
|
241
|
+
type: hash
|
|
242
|
+
default: {}
|
|
243
|
+
aliases:
|
|
244
|
+
- array
|
|
245
|
+
- hash
|
|
246
|
+
validator: json_expectation
|
|
247
|
+
description: |-
|
|
248
|
+
JSON response validation. Supports structure validation (shape/schema),
|
|
249
|
+
size validation, and content matching. Use shape for simple cases,
|
|
250
|
+
schema for complex or positional array validation.
|
|
251
|
+
structure:
|
|
252
|
+
size:
|
|
253
|
+
type:
|
|
254
|
+
- string
|
|
255
|
+
- integer
|
|
256
|
+
description: |-
|
|
257
|
+
Validates the size of an array response. Can be an exact
|
|
258
|
+
number or a matcher for range validation.
|
|
259
|
+
examples:
|
|
260
|
+
- 5
|
|
261
|
+
- "be.greater_than: 3"
|
|
262
|
+
|
|
263
|
+
shape:
|
|
264
|
+
type:
|
|
265
|
+
- array
|
|
266
|
+
- hash
|
|
267
|
+
transformer: normalize_shape
|
|
268
|
+
description: |-
|
|
269
|
+
Type validation for JSON responses. Use for most API testing.
|
|
270
|
+
|
|
271
|
+
Type flags:
|
|
272
|
+
? = nullable (value can be null)
|
|
273
|
+
* = optional (key can be missing)
|
|
274
|
+
*? or ?* = both (missing or null allowed)
|
|
275
|
+
|
|
276
|
+
Array behavior:
|
|
277
|
+
Single item = pattern (all elements must match)
|
|
278
|
+
Multiple items = positional (element N matches item N)
|
|
279
|
+
|
|
280
|
+
Available types: string, integer, float, number (numeric), boolean (bool), hash (object), array, nil (null)
|
|
281
|
+
examples:
|
|
282
|
+
- |-
|
|
283
|
+
shape:
|
|
284
|
+
id: integer
|
|
285
|
+
name: string
|
|
286
|
+
deleted_at: ?string
|
|
287
|
+
nickname: *string
|
|
288
|
+
bio: *?string
|
|
289
|
+
- |-
|
|
290
|
+
shape:
|
|
291
|
+
- id: integer
|
|
292
|
+
name: string
|
|
293
|
+
- |-
|
|
294
|
+
shape:
|
|
295
|
+
users:
|
|
296
|
+
- id: integer
|
|
297
|
+
profile:
|
|
298
|
+
name: string
|
|
299
|
+
|
|
300
|
+
schema:
|
|
301
|
+
type:
|
|
302
|
+
- hash
|
|
303
|
+
transformer: normalize_schema
|
|
304
|
+
validator: json_schema
|
|
305
|
+
description: |-
|
|
306
|
+
Explicit structure control for JSON validation. Use when shape
|
|
307
|
+
can't express what you need:
|
|
308
|
+
- Union types (field can be string OR integer)
|
|
309
|
+
- Explicit pattern vs structure control on arrays
|
|
310
|
+
- Verbose, self-documenting style preference
|
|
311
|
+
|
|
312
|
+
Key concepts:
|
|
313
|
+
structure = positional (element N matches item N)
|
|
314
|
+
pattern = repeating (all elements match same schema)
|
|
315
|
+
|
|
316
|
+
Supports optional: and nullable: keys, or type flags (?*).
|
|
317
|
+
examples:
|
|
318
|
+
- |-
|
|
319
|
+
schema:
|
|
320
|
+
type: array
|
|
321
|
+
structure:
|
|
322
|
+
- integer
|
|
323
|
+
- string
|
|
324
|
+
- hash
|
|
325
|
+
- |-
|
|
326
|
+
schema:
|
|
327
|
+
type: hash
|
|
328
|
+
structure:
|
|
329
|
+
id: integer
|
|
330
|
+
value:
|
|
331
|
+
type: [string, integer]
|
|
332
|
+
- |-
|
|
333
|
+
schema:
|
|
334
|
+
type: hash
|
|
335
|
+
structure:
|
|
336
|
+
bio:
|
|
337
|
+
type: string
|
|
338
|
+
optional: true
|
|
339
|
+
nullable: true
|
|
340
|
+
|
|
341
|
+
content:
|
|
342
|
+
type:
|
|
343
|
+
- array
|
|
344
|
+
- hash
|
|
345
|
+
description: |-
|
|
346
|
+
Validates specific values in the response. Unlike shape/schema,
|
|
347
|
+
supports expressions, matchers, and variables. Only checks fields
|
|
348
|
+
you specify, extra fields are ignored.
|
|
349
|
+
examples:
|
|
350
|
+
- |-
|
|
351
|
+
content:
|
|
352
|
+
status: "active"
|
|
353
|
+
id: "{{ created_user_id }}"
|
|
354
|
+
- |-
|
|
355
|
+
content:
|
|
356
|
+
email: /@example\.com$/
|
|
357
|
+
count:
|
|
358
|
+
be.greater_than: 0
|
|
359
|
+
|
|
360
|
+
# -----------------------------------------------------------------------------
|
|
361
|
+
# Variable Storage
|
|
362
|
+
# -----------------------------------------------------------------------------
|
|
363
|
+
|
|
364
|
+
store:
|
|
365
|
+
type: hash
|
|
366
|
+
default: {}
|
|
367
|
+
description: |-
|
|
368
|
+
Saves values as local variables available to subsequent steps.
|
|
369
|
+
- Without request: Sets variables directly
|
|
370
|
+
- With request: Can capture values from response (response and request
|
|
371
|
+
variables are temporarily available during store: evaluation)
|
|
372
|
+
Values are available to all subsequent steps via {{ variable }} syntax.
|
|
373
|
+
Note: request, response, and index are reserved variable names.
|
|
374
|
+
examples:
|
|
375
|
+
- |-
|
|
376
|
+
store:
|
|
377
|
+
base_email: "test@example.com"
|
|
378
|
+
default_role: "user"
|
|
379
|
+
- |-
|
|
380
|
+
store:
|
|
381
|
+
user_id: "{{ response.body.id }}"
|
|
382
|
+
location: "{{ response.headers.location }}"
|
|
383
|
+
|
|
384
|
+
# -----------------------------------------------------------------------------
|
|
385
|
+
# Callbacks
|
|
386
|
+
# -----------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
call:
|
|
389
|
+
type: [string, hash, array]
|
|
390
|
+
default: []
|
|
391
|
+
aliases: [calls]
|
|
392
|
+
transformer: normalize_callback
|
|
393
|
+
validator: callback
|
|
394
|
+
description: |-
|
|
395
|
+
Executes a named callback registered in forge_helper.rb. Use for
|
|
396
|
+
database seeding, cleanup, or other custom operations. Can pass
|
|
397
|
+
arguments as a hash (keyword args) or array (positional args).
|
|
398
|
+
examples:
|
|
399
|
+
- "call: seed_database"
|
|
400
|
+
- |-
|
|
401
|
+
call:
|
|
402
|
+
name: "create_records"
|
|
403
|
+
arguments:
|
|
404
|
+
count: 50
|
|
405
|
+
type: "User"
|
|
406
|
+
- |-
|
|
407
|
+
call:
|
|
408
|
+
name: "increment_counter"
|
|
409
|
+
arguments: [1]
|
|
410
|
+
|
|
411
|
+
hook: &hook_structure
|
|
412
|
+
type: hash
|
|
413
|
+
default: {}
|
|
414
|
+
aliases: [hooks]
|
|
415
|
+
description: |-
|
|
416
|
+
Lifecycle hooks that execute callbacks at specific points during test
|
|
417
|
+
execution. Hooks allow setup and teardown logic to run before/after
|
|
418
|
+
files or individual steps.
|
|
419
|
+
|
|
420
|
+
Execution order: before hooks run outside-in, after hooks run inside-out.
|
|
421
|
+
Hooks are deduplicated by callback name at each scope.
|
|
422
|
+
examples:
|
|
423
|
+
- |-
|
|
424
|
+
hook:
|
|
425
|
+
before_blueprint: seed_database
|
|
426
|
+
after_blueprint: cleanup
|
|
427
|
+
- |-
|
|
428
|
+
hook:
|
|
429
|
+
before_step:
|
|
430
|
+
name: create_user
|
|
431
|
+
arguments:
|
|
432
|
+
role: admin
|
|
433
|
+
after_step: delete_user
|
|
434
|
+
- |-
|
|
435
|
+
hook:
|
|
436
|
+
before_blueprint:
|
|
437
|
+
- seed_users
|
|
438
|
+
- seed_posts
|
|
439
|
+
structure:
|
|
440
|
+
before_forge:
|
|
441
|
+
type: [string, hash, array]
|
|
442
|
+
transformer: normalize_callback
|
|
443
|
+
validator: callback
|
|
444
|
+
description: |-
|
|
445
|
+
Executes once before all blueprints.
|
|
446
|
+
after_forge:
|
|
447
|
+
type: [string, hash, array]
|
|
448
|
+
transformer: normalize_callback
|
|
449
|
+
validator: callback
|
|
450
|
+
description: |-
|
|
451
|
+
Executes once after all blueprints complete.
|
|
452
|
+
before_blueprint:
|
|
453
|
+
type: [string, hash, array]
|
|
454
|
+
transformer: normalize_callback
|
|
455
|
+
validator: callback
|
|
456
|
+
description: |-
|
|
457
|
+
Executes once before all steps in a blueprint.
|
|
458
|
+
after_blueprint:
|
|
459
|
+
type: [string, hash, array]
|
|
460
|
+
transformer: normalize_callback
|
|
461
|
+
validator: callback
|
|
462
|
+
description: |-
|
|
463
|
+
Executes once after all steps in a blueprint complete.
|
|
464
|
+
before_step:
|
|
465
|
+
type: [string, hash, array]
|
|
466
|
+
transformer: normalize_callback
|
|
467
|
+
validator: callback
|
|
468
|
+
description: |-
|
|
469
|
+
Executes before each step in a blueprint.
|
|
470
|
+
after_step:
|
|
471
|
+
type: [string, hash, array]
|
|
472
|
+
transformer: normalize_callback
|
|
473
|
+
validator: callback
|
|
474
|
+
description: |-
|
|
475
|
+
Executes after each step in a blueprint completes.
|
|
476
|
+
|
|
477
|
+
# -----------------------------------------------------------------------------
|
|
478
|
+
# File Inclusion
|
|
479
|
+
# -----------------------------------------------------------------------------
|
|
480
|
+
|
|
481
|
+
include:
|
|
482
|
+
type: [string, array]
|
|
483
|
+
default: []
|
|
484
|
+
aliases: [includes]
|
|
485
|
+
transformer: normalize_includes
|
|
486
|
+
description: |-
|
|
487
|
+
Injects steps from other blueprint files at load-time. Included steps
|
|
488
|
+
become indistinguishable from locally defined ones after loading.
|
|
489
|
+
Cannot be combined with other step configuration (like request, store, hook).
|
|
490
|
+
Use shared: with steps: when you need included steps to inherit configuration.
|
|
491
|
+
examples:
|
|
492
|
+
- "include: auth_setup"
|
|
493
|
+
- |-
|
|
494
|
+
include:
|
|
495
|
+
- auth_setup
|
|
496
|
+
- seed_data
|
|
497
|
+
|
|
498
|
+
# -----------------------------------------------------------------------------
|
|
499
|
+
# Inheritance
|
|
500
|
+
# -----------------------------------------------------------------------------
|
|
501
|
+
|
|
502
|
+
shared:
|
|
503
|
+
type: hash
|
|
504
|
+
structure:
|
|
505
|
+
request: *request_structure
|
|
506
|
+
hook: *hook_structure
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpecForge
|
|
4
|
+
class Step
|
|
5
|
+
#
|
|
6
|
+
# Represents a callback invocation within a step
|
|
7
|
+
#
|
|
8
|
+
# Holds the callback name and any arguments to pass when the
|
|
9
|
+
# callback is executed during step processing.
|
|
10
|
+
#
|
|
11
|
+
class Call < Data.define(:callback_name, :arguments)
|
|
12
|
+
#
|
|
13
|
+
# Transforms raw hook data into Call objects
|
|
14
|
+
#
|
|
15
|
+
# @param hooks [Hash, nil] Raw hooks hash with callback definitions
|
|
16
|
+
#
|
|
17
|
+
# @return [Hash{Symbol => Array<Call>}] Transformed hooks with Call objects
|
|
18
|
+
#
|
|
19
|
+
def self.wrap_hooks(hooks)
|
|
20
|
+
hooks = hooks&.compact_blank
|
|
21
|
+
return {} if hooks.blank?
|
|
22
|
+
|
|
23
|
+
hooks.transform_values do |call|
|
|
24
|
+
calls =
|
|
25
|
+
if call.is_a?(Set)
|
|
26
|
+
call.to_a
|
|
27
|
+
else
|
|
28
|
+
Array.wrap(call)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
calls.map { |c| Call.new(callback_name: c[:name], arguments: c[:arguments]) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|