spec_forge 0.5.0 → 0.7.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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +3 -3
  3. data/CHANGELOG.md +217 -2
  4. data/README.md +162 -25
  5. data/flake.lock +3 -3
  6. data/flake.nix +11 -5
  7. data/lib/spec_forge/attribute/chainable.rb +208 -20
  8. data/lib/spec_forge/attribute/factory.rb +92 -15
  9. data/lib/spec_forge/attribute/faker.rb +62 -13
  10. data/lib/spec_forge/attribute/global.rb +96 -0
  11. data/lib/spec_forge/attribute/literal.rb +15 -2
  12. data/lib/spec_forge/attribute/matcher.rb +186 -11
  13. data/lib/spec_forge/attribute/parameterized.rb +45 -12
  14. data/lib/spec_forge/attribute/regex.rb +55 -5
  15. data/lib/spec_forge/attribute/resolvable.rb +48 -5
  16. data/lib/spec_forge/attribute/resolvable_array.rb +62 -4
  17. data/lib/spec_forge/attribute/resolvable_hash.rb +62 -4
  18. data/lib/spec_forge/attribute/store.rb +65 -0
  19. data/lib/spec_forge/attribute/transform.rb +33 -5
  20. data/lib/spec_forge/attribute/variable.rb +37 -6
  21. data/lib/spec_forge/attribute.rb +166 -66
  22. data/lib/spec_forge/backtrace_formatter.rb +26 -3
  23. data/lib/spec_forge/callbacks.rb +88 -0
  24. data/lib/spec_forge/cli/actions.rb +27 -0
  25. data/lib/spec_forge/cli/command.rb +78 -24
  26. data/lib/spec_forge/cli/docs/generate.rb +72 -0
  27. data/lib/spec_forge/cli/docs.rb +92 -0
  28. data/lib/spec_forge/cli/init.rb +51 -9
  29. data/lib/spec_forge/cli/new.rb +67 -6
  30. data/lib/spec_forge/cli/run.rb +32 -4
  31. data/lib/spec_forge/cli/serve.rb +155 -0
  32. data/lib/spec_forge/cli.rb +26 -7
  33. data/lib/spec_forge/configuration.rb +96 -24
  34. data/lib/spec_forge/context/callbacks.rb +91 -0
  35. data/lib/spec_forge/context/global.rb +72 -0
  36. data/lib/spec_forge/context/store.rb +131 -0
  37. data/lib/spec_forge/context/variables.rb +91 -0
  38. data/lib/spec_forge/context.rb +36 -0
  39. data/lib/spec_forge/core_ext/array.rb +27 -0
  40. data/lib/spec_forge/core_ext/rspec.rb +22 -4
  41. data/lib/spec_forge/documentation/builder.rb +383 -0
  42. data/lib/spec_forge/documentation/document/operation.rb +47 -0
  43. data/lib/spec_forge/documentation/document/parameter.rb +22 -0
  44. data/lib/spec_forge/documentation/document/request_body.rb +24 -0
  45. data/lib/spec_forge/documentation/document/response.rb +39 -0
  46. data/lib/spec_forge/documentation/document/response_body.rb +27 -0
  47. data/lib/spec_forge/documentation/document.rb +48 -0
  48. data/lib/spec_forge/documentation/generators/base.rb +81 -0
  49. data/lib/spec_forge/documentation/generators/openapi/base.rb +100 -0
  50. data/lib/spec_forge/documentation/generators/openapi/error_formatter.rb +149 -0
  51. data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +65 -0
  52. data/lib/spec_forge/documentation/generators/openapi.rb +59 -0
  53. data/lib/spec_forge/documentation/generators.rb +17 -0
  54. data/lib/spec_forge/documentation/loader/cache.rb +138 -0
  55. data/lib/spec_forge/documentation/loader.rb +159 -0
  56. data/lib/spec_forge/documentation/openapi/base.rb +33 -0
  57. data/lib/spec_forge/documentation/openapi/v3_0/example.rb +44 -0
  58. data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +42 -0
  59. data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +175 -0
  60. data/lib/spec_forge/documentation/openapi/v3_0/response.rb +65 -0
  61. data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +80 -0
  62. data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +71 -0
  63. data/lib/spec_forge/documentation/openapi.rb +23 -0
  64. data/lib/spec_forge/documentation.rb +27 -0
  65. data/lib/spec_forge/error.rb +284 -113
  66. data/lib/spec_forge/factory.rb +35 -16
  67. data/lib/spec_forge/filter.rb +86 -0
  68. data/lib/spec_forge/forge.rb +171 -0
  69. data/lib/spec_forge/http/backend.rb +101 -29
  70. data/lib/spec_forge/http/client.rb +23 -13
  71. data/lib/spec_forge/http/request.rb +85 -62
  72. data/lib/spec_forge/http/verb.rb +79 -0
  73. data/lib/spec_forge/http.rb +105 -0
  74. data/lib/spec_forge/loader.rb +244 -0
  75. data/lib/spec_forge/matchers.rb +130 -0
  76. data/lib/spec_forge/normalizer/default.rb +51 -0
  77. data/lib/spec_forge/normalizer/definition.rb +248 -0
  78. data/lib/spec_forge/normalizer/validators.rb +99 -0
  79. data/lib/spec_forge/normalizer.rb +486 -115
  80. data/lib/spec_forge/normalizers/_shared.yml +74 -0
  81. data/lib/spec_forge/normalizers/configuration.yml +23 -0
  82. data/lib/spec_forge/normalizers/constraint.yml +8 -0
  83. data/lib/spec_forge/normalizers/expectation.yml +47 -0
  84. data/lib/spec_forge/normalizers/factory.yml +12 -0
  85. data/lib/spec_forge/normalizers/factory_reference.yml +15 -0
  86. data/lib/spec_forge/normalizers/global_context.yml +28 -0
  87. data/lib/spec_forge/normalizers/spec.yml +50 -0
  88. data/lib/spec_forge/runner/adapter.rb +183 -0
  89. data/lib/spec_forge/runner/callbacks.rb +246 -0
  90. data/lib/spec_forge/runner/debug_proxy.rb +213 -0
  91. data/lib/spec_forge/runner/listener.rb +54 -0
  92. data/lib/spec_forge/runner/metadata.rb +58 -0
  93. data/lib/spec_forge/runner/state.rb +98 -0
  94. data/lib/spec_forge/runner.rb +50 -125
  95. data/lib/spec_forge/spec/expectation/constraint.rb +100 -21
  96. data/lib/spec_forge/spec/expectation.rb +47 -51
  97. data/lib/spec_forge/spec.rb +50 -108
  98. data/lib/spec_forge/type.rb +36 -4
  99. data/lib/spec_forge/version.rb +4 -1
  100. data/lib/spec_forge.rb +168 -76
  101. data/lib/templates/openapi.yml.tt +22 -0
  102. data/lib/templates/redoc.html.tt +28 -0
  103. data/lib/templates/swagger.html.tt +59 -0
  104. metadata +109 -16
  105. data/lib/spec_forge/normalizer/configuration.rb +0 -77
  106. data/lib/spec_forge/normalizer/constraint.rb +0 -47
  107. data/lib/spec_forge/normalizer/expectation.rb +0 -86
  108. data/lib/spec_forge/normalizer/factory.rb +0 -65
  109. data/lib/spec_forge/normalizer/factory_reference.rb +0 -71
  110. data/lib/spec_forge/normalizer/spec.rb +0 -74
  111. data/spec_forge/factories/user.yml +0 -4
  112. data/spec_forge/forge_helper.rb +0 -48
  113. data/spec_forge/specs/users.yml +0 -65
  114. /data/lib/templates/{forge_helper.tt → forge_helper.rb.tt} +0 -0
  115. /data/lib/templates/{new_factory.tt → new_factory.yml.tt} +0 -0
  116. /data/lib/templates/{new_spec.tt → new_spec.yml.tt} +0 -0
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Normalizer
5
- class Constraint < Normalizer
6
- STRUCTURE = {
7
- status: {
8
- type: Integer
9
- },
10
- json: {
11
- type: [Hash, Array],
12
- default: {}
13
- }
14
- }.freeze
15
- end
16
-
17
- # On Normalizer
18
- class << self
19
- #
20
- # Generates an empty constraint hash
21
- #
22
- # @return [Hash]
23
- #
24
- def default_constraint
25
- Constraint.default
26
- end
27
-
28
- #
29
- # Normalize a constraint hash
30
- # Used internally by .normalize_spec, but is available for utility
31
- #
32
- # @param constraint [Hash] Constraint representation as a Hash
33
- #
34
- # @return [Array] Two item array
35
- # First - The normalized hash
36
- # Second - Array of errors, if any
37
- #
38
- # @private
39
- #
40
- def normalize_constraint(constraint)
41
- raise InvalidTypeError.new(constraint, Hash, for: "expect") unless Type.hash?(constraint)
42
-
43
- Normalizer::Constraint.new("expect", constraint).normalize
44
- end
45
- end
46
- end
47
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Normalizer
5
- class Expectation < Normalizer
6
- STRUCTURE = {
7
- name: {type: String, default: ""},
8
- base_url: Normalizer::SHARED_ATTRIBUTES[:base_url],
9
- url: Normalizer::SHARED_ATTRIBUTES[:url],
10
- http_method: Normalizer::SHARED_ATTRIBUTES[:http_method],
11
- headers: Normalizer::SHARED_ATTRIBUTES[:headers],
12
- query: Normalizer::SHARED_ATTRIBUTES[:query],
13
- body: Normalizer::SHARED_ATTRIBUTES[:body],
14
- variables: Normalizer::SHARED_ATTRIBUTES[:variables],
15
- debug: Normalizer::SHARED_ATTRIBUTES[:debug],
16
- expect: {type: Hash}
17
- }.freeze
18
- end
19
-
20
- # On Normalizer
21
- class << self
22
- #
23
- # Generates an empty expectation hash
24
- #
25
- # @return [Hash]
26
- #
27
- def default_expectation
28
- Expectation.default
29
- end
30
-
31
- #
32
- # Normalize an array of expectation hashes
33
- #
34
- # @raises InvalidStructureError if anything is missing/invalid type
35
- #
36
- # @param input [Hash] The hash to normalize
37
- #
38
- # @return [Hash] A normalized hash as a new instance
39
- #
40
- def normalize_expectations!(input)
41
- raise_errors! do
42
- normalize_expectations(input)
43
- end
44
- end
45
-
46
- #
47
- # Normalize an array of expectation hashes
48
- # Used internally by .normalize_spec, but is available for utility
49
- #
50
- # @param expectations [Array<Hash>] An array of expectation hashes
51
- #
52
- # @return [Array] Two item array
53
- # First - The normalized Array<Hash>
54
- # Second - Array of errors, if any
55
- #
56
- # @private
57
- #
58
- def normalize_expectations(expectations)
59
- if !Type.array?(expectations)
60
- raise InvalidTypeError.new(expectations, Array, for: "\"expectations\" on spec")
61
- end
62
-
63
- final_errors = Set.new
64
- final_output = expectations.map.with_index do |expectation, index|
65
- normalizer = Normalizer::Expectation.new("expectation (item #{index})", expectation)
66
- output, errors = normalizer.normalize
67
-
68
- # If expect is not provided, skip the constraints
69
- if (constraint = expectation[:expect])
70
- constraint_output, constraint_errors = Normalizer::Constraint.new(
71
- "expect (item #{index})", constraint
72
- ).normalize
73
-
74
- output[:expect] = constraint_output
75
- errors.merge(constraint_errors) if constraint_errors.size > 0
76
- end
77
-
78
- final_errors.merge(errors) if errors.size > 0
79
- output
80
- end
81
-
82
- [final_output, final_errors]
83
- end
84
- end
85
- end
86
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Normalizer
5
- class Factory < Normalizer
6
- STRUCTURE = {
7
- model_class: {
8
- type: String,
9
- aliases: %i[class],
10
- default: ""
11
- },
12
- variables: Normalizer::SHARED_ATTRIBUTES[:variables],
13
- attributes: {
14
- type: Hash,
15
- default: {}
16
- }
17
- }.freeze
18
- end
19
-
20
- # On Normalizer
21
- class << self
22
- #
23
- # Generates an empty factory hash
24
- #
25
- # @return [Hash]
26
- #
27
- def default_factory
28
- Factory.default
29
- end
30
-
31
- #
32
- # Normalizes a factory hash by standardizing its keys while ensuring the required data
33
- # is provided or defaulted.
34
- # Raises InvalidStructureError if anything is missing/invalid type
35
- #
36
- # @param input [Hash] The hash to normalize
37
- #
38
- # @return [Hash] A normalized hash as a new instance
39
- #
40
- def normalize_factory!(input)
41
- raise_errors! do
42
- normalize_factory(input)
43
- end
44
- end
45
-
46
- #
47
- # Normalize a factory hash
48
- # Used internally by .normalize_factory, but is available for utility
49
- #
50
- # @param factory [Hash] Factory representation as a Hash
51
- #
52
- # @return [Array] Two item array
53
- # First - The normalized hash
54
- # Second - Array of errors, if any
55
- #
56
- # @private
57
- #
58
- def normalize_factory(factory)
59
- raise InvalidTypeError.new(factory, Hash, for: "factory") unless Type.hash?(factory)
60
-
61
- Normalizer::Factory.new("factory", factory).normalize
62
- end
63
- end
64
- end
65
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Normalizer
5
- class FactoryReference < Normalizer
6
- STRUCTURE = {
7
- attributes: {
8
- type: Hash,
9
- default: {}
10
- },
11
- build_strategy: {
12
- type: String,
13
- aliases: %i[strategy],
14
- default: "create"
15
- },
16
- size: {
17
- type: Integer,
18
- aliases: %i[count],
19
- default: 0
20
- }
21
- }.freeze
22
- end
23
-
24
- # On Normalizer
25
- class << self
26
- #
27
- # Generates an empty Attribute::Factory hash
28
- #
29
- # @return [Hash]
30
- #
31
- def default_factory_reference
32
- FactoryReference.default
33
- end
34
-
35
- #
36
- # Normalizes a Attribute::Factory hash by standardizing
37
- # its keys while ensuring the required data is provided or defaulted.
38
- # Raises InvalidStructureError if anything is missing/invalid type
39
- #
40
- # @param input [Hash] The hash to normalize
41
- #
42
- # @return [Hash] A normalized hash as a new instance
43
- #
44
- def normalize_factory_reference!(input, **)
45
- raise_errors! do
46
- normalize_factory_reference(input, **)
47
- end
48
- end
49
-
50
- #
51
- # Normalize a factory hash
52
- # Used internally by .normalize_factory_reference, but is available for utility
53
- #
54
- # @param factory [Hash] Attribute::Factory representation as a Hash
55
- #
56
- # @return [Array] Two item array
57
- # First - The normalized hash
58
- # Second - Array of errors, if any
59
- #
60
- # @private
61
- #
62
- def normalize_factory_reference(factory, label: "factory reference")
63
- if !Type.hash?(factory)
64
- raise InvalidTypeError.new(factory, Hash, for: "factory reference")
65
- end
66
-
67
- Normalizer::FactoryReference.new(label, factory).normalize
68
- end
69
- end
70
- end
71
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Normalizer
5
- class Spec < Normalizer
6
- STRUCTURE = {
7
- base_url: Normalizer::SHARED_ATTRIBUTES[:base_url],
8
- url: Normalizer::SHARED_ATTRIBUTES[:url],
9
- http_method: Normalizer::SHARED_ATTRIBUTES[:http_method],
10
- headers: Normalizer::SHARED_ATTRIBUTES[:headers],
11
- query: Normalizer::SHARED_ATTRIBUTES[:query],
12
- body: Normalizer::SHARED_ATTRIBUTES[:body],
13
- variables: Normalizer::SHARED_ATTRIBUTES[:variables],
14
- debug: Normalizer::SHARED_ATTRIBUTES[:debug],
15
- expectations: {type: Array}
16
- }.freeze
17
- end
18
-
19
- # On Normalizer
20
- class << self
21
- #
22
- # Generates an empty spec hash
23
- #
24
- # @return [Hash]
25
- #
26
- def default_spec
27
- Spec.default
28
- end
29
-
30
- #
31
- # Normalizes a complete spec hash by standardizing its keys while ensuring the required data
32
- # is provided or defaulted.
33
- # Raises InvalidStructureError if anything is missing/invalid type
34
- #
35
- # @param input [Hash] The hash to normalize
36
- #
37
- # @return [Hash] A normalized hash as a new instance
38
- #
39
- def normalize_spec!(input)
40
- raise_errors! do
41
- output, errors = normalize_spec(input)
42
-
43
- # Process expectations
44
- if (expectations = input[:expectations]) && Type.array?(expectations)
45
- expectation_output, expectation_errors = normalize_expectations(expectations)
46
-
47
- output[:expectations] = expectation_output
48
- errors += expectation_errors if expectation_errors.size > 0
49
- end
50
-
51
- [output, errors]
52
- end
53
- end
54
-
55
- #
56
- # Normalize a spec hash
57
- # Used internally by .normalize_spec, but is available for utility
58
- #
59
- # @param spec [Hash] Spec representation as a Hash
60
- #
61
- # @return [Array] Two item array
62
- # First - The normalized hash
63
- # Second - Array of errors, if any
64
- #
65
- # @private
66
- #
67
- def normalize_spec(spec)
68
- raise InvalidTypeError.new(spec, Hash, for: "spec") unless Type.hash?(spec)
69
-
70
- Normalizer::Spec.new("spec", spec).normalize
71
- end
72
- end
73
- end
74
- end
@@ -1,4 +0,0 @@
1
- user:
2
- class: User
3
- attributes:
4
- attribute: value
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ##########################################
4
- # Framework Integration
5
- ##########################################
6
-
7
- # Rails Integration
8
- # require_relative "../config/environment"
9
-
10
- # RSpec Integration (includes your existing configurations)
11
- # require_relative "../spec/spec_helper"
12
-
13
- # Custom requires (models, libraries, etc)
14
- # Dir[File.join(__dir__, "..", "lib", "**", "*.rb")].sort.each { |f| require f }
15
-
16
- ##########################################
17
- # Configuration
18
- ##########################################
19
-
20
- SpecForge.configure do |config|
21
- # Base configuration
22
- config.base_url = "http://localhost:3000"
23
-
24
- # Default request headers
25
- config.headers = {
26
- "Authorization" => "Bearer #{ENV.fetch("API_TOKEN", "")}"
27
- }
28
-
29
- # Optional: Default query parameters
30
- # config.query = {api_key: ENV['API_KEY']}
31
-
32
- # Factory configuration
33
- # config.factories.auto_discover = false # Default: true
34
- # config.factories.paths += ["lib/factories"] # Adds to default paths
35
-
36
- # Debug configuration
37
- # Available in specs with debug: true (aliases: breakpoint, pry)
38
- # Defaults to printing state overview (-> { puts inspect })
39
- # Available context: expectation, variables, request, response,
40
- # expected_status, expected_json
41
- # config.on_debug { binding.pry }
42
-
43
- # Test Framework Configuration
44
- # Useful for database cleaners, test data setup, etc
45
- # config.specs.before(:suite) { }
46
- # config.specs.around { |example| example.run }
47
- # config.specs.formatter = :documentation
48
- end
@@ -1,65 +0,0 @@
1
- index_users:
2
- url: /users
3
- expectations:
4
- - expect:
5
- status: 200
6
-
7
- show_user:
8
- url: /users/{id}
9
- expectations:
10
- - query:
11
- id: -1
12
- expect:
13
- status: 404
14
- - query:
15
- id: 1
16
- expect:
17
- status: 200
18
- json:
19
- name: kind_of.string
20
- email: /\w+@example\.com/i
21
-
22
- create_user:
23
- url: /users
24
- method: post
25
- expectations:
26
- - expect:
27
- status: 400
28
- - expect:
29
- status: 200
30
- json:
31
- name: variables.name
32
- role: variables.role
33
- variables:
34
- name: faker.name.name
35
- role: user
36
- body:
37
- name: variables.name
38
-
39
- update_user:
40
- url: /users/{id}
41
- method: patch
42
- query:
43
- id: 1
44
- variables:
45
- number:
46
- faker.number.between:
47
- from: 100000
48
- to: 999999
49
- expectations:
50
- - expect:
51
- status: 200
52
- json:
53
- name: kind_of.string
54
- number: kind_of.integer
55
- body:
56
- number: variables.number
57
-
58
- destroy_user:
59
- url: /users/{id}
60
- method: delete
61
- query:
62
- id: 1
63
- expectations:
64
- - expect:
65
- status: 200
File without changes