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.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -1
  3. data/README.md +124 -202
  4. data/bin/spec_forge +1 -1
  5. data/flake.lock +76 -4
  6. data/flake.nix +5 -4
  7. data/lib/spec_forge/attribute/chainable.rb +6 -6
  8. data/lib/spec_forge/attribute/environment.rb +45 -0
  9. data/lib/spec_forge/attribute/factory.rb +26 -17
  10. data/lib/spec_forge/attribute/faker.rb +6 -1
  11. data/lib/spec_forge/attribute/generate.rb +114 -0
  12. data/lib/spec_forge/attribute/literal.rb +1 -14
  13. data/lib/spec_forge/attribute/matcher.rb +6 -2
  14. data/lib/spec_forge/attribute/parameterized.rb +20 -22
  15. data/lib/spec_forge/attribute/resolvable_array.rb +16 -16
  16. data/lib/spec_forge/attribute/resolvable_hash.rb +17 -16
  17. data/lib/spec_forge/attribute/resolvable_struct.rb +67 -0
  18. data/lib/spec_forge/attribute/template.rb +118 -0
  19. data/lib/spec_forge/attribute/transform.rb +14 -19
  20. data/lib/spec_forge/attribute/variable.rb +31 -31
  21. data/lib/spec_forge/attribute.rb +54 -100
  22. data/lib/spec_forge/blueprint.rb +27 -0
  23. data/lib/spec_forge/cli/docs/generate.rb +28 -8
  24. data/lib/spec_forge/cli/docs.rb +5 -2
  25. data/lib/spec_forge/cli/init.rb +4 -4
  26. data/lib/spec_forge/cli/new.rb +78 -27
  27. data/lib/spec_forge/cli/run.rb +84 -52
  28. data/lib/spec_forge/cli/serve.rb +5 -0
  29. data/lib/spec_forge/cli.rb +6 -14
  30. data/lib/spec_forge/configuration.rb +209 -79
  31. data/lib/spec_forge/documentation/{loader → builder}/cache.rb +26 -23
  32. data/lib/spec_forge/documentation/builder/compiler.rb +373 -0
  33. data/lib/spec_forge/documentation/builder/extractor.rb +75 -0
  34. data/lib/spec_forge/documentation/builder.rb +77 -329
  35. data/lib/spec_forge/documentation/document/operation.rb +4 -4
  36. data/lib/spec_forge/documentation/document.rb +0 -6
  37. data/lib/spec_forge/documentation/generator.rb +88 -0
  38. data/lib/spec_forge/documentation/{generators/openapi → openapi/v3_0}/error_formatter.rb +2 -2
  39. data/lib/spec_forge/documentation/openapi/v3_0/example.rb +1 -1
  40. data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +1 -1
  41. data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +21 -5
  42. data/lib/spec_forge/documentation/openapi/v3_0/response.rb +28 -6
  43. data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +20 -2
  44. data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +1 -1
  45. data/lib/spec_forge/documentation/openapi/v3_0.rb +116 -0
  46. data/lib/spec_forge/documentation/openapi.rb +40 -12
  47. data/lib/spec_forge/documentation.rb +1 -7
  48. data/lib/spec_forge/error.rb +215 -41
  49. data/lib/spec_forge/factory.rb +38 -18
  50. data/lib/spec_forge/forge/action.rb +41 -0
  51. data/lib/spec_forge/forge/actions/call.rb +33 -0
  52. data/lib/spec_forge/forge/actions/debug.rb +47 -0
  53. data/lib/spec_forge/forge/actions/expect.rb +44 -0
  54. data/lib/spec_forge/forge/actions/request.rb +65 -0
  55. data/lib/spec_forge/forge/actions/store.rb +31 -0
  56. data/lib/spec_forge/forge/callbacks.rb +80 -0
  57. data/lib/spec_forge/forge/context.rb +41 -0
  58. data/lib/spec_forge/forge/display.rb +503 -0
  59. data/lib/spec_forge/forge/hooks.rb +131 -0
  60. data/lib/spec_forge/forge/runner/array_io.rb +81 -0
  61. data/lib/spec_forge/forge/runner/content_validator.rb +92 -0
  62. data/lib/spec_forge/forge/runner/header_validator.rb +66 -0
  63. data/lib/spec_forge/forge/runner/reporter.rb +56 -0
  64. data/lib/spec_forge/forge/runner/schema_validator.rb +113 -0
  65. data/lib/spec_forge/forge/runner.rb +118 -0
  66. data/lib/spec_forge/forge/timer.rb +94 -0
  67. data/lib/spec_forge/forge/variables.rb +38 -0
  68. data/lib/spec_forge/forge.rb +207 -133
  69. data/lib/spec_forge/http/backend.rb +49 -146
  70. data/lib/spec_forge/http/client.rb +14 -17
  71. data/lib/spec_forge/http/request.rb +37 -84
  72. data/lib/spec_forge/http/verb.rb +4 -0
  73. data/lib/spec_forge/http.rb +0 -5
  74. data/lib/spec_forge/loader/filter.rb +85 -0
  75. data/lib/spec_forge/loader/step_processor.rb +282 -0
  76. data/lib/spec_forge/loader.rb +105 -220
  77. data/lib/spec_forge/normalizer/default.rb +1 -1
  78. data/lib/spec_forge/normalizer/structure.rb +140 -0
  79. data/lib/spec_forge/normalizer/transformers.rb +168 -0
  80. data/lib/spec_forge/normalizer/validators.rb +50 -8
  81. data/lib/spec_forge/normalizer.rb +76 -119
  82. data/lib/spec_forge/normalizers/callback.yml +38 -0
  83. data/lib/spec_forge/normalizers/configuration.yml +59 -9
  84. data/lib/spec_forge/normalizers/factory.yml +53 -2
  85. data/lib/spec_forge/normalizers/factory_reference.yml +63 -2
  86. data/lib/spec_forge/normalizers/json_schema.yml +79 -0
  87. data/lib/spec_forge/normalizers/step.yml +506 -0
  88. data/lib/spec_forge/step/call.rb +36 -0
  89. data/lib/spec_forge/step/expect.rb +110 -0
  90. data/lib/spec_forge/step/source.rb +22 -0
  91. data/lib/spec_forge/step.rb +129 -0
  92. data/lib/spec_forge/type.rb +115 -66
  93. data/lib/spec_forge/version.rb +1 -1
  94. data/lib/spec_forge.rb +44 -106
  95. data/lib/templates/forge_helper.rb.tt +43 -22
  96. data/lib/templates/new_blueprint.yml.tt +54 -0
  97. metadata +75 -44
  98. data/lib/spec_forge/attribute/global.rb +0 -96
  99. data/lib/spec_forge/attribute/store.rb +0 -65
  100. data/lib/spec_forge/backtrace_formatter.rb +0 -50
  101. data/lib/spec_forge/callbacks.rb +0 -88
  102. data/lib/spec_forge/context/callbacks.rb +0 -91
  103. data/lib/spec_forge/context/global.rb +0 -72
  104. data/lib/spec_forge/context/store.rb +0 -131
  105. data/lib/spec_forge/context/variables.rb +0 -91
  106. data/lib/spec_forge/context.rb +0 -36
  107. data/lib/spec_forge/core_ext/rspec.rb +0 -55
  108. data/lib/spec_forge/core_ext.rb +0 -5
  109. data/lib/spec_forge/documentation/generators/base.rb +0 -81
  110. data/lib/spec_forge/documentation/generators/openapi/base.rb +0 -100
  111. data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +0 -65
  112. data/lib/spec_forge/documentation/generators/openapi.rb +0 -59
  113. data/lib/spec_forge/documentation/generators.rb +0 -17
  114. data/lib/spec_forge/documentation/loader.rb +0 -159
  115. data/lib/spec_forge/documentation/openapi/base.rb +0 -33
  116. data/lib/spec_forge/filter.rb +0 -86
  117. data/lib/spec_forge/normalizer/definition.rb +0 -248
  118. data/lib/spec_forge/normalizers/_shared.yml +0 -76
  119. data/lib/spec_forge/normalizers/constraint.yml +0 -8
  120. data/lib/spec_forge/normalizers/expectation.yml +0 -47
  121. data/lib/spec_forge/normalizers/global_context.yml +0 -28
  122. data/lib/spec_forge/normalizers/spec.yml +0 -50
  123. data/lib/spec_forge/runner/adapter.rb +0 -181
  124. data/lib/spec_forge/runner/callbacks.rb +0 -246
  125. data/lib/spec_forge/runner/debug_proxy.rb +0 -215
  126. data/lib/spec_forge/runner/listener.rb +0 -54
  127. data/lib/spec_forge/runner/metadata.rb +0 -58
  128. data/lib/spec_forge/runner/state.rb +0 -98
  129. data/lib/spec_forge/runner.rb +0 -75
  130. data/lib/spec_forge/spec/expectation/constraint.rb +0 -127
  131. data/lib/spec_forge/spec/expectation.rb +0 -68
  132. data/lib/spec_forge/spec.rb +0 -68
  133. data/lib/templates/new_spec.yml.tt +0 -43
@@ -3,70 +3,121 @@
3
3
  module SpecForge
4
4
  class CLI
5
5
  #
6
- # Command for generating new specs or factories
6
+ # Command for generating new blueprints or factories from templates
7
7
  #
8
- # @example Creating a new spec
9
- # spec_forge new spec users
8
+ # Creates workflow blueprints or test data factories with sensible defaults
9
+ # and realistic examples to get you started quickly.
10
+ #
11
+ # @example Creating a new blueprint
12
+ # spec_forge new blueprint users
10
13
  #
11
14
  # @example Creating a new factory
12
15
  # spec_forge new factory user
13
16
  #
14
17
  class New < Command
15
18
  command_name "new"
16
- summary "Create new test specs or data factories"
19
+ summary "Create new workflow blueprints or data factories"
17
20
 
18
21
  description <<~DESC
19
- Generate new files from templates.
22
+ Generate new files from templates with realistic examples.
20
23
 
21
24
  Types:
22
- spec - Creates YAML test files with common patterns
23
- • factory - Creates FactoryBot factories for test data
24
-
25
- Files are created in the appropriate spec_forge/ subdirectory.
25
+ blueprint - Creates workflow files with complete CRUD examples
26
+ • factory - Creates FactoryBot factories for test data
27
+
28
+ Blueprints include examples of:
29
+ • Variable storage and interpolation
30
+ • Sequential request workflows
31
+ • Multiple HTTP methods (GET, POST, PATCH, DELETE)
32
+ • Different expectation patterns
33
+ • Response value chaining
34
+
35
+ Files are created in the appropriate spec_forge/ subdirectory
36
+ with proper naming conventions and ready-to-run examples.
26
37
  DESC
27
38
 
28
39
  syntax "new <type> <name>"
29
40
 
30
- example "new spec users",
31
- "Creates a new spec located at 'spec_forge/specs/users.yml'"
41
+ example "new blueprint users",
42
+ "Creates spec_forge/blueprints/users.yml with CRUD examples"
32
43
 
33
- example "new factory user",
34
- "Creates a new factory located at 'spec_forge/factories/user.yml'"
44
+ example "new blueprint auth/login",
45
+ "Creates spec_forge/blueprints/auth/login.yml in subdirectory"
35
46
 
36
- example "generate spec accounts",
37
- "Uses the generate alias (shorthand 'g') instead of 'new'"
47
+ example "new factory user",
48
+ "Creates spec_forge/factories/user.yml"
38
49
 
39
- aliases :generate, :g
50
+ aliases :generate, :g, :n
40
51
 
41
52
  #
42
- # Creates a new spec or factory file in the corresponding directory using templates
53
+ # Creates a new blueprint or factory file from templates
54
+ #
55
+ # @return [void]
43
56
  #
44
57
  def call
45
- type = arguments.first.downcase
58
+ type = arguments.first&.downcase
46
59
  name = arguments.second
47
60
 
48
- # Cleanup
49
- name.delete_suffix!(".yml") if name.end_with?(".yml")
50
- name.delete_suffix!(".yaml") if name.end_with?(".yaml")
61
+ if type.nil? || name.nil?
62
+ puts "Error: Both type and name are required."
63
+ puts "Usage: spec_forge new <type> <name>"
64
+ puts ""
65
+ puts "Examples:"
66
+ puts " spec_forge new blueprint users"
67
+ puts " spec_forge new factory user"
68
+ exit(1)
69
+ end
70
+
71
+ # Clean up the name
72
+ name = normalize_name(name)
51
73
 
52
74
  case type
53
- when "spec"
54
- create_new_spec(name)
55
- when "factory"
75
+ when "blueprint", "blueprints", "spec", "specs"
76
+ create_new_blueprint(name)
77
+ when "factory", "factories"
56
78
  create_new_factory(name)
79
+ else
80
+ puts "Error: Unknown type '#{type}'"
81
+ puts "Valid types: blueprint, factory"
82
+ exit(1)
57
83
  end
58
84
  end
59
85
 
60
86
  private
61
87
 
62
- def create_new_spec(name)
88
+ #
89
+ # Normalizes the name by removing extensions
90
+ #
91
+ # @param name [String] The raw name from user input
92
+ #
93
+ # @return [String] Cleaned name without extensions
94
+ #
95
+ def normalize_name(name)
96
+ name.delete_suffix(".yml").delete_suffix(".yaml")
97
+ end
98
+
99
+ #
100
+ # Creates a new blueprint file with workflow template
101
+ #
102
+ # @param name [String] The blueprint name
103
+ #
104
+ # @return [void]
105
+ #
106
+ def create_new_blueprint(name)
63
107
  actions.template(
64
- "new_spec.yml.tt",
65
- SpecForge.forge_path.join("specs", "#{name}.yml"),
108
+ "new_blueprint.yml.tt",
109
+ SpecForge.forge_path.join("blueprints", "#{name}.yml"),
66
110
  context: Proxy.new(name).call
67
111
  )
68
112
  end
69
113
 
114
+ #
115
+ # Creates a new factory file with template
116
+ #
117
+ # @param name [String] The factory name
118
+ #
119
+ # @return [void]
120
+ #
70
121
  def create_new_factory(name)
71
122
  actions.template(
72
123
  "new_factory.yml.tt",
@@ -3,92 +3,124 @@
3
3
  module SpecForge
4
4
  class CLI
5
5
  #
6
- # Command for running SpecForge tests with filtering options
6
+ # Command for running SpecForge blueprints with filtering and output options
7
7
  #
8
- # @example Running all specs
9
- # spec_forge run
8
+ # Executes workflow blueprints with support for file filtering, tag-based
9
+ # selection, and configurable verbosity levels for output detail.
10
10
  #
11
- # @example Running specific file
12
- # spec_forge run users
11
+ # @example Running all blueprints
12
+ # spec_forge run
13
13
  #
14
- # @example Running specific spec
15
- # spec_forge run users:create_user
14
+ # @example Running specific blueprint
15
+ # spec_forge run users_workflow
16
16
  #
17
- # @example Running specific expectation
18
- # spec_forge run users:create_user:"POST /users"
17
+ # @example Running with tags
18
+ # spec_forge run --tags smoke
19
19
  #
20
20
  class Run < Command
21
21
  command_name "run"
22
- syntax "run [target]"
23
-
24
- summary "Execute your API tests with smart filtering options"
22
+ syntax "run [path] [options]"
23
+ summary "Execute workflow blueprints with optional filtering"
25
24
 
26
25
  description <<~DESC
27
- Execute API tests with filtering options.
26
+ Execute SpecForge workflow blueprints with flexible filtering options.
27
+
28
+ Supports:
29
+ • File/directory targeting for selective execution
30
+ • Tag-based filtering to run specific test categories
31
+ • Multiple verbosity levels for output detail
32
+ • Tag exclusion to skip certain tests
33
+
34
+ Verbosity Levels:
35
+ (default) - Minimal output, dots for progress
36
+ --verbose - Show all steps with detailed results
37
+ --debug - Add full request/response for failures
38
+ --trace - Show everything for all steps
39
+ DESC
28
40
 
29
- Target formats:
30
- file_name - Run all specs in a file
31
- • file:spec - Run specific spec
32
- • file:spec:"expectation" - Run individual expectation
41
+ example "run",
42
+ "Runs all blueprints in spec_forge/blueprints/"
33
43
 
34
- Uses RSpec for execution with detailed error reporting.
35
- DESC
44
+ example "run users_workflow",
45
+ "Runs only the users_workflow.yml blueprint"
46
+
47
+ example "run blueprints/integration/",
48
+ "Runs all blueprints in the integration directory"
36
49
 
37
- example "spec_forge run",
38
- "Run all specs in spec_forge/specs/"
50
+ example "run --tags smoke",
51
+ "Runs all blueprints tagged with 'smoke'"
39
52
 
40
- example "spec_forge run users",
41
- "Run all specs in users.yml"
53
+ example "run --tags smoke,auth --skip-tags slow",
54
+ "Runs smoke and auth tests, excluding slow ones"
42
55
 
43
- example "spec_forge run users:create_user",
44
- "Run all expectations in the create_user spec"
56
+ example "run users_workflow --tags smoke --debug",
57
+ "Runs users_workflow smoke tests with debug output"
45
58
 
46
- example "spec_forge run users:create_user:\"POST /users\"",
47
- "Run expectations matching POST /users"
59
+ option "--tags=TAGS", "Run only steps with these tags (comma-separated)"
60
+ option "--skip-tags=TAGS", "Skip steps with these tags (comma-separated)"
61
+ option "--verbose", "Show detailed step execution (verbosity level 1)"
62
+ option "--debug", "Show full request/response for failures (verbosity level 2)"
63
+ option "--trace", "Show everything for all steps (verbosity level 3)"
48
64
 
49
- example "spec_forge run users:create_user:\"POST /users - Create Admin\"",
50
- "Run the specific expectation named \"Create Admin\""
65
+ aliases :r
51
66
 
52
67
  #
53
- # Loads and runs all specs, or a subset of specs based on the provided arguments
68
+ # Executes the workflow blueprints with specified filters and options
69
+ #
70
+ # @return [void]
54
71
  #
55
72
  def call
56
- return SpecForge.run if arguments.blank?
73
+ base_path = determine_path
74
+ tags = parse_tags(options.tags)
75
+ skip_tags = parse_tags(options.skip_tags)
76
+ verbosity_level = determine_verbosity_level
77
+
78
+ blueprints, forge_hooks = Loader.load_blueprints(base_path:, tags:, skip_tags:)
57
79
 
58
- # spec_forge users:show_user
59
- filter = extract_filter(arguments.first)
80
+ if blueprints.empty?
81
+ puts "No blueprints found matching the criteria."
82
+ exit(0)
83
+ end
60
84
 
61
- # Filter and run the specs
62
- SpecForge.run(**filter)
85
+ Forge.ignite.run(blueprints, verbosity_level:, hooks: forge_hooks)
63
86
  end
64
87
 
65
88
  private
66
89
 
67
90
  #
68
- # The input can be
91
+ # Determines the path to run from command arguments
69
92
  #
70
- # "<file_name>" for a file
71
- # Example: "users"
93
+ # @return [Pathname, nil] The path to execute, or nil for all blueprints
72
94
  #
73
- # "<file_name>:<spec_name>" for a single spec
74
- # Example: "users:show_user"
95
+ def determine_path
96
+ return nil if arguments.empty?
97
+
98
+ Pathname.new(arguments.first)
99
+ end
100
+
101
+ #
102
+ # Parses comma-separated tags from a string
75
103
  #
76
- # "<file_name:<spec_name>:'<verb> <path> - <?name>'" for a single expectation
77
- # Example:
78
- # "users:show_user:'GET /users/:id'"
79
- # Example with name:
80
- # "users:show_user:'GET /users/:id - Returns 404 due to missing user'"
104
+ # @param tag_string [String, nil] Comma-separated tag string
81
105
  #
82
- # @private
106
+ # @return [Array<String>] Array of tag strings
83
107
  #
84
- def extract_filter(input)
85
- # Note: Only split 3 because the expectation name can have colons in them.
86
- file_name, spec_name, expectation_name = input.split(":", 3).map(&:strip)
108
+ def parse_tags(tag_string)
109
+ return [] if tag_string.blank?
87
110
 
88
- # Remove the quotes
89
- expectation_name.gsub!(/^['"]|['"]$/, "") if expectation_name.present?
111
+ tag_string.split(",").map(&:strip).reject(&:blank?)
112
+ end
90
113
 
91
- {file_name:, spec_name:, expectation_name:}
114
+ #
115
+ # Determines verbosity level from command options
116
+ #
117
+ # @return [Integer] Verbosity level (0-3)
118
+ #
119
+ def determine_verbosity_level
120
+ return 3 if options.trace
121
+ return 2 if options.debug
122
+ return 1 if options.verbose
123
+ 0
92
124
  end
93
125
  end
94
126
  end
@@ -62,6 +62,11 @@ module SpecForge
62
62
  option "--ui=UI", "Documentation interface: swagger or redoc (default: swagger)"
63
63
  option "--port=PORT", "Port to serve documentation on (default: 8080)"
64
64
 
65
+ # Verbosity options
66
+ option "--verbose", "Show detailed step execution (verbosity level 1)"
67
+ option "--debug", "Show full request/response for failures (verbosity level 2)"
68
+ option "--trace", "Show everything for all steps (verbosity level 3)"
69
+
65
70
  aliases :s
66
71
 
67
72
  #
@@ -1,13 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "cli/actions"
4
- require_relative "cli/command"
5
- require_relative "cli/docs"
6
- require_relative "cli/init"
7
- require_relative "cli/new"
8
- require_relative "cli/run"
9
- require_relative "cli/serve"
10
-
11
3
  module SpecForge
12
4
  #
13
5
  # Command-line interface for SpecForge that provides the overall command structure
@@ -31,14 +23,14 @@ module SpecForge
31
23
  program :name, "SpecForge"
32
24
  program :version, SpecForge::VERSION
33
25
  program :description, <<~DESC.strip
34
- Write expressive API tests in YAML with the power of RSpec matchers.
26
+ Write API tests as sequential workflows in YAML with RSpec matchers.
35
27
 
36
28
  Quick Start:
37
- spec_forge init # Set up your project
38
- spec_forge new spec users # Create your first test
39
- spec_forge run # Execute tests
40
- spec_forge docs # Generate API docs
41
- spec_forge serve # Serve API docs locally
29
+ spec_forge init # Set up your project
30
+ spec_forge new blueprint users # Create your first workflow
31
+ spec_forge run # Execute workflows
32
+ spec_forge docs # Generate OpenAPI docs
33
+ spec_forge serve # Preview docs locally
42
34
  DESC
43
35
 
44
36
  register_commands