tcr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@tcr --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tcr.gemspec
4
+ gemspec
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,51 @@
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. 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.
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ RSpec::Core::RakeTask.new('spec')
4
+ task :default => :spec
data/lib/tcr.rb ADDED
@@ -0,0 +1,53 @@
1
+ require "tcr/configuration"
2
+ require "tcr/errors"
3
+ require "tcr/recordable_tcp_socket"
4
+ require "tcr/version"
5
+ require "socket"
6
+ require "json"
7
+
8
+
9
+ module TCR
10
+ extend self
11
+
12
+ def configure
13
+ yield configuration
14
+ end
15
+
16
+ def configuration
17
+ @configuration ||= Configuration.new
18
+ end
19
+
20
+ def current_cassette
21
+ raise TCR::NoCassetteError unless @current_cassette
22
+ @current_cassette
23
+ end
24
+
25
+ def use_cassette(name, options = {}, &block)
26
+ raise ArgumentError, "`TCR.use_cassette` requires a block." unless block
27
+ set_cassette(name)
28
+ yield
29
+ @current_cassette = nil
30
+ end
31
+
32
+ protected
33
+
34
+ def set_cassette(name)
35
+ @current_cassette = "#{TCR.configuration.cassette_library_dir}/#{name}.json"
36
+ end
37
+ end
38
+
39
+
40
+ # The monkey patch shim
41
+ class TCPSocket
42
+ class << self
43
+ alias_method :real_open, :open
44
+
45
+ def open(address, port)
46
+ if TCR.configuration.hook_tcp_ports.include?(port)
47
+ TCR::RecordableTCPSocket.new(address, port, TCR.current_cassette)
48
+ else
49
+ real_open(address, port)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,14 @@
1
+ module TCR
2
+ class Configuration
3
+ attr_accessor :cassette_library_dir, :hook_tcp_ports
4
+
5
+ def initialize
6
+ reset_defaults!
7
+ end
8
+
9
+ def reset_defaults!
10
+ @cassette_library_dir = "fixtures/tcr_cassettes"
11
+ @hook_tcp_ports = []
12
+ end
13
+ end
14
+ end
data/lib/tcr/errors.rb ADDED
@@ -0,0 +1,5 @@
1
+ module TCR
2
+ class TCRError < StandardError; end
3
+ class NoCassetteError < TCRError; end
4
+ class DirectionMismatchError < TCRError; end
5
+ end
@@ -0,0 +1,65 @@
1
+ module TCR
2
+ class RecordableTCPSocket
3
+ attr_reader :live, :recording_file
4
+ attr_accessor :recordings
5
+
6
+ def initialize(address, port, recording_file)
7
+ @recording_file = recording_file
8
+
9
+ if File.exists?(recording_file)
10
+ @live = false
11
+ @recordings = JSON.parse(File.open(recording_file, "r") { |f| f.read })
12
+ else
13
+ @live = true
14
+ @recordings = []
15
+ @socket = TCPSocket.real_open(address, port)
16
+ end
17
+ end
18
+
19
+ def read_nonblock(bytes)
20
+ if live
21
+ data = @socket.read_nonblock(bytes)
22
+ recordings << ["read", data]
23
+ else
24
+ direction, data = recordings.shift
25
+ raise DirectionMismatchError("Expected to 'read' but next in recording was 'write'") unless direction == "read"
26
+ end
27
+
28
+ data
29
+ end
30
+
31
+ def write(str)
32
+ if live
33
+ len = @socket.write(str)
34
+ recordings << ["write", str]
35
+ else
36
+ direction, data = recordings.shift
37
+ raise DirectionMismatchError("Expected to 'write' but next in recording was 'read'") unless direction == "write"
38
+ len = data.length
39
+ end
40
+
41
+ len
42
+ end
43
+
44
+ def to_io
45
+ if live
46
+ @socket.to_io
47
+ end
48
+ end
49
+
50
+ def closed?
51
+ if live
52
+ @socket.closed?
53
+ else
54
+ false
55
+ end
56
+ end
57
+
58
+ def close
59
+ if live
60
+ @socket.close
61
+ File.open(recording_file, "w") { |f| f.write(JSON.pretty_generate(recordings)) }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module TCR
2
+ VERSION = "0.0.1"
3
+ end
@@ -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,79 @@
1
+ require "spec_helper"
2
+ require "tcr"
3
+ require "net/protocol"
4
+
5
+ describe TCR do
6
+ before(:each) do
7
+ TCR.configuration.reset_defaults!
8
+ end
9
+
10
+ describe ".configuration" do
11
+ it "has a default cassette location configured" do
12
+ TCR.configuration.cassette_library_dir.should == "fixtures/tcr_cassettes"
13
+ end
14
+
15
+ it "has an empty list of hook ports by default" do
16
+ TCR.configuration.hook_tcp_ports.should == []
17
+ end
18
+ end
19
+
20
+ describe ".confige" do
21
+ it "configures cassette location" do
22
+ expect {
23
+ TCR.configure { |c| c.cassette_library_dir = "some/dir" }
24
+ }.to change{ TCR.configuration.cassette_library_dir }.from("fixtures/tcr_cassettes").to("some/dir")
25
+ end
26
+
27
+ it "configures tcp ports to hook" do
28
+ expect {
29
+ TCR.configure { |c| c.hook_tcp_ports = [25] }
30
+ }.to change{ TCR.configuration.hook_tcp_ports }.from([]).to([25])
31
+ end
32
+ end
33
+
34
+ it "raises an error if you connect to a hooked port without using a cassette" do
35
+ TCR.configure { |c| c.hook_tcp_ports = [25] }
36
+ expect {
37
+ tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
38
+ }.to raise_error(TCR::NoCassetteError)
39
+ end
40
+
41
+ describe ".use_cassette" do
42
+ before(:each) {
43
+ TCR.configure { |c|
44
+ c.hook_tcp_ports = [25]
45
+ c.cassette_library_dir = "."
46
+ }
47
+ File.unlink("./test.json") if File.exists?("./test.json")
48
+ }
49
+ after(:each) {
50
+ File.unlink("./test.json") if File.exists?("./test.json")
51
+ }
52
+
53
+ it "requires a block to call" do
54
+ expect {
55
+ TCR.use_cassette("test")
56
+ }.to raise_error(ArgumentError)
57
+ end
58
+
59
+ it "creates a cassette file on use" do
60
+ expect {
61
+ TCR.use_cassette("test") do
62
+ tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
63
+ tcp_socket.close
64
+ end
65
+ }.to change{ File.exists?("./test.json") }.from(false).to(true)
66
+ end
67
+
68
+ it "records the tcp session data into the file" do
69
+ TCR.use_cassette("test") do
70
+ tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
71
+ io = Net::InternetMessageIO.new(tcp_socket)
72
+ line = io.readline
73
+ tcp_socket.close
74
+ end
75
+ file_contents = File.open("./test.json") { |f| f.read }
76
+ file_contents.include?("220 mx.google.com ESMTP").should == true
77
+ end
78
+ end
79
+ 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 = "tcr"
8
+ gem.version = TCR::VERSION
9
+ gem.authors = ["Rob Forman"]
10
+ gem.email = ["rob@robforman.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,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tcr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rob Forman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: geminabox
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: TCR is a lightweight VCR for TCP sockets.
47
+ email:
48
+ - rob@robforman.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - .rvmrc
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - lib/tcr.rb
60
+ - lib/tcr/configuration.rb
61
+ - lib/tcr/errors.rb
62
+ - lib/tcr/recordable_tcp_socket.rb
63
+ - lib/tcr/version.rb
64
+ - spec/spec_helper.rb
65
+ - spec/tcr_spec.rb
66
+ - tcr.gemspec
67
+ homepage: ''
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 1.8.25
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: TCR is a lightweight VCR for TCP sockets.
91
+ test_files:
92
+ - spec/spec_helper.rb
93
+ - spec/tcr_spec.rb