tcr 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ module TCR
2
+ class Cassette
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+
8
+ if File.exists?(filename)
9
+ @recording = false
10
+ @contents = File.open(filename) { |f| f.read }
11
+ @sessions = JSON.parse(@contents)
12
+ else
13
+ @recording = true
14
+ @sessions = []
15
+ end
16
+ end
17
+
18
+ def recording?
19
+ @recording
20
+ end
21
+
22
+ def next_session
23
+ session = @sessions.shift
24
+ raise NoMoreSessionsError unless session
25
+ session
26
+ end
27
+
28
+ def append(session)
29
+ raise "Can't append session unless recording" unless recording?
30
+ @sessions << session
31
+ File.open(filename, "w") { |f| f.write(JSON.pretty_generate(@sessions)) }
32
+ end
33
+
34
+ protected
35
+
36
+ def filename
37
+ "#{TCR.configuration.cassette_library_dir}/#{name}.json"
38
+ end
39
+ end
40
+ end
data/lib/tcr/errors.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module TCR
2
2
  class TCRError < StandardError; end
3
3
  class NoCassetteError < TCRError; end
4
+ class NoMoreSessionsError < TCRError; end
4
5
  class DirectionMismatchError < TCRError; end
5
6
  end
@@ -1,28 +1,29 @@
1
1
  module TCR
2
2
  class RecordableTCPSocket
3
- attr_reader :live, :recording_file
4
- attr_accessor :recordings
3
+ attr_reader :live, :cassette
4
+ attr_accessor :recording
5
5
 
6
- def initialize(address, port, recording_file)
7
- @recording_file = recording_file
6
+ def initialize(address, port, cassette)
7
+ raise TCR::NoCassetteError.new unless TCR.cassette
8
8
 
9
- if File.exists?(recording_file)
10
- @live = false
11
- @recordings = JSON.parse(File.open(recording_file, "r") { |f| f.read })
12
- else
9
+ if cassette.recording?
13
10
  @live = true
14
- @recordings = []
15
11
  @socket = TCPSocket.real_open(address, port)
12
+ @recording = []
13
+ else
14
+ @live = false
15
+ @recording = cassette.next_session
16
16
  end
17
+ @cassette = cassette
17
18
  end
18
19
 
19
20
  def read_nonblock(bytes)
20
21
  if live
21
22
  data = @socket.read_nonblock(bytes)
22
- recordings << ["read", data]
23
+ recording << ["read", data]
23
24
  else
24
- direction, data = recordings.shift
25
- raise DirectionMismatchError("Expected to 'read' but next in recording was 'write'") unless direction == "read"
25
+ direction, data = recording.shift
26
+ raise TCR::DirectionMismatchError.new("Expected to 'read' but next in recording was 'write'") unless direction == "read"
26
27
  end
27
28
 
28
29
  data
@@ -31,10 +32,10 @@ module TCR
31
32
  def write(str)
32
33
  if live
33
34
  len = @socket.write(str)
34
- recordings << ["write", str]
35
+ recording << ["write", str]
35
36
  else
36
- direction, data = recordings.shift
37
- raise DirectionMismatchError("Expected to 'write' but next in recording was 'read'") unless direction == "write"
37
+ direction, data = recording.shift
38
+ raise TCR::DirectionMismatchError.new("Expected to 'write' but next in recording was 'read'") unless direction == "write"
38
39
  len = data.length
39
40
  end
40
41
 
@@ -58,7 +59,7 @@ module TCR
58
59
  def close
59
60
  if live
60
61
  @socket.close
61
- File.open(recording_file, "w") { |f| f.write(JSON.pretty_generate(recordings)) }
62
+ cassette.append(recording)
62
63
  end
63
64
  end
64
65
  end
data/lib/tcr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TCR
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/tcr.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require "tcr/cassette"
1
2
  require "tcr/configuration"
2
3
  require "tcr/errors"
3
4
  require "tcr/recordable_tcp_socket"
@@ -17,23 +18,23 @@ module TCR
17
18
  @configuration ||= Configuration.new
18
19
  end
19
20
 
20
- def current_cassette
21
- raise TCR::NoCassetteError unless @current_cassette
22
- @current_cassette
21
+ def cassette
22
+ @cassette
23
+ end
24
+
25
+ def cassette=(v)
26
+ @cassette = v
27
+ end
28
+
29
+ def save_session
23
30
  end
24
31
 
25
32
  def use_cassette(name, options = {}, &block)
26
33
  raise ArgumentError, "`TCR.use_cassette` requires a block." unless block
27
- set_cassette(name)
34
+ TCR.cassette = Cassette.new(name)
28
35
  yield
29
36
  @current_cassette = nil
30
37
  end
31
-
32
- protected
33
-
34
- def set_cassette(name)
35
- @current_cassette = "#{TCR.configuration.cassette_library_dir}/#{name}.json"
36
- end
37
38
  end
38
39
 
39
40
 
@@ -44,7 +45,7 @@ class TCPSocket
44
45
 
45
46
  def open(address, port)
46
47
  if TCR.configuration.hook_tcp_ports.include?(port)
47
- TCR::RecordableTCPSocket.new(address, port, TCR.current_cassette)
48
+ TCR::RecordableTCPSocket.new(address, port, TCR.cassette)
48
49
  else
49
50
  real_open(address, port)
50
51
  end
@@ -0,0 +1,8 @@
1
+ [
2
+ [
3
+ [
4
+ "read",
5
+ "220 mx.google.com ESMTP x3si2474860qas.18 - gsmtp\r\n"
6
+ ]
7
+ ]
8
+ ]
@@ -0,0 +1,46 @@
1
+ [
2
+ [
3
+ [
4
+ "read",
5
+ "220 mx.google.com ESMTP d8si2472149qai.124 - gsmtp\r\n"
6
+ ],
7
+ [
8
+ "write",
9
+ "EHLO localhost\r\n"
10
+ ],
11
+ [
12
+ "read",
13
+ "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"
14
+ ],
15
+ [
16
+ "write",
17
+ "QUIT\r\n"
18
+ ],
19
+ [
20
+ "read",
21
+ "221 2.0.0 closing connection d8si2472149qai.124 - gsmtp\r\n"
22
+ ]
23
+ ],
24
+ [
25
+ [
26
+ "read",
27
+ "220 mta1579.mail.gq1.yahoo.com ESMTP YSmtpProxy service ready\r\n"
28
+ ],
29
+ [
30
+ "write",
31
+ "EHLO localhost\r\n"
32
+ ],
33
+ [
34
+ "read",
35
+ "250-mta1579.mail.gq1.yahoo.com\r\n250-8BITMIME\r\n250-SIZE 41943040\r\n250 PIPELINING\r\n"
36
+ ],
37
+ [
38
+ "write",
39
+ "QUIT\r\n"
40
+ ],
41
+ [
42
+ "read",
43
+ "221 mta1579.mail.gq1.yahoo.com\r\n"
44
+ ]
45
+ ]
46
+ ]
@@ -0,0 +1,14 @@
1
+ [
2
+ [
3
+ [
4
+ "read",
5
+ "220 mx.google.com ESMTP h5si2286277qec.54 - gsmtp\r\n"
6
+ ]
7
+ ],
8
+ [
9
+ [
10
+ "read",
11
+ "220 mta1009.mail.gq1.yahoo.com ESMTP YSmtpProxy service ready\r\n"
12
+ ]
13
+ ]
14
+ ]
data/spec/tcr_spec.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "spec_helper"
2
2
  require "tcr"
3
3
  require "net/protocol"
4
+ require "net/smtp"
4
5
 
5
6
  describe TCR do
6
7
  before(:each) do
@@ -44,10 +45,10 @@ describe TCR do
44
45
  c.hook_tcp_ports = [25]
45
46
  c.cassette_library_dir = "."
46
47
  }
47
- File.unlink("./test.json") if File.exists?("./test.json")
48
+ File.unlink("test.json") if File.exists?("test.json")
48
49
  }
49
50
  after(:each) {
50
- File.unlink("./test.json") if File.exists?("./test.json")
51
+ File.unlink("test.json") if File.exists?("test.json")
51
52
  }
52
53
 
53
54
  it "requires a block to call" do
@@ -72,8 +73,65 @@ describe TCR do
72
73
  line = io.readline
73
74
  tcp_socket.close
74
75
  end
75
- file_contents = File.open("./test.json") { |f| f.read }
76
- file_contents.include?("220 mx.google.com ESMTP").should == true
76
+ cassette_contents = File.open("test.json") { |f| f.read }
77
+ cassette_contents.include?("220 mx.google.com ESMTP").should == true
78
+ end
79
+
80
+ it "plays back tcp sessions without opening a real connection" do
81
+ expect(TCPSocket).to_not receive(:real_open)
82
+
83
+ TCR.use_cassette("spec/fixtures/google_smtp") do
84
+ tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
85
+ io = Net::InternetMessageIO.new(tcp_socket)
86
+ line = io.readline.should include("220 mx.google.com ESMTP")
87
+ end
88
+ end
89
+
90
+ it "raises an error if the recording gets out of order (i.e., we went to write but it expected a read)" do
91
+ expect {
92
+ TCR.use_cassette("spec/fixtures/google_smtp") do
93
+ tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
94
+ io = Net::InternetMessageIO.new(tcp_socket)
95
+ io.write("hi")
96
+ end
97
+ }.to raise_error(TCR::DirectionMismatchError)
98
+ end
99
+
100
+
101
+ context "multiple connections" do
102
+ it "records multiple sessions per cassette" do
103
+ TCR.use_cassette("test") do
104
+ smtp = Net::SMTP.start("aspmx.l.google.com", 25)
105
+ smtp.finish
106
+ smtp = Net::SMTP.start("mta6.am0.yahoodns.net", 25)
107
+ smtp.finish
108
+ end
109
+ cassette_contents = File.open("test.json") { |f| f.read }
110
+ cassette_contents.include?("google.com ESMTP").should == true
111
+ cassette_contents.include?("yahoo.com ESMTP").should == true
112
+ end
113
+
114
+ it "plays back multiple sessions per cassette in order" do
115
+ TCR.use_cassette("spec/fixtures/multitest") do
116
+ tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
117
+ io = Net::InternetMessageIO.new(tcp_socket)
118
+ line = io.readline.should include("google.com ESMTP")
119
+
120
+ tcp_socket = TCPSocket.open("mta6.am0.yahoodns.net", 25)
121
+ io = Net::InternetMessageIO.new(tcp_socket)
122
+ line = io.readline.should include("yahoo.com ESMTP")
123
+ end
124
+ end
125
+
126
+ it "raises an error if you try to playback more sessions than you previously recorded" do
127
+ expect {
128
+ TCR.use_cassette("spec/fixtures/multitest-smtp") do
129
+ smtp = Net::SMTP.start("aspmx.l.google.com", 25)
130
+ smtp = Net::SMTP.start("mta6.am0.yahoodns.net", 25)
131
+ smtp = Net::SMTP.start("mta6.am0.yahoodns.net", 25)
132
+ end
133
+ }.to raise_error(TCR::NoMoreSessionsError)
134
+ end
77
135
  end
78
136
  end
79
137
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -57,10 +57,14 @@ files:
57
57
  - README.md
58
58
  - Rakefile
59
59
  - lib/tcr.rb
60
+ - lib/tcr/cassette.rb
60
61
  - lib/tcr/configuration.rb
61
62
  - lib/tcr/errors.rb
62
63
  - lib/tcr/recordable_tcp_socket.rb
63
64
  - lib/tcr/version.rb
65
+ - spec/fixtures/google_smtp.json
66
+ - spec/fixtures/multitest-smtp.json
67
+ - spec/fixtures/multitest.json
64
68
  - spec/spec_helper.rb
65
69
  - spec/tcr_spec.rb
66
70
  - tcr.gemspec
@@ -89,5 +93,8 @@ signing_key:
89
93
  specification_version: 3
90
94
  summary: TCR is a lightweight VCR for TCP sockets.
91
95
  test_files:
96
+ - spec/fixtures/google_smtp.json
97
+ - spec/fixtures/multitest-smtp.json
98
+ - spec/fixtures/multitest.json
92
99
  - spec/spec_helper.rb
93
100
  - spec/tcr_spec.rb