shopify-tcr 0.0.5.pre.shopify
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 +7 -0
- data/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +4 -0
- data/lib/tcr.rb +67 -0
- data/lib/tcr/cassette.rb +176 -0
- data/lib/tcr/configuration.rb +14 -0
- data/lib/tcr/errors.rb +6 -0
- data/lib/tcr/recordable.rb +45 -0
- data/lib/tcr/socket_extension.rb +11 -0
- data/lib/tcr/ssl_socket_extension.rb +16 -0
- data/lib/tcr/version.rb +3 -0
- data/spec/fixtures/google_smtp.json +12 -0
- data/spec/fixtures/multitest-smtp.json +56 -0
- data/spec/fixtures/multitest.json +20 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/tcr_spec.rb +151 -0
- data/tcr.gemspec +22 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5765907faf7f2ab832ec3c3d3b5477cd88dedda1
|
4
|
+
data.tar.gz: 3ab7b0a348ed8ec457f26cc910eba7c33aa602b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b6358e5fca32fba779383cb6f0b12f39a00d03f443bc9940eb12e37442058c1c4c055e82914fe42684fe5268f77518605dfb263b368dd6136116c9b4a8ec4b8
|
7
|
+
data.tar.gz: f9c552dc0527e9f190393a85b7fa66997915e111db60293390d77b4c1e8424ca0a19d480c4535f198ad7ae9dd83048b774fd061c46f947bb92583bbb6aac983a
|
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@tcr --create
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Rob Forman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# TCR (TCP + VCR)
|
2
|
+
|
3
|
+
TCR is a *very* lightweight version of [VCR](https://github.com/vcr/vcr) for TCP sockets.
|
4
|
+
|
5
|
+
Currently used for recording 'net/smtp' interactions so only a few of the TCPSocket methods are recorded out.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'tcr'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install tcr
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'test/unit'
|
25
|
+
require 'tcr'
|
26
|
+
|
27
|
+
TCR.configure do |c|
|
28
|
+
c.cassette_library_dir = 'fixtures/tcr_cassettes'
|
29
|
+
c.hook_tcp_ports = [25]
|
30
|
+
end
|
31
|
+
|
32
|
+
class TCRTest < Test::Unit::TestCase
|
33
|
+
def test_example_dot_com
|
34
|
+
TCR.use_cassette('google_smtp') do
|
35
|
+
tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
|
36
|
+
io = Net::InternetMessageIO.new(tcp_socket)
|
37
|
+
assert_match /220 mx.google.com ESMTP/, io.readline
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Run this test once, and TCR will record the tcp interactions to fixtures/tcr_cassettes/google_smtp.json.
|
44
|
+
|
45
|
+
```json
|
46
|
+
[
|
47
|
+
[
|
48
|
+
[
|
49
|
+
"read",
|
50
|
+
"220 mx.google.com ESMTP x3si2474860qas.18 - gsmtp\r\n"
|
51
|
+
]
|
52
|
+
]
|
53
|
+
]
|
54
|
+
```
|
55
|
+
|
56
|
+
Run it again, and TCR will replay the interactions from json when the tcp request is made. This test is now fast (no real TCP requests are made anymore), deterministic and accurate.
|
57
|
+
|
58
|
+
You can disable TCR hooking TCPSocket ports for a given block via `turned_off`:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
TCR.turned_off do
|
62
|
+
tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
1. Fork it
|
69
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
70
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
71
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
72
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/tcr.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require "tcr/cassette"
|
2
|
+
require "tcr/configuration"
|
3
|
+
require "tcr/errors"
|
4
|
+
require "tcr/recordable"
|
5
|
+
require "tcr/socket_extension"
|
6
|
+
require "tcr/ssl_socket_extension"
|
7
|
+
require "tcr/version"
|
8
|
+
require "socket"
|
9
|
+
require "json"
|
10
|
+
|
11
|
+
module TCR
|
12
|
+
ALL_PORTS = '*'
|
13
|
+
|
14
|
+
extend self
|
15
|
+
|
16
|
+
def configure
|
17
|
+
yield configuration
|
18
|
+
end
|
19
|
+
|
20
|
+
def configuration
|
21
|
+
@configuration ||= Configuration.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def cassette
|
25
|
+
@cassette
|
26
|
+
end
|
27
|
+
|
28
|
+
def cassette=(v)
|
29
|
+
@cassette = v
|
30
|
+
end
|
31
|
+
|
32
|
+
def disabled
|
33
|
+
@disabled || false
|
34
|
+
end
|
35
|
+
|
36
|
+
def disabled=(v)
|
37
|
+
@disabled = v
|
38
|
+
end
|
39
|
+
|
40
|
+
def record_port?(port)
|
41
|
+
!disabled && configuration.hook_tcp_ports == ALL_PORTS || configuration.hook_tcp_ports.include?(port)
|
42
|
+
end
|
43
|
+
|
44
|
+
def use_cassette(name, options = {}, &block)
|
45
|
+
raise ArgumentError, "`TCR.use_cassette` requires a block." unless block
|
46
|
+
begin
|
47
|
+
TCR.cassette = Cassette.build(name)
|
48
|
+
yield TCR.cassette
|
49
|
+
ensure
|
50
|
+
TCR.cassette.finish
|
51
|
+
TCR.cassette = nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def turned_off(&block)
|
56
|
+
raise ArgumentError, "`TCR.turned_off` requires a block." unless block
|
57
|
+
begin
|
58
|
+
disabled = true
|
59
|
+
yield
|
60
|
+
ensure
|
61
|
+
disabled = false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
TCPSocket.prepend(TCR::SocketExtension)
|
67
|
+
OpenSSL::SSL::SSLSocket.send(:include, TCR::SSLSocketExtension)
|
data/lib/tcr/cassette.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
module TCR
|
2
|
+
class Cassette
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def self.build(name)
|
6
|
+
if cassette_exists?(name)
|
7
|
+
RecordedCassette.new(name)
|
8
|
+
else
|
9
|
+
RecordingCassette.new(name)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.filename(name)
|
14
|
+
"#{TCR.configuration.cassette_library_dir}/#{name}.json"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.cassette_exists?(name)
|
18
|
+
File.exists?(filename(name))
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(name)
|
22
|
+
@name = name
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def filename
|
28
|
+
self.class.filename(name)
|
29
|
+
end
|
30
|
+
|
31
|
+
class RecordingCassette < Cassette
|
32
|
+
attr_reader :originally_recorded_at
|
33
|
+
|
34
|
+
def initialize(*args)
|
35
|
+
super
|
36
|
+
@originally_recorded_at = Time.now
|
37
|
+
end
|
38
|
+
|
39
|
+
def sessions
|
40
|
+
@sessions ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def next_session
|
44
|
+
Session.new.tap do |session|
|
45
|
+
sessions << session
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def finish
|
50
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
51
|
+
File.open(filename, "w") { |f| f.write(serialized_form) }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def serialized_form
|
57
|
+
raw = {
|
58
|
+
"originally_recorded_at" => originally_recorded_at,
|
59
|
+
'sessions' => sessions.map(&:as_json)
|
60
|
+
}
|
61
|
+
JSON.pretty_generate(raw)
|
62
|
+
end
|
63
|
+
|
64
|
+
def empty?
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
class Session
|
69
|
+
def initialize
|
70
|
+
@recording = []
|
71
|
+
end
|
72
|
+
|
73
|
+
def connect(&block)
|
74
|
+
next_command('connect', ret: nil, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
def close(&block)
|
78
|
+
next_command('close', &block)
|
79
|
+
end
|
80
|
+
|
81
|
+
def read(*args, &block)
|
82
|
+
next_command('read', data: args, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def write(str, &block)
|
86
|
+
next_command('write', data: str, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def as_json
|
90
|
+
{"recording" => @recording}
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def next_command(command, options={}, &block)
|
96
|
+
yield.tap do |return_value|
|
97
|
+
return_value = options[:ret] if options.has_key?(:ret)
|
98
|
+
@recording << [command, return_value] + Array(options[:data])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class RecordedCassette < Cassette
|
105
|
+
def sessions
|
106
|
+
@sessions ||= serialized_form['sessions'].map{|raw| Session.new(raw)}
|
107
|
+
end
|
108
|
+
|
109
|
+
def originally_recorded_at
|
110
|
+
serialized_form['originally_recorded_at']
|
111
|
+
end
|
112
|
+
|
113
|
+
def next_session
|
114
|
+
session = sessions.shift
|
115
|
+
raise NoMoreSessionsError unless session
|
116
|
+
session
|
117
|
+
end
|
118
|
+
|
119
|
+
def finish
|
120
|
+
# no-op
|
121
|
+
end
|
122
|
+
|
123
|
+
def empty?
|
124
|
+
sessions.all?(&:empty?)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def serialized_form
|
130
|
+
@serialized_form ||= begin
|
131
|
+
raw = File.open(filename) { |f| f.read }
|
132
|
+
JSON.parse(raw)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class Session
|
137
|
+
def initialize(raw)
|
138
|
+
@recording = raw["recording"]
|
139
|
+
end
|
140
|
+
|
141
|
+
def connect
|
142
|
+
next_command('connect')
|
143
|
+
end
|
144
|
+
|
145
|
+
def close
|
146
|
+
next_command('close')
|
147
|
+
end
|
148
|
+
|
149
|
+
def read(*args)
|
150
|
+
next_command('read') do |str, *data|
|
151
|
+
raise TCR::DataMismatchError.new("Expected to read to be called with args '#{args.inspect}' but was called with '#{data.inspect}'") unless args == data
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def write(str)
|
156
|
+
next_command('write') do |len, data|
|
157
|
+
raise TCR::DataMismatchError.new("Expected to write '#{str}' but next data in recording was '#{data}'") unless str == data
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def empty?
|
162
|
+
@recording.empty?
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def next_command(expected)
|
168
|
+
command, return_value, data = @recording.shift
|
169
|
+
raise TCR::CommandMismatchError.new("Expected to '#{expected}' but next in recording was '#{command}'") unless expected == command
|
170
|
+
yield return_value, *data if block_given?
|
171
|
+
return_value
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/tcr/errors.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module TCR
|
2
|
+
module Recordable
|
3
|
+
attr_accessor :cassette
|
4
|
+
|
5
|
+
def recording
|
6
|
+
@recording ||= cassette.next_session
|
7
|
+
end
|
8
|
+
|
9
|
+
def connect
|
10
|
+
recording.connect do
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def read_nonblock(bytes)
|
16
|
+
recording.read do
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def gets(*args)
|
22
|
+
recording.read do
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(str)
|
28
|
+
recording.write(str) do
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def read(*args)
|
34
|
+
recording.read do
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
recording.close do
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module TCR
|
2
|
+
module SSLSocketExtension
|
3
|
+
def self.included(klass)
|
4
|
+
klass.send(:alias_method, :initialize_without_tcr, :initialize)
|
5
|
+
klass.send(:alias_method, :initialize, :initialize_with_tcr)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize_with_tcr(s, context)
|
9
|
+
initialize_without_tcr(s, context)
|
10
|
+
if TCR.record_port?(s.remote_address.ip_port) && TCR.cassette
|
11
|
+
extend(TCR::Recordable)
|
12
|
+
self.cassette = TCR.cassette
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/tcr/version.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
{
|
2
|
+
"sessions": [
|
3
|
+
{
|
4
|
+
"recording": [
|
5
|
+
[
|
6
|
+
"read",
|
7
|
+
"220 mx.google.com ESMTP d8si2472149qai.124 - gsmtp\r\n"
|
8
|
+
],
|
9
|
+
[
|
10
|
+
"write",
|
11
|
+
16,
|
12
|
+
"EHLO localhost\r\n"
|
13
|
+
],
|
14
|
+
[
|
15
|
+
"read",
|
16
|
+
"250-mx.google.com at your service, [54.227.243.167]\r\n250-SIZE 35882577\r\n250-8BITMIME\r\n250-STARTTLS\r\n250 ENHANCEDSTATUSCODES\r\n"
|
17
|
+
],
|
18
|
+
[
|
19
|
+
"write",
|
20
|
+
6,
|
21
|
+
"QUIT\r\n"
|
22
|
+
],
|
23
|
+
[
|
24
|
+
"read",
|
25
|
+
"221 2.0.0 closing connection d8si2472149qai.124 - gsmtp\r\n"
|
26
|
+
]
|
27
|
+
]
|
28
|
+
},
|
29
|
+
{
|
30
|
+
"recording": [
|
31
|
+
[
|
32
|
+
"read",
|
33
|
+
"220 mta1579.mail.gq1.yahoo.com ESMTP YSmtpProxy service ready\r\n"
|
34
|
+
],
|
35
|
+
[
|
36
|
+
"write",
|
37
|
+
16,
|
38
|
+
"EHLO localhost\r\n"
|
39
|
+
],
|
40
|
+
[
|
41
|
+
"read",
|
42
|
+
"250-mta1579.mail.gq1.yahoo.com\r\n250-8BITMIME\r\n250-SIZE 41943040\r\n250 PIPELINING\r\n"
|
43
|
+
],
|
44
|
+
[
|
45
|
+
"write",
|
46
|
+
6,
|
47
|
+
"QUIT\r\n"
|
48
|
+
],
|
49
|
+
[
|
50
|
+
"read",
|
51
|
+
"221 mta1579.mail.gq1.yahoo.com\r\n"
|
52
|
+
]
|
53
|
+
]
|
54
|
+
}
|
55
|
+
]
|
56
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"sessions": [
|
3
|
+
{
|
4
|
+
"recording": [
|
5
|
+
[
|
6
|
+
"read",
|
7
|
+
"220 mx.google.com ESMTP h5si2286277qec.54 - gsmtp\r\n"
|
8
|
+
]
|
9
|
+
]
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"recording": [
|
13
|
+
[
|
14
|
+
"read",
|
15
|
+
"220 mta1009.mail.gq1.yahoo.com ESMTP YSmtpProxy service ready\r\n"
|
16
|
+
]
|
17
|
+
]
|
18
|
+
}
|
19
|
+
]
|
20
|
+
}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
data/spec/tcr_spec.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "tcr"
|
3
|
+
require "net/protocol"
|
4
|
+
require "net/smtp"
|
5
|
+
|
6
|
+
describe TCR do
|
7
|
+
before(:each) do
|
8
|
+
TCR.configuration.reset_defaults!
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ".configuration" do
|
12
|
+
it "has a default cassette location configured" do
|
13
|
+
TCR.configuration.cassette_library_dir.should == "fixtures/tcr_cassettes"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "has an empty list of hook ports by default" do
|
17
|
+
TCR.configuration.hook_tcp_ports.should == []
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ".configure" do
|
22
|
+
it "configures cassette location" do
|
23
|
+
expect {
|
24
|
+
TCR.configure { |c| c.cassette_library_dir = "some/dir" }
|
25
|
+
}.to change{ TCR.configuration.cassette_library_dir }.from("fixtures/tcr_cassettes").to("some/dir")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "configures tcp ports to hook" do
|
29
|
+
expect {
|
30
|
+
TCR.configure { |c| c.hook_tcp_ports = [25] }
|
31
|
+
}.to change{ TCR.configuration.hook_tcp_ports }.from([]).to([25])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe ".turned_off" do
|
36
|
+
it "requires a block to call" do
|
37
|
+
expect {
|
38
|
+
TCR.turned_off
|
39
|
+
}.to raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "makes real TCPSocket.open calls even when hooks are setup" do
|
43
|
+
TCR.configure { |c| c.hook_tcp_ports = [25] }
|
44
|
+
TCR.turned_off do
|
45
|
+
tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
|
46
|
+
expect(tcp_socket).not_to respond_to(:cassette)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ".use_cassette" do
|
52
|
+
before(:each) {
|
53
|
+
TCR.configure { |c|
|
54
|
+
c.hook_tcp_ports = [25]
|
55
|
+
c.cassette_library_dir = "."
|
56
|
+
}
|
57
|
+
File.unlink("test.json") if File.exists?("test.json")
|
58
|
+
}
|
59
|
+
after(:each) {
|
60
|
+
File.unlink("test.json") if File.exists?("test.json")
|
61
|
+
}
|
62
|
+
|
63
|
+
it "requires a block to call" do
|
64
|
+
expect {
|
65
|
+
TCR.use_cassette("test")
|
66
|
+
}.to raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "resets the cassette after use" do
|
70
|
+
expect(TCR.cassette).to be_nil
|
71
|
+
TCR.use_cassette("test") { }
|
72
|
+
expect(TCR.cassette).to be_nil
|
73
|
+
end
|
74
|
+
|
75
|
+
it "creates a cassette file on use" do
|
76
|
+
expect {
|
77
|
+
TCR.use_cassette("test") do
|
78
|
+
tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
|
79
|
+
tcp_socket.close
|
80
|
+
end
|
81
|
+
}.to change{ File.exists?("./test.json") }.from(false).to(true)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "records the tcp session data into the file" do
|
85
|
+
TCR.use_cassette("test") do
|
86
|
+
tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
|
87
|
+
io = Net::InternetMessageIO.new(tcp_socket)
|
88
|
+
line = io.readline
|
89
|
+
tcp_socket.close
|
90
|
+
end
|
91
|
+
cassette_contents = File.open("test.json") { |f| f.read }
|
92
|
+
cassette_contents.include?("220 mx.google.com ESMTP").should == true
|
93
|
+
end
|
94
|
+
|
95
|
+
it "plays back tcp sessions without opening a real connection" do
|
96
|
+
TCR.use_cassette("spec/fixtures/google_smtp") do
|
97
|
+
tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
|
98
|
+
expect(tcp_socket).to respond_to(:cassette)
|
99
|
+
io = Net::InternetMessageIO.new(tcp_socket)
|
100
|
+
line = io.readline.should include("220 mx.google.com ESMTP")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "raises an error if the recording gets out of order (i.e., we went to write but it expected a read)" do
|
105
|
+
expect {
|
106
|
+
TCR.use_cassette("spec/fixtures/google_smtp") do
|
107
|
+
tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
|
108
|
+
io = Net::InternetMessageIO.new(tcp_socket)
|
109
|
+
io.write("hi")
|
110
|
+
end
|
111
|
+
}.to raise_error(TCR::CommandMismatchError)
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
context "multiple connections" do
|
116
|
+
it "records multiple sessions per cassette" do
|
117
|
+
TCR.use_cassette("test") do
|
118
|
+
smtp = Net::SMTP.start("aspmx.l.google.com", 25)
|
119
|
+
smtp.finish
|
120
|
+
smtp = Net::SMTP.start("mta6.am0.yahoodns.net", 25)
|
121
|
+
smtp.finish
|
122
|
+
end
|
123
|
+
cassette_contents = File.open("test.json") { |f| f.read }
|
124
|
+
cassette_contents.include?("google.com ESMTP").should == true
|
125
|
+
cassette_contents.include?("yahoo.com ESMTP").should == true
|
126
|
+
end
|
127
|
+
|
128
|
+
it "plays back multiple sessions per cassette in order" do
|
129
|
+
TCR.use_cassette("spec/fixtures/multitest") do
|
130
|
+
tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
|
131
|
+
io = Net::InternetMessageIO.new(tcp_socket)
|
132
|
+
line = io.readline.should include("google.com ESMTP")
|
133
|
+
|
134
|
+
tcp_socket = TCPSocket.open("mta6.am0.yahoodns.net", 25)
|
135
|
+
io = Net::InternetMessageIO.new(tcp_socket)
|
136
|
+
line = io.readline.should include("yahoo.com ESMTP")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it "raises an error if you try to playback more sessions than you previously recorded" do
|
141
|
+
expect {
|
142
|
+
TCR.use_cassette("spec/fixtures/multitest-smtp") do
|
143
|
+
smtp = Net::SMTP.start("aspmx.l.google.com", 25)
|
144
|
+
smtp = Net::SMTP.start("mta6.am0.yahoodns.net", 25)
|
145
|
+
smtp = Net::SMTP.start("mta6.am0.yahoodns.net", 25)
|
146
|
+
end
|
147
|
+
}.to raise_error(TCR::NoMoreSessionsError)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/tcr.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tcr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "shopify-tcr"
|
8
|
+
gem.version = TCR::VERSION
|
9
|
+
gem.authors = ["Luke Hutscal"]
|
10
|
+
gem.email = 'luke.hutscal@shopify.com'
|
11
|
+
gem.description = %q{TCR is a lightweight VCR for TCP sockets.}
|
12
|
+
gem.summary = %q{TCR is a lightweight VCR for TCP sockets.}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency "rspec"
|
21
|
+
gem.add_development_dependency "geminabox"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shopify-tcr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5.pre.shopify
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luke Hutscal
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: geminabox
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: TCR is a lightweight VCR for TCP sockets.
|
42
|
+
email: luke.hutscal@shopify.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- ".gitignore"
|
48
|
+
- ".rvmrc"
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- lib/tcr.rb
|
54
|
+
- lib/tcr/cassette.rb
|
55
|
+
- lib/tcr/configuration.rb
|
56
|
+
- lib/tcr/errors.rb
|
57
|
+
- lib/tcr/recordable.rb
|
58
|
+
- lib/tcr/socket_extension.rb
|
59
|
+
- lib/tcr/ssl_socket_extension.rb
|
60
|
+
- lib/tcr/version.rb
|
61
|
+
- spec/fixtures/google_smtp.json
|
62
|
+
- spec/fixtures/multitest-smtp.json
|
63
|
+
- spec/fixtures/multitest.json
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
- spec/tcr_spec.rb
|
66
|
+
- tcr.gemspec
|
67
|
+
homepage: ''
|
68
|
+
licenses: []
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 1.3.1
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.2.2
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: TCR is a lightweight VCR for TCP sockets.
|
90
|
+
test_files:
|
91
|
+
- spec/fixtures/google_smtp.json
|
92
|
+
- spec/fixtures/multitest-smtp.json
|
93
|
+
- spec/fixtures/multitest.json
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
- spec/tcr_spec.rb
|