spec_forge 0.7.1 → 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 +75 -1
- data/README.md +124 -202
- 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 +5 -0
- data/lib/spec_forge/cli.rb +6 -14
- data/lib/spec_forge/configuration.rb +209 -79
- 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 +21 -5
- data/lib/spec_forge/documentation/openapi/v3_0/response.rb +28 -6
- 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 -146
- 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 -76
- 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 -181
- data/lib/spec_forge/runner/callbacks.rb +0 -246
- data/lib/spec_forge/runner/debug_proxy.rb +0 -215
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a63a7870c53ed507204c4848b271094c53a5be8d2925e7b04b359f6885b61cc
|
|
4
|
+
data.tar.gz: 12684cb3c690072fe1ead4c4ef5d4e9b2fb13d2e40f27c5eeea75fba9cdbff0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f656557492767c8f2391f334461ee4efbcad40118f7608b9d26ba6e5ee1492c3adddce4d76565a79b145b4d2f017de9f43aabbdfdf7e9e837fc71c77b54a4555
|
|
7
|
+
data.tar.gz: dbaf154a1d50d7399d7c25d5655ad9809185dd410de71d3f2b62809435dfdf75b1d0948ddcc848059c2ee0b7053fc31c148dac164c63636edf2b823c5562a963
|
data/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,79 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
### Removed
|
|
16
16
|
-->
|
|
17
17
|
|
|
18
|
+
## [1.0.0] - 12026-01-29
|
|
19
|
+
|
|
20
|
+
### ⚠️ Breaking Changes
|
|
21
|
+
|
|
22
|
+
This release is a complete architectural redesign. See the [Migration Guide](https://github.com/itsthedevman/spec_forge/wiki/Migration-Guide) for detailed upgrade instructions.
|
|
23
|
+
|
|
24
|
+
**Core changes:**
|
|
25
|
+
- Directory renamed: `spec_forge/specs/` → `spec_forge/blueprints/`
|
|
26
|
+
- CLI renamed: `spec_forge new spec` → `spec_forge new blueprint`
|
|
27
|
+
- YAML structure rewritten from spec-based to sequential step-based workflows
|
|
28
|
+
- Variable syntax changed: `variables.name` → `{{ name }}` template syntax
|
|
29
|
+
- Execution architecture changed: Forge now orchestrates workflows and delegates validation to RSpec, replacing the previous fully RSpec-driven approach
|
|
30
|
+
|
|
31
|
+
**Configuration:**
|
|
32
|
+
- Removed: `config.headers`, `config.query` (use explicit inheritance instead)
|
|
33
|
+
- Changed: `config.specs` → `config.rspec`
|
|
34
|
+
- Changed: `config.on_debug = proc` → `config.on_debug { block }`
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **Step-based workflows**: Tests execute as sequential steps with explicit data flow between them. See [Writing Tests](https://github.com/itsthedevman/spec_forge/wiki/Writing-Tests).
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- name: "Create user"
|
|
42
|
+
request:
|
|
43
|
+
url: /users
|
|
44
|
+
http_verb: POST
|
|
45
|
+
json:
|
|
46
|
+
email: "{{ faker.internet.email }}"
|
|
47
|
+
expect:
|
|
48
|
+
- status: 201
|
|
49
|
+
store:
|
|
50
|
+
user_id: "{{ response.body.id }}"
|
|
51
|
+
|
|
52
|
+
- name: "Fetch created user"
|
|
53
|
+
request:
|
|
54
|
+
url: "/users/{{ user_id }}"
|
|
55
|
+
expect:
|
|
56
|
+
- status: 200
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- **Template variable system**: New `{{ }}` syntax for dynamic values—supports variables, Faker, factories, and environment variables. See [Dynamic Features](https://github.com/itsthedevman/spec_forge/wiki/Dynamic-Features).
|
|
60
|
+
|
|
61
|
+
- **JSON validation modes**: Three modes for response validation—`shape:` for type checking with nullable/optional flags, `content:` for value matching, and `schema:` for explicit structure control. See [Validating Responses](https://github.com/itsthedevman/spec_forge/wiki/Validating-Responses).
|
|
62
|
+
|
|
63
|
+
- **Configuration inheritance**: The `shared:` wrapper applies request configuration and hooks to nested steps, making auth flows and grouped operations cleaner.
|
|
64
|
+
|
|
65
|
+
- **Lifecycle hooks**: Register callbacks for `before`/`after` events at forge, blueprint, and step levels. See [Callbacks](https://github.com/itsthedevman/spec_forge/wiki/Callbacks).
|
|
66
|
+
|
|
67
|
+
- **Tag-based filtering**: Organize steps with tags and run subsets via `--tags` and `--skip-tags` CLI options. See [Running Tests](https://github.com/itsthedevman/spec_forge/wiki/Running-Tests).
|
|
68
|
+
|
|
69
|
+
- **Improved output display**: Verbosity levels (`--verbose`, `--debug`, `--trace`) with colorized terminal output, detailed failure context, and YAML line number tracking in error messages.
|
|
70
|
+
|
|
71
|
+
- **File includes**: Extract common workflows into separate files and inject them with `include:`.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- Request options now nested under `request:` key
|
|
76
|
+
- Expectations simplified from `expectations: [{ expect: ... }]` to `expect: [...]`
|
|
77
|
+
- Error messages now include YAML line numbers and source file context
|
|
78
|
+
- Factory references now support traits and attribute overrides via expanded syntax
|
|
79
|
+
- Global variables defined in `forge_helper.rb` via `config.global_variables` instead of YAML
|
|
80
|
+
|
|
81
|
+
### Removed
|
|
82
|
+
|
|
83
|
+
- Global context system (`Context::Global`, `Context::Store`, `Context::Variables`)
|
|
84
|
+
- Spec and Expectation classes (replaced with Step-based architecture)
|
|
85
|
+
- Global headers/query configuration (use `shared:` inheritance instead)
|
|
86
|
+
|
|
87
|
+
**[Full documentation](https://github.com/itsthedevman/spec_forge/wiki)** | **[Migration Guide](https://github.com/itsthedevman/spec_forge/wiki/Migration-Guide)**
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
18
91
|
## [0.7.1] - 12025-10-08
|
|
19
92
|
|
|
20
93
|
### Added
|
|
@@ -394,7 +467,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
394
467
|
|
|
395
468
|
- Initial commit
|
|
396
469
|
|
|
397
|
-
[unreleased]: https://github.com/itsthedevman/spec_forge/compare/
|
|
470
|
+
[unreleased]: https://github.com/itsthedevman/spec_forge/compare/v1.0.0...HEAD
|
|
471
|
+
[1.0.0]: https://github.com/itsthedevman/spec_forge/compare/v0.7.1...v1.0.0
|
|
398
472
|
[0.7.1]: https://github.com/itsthedevman/spec_forge/compare/v0.7.0...v0.7.1
|
|
399
473
|
[0.7.0]: https://github.com/itsthedevman/spec_forge/compare/v0.6.0...v0.7.0
|
|
400
474
|
[0.6.0]: https://github.com/itsthedevman/spec_forge/compare/v0.5.0...v0.6.0
|
data/README.md
CHANGED
|
@@ -4,254 +4,176 @@
|
|
|
4
4
|

|
|
5
5
|
[](https://github.com/itsthedevman/spec_forge/actions/workflows/main.yml)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Write API tests in YAML that read like documentation and generate OpenAPI specifications:
|
|
7
|
+
Write API tests as sequential workflows in YAML. Generate OpenAPI documentation automatically.
|
|
10
8
|
|
|
11
9
|
```yaml
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
10
|
+
# spec_forge/blueprints/users.yml
|
|
11
|
+
- name: "Create and verify user"
|
|
12
|
+
request:
|
|
13
|
+
url: /users
|
|
14
|
+
http_verb: POST
|
|
15
|
+
json:
|
|
16
|
+
name: "Jane Smith"
|
|
17
|
+
email: "jane@example.com"
|
|
18
|
+
expect:
|
|
19
|
+
- status: 201
|
|
20
|
+
json:
|
|
21
|
+
shape:
|
|
22
|
+
id: integer
|
|
23
|
+
name: string
|
|
24
|
+
email: string
|
|
25
|
+
store:
|
|
26
|
+
user_id: "{{ response.body.id }}"
|
|
27
|
+
|
|
28
|
+
- name: "Fetch created user"
|
|
29
|
+
request:
|
|
30
|
+
url: "/users/{{ user_id }}"
|
|
31
|
+
expect:
|
|
32
|
+
- status: 200
|
|
33
|
+
json:
|
|
34
|
+
content:
|
|
35
|
+
name: "Jane Smith"
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
Two steps. One workflow. No Ruby code required.
|
|
34
39
|
|
|
35
40
|
## Why SpecForge?
|
|
36
41
|
|
|
37
|
-
**For Testing
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
- **Clear Syntax**: Tests that everyone can read and understand, regardless of Ruby expertise
|
|
42
|
-
|
|
43
|
-
**For Documentation:**
|
|
44
|
-
|
|
45
|
-
- **OpenAPI Generation**: Generate OpenAPI specifications from your test structure, with full customization through configuration files
|
|
46
|
-
- **Living Documentation**: Your tests ensure the documentation always matches your actual API behavior
|
|
47
|
-
- **Professional Output**: View your API docs in Swagger UI or Redoc with minimal setup
|
|
48
|
-
|
|
49
|
-
**For Teams:**
|
|
50
|
-
|
|
51
|
-
- **Developer & QA Collaboration**: Create specifications that both developers and QA can maintain
|
|
52
|
-
- **Gradual Adoption**: Use alongside your existing test suite, introducing it incrementally where it makes sense
|
|
42
|
+
**For Testing**
|
|
43
|
+
- Write tests the way you think about APIs - as sequences of actions
|
|
44
|
+
- Zero boilerplate: no HTTP client setup, no configuration objects
|
|
45
|
+
- Full access to RSpec matchers, Faker, and FactoryBot without writing Ruby
|
|
53
46
|
|
|
54
|
-
|
|
47
|
+
**For Documentation**
|
|
48
|
+
- Auto-generate OpenAPI specs from your workflows
|
|
49
|
+
- Tests ensure documentation always matches your actual API
|
|
50
|
+
- View in Swagger UI or Redoc with one command
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
- **Header Testing**: Comprehensive HTTP header validation with compound matchers
|
|
61
|
-
- **FactoryBot Integration**: Generate test data with FactoryBot integration
|
|
62
|
-
- **Faker Integration**: Create realistic test data with Faker
|
|
63
|
-
- **Variable System**: Define and reference variables for dynamic test data
|
|
64
|
-
- **Context Storage**: Store API responses and reference them in subsequent tests
|
|
65
|
-
- **Compound Matchers**: Combine multiple validations with `matcher.and` for precise expectations
|
|
66
|
-
- **Global Variables**: Define shared configuration at the file level
|
|
67
|
-
- **Callback System**: Hook into the test lifecycle using Ruby for setup, teardown, and much more!
|
|
52
|
+
**For Teams**
|
|
53
|
+
- YAML workflows are easier to review than Ruby test code
|
|
54
|
+
- QA, developers, and product can all read and contribute
|
|
55
|
+
- Version-controlled specifications that live with your code
|
|
68
56
|
|
|
69
57
|
## Quick Start
|
|
70
58
|
|
|
71
|
-
Get started with SpecForge in 3 commands:
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
# 1. Initialize SpecForge
|
|
75
|
-
spec_forge init
|
|
76
|
-
|
|
77
|
-
# 2. Create your first test
|
|
78
|
-
spec_forge new spec users
|
|
79
|
-
|
|
80
|
-
# 3. View your documentation
|
|
81
|
-
spec_forge serve
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Then visit `http://localhost:8080` to see your API documentation!
|
|
85
|
-
|
|
86
|
-
## When Not to Use SpecForge
|
|
87
|
-
|
|
88
|
-
Consider alternatives when you need:
|
|
89
|
-
|
|
90
|
-
1. **Complex Ruby Logic**: If your tests require custom Ruby code for data transformations or validations.
|
|
91
|
-
2. **Complex Test Setup**: When you need intricate database states or specific service mocks.
|
|
92
|
-
3. **Custom Response Validation**: For validation logic beyond what matchers provide.
|
|
93
|
-
4. **Complex Non-JSON Testing**: While SpecForge handles basic XML/HTML responses (coming soon), complex validation might need specialized tools.
|
|
94
|
-
|
|
95
|
-
## Installation
|
|
96
|
-
|
|
97
|
-
Add this line to your application's Gemfile:
|
|
98
|
-
|
|
99
|
-
```ruby
|
|
100
|
-
gem "spec_forge"
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
And then execute:
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
bundle install
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Or install it yourself as:
|
|
110
|
-
|
|
111
59
|
```bash
|
|
60
|
+
# Install
|
|
112
61
|
gem install spec_forge
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Getting Started
|
|
116
62
|
|
|
117
|
-
Initialize
|
|
118
|
-
|
|
119
|
-
```bash
|
|
63
|
+
# Initialize project structure
|
|
120
64
|
spec_forge init
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Create your first test:
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
spec_forge new spec users
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Generate documentation (default command):
|
|
130
65
|
|
|
131
|
-
|
|
132
|
-
spec_forge
|
|
133
|
-
```
|
|
66
|
+
# Create your first workflow
|
|
67
|
+
spec_forge new blueprint users
|
|
134
68
|
|
|
135
|
-
|
|
69
|
+
# Run it
|
|
70
|
+
spec_forge run
|
|
136
71
|
|
|
137
|
-
|
|
72
|
+
# Generate and view documentation
|
|
138
73
|
spec_forge serve
|
|
139
74
|
```
|
|
140
75
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
spec_forge run
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## Documentation Workflow
|
|
76
|
+
Visit `http://localhost:8080` to see your API documentation!
|
|
148
77
|
|
|
149
|
-
|
|
78
|
+
## Key Features
|
|
150
79
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
spec_forge serve # Generate if needed + serve
|
|
159
|
-
spec_forge serve --fresh # Force regeneration + serve
|
|
160
|
-
spec_forge serve --ui redoc # Use Redoc instead
|
|
161
|
-
spec_forge serve --port 3001 # Custom port
|
|
162
|
-
|
|
163
|
-
# Traditional testing
|
|
164
|
-
spec_forge run # Pure testing mode
|
|
165
|
-
spec_forge run users:show_user # Run specific tests
|
|
166
|
-
```
|
|
80
|
+
- **Step-based workflows**: Tests execute sequentially with explicit variable flow
|
|
81
|
+
- **Variable storage**: Capture response values with `store:`, reference them with `{{ variable }}`
|
|
82
|
+
- **Validation modes**: Simple `shape:` matching for structure, `schema:` for precise control
|
|
83
|
+
- **Tag filtering**: Run subsets with `--tags smoke` or `--skip-tags slow`
|
|
84
|
+
- **Lifecycle hooks**: Execute Ruby code at forge, blueprint, or step boundaries
|
|
85
|
+
- **Dynamic data**: Built-in Faker and FactoryBot integration
|
|
86
|
+
- **Nesting**: Group steps and share configuration with the `shared:` wrapper
|
|
167
87
|
|
|
168
|
-
## Example:
|
|
88
|
+
## Example: Authentication Flow
|
|
169
89
|
|
|
170
90
|
```yaml
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
path: /users
|
|
190
|
-
method: POST
|
|
191
|
-
variables:
|
|
192
|
-
username: faker.internet.username
|
|
193
|
-
email: faker.internet.email
|
|
194
|
-
body:
|
|
195
|
-
name: variables.username
|
|
196
|
-
email: variables.email
|
|
197
|
-
role: global.variables.admin_role
|
|
198
|
-
store_as: new_user
|
|
199
|
-
expectations:
|
|
200
|
-
- expect:
|
|
201
|
-
status: 201
|
|
91
|
+
- name: "Login"
|
|
92
|
+
request:
|
|
93
|
+
url: /auth/login
|
|
94
|
+
http_verb: POST
|
|
95
|
+
json:
|
|
96
|
+
email: "{{ faker.internet.email }}"
|
|
97
|
+
password: "testpass123"
|
|
98
|
+
expect:
|
|
99
|
+
- status: 200
|
|
100
|
+
json:
|
|
101
|
+
shape:
|
|
102
|
+
token: string
|
|
103
|
+
store:
|
|
104
|
+
auth_token: "{{ response.body.token }}"
|
|
105
|
+
|
|
106
|
+
- name: "Authenticated requests"
|
|
107
|
+
shared:
|
|
108
|
+
request:
|
|
202
109
|
headers:
|
|
203
|
-
|
|
110
|
+
Authorization: "Bearer {{ auth_token }}"
|
|
111
|
+
steps:
|
|
112
|
+
- name: "Get profile"
|
|
113
|
+
request:
|
|
114
|
+
url: /me
|
|
115
|
+
expect:
|
|
116
|
+
- status: 200
|
|
117
|
+
|
|
118
|
+
- name: "Update profile"
|
|
119
|
+
request:
|
|
120
|
+
url: /me
|
|
121
|
+
http_verb: PUT
|
|
204
122
|
json:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
role: global.variables.admin_role
|
|
209
|
-
|
|
210
|
-
show_user:
|
|
211
|
-
path: /users/{id}
|
|
212
|
-
query:
|
|
213
|
-
id: store.new_user.body.id
|
|
214
|
-
expectations:
|
|
215
|
-
- expect:
|
|
216
|
-
status: 200
|
|
217
|
-
json:
|
|
218
|
-
id: store.new_user.body.id
|
|
219
|
-
name: store.new_user.body.name
|
|
220
|
-
email: store.new_user.body.email
|
|
221
|
-
role: global.variables.admin_role
|
|
123
|
+
name: "Updated Name"
|
|
124
|
+
expect:
|
|
125
|
+
- status: 200
|
|
222
126
|
```
|
|
223
127
|
|
|
224
|
-
|
|
128
|
+
## When Not to Use SpecForge
|
|
129
|
+
|
|
130
|
+
Consider alternatives when you need:
|
|
131
|
+
|
|
132
|
+
1. **Complex Ruby logic** in tests - custom transformations, calculations, or validation beyond matchers
|
|
133
|
+
2. **Intricate test setup** - complex database states, multiple service mocks, elaborate fixtures
|
|
134
|
+
3. **Non-REST APIs** - GraphQL, gRPC, or WebSocket testing may need specialized tools
|
|
135
|
+
4. **Heavy computational testing** - load testing, performance benchmarks, parallel execution
|
|
136
|
+
|
|
137
|
+
You can use SpecForge alongside traditional RSpec tests. Use SpecForge for standard REST workflows and RSpec for complex scenarios.
|
|
225
138
|
|
|
226
139
|
## Documentation
|
|
227
140
|
|
|
228
|
-
For comprehensive
|
|
141
|
+
For comprehensive guides and reference:
|
|
142
|
+
|
|
143
|
+
- **[Getting Started](https://github.com/itsthedevman/spec_forge/wiki/Getting-Started)** - Installation and your first workflow
|
|
144
|
+
- **[Writing Tests](https://github.com/itsthedevman/spec_forge/wiki/Writing-Tests)** - Complete syntax reference
|
|
145
|
+
- **[Configuration](https://github.com/itsthedevman/spec_forge/wiki/Configuration)** - Setup and framework integration
|
|
146
|
+
- **[Running Tests](https://github.com/itsthedevman/spec_forge/wiki/Running-Tests)** - CLI commands and filtering
|
|
147
|
+
- **[Dynamic Features](https://github.com/itsthedevman/spec_forge/wiki/Dynamic-Features)** - Variables, Faker, FactoryBot
|
|
148
|
+
- **[Documentation Generation](https://github.com/itsthedevman/spec_forge/wiki/Documentation-Generation)** - OpenAPI customization
|
|
149
|
+
|
|
150
|
+
**Upgrading from 0.7?** See the **[Migration Guide](https://github.com/itsthedevman/spec_forge/wiki/Migration-Guide)**.
|
|
229
151
|
|
|
230
|
-
|
|
231
|
-
- [Configuration Options](https://github.com/itsthedevman/spec_forge/wiki/Configuration)
|
|
232
|
-
- [Writing Tests](https://github.com/itsthedevman/spec_forge/wiki/Writing-Tests)
|
|
233
|
-
- [Running Tests](https://github.com/itsthedevman/spec_forge/wiki/Running-Tests)
|
|
234
|
-
- [Debugging Guide](https://github.com/itsthedevman/spec_forge/wiki/Debugging)
|
|
235
|
-
- [Dynamic Features](https://github.com/itsthedevman/spec_forge/wiki/Dynamic-Features)
|
|
236
|
-
- [Factory Support](https://github.com/itsthedevman/spec_forge/wiki/Factory-Support)
|
|
237
|
-
- [RSpec Matchers](https://github.com/itsthedevman/spec_forge/wiki/RSpec-Matchers)
|
|
152
|
+
**API Reference:** [itsthedevman.com/docs/spec_forge](https://itsthedevman.com/docs/spec_forge)
|
|
238
153
|
|
|
239
|
-
|
|
154
|
+
## CLI Reference
|
|
240
155
|
|
|
241
|
-
|
|
156
|
+
| Command | Description |
|
|
157
|
+
|---------|-------------|
|
|
158
|
+
| `spec_forge init` | Initialize project structure |
|
|
159
|
+
| `spec_forge new blueprint <name>` | Create a workflow file |
|
|
160
|
+
| `spec_forge new factory <name>` | Create a factory file |
|
|
161
|
+
| `spec_forge run` | Execute workflows |
|
|
162
|
+
| `spec_forge docs` | Generate OpenAPI specs |
|
|
163
|
+
| `spec_forge serve` | Generate and serve documentation |
|
|
242
164
|
|
|
243
|
-
|
|
165
|
+
Run `spec_forge help <command>` for detailed options.
|
|
244
166
|
|
|
245
167
|
## Contributing
|
|
246
168
|
|
|
247
|
-
Contributions
|
|
169
|
+
Contributions welcome! See the [Contributing Guide](https://github.com/itsthedevman/spec_forge/wiki/Contributing).
|
|
248
170
|
|
|
249
171
|
## License
|
|
250
172
|
|
|
251
|
-
|
|
173
|
+
Available as open source under the [MIT License](LICENSE.txt).
|
|
252
174
|
|
|
253
175
|
## Looking for a Software Engineer?
|
|
254
176
|
|
|
255
177
|
I'm currently looking for opportunities where I can tackle meaningful problems and help build reliable software while mentoring the next generation of developers. If you're looking for a senior engineer with full-stack Rails expertise and a passion for clean, maintainable code, let's talk!
|
|
256
178
|
|
|
257
|
-
[bryan@itsthedevman.com](mailto:bryan@itsthedevman.com)
|
|
179
|
+
[bryan@itsthedevman.com](mailto:bryan@itsthedevman.com)
|
data/bin/spec_forge
CHANGED
data/flake.lock
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"nodes": {
|
|
3
|
+
"flake-compat": {
|
|
4
|
+
"flake": false,
|
|
5
|
+
"locked": {
|
|
6
|
+
"lastModified": 1747046372,
|
|
7
|
+
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
|
8
|
+
"owner": "edolstra",
|
|
9
|
+
"repo": "flake-compat",
|
|
10
|
+
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
|
11
|
+
"type": "github"
|
|
12
|
+
},
|
|
13
|
+
"original": {
|
|
14
|
+
"owner": "edolstra",
|
|
15
|
+
"repo": "flake-compat",
|
|
16
|
+
"type": "github"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
3
19
|
"flake-utils": {
|
|
4
20
|
"inputs": {
|
|
5
21
|
"systems": "systems"
|
|
@@ -18,13 +34,31 @@
|
|
|
18
34
|
"type": "github"
|
|
19
35
|
}
|
|
20
36
|
},
|
|
37
|
+
"flake-utils_2": {
|
|
38
|
+
"inputs": {
|
|
39
|
+
"systems": "systems_2"
|
|
40
|
+
},
|
|
41
|
+
"locked": {
|
|
42
|
+
"lastModified": 1731533236,
|
|
43
|
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
44
|
+
"owner": "numtide",
|
|
45
|
+
"repo": "flake-utils",
|
|
46
|
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
47
|
+
"type": "github"
|
|
48
|
+
},
|
|
49
|
+
"original": {
|
|
50
|
+
"owner": "numtide",
|
|
51
|
+
"repo": "flake-utils",
|
|
52
|
+
"type": "github"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
21
55
|
"nixpkgs": {
|
|
22
56
|
"locked": {
|
|
23
|
-
"lastModified":
|
|
24
|
-
"narHash": "sha256-
|
|
57
|
+
"lastModified": 1767379071,
|
|
58
|
+
"narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=",
|
|
25
59
|
"owner": "NixOS",
|
|
26
60
|
"repo": "nixpkgs",
|
|
27
|
-
"rev": "
|
|
61
|
+
"rev": "fb7944c166a3b630f177938e478f0378e64ce108",
|
|
28
62
|
"type": "github"
|
|
29
63
|
},
|
|
30
64
|
"original": {
|
|
@@ -34,10 +68,33 @@
|
|
|
34
68
|
"type": "github"
|
|
35
69
|
}
|
|
36
70
|
},
|
|
71
|
+
"nixpkgs-ruby": {
|
|
72
|
+
"inputs": {
|
|
73
|
+
"flake-compat": "flake-compat",
|
|
74
|
+
"flake-utils": "flake-utils_2",
|
|
75
|
+
"nixpkgs": [
|
|
76
|
+
"nixpkgs"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
"locked": {
|
|
80
|
+
"lastModified": 1766728475,
|
|
81
|
+
"narHash": "sha256-9y9WBiQONK9TgD4lu8SW6TpcfEaJTxEs1HjhC4s4vnY=",
|
|
82
|
+
"owner": "bobvanderlinden",
|
|
83
|
+
"repo": "nixpkgs-ruby",
|
|
84
|
+
"rev": "f167828eab19c3a7e3faa066a140d92e307f7b16",
|
|
85
|
+
"type": "github"
|
|
86
|
+
},
|
|
87
|
+
"original": {
|
|
88
|
+
"owner": "bobvanderlinden",
|
|
89
|
+
"repo": "nixpkgs-ruby",
|
|
90
|
+
"type": "github"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
37
93
|
"root": {
|
|
38
94
|
"inputs": {
|
|
39
95
|
"flake-utils": "flake-utils",
|
|
40
|
-
"nixpkgs": "nixpkgs"
|
|
96
|
+
"nixpkgs": "nixpkgs",
|
|
97
|
+
"nixpkgs-ruby": "nixpkgs-ruby"
|
|
41
98
|
}
|
|
42
99
|
},
|
|
43
100
|
"systems": {
|
|
@@ -54,6 +111,21 @@
|
|
|
54
111
|
"repo": "default",
|
|
55
112
|
"type": "github"
|
|
56
113
|
}
|
|
114
|
+
},
|
|
115
|
+
"systems_2": {
|
|
116
|
+
"locked": {
|
|
117
|
+
"lastModified": 1681028828,
|
|
118
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
119
|
+
"owner": "nix-systems",
|
|
120
|
+
"repo": "default",
|
|
121
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
122
|
+
"type": "github"
|
|
123
|
+
},
|
|
124
|
+
"original": {
|
|
125
|
+
"owner": "nix-systems",
|
|
126
|
+
"repo": "default",
|
|
127
|
+
"type": "github"
|
|
128
|
+
}
|
|
57
129
|
}
|
|
58
130
|
},
|
|
59
131
|
"root": "root",
|
data/flake.nix
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
inputs = {
|
|
5
5
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
6
6
|
flake-utils.url = "github:numtide/flake-utils";
|
|
7
|
+
nixpkgs-ruby.url = "github:bobvanderlinden/nixpkgs-ruby";
|
|
8
|
+
nixpkgs-ruby.inputs.nixpkgs.follows = "nixpkgs";
|
|
7
9
|
};
|
|
8
10
|
|
|
9
11
|
outputs =
|
|
@@ -11,19 +13,18 @@
|
|
|
11
13
|
self,
|
|
12
14
|
nixpkgs,
|
|
13
15
|
flake-utils,
|
|
16
|
+
nixpkgs-ruby,
|
|
14
17
|
}:
|
|
15
18
|
flake-utils.lib.eachDefaultSystem (
|
|
16
19
|
system:
|
|
17
20
|
let
|
|
18
21
|
pkgs = nixpkgs.legacyPackages.${system};
|
|
22
|
+
ruby = nixpkgs-ruby.packages.${system}."ruby-3.2.9";
|
|
19
23
|
in
|
|
20
24
|
{
|
|
21
25
|
devShells.default = pkgs.mkShell {
|
|
22
26
|
buildInputs = with pkgs; [
|
|
23
|
-
|
|
24
|
-
jemallocSupport = false;
|
|
25
|
-
docSupport = false;
|
|
26
|
-
})
|
|
27
|
+
ruby
|
|
27
28
|
|
|
28
29
|
# Dependencies for native gems
|
|
29
30
|
pkg-config
|