require 'set' require 'rubygems' require 'facets/array' require 'facets/enumerable' require 'facets/kernel' require 'facets/string' require 'anise' require 'libs/misc' require 'libs/stem_facade' require 'libs/daemon' require 'libs/stem' require 'libs/ctcp' describe Autumn::CTCP do before :each do @ctcp = Autumn::CTCP.new @sender_hash = { :user => 'TestUser', :nick => 'TestNick', :host => 'ca.testhost.org' } end describe "with a mock stem" do before :each do @stem = mock('stem') end it "should parse CTCP requests in PRIVMSGs and broadcast two request-received methods" do @stem.should_receive(:broadcast).once.with(:ctcp_test_request, @ctcp, @stem, @sender_hash, []) @stem.should_receive(:broadcast).once.with(:ctcp_request_received, :test, @ctcp, @stem, @sender_hash, []) @ctcp.irc_privmsg_event @stem, @sender_hash, :message => "\01TEST\01" end it "should parse unencoded arguments in CTCP requests" do @stem.should_receive(:broadcast).once.with(:ctcp_test_request, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg2' ]) @stem.should_receive(:broadcast).once.with(:ctcp_request_received, :test, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg2' ]) @ctcp.irc_privmsg_event @stem, @sender_hash, :message => "\01TEST arg1 arg2\01" end it "should parse encoded arguments in CTCP requests" do Autumn::CTCP::ENCODED_COMMANDS << 'TEST' @stem.should_receive(:broadcast).once.with(:ctcp_test_request, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg 2' ]) @stem.should_receive(:broadcast).once.with(:ctcp_request_received, :test, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg 2' ]) @ctcp.irc_privmsg_event @stem, @sender_hash, :message => "\01TEST arg1 arg\\@2\01" end it "should correctly unescape all magic characters in its response" do Autumn::CTCP::ENCODED_COMMANDS << 'TEST' @stem.should_receive(:broadcast).once.with(:ctcp_test_request, @ctcp, @stem, @sender_hash, [ "\000\001\n\r \\" ]) @stem.should_receive(:broadcast).once.with(:ctcp_request_received, :test, @ctcp, @stem, @sender_hash, [ "\000\001\n\r \\" ]) @ctcp.irc_privmsg_event @stem, @sender_hash, :message => "\01TEST \\0\\1\\n\\r\\@\\\\\01" end it "should not parse space escapes in unencoded arguments in CTCP requests" do @stem.should_receive(:broadcast).once.with(:ctcp_test_request, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg\@2' ]) @stem.should_receive(:broadcast).once.with(:ctcp_request_received, :test, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg\@2' ]) @ctcp.irc_privmsg_event @stem, @sender_hash, :message => "\01TEST arg1 arg\\@2\01" end it "should parse CTCP responses in NOTICEs and broadcast two response-received methods" do @stem.should_receive(:broadcast).once.with(:ctcp_test_response, @ctcp, @stem, @sender_hash, []) @stem.should_receive(:broadcast).once.with(:ctcp_response_received, :test, @ctcp, @stem, @sender_hash, []) @ctcp.irc_notice_event @stem, @sender_hash, :message => "\01TEST\01" end it "should parse unencoded arguments in CTCP responses" do @stem.should_receive(:broadcast).once.with(:ctcp_test_response, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg2' ]) @stem.should_receive(:broadcast).once.with(:ctcp_response_received, :test, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg2' ]) @ctcp.irc_notice_event @stem, @sender_hash, :message => "\01TEST arg1 arg2\01" end it "should parse encoded arguments in CTCP responses" do Autumn::CTCP::ENCODED_COMMANDS << 'TEST' @stem.should_receive(:broadcast).once.with(:ctcp_test_response, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg 2' ]) @stem.should_receive(:broadcast).once.with(:ctcp_response_received, :test, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg 2' ]) @ctcp.irc_notice_event @stem, @sender_hash, :message => "\01TEST arg1 arg\\@2\01" end it "should not parse space escapes in unencoded arguments in CTCP responses" do @stem.should_receive(:broadcast).once.with(:ctcp_test_response, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg\@2' ]) @stem.should_receive(:broadcast).once.with(:ctcp_response_received, :test, @ctcp, @stem, @sender_hash, [ 'arg1', 'arg\@2' ]) @ctcp.irc_notice_event @stem, @sender_hash, :message => "\01TEST arg1 arg\\@2\01" end end describe "with an actual stem" do before :each do @ctcp.instance_variable_set(:@reply_queue, Hash.new { |h,k| h[k] = Array.new }) @stem = Autumn::Stem.new('irc.example.com', 'Example', :channel => '#example') #TODO proper way to stub this? @stem.instance_eval do def privmsg(*args) args end end @stem.add_listener @ctcp end it "should set the @ctcp variable in a stem when it's added as a listener to that stem" do @stem.instance_variable_get(:@ctcp).should eql(@ctcp) end it "should add a method of the form ctcp_* to the stem" do lambda { @stem.ctcp_action("#example") }.should_not raise_error(NoMethodError) end it "... which replies with a CTCP message" do @stem.ctcp_action("#example").should eql([ "#example", "\001ACTION\001" ]) end it "... ... that properly encodes arguments when appropriate" do @stem.ctcp_ping("#example", 'arg1', 'arg 2').should eql([ "#example", "\001PING arg1 arg\\@2\001" ]) end it "... ... ... escaping all magic characters" do @stem.ctcp_ping("#example", "\n\r \\\000\001").should eql([ "#example", "\001PING " + '\n\r\@\\\\\0\1' + "\001" ]) end it "... ... that does not encode arguments when appropriate" do @stem.ctcp_action("#example", "ABC 123").should eql([ "#example", "\001ACTION ABC 123\001" ]) end it "should add a method of the form ctcp_reply_* to the stem" do lambda { @stem.ctcp_reply_ping("Pinger") }.should_not raise_error(NoMethodError) end it "... which replies with a CTCP response" do @stem.ctcp_reply_ping("Pinger") reply_queue(@ctcp, @stem).shift.should == { :recipient => "Pinger", :message => "\001PING\001" } end it "... ... that properly encodes arguments when appropriate" do @stem.ctcp_reply_ping("Pinger", 'arg1', 'arg 2') reply_queue(@ctcp, @stem).shift.should == { :recipient => "Pinger", :message => "\001PING arg1 arg\\@2\001" } end it "... ... ... escaping all magic characters" do @stem.ctcp_reply_ping("Pinger", "\n\r \\\000\001") reply_queue(@ctcp, @stem).shift.should == { :recipient => "Pinger", :message => "\001PING " + '\n\r\@\\\\\0\1' + "\001" } end it "... ... that does not encode arguments when appropriate" do @stem.ctcp_reply_example("Tester", "ABC 123") reply_queue(@ctcp, @stem).shift.should == { :recipient => "Tester", :message => "\001EXAMPLE ABC 123\001" } end end describe "with a mock stem that records message intervals" do before :each do @stem = Object.new class << @stem attr :received def notice(*args) @received ||= Array.new @received << Time.now end def privmsg(*args) end def average_interval times = Array.new @received.each_by { |a, b| times << b - a if a and b } return times.sum/times.size.to_f end end @ctcp.added @stem end it "should queue replies and fire them at the default interval of 0.25 seconds" do 10.times { @stem.ctcp_reply_ping "Pinger", "ABC123" } sleep 3 @stem.average_interval.should be_close(0.25, 0.05) end it "should drop replies from the queue when the default maximum of 10 is exceeded" do Thread.exclusive { 15.times { @stem.ctcp_reply_ping "Pinger", "ABC123" } } sleep 4 @stem.received.size.should be_close(10, 1.5) end describe "with custom CTCP reply rate and queue length values" do before :each do @ctcp = Autumn::CTCP.new(:reply_rate => 0.5, :reply_queue_size => 5) @ctcp.added @stem end it "should queue replies and fire them at a custom interval" do 5.times { @stem.ctcp_reply_ping "Pinger", "ABC123" } sleep 3 @stem.average_interval.should be_close(0.5, 0.05) end it "should drop replies from the queue when a custom maximum is exceeded" do 15.times { @stem.ctcp_reply_ping "Pinger", "ABC123" } sleep 4 @stem.received.size.should be_close(5, 1.5) end end end it "should respond to CTCP VERSION requests" do @ctcp.should respond_to(:ctcp_version_request) end it "should respond to CTCP PING requests" do @ctcp.should respond_to(:ctcp_ping_request) end it "should respond to CTCP TIME requests" do @ctcp.should respond_to(:ctcp_time_request) end after :each do Autumn::CTCP::ENCODED_COMMANDS.delete 'TEST' end def reply_queue(ctcp, stem) ctcp.instance_variable_get(:@reply_queue)[stem] end end