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.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +139 -9
  3. data/README.md +125 -203
  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 +6 -0
  29. data/lib/spec_forge/cli.rb +6 -14
  30. data/lib/spec_forge/configuration.rb +212 -78
  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 +22 -6
  42. data/lib/spec_forge/documentation/openapi/v3_0/response.rb +29 -7
  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 -143
  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 -74
  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 -183
  124. data/lib/spec_forge/runner/callbacks.rb +0 -246
  125. data/lib/spec_forge/runner/debug_proxy.rb +0 -213
  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
@@ -76,23 +76,65 @@ module SpecForge
76
76
  end
77
77
 
78
78
  #
79
- # Validates that a callback is registered in the system
79
+ # Validates that shape and schema are not both defined
80
80
  #
81
- # Ensures the referenced callback name has been registered with SpecForge
82
- # before it's used in a test configuration.
81
+ # Ensures only one of shape or schema is used for JSON validation,
82
+ # as they represent different validation approaches that cannot be combined.
83
83
  #
84
- # @param value [String, Symbol, nil] The callback name to validate, or nil
84
+ # @param value [Hash] The expectation hash containing shape and/or schema keys
85
85
  #
86
- # @raise [Error::UndefinedCallbackError] If the callback is not registered
86
+ # @raise [Error] If both shape and schema are defined
87
87
  #
88
88
  # @example Using the validator in a structure
89
- # before_file: {type: String, validator: :callback}
89
+ # json: {type: Hash, validator: :json_expectation}
90
+ #
91
+ def json_expectation(value)
92
+ # Both shape and schema cannot be defined at the same time
93
+ return if value[:shape].blank? || value[:schema].blank?
94
+
95
+ raise Error, "Cannot define both \"shape\" and \"schema\". Use \"shape\" for simple validation or \"schema\" for explicit control."
96
+ end
97
+
98
+ #
99
+ # Validates a JSON schema structure recursively
100
+ #
101
+ # Ensures the schema definition follows the expected format,
102
+ # validating nested structures and patterns.
103
+ #
104
+ # @param value [Hash] The schema definition to validate
105
+ #
106
+ # @raise [Error::InvalidStructureError] If the schema is invalid
107
+ #
108
+ def json_schema(value)
109
+ Normalizer.validate!(value, using: :json_schema)
110
+ json_schema(value[:pattern]) if value[:pattern]
111
+
112
+ case value[:structure]
113
+ when Array
114
+ value[:structure].each { |v| json_schema(v) }
115
+ when Hash
116
+ value[:structure].each_value { |v| json_schema(v) }
117
+ end
118
+ end
119
+
120
+ #
121
+ # Validates a callback definition
122
+ #
123
+ # Ensures the callback has a valid structure with a required name
124
+ # and optional arguments. Handles both single callbacks and arrays
125
+ # of callbacks.
126
+ #
127
+ # @param value [Array<Hash>] The callback definition(s) to validate
128
+ #
129
+ # @raise [Error::InvalidStructureError] If the callback structure is invalid
130
+ #
131
+ # @example Using the validator in a structure
132
+ # call: {type: Hash, validator: :callback}
90
133
  #
91
134
  def callback(value)
92
135
  return if value.blank?
93
- return if SpecForge::Callbacks.registered?(value)
94
136
 
95
- raise Error::UndefinedCallbackError.new(value, SpecForge::Callbacks.registered_names)
137
+ value.each { |v| Normalizer.validate!(v, using: :callback) }
96
138
  end
97
139
  end
98
140
  end
@@ -1,93 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "normalizer/default"
4
- require_relative "normalizer/definition"
5
- require_relative "normalizer/validators"
6
-
7
3
  module SpecForge
8
4
  #
9
- # This class provides a powerful system for validating and normalizing input data
10
- # according to defined structures. It handles type checking, default values,
11
- # references between structures, and custom validation logic.
12
- #
13
- # == Structure Definitions
14
- #
15
- # Structures define validation rules as YAML files in the lib/spec_forge/normalizers directory:
16
- #
17
- # # Example structure (users.yml)
18
- # name:
19
- # type: String
20
- # required: true
21
- #
22
- # age:
23
- # type: Integer
24
- # default: 0
25
- #
26
- # settings:
27
- # type: Hash
28
- # structure:
29
- # notifications:
30
- # type: Boolean
31
- # default: true
32
- #
33
- # == Core Attribute Behaviors
34
- #
35
- # 1. With 'default:' - Always included in output, using default if nil
36
- # 2. With 'required: false' - Omitted from output if nil
37
- # 3. Default behavior - Required, errors if missing/nil
38
- #
39
- # == Available Options
40
- #
41
- # * type: - Required. Class name or array of class names (string, integer, hash, etc.)
42
- # * default: - Optional. Default value if attribute is nil
43
- # * required: - Optional. Set to false to make attribute optional
44
- # * aliases: - Optional. Alternative keys to check for value
45
- # * structure: - Optional. Sub-structure for nested objects
46
- # * validator: - Optional. Custom validation method (see Validators)
47
- # * reference: - Optional. Reference another structure definition (e.g., reference: headers)
48
- #
49
- # == Structure References
50
- #
51
- # References allow reusing common structures:
52
- #
53
- # # In your YAML definition:
54
- # user_id:
55
- # reference: id # Will inherit all properties from the 'id' structure
56
- # required: false # Can override specific properties
57
- #
58
- # # Nested structure references:
59
- # settings:
60
- # type: Hash
61
- # structure:
62
- # email_prefs:
63
- # reference: email_preferences # References another complete structure
64
- #
65
- # == Common Usage Patterns
66
- #
67
- # Basic Normalization:
68
- # result = SpecForge::Normalizer.normalize!({name: "Test"}, using: :user)
69
- #
70
- # Using Custom Structure:
71
- # structure = {count: {type: Integer, default: 0}}
72
- # result = SpecForge::Normalizer.normalize!({}, using: structure, label: "counter")
73
- #
74
- # Getting Default Values:
75
- # defaults = SpecForge::Normalizer.default(:user)
76
- #
77
- # == Error Handling
5
+ # Validates and transforms input data against structure definitions
78
6
  #
79
- # Validation errors are collected during normalization and can be:
80
- # - Raised via normalize! method
81
- # - Returned as a set via normalize method
82
- #
83
- # == Creating Custom Structures
84
- #
85
- # Add YAML files to lib/spec_forge/normalizers/ directory:
86
- # - Use '_shared.yml' for common structures that can be referenced
87
- # - Create custom validators in Normalizer::Validators class
88
- # - Specify labels for error messages with default_label method
7
+ # The Normalizer system ensures that YAML input conforms to expected
8
+ # structures, applying defaults, type checking, and custom validations.
9
+ # Structure definitions are loaded from YAML files in the normalizers/ directory.
89
10
  #
90
11
  class Normalizer
12
+ #
13
+ # Mapping of structure names to their human-readable labels
14
+ #
15
+ # @return [Hash<Symbol, String>]
16
+ #
17
+ LABELS = {
18
+ factory_reference: "factory reference",
19
+ global_context: "global context"
20
+ }.freeze
21
+
91
22
  class << self
92
23
  #
93
24
  # Collection of structure definitions used for validation
@@ -128,6 +59,8 @@ module SpecForge
128
59
  raise_errors! { normalize(input, using:, label:) }
129
60
  end
130
61
 
62
+ alias_method :validate!, :normalize!
63
+
131
64
  #
132
65
  # Normalizes input data against a structure without raising errors
133
66
  #
@@ -154,11 +87,10 @@ module SpecForge
154
87
  raise ArgumentError, "A label must be provided when using a custom structure"
155
88
  end
156
89
  else
157
- data = @structures[using.to_sym]
90
+ structure = @structures[using.to_sym]
158
91
 
159
92
  # We have a predefined structure and structures all have labels
160
- label ||= data[:label]
161
- structure = data[:structure]
93
+ label ||= structure.label
162
94
  end
163
95
 
164
96
  # Ensure we have a structure
@@ -171,7 +103,7 @@ module SpecForge
171
103
 
172
104
  # This is checked down here because it felt like it belonged...
173
105
  # and because of that pesky label
174
- raise Error::InvalidTypeError.new(input, Hash, for: label) if !Type.hash?(input)
106
+ raise Error::InvalidTypeError.new(input, Hash, for: label) unless input.is_a?(Hash)
175
107
 
176
108
  new(label, input, structure:).normalize
177
109
  end
@@ -198,10 +130,17 @@ module SpecForge
198
130
  # # => {name: "Unnamed"}
199
131
  #
200
132
  def default(name = nil, structure: nil, include_optional: false)
201
- structure ||= @structures.dig(name.to_sym, :structure)
133
+ structure ||= @structures[name.to_sym]
202
134
 
203
135
  if !structure.is_a?(Hash)
204
- raise ArgumentError, "Invalid structure. Provide either the name of the structure ('name') or a hash ('structure')"
136
+ message =
137
+ if name.present?
138
+ "No normalizer structure exists with name #{name.in_quotes}"
139
+ else
140
+ "The provided normalizer structure must be a Hash. Got #{structure.inspect}"
141
+ end
142
+
143
+ raise ArgumentError, message
205
144
  end
206
145
 
207
146
  default_from_structure(structure, include_optional:)
@@ -218,7 +157,21 @@ module SpecForge
218
157
  # @api private
219
158
  #
220
159
  def load_from_files
221
- @structures = Definition.from_files
160
+ base_path = Pathname.new(File.expand_path("normalizers", __dir__))
161
+ paths = Dir[base_path.join("**/*.yml")].sort
162
+
163
+ @structures =
164
+ paths.each_with_object({}) do |path, hash|
165
+ path = Pathname.new(path)
166
+
167
+ # Include the directory name in the path to include normalizers in directories
168
+ name = path.relative_path_from(base_path).to_s.delete_suffix(".yml").to_sym
169
+
170
+ input = YAML.safe_load_file(path, symbolize_names: true, aliases: true)
171
+ raise Error, "Normalizer defined at #{path.to_s.in_quotes} is empty" if input.blank?
172
+
173
+ hash[name] = Structure.new(input, label: LABELS[name] || name.to_s.humanize.downcase)
174
+ end
222
175
  end
223
176
 
224
177
  #
@@ -252,15 +205,6 @@ module SpecForge
252
205
  include Default
253
206
  end
254
207
 
255
- # @return [String] A label that describes the data itself
256
- attr_reader :label
257
-
258
- # @return [Hash] The data to normalize
259
- attr_reader :input
260
-
261
- # @return [Hash] The structure to normalize the data to
262
- attr_reader :structure
263
-
264
208
  #
265
209
  # Creates a normalizer for normalizing Hash data based on a structure
266
210
  #
@@ -282,7 +226,7 @@ module SpecForge
282
226
  # @return [Array<Hash, Set>] The normalized data and any errors
283
227
  #
284
228
  def normalize
285
- case input
229
+ case @input
286
230
  when Hash
287
231
  normalize_hash
288
232
  when Array
@@ -290,7 +234,7 @@ module SpecForge
290
234
  end
291
235
  end
292
236
 
293
- protected
237
+ private
294
238
 
295
239
  #
296
240
  # Extracts a value from a hash checking multiple keys
@@ -348,7 +292,8 @@ module SpecForge
348
292
  error_label += " (aliases #{aliases})"
349
293
  end
350
294
 
351
- error_label + " in #{label}"
295
+ error_label += " in #{@label}" if @label.present?
296
+ error_label
352
297
  end
353
298
 
354
299
  #
@@ -372,7 +317,7 @@ module SpecForge
372
317
  def normalize_hash
373
318
  output, errors = {}, Set.new
374
319
 
375
- structure.each do |key, definition|
320
+ @structure.each do |key, definition|
376
321
  # Skip the wildcard key if it exists, handled below
377
322
  next if key == :* || key == "*"
378
323
 
@@ -385,17 +330,17 @@ module SpecForge
385
330
  end
386
331
 
387
332
  # A wildcard will normalize the rest of the keys in the input
388
- wildcard_structure = structure[:*] || structure["*"]
333
+ wildcard_structure = @structure[:*] || @structure["*"]
389
334
 
390
335
  if wildcard_structure.present?
391
336
  # We need to determine which keys we need to check
392
- structure_keys = (structure.keys + structure.values.key_map(:aliases))
337
+ structure_keys = (@structure.keys + @structure.values.key_map(:aliases))
393
338
  .compact
394
339
  .flatten
395
340
  .map(&:to_sym)
396
341
 
397
342
  # Once we have which keys the structure used, we can get the remaining keys
398
- keys_to_normalize = (input.keys - structure_keys)
343
+ keys_to_normalize = (@input.keys - structure_keys)
399
344
 
400
345
  # They are checked against the wildcard's structure
401
346
  keys_to_normalize.each do |key|
@@ -439,10 +384,10 @@ module SpecForge
439
384
  type_class = definition[:type]
440
385
  aliases = definition[:aliases] || []
441
386
  default = definition[:default]
442
- required = definition[:required] != false
387
+ required = definition[:required] == true
443
388
 
444
389
  # Get the value
445
- value = value_from_keys(input, [key.to_s] + aliases)
390
+ value = value_from_keys(@input, [key.to_s] + aliases)
446
391
 
447
392
  # Drop the key if needed
448
393
  return [false] if value.nil? && !has_default && !required
@@ -452,13 +397,20 @@ module SpecForge
452
397
 
453
398
  error_label = generate_error_label(key, aliases)
454
399
 
455
- # Type + existence check
456
400
  if !valid_class?(value, type_class, nilable: has_default)
457
- if (line_number = input[:line_number])
458
- error_label += " (line #{line_number})"
459
- end
401
+ raise Error::InvalidTypeError.new(
402
+ value,
403
+ type_class,
404
+ for: error_label,
405
+ attribute_name: key.to_s,
406
+ description: definition[:description],
407
+ examples: definition[:examples]
408
+ )
409
+ end
460
410
 
461
- raise Error::InvalidTypeError.new(value, type_class, for: error_label)
411
+ # Call the transformer if it has one
412
+ if (name = definition[:transformer]) && name.present?
413
+ value = Transformers.call(name, value)
462
414
  end
463
415
 
464
416
  # Call the validator if it has one
@@ -495,7 +447,7 @@ module SpecForge
495
447
  #
496
448
  def normalize_substructure(new_label, value, substructure, errors)
497
449
  if substructure.is_a?(Proc)
498
- return substructure.call(value, errors:, label:)
450
+ return substructure.call(value, errors:, label: @label)
499
451
  end
500
452
 
501
453
  return value unless value.is_a?(Hash) || value.is_a?(Array)
@@ -526,20 +478,25 @@ module SpecForge
526
478
  def normalize_array
527
479
  output, errors = [], Set.new
528
480
 
529
- input.each_with_index do |value, index|
530
- type_class = structure[:type]
531
- error_label = "index #{index} of #{label}"
481
+ @input.each_with_index do |value, index|
482
+ type_class = @structure[:type]
483
+ error_label = "index #{index} of #{@label}"
532
484
 
533
485
  if !valid_class?(value, type_class)
534
486
  raise Error::InvalidTypeError.new(value, type_class, for: error_label)
535
487
  end
536
488
 
489
+ # Call the transformer if it has one
490
+ if (name = @structure[:transformer]) && name.present?
491
+ value = Transformers.call(name, value)
492
+ end
493
+
537
494
  # Call the validator if it has one
538
- if (name = structure[:validator]) && name.present?
495
+ if (name = @structure[:validator]) && name.present?
539
496
  Validators.call(name, value, label: error_label)
540
497
  end
541
498
 
542
- if (substructure = structure[:structure])
499
+ if (substructure = @structure[:structure])
543
500
  value = normalize_substructure(error_label, value, substructure, errors)
544
501
  end
545
502
 
@@ -0,0 +1,38 @@
1
+ # =============================================================================
2
+ # Callback Structure Definition
3
+ # =============================================================================
4
+ # Defines the structure for callback invocations in SpecForge blueprints.
5
+ # Callbacks execute registered Ruby code during test execution for tasks
6
+ # like database seeding, cleanup, or custom operations.
7
+ #
8
+ # Callbacks are registered in forge_helper.rb:
9
+ # SpecForge.configure do |config|
10
+ # config.register_callback(:seed_users) do |context, count:|
11
+ # count.times { User.create!(name: Faker::Name.name) }
12
+ # end
13
+ # end
14
+ #
15
+ # The context parameter provides access to variables, step, blueprint, etc.
16
+ # =============================================================================
17
+
18
+ name:
19
+ type: string
20
+ required: true
21
+ description: The name of the registered callback to execute.
22
+ examples:
23
+ - "seed_database"
24
+ - "cleanup"
25
+ - "create_users"
26
+
27
+ arguments:
28
+ type: [array, hash]
29
+ description: |-
30
+ Arguments to pass to the callback. Use a hash for keyword arguments
31
+ or an array for positional arguments.
32
+ examples:
33
+ - |-
34
+ arguments:
35
+ count: 10
36
+ role: "admin"
37
+ - |-
38
+ arguments: [1, 2, 3]
@@ -1,23 +1,73 @@
1
- base_url: string
1
+ # =============================================================================
2
+ # Configuration Structure Definition
3
+ # =============================================================================
4
+ # Defines the structure for SpecForge global configuration.
5
+ # Configuration is set in forge_helper.rb via SpecForge.configure block.
6
+ #
7
+ # Example usage in forge_helper.rb:
8
+ # SpecForge.configure do |config|
9
+ # config.base_url = "https://api.example.com"
10
+ # config.global_variables = {
11
+ # api_version: "v1",
12
+ # admin_email: "admin@test.com"
13
+ # }
14
+ # end
15
+ # =============================================================================
2
16
 
3
- headers:
4
- reference: headers
17
+ base_url:
18
+ type: string
19
+ required: true
20
+ description: |-
21
+ Base URL prepended to all request paths. Can be overridden per-step
22
+ using request.base_url.
23
+ examples:
24
+ - "https://api.example.com"
25
+ - "http://localhost:3000"
5
26
 
6
- query:
7
- reference: query
27
+ global_variables:
28
+ type: hash
29
+ default: {}
30
+ description: |-
31
+ Variables available to all blueprints via {{ variable_name }} syntax.
32
+ Local variables (defined with store:) can shadow global variables
33
+ within a blueprint. Supports nested hashes for grouped configuration.
34
+ examples:
35
+ - |-
36
+ global_variables:
37
+ api_version: "v1"
38
+ admin_email: "admin@test.com"
39
+ - |-
40
+ global_variables:
41
+ admin_credentials:
42
+ email: "admin@test.com"
43
+ password: "admin123"
8
44
 
9
45
  factories:
10
46
  type: hash
11
47
  default: {}
48
+ description: |-
49
+ Configuration for FactoryBot integration. Controls how factories
50
+ are discovered and loaded for use in blueprints via the
51
+ factories. namespace.
12
52
  structure:
13
- ###########################################
14
53
  auto_discover:
15
54
  type: boolean
16
55
  default: true
56
+ description: |-
57
+ When true, automatically discovers and loads factory definitions
58
+ from standard paths. Set to false to manually specify factory paths.
59
+ examples:
60
+ - true
61
+ - false
17
62
 
18
63
  paths:
19
64
  type: array
20
65
  default: []
21
-
22
- on_debug:
23
- type: proc
66
+ structure:
67
+ type: [string, pathname]
68
+ description: |-
69
+ Additional paths to search for factory definitions. These are
70
+ searched in addition to (not instead of) auto-discovered paths
71
+ when auto_discover is true.
72
+ examples:
73
+ - '["lib/factories", "spec/support/factories"]'
@@ -1,12 +1,63 @@
1
+ # =============================================================================
2
+ # Factory Structure Definition
3
+ # =============================================================================
4
+ # Defines the structure for YAML factory definitions in spec_forge/factories/.
5
+ # The filename determines the factory name (e.g., user.yml creates a user factory).
6
+ #
7
+ # Factories are referenced in blueprints using the factories. namespace:
8
+ # {{ factories.user.id }}
9
+ # {{ factories.post.title }}
10
+ # =============================================================================
11
+
1
12
  model_class:
2
13
  type: string
3
14
  default: ""
4
15
  aliases:
5
- - class
16
+ - class
17
+ description: |-
18
+ Override the inferred model class when the factory name differs from the model.
19
+ Usually inferred automatically from the factory name.
20
+ examples:
21
+ - "User"
22
+ - "Admin::Account"
6
23
 
7
24
  variables:
8
- reference: variables
25
+ type: hash
26
+ default: {}
27
+ description: |-
28
+ Computed values for use in attributes. Use when you need the same
29
+ generated value in multiple attributes.
30
+ examples:
31
+ - |-
32
+ variables:
33
+ hire_date: "{{ faker.date.backward(days: 365) }}"
34
+
35
+ traits:
36
+ type: hash
37
+ default: {}
38
+ description: |-
39
+ Named variations that modify or extend the base factory definition.
40
+ Each trait defines attribute overrides that are applied when the trait is used.
41
+ examples:
42
+ - |-
43
+ traits:
44
+ admin:
45
+ role: "admin"
46
+ inactive:
47
+ active: false
48
+ with_department:
49
+ department: "{{ faker.company.department }}"
9
50
 
10
51
  attributes:
11
52
  type: hash
12
53
  default: {}
54
+ description: |-
55
+ Core record data for the factory. Supports variable interpolation
56
+ and faker data generation.
57
+ examples:
58
+ - |-
59
+ attributes:
60
+ name: "{{ faker.name.name }}"
61
+ email: "{{ faker.internet.email }}"
62
+ role: "user"
63
+ active: true
@@ -1,15 +1,76 @@
1
+ # =============================================================================
2
+ # Factory Reference Structure Definition
3
+ # =============================================================================
4
+ # Defines the structure for referencing factories within blueprints.
5
+ # Factory references allow inline factory usage with custom attributes
6
+ # and build strategies.
7
+ #
8
+ # Example usage in blueprints:
9
+ # store:
10
+ # user:
11
+ # factories.user:
12
+ # strategy: build
13
+ # attributes:
14
+ # role: "admin"
15
+ # =============================================================================
16
+
17
+ traits:
18
+ type: array
19
+ default: []
20
+ aliases:
21
+ - trait
22
+ description: |-
23
+ FactoryBot traits to apply to this factory invocation.
24
+ Traits are defined in the factory file and modify the built object.
25
+ Multiple traits can be combined and are applied in order.
26
+ examples:
27
+ - "traits: [admin]"
28
+ - "traits: [admin, inactive]"
29
+
1
30
  attributes:
2
31
  type: hash
3
32
  default: {}
33
+ description: |-
34
+ Attribute overrides for this specific factory invocation.
35
+ Merged with the factory's default attributes. Supports variable
36
+ interpolation and faker data generation.
37
+ examples:
38
+ - |-
39
+ attributes:
40
+ name: "Custom Name"
41
+ email: "custom@test.com"
42
+ - |-
43
+ attributes:
44
+ department: "Engineering"
4
45
 
5
46
  build_strategy:
6
47
  type: string
7
48
  default: create
8
49
  aliases:
9
- - strategy
50
+ - strategy
51
+ description: |-
52
+ The FactoryBot build strategy to use. Determines whether records
53
+ are persisted to the database or just built in memory.
54
+ - create: Persists to database (default)
55
+ - build: Instantiates without saving
56
+ - build_stubbed / stubbed: Creates a stubbed instance
57
+ - attributes_for: Returns a hash of attributes
58
+ examples:
59
+ - "create"
60
+ - "build"
61
+ - "build_stubbed"
10
62
 
11
63
  size:
12
64
  type: integer
13
65
  default: 0
14
66
  aliases:
15
- - count
67
+ - count
68
+ transformer: abs
69
+ description: |-
70
+ Number of records to create. When 0 (default), creates a single
71
+ record. When > 0, creates that many records and returns an array.
72
+ Useful for testing list endpoints or batch operations.
73
+ examples:
74
+ - 0
75
+ - 5
76
+ - 100