reqcord 0.1.0 → 0.1.2
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 +90 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +252 -95
- data/Rakefile +13 -0
- data/docs/configuration.md +319 -0
- data/examples/reqcord.yml +58 -0
- data/gemfiles/rails_7.1.gemfile +13 -0
- data/gemfiles/rails_7.2.gemfile +13 -0
- data/gemfiles/rails_8.0.gemfile +13 -0
- data/gemfiles/rails_8.1.gemfile +13 -0
- data/lib/reqcord/capture/collector.rb +31 -0
- data/lib/reqcord/capture/integration_patch.rb +209 -0
- data/lib/reqcord/capture/minitest_context.rb +34 -0
- data/lib/reqcord/capture/rspec_context.rb +42 -0
- data/lib/reqcord/capture/test_context.rb +25 -0
- data/lib/reqcord/capture.rb +19 -0
- data/lib/reqcord/configuration.rb +198 -0
- data/lib/reqcord/dataset.rb +176 -0
- data/lib/reqcord/endpoint.rb +263 -0
- data/lib/reqcord/errors.rb +9 -0
- data/lib/reqcord/exporters/curl.rb +68 -0
- data/lib/reqcord/exporters/markdown.rb +295 -0
- data/lib/reqcord/exporters/postman.rb +206 -0
- data/lib/reqcord/exporters.rb +32 -0
- data/lib/reqcord/generator.rb +364 -0
- data/lib/reqcord/railtie.rb +51 -0
- data/lib/reqcord/renderers/curl.rb +56 -0
- data/lib/reqcord/renderers/payload.rb +69 -0
- data/lib/reqcord/request_example.rb +104 -0
- data/lib/reqcord/response_example.rb +72 -0
- data/lib/reqcord/route_collector.rb +242 -0
- data/lib/reqcord/sanitizers/sanitizer.rb +140 -0
- data/lib/reqcord/schema.rb +187 -0
- data/lib/reqcord/support.rb +58 -0
- data/lib/reqcord/version.rb +5 -0
- data/lib/reqcord.rb +78 -0
- data/lib/tasks/reqcord.rake +99 -0
- data/reqcord.gemspec +60 -0
- metadata +165 -3
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reqcord
|
|
4
|
+
# Describes what an endpoint accepts, inferred from the requests that
|
|
5
|
+
# actually worked. One test sends status "active", another "passive", a third
|
|
6
|
+
# sends "inactive" and gets a 422: the documentation should say the field
|
|
7
|
+
# takes "active" | "passive", not list three requests.
|
|
8
|
+
class Schema
|
|
9
|
+
# Beyond this many distinct values a field is an open set, not a choice.
|
|
10
|
+
ENUM_LIMIT = 6
|
|
11
|
+
|
|
12
|
+
Field = Struct.new(:path, :types, :values, :present_count, :total_count, :repetition, keyword_init: true) do
|
|
13
|
+
def type
|
|
14
|
+
types.to_a.sort.join(" | ")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# A field missing from a working request cannot be required.
|
|
18
|
+
def required?
|
|
19
|
+
present_count == total_count
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Telling a closed set from free text: either a value came back in more
|
|
23
|
+
# than one request, or every value reads like a token ("active"), not
|
|
24
|
+
# like content ("Ada Lovelace", "ada@example.com").
|
|
25
|
+
def enum?
|
|
26
|
+
return false unless values.size.between?(2, ENUM_LIMIT)
|
|
27
|
+
return false unless values.all? { |value| scalar?(value) }
|
|
28
|
+
return false if identifier?
|
|
29
|
+
|
|
30
|
+
# Repetition only counts among the requests that carried the field:
|
|
31
|
+
# two requests, two different values, is not a set.
|
|
32
|
+
return true if repetition && values.size < present_count
|
|
33
|
+
|
|
34
|
+
values.all? { |value| token?(value) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def example
|
|
38
|
+
values.first
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Test order is not stable, so a listed set must be ordered by something
|
|
42
|
+
# that is.
|
|
43
|
+
def listed_values
|
|
44
|
+
values.sort_by { |value| [value.to_s, value.class.name] }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_h
|
|
48
|
+
{
|
|
49
|
+
path: path,
|
|
50
|
+
type: type,
|
|
51
|
+
required: required?,
|
|
52
|
+
values: listed_values
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def scalar?(value)
|
|
59
|
+
value.is_a?(String) || value.is_a?(Numeric) || value == true || value == false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Every record has a different one, so listing the ones a test happened
|
|
63
|
+
# to use would read as if those were the only allowed values.
|
|
64
|
+
def identifier?
|
|
65
|
+
path.to_s.split(/[.\[\]]/).last.to_s.match?(/\A(id|uuid|.*_id|.*_uuid|.*_key|token|slug)\z/i)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# A word, not content: "active", "pending_review", "USD". Digits and
|
|
69
|
+
# dashes usually mean a generated value ("e-00056197", "safari-859f04").
|
|
70
|
+
def token?(value)
|
|
71
|
+
case value
|
|
72
|
+
when true, false then true
|
|
73
|
+
when String then value.match?(/\A[a-z][a-z_]*\z/) || value.match?(/\A[A-Z_]{2,}\z/)
|
|
74
|
+
else false
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# `payloads` are request bodies, query hashes or path parameter hashes from
|
|
80
|
+
# successful captures. With `repetition: true` a value seen in more than
|
|
81
|
+
# one payload marks a closed set — right for what tests *send*, wrong for
|
|
82
|
+
# what an application *returns*, where fixtures repeat by nature; response
|
|
83
|
+
# bodies are inferred with `repetition: false`, so only token-like values
|
|
84
|
+
# ("open", "done") are listed as a set.
|
|
85
|
+
def self.infer(payloads, repetition: true)
|
|
86
|
+
new(payloads, repetition: repetition).call
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Empty payloads are kept: a request that was accepted without any
|
|
90
|
+
# parameters is the proof that every parameter is optional.
|
|
91
|
+
def initialize(payloads, repetition: true)
|
|
92
|
+
@payloads = Array(payloads)
|
|
93
|
+
@repetition = repetition
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def call
|
|
97
|
+
return self.class.empty if @payloads.empty?
|
|
98
|
+
|
|
99
|
+
fields = {}
|
|
100
|
+
|
|
101
|
+
@payloads.each do |payload|
|
|
102
|
+
flatten(payload).each do |path, value|
|
|
103
|
+
field = fields[path] ||= Field.new(
|
|
104
|
+
path: path,
|
|
105
|
+
types: [],
|
|
106
|
+
values: [],
|
|
107
|
+
present_count: 0,
|
|
108
|
+
total_count: @payloads.size,
|
|
109
|
+
repetition: @repetition
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
field.types |= [type_of(value)]
|
|
113
|
+
field.values |= [value] unless value.nil? || value.is_a?(Hash) || value.is_a?(Array)
|
|
114
|
+
field.present_count += 1
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
Result.new(fields.values)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.empty
|
|
122
|
+
Result.new([])
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# The inferred description of one payload shape.
|
|
126
|
+
class Result
|
|
127
|
+
include Enumerable
|
|
128
|
+
|
|
129
|
+
attr_reader :fields
|
|
130
|
+
|
|
131
|
+
def initialize(fields)
|
|
132
|
+
@fields = fields
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def each(&block)
|
|
136
|
+
fields.each(&block)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def empty?
|
|
140
|
+
fields.empty?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def size
|
|
144
|
+
fields.size
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def [](path)
|
|
148
|
+
fields.find { |field| field.path == path }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def to_a
|
|
152
|
+
fields.map(&:to_h)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
# { "customer" => { "tags" => ["a"] } } -> { "customer.tags[]" => "a" }
|
|
159
|
+
def flatten(value, prefix = nil, result = {})
|
|
160
|
+
case value
|
|
161
|
+
when Hash
|
|
162
|
+
value.each { |key, nested| flatten(nested, prefix ? "#{prefix}.#{key}" : key.to_s, result) }
|
|
163
|
+
when Array
|
|
164
|
+
# An empty array still tells the reader the field is a list.
|
|
165
|
+
result[prefix] = [] if value.empty? && prefix
|
|
166
|
+
value.each { |item| flatten(item, "#{prefix}[]", result) }
|
|
167
|
+
else
|
|
168
|
+
result[prefix] = value if prefix
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
result
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def type_of(value)
|
|
175
|
+
case value
|
|
176
|
+
when String then "string"
|
|
177
|
+
when Integer then "integer"
|
|
178
|
+
when Float then "number"
|
|
179
|
+
when true, false then "boolean"
|
|
180
|
+
when nil then "null"
|
|
181
|
+
when Array then "array"
|
|
182
|
+
when Hash then "object"
|
|
183
|
+
else value.class.name.downcase
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reqcord
|
|
4
|
+
# Minimal inflection helpers. ActiveSupport is used when it is available so
|
|
5
|
+
# that generated titles match the host application's inflections.
|
|
6
|
+
module Support
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def underscore(value)
|
|
10
|
+
string = value.to_s.dup
|
|
11
|
+
return ActiveSupport::Inflector.underscore(string) if defined?(ActiveSupport::Inflector)
|
|
12
|
+
|
|
13
|
+
string.gsub("::", "/")
|
|
14
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
15
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
16
|
+
.tr("-", "_")
|
|
17
|
+
.downcase
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def humanize(value)
|
|
21
|
+
underscore(value).tr("_", " ").strip
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def titleize(value)
|
|
25
|
+
humanize(value).split(/\s+/).map { |word| word.empty? ? word : word[0].upcase + word[1..] }.join(" ")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def singularize(value)
|
|
29
|
+
string = value.to_s
|
|
30
|
+
return ActiveSupport::Inflector.singularize(string) if defined?(ActiveSupport::Inflector)
|
|
31
|
+
|
|
32
|
+
case string
|
|
33
|
+
when /ies\z/i then string.sub(/ies\z/i, "y")
|
|
34
|
+
when /(ss|sh|ch|x|z)es\z/i then string.sub(/es\z/i, "")
|
|
35
|
+
when /ss\z/i then string
|
|
36
|
+
when /s\z/i then string.sub(/s\z/i, "")
|
|
37
|
+
else string
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def pluralize(value)
|
|
42
|
+
string = value.to_s
|
|
43
|
+
return ActiveSupport::Inflector.pluralize(string) if defined?(ActiveSupport::Inflector)
|
|
44
|
+
|
|
45
|
+
case string
|
|
46
|
+
when /(ss|sh|ch|x|z)\z/i then "#{string}es"
|
|
47
|
+
when /[^aeiou]y\z/i then string.sub(/y\z/i, "ies")
|
|
48
|
+
# Already plural, as resource names usually are.
|
|
49
|
+
when /s\z/i then string
|
|
50
|
+
else "#{string}s"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parameterize(value)
|
|
55
|
+
underscore(value).gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/reqcord.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "time"
|
|
6
|
+
require "uri"
|
|
7
|
+
require "rack/utils"
|
|
8
|
+
require "fileutils"
|
|
9
|
+
require "pathname"
|
|
10
|
+
|
|
11
|
+
require_relative "reqcord/version"
|
|
12
|
+
require_relative "reqcord/errors"
|
|
13
|
+
require_relative "reqcord/support"
|
|
14
|
+
require_relative "reqcord/configuration"
|
|
15
|
+
|
|
16
|
+
require_relative "reqcord/request_example"
|
|
17
|
+
require_relative "reqcord/response_example"
|
|
18
|
+
require_relative "reqcord/schema"
|
|
19
|
+
require_relative "reqcord/endpoint"
|
|
20
|
+
require_relative "reqcord/dataset"
|
|
21
|
+
|
|
22
|
+
require_relative "reqcord/route_collector"
|
|
23
|
+
|
|
24
|
+
require_relative "reqcord/capture"
|
|
25
|
+
require_relative "reqcord/capture/test_context"
|
|
26
|
+
require_relative "reqcord/capture/collector"
|
|
27
|
+
|
|
28
|
+
require_relative "reqcord/sanitizers/sanitizer"
|
|
29
|
+
require_relative "reqcord/renderers/payload"
|
|
30
|
+
require_relative "reqcord/renderers/curl"
|
|
31
|
+
require_relative "reqcord/exporters"
|
|
32
|
+
require_relative "reqcord/exporters/curl"
|
|
33
|
+
require_relative "reqcord/exporters/markdown"
|
|
34
|
+
require_relative "reqcord/exporters/postman"
|
|
35
|
+
require_relative "reqcord/generator"
|
|
36
|
+
|
|
37
|
+
require_relative "reqcord/railtie" if defined?(Rails::Railtie)
|
|
38
|
+
|
|
39
|
+
module Reqcord
|
|
40
|
+
class << self
|
|
41
|
+
attr_writer :root
|
|
42
|
+
|
|
43
|
+
def root
|
|
44
|
+
return @root if @root
|
|
45
|
+
|
|
46
|
+
if defined?(Rails) &&
|
|
47
|
+
Rails.respond_to?(:root) &&
|
|
48
|
+
Rails.root
|
|
49
|
+
|
|
50
|
+
Rails.root
|
|
51
|
+
else
|
|
52
|
+
Pathname(Dir.pwd)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def configuration
|
|
57
|
+
@configuration ||=
|
|
58
|
+
Configuration.load(
|
|
59
|
+
root: root
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def reload_configuration!
|
|
64
|
+
@configuration =
|
|
65
|
+
Configuration.load(
|
|
66
|
+
root: root
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def log(message)
|
|
71
|
+
$stdout.puts("[reqcord] #{message}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def warn(message)
|
|
75
|
+
$stderr.puts("[reqcord] #{message}")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :reqcord do
|
|
4
|
+
desc "Create reqcord.yml"
|
|
5
|
+
task init: :environment do
|
|
6
|
+
path = Rails.root.join("reqcord.yml")
|
|
7
|
+
|
|
8
|
+
if path.exist?
|
|
9
|
+
puts "reqcord.yml already exists."
|
|
10
|
+
next
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
content = <<~YAML
|
|
14
|
+
version: 1
|
|
15
|
+
|
|
16
|
+
test:
|
|
17
|
+
framework: minitest
|
|
18
|
+
command: bin/rails test
|
|
19
|
+
|
|
20
|
+
routes:
|
|
21
|
+
prefix: /api
|
|
22
|
+
|
|
23
|
+
output:
|
|
24
|
+
directory: docs/api
|
|
25
|
+
include_uncovered: false
|
|
26
|
+
|
|
27
|
+
exporters:
|
|
28
|
+
- curl
|
|
29
|
+
- markdown
|
|
30
|
+
- postman
|
|
31
|
+
|
|
32
|
+
variables:
|
|
33
|
+
base_url: http://localhost:3000
|
|
34
|
+
|
|
35
|
+
sanitize:
|
|
36
|
+
headers:
|
|
37
|
+
Authorization: "Bearer {{token}}"
|
|
38
|
+
X-Api-Key: "{{api_key}}"
|
|
39
|
+
YAML
|
|
40
|
+
|
|
41
|
+
File.write(path, content)
|
|
42
|
+
|
|
43
|
+
FileUtils.mkdir_p(
|
|
44
|
+
Rails.root.join("docs", "api")
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
puts "Created reqcord.yml"
|
|
48
|
+
puts "Created docs/api/"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
desc "Generate API documentation from Rails integration tests"
|
|
52
|
+
task generate: :environment do
|
|
53
|
+
resources =
|
|
54
|
+
ENV.fetch("RESOURCE", "")
|
|
55
|
+
.split(",")
|
|
56
|
+
.map(&:strip)
|
|
57
|
+
.reject(&:empty?)
|
|
58
|
+
|
|
59
|
+
version = ENV["VERSION"]&.strip
|
|
60
|
+
|
|
61
|
+
version = nil if version&.empty?
|
|
62
|
+
|
|
63
|
+
Reqcord.reload_configuration!
|
|
64
|
+
|
|
65
|
+
dataset =
|
|
66
|
+
Reqcord::Generator.call(
|
|
67
|
+
resources: resources,
|
|
68
|
+
version: version
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
documented = dataset.curl_ready_endpoints.size
|
|
72
|
+
|
|
73
|
+
puts
|
|
74
|
+
puts "Reqcord generated API documentation."
|
|
75
|
+
puts "Endpoints: #{dataset.endpoints.size} (#{documented} with successful 2xx request)"
|
|
76
|
+
puts "Output: #{Reqcord.configuration.output_directory}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
desc "List the routes Reqcord would document"
|
|
80
|
+
task routes: :environment do
|
|
81
|
+
routes =
|
|
82
|
+
Reqcord::RouteCollector.call(
|
|
83
|
+
resources: ENV.fetch("RESOURCE", "").split(",").map(&:strip).reject(&:empty?),
|
|
84
|
+
version: ENV["VERSION"],
|
|
85
|
+
prefix: Reqcord.configuration.route_prefix
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if routes.empty?
|
|
89
|
+
puts "No routes matched the configured prefix."
|
|
90
|
+
next
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
width = routes.map { |route| route.method.length }.max
|
|
94
|
+
|
|
95
|
+
routes.each do |route|
|
|
96
|
+
puts format("%-#{width}s %s (%s#%s)", route.method, route.path, route.controller, route.action)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
data/reqcord.gemspec
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/reqcord/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "reqcord"
|
|
7
|
+
spec.version = Reqcord::VERSION
|
|
8
|
+
|
|
9
|
+
spec.authors = ["Ahmet Saridogan"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Generate living API documentation from Rails integration tests"
|
|
12
|
+
spec.description = <<~DESC
|
|
13
|
+
Reqcord captures HTTP requests and responses from Rails integration tests
|
|
14
|
+
and generates static API documentation with executable cURL examples.
|
|
15
|
+
DESC
|
|
16
|
+
|
|
17
|
+
spec.homepage = "https://github.com/ahmetsaridogan/reqcord"
|
|
18
|
+
spec.license = "MIT"
|
|
19
|
+
|
|
20
|
+
spec.required_ruby_version = ">= 3.2"
|
|
21
|
+
|
|
22
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
23
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
|
24
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
25
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
26
|
+
|
|
27
|
+
# What ships: lib/, the rake task, the docs and the annotated config. Tests,
|
|
28
|
+
# CI files and the example apps (with their generated output) stay on
|
|
29
|
+
# GitHub — `git ls-files` runs against the checkout, so `gem build` must be
|
|
30
|
+
# run from the repository root.
|
|
31
|
+
spec.files = Dir.chdir(__dir__) do
|
|
32
|
+
`git ls-files -z`.split("\x0").reject do |file|
|
|
33
|
+
file.start_with?(
|
|
34
|
+
"test/",
|
|
35
|
+
"spec/",
|
|
36
|
+
"features/",
|
|
37
|
+
"examples/",
|
|
38
|
+
".git",
|
|
39
|
+
".github/"
|
|
40
|
+
) || file.end_with?(".gem")
|
|
41
|
+
end + ["examples/reqcord.yml"]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
spec.require_paths = ["lib"]
|
|
45
|
+
|
|
46
|
+
# Rails 7.1 through 8.x; every version is exercised in CI (see gemfiles/).
|
|
47
|
+
spec.add_dependency "railties", ">= 7.1", "< 9.0"
|
|
48
|
+
spec.add_dependency "actionpack", ">= 7.1", "< 9.0"
|
|
49
|
+
|
|
50
|
+
spec.add_development_dependency "minitest"
|
|
51
|
+
spec.add_development_dependency "rake"
|
|
52
|
+
|
|
53
|
+
# The test suite serves the dummy app over HTTP to replay the generated
|
|
54
|
+
# cURL and Postman collection, validates the collection against the
|
|
55
|
+
# Postman schema, and runs the RSpec example apps.
|
|
56
|
+
spec.add_development_dependency "puma"
|
|
57
|
+
spec.add_development_dependency "json-schema"
|
|
58
|
+
spec.add_development_dependency "rspec-core"
|
|
59
|
+
spec.add_development_dependency "rspec-expectations"
|
|
60
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,138 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: reqcord
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ahmet Saridogan
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: railties
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: actionpack
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '7.1'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '9.0'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '7.1'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '9.0'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: minitest
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
type: :development
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '0'
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: rake
|
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
type: :development
|
|
74
|
+
prerelease: false
|
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
- !ruby/object:Gem::Dependency
|
|
81
|
+
name: puma
|
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
type: :development
|
|
88
|
+
prerelease: false
|
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: json-schema
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0'
|
|
101
|
+
type: :development
|
|
102
|
+
prerelease: false
|
|
103
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0'
|
|
108
|
+
- !ruby/object:Gem::Dependency
|
|
109
|
+
name: rspec-core
|
|
110
|
+
requirement: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '0'
|
|
115
|
+
type: :development
|
|
116
|
+
prerelease: false
|
|
117
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - ">="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '0'
|
|
122
|
+
- !ruby/object:Gem::Dependency
|
|
123
|
+
name: rspec-expectations
|
|
124
|
+
requirement: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '0'
|
|
129
|
+
type: :development
|
|
130
|
+
prerelease: false
|
|
131
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - ">="
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '0'
|
|
12
136
|
description: |
|
|
13
137
|
Reqcord captures HTTP requests and responses from Rails integration tests
|
|
14
138
|
and generates static API documentation with executable cURL examples.
|
|
@@ -16,14 +140,52 @@ executables: []
|
|
|
16
140
|
extensions: []
|
|
17
141
|
extra_rdoc_files: []
|
|
18
142
|
files:
|
|
143
|
+
- CHANGELOG.md
|
|
144
|
+
- Gemfile
|
|
145
|
+
- LICENSE.txt
|
|
19
146
|
- README.md
|
|
147
|
+
- Rakefile
|
|
148
|
+
- docs/configuration.md
|
|
149
|
+
- examples/reqcord.yml
|
|
150
|
+
- gemfiles/rails_7.1.gemfile
|
|
151
|
+
- gemfiles/rails_7.2.gemfile
|
|
152
|
+
- gemfiles/rails_8.0.gemfile
|
|
153
|
+
- gemfiles/rails_8.1.gemfile
|
|
154
|
+
- lib/reqcord.rb
|
|
155
|
+
- lib/reqcord/capture.rb
|
|
156
|
+
- lib/reqcord/capture/collector.rb
|
|
157
|
+
- lib/reqcord/capture/integration_patch.rb
|
|
158
|
+
- lib/reqcord/capture/minitest_context.rb
|
|
159
|
+
- lib/reqcord/capture/rspec_context.rb
|
|
160
|
+
- lib/reqcord/capture/test_context.rb
|
|
161
|
+
- lib/reqcord/configuration.rb
|
|
162
|
+
- lib/reqcord/dataset.rb
|
|
163
|
+
- lib/reqcord/endpoint.rb
|
|
164
|
+
- lib/reqcord/errors.rb
|
|
165
|
+
- lib/reqcord/exporters.rb
|
|
166
|
+
- lib/reqcord/exporters/curl.rb
|
|
167
|
+
- lib/reqcord/exporters/markdown.rb
|
|
168
|
+
- lib/reqcord/exporters/postman.rb
|
|
169
|
+
- lib/reqcord/generator.rb
|
|
170
|
+
- lib/reqcord/railtie.rb
|
|
171
|
+
- lib/reqcord/renderers/curl.rb
|
|
172
|
+
- lib/reqcord/renderers/payload.rb
|
|
173
|
+
- lib/reqcord/request_example.rb
|
|
174
|
+
- lib/reqcord/response_example.rb
|
|
175
|
+
- lib/reqcord/route_collector.rb
|
|
176
|
+
- lib/reqcord/sanitizers/sanitizer.rb
|
|
177
|
+
- lib/reqcord/schema.rb
|
|
178
|
+
- lib/reqcord/support.rb
|
|
179
|
+
- lib/reqcord/version.rb
|
|
180
|
+
- lib/tasks/reqcord.rake
|
|
181
|
+
- reqcord.gemspec
|
|
20
182
|
homepage: https://github.com/ahmetsaridogan/reqcord
|
|
21
183
|
licenses:
|
|
22
184
|
- MIT
|
|
23
185
|
metadata:
|
|
24
186
|
homepage_uri: https://github.com/ahmetsaridogan/reqcord
|
|
187
|
+
changelog_uri: https://github.com/ahmetsaridogan/reqcord
|
|
25
188
|
source_code_uri: https://github.com/ahmetsaridogan/reqcord
|
|
26
|
-
changelog_uri: https://github.com/ahmetsaridogan/reqcord/blob/main/CHANGELOG.md
|
|
27
189
|
rubygems_mfa_required: 'true'
|
|
28
190
|
rdoc_options: []
|
|
29
191
|
require_paths:
|