rspec-cassette 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a295971bad6b1f3461dd25a592349835c0944f587d6276098e53c3ac64468267
4
+ data.tar.gz: 4bde9b73040304c11eef974d844f11ffc97737989eabbe0ed8c9b1a3098c32a0
5
+ SHA512:
6
+ metadata.gz: ef495ef8f33774e6433f6f5bcf1a97391bf056487562e52bb44e63f04e842faed96c3b84cef48e28f920acb6fc77523b4f5b725a0f412f578725c732a3fa0562
7
+ data.tar.gz: a8299f4c8a17f80c80f6dc7731017b4466fdcbc0e683fca9aa255d2d94985b209bf7216f945694d9a5f46944cd95ece2bb158dcd92aa0412fd9dcc8cad9f5d20
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-02-16)
4
+
5
+ - Initial release.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Yudai Takada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # rspec-cassette
2
+
3
+ Replay test suite's HTTP interactions as WebMock stubs in RSpec.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ ```bash
10
+ bundle add rspec-cassette
11
+ ```
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ ```bash
16
+ gem install rspec-cassette
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Require the RSpec integration in your `spec_helper.rb` or `rails_helper.rb`:
22
+
23
+ ```ruby
24
+ require "rspec/cassette/rspec_helper"
25
+
26
+ Rspec::Cassette.configure do |config|
27
+ config.cassettes_dir = "spec/fixtures/cassettes"
28
+ config.default_match_on = %i[method uri]
29
+ end
30
+ ```
31
+
32
+ Helper method style:
33
+
34
+ ```ruby
35
+ describe "API client" do
36
+ it "fetches users" do
37
+ use_cassette("users/index")
38
+ # ...
39
+ end
40
+ end
41
+ ```
42
+
43
+ Metadata style:
44
+
45
+ ```ruby
46
+ it "fetches users", use_cassette: "users/index" do
47
+ # ...
48
+ end
49
+ ```
50
+
51
+ To pass match options per example:
52
+
53
+ ```ruby
54
+ it "matches body", use_cassette: "users/index", cassette_options: { match_on: %i[method uri body] } do
55
+ # ...
56
+ end
57
+ ```
58
+
59
+ ## Migration Guide
60
+
61
+ Before:
62
+
63
+ ```ruby
64
+ it "fetches users" do
65
+ VCR.use_cassette("users/index") do
66
+ # ...
67
+ end
68
+ end
69
+ ```
70
+
71
+ After:
72
+
73
+ ```ruby
74
+ it "fetches users", use_cassette: "users/index" do
75
+ # ...
76
+ end
77
+ ```
78
+
79
+ ## Configuration Options
80
+
81
+ | Option | Default | Description |
82
+ | --- | --- | --- |
83
+ | `cassettes_dir` | `spec/fixtures/cassettes` | Base directory for cassette files |
84
+ | `default_match_on` | `[:method, :uri]` | WebMock matchers to apply |
85
+
86
+ ## Development
87
+
88
+ After checking out the repo, run `bundle install` to install dependencies. Then run `bundle exec rspec` to execute the tests.
89
+
90
+ ## Contributing
91
+
92
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ydah/rspec-cassette.
93
+
94
+ ## License
95
+
96
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "time"
5
+
6
+ module RSpec
7
+ module Cassette
8
+ class Cassette
9
+ attr_reader :interactions, :path
10
+
11
+ def initialize(path)
12
+ @path = resolve_path(path)
13
+ @interactions = load_interactions(@path)
14
+ end
15
+
16
+ private
17
+
18
+ def resolve_path(path)
19
+ expanded = path.end_with?(".yml") ? path : "#{path}.yml"
20
+ return expanded if File.exist?(expanded)
21
+
22
+ raise CassetteNotFoundError, "Cassette not found: #{expanded}"
23
+ end
24
+
25
+ def load_interactions(path)
26
+ data = YAML.safe_load(
27
+ File.read(path),
28
+ permitted_classes: [Time, Symbol],
29
+ aliases: true
30
+ )
31
+
32
+ raw_interactions = Array(data.fetch("http_interactions"))
33
+ raw_interactions.map { |raw| Interaction.new(raw) }
34
+ rescue KeyError, Psych::Exception => e
35
+ raise CassetteParseError, "Cassette parse error: #{e.message}"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Cassette
5
+ class Configuration
6
+ attr_accessor :cassettes_dir, :default_match_on
7
+
8
+ def initialize
9
+ @cassettes_dir = "spec/fixtures/cassettes"
10
+ @default_match_on = %i[method uri]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Cassette
5
+ class Error < StandardError; end
6
+ class CassetteNotFoundError < Error; end
7
+ class CassetteParseError < Error; end
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Cassette
5
+ class Interaction
6
+ attr_reader :request, :response
7
+
8
+ def initialize(raw_interaction)
9
+ @request = raw_interaction.fetch("request")
10
+ @response = raw_interaction.fetch("response")
11
+ end
12
+
13
+ def method
14
+ request.fetch("method").to_sym
15
+ end
16
+
17
+ def uri
18
+ request.fetch("uri")
19
+ end
20
+
21
+ def request_headers
22
+ request.fetch("headers", {}) || {}
23
+ end
24
+
25
+ def request_body
26
+ body = request.fetch("body", {}) || {}
27
+ body.fetch("string", "")
28
+ end
29
+
30
+ def status
31
+ response.fetch("status", {}).fetch("code")
32
+ end
33
+
34
+ def response_headers
35
+ response.fetch("headers", {}) || {}
36
+ end
37
+
38
+ def response_body
39
+ body = response.fetch("body", {}) || {}
40
+ body.fetch("string", "")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/cassette"
4
+
5
+ module RSpec
6
+ module Cassette
7
+ module RSpecHelper
8
+ def use_cassette(name, **options)
9
+ cassette = Cassette.new(cassette_path(name))
10
+ StubRegistrar.new(cassette, match_on: options[:match_on]).register!
11
+ end
12
+
13
+ private
14
+
15
+ def cassette_path(name)
16
+ base_dir = RSpec::Cassette.configuration.cassettes_dir
17
+ File.join(base_dir, name)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ RSpec.configure do |config|
24
+ config.include RSpec::Cassette::RSpecHelper
25
+
26
+ config.around(:each) do |example|
27
+ cassette_name = example.metadata[:use_cassette]
28
+ options = example.metadata[:cassette_options] || {}
29
+ use_cassette(cassette_name, **options) if cassette_name
30
+ example.run
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webmock"
4
+
5
+ module RSpec
6
+ module Cassette
7
+ class StubRegistrar
8
+ def initialize(cassette, match_on: nil)
9
+ @cassette = cassette
10
+ @match_on = Array(match_on || RSpec::Cassette.configuration.default_match_on)
11
+ end
12
+
13
+ def register!
14
+ grouped_interactions.each do |(method, uri), interactions|
15
+ stub = WebMock.stub_request(method, uri)
16
+ stub = apply_matchers(stub, interactions.first)
17
+ stub.to_return(*interactions.map { |interaction| build_response(interaction) })
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def grouped_interactions
24
+ @cassette.interactions.group_by { |interaction| [interaction.method, interaction.uri] }
25
+ end
26
+
27
+ def apply_matchers(stub, interaction)
28
+ matcher_options = {}
29
+ matcher_options[:body] = interaction.request_body if @match_on.include?(:body)
30
+ matcher_options[:headers] = interaction.request_headers if @match_on.include?(:headers)
31
+ return stub if matcher_options.empty?
32
+
33
+ stub.with(matcher_options)
34
+ end
35
+
36
+ def build_response(interaction)
37
+ {
38
+ status: interaction.status,
39
+ body: interaction.response_body,
40
+ headers: interaction.response_headers
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Cassette
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cassette/version"
4
+ require_relative "cassette/errors"
5
+ require_relative "cassette/configuration"
6
+ require_relative "cassette/cassette"
7
+ require_relative "cassette/interaction"
8
+ require_relative "cassette/stub_registrar"
9
+
10
+ module RSpec
11
+ module Cassette
12
+ class << self
13
+ def configuration
14
+ @configuration ||= Configuration.new
15
+ end
16
+
17
+ def configure
18
+ yield(configuration)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ module RSpec
2
+ module Cassette
3
+ VERSION: String
4
+ class Error < StandardError
5
+ end
6
+
7
+ class CassetteNotFoundError < Error
8
+ end
9
+
10
+ class CassetteParseError < Error
11
+ end
12
+
13
+ class Configuration
14
+ attr_accessor cassettes_dir: String
15
+ attr_accessor default_match_on: Array[Symbol]
16
+ end
17
+
18
+ def self.configuration: () -> Configuration
19
+ def self.configure: () { (Configuration) -> void } -> void
20
+
21
+ class Interaction
22
+ attr_reader request: Hash[String, untyped]
23
+ attr_reader response: Hash[String, untyped]
24
+ def initialize: (Hash[String, untyped] raw_interaction) -> void
25
+ def method: () -> Symbol
26
+ def uri: () -> String
27
+ def request_headers: () -> Hash[String, Array[String]]
28
+ def request_body: () -> String
29
+ def status: () -> Integer
30
+ def response_headers: () -> Hash[String, Array[String]]
31
+ def response_body: () -> String
32
+ end
33
+
34
+ class Cassette
35
+ attr_reader interactions: Array[Interaction]
36
+ attr_reader path: String
37
+ def initialize: (String path) -> void
38
+ end
39
+
40
+ class StubRegistrar
41
+ def initialize: (Cassette cassette, ?match_on: Array[Symbol] | nil) -> void
42
+ def register!: () -> void
43
+ end
44
+ end
45
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-cassette
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: webmock
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '3.14'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '3.14'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec-core
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '3.12'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '3.12'
40
+ description: Load test suite's HTTP interactions and register WebMock stubs for RSpec.
41
+ email:
42
+ - t.yudai92@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - CHANGELOG.md
48
+ - LICENSE.txt
49
+ - README.md
50
+ - Rakefile
51
+ - lib/rspec/cassette.rb
52
+ - lib/rspec/cassette/cassette.rb
53
+ - lib/rspec/cassette/configuration.rb
54
+ - lib/rspec/cassette/errors.rb
55
+ - lib/rspec/cassette/interaction.rb
56
+ - lib/rspec/cassette/rspec_helper.rb
57
+ - lib/rspec/cassette/stub_registrar.rb
58
+ - lib/rspec/cassette/version.rb
59
+ - sig/rspec/cassette.rbs
60
+ homepage: https://github.com/ydah/rspec-cassette
61
+ licenses:
62
+ - MIT
63
+ metadata:
64
+ allowed_push_host: https://rubygems.org
65
+ homepage_uri: https://github.com/ydah/rspec-cassette
66
+ source_code_uri: https://github.com/ydah/rspec-cassette
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.1.0
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 4.0.6
82
+ specification_version: 4
83
+ summary: Replay test suite's HTTP interactions as WebMock stubs in RSpec.
84
+ test_files: []