robut 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,10 +2,10 @@ require 'yaml'
2
2
 
3
3
  # A store backed by a persistent on-disk yaml file.
4
4
  class Robut::Storage::YamlStore < Robut::Storage::Base
5
-
5
+
6
6
  class << self
7
7
 
8
- # The path to the file this store will persist to.
8
+ # The path to the file this store will persist to.
9
9
  attr_reader :file
10
10
 
11
11
  # Sets the path to the file this store will persist to, and forces
@@ -16,7 +16,7 @@ class Robut::Storage::YamlStore < Robut::Storage::Base
16
16
  @file
17
17
  end
18
18
 
19
- # Sets the key +k+ to the value +v+
19
+ # Sets the key +k+ to the value +v+
20
20
  def []=(k, v)
21
21
  internal[k] = v
22
22
  persist!
@@ -32,20 +32,26 @@ class Robut::Storage::YamlStore < Robut::Storage::Base
32
32
 
33
33
  # The internal in-memory representation of the yaml file
34
34
  def internal
35
- @internal ||= begin
36
- YAML.load_file(file) rescue {}
37
- end
35
+ @internal ||= load_from_file
38
36
  end
39
-
37
+
40
38
  # Persists the data in this store to disk. Throws an exception if
41
39
  # we don't have a file set.
42
40
  def persist!
43
41
  raise "Robut::Storage::YamlStore.file must be set" unless file
44
- f = File.open(file, "w")
45
- f.puts internal.to_yaml
46
- f.close
42
+ File.open(file, "w") do |f|
43
+ f.puts internal.to_yaml
44
+ end
47
45
  end
48
-
46
+
47
+ def load_from_file
48
+ begin
49
+ store = YAML.load_file(file)
50
+ rescue Errno::ENOENT
51
+ end
52
+
53
+ store || Hash.new
54
+ end
55
+
49
56
  end
50
-
51
57
  end
data/lib/robut/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Robut # :nodoc:
2
2
  # Robut's version number.
3
- VERSION = "0.2.1"
3
+ VERSION = "0.3.0"
4
4
  end
@@ -0,0 +1 @@
1
+ <?xml version="1.0"?><xml_api_reply version="1"><weather module_id="0" tab_id="0" mobile_row="0" mobile_zipped="1" row="0" section="0" ><problem_cause data=""/></weather></xml_api_reply>
@@ -0,0 +1 @@
1
+ <?xml version="1.0"?><xml_api_reply version="1"><weather module_id="0" tab_id="0" mobile_row="0" mobile_zipped="1" row="0" section="0" ><forecast_information><city data="Las Vegas, NV"/><postal_code data="Las Vegas"/><latitude_e6 data=""/><longitude_e6 data=""/><forecast_date data="2011-05-31"/><current_date_time data="2011-05-31 19:50:12 +0000"/><unit_system data="US"/></forecast_information><current_conditions><condition data="Mostly Cloudy"/><temp_f data="83"/><temp_c data="28"/><humidity data="Humidity: 8%"/><icon data="http://g0.gstatic.com/images/icons/onebox/weather_mostlycloudy-40.gif"/><wind_condition data="Wind: S at 9 mph"/></current_conditions><forecast_conditions><day_of_week data="Tue"/><low data="66"/><high data="91"/><icon data="http://g0.gstatic.com/images/icons/onebox/weather_partlycloudy-40.gif"/><condition data="Partly Cloudy"/></forecast_conditions><forecast_conditions><day_of_week data="Wed"/><low data="60"/><high data="86"/><icon data="http://g0.gstatic.com/images/icons/onebox/weather_partlycloudy-40.gif"/><condition data="Partly Cloudy"/></forecast_conditions><forecast_conditions><day_of_week data="Thu"/><low data="63"/><high data="80"/><icon data="http://g0.gstatic.com/images/icons/onebox/weather_sunny-40.gif"/><condition data="Sunny"/></forecast_conditions><forecast_conditions><day_of_week data="Fri"/><low data="70"/><high data="84"/><icon data="http://g0.gstatic.com/images/icons/onebox/weather_partlycloudy-40.gif"/><condition data="Partly Cloudy"/></forecast_conditions></weather></xml_api_reply>
@@ -0,0 +1 @@
1
+ <?xml version="1.0"?><xml_api_reply version="1"><weather module_id="0" tab_id="0" mobile_row="0" mobile_zipped="1" row="0" section="0" ><forecast_information><city data="Seattle, WA"/><postal_code data="Seattle"/><latitude_e6 data=""/><longitude_e6 data=""/><forecast_date data="2011-05-23"/><current_date_time data="2011-05-23 22:52:57 +0000"/><unit_system data="US"/></forecast_information><current_conditions><condition data="Mostly Cloudy"/><temp_f data="58"/><temp_c data="14"/><humidity data="Humidity: 45%"/><icon data="/ig/images/weather/mostly_cloudy.gif"/><wind_condition data="Wind: E at 7 mph"/></current_conditions><forecast_conditions><day_of_week data="Mon"/><low data="48"/><high data="59"/><icon data="/ig/images/weather/partly_cloudy.gif"/><condition data="Partly Cloudy"/></forecast_conditions><forecast_conditions><day_of_week data="Tue"/><low data="51"/><high data="67"/><icon data="/ig/images/weather/partly_cloudy.gif"/><condition data="Partly Cloudy"/></forecast_conditions><forecast_conditions><day_of_week data="Wed"/><low data="49"/><high data="61"/><icon data="/ig/images/weather/rain.gif"/><condition data="Showers"/></forecast_conditions><forecast_conditions><day_of_week data="Thu"/><low data="49"/><high data="57"/><icon data="/ig/images/weather/rain.gif"/><condition data="Showers"/></forecast_conditions></weather></xml_api_reply>
@@ -0,0 +1 @@
1
+ <?xml version="1.0"?><xml_api_reply version="1"><weather module_id="0" tab_id="0" mobile_row="0" mobile_zipped="1" row="0" section="0" ><forecast_information><city data="Tacoma, WA"/><postal_code data="tacoma"/><latitude_e6 data=""/><longitude_e6 data=""/><forecast_date data="2011-05-23"/><current_date_time data="2011-05-23 22:46:28 +0000"/><unit_system data="US"/></forecast_information><current_conditions><condition data="Cloudy"/><temp_f data="60"/><temp_c data="16"/><humidity data="Humidity: 51%"/><icon data="/ig/images/weather/cloudy.gif"/><wind_condition data="Wind: N at 4 mph"/></current_conditions><forecast_conditions><day_of_week data="Mon"/><low data="45"/><high data="61"/><icon data="/ig/images/weather/rain.gif"/><condition data="Showers"/></forecast_conditions><forecast_conditions><day_of_week data="Tue"/><low data="50"/><high data="67"/><icon data="/ig/images/weather/partly_cloudy.gif"/><condition data="Partly Cloudy"/></forecast_conditions><forecast_conditions><day_of_week data="Wed"/><low data="48"/><high data="59"/><icon data="/ig/images/weather/rain.gif"/><condition data="Showers"/></forecast_conditions><forecast_conditions><day_of_week data="Thu"/><low data="48"/><high data="57"/><icon data="/ig/images/weather/rain.gif"/><condition data="Showers"/></forecast_conditions></weather></xml_api_reply>
@@ -1,6 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class SimplePlugin
4
+ include Robut::Plugin
4
5
  attr_accessor :run
5
6
 
6
7
  def initialize(*args)
@@ -13,7 +14,9 @@ class SimplePlugin
13
14
  end
14
15
  end
15
16
 
16
- class ReplyToUserPlugin < Robut::Plugin::Base
17
+ class ReplyToUserPlugin
18
+ include Robut::Plugin
19
+
17
20
  def initialize(*args)
18
21
  super(*args)
19
22
  end
@@ -23,7 +26,9 @@ class ReplyToUserPlugin < Robut::Plugin::Base
23
26
  end
24
27
  end
25
28
 
26
- class ReplyToRoomPlugin < Robut::Plugin::Base
29
+ class ReplyToRoomPlugin
30
+ include Robut::Plugin
31
+
27
32
  def initialize(*args)
28
33
  super(*args)
29
34
  end
@@ -39,7 +44,7 @@ class ReplyMock
39
44
  def initialize
40
45
  @messages = []
41
46
  end
42
-
47
+
43
48
  def send(message)
44
49
  @messages << message
45
50
  end
@@ -96,7 +101,7 @@ class ConnectionTest < Test::Unit::TestCase
96
101
  })
97
102
  }
98
103
  })
99
-
104
+
100
105
  @connection.handle_message(plugins(@connection), Time.now, 'justin WEISS', 'Test Message')
101
106
  message = @connection.client.messages.first
102
107
  assert_equal(justin, message.to.to_s)
@@ -119,5 +124,5 @@ class ConnectionTest < Test::Unit::TestCase
119
124
  def plugins(connection, sender = nil)
120
125
  Robut::Plugin.plugins.map { |p| p.new(connection, sender) }
121
126
  end
122
-
127
+
123
128
  end
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+ require 'robut/plugin/alias'
3
+ require 'robut/plugin/echo'
4
+
5
+ class Robut::Plugin::AliasTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @connection = Robut::ConnectionMock.new
9
+ @plugin = Robut::Plugin::Alias.new(@connection)
10
+ @plugin.aliases = {}
11
+ end
12
+
13
+ def test_aliases_this_to_that
14
+ @plugin.handle(Time.now, "@john", "@robut alias w weather?")
15
+ assert_equal 'weather?', @plugin.aliases['w']
16
+ end
17
+
18
+ def test_thinks_this_is_that
19
+ @plugin.handle(Time.now, "@john", "@robut alias this @robut echo that")
20
+ @plugin.handle(Time.now, "@john", "this")
21
+ message = @plugin.connection.messages.first
22
+ assert_equal "@john", message[1]
23
+ assert_equal "@robut echo that", message[2]
24
+ end
25
+
26
+ def test_doesnt_alias_when_it_shouldnt
27
+ @plugin.handle(Time.now, "@john", "@robut somthing alias w weather?")
28
+ assert @plugin.aliases.empty?
29
+ end
30
+
31
+ def test_can_alias_with_quotes
32
+ @plugin.handle(Time.now, "@john", '@robut alias "long string" "some other long string"')
33
+ assert_equal 'some other long string', @plugin.aliases['long string']
34
+ end
35
+
36
+ def test_can_apply_aliases_with_quotes
37
+ @plugin.handle(Time.now, "@john", '@robut alias "long string" "some other long string"')
38
+ @plugin.handle(Time.now, "@john", "long string")
39
+ message = @plugin.connection.messages.first
40
+ assert_equal "@john", message[1]
41
+ assert_equal "some other long string", message[2]
42
+ end
43
+
44
+ def test_aliases_can_be_removed
45
+ @plugin.handle(Time.now, "@john", "@robut alias this that")
46
+ @plugin.handle(Time.now, "@john", "@robut remove alias this")
47
+ assert @plugin.aliases.empty?
48
+ end
49
+
50
+ def test_remove_aliases_doesnt_choke_on_missing_key
51
+ @plugin.handle(Time.now, "@john", "@robut alias this that")
52
+ @plugin.handle(Time.now, "@john", "@robut remove alias")
53
+ assert_equal({'this' => 'that'}, @plugin.aliases)
54
+ end
55
+
56
+ def test_remove_alias_handles_quotes
57
+ @plugin.handle(Time.now, "@john", '@robut alias "long string" "that"')
58
+ @plugin.handle(Time.now, "@john", '@robut remove alias "long string"')
59
+ assert @plugin.aliases.empty?
60
+ end
61
+
62
+ def test_can_list_all_aliases
63
+ @plugin.handle(Time.now, "@john", "@robut alias this that")
64
+ @plugin.handle(Time.now, "@john", "@robut alias something something else")
65
+ @plugin.handle(Time.now, "@john", "@robut aliases")
66
+ assert_equal ["something => something else\nthis => that"], @plugin.connection.replies
67
+ end
68
+
69
+ def test_can_clear_aliases
70
+ @plugin.handle(Time.now, "@john", "@robut alias this that")
71
+ @plugin.handle(Time.now, "@john", "@robut clear aliases")
72
+ assert_equal({}, @plugin.aliases)
73
+ end
74
+
75
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+ require 'robut/plugin/echo'
3
+ require 'robut/plugin/help'
4
+
5
+ class Robut::Plugin::PluginWithoutHelp
6
+ include Robut::Plugin
7
+
8
+ def usage
9
+ super
10
+ end
11
+ end
12
+
13
+ class Robut::Plugin::HelpTest < Test::Unit::TestCase
14
+
15
+ def setup
16
+ @connection = Robut::ConnectionMock.new
17
+ Robut::Plugin.plugins << Robut::Plugin::Echo
18
+ Robut::Plugin.plugins << Robut::Plugin::Help
19
+ @plugin = Robut::Plugin::Help.new(@connection)
20
+ end
21
+
22
+ def teardown
23
+ Robut::Plugin.plugins = []
24
+ end
25
+
26
+ def test_help
27
+ @plugin.handle(Time.now, "@justin", "@robut help")
28
+ assert_equal [
29
+ "Supported commands:",
30
+ "@robut echo <message> - replies to the channel with <message>",
31
+ "@robut help - displays this message",
32
+ ], @plugin.connection.replies
33
+ end
34
+
35
+ def test_empty_help
36
+ Robut::Plugin.plugins << Robut::Plugin::PluginWithoutHelp
37
+ @plugin.handle(Time.now, "@justin", "@robut help")
38
+ assert_equal [
39
+ "Supported commands:",
40
+ "@robut echo <message> - replies to the channel with <message>",
41
+ "@robut help - displays this message",
42
+ ], @plugin.connection.replies
43
+ end
44
+ end
45
+
@@ -25,4 +25,9 @@ class Robut::Plugin::SayTest < Test::Unit::TestCase
25
25
  assert_equal [], @plugin.system_calls
26
26
  end
27
27
 
28
+ def test_strips_bad_chars
29
+ @plugin.handle(Time.now, "@john", "@robut say it's time for $%@ more_test 123 p\"")
30
+ assert_equal ["say its time for more test 123 p"], @plugin.system_calls
31
+ end
32
+
28
33
  end
@@ -0,0 +1,100 @@
1
+ require 'test_helper'
2
+ require 'webmock/test_unit'
3
+ require 'time-warp'
4
+ require 'robut/plugin/weather'
5
+
6
+ class Robut::Plugin::WeatherTest < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @connection = Robut::ConnectionMock.new
10
+ @plugin = Robut::Plugin::Weather.new(@connection)
11
+ end
12
+
13
+ def teardown
14
+ Robut::Plugin::Weather.default_location = nil
15
+ end
16
+
17
+ def test_handle_no_weather
18
+ @plugin.handle(Time.now, "John", "lunch?")
19
+ assert_equal( [], @plugin.connection.replies )
20
+
21
+ @plugin.handle(Time.now, "John", "?")
22
+ assert_equal( [], @plugin.connection.replies )
23
+ end
24
+
25
+ def test_handle_no_location_no_default
26
+ @plugin.handle(Time.now, "John", "weather?")
27
+ assert_equal( ["I don't have a default location!"], @plugin.connection.replies )
28
+ end
29
+
30
+ def test_handle_no_location_default_set
31
+ Robut::Plugin::Weather.default_location = "Seattle"
32
+ stub_request(:any, "http://www.google.com/ig/api?weather=Seattle").to_return(:body => File.open(File.expand_path("../../../fixtures/seattle.xml", __FILE__), "r").read)
33
+
34
+ @plugin.handle(Time.now, "John", "weather?")
35
+ assert_equal( ["Weather for Seattle, WA: Mostly Cloudy, 58F"], @plugin.connection.replies )
36
+ end
37
+
38
+ def test_handle_location
39
+ stub_request(:any, "http://www.google.com/ig/api?weather=tacoma").to_return(:body => File.open(File.expand_path("../../../fixtures/tacoma.xml", __FILE__), "r").read)
40
+
41
+ @plugin.handle(Time.now, "John", "tacoma weather?")
42
+ assert_equal( ["Weather for Tacoma, WA: Cloudy, 60F"], @plugin.connection.replies )
43
+ end
44
+
45
+ def test_no_question_mark
46
+ @plugin.handle(Time.now, "John", "seattle weather")
47
+ assert_equal( [], @plugin.connection.replies )
48
+ end
49
+
50
+ def test_handle_day
51
+ stub_request(:any, "http://www.google.com/ig/api?weather=Seattle").to_return(:body => File.open(File.expand_path("../../../fixtures/seattle.xml", __FILE__), "r").read)
52
+
53
+ pretend_now_is(2011,"may",23,17) do
54
+ @plugin.handle(Time.now, "John", "Seattle weather tuesday?")
55
+ assert_equal( ["Forecast for Seattle, WA on Tue: Partly Cloudy, High: 67F, Low: 51F"], @plugin.connection.replies )
56
+ end
57
+ end
58
+
59
+ def test_handle_tomorrow
60
+ stub_request(:any, "http://www.google.com/ig/api?weather=Seattle").to_return(:body => File.open(File.expand_path("../../../fixtures/seattle.xml", __FILE__), "r").read)
61
+
62
+ pretend_now_is(2011,"may",23,17) do
63
+ @plugin.handle(Time.now, "John", "Seattle weather tomorrow?")
64
+ assert_equal( ["Forecast for Seattle, WA on Tue: Partly Cloudy, High: 67F, Low: 51F"], @plugin.connection.replies )
65
+ end
66
+ end
67
+
68
+ def test_handle_today
69
+ stub_request(:any, "http://www.google.com/ig/api?weather=Seattle").to_return(:body => File.open(File.expand_path("../../../fixtures/seattle.xml", __FILE__), "r").read)
70
+
71
+ pretend_now_is(2011,"may",23,17) do
72
+ @plugin.handle(Time.now, "John", "Seattle weather today?")
73
+ assert_equal( ["Forecast for Seattle, WA on Mon: Partly Cloudy, High: 59F, Low: 48F"], @plugin.connection.replies )
74
+ end
75
+ end
76
+
77
+ def test_handle_multi_word_location
78
+ stub_request(:any, "http://www.google.com/ig/api?weather=Las%20Vegas").to_return(:body => File.open(File.expand_path("../../../fixtures/las_vegas.xml", __FILE__), "r").read)
79
+ @plugin.handle(Time.now, "John", "Las Vegas weather?")
80
+ assert_equal( ["Weather for Las Vegas, NV: Mostly Cloudy, 83F"], @plugin.connection.replies )
81
+ end
82
+
83
+ def test_handle_location_with_comma
84
+ stub_request(:any, "http://www.google.com/ig/api?weather=Las%20Vegas,%20NV").to_return(:body => File.open(File.expand_path("../../../fixtures/las_vegas.xml", __FILE__), "r").read)
85
+ @plugin.handle(Time.now, "John", "Las Vegas, NV weather?")
86
+ assert_equal( ["Weather for Las Vegas, NV: Mostly Cloudy, 83F"], @plugin.connection.replies )
87
+ end
88
+
89
+ def test_handle_bad_location
90
+ stub_request(:any, "http://www.google.com/ig/api?weather=asdf").to_return(:body => File.open(File.expand_path("../../../fixtures/bad_location.xml", __FILE__), "r").read)
91
+ @plugin.handle(Time.now, "John", "asdf weather?")
92
+ assert_equal( ["I don't recognize the location: \"asdf\""], @plugin.connection.replies )
93
+ end
94
+
95
+ def test_handle_bad_date
96
+ @plugin.handle(Time.now, "John", "Seattle weather asdf?")
97
+ assert_equal( ["I don't recognize the date: \"asdf\""], @plugin.connection.replies )
98
+ end
99
+
100
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+
3
+ class Robut::PluginTest < Test::Unit::TestCase
4
+
5
+ class HandrolledStubPlugin
6
+ include Robut::Plugin
7
+ end
8
+
9
+ def setup
10
+ @plugin = HandrolledStubPlugin.new(Robut::ConnectionMock.new)
11
+ end
12
+
13
+ def test_sent_to_me?
14
+ assert @plugin.sent_to_me?("@Robut hello there")
15
+ assert !@plugin.sent_to_me?("@Robuto hello there")
16
+ assert !@plugin.sent_to_me?("@David hello there")
17
+ assert @plugin.sent_to_me?("this is a @Robut message")
18
+ assert @plugin.sent_to_me?("this is a message to @robut")
19
+ assert !@plugin.sent_to_me?("this is a message to@robut")
20
+ end
21
+
22
+ def test_without_nick_robut_do_this
23
+ assert_equal "do this", @plugin.without_nick("@robut do this")
24
+ end
25
+
26
+ def test_without_nick_do_this_robut
27
+ assert_equal "do this @robut", @plugin.without_nick("do this @robut")
28
+ end
29
+
30
+ end
@@ -7,7 +7,7 @@ class Robut::Storage::YamlStoreTest < Test::Unit::TestCase
7
7
  @store = Robut::Storage::YamlStore
8
8
  @store.file = new_yaml_file
9
9
  end
10
-
10
+
11
11
  def teardown
12
12
  File.delete new_yaml_file if File.exists?(new_yaml_file)
13
13
  end
@@ -16,27 +16,32 @@ class Robut::Storage::YamlStoreTest < Test::Unit::TestCase
16
16
  assert_equal 'in the trunk', (@store['junk'] = 'in the trunk')
17
17
  assert_equal 'in the trunk', @store['junk']
18
18
  end
19
-
19
+
20
+ def test_load_an_empty_file
21
+ FileUtils.touch(new_yaml_file)
22
+ assert_equal nil, @store['non-exising-key']
23
+ end
24
+
20
25
  def test_read_from_file
21
26
  @store.file = test_yaml_file
22
27
  assert_equal 'bar', @store['foo']
23
28
  end
24
-
29
+
25
30
  def test_persists_to_file
26
31
  @store['pot'] = 'roast'
27
32
  assert File.exists?(new_yaml_file)
28
33
  yaml = YAML.load_file(new_yaml_file)
29
34
  assert_equal 'roast', yaml['pot']
30
35
  end
31
-
36
+
32
37
  private
33
-
38
+
34
39
  def test_yaml_file
35
40
  File.join(File.dirname(__FILE__), 'yaml_test.yml')
36
41
  end
37
-
42
+
38
43
  def new_yaml_file
39
44
  File.join(File.dirname(__FILE__), 'new_yaml_test.yml')
40
45
  end
41
-
42
- end
46
+
47
+ end