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
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SpecForge
|
|
4
|
-
class Context
|
|
5
|
-
#
|
|
6
|
-
# Manages user-defined callbacks grouped by lifecycle hook
|
|
7
|
-
#
|
|
8
|
-
# This class collects and organizes callbacks by their hook type
|
|
9
|
-
# (before_file, after_each, etc.) to support the test lifecycle.
|
|
10
|
-
# It ensures callbacks are properly categorized for execution.
|
|
11
|
-
#
|
|
12
|
-
# @example Creating callback groups
|
|
13
|
-
# callbacks = Context::Callbacks.new([
|
|
14
|
-
# {before_file: "setup_environment"},
|
|
15
|
-
# {after_each: "log_test_result"}
|
|
16
|
-
# ])
|
|
17
|
-
#
|
|
18
|
-
class Callbacks
|
|
19
|
-
#
|
|
20
|
-
# Creates a new callbacks collection
|
|
21
|
-
#
|
|
22
|
-
# @param callback_array [Array] Optional initial callbacks to register
|
|
23
|
-
#
|
|
24
|
-
# @return [Callbacks] A new callbacks collection
|
|
25
|
-
#
|
|
26
|
-
def initialize(callback_array = [])
|
|
27
|
-
set(callback_array)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
# Updates the callbacks collection
|
|
32
|
-
#
|
|
33
|
-
# @param callback_array [Array] New callbacks to register
|
|
34
|
-
#
|
|
35
|
-
# @return [self]
|
|
36
|
-
#
|
|
37
|
-
def set(callback_array)
|
|
38
|
-
@inner = organize_callbacks_by_hook(callback_array)
|
|
39
|
-
self
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
#
|
|
43
|
-
# Returns the hash representation of callbacks
|
|
44
|
-
#
|
|
45
|
-
# @return [Hash] Callbacks organized by hook type
|
|
46
|
-
#
|
|
47
|
-
def to_h
|
|
48
|
-
@inner
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
# Executes all registered callbacks for a specific lifecycle hook
|
|
53
|
-
#
|
|
54
|
-
# @param hook_name [String, Symbol] The lifecycle hook (before_file, after_each, etc.)
|
|
55
|
-
# @param context [Hash] State data that will be converted to a structured object
|
|
56
|
-
# and passed to callbacks
|
|
57
|
-
#
|
|
58
|
-
def run(hook_name, context = {})
|
|
59
|
-
context = context.to_struct
|
|
60
|
-
|
|
61
|
-
@inner[hook_name].each do |callback_name|
|
|
62
|
-
SpecForge::Callbacks.run(callback_name, context)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
#
|
|
69
|
-
# Organizes callbacks from an array to hash structure by hook type
|
|
70
|
-
# Groups callbacks like before_file, after_each, etc. for easier lookup
|
|
71
|
-
#
|
|
72
|
-
# @param callback_array [Array] The array of callbacks
|
|
73
|
-
#
|
|
74
|
-
# @return [Hash] Callbacks indexed by hook type
|
|
75
|
-
#
|
|
76
|
-
# @private
|
|
77
|
-
#
|
|
78
|
-
def organize_callbacks_by_hook(callback_array)
|
|
79
|
-
groups = Hash.new { |h, k| h[k] = Set.new }
|
|
80
|
-
|
|
81
|
-
callback_array.each_with_object(groups) do |callbacks, groups|
|
|
82
|
-
callbacks.each do |hook, name|
|
|
83
|
-
next if name.blank?
|
|
84
|
-
|
|
85
|
-
groups[hook].add(name)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SpecForge
|
|
4
|
-
class Context
|
|
5
|
-
#
|
|
6
|
-
# Manages global state and variables at the spec file level.
|
|
7
|
-
#
|
|
8
|
-
# The Global class provides access to variables that are defined at the global level
|
|
9
|
-
# in a spec file and are accessible across all specs and expectations in a file.
|
|
10
|
-
# Unlike regular variables, global variables do not support overlaying - they maintain
|
|
11
|
-
# consistent values throughout test execution.
|
|
12
|
-
#
|
|
13
|
-
# @example Basic usage
|
|
14
|
-
# global = Global.new(variables: {api_version: "v2", environment: "test"})
|
|
15
|
-
#
|
|
16
|
-
# global.variables[:api_version] #=> "v2"
|
|
17
|
-
# global.variables[:environment] #=> "test"
|
|
18
|
-
#
|
|
19
|
-
# # Update global variables
|
|
20
|
-
# global.set(variables: {environment: "staging"})
|
|
21
|
-
# global.variables[:environment] #=> "staging"
|
|
22
|
-
# global.variables[:api_version] #=> nil
|
|
23
|
-
#
|
|
24
|
-
class Global
|
|
25
|
-
# @return [Context::Variables] The container for global variables
|
|
26
|
-
attr_reader :variables
|
|
27
|
-
|
|
28
|
-
# @return [Context::Callbacks] The container for callbacks
|
|
29
|
-
attr_reader :callbacks
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
# Creates a new Global context instance
|
|
33
|
-
#
|
|
34
|
-
# @param variables [Hash<Symbol, Object>] A hash of variable names and values
|
|
35
|
-
# @param callbacks [Array<Hash<Symbol, String>>] An array of callback hooks
|
|
36
|
-
#
|
|
37
|
-
# @return [Global] The new Global instance
|
|
38
|
-
#
|
|
39
|
-
def initialize(variables: {}, callbacks: [])
|
|
40
|
-
@variables = Variables.new(base: variables)
|
|
41
|
-
@callbacks = Callbacks.new(callbacks)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
#
|
|
45
|
-
# Sets the global variables
|
|
46
|
-
#
|
|
47
|
-
# @param variables [Hash<Symbol, Object>] A hash of variable names and values
|
|
48
|
-
# @param callbacks [Array<Hash<Symbol, String>>] An array of callback hooks
|
|
49
|
-
#
|
|
50
|
-
# @return [self]
|
|
51
|
-
#
|
|
52
|
-
def set(variables: {}, callbacks: [])
|
|
53
|
-
@variables.set(base: variables)
|
|
54
|
-
@callbacks.set(callbacks)
|
|
55
|
-
|
|
56
|
-
self
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
-
# Returns a hash representation of the global context
|
|
61
|
-
#
|
|
62
|
-
# @return [Hash]
|
|
63
|
-
#
|
|
64
|
-
def to_h
|
|
65
|
-
{
|
|
66
|
-
variables: variables.to_h,
|
|
67
|
-
callbacks: callbacks.to_h
|
|
68
|
-
}
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SpecForge
|
|
4
|
-
class Context
|
|
5
|
-
#
|
|
6
|
-
# Manages storage of API responses for use in subsequent tests
|
|
7
|
-
#
|
|
8
|
-
# This class provides a mechanism to store HTTP requests and responses
|
|
9
|
-
# during test execution, allowing values to be referenced in later tests
|
|
10
|
-
# through the `store.id.body.attribute` syntax.
|
|
11
|
-
#
|
|
12
|
-
# @example Storing and retrieving a response in specs
|
|
13
|
-
# # In one expectation:
|
|
14
|
-
# store_as: user_creation
|
|
15
|
-
#
|
|
16
|
-
# # In a later test:
|
|
17
|
-
# query:
|
|
18
|
-
# id: store.user_creation.body.id
|
|
19
|
-
#
|
|
20
|
-
class Store
|
|
21
|
-
#
|
|
22
|
-
# Represents a stored entry containing arbitrary data from test execution
|
|
23
|
-
#
|
|
24
|
-
# Entries are created during test execution to store custom data that can be
|
|
25
|
-
# accessed in subsequent tests. Unlike the original rigid Data structure, this
|
|
26
|
-
# OpenStruct-based approach allows storing any key-value pairs, making it perfect
|
|
27
|
-
# for complex test scenarios that need custom configuration, metadata, or
|
|
28
|
-
# computed values.
|
|
29
|
-
#
|
|
30
|
-
# @example Storing custom configuration data
|
|
31
|
-
# SpecForge.context.store.set(
|
|
32
|
-
# "app_config",
|
|
33
|
-
# api_version: "v2.1",
|
|
34
|
-
# feature_flags: { advanced_search: true }
|
|
35
|
-
# )
|
|
36
|
-
#
|
|
37
|
-
# @example Accessing stored data in tests
|
|
38
|
-
# headers:
|
|
39
|
-
# X-API-Version: store.app_config.api_version
|
|
40
|
-
# query:
|
|
41
|
-
# search_enabled: store.app_config.feature_flags.advanced_search
|
|
42
|
-
#
|
|
43
|
-
class Entry < OpenStruct
|
|
44
|
-
#
|
|
45
|
-
# Creates a new store entry
|
|
46
|
-
#
|
|
47
|
-
# @param scope [Symbol] Scope of this entry, either :file or :spec
|
|
48
|
-
#
|
|
49
|
-
# @return [Entry] A new entry instance
|
|
50
|
-
#
|
|
51
|
-
def initialize(scope: :file, **)
|
|
52
|
-
super
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
# Returns all available methods that can be called
|
|
57
|
-
#
|
|
58
|
-
# @return [Array] The method names
|
|
59
|
-
#
|
|
60
|
-
def available_methods
|
|
61
|
-
@table.keys
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
# Creates a new empty store
|
|
67
|
-
#
|
|
68
|
-
# @return [Store] A new store instance
|
|
69
|
-
#
|
|
70
|
-
def initialize
|
|
71
|
-
@inner = {}
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
#
|
|
75
|
-
# Retrieves a stored entry by ID
|
|
76
|
-
#
|
|
77
|
-
# @param id [String, Symbol] The identifier for the stored entry
|
|
78
|
-
#
|
|
79
|
-
# @return [Entry, nil] The stored entry or nil if not found
|
|
80
|
-
#
|
|
81
|
-
def [](id)
|
|
82
|
-
@inner[id]
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
#
|
|
86
|
-
# Returns the number of entries in the store
|
|
87
|
-
#
|
|
88
|
-
# @return [Integer] The count of stored entries
|
|
89
|
-
#
|
|
90
|
-
def size
|
|
91
|
-
@inner.size
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
# Stores an entry with the specified ID
|
|
96
|
-
#
|
|
97
|
-
# @param id [String, Symbol] The identifier to store the entry under
|
|
98
|
-
#
|
|
99
|
-
# @return [self]
|
|
100
|
-
#
|
|
101
|
-
def set(id, **)
|
|
102
|
-
@inner[id] = Entry.new(**)
|
|
103
|
-
|
|
104
|
-
self
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
#
|
|
108
|
-
# Removes all entries from the store
|
|
109
|
-
#
|
|
110
|
-
def clear
|
|
111
|
-
@inner.clear
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
#
|
|
115
|
-
# Removes all spec entries from the store
|
|
116
|
-
#
|
|
117
|
-
def clear_specs
|
|
118
|
-
@inner.delete_if { |_k, v| v.scope == :spec }
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
#
|
|
122
|
-
# Returns a hash representation of store
|
|
123
|
-
#
|
|
124
|
-
# @return [Hash]
|
|
125
|
-
#
|
|
126
|
-
def to_h
|
|
127
|
-
@inner.transform_values(&:to_h).deep_stringify_keys
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SpecForge
|
|
4
|
-
class Context
|
|
5
|
-
#
|
|
6
|
-
# Manages variable resolution across different expectations in SpecForge tests.
|
|
7
|
-
#
|
|
8
|
-
# The Variables class handles two layers of variable definitions:
|
|
9
|
-
# - Base variables: The core set of variables defined at the spec level
|
|
10
|
-
# - Overlay variables: Additional variables defined at the expectation level
|
|
11
|
-
# that can override base variables with the same name.
|
|
12
|
-
#
|
|
13
|
-
# @example Basic usage
|
|
14
|
-
# variables = Variables.new(
|
|
15
|
-
# base: {user_id: 123, name: "Test User"},
|
|
16
|
-
# overlay: {
|
|
17
|
-
# "expectation_1": {name: "Override User"}
|
|
18
|
-
# }
|
|
19
|
-
# )
|
|
20
|
-
#
|
|
21
|
-
# variables[:user_id] #=> 123
|
|
22
|
-
# variables[:name] #=> "Test User"
|
|
23
|
-
#
|
|
24
|
-
# variables.use_overlay("expectation_1")
|
|
25
|
-
# variables[:name] #=> "Override User"
|
|
26
|
-
# variables[:user_id] #=> 123 (unchanged)
|
|
27
|
-
#
|
|
28
|
-
class Variables < Hash
|
|
29
|
-
attr_reader :base, :overlay
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
# Creates a new Variables container with base and overlay definitions
|
|
33
|
-
#
|
|
34
|
-
# @param base [Hash] The base set of variables (typically defined at spec level)
|
|
35
|
-
# @param overlay [Hash<String, Hash>] A hash of overlay variable sets keyed by ID
|
|
36
|
-
#
|
|
37
|
-
# @return [Variables]
|
|
38
|
-
#
|
|
39
|
-
def initialize(base: {}, overlay: {})
|
|
40
|
-
set(base:, overlay:)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
# Sets the base and overlay variable hashes
|
|
45
|
-
#
|
|
46
|
-
# @param base [Hash] The new base variable hash
|
|
47
|
-
# @param overlay [Hash<String, Hash>] The new overlay variable hashes
|
|
48
|
-
#
|
|
49
|
-
# @return [self]
|
|
50
|
-
#
|
|
51
|
-
def set(base:, overlay: {})
|
|
52
|
-
@base = Attribute.from(base)
|
|
53
|
-
@overlay = overlay
|
|
54
|
-
|
|
55
|
-
resolve_into_self(@base)
|
|
56
|
-
self
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
-
# Applies a specific overlay to the base variables
|
|
61
|
-
# If the overlay doesn't exist or is empty, no changes are made.
|
|
62
|
-
#
|
|
63
|
-
# @param id [String] The ID of the overlay to apply
|
|
64
|
-
#
|
|
65
|
-
# @return [nil]
|
|
66
|
-
#
|
|
67
|
-
def use_overlay(id)
|
|
68
|
-
active = @base
|
|
69
|
-
|
|
70
|
-
if (overlay = @overlay[id]) && overlay.present?
|
|
71
|
-
active = active.deep_merge(overlay)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
resolve_into_self(active)
|
|
75
|
-
self
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
private
|
|
79
|
-
|
|
80
|
-
def resolve_into_self(hash)
|
|
81
|
-
# Start fresh
|
|
82
|
-
clear
|
|
83
|
-
|
|
84
|
-
# Load the resolved values into self
|
|
85
|
-
hash.each do |key, value|
|
|
86
|
-
self[key] = Attribute.from(value).resolved
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
data/lib/spec_forge/context.rb
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SpecForge
|
|
4
|
-
#
|
|
5
|
-
# Core data structure that maintains context during test execution
|
|
6
|
-
#
|
|
7
|
-
# Context stores and provides access to global variables, test variables, and
|
|
8
|
-
# shared state across specs.
|
|
9
|
-
# It acts as a central repository for test data during execution.
|
|
10
|
-
#
|
|
11
|
-
# @example Accessing the current context
|
|
12
|
-
# SpecForge.context.variables[:user_id] #=> 123
|
|
13
|
-
#
|
|
14
|
-
class Context < Data.define(:global, :store, :variables)
|
|
15
|
-
#
|
|
16
|
-
# Creates a new context with default values
|
|
17
|
-
#
|
|
18
|
-
# @param global [Hash] Global variables shared across all specs
|
|
19
|
-
# @param variables [Hash] Test variables specific to the current context
|
|
20
|
-
#
|
|
21
|
-
# @return [Context] A new context instance
|
|
22
|
-
#
|
|
23
|
-
def initialize(global: {}, variables: {})
|
|
24
|
-
super(
|
|
25
|
-
global: Global.new(**global),
|
|
26
|
-
store: Store.new,
|
|
27
|
-
variables: Variables.new(**variables)
|
|
28
|
-
)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
require_relative "context/callbacks"
|
|
34
|
-
require_relative "context/global"
|
|
35
|
-
require_relative "context/store"
|
|
36
|
-
require_relative "context/variables"
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
return if defined?(SPEC_FORGE_INTERNAL_TESTING)
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# RSpec's core testing framework module
|
|
7
|
-
# Provides the fundamental structure and functionality for RSpec tests
|
|
8
|
-
#
|
|
9
|
-
module RSpec
|
|
10
|
-
#
|
|
11
|
-
# Core implementation details and extensions for RSpec
|
|
12
|
-
# Contains the fundamental building blocks of the RSpec testing framework
|
|
13
|
-
#
|
|
14
|
-
module Core
|
|
15
|
-
#
|
|
16
|
-
# Handles notifications and reporting for RSpec test runs
|
|
17
|
-
# Manages how test results and metadata are processed and communicated
|
|
18
|
-
#
|
|
19
|
-
module Notifications
|
|
20
|
-
#
|
|
21
|
-
# A monkey patch of an internal RSpec class to allow SpecForge to replace parts of
|
|
22
|
-
# RSpec's reporting output in order to provide useful feedback to the user.
|
|
23
|
-
# This replaces "rspec" in commands with "spec_forge", removes any line numbers, and
|
|
24
|
-
# ensures that failures properly report the YAML file that it occurred in.
|
|
25
|
-
#
|
|
26
|
-
class SummaryNotification
|
|
27
|
-
#
|
|
28
|
-
# Create an alias to RSpec original colorized_rerun_commands so it can be called at a
|
|
29
|
-
# later point.
|
|
30
|
-
#
|
|
31
|
-
alias_method :og_colorized_rerun_commands, :colorized_rerun_commands
|
|
32
|
-
|
|
33
|
-
# Customizes RSpec's failure output to:
|
|
34
|
-
# 1. Use 'spec_forge' instead of 'rspec' for rerun commands
|
|
35
|
-
# 2. Remove line numbers since SpecForge uses dynamic spec generation
|
|
36
|
-
def colorized_rerun_commands(colorizer)
|
|
37
|
-
# Updating these at this point fixes the re-run for some failures - it depends
|
|
38
|
-
failed_examples.each do |example|
|
|
39
|
-
metadata = example.metadata[:example_group]
|
|
40
|
-
|
|
41
|
-
# I might've uncovered an inconsistency here
|
|
42
|
-
# When multiple specs fail, it appears that the rerun_commands will use
|
|
43
|
-
# :rerun_file_path from the example's metadata.
|
|
44
|
-
# But when a single spec is ran and fails, it's using :location.
|
|
45
|
-
example.metadata[:location] = metadata[:rerun_file_path]
|
|
46
|
-
example.metadata[:line_number] = metadata[:line_number]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
og_colorized_rerun_commands.gsub(/rspec/i, "spec_forge")
|
|
50
|
-
.gsub(/\[[\d:]+\]/, "")
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
data/lib/spec_forge/core_ext.rb
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SpecForge
|
|
4
|
-
module Documentation
|
|
5
|
-
module Generators
|
|
6
|
-
#
|
|
7
|
-
# Base class for all documentation generators
|
|
8
|
-
#
|
|
9
|
-
# Provides the common interface and shared functionality for generators
|
|
10
|
-
# that transform SpecForge documents into various output formats.
|
|
11
|
-
# Subclasses implement format-specific generation logic.
|
|
12
|
-
#
|
|
13
|
-
# @example Creating a custom generator
|
|
14
|
-
# class MyGenerator < Base
|
|
15
|
-
# def generate
|
|
16
|
-
# # Transform input document to custom format
|
|
17
|
-
# end
|
|
18
|
-
# end
|
|
19
|
-
#
|
|
20
|
-
class Base
|
|
21
|
-
#
|
|
22
|
-
# Generates documentation from test data with optional caching
|
|
23
|
-
#
|
|
24
|
-
# @param use_cache [Boolean] Whether to use cached test data if available
|
|
25
|
-
#
|
|
26
|
-
# @return [Object] The generated documentation in the target format
|
|
27
|
-
#
|
|
28
|
-
# @raise [RuntimeError] Must be implemented by subclasses
|
|
29
|
-
#
|
|
30
|
-
def self.generate(use_cache: false)
|
|
31
|
-
raise "not implemented"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
-
# Validates the generated output according to format specifications
|
|
36
|
-
#
|
|
37
|
-
# @param input [Object] The generated documentation to validate
|
|
38
|
-
#
|
|
39
|
-
# @return [void]
|
|
40
|
-
#
|
|
41
|
-
# @raise [RuntimeError] Must be implemented by subclasses
|
|
42
|
-
#
|
|
43
|
-
def self.validate!(input)
|
|
44
|
-
raise "not implemented"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
# The input document containing structured API data
|
|
49
|
-
#
|
|
50
|
-
# Contains all the endpoint information extracted from tests,
|
|
51
|
-
# organized and ready for transformation into the target format.
|
|
52
|
-
#
|
|
53
|
-
# @return [Document] The document to be processed by the generator
|
|
54
|
-
#
|
|
55
|
-
attr_reader :input
|
|
56
|
-
|
|
57
|
-
#
|
|
58
|
-
# Initializes a new generators
|
|
59
|
-
#
|
|
60
|
-
# @param input [Hash, Document] The document to generate
|
|
61
|
-
#
|
|
62
|
-
# @return [Base] A new generator instance
|
|
63
|
-
#
|
|
64
|
-
def initialize(input = {})
|
|
65
|
-
@input = input
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
#
|
|
69
|
-
# Generates the document into a specific format
|
|
70
|
-
#
|
|
71
|
-
# @raise [RuntimeError] Must be implemented by subclasses
|
|
72
|
-
#
|
|
73
|
-
# @return [Object] The generated document
|
|
74
|
-
#
|
|
75
|
-
def generate
|
|
76
|
-
raise "not implemented"
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SpecForge
|
|
4
|
-
module Documentation
|
|
5
|
-
module Generators
|
|
6
|
-
module OpenAPI
|
|
7
|
-
#
|
|
8
|
-
# Base class for OpenAPI generators
|
|
9
|
-
#
|
|
10
|
-
# Provides common functionality for OpenAPI generators of different versions.
|
|
11
|
-
#
|
|
12
|
-
class Base < Generators::Base
|
|
13
|
-
#
|
|
14
|
-
# Converts the generator's version to a semantic version object
|
|
15
|
-
#
|
|
16
|
-
# @return [SemVersion] The semantic version
|
|
17
|
-
#
|
|
18
|
-
def self.to_sem_version
|
|
19
|
-
SemVersion.new(CURRENT_VERSION)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
# Generates OpenAPI documentation from test data with optional caching
|
|
24
|
-
#
|
|
25
|
-
# Loads endpoint data from tests (either fresh or cached), creates a document,
|
|
26
|
-
# and generates the OpenAPI specification using the appropriate version generator.
|
|
27
|
-
#
|
|
28
|
-
# @param use_cache [Boolean] Whether to use cached test data if available
|
|
29
|
-
#
|
|
30
|
-
# @return [Hash] The generated OpenAPI specification
|
|
31
|
-
#
|
|
32
|
-
def self.generate(use_cache: false)
|
|
33
|
-
document = Documentation::Loader.load_document(use_cache:)
|
|
34
|
-
new(document).generate
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
# Validates an OpenAPI specification against the standard
|
|
39
|
-
#
|
|
40
|
-
# Uses the openapi3_parser gem to validate the generated specification
|
|
41
|
-
# and provides detailed error reporting if validation fails.
|
|
42
|
-
#
|
|
43
|
-
# @param output [Hash] The OpenAPI specification to validate
|
|
44
|
-
#
|
|
45
|
-
# @return [void]
|
|
46
|
-
#
|
|
47
|
-
# @raise [Error::InvalidOASDocument] If the specification is invalid
|
|
48
|
-
#
|
|
49
|
-
def self.validate!(output)
|
|
50
|
-
document = Openapi3Parser.load(output)
|
|
51
|
-
if document.valid?
|
|
52
|
-
puts "✅ No validation errors found!"
|
|
53
|
-
return
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
puts ErrorFormatter.format(document.errors.errors)
|
|
57
|
-
raise Error::InvalidOASDocument
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
protected
|
|
61
|
-
|
|
62
|
-
#
|
|
63
|
-
# Loads OpenAPI configuration from YAML
|
|
64
|
-
#
|
|
65
|
-
# @return [Hash] The normalized OpenAPI configuration
|
|
66
|
-
#
|
|
67
|
-
# @api private
|
|
68
|
-
#
|
|
69
|
-
def config
|
|
70
|
-
@config ||= begin
|
|
71
|
-
file_extension_glob = "*.{yml,yaml}"
|
|
72
|
-
base_path = SpecForge.openapi_path.join("config")
|
|
73
|
-
|
|
74
|
-
root_paths = base_path.join(file_extension_glob)
|
|
75
|
-
path_paths = base_path.join("paths", "**", file_extension_glob)
|
|
76
|
-
component_paths = base_path.join("components", "**", file_extension_glob)
|
|
77
|
-
|
|
78
|
-
config = load_yml_from_paths(root_paths).to_merged_h
|
|
79
|
-
paths_config = load_yml_from_paths(path_paths).to_merged_h
|
|
80
|
-
component_config = load_yml_from_paths(component_paths).to_merged_h
|
|
81
|
-
|
|
82
|
-
(config["paths"] ||= {}).deep_merge!(paths_config)
|
|
83
|
-
(config["components"] ||= {}).deep_merge!(component_config)
|
|
84
|
-
|
|
85
|
-
config
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
private
|
|
90
|
-
|
|
91
|
-
def load_yml_from_paths(paths)
|
|
92
|
-
Dir[paths].map do |path|
|
|
93
|
-
YAML.safe_load_file(path)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|