diff --git a/README.md b/README.md index bba2d39..77a8453 100644 --- a/README.md +++ b/README.md @@ -27,34 +27,38 @@ Or install it yourself as: ## Usage +### HTTP ```ruby require 'test/unit' require 'tcr' TCR.configure do |c| c.cassette_library_dir = 'fixtures/tcr_cassettes' - c.hook_tcp_ports = [25] + c.hook_tcp_ports = [80] end class TCRTest < Test::Unit::TestCase def test_example_dot_com - TCR.use_cassette('google_smtp') do - tcp_socket = TCPSocket.open("aspmx.l.google.com", 25) - io = Net::InternetMessageIO.new(tcp_socket) - assert_match /220 mx.google.com ESMTP/, io.readline + TCR.use_cassette('google') do + data = Net::HTTP.get("google.com", "/") + assert_match /301 Moved/, data end end end ``` -Run this test once, and TCR will record the tcp interactions to fixtures/tcr_cassettes/google_smtp.json. +Run this test once, and TCR will record the tcp interactions to fixtures/tcr_cassettes/google.json. ```json [ [ + [ + "write", + "GET / HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: google.com\r\n\r\n" + ], [ "read", - "220 mx.google.com ESMTP x3si2474860qas.18 - gsmtp\r\n" + "HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.google.com/\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Sun, 08 Feb 2015 02:42:29 GMT\r\nExpires: Tue, 10 Mar 2015 02:42:29 GMT\r\nCache-Control: public, max-age=2592000\r\nServer: gws\r\nContent-Length: 219\r\nX-XSS-Protection: 1; mode=block\r\nX-Frame-Options: SAMEORIGIN\r\nAlternate-Protocol: 80:quic,p=0.02\r\n\r\n\n301 Moved\n

301 Moved

\nThe document has moved\nhere.\r\n\r\n" ] ] ] @@ -66,10 +70,53 @@ You can disable TCR hooking TCPSocket ports for a given block via `turned_off`: ```ruby TCR.turned_off do - tcp_socket = TCPSocket.open("aspmx.l.google.com", 25) + data = Net::HTTP.get("google.com", "/") end ``` +### SMTP +You can use TCR to record any TCP interaction. Here we record the start of an SMTP session. **Note that many residential ISPs block port 25 outbound, so this may not work for you.** + +```ruby +require 'test/unit' +require 'tcr' + +TCR.configure do |c| + c.cassette_library_dir = 'fixtures/tcr_cassettes' + c.hook_tcp_ports = [25] +end + +class TCRTest < Test::Unit::TestCase + def test_example_dot_com + TCR.use_cassette('google_smtp') do + tcp_socket = TCPSocket.open("aspmx.l.google.com", 25) + io = Net::InternetMessageIO.new(tcp_socket) + assert_match /220 mx.google.com ESMTP/, io.readline + end + end +end +``` + +TCR will record the tcp interactions to fixtures/tcr_cassettes/google_smtp.json. + +```json +[ + [ + [ + "read", + "220 mx.google.com ESMTP x3si2474860qas.18 - gsmtp\r\n" + ] + ] +] +``` + +## Configuration +TCR accepts the following configuration parameters: +* **cassette_library_directory**: the directory, relative to your current directory, to save and read recordings from +* **hook_tcp_ports**: the TCP ports that will be intercepted for recording and playback +* **block_for_reads**: when reading data from a cassette, whether TCR should wait for matching "write" data to be written to the socket before allowing a read +* **recording_format**: the format of the cassettes. Can be :json, :yaml, :bson, or :msgpack + ## Contributing 1. Fork it diff --git a/lib/tcr.rb b/lib/tcr.rb index a93c6e5..d10272c 100644 --- a/lib/tcr.rb +++ b/lib/tcr.rb @@ -5,6 +5,9 @@ require "tcr/version" require "socket" require "json" +require "yaml" +require "bson" +require "msgpack" module TCR @@ -39,7 +42,7 @@ def save_session def use_cassette(name, options = {}, &block) raise ArgumentError, "`TCR.use_cassette` requires a block." unless block - TCR.cassette = Cassette.new(name) + TCR.cassette = Cassette.get_cassette(name, configuration.recording_format) yield TCR.cassette.save TCR.cassette = nil diff --git a/lib/tcr/cassette.rb b/lib/tcr/cassette.rb index 949bf32..f003e3a 100644 --- a/lib/tcr/cassette.rb +++ b/lib/tcr/cassette.rb @@ -8,7 +8,7 @@ def initialize(name) if File.exists?(filename) @recording = false @contents = File.open(filename) { |f| f.read } - @sessions = JSON.parse(@contents) + @sessions = parse else @recording = true @sessions = [] @@ -31,14 +31,102 @@ def next_session def save if recording? - File.open(filename, "w") { |f| f.write(JSON.pretty_generate(@sessions)) } + File.open(filename, "w") { |f| f.write(dump) } end end + def self.get_cassette(name, recording_format) + if recording_format == :json + JSONCassette.new(name) + elsif recording_format == :yaml + YAMLCassette.new(name) + elsif recording_format == :bson + BSONCassette.new(name) + elsif recording_format == :msgpack + MsgpackCassette.new(name) + else + raise TCR::FormatError.new + end + end + end + + class JSONCassette < Cassette + def parse + JSON.parse(@contents) + end + + def dump + JSON.pretty_generate(@sessions) + end + protected def filename "#{TCR.configuration.cassette_library_dir}/#{name}.json" end end + + class YAMLCassette < Cassette + def parse + YAML.load(@contents) + end + + def dump + YAML.dump(@sessions) + end + + protected + + def filename + "#{TCR.configuration.cassette_library_dir}/#{name}.yaml" + end + end + + class BSONCassette < Cassette + def parse + data = Array.from_bson(StringIO.new(@contents)) + self.class.debinaryize(data) + end + + def dump + self.class.binaryize(@sessions).to_bson + end + + def self.binaryize(data) + if Array === data + data.map { |item| binaryize(item) } + elsif String === data + BSON::Binary.new(data) + end + end + + def self.debinaryize(data) + if Array === data + data.map { |item| debinaryize(item) } + elsif BSON::Binary === data + data.data + end + end + + protected + def filename + "#{TCR.configuration.cassette_library_dir}/#{name}.bson" + end + end + + class MsgpackCassette < Cassette + def parse + MessagePack.unpack(@contents) + end + + def dump + @sessions.to_msgpack + end + + protected + + def filename + "#{TCR.configuration.cassette_library_dir}/#{name}.msgpack" + end + end end diff --git a/lib/tcr/configuration.rb b/lib/tcr/configuration.rb index b60aada..1d642b4 100644 --- a/lib/tcr/configuration.rb +++ b/lib/tcr/configuration.rb @@ -1,6 +1,6 @@ module TCR class Configuration - attr_accessor :cassette_library_dir, :hook_tcp_ports, :block_for_reads + attr_accessor :cassette_library_dir, :hook_tcp_ports, :block_for_reads, :recording_format def initialize reset_defaults! @@ -10,6 +10,7 @@ def reset_defaults! @cassette_library_dir = "fixtures/tcr_cassettes" @hook_tcp_ports = [] @block_for_reads = false + @recording_format = :json end end end diff --git a/lib/tcr/errors.rb b/lib/tcr/errors.rb index c5bb897..bb1cabd 100644 --- a/lib/tcr/errors.rb +++ b/lib/tcr/errors.rb @@ -1,5 +1,6 @@ module TCR class TCRError < StandardError; end + class FormatError < TCRError; end class NoCassetteError < TCRError; end class NoMoreSessionsError < TCRError; end class DirectionMismatchError < TCRError; end diff --git a/spec/fixtures/binary_data.bson b/spec/fixtures/binary_data.bson new file mode 100644 index 0000000..c7cc1fc Binary files /dev/null and b/spec/fixtures/binary_data.bson differ diff --git a/spec/fixtures/binary_data.msgpack b/spec/fixtures/binary_data.msgpack new file mode 100644 index 0000000..6a3009e Binary files /dev/null and b/spec/fixtures/binary_data.msgpack differ diff --git a/spec/fixtures/binary_data.yaml b/spec/fixtures/binary_data.yaml new file mode 100644 index 0000000..c274ef5 --- /dev/null +++ b/spec/fixtures/binary_data.yaml @@ -0,0 +1,164 @@ +--- +- - - write + - "GET /cbzcache/3rdparty/terminal.png HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: + */*\r\nUser-Agent: Ruby\r\nHost: c.cyberciti.biz\r\n\r\n" + - - read + - !binary |- + SFRUUC8xLjEgMjAwIE9LDQpDb250ZW50LVR5cGU6IGltYWdlL3BuZw0KQ29u + dGVudC1MZW5ndGg6IDY0NDINCkNvbm5lY3Rpb246IGtlZXAtYWxpdmUNClNl + cnZlcjogbmdpbngNCkRhdGU6IFR1ZSwgMTggTm92IDIwMTQgMDE6Mjk6NDIg + R01UDQpYLVdob206IGwxLW5ldC1pcHY2dA0KRXhwaXJlczogTW9uLCAwNyBO + b3YgMjAxNiAwMToyOTo0MiBHTVQNCkNhY2hlLUNvbnRyb2w6IG1heC1hZ2U9 + NjIyMDgwMDANCkFjY2VwdC1SYW5nZXM6IGJ5dGVzDQpFVGFnOiAiMzEyNzQy + Mzg3OCINCkxhc3QtTW9kaWZpZWQ6IFdlZCwgMTQgSmFuIDIwMDkgMTg6NDc6 + NDAgR01UDQpYLUdhbGF4eTogQW5kcm9tZWRhLTINCkFnZTogNzE1Mzk5NQ0K + WC1DYWNoZTogSGl0IGZyb20gY2xvdWRmcm9udA0KVmlhOiAxLjEgOWYyMTk1 + MmQzM2Q5MTI2Njg5MGYyYWIzNGI4NDlhOWQuY2xvdWRmcm9udC5uZXQgKENs + b3VkRnJvbnQpDQpYLUFtei1DZi1JZDogYlZuVDRxMVQ5RmVkOWx0N3ZCUTZs + dTVRem5ILWJFeE8wYnpISTBYSnhvZk5DUDdsT2JUNGtnPT0NCg0KiVBORw0K + GgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABmJLR0QA/wD/AP+gvaeT + AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1wEDCh8dT/Hn+AAAGLdJ + REFUeNrtXXtQVOfdfvbs2V12WVhEhMod0Ri0KgpyMVqRYKoNKjZ2HHVs1TaJ + U5OxTSpO800SY61TW6cmf3xt0mha05jUiMkksYTPGDUKCOIFowLWiCA3QQ2w + y8XdZc9+f7hne/bsue+C2p7fzM5ezmXPe57n/d3e3/seQBVVVFFFFVVUUUUV + VVRRRRVVVFFFFVVUUUUVVVRRRZX/WNGI7bB69erpGo1mjUajySMIYqp6yx5M + cblcFymKOjk4OPjuwYMHzwEYAuBWTIBVq1bNIElyF0EQ3zMYDEhNTUV4eDji + 4+PvSwPdbvd/NcBC7W9ra4PVakVjYyPsdjuGhoYqu7u7Nx86dKgGgBMAJYcA + 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 + +/ZtHDhwQPD6u7q6EBsbi+XLl6OkpMTr + - - read + - !binary |- + jPHF/7T87ne/Q35+PoB7T0l55pln0NXVhffee8977M6dO7F48WKf/0tOTsb6 + 9etRUFCAwsJC9Pb2+mkAOhyUYgICzQMoNgFsZtLS2dmJjo4OXLt2DcXFxTh4 + 8CA2bdrkpypLS0uxdOlSTJkyBfPmzUNpaSl0Oh3Wrl3r3WfevHkAgB07dmDO + nDnIycnB2rVrcfz4cb9eMnr0aCxbtgxz587Fhx9+CAB48sknRTXAO++8AwB4 + 9tlncfjwYXz88cfYvn07CgsLvd43U3sxTV1hYSGysrKwb98+APeWdKf3X758 + ORYvXoyWlhasX78e6enpeOSRR1BYWIiamhqMHz8ev/jFLzg7EF+n4ivECSQT + SCiNAqRUBun1ejz22GMoLCz0s3kvvPACGhoa4HK50NXVhW3btnk1AX0T6d44 + c+ZMrFq1CpmZmaivr8fmzZv9CPD73/8e7e3tcDqd2Lt3r7e3iRHg888/x3PP + PYfy8nIMDg4iPj4e8+fPx5YtW/Dee+8hKiqKU1X/9re/RVtbGxwOB3bv3g3g + 3mLO9H4/+tGPAAC//OUvcfToUfT19cHlcqGurg7PPfccAGDBggWC9p7P6R6W + sYBgmYD4+Hjv40pTUlJQXFyMrVu3wmAwYO/evd7yszVr1mDRokVITk72WVmE + vuEajQY7duzA1q1bkZ+f71W3LS0tKC4uRlNTk8//NjU1eY+jH2ppNBoFZyTR + N+fChQuora0FQRBISEjA1KlTsWrVKowfPx4bN27Eli1b/CKBxsZGrxdPE5X2 + deg8AQCUlJTw/n9cXBynE8hnAvgSXgFHAYEUhbAbQH+nKArXr1/HSy+9BAD4 + 6U9/6t1/8+bNKC4uRlpamt+yMvTzbjUaDU6ePIknn3wSmzdvxgcffICuri4k + JCTg17/+tZ8GYEcCSqID4N5U7EOHDuGFF14AAD/HlD2ELKSuxYQ2MVJNAJcT + eF/mBgpdLNtW0T0wMjLS+9uSJUsAAJs2bUJlZSUGBgZgNptx8uRJvxDLbrfj + xIkTOHnyJN5//3189tlnmDRpkt+NY9YY8OUH5BSBDAwM+PRqtgZgJ4LYv1+7 + dg2TJ0/GkiVLcPXqVZ80MfMz+/qYHUhqscuI5gG48txcbNVqtUhNTfXa9qtX + r3q30Q+pdjqdcDqdiI+Px8svv+zXa19//XVkZ2fDZDIhNDQUc+bM8Q68iPV0 + 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 + /w8NO4Xb+BTdJAAAAABJRU5ErkJggg== diff --git a/spec/fixtures/block_for_reads.bson b/spec/fixtures/block_for_reads.bson new file mode 100644 index 0000000..558158a Binary files /dev/null and b/spec/fixtures/block_for_reads.bson differ diff --git a/spec/fixtures/block_for_reads.msgpack b/spec/fixtures/block_for_reads.msgpack new file mode 100644 index 0000000..c328465 --- /dev/null +++ b/spec/fixtures/block_for_reads.msgpack @@ -0,0 +1,2 @@ +‘’’¥write¦hello +’¤read§world! diff --git a/spec/fixtures/block_for_reads.yaml b/spec/fixtures/block_for_reads.yaml new file mode 100644 index 0000000..f6e7ad1 --- /dev/null +++ b/spec/fixtures/block_for_reads.yaml @@ -0,0 +1,7 @@ +--- +- - - write + - | + hello + - - read + - | + world! diff --git a/spec/fixtures/google_https.bson b/spec/fixtures/google_https.bson new file mode 100644 index 0000000..9fae4f5 Binary files /dev/null and b/spec/fixtures/google_https.bson differ diff --git a/spec/fixtures/google_https.msgpack b/spec/fixtures/google_https.msgpack new file mode 100644 index 0000000..37270dc Binary files /dev/null and b/spec/fixtures/google_https.msgpack differ diff --git a/spec/fixtures/google_https.yaml b/spec/fixtures/google_https.yaml new file mode 100644 index 0000000..e6131f7 --- /dev/null +++ b/spec/fixtures/google_https.yaml @@ -0,0 +1,90 @@ +--- +- - - write + - "GET / HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: + */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.google.com\r\n\r\n" + - - read + - "HTTP/1.1 200 OK\r\nDate: Thu, 15 May 2014 15:04:03 GMT\r\nExpires: -1\r\nCache-Control: + private, max-age=0\r\nContent-Type: text/html; charset=ISO-8859-1\r\nSet-Cookie: + PREF=ID=46c9c47b277ecf04:FF=0:TM=1400166243:LM=1400166243:S=godo17lIPQRSk5e6; + expires=Sat, 14-May-2016 15:04:03 GMT; path=/; domain=.google.com\r\nSet-Cookie: + NID=67=hkRuWOEJ-mwgTBbt5NpMcYlD7G38o1tTA_mF_l2CvgY9BPVHzf3wom6KaSnvy5Ln6EqfhpD5t0KSia4b-yBj0AUFJbjfZpf_6487tcmyQMw9PtdewdXO1ZtMDD7McNeB; + expires=Fri, 14-Nov-2014 15:04:03 GMT; path=/; domain=.google.com; HttpOnly\r\nP3P: + CP=\"This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 + for more info.\"\r\nServer: gws\r\nX-XSS-Protection: 1; mode=block\r\nX-Frame-Options: + SAMEORIGIN\r\nAlternate-Protocol: 443:quic\r\nConnection: close\r\n\r\nGoogle
Search Images Maps Play YouTube News Gmail Drive More »
Web History | Settings + | Sign in



 

Advanced searchLanguage tools

© 2013 - Privacy & Terms

diff --git a/spec/fixtures/google_imap.bson b/spec/fixtures/google_imap.bson new file mode 100644 index 0000000..2d7fe3d Binary files /dev/null and b/spec/fixtures/google_imap.bson differ diff --git a/spec/fixtures/google_imap.msgpack b/spec/fixtures/google_imap.msgpack new file mode 100644 index 0000000..e4303fa Binary files /dev/null and b/spec/fixtures/google_imap.msgpack differ diff --git a/spec/fixtures/google_imap.yaml b/spec/fixtures/google_imap.yaml new file mode 100644 index 0000000..21d8e62 --- /dev/null +++ b/spec/fixtures/google_imap.yaml @@ -0,0 +1,46 @@ +--- +- - - read + - "* OK Gimap ready for requests from 72.16.218.22 n58mb218005052yhi\r\n" + - - write + - RUBY0001 LOGIN + - - write + - " " + - - write + - ben.olive@example.net + - - write + - " " + - - write + - password + - - write + - "\r\n" + - - read + - "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 + UIDPLUS COMPRESS=DEFLATE ENABLE MOVE CONDSTORE ESEARCH\r\n" + - - read + - "RUBY0001 OK ben.olive@example.net Ben Olive authenticated (Success)\r\n" + - - write + - RUBY0002 EXAMINE + - - write + - " " + - - write + - INBOX + - - write + - "\r\n" + - - read + - "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen $Phishing $NotPhishing)\r\n" + - - read + - "* OK [PERMANENTFLAGS ()] Flags permitted.\r\n" + - - read + - "* OK [UIDVALIDITY 1] UIDs valid.\r\n" + - - read + - "* 206 EXISTS\r\n" + - - read + - "* 0 RECENT\r\n" + - - read + - "* OK [UIDNEXT 5504] Predicted next UID.\r\n" + - - read + - "* OK [HIGHESTMODSEQ 609503]\r\n" + - - read + - "RUBY0002 OK [READ-ONLY] INBOX selected. (Success)\r\n" + - - read + - diff --git a/spec/fixtures/google_smtp.bson b/spec/fixtures/google_smtp.bson new file mode 100644 index 0000000..e26075a Binary files /dev/null and b/spec/fixtures/google_smtp.bson differ diff --git a/spec/fixtures/google_smtp.msgpack b/spec/fixtures/google_smtp.msgpack new file mode 100644 index 0000000..947a2d1 Binary files /dev/null and b/spec/fixtures/google_smtp.msgpack differ diff --git a/spec/fixtures/google_smtp.yaml b/spec/fixtures/google_smtp.yaml new file mode 100644 index 0000000..a24dd54 --- /dev/null +++ b/spec/fixtures/google_smtp.yaml @@ -0,0 +1,3 @@ +--- +- - - read + - "220 mx.google.com ESMTP x3si2474860qas.18 - gsmtp\r\n" diff --git a/spec/fixtures/multitest-smtp.bson b/spec/fixtures/multitest-smtp.bson new file mode 100644 index 0000000..786a0a8 Binary files /dev/null and b/spec/fixtures/multitest-smtp.bson differ diff --git a/spec/fixtures/multitest-smtp.msgpack b/spec/fixtures/multitest-smtp.msgpack new file mode 100644 index 0000000..097ace6 Binary files /dev/null and b/spec/fixtures/multitest-smtp.msgpack differ diff --git a/spec/fixtures/multitest-smtp.yaml b/spec/fixtures/multitest-smtp.yaml new file mode 100644 index 0000000..12322ba --- /dev/null +++ b/spec/fixtures/multitest-smtp.yaml @@ -0,0 +1,23 @@ +--- +- - - read + - "220 mx.google.com ESMTP d8si2472149qai.124 - gsmtp\r\n" + - - write + - "EHLO localhost\r\n" + - - read + - "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" + - - write + - "QUIT\r\n" + - - read + - "221 2.0.0 closing connection d8si2472149qai.124 - gsmtp\r\n" +- - - read + - "220 mta1579.mail.gq1.yahoo.com ESMTP YSmtpProxy service ready\r\n" + - - write + - "EHLO localhost\r\n" + - - read + - "250-mta1579.mail.gq1.yahoo.com\r\n250-8BITMIME\r\n250-SIZE 41943040\r\n250 + PIPELINING\r\n" + - - write + - "QUIT\r\n" + - - read + - "221 mta1579.mail.gq1.yahoo.com\r\n" diff --git a/spec/fixtures/multitest.bson b/spec/fixtures/multitest.bson new file mode 100644 index 0000000..7400ecc Binary files /dev/null and b/spec/fixtures/multitest.bson differ diff --git a/spec/fixtures/multitest.msgpack b/spec/fixtures/multitest.msgpack new file mode 100644 index 0000000..66f8cb8 Binary files /dev/null and b/spec/fixtures/multitest.msgpack differ diff --git a/spec/fixtures/multitest.yaml b/spec/fixtures/multitest.yaml new file mode 100644 index 0000000..c28604a --- /dev/null +++ b/spec/fixtures/multitest.yaml @@ -0,0 +1,5 @@ +--- +- - - read + - "220 mx.google.com ESMTP h5si2286277qec.54 - gsmtp\r\n" +- - - read + - "220 mta1009.mail.gq1.yahoo.com ESMTP YSmtpProxy service ready\r\n" diff --git a/spec/fixtures/starwars_telnet.bson b/spec/fixtures/starwars_telnet.bson new file mode 100644 index 0000000..b4b63c9 Binary files /dev/null and b/spec/fixtures/starwars_telnet.bson differ diff --git a/spec/fixtures/starwars_telnet.msgpack b/spec/fixtures/starwars_telnet.msgpack new file mode 100644 index 0000000..c664fbb Binary files /dev/null and b/spec/fixtures/starwars_telnet.msgpack differ diff --git a/spec/fixtures/starwars_telnet.yaml b/spec/fixtures/starwars_telnet.yaml new file mode 100644 index 0000000..73f6a1e --- /dev/null +++ b/spec/fixtures/starwars_telnet.yaml @@ -0,0 +1,3 @@ +--- +- - - read + - "\e[H\e[J\e[H\r\n\r\n\r\n\r\n\r\n\r\n " diff --git a/spec/tcr_spec.rb b/spec/tcr_spec.rb index a45940d..ae35a11 100644 --- a/spec/tcr_spec.rb +++ b/spec/tcr_spec.rb @@ -12,12 +12,6 @@ TCR.configuration.reset_defaults! end - around(:each) do |example| - File.unlink("test.json") if File.exists?("test.json") - example.run - File.unlink("test.json") if File.exists?("test.json") - end - describe ".configuration" do it "has a default cassette location configured" do TCR.configuration.cassette_library_dir.should == "fixtures/tcr_cassettes" @@ -28,7 +22,11 @@ end it "defaults to erroring on read/write mismatch access" do - TCR.configuration.block_for_reads.should be_false + TCR.configuration.block_for_reads.should be_falsey + end + + it "defaults to JSON" do + TCR.configuration.recording_format.should == :json end end @@ -39,6 +37,12 @@ }.to change{ TCR.configuration.cassette_library_dir }.from("fixtures/tcr_cassettes").to("some/dir") end + it "configures cassette format" do + expect { + TCR.configure { |c| c.recording_format = :yaml } + }.to change{ TCR.configuration.recording_format }.from(:json).to(:yaml) + end + it "configures tcp ports to hook" do expect { TCR.configure { |c| c.hook_tcp_ports = [25] } @@ -120,7 +124,7 @@ end end - describe ".use_cassette" do + shared_examples "a cassette" do before(:each) { TCR.configure { |c| c.hook_tcp_ports = [25] @@ -128,6 +132,12 @@ } } + around(:each) do |example| + File.unlink(test_file_name) if File.exists?(test_file_name) + example.run + File.unlink(test_file_name) if File.exists?(test_file_name) + end + it "requires a block to call" do expect { TCR.use_cassette("test") @@ -141,22 +151,25 @@ end it "creates a cassette file on use" do + TCR.configure { |c| c.hook_tcp_ports = [25] } expect { TCR.use_cassette("test") do - tcp_socket = TCPSocket.open("aspmx.l.google.com", 25) + tcp_socket = TCPSocket.open("google.com", 80) end - }.to change{ File.exists?("./test.json") }.from(false).to(true) + }.to change{ File.exists?(test_file_name) }.from(false).to(true) end it "records the tcp session data into the file" do + TCR.configure { |c| c.hook_tcp_ports = [80] } TCR.use_cassette("test") do - tcp_socket = TCPSocket.open("aspmx.l.google.com", 25) + tcp_socket = TCPSocket.open("google.com", 80) io = Net::InternetMessageIO.new(tcp_socket) + io.write("GET /\n") line = io.readline tcp_socket.close end - cassette_contents = File.open("test.json") { |f| f.read } - cassette_contents.include?("220 mx.google.com ESMTP").should == true + cassette_contents = File.open(test_file_name) { |f| f.read } + cassette_contents.include?("HTTP/1.0 200 OK").should == true end it "plays back tcp sessions without opening a real connection" do @@ -218,15 +231,14 @@ context "multiple connections" do it "records multiple sessions per cassette" do + TCR.configure { |c| c.hook_tcp_ports = [80] } TCR.use_cassette("test") do - smtp = Net::SMTP.start("aspmx.l.google.com", 25) - smtp.finish - smtp = Net::SMTP.start("mta6.am0.yahoodns.net", 25) - smtp.finish + Net::HTTP.get("google.com", "/") + Net::HTTP.get("yahoo.com", "/") end - cassette_contents = File.open("test.json") { |f| f.read } - cassette_contents.include?("google.com ESMTP").should == true - cassette_contents.include?("yahoo.com ESMTP").should == true + cassette_contents = File.open(test_file_name) { |f| f.read } + cassette_contents.include?("Host: google.com").should == true + cassette_contents.include?("Host: yahoo.com").should == true end it "plays back multiple sessions per cassette in order" do @@ -281,4 +293,98 @@ end end end + + shared_examples "a binary compatible cassette" do + before(:each) { + TCR.configure { |c| + c.hook_tcp_ports = [25] + c.cassette_library_dir = "." + } + } + + around(:each) do |example| + File.unlink(test_file_name) if File.exists?(test_file_name) + example.run + File.unlink(test_file_name) if File.exists?(test_file_name) + end + + it "handles recording binary data properly" do + TCR.configure { |c| c.hook_tcp_ports = [80] } + TCR.use_cassette("test") do + uri = URI("http://c.cyberciti.biz/cbzcache/3rdparty/terminal.png") + data = Net::HTTP.get(uri) + end + cassette_contents = File.open(test_file_name) { |f| f.read } + cassette_contents.include?("User-Agent: Ruby").should == true + end + + it "handles reading binary data properly" do + TCR.configure { |c| c.hook_tcp_ports = [80] } + TCR.use_cassette("spec/fixtures/binary_data") do + uri = URI("http://c.cyberciti.biz/cbzcache/3rdparty/terminal.png") + data = Net::HTTP.get(uri) + data.include?("PNG").should == true + end + end + end + + describe "a JSON cassette" do + let(:test_file_name) { "test.json" } + # we default to JSON so no need to specify recording_format + + it_behaves_like "a cassette" + end + + describe "a YAML cassette" do + let(:test_file_name) { "test.yaml" } + + before(:each) do + TCR.configure do |c| + c.recording_format = :yaml + end + end + + it_behaves_like "a cassette" + it_behaves_like "a binary compatible cassette" + end + + describe "a BSON cassette" do + let(:test_file_name) { "test.bson" } + + before(:each) do + TCR.configure do |c| + c.recording_format = :bson + end + end + + it_behaves_like "a cassette" + it_behaves_like "a binary compatible cassette" + end + + describe "a msgpack cassette" do + let(:test_file_name) { "test.msgpack" } + + before(:each) do + TCR.configure do |c| + c.recording_format = :msgpack + end + end + + it_behaves_like "a cassette" + it_behaves_like "a binary compatible cassette" + end + + describe "an invalid format cassette" do + before(:each) do + TCR.configure do |c| + c.recording_format = :foobar + end + end + + it "raises an error" do + expect { + TCR.use_cassette("test") {} + }.to raise_error(TCR::FormatError) + end + end end diff --git a/tcr.gemspec b/tcr.gemspec index abb60c2..8021376 100644 --- a/tcr.gemspec +++ b/tcr.gemspec @@ -17,6 +17,8 @@ Gem::Specification.new do |gem| gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] + gem.add_dependency "bson" + gem.add_dependency "msgpack" gem.add_development_dependency "rspec" gem.add_development_dependency "geminabox" end