skooma 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eef2c83a80d1526de1a465acbb29b70fc053b6b5e54ed1e6b6ed131f56a8fe0a
4
- data.tar.gz: 0c4cdec06703bdeb6fd3cf6922401552afaa13aa20b9189e472e703288ba60aa
3
+ metadata.gz: 6263d8960b9a3b49287103608c6770c967b6fadfcf58d826df2a492c4dedbd35
4
+ data.tar.gz: 5dd2c141d4baa7f0b95c1b3f3fbfd1afdafaae0097a1139956ba1c7e45f876a1
5
5
  SHA512:
6
- metadata.gz: b5089b0a710836b072b723014cfaaf88b4504853f09a42ddf6a6f9e36ed50271b082dfcc184e047f2bf4481e6a343b4bebbd3bced7dfcec2c5a4f85697fbf113
7
- data.tar.gz: aacb7329ef950a1fc9ff87513a85803e5530cacaec97f0dad1330bf20fb45e9bc339fe9004af11e1b90cfa6cbb725a2cd339a69c6a23721b667e8d10959b37fb
6
+ metadata.gz: e5ddc4010685045a5b2a9908597096af07fb5ac21cce3e1c456568111fed4fc2fb708603be0a47dbd6e61bec0d64e0d85e27841ab36583f0fb61b73759d3f05b
7
+ data.tar.gz: 1210a2ed7f56206369242baa147795acd3a6bc77aa6b719cb3ccc26ab33367a775921d0d94fd8741b651274afdc9183c20af194e4a289190d5968de239a9ae56
data/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.3] - 2024-01-18
11
+
12
+ ### Added
13
+
14
+ - Add support for multiple OpenAPI documents. ([@skryukov])
15
+
16
+ ### Fixed
17
+
18
+ - Fix `Skooma::Error: Missing name key /request` by setting `content` and `required` keyword dependencies. ([@skryukov])
19
+
20
+ ## [0.2.2] - 2024-01-04
21
+
22
+ ### Added
23
+
24
+ - Add support for APIs mounted under a path prefix. ([@skryukov])
25
+
26
+ ```ruby
27
+ # spec/rails_helper.rb
28
+
29
+ RSpec.configure do |config|
30
+ # ...
31
+ path_to_openapi = Rails.root.join("docs", "openapi.yml")
32
+ # pass path_prefix option if your API is mounted under a prefix:
33
+ config.include Skooma::RSpec[path_to_openapi, path_prefix: "/internal/api"], type: :request
34
+ end
35
+ ```
36
+
37
+ ### Changed
38
+
39
+ - Bump `json_skooma` version to `~> 0.2.0`. ([@skryukov])
40
+
41
+ ### Fixed
42
+
43
+ - Better checks to automatic request/response detection to prevent methods overrides via RSpec helpers (i.e. `subject(:response)`). ([@skryukov])
44
+ - Fail response validation when expected response code or `responses` keyword aren't listed. ([@skryukov])
45
+
10
46
  ## [0.2.1] - 2023-10-23
11
47
 
12
48
  ### Fixed
@@ -33,7 +69,9 @@ and this project adheres to [Semantic Versioning].
33
69
 
34
70
  [@skryukov]: https://github.com/skryukov
35
71
 
36
- [Unreleased]: https://github.com/skryukov/skooma/compare/v0.2.1...HEAD
72
+ [Unreleased]: https://github.com/skryukov/skooma/compare/v0.2.3...HEAD
73
+ [0.2.3]: https://github.com/skryukov/skooma/compare/v0.2.2...v0.2.3
74
+ [0.2.2]: https://github.com/skryukov/skooma/compare/v0.2.1...v0.2.2
37
75
  [0.2.1]: https://github.com/skryukov/skooma/compare/v0.2.0...v0.2.1
38
76
  [0.2.0]: https://github.com/skryukov/skooma/compare/v0.1.0...v0.2.0
39
77
  [0.1.0]: https://github.com/skryukov/skooma/commits/v0.1.0
data/README.md CHANGED
@@ -14,6 +14,10 @@ Skooma is a Ruby library for validating API implementations against OpenAPI docu
14
14
  - Supports request/response validations against OpenAPI document
15
15
  - Includes RSpec and Minitest helpers
16
16
 
17
+ ### Learn more
18
+
19
+ - [Let there be docs! A documentation-first approach to Rails API development](https://evilmartians.com/events/let-there-be-docs-a-documentation-first-approach-to-rails-api-development) – Talk and slides from Friendly.rb 2023
20
+
17
21
  <a href="https://evilmartians.com/?utm_source=skooma&utm_campaign=project_page">
18
22
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54">
19
23
  </a>
@@ -44,6 +48,9 @@ RSpec.configure do |config|
44
48
  # ...
45
49
  path_to_openapi = Rails.root.join("docs", "openapi.yml")
46
50
  config.include Skooma::RSpec[path_to_openapi], type: :request
51
+
52
+ # OR pass path_prefix option if your API is mounted under a prefix:
53
+ config.include Skooma::RSpec[path_to_openapi, path_prefix: "/internal/api"], type: :request
47
54
  end
48
55
  ```
49
56
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pp"
4
+
3
5
  module Skooma
4
6
  module Matchers
5
7
  class BeValidDocument
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pp"
4
+
3
5
  module Skooma
4
6
  module Matchers
5
7
  class ConformRequestSchema
@@ -19,7 +19,7 @@ module Skooma
19
19
  end
20
20
 
21
21
  def failure_message
22
- return "Expected #{@expected} status code" unless status_matches?
22
+ return "Expected #{@expected} status code, but got #{@mapped_response["response"]["status"]}" unless status_matches?
23
23
 
24
24
  super
25
25
  end
@@ -23,33 +23,36 @@ module Skooma
23
23
 
24
24
  def request_object
25
25
  # `rails` integration
26
- return request if defined?(::ActionDispatch)
26
+ return @request if defined?(::ActionDispatch) && @request.is_a?(::ActionDispatch::Request)
27
27
  # `rack-test` integration
28
- return last_request if defined?(::Rack::Test)
28
+ return last_request if defined?(::Rack::Test) && defined?(:last_request)
29
29
 
30
30
  raise "Request object not found"
31
31
  end
32
32
 
33
33
  def response_object
34
34
  # `rails` integration
35
- return response if defined?(::ActionDispatch)
35
+ return @response if defined?(::ActionDispatch) && @response.is_a?(::ActionDispatch::Response)
36
36
  # `rack-test` integration
37
- return last_response if defined?(::Rack::Test)
37
+ return last_response if defined?(::Rack::Test) && defined?(:last_response)
38
38
 
39
39
  raise "Response object not found"
40
40
  end
41
41
  end
42
42
 
43
- def initialize(helper_methods_module, openapi_path, base_uri: "https://skoomarb.dev/")
43
+ def initialize(helper_methods_module, openapi_path, base_uri: "https://skoomarb.dev/", path_prefix: "")
44
44
  super()
45
45
 
46
46
  registry = create_test_registry
47
47
  pathname = Pathname.new(openapi_path)
48
+ source_uri = "#{base_uri}#{path_prefix.delete_suffix("/")}"
49
+ source_uri += "/" unless source_uri.end_with?("/")
48
50
  registry.add_source(
49
- base_uri,
51
+ source_uri,
50
52
  JSONSkooma::Sources::Local.new(pathname.dirname.to_s)
51
53
  )
52
- schema = registry.schema(URI.parse("#{base_uri}#{pathname.basename}"), schema_class: Skooma::Objects::OpenAPI)
54
+ schema = registry.schema(URI.parse("#{source_uri}#{pathname.basename}"), schema_class: Skooma::Objects::OpenAPI)
55
+ schema.path_prefix = path_prefix
53
56
 
54
57
  include DefaultHelperMethods
55
58
  include helper_methods_module
@@ -32,7 +32,7 @@ module Skooma
32
32
  subresult.annotate({"path_attributes" => attributes})
33
33
  path_schema.evaluate(instance, subresult)
34
34
 
35
- if subresult.passed?
35
+ if subresult.passed? && subresult.children.any?
36
36
  result.success
37
37
  else
38
38
  result.failure("Path #{instance["path"]} is invalid")
@@ -43,6 +43,7 @@ module Skooma
43
43
  private
44
44
 
45
45
  def find_route(instance_path)
46
+ instance_path = instance_path.delete_prefix(json.root.path_prefix)
46
47
  return [instance_path, {}, json[instance_path]] if json.key?(instance_path)
47
48
 
48
49
  @regexp_map.reduce(nil) do |result, (path, path_regex, subschema)|
@@ -31,6 +31,18 @@ module Skooma
31
31
  super(Instance.new(instance), result)
32
32
  end
33
33
 
34
+ def path_prefix=(value)
35
+ raise ArgumentError, "Path prefix must be a string" unless value.is_a?(String)
36
+
37
+ @path_prefix = value
38
+ @path_prefix = "/#{@path_prefix}" unless @path_prefix.start_with?("/")
39
+ @path_prefix = @path_prefix.delete_suffix("/") if @path_prefix.end_with?("/")
40
+ end
41
+
42
+ def path_prefix
43
+ @path_prefix || ""
44
+ end
45
+
34
46
  def json_schema_dialect_uri
35
47
  @json_schema_dialect_uri || parent_schema&.json_schema_dialect_uri
36
48
  end
@@ -12,7 +12,7 @@ module Skooma
12
12
  def initialize(parent_schema, value)
13
13
  super
14
14
  keys = json.filter_map { |v| v["in"] && [v["in"].value, v["name"].value] }
15
- parent_params = (parent_schema.parent["parameters"] || [])
15
+ parent_params = parent_schema.parent["parameters"] || []
16
16
  parent_params.reject! do |v|
17
17
  v["in"] && keys.include?([v["in"].value, v["name"].value])
18
18
  end
@@ -8,6 +8,7 @@ module Skooma
8
8
  self.key = "content"
9
9
  self.value_schema = :object_of_schemas
10
10
  self.schema_value_class = Objects::MediaType
11
+ self.depends_on = %w[in name style explode allowReserved allowEmptyValue]
11
12
 
12
13
  def evaluate(instance, result)
13
14
  return if instance.value.nil?
@@ -6,6 +6,7 @@ module Skooma
6
6
  module Keywords
7
7
  class Required < JSONSkooma::Keywords::Base
8
8
  self.key = "required"
9
+ self.depends_on = %w[in name style explode allowReserved allowEmptyValue]
9
10
 
10
11
  def evaluate(instance, result)
11
12
  if json.value && ValueParser.call(instance, result)&.value.nil?
@@ -13,7 +13,7 @@ module Skooma
13
13
  raise Error, "Missing `in` key #{result.path}" unless type
14
14
 
15
15
  key = result.sibling(instance, "name")&.annotation
16
- raise Error, "Missing `name` key #{instance.path}: #{key}" unless key
16
+ raise Error, "Missing `name` key #{result.path}" unless key
17
17
 
18
18
  case type
19
19
  when "query"
@@ -8,6 +8,10 @@ module Skooma
8
8
  def evaluate(instance, result)
9
9
  return result.discard unless instance["method"] == key
10
10
 
11
+ if json["responses"].nil? && instance["response"]
12
+ return result.failure("Responses are not listed for #{key.upcase} #{instance["path"]}")
13
+ end
14
+
11
15
  json.evaluate(instance, result)
12
16
  return result.success if result.passed?
13
17
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Skooma
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skooma
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-23 00:00:00.000000000 Z
11
+ date: 2024-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -30,15 +30,15 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.2'
33
+ version: 0.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.2'
41
- description: I bring some sugar for your APIs.
40
+ version: 0.2.0
41
+ description: Apply a documentation-first approach to API development.
42
42
  email:
43
43
  - me@skryukov.dev
44
44
  executables: []
@@ -166,5 +166,5 @@ requirements: []
166
166
  rubygems_version: 3.3.7
167
167
  signing_key:
168
168
  specification_version: 4
169
- summary: I bring some sugar for your APIs.
169
+ summary: Validate API implementations against OpenAPI documents.
170
170
  test_files: []