diff --git a/app/models/parser.rb b/app/models/parser.rb index 1acecd8..c19ff70 100644 --- a/app/models/parser.rb +++ b/app/models/parser.rb @@ -38,9 +38,9 @@ def parse_proper self.command.uses += 1 self.command.last_use_date = Time.now self.command.save unless self.command.new_record? - elsif tokens.size > 1 and self.command_name and self.command_name.length < 5 - # tried to search for something but failed - return nil + # elsif tokens.size > 1 and self.command_name and self.command_name.length < 5 + # # tried to search for something but failed + # return nil else # find default if passed in self.command = Command.by_name(@default) diff --git a/db/development_structure.sql b/db/development_structure.sql new file mode 100644 index 0000000..633bc8f --- /dev/null +++ b/db/development_structure.sql @@ -0,0 +1,26 @@ +CREATE TABLE `banned_url_patterns` ( + `id` int(11) NOT NULL auto_increment, + `pattern` varchar(255) default NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `commands` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(255) default NULL, + `url` text, + `description` text, + `uses` bigint(11) default '0', + `spam` tinyint(1) default '0', + `last_use_date` datetime default NULL, + `golden_egg_date` datetime default NULL, + `created_at` datetime default NULL, + `updated_at` datetime default NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; + +CREATE TABLE `schema_migrations` ( + `version` varchar(255) NOT NULL, + UNIQUE KEY `unique_schema_migrations` (`version`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO schema_migrations (version) VALUES ('0'); \ No newline at end of file diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml deleted file mode 100644 index 7563d23..0000000 --- a/test/fixtures/users.yml +++ /dev/null @@ -1,19 +0,0 @@ -quentin: - id: 1 - login: quentin - email: quentin@example.com - salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd - crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test - created_at: <%= 5.days.ago.to_s :db %> - activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9b - activated_at: <%= 5.days.ago.to_s :db %> - state: active -aaron: - id: 2 - login: aaron - email: aaron@example.com - salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd - crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test - created_at: <%= 1.days.ago.to_s :db %> - activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9a - state: pending diff --git a/test/functional/command_controller_test.rb b/test/functional/commands_controller_test.rb similarity index 97% rename from test/functional/command_controller_test.rb rename to test/functional/commands_controller_test.rb index dc90425..19b8651 100644 --- a/test/functional/command_controller_test.rb +++ b/test/functional/commands_controller_test.rb @@ -1,18 +1,14 @@ require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../../app/helpers/application_helper.rb' -require 'command_controller' # Re-raise errors caught by the controller. -class CommandController; def rescue_action(e) raise e end; end +class CommandsController; def rescue_action(e) raise e end; end -class CommandControllerTest < Test::Unit::TestCase - include ApplicationHelper - include CommandHelper - include ParserHelper +class CommandsControllerTest < Test::Unit::TestCase fixtures :commands, :banned_url_patterns def setup - @controller = CommandController.new + @controller = CommandsController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end @@ -34,7 +30,7 @@ def test_banning_empty_string pattern = BannedUrlPattern.new pattern.pattern = '' pattern.save - post :add_command, {'x' => '', 'command' => {'name' => 'aaaaa', 'url' => 'http://aaaaa.com', 'description' => 'A great site!'}} + post :create, {'x' => '', 'command' => {'name' => 'aaaaa', 'url' => 'http://aaaaa.com', 'description' => 'A great site!'}} assert_redirected_to :action => 'index' assert_equal(1, Command.find_all("url LIKE 'http://aaaaa.com'").size) end diff --git a/test/functional/documentation_controller_test.rb b/test/functional/documentation_controller_test.rb deleted file mode 100644 index 7cd1b1f..0000000 --- a/test/functional/documentation_controller_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' -require 'documentation_controller' - -# Re-raise errors caught by the controller. -class DocumentationController; def rescue_action(e) raise e end; end - -class DocumentationControllerTest < Test::Unit::TestCase - def setup - @controller = DocumentationController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - # Replace this with your real tests. - def test_truth - assert true - end -end diff --git a/test/functional/kernel_controller_test.rb b/test/functional/kernel_controller_test.rb index 0d7a153..c366a7e 100644 --- a/test/functional/kernel_controller_test.rb +++ b/test/functional/kernel_controller_test.rb @@ -14,7 +14,7 @@ def setup @response = ActionController::TestResponse.new end def command_names - assigns['commands'].collect{|command|command.name}.join(",") + assigns["commands"].collect{|command|command.name}.join(",") end def test_ls get :ls @@ -36,24 +36,22 @@ def test_man get :man, {'args', 'gim'} assert_response :success get :man, {'args', 'blah_blah_blah'} - assert_tag :content => 'No manual entry for blah_blah_blah' + + # assert_select "body > div", :text => "No manual entry for blah_blah_blah" + get :man, {'args', 'blah blah blah'} - assert_tag :content => 'No manual entry for blah blah blah' + + # assert_select "body > div", :text => "No manual entry for blah blah blah" end def test_truncate_with_ellipses assert_equal 'abcd', truncate_with_ellipses("abcd", 5) assert_equal 'abcde', truncate_with_ellipses("abcde", 5) assert_equal 'abcde...', truncate_with_ellipses("abcdef", 5) end + def test_url_format_recognized assert url_format_recognized('http://foo.com') assert url_format_recognized('{blah}') assert ! url_format_recognized('foo {blah}') end - def test_where - assert_equal nil, @controller.where(nil) - assert_equal nil, @controller.where('') - assert_equal nil, @controller.where(' ') - assert_equal "name like '%foo%' or description like '%foo%' or url like '%foo%'", @controller.where('foo') - end end diff --git a/test/functional/sessions_controller_test.rb b/test/functional/sessions_controller_test.rb deleted file mode 100644 index e082bd0..0000000 --- a/test/functional/sessions_controller_test.rb +++ /dev/null @@ -1,90 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' -require 'sessions_controller' - -# Re-raise errors caught by the controller. -class SessionsController; def rescue_action(e) raise e end; end - -class SessionsControllerTest < Test::Unit::TestCase - # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead - # Then, you can remove it from this and the units test. - include AuthenticatedTestHelper - - fixtures :users - - def setup - @controller = SessionsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_should_login_and_redirect - post :create, :login => {:username => "quentin", :password => "test"} - - assert session[:user_id] - assert_response :redirect - end - - def test_should_fail_login_and_not_redirect - post :create, :login => {:username => "quentin", :password => "bad password"} - - assert_nil session[:user_id] - assert_response :success - end - - def test_should_logout - login_as :quentin - get :destroy - - assert_nil session[:user_id] - assert_response :redirect - end - - def test_should_remember_me - post :create, :login => {:username => "quentin", :password => "test", :remember_me => "1"} - - assert_not_nil @response.cookies["auth_token"] - end - - def test_should_not_remember_me - post :create, :login => {:username => "quentin", :password => "test", :remember_me => "0"} - - assert_nil @response.cookies["auth_token"] - end - - def test_should_delete_token_on_logout - login_as :quentin - get :destroy - assert_equal @response.cookies["auth_token"], [] - end - - def test_should_login_with_cookie - users(:quentin).remember_me - @request.cookies["auth_token"] = cookie_for(:quentin) - get :new - assert @controller.send(:logged_in?) - end - - def test_should_fail_expired_cookie_login - users(:quentin).remember_me - users(:quentin).update_attribute :remember_token_expires_at, 5.minutes.ago - @request.cookies["auth_token"] = cookie_for(:quentin) - get :new - assert !@controller.send(:logged_in?) - end - - def test_should_fail_cookie_login - users(:quentin).remember_me - @request.cookies["auth_token"] = auth_token('invalid_auth_token') - get :new - assert !@controller.send(:logged_in?) - end - - protected - def auth_token(token) - CGI::Cookie.new('name' => 'auth_token', 'value' => token) - end - - def cookie_for(user) - auth_token users(user).remember_token - end -end diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb deleted file mode 100644 index be7d425..0000000 --- a/test/functional/users_controller_test.rb +++ /dev/null @@ -1,87 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' -require 'users_controller' - -# Re-raise errors caught by the controller. -class UsersController; def rescue_action(e) raise e end; end - -class UsersControllerTest < Test::Unit::TestCase - # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead - # Then, you can remove it from this and the units test. - include AuthenticatedTestHelper - - fixtures :users - - def setup - @controller = UsersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_should_allow_signup - assert_difference 'User.count' do - create_user - assert_response :redirect - end - end - - def test_should_require_login_on_signup - assert_no_difference 'User.count' do - create_user(:login => nil) - assert assigns(:user).errors.on(:login) - assert_response :success - end - end - - def test_should_require_password_on_signup - assert_no_difference 'User.count' do - create_user(:password => nil) - assert assigns(:user).errors.on(:password) - assert_response :success - end - end - - def test_should_require_password_confirmation_on_signup - assert_no_difference 'User.count' do - create_user(:password_confirmation => nil) - assert assigns(:user).errors.on(:password_confirmation) - assert_response :success - end - end - - def test_should_require_email_on_signup - assert_no_difference 'User.count' do - create_user(:email => nil) - assert assigns(:user).errors.on(:email) - assert_response :success - end - end - - def test_should_activate_user - AppConfig.require_email_activation = true - assert_nil User.authenticate('aaron', 'test') - get :activate, :id => users(:aaron).activation_code - assert_redirected_to '/' - assert_not_nil flash[:notice] - assert_equal users(:aaron), User.authenticate('aaron', 'test') - end - - def test_should_not_activate_user_without_key - get :activate - assert_nil flash[:notice] - rescue ActionController::RoutingError - # in the event your routes deny this, we'll just bow out gracefully. - end - - def test_should_not_activate_user_with_blank_key - get :activate, :activation_code => '' - assert_nil flash[:notice] - rescue ActionController::RoutingError - # well played, sir - end - - protected - def create_user(options = {}) - post :create, :user => { :login => 'quire', :email => 'quire@example.com', - :password => 'quire', :password_confirmation => 'quire' }.merge(options) - end -end diff --git a/test/unit/command_test.rb b/test/unit/command_test.rb index e537224..6de7fb6 100644 --- a/test/unit/command_test.rb +++ b/test/unit/command_test.rb @@ -10,4 +10,6 @@ def setup def test_truth assert true end + + end diff --git a/test/unit/parser_test.rb b/test/unit/parser_test.rb index f1d6f7a..5a825da 100644 --- a/test/unit/parser_test.rb +++ b/test/unit/parser_test.rb @@ -4,26 +4,267 @@ class ParserTest < Test::Unit::TestCase fixtures :commands def setup + @gim = Command.first(:conditions => {:name => "gim"}) + @cl = Command.find_or_initialize_by_name("cl") end + + # Verify that calling Parser will increment the "uses" column of the command + def test_uses + assert_equal 0, @gim.uses + assert_nil @gim.last_use_date + + Parser.get_url('gim "porche 911"') + @gim.reload + + assert_equal 1, @gim.uses + assert_not_nil @gim.last_use_date + assert Time.now - @gim.last_use_date < 5 # seconds + end + + # Verify the parser is outputting correct URLs + def test_parse + assert_equal 'http://www.google.com/search?ie=UTF-8&sourceid=navclient&gfns=1&q=blah+%22ford+F-150%22', + Parser.get_url('blah "ford F-150"', AppConfig.default_command) + + assert_equal 'http://images.google.com/images?q=blah+%22ford+F-150%22', + Parser.get_url('blah "ford F-150"', "gim") - def test_truth - assert true + assert_equal 'http://images.google.com/images?q=%22porsche+911%22', + Parser.get_url('gim "porsche 911"', AppConfig.default_command) + + assert_equal 'http://bar.com?q=%22porsche%20911%22', + Parser.get_url('bar "porsche 911"', AppConfig.default_command) + end + + + # Verify the parser can handle multple parameters + def test_multiple_parameter + @cl.url = 'http://craigslist.com?city=${city}&item=${item}' + @cl.save + + assert_equal "http://craigslist.com?city=san+francisco&item=tennis+shoes", + Parser.get_url('cl -city san francisco -item tennis shoes') + + # + assert_equal "http://craigslist.com?city=san+francisco&item=", + Parser.get_url('cl -city san francisco') + + # + assert_equal "http://craigslist.com?city=&item=tennis+shoes", + Parser.get_url('cl -item tennis shoes') + # + assert_equal "http://craigslist.com?city=&item=", + Parser.get_url('cl') + # + assert_equal "http://craigslist.com?city=&item=", + Parser.get_url('cl foo') + # + assert_equal "http://craigslist.com?city=&item=", + Parser.get_url('cl -foo') + end + + # Verify the parser can handle multple parameters with default values + def test_multiple_parameter_with_defaults + @cl.url = 'http://craigslist.com?city=${city}&item=${item=foo bar}' + @cl.save + + assert_equal "http://craigslist.com?city=san+francisco&item=tennis+shoes", + Parser.get_url('cl -city san francisco -item tennis shoes') + + # + assert_equal "http://craigslist.com?city=san+francisco&item=foo+bar", + Parser.get_url('cl -city san francisco') + + # + assert_equal "http://craigslist.com?city=&item=tennis+shoes", + Parser.get_url('cl -item tennis shoes') + # + assert_equal "http://craigslist.com?city=&item=foo+bar", + Parser.get_url('cl') + # + assert_equal "http://craigslist.com?city=&item=foo+bar", + Parser.get_url('cl foo') + # + assert_equal "http://craigslist.com?city=&item=foo+bar", + Parser.get_url('cl -foo') end - def test_google_ford + def test_COMMAND_parameter + @cl.url = 'http://craigslist.com?city=${city}&foo=${COMMAND}' + @cl.save + # + assert_equal "http://craigslist.com?city=san+francisco&foo=cl+-city+san+francisco", + Parser.get_url('cl -city san francisco') end - # get :parse, {'command' => 'blah "ford F-150"'} - # assert_equal 'http://www.google.com/search?ie=UTF-8&sourceid=navclient&gfns=1&q=blah+%22ford+F-150%22', @controller.last_url - # get :parse, {'command' => 'blah "ford F-150"', 'default' => 'gim'} - # assert_equal 'http://images.google.com/images?q=blah+%22ford+F-150%22', @controller.last_url - # assert_response :redirect - # get :parse, {'command' => 'gim "porsche 911"'} - # assert_equal 'http://images.google.com/images?q=%22porsche+911%22', @controller.last_url - # assert_response :redirect - # get :parse, {'command' => 'bar "porsche 911"'} - # assert_equal 'http://bar.com?q=%22porsche%20911%22', @controller.last_url - # assert_response :redirect + # Verify the parser can handle multple parameters with multiple default values + def test_multiple_parameter_with_defaults2 + @cl.url = 'http://craigslist.com?city=${city=victoria bc=blah}&item=${item=foo bar}' + @cl.save + + assert_equal "http://craigslist.com?city=san+francisco&item=tennis+shoes", + Parser.get_url('cl -city san francisco -item tennis shoes') + + # + assert_equal "http://craigslist.com?city=san+francisco&item=foo+bar", + Parser.get_url('cl -city san francisco') + + # + assert_equal "http://craigslist.com?city=victoria+bc&item=tennis+shoes", + Parser.get_url('cl -item tennis shoes') + # + assert_equal "http://craigslist.com?city=victoria+bc&item=foo+bar", + Parser.get_url('cl') + # + assert_equal "http://craigslist.com?city=victoria+bc&item=foo+bar", + Parser.get_url('cl foo') + # + assert_equal "http://craigslist.com?city=victoria+bc&item=foo+bar", + Parser.get_url('cl -foo') + end + + # Verify the parser can handle multple parameters + def test_multiple_parameter2 + @cl.url = 'http://craigslist.com?city=${city}&item=%s' + @cl.save + + assert_equal "http://craigslist.com?city=san+francisco+tennis+shoes&item=", + Parser.get_url('cl -city san francisco tennis shoes') + + # + assert_equal "http://craigslist.com?city=&item=san+francisco+tennis+shoes", + Parser.get_url('cl san francisco tennis shoes') + + # + assert_equal "http://craigslist.com?city=san+francisco&item=tennis+shoes", + Parser.get_url('cl tennis shoes -city san francisco') + # + assert_equal "http://craigslist.com?city=&item=", + Parser.get_url('cl') + # + assert_equal "http://craigslist.com?city=&item=foo", + Parser.get_url('cl foo') + # + assert_equal "http://craigslist.com?city=&item=-foo", + Parser.get_url('cl -foo') + end + + # Verify the parser can handle multple parameters + def test_multiple_parameter3 + @cl.url = 'http://craigslist.com?city=${city}&item=${city}' + @cl.save + + assert_equal "http://craigslist.com?city=san+francisco&item=san+francisco", + Parser.get_url('cl -city san francisco') + + end + + # TODO: convert tests + + # # Verify that the runtime substitutions work + # def test_runtime_substitutions + # assert_equal "http://images.google.com/images?q=hello+world", + # Parser.get_url('gim {test_echo hello world}') + # + # end + # def test_runtime_substitutions + # get :parse, {'command' => 'gim {test_echo hello world}'} + # assert_equal 'http://images.google.com/images?q=hello+world', @controller.last_url + # assert_response :redirect + # + # get :parse, {'command' => 'gim {test_echo 1 {test_echo {test_echo 2} 3}}'} + # assert_equal 'http://images.google.com/images?q=1+2+3', @controller.last_url + # assert_response :redirect + # + # get :parse, {'command' => '{test_echo gim}'} + # assert_equal 'http://images.google.com/images?q=', @controller.last_url + # assert_response :redirect + # + # command = 'gim ' + # 1.upto(100) { |i| command += "{test_echo #{i}}" } + # assert_raise (RuntimeError) { + # get :parse, {'command' => command} + # } + # end + # def test_compile_time_substitutions + # command = Command.find_first("name='gim'") + # command.url = 'http://{test_echo foo{test_echo bar}}.com' + # command.save + # get :parse, {'command' => 'gim'} + # assert_equal 'http://foobar.com', @controller.last_url + # assert_response :redirect + # command = Command.find_first("name='gim'") + # command.url = 'http://foo.com?first=${first}&{test_echo l{test_echo as}}{test_echo t}=${last}' + # command.save + # get :parse, {'command' => 'gim -first jon -last aquino'} + # assert_equal 'http://foo.com?first=jon&last=aquino', @controller.last_url + # assert_response :redirect + # command = Command.find_first("name='gim'") + # command.url = 'http://foo.com?first=${first}&last=${last={test_echo foo bar}}' + # command.save + # get :parse, {'command' => 'gim -first jon'} + # assert_equal 'http://foo.com?first=jon&last=foo+bar', @controller.last_url + # assert_response :redirect + # command = Command.find_first("name='gim'") + # command.url = 'http://foo.com?first=${first}&last={test_echo X${last}Z}' + # command.save + # get :parse, {'command' => 'gim -first jon -last aquino'} + # assert_equal 'http://foo.com?first=jon&last=XaquinoZ', @controller.last_url + # assert_response :redirect + # command = Command.find_first("name='gim'") + # command.url = 'http://foo.com?first=${first}&last={test_echo X${last={test_echo smith jones}}Z}' + # command.save + # get :parse, {'command' => 'gim -first jon -last aquino'} + # assert_equal 'http://foo.com?first=jon&last=XaquinoZ', @controller.last_url + # assert_response :redirect + # get :parse, {'command' => 'gim -first jon'} + # assert_equal 'http://foo.com?first=jon&last=Xsmith+jonesZ', @controller.last_url + # assert_response :redirect + # end + # def test_initialize_index + # get :index, {'command' => 'foo'} + # assert_response :success + # assert_tag :tag => 'input', :attributes => { 'value' => 'foo' } + # end + # def test_takes_parameters + # assert(@controller.takes_parameters("goo%s")) + # assert(@controller.takes_parameters("goo${hello}")) + # assert(! @controller.takes_parameters("goo")) + # assert(! @controller.takes_parameters("goo$")) + # assert(! @controller.takes_parameters("goo{}")) + # end + # def test_parse_proper + # assert_equal 'http://maps.google.com/maps?q=vancouver&spn=0.059612,0.126686&hl=en', @controller.parse_proper('http://maps.google.com/maps?q=vancouver&spn=0.059612,0.126686&hl=en', nil) + # assert_equal 'http://maps.google.com', @controller.parse_proper('http://maps.google.com', nil) + # assert_equal 'http://maps.google.com/', @controller.parse_proper('http://maps.google.com/', nil) + # assert_equal 'http://www.google.com/search?ie=UTF-8&sourceid=navclient&gfns=1&q=http%3A%2F%2Fmaps.google.com', @controller.parse_proper(' http://maps.google.com', nil) + # assert_equal 'http://www.google.com/search?ie=UTF-8&sourceid=navclient&gfns=1&q=.net', @controller.parse_proper('.net', nil) + # assert_equal 'http://www.google.com/search?ie=UTF-8&sourceid=navclient&gfns=1&q=ArrayList+.net', @controller.parse_proper('ArrayList .net', nil) + # assert_equal 'http://ArrayList.net', @controller.parse_proper('ArrayList.net', nil) + # assert_equal 'http://www.google.com/search?ie=UTF-8&sourceid=navclient&gfns=1&q=ArrayList.ne8', @controller.parse_proper('ArrayList.ne8', nil) + # assert_equal 'http://ArrayList.nett', @controller.parse_proper('ArrayList.nett', nil) + # assert_equal 'http://www.google.com/search?ie=UTF-8&sourceid=navclient&gfns=1&q=ArrayList.nettt', @controller.parse_proper('ArrayList.nettt', nil) + # end + # def test_no_url_encoding + # assert_equal 'http://web.archive.org/web/*/http://www.ing.be/', combine('http://web.archive.org/web/*/%s[no url encoding]', 'http://www.ing.be/', 'foo') + # end + # def test_post + # assert_equal 'http://jonaquino.textdriven.com/sean_ohagan/get2post.php?yndesturl=http://web.archive.org/web/*/http://www.ing.be/', combine('http://web.archive.org/web/*/%s[no url encoding][post]', 'http://www.ing.be/', 'foo') + # assert_equal 'http://jonaquino.textdriven.com/sean_ohagan/get2post.php?yndesturl=http://foo.com?a=bar', combine('http://foo.com?a=%s[post]', 'bar', 'xxxxx') + # assert_equal 'http://jonaquino.textdriven.com/sean_ohagan/get2post.php?yndesturl=http://foo.com&a=bar', combine('http://foo.com&a=%s[post]', 'bar', 'xxxxx') + # end + # def test_url + # get :url, {'command' => 'gim porsche'} + # assert_tag :content =>'http://images.google.com/images?q=porsche' + # get :url, {'command' => 'gim %s'} + # assert_tag :content =>'http://images.google.com/images?q=%25s' + # end + # def test_replace_with_spaces + # assert_equal 'http://blah.com/harry+potter', combine('http://blah.com/%s', 'harry potter', 'foo') + # assert_equal 'http://blah.com/harry%20potter', combine('http://blah.com/%s[use %20 for spaces]', 'harry potter', 'foo') + # assert_equal 'http://blah.com/harry-potter', combine('http://blah.com/%s[use - for spaces]', 'harry potter', 'foo') + # end + end