diff --git a/lib/tcr.rb b/lib/tcr.rb index af42e6c..4a6b741 100644 --- a/lib/tcr.rb +++ b/lib/tcr.rb @@ -39,7 +39,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 = TCR::Cassette.build(name, configuration.serialize_with) yield TCR.cassette.save ensure diff --git a/lib/tcr/cassette.rb b/lib/tcr/cassette.rb index 949bf32..270d17c 100644 --- a/lib/tcr/cassette.rb +++ b/lib/tcr/cassette.rb @@ -1,44 +1,16 @@ module TCR - class Cassette - attr_reader :name - - def initialize(name) - @name = name - - if File.exists?(filename) - @recording = false - @contents = File.open(filename) { |f| f.read } - @sessions = JSON.parse(@contents) - else - @recording = true - @sessions = [] - end - end - - def recording? - @recording - end - - def next_session - if recording? - @sessions << [] - @sessions.last + module Cassette + def self.build(name, type) + case type + when :gzip + TCR::Cassette::Gzip.new(name) else - raise NoMoreSessionsError if @sessions.empty? - @sessions.shift + TCR::Cassette::JSON.new(name) end end - - def save - if recording? - File.open(filename, "w") { |f| f.write(JSON.pretty_generate(@sessions)) } - end - end - - protected - - def filename - "#{TCR.configuration.cassette_library_dir}/#{name}.json" - end end end + +require "tcr/cassette/base" +require "tcr/cassette/json" +require "tcr/cassette/gzip" diff --git a/lib/tcr/cassette/base.rb b/lib/tcr/cassette/base.rb new file mode 100644 index 0000000..28646e3 --- /dev/null +++ b/lib/tcr/cassette/base.rb @@ -0,0 +1,41 @@ +module TCR + module Cassette + class Base + attr_reader :name + + def initialize(name) + @name = name + + if File.exist?(filename) + @recording = false + @sessions = deserialize + else + @recording = true + @sessions = [] + end + end + + def recording? + @recording + end + + def next_session + if recording? + @sessions << [] + @sessions.last + else + raise NoMoreSessionsError if @sessions.empty? + @sessions.shift + end + end + + def save + serialize(@sessions) if recording? + end + + def filename + File.join(TCR.configuration.cassette_library_dir, "#{name}.#{extension}") + end + end + end +end diff --git a/lib/tcr/cassette/gzip.rb b/lib/tcr/cassette/gzip.rb new file mode 100644 index 0000000..22d82ee --- /dev/null +++ b/lib/tcr/cassette/gzip.rb @@ -0,0 +1,25 @@ +module TCR + module Cassette + class Gzip < Base + def initialize(_name) + super + end + + def extension + :gz + end + + def serialize(data) + Zlib::GzipWriter.open(filename) do |gz| + gz.write(Marshal.dump(data)) + end + end + + def deserialize + Zlib::GzipReader.open(filename) do |gz| + return Marshal.load(gz.read) + end + end + end + end +end diff --git a/lib/tcr/cassette/json.rb b/lib/tcr/cassette/json.rb new file mode 100644 index 0000000..b88ad5e --- /dev/null +++ b/lib/tcr/cassette/json.rb @@ -0,0 +1,21 @@ +module TCR + module Cassette + class JSON < Base + def initialize(_name) + super + end + + def extension + :json + end + + def serialize(data) + File.binwrite(filename, ::JSON.pretty_generate(data)) + end + + def deserialize + ::JSON.parse(File.binread(filename)) + end + end + end +end diff --git a/lib/tcr/configuration.rb b/lib/tcr/configuration.rb index b60aada..17efc06 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, :serialize_with def initialize reset_defaults! @@ -10,6 +10,7 @@ def reset_defaults! @cassette_library_dir = "fixtures/tcr_cassettes" @hook_tcp_ports = [] @block_for_reads = false + @serialize_with = :json end end end diff --git a/lib/tcr/recordable_tcp_socket.rb b/lib/tcr/recordable_tcp_socket.rb index 0de0793..9e86c87 100644 --- a/lib/tcr/recordable_tcp_socket.rb +++ b/lib/tcr/recordable_tcp_socket.rb @@ -5,6 +5,16 @@ module TCR class RecordableTCPSocket + module MockedIO + def self.wait_readable(_timeout) + true + end + + def self.wait_writable(_timeout) + true + end + end + attr_reader :live, :socket attr_accessor :recording @@ -46,6 +56,8 @@ def write(str) def to_io if live @socket.to_io + else + MockedIO end end diff --git a/spec/fixtures/google_http.gz b/spec/fixtures/google_http.gz new file mode 100644 index 0000000..b92d15b Binary files /dev/null and b/spec/fixtures/google_http.gz differ diff --git a/spec/tcr_spec.rb b/spec/tcr_spec.rb index d1f1996..343fc59 100644 --- a/spec/tcr_spec.rb +++ b/spec/tcr_spec.rb @@ -31,7 +31,11 @@ it "defaults to erroring on read/write mismatch access" do TCR.configuration.block_for_reads.should be_falsey end - end + + it "has a default cassette type" do + TCR.configuration.serialize_with.should == :json + end + end describe ".configure" do it "configures cassette location" do @@ -51,6 +55,12 @@ TCR.configure { |c| c.block_for_reads = true } }.to change{ TCR.configuration.block_for_reads }.from(false).to(true) end + + it "configures cassette type" do + expect { + TCR.configure { |c| c.serialize_with = :gzip } + }.to change{ TCR.configuration.serialize_with }.from(:json).to(:gzip) + end end it "raises an error if you connect to a hooked port without using a cassette" do @@ -225,6 +235,20 @@ }.not_to raise_error end + it "supports gzip cassettes" do + TCR.configure { |c| + c.hook_tcp_ports = [80] + c.serialize_with = :gzip + } + + expect { + TCR.use_cassette("spec/fixtures/google_http") do + body = Net::HTTP.get(URI('http://google.com/')) + expect(body).to_not be_empty + end + }.not_to raise_error + end + it "can stub the full session of a real server accepting a real email over SMTPS with STARTTLS" do TCR.configure { |c| c.hook_tcp_ports = [587]