diff options
Diffstat (limited to 'spec')
55 files changed, 3114 insertions, 460 deletions
diff --git a/spec/controllers/admin_public_body_controller_spec.rb b/spec/controllers/admin_public_body_controller_spec.rb index 22af3df80..1e82a0ba4 100644 --- a/spec/controllers/admin_public_body_controller_spec.rb +++ b/spec/controllers/admin_public_body_controller_spec.rb @@ -2,14 +2,19 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe AdminPublicBodyController, "when administering public bodies" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do username = MySociety::Config.get('ADMIN_USERNAME', '') password = MySociety::Config.get('ADMIN_PASSWORD', '') basic_auth_login @request + + @old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new end + after do + ActionController::Routing::Routes.filters = @old_filters + end it "shows the index page" do get :index @@ -25,9 +30,9 @@ describe AdminPublicBodyController, "when administering public bodies" do end it "creates a new public body" do - PublicBody.count.should == 2 + n = PublicBody.count post :create, { :public_body => { :name => "New Quango", :short_name => "", :tag_string => "blah", :request_email => 'newquango@localhost', :last_edit_comment => 'From test code' } } - PublicBody.count.should == 3 + PublicBody.count.should == n + 1 end it "edits a public body" do @@ -42,29 +47,45 @@ describe AdminPublicBodyController, "when administering public bodies" do pb.name.should == "Renamed" end + it "does not destroy a public body that has associated requests" do + id = public_bodies(:humpadink_public_body).id + n = PublicBody.count + post :destroy, { :id => id } + response.should redirect_to(:controller=>'admin_public_body', :action=>'show', :id => id) + PublicBody.count.should == n + end + it "destroys a public body" do - PublicBody.count.should == 2 - post :destroy, { :id => 3 } - PublicBody.count.should == 1 + n = PublicBody.count + post :destroy, { :id => public_bodies(:forlorn_public_body).id } + response.should redirect_to(:controller=>'admin_public_body', :action=>'list') + PublicBody.count.should == n - 1 end it "sets a using_admin flag" do get :show, :id => 2 session[:using_admin].should == 1 end + + it "mass assigns tags" do + n = PublicBody.count + post :mass_tag_add, { :new_tag => "department", :table_name => "substring" } + response.flash[:notice].should == "Added tag to table of bodies." + response.should redirect_to(:action=>'list') + PublicBody.find_by_tag("department").count.should == n + end end describe AdminPublicBodyController, "when administering public bodies and paying attention to authentication" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things it "disallows non-authenticated users to do anything" do @request.env["HTTP_AUTHORIZATION"] = "" - PublicBody.count.should == 2 + n = PublicBody.count post :destroy, { :id => 3 } response.code.should == "401" - PublicBody.count.should == 2 + PublicBody.count.should == n session[:using_admin].should == nil end @@ -73,19 +94,22 @@ describe AdminPublicBodyController, "when administering public bodies and paying config['ADMIN_USERNAME'] = '' config['ADMIN_PASSWORD'] = '' @request.env["HTTP_AUTHORIZATION"] = "" - PublicBody.count.should == 2 - post :destroy, { :id => 3 } - PublicBody.count.should == 1 + + n = PublicBody.count + post :destroy, { :id => public_bodies(:forlorn_public_body).id } + PublicBody.count.should == n - 1 session[:using_admin].should == 1 end + it "skips admin authorisation when no username set" do config = MySociety::Config.load_default() config['ADMIN_USERNAME'] = '' config['ADMIN_PASSWORD'] = 'fuz' @request.env["HTTP_AUTHORIZATION"] = "" - PublicBody.count.should == 2 - post :destroy, { :id => 3 } - PublicBody.count.should == 1 + + n = PublicBody.count + post :destroy, { :id => public_bodies(:forlorn_public_body).id } + PublicBody.count.should == n - 1 session[:using_admin].should == 1 end it "forces authorisation when password and username set" do @@ -93,11 +117,11 @@ describe AdminPublicBodyController, "when administering public bodies and paying config['ADMIN_USERNAME'] = 'biz' config['ADMIN_PASSWORD'] = 'fuz' @request.env["HTTP_AUTHORIZATION"] = "" - PublicBody.count.should == 2 + n = PublicBody.count basic_auth_login(@request, "baduser", "badpassword") - post :destroy, { :id => 3 } + post :destroy, { :id => public_bodies(:forlorn_public_body).id } response.code.should == "401" - PublicBody.count.should == 2 + PublicBody.count.should == n session[:using_admin].should == nil end @@ -107,7 +131,6 @@ end describe AdminPublicBodyController, "when administering public bodies with i18n" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do username = MySociety::Config.get('ADMIN_USERNAME', '') @@ -167,43 +190,47 @@ describe AdminPublicBodyController, "when administering public bodies with i18n" end it "destroy a public body" do - PublicBody.count.should == 2 - post :destroy, { :id => 3 } - PublicBody.count.should == 1 + n = PublicBody.count + post :destroy, { :id => public_bodies(:forlorn_public_body).id } + PublicBody.count.should == n - 1 end end describe AdminPublicBodyController, "when creating public bodies with i18n" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do username = MySociety::Config.get('ADMIN_USERNAME', '') password = MySociety::Config.get('ADMIN_PASSWORD', '') basic_auth_login @request - ActionController::Routing::Routes.filters.clear # don't auto-insert locale, complicates assertions + @old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + end + + after do + ActionController::Routing::Routes.filters = @old_filters end it "creates a new public body in one locale" do - PublicBody.count.should == 2 + n = PublicBody.count post :create, { :public_body => { :name => "New Quango", :short_name => "", :tag_string => "blah", :request_email => 'newquango@localhost', :last_edit_comment => 'From test code' } } - PublicBody.count.should == 3 + PublicBody.count.should == n + 1 body = PublicBody.find_by_name("New Quango") response.should redirect_to(:controller=>'admin_public_body', :action=>'show', :id=>body.id) end it "creates a new public body with multiple locales" do - PublicBody.count.should == 2 + n = PublicBody.count post :create, { :public_body => { :name => "New Quango", :short_name => "", :tag_string => "blah", :request_email => 'newquango@localhost', :last_edit_comment => 'From test code', :translated_versions => [{ :locale => "es", :name => "Mi Nuevo Quango", :short_name => "", :request_email => 'newquango@localhost' }] } } - PublicBody.count.should == 3 + PublicBody.count.should == n + 1 body = PublicBody.find_by_name("New Quango") body.translations.map {|t| t.locale.to_s}.sort.should == ["en", "es"] diff --git a/spec/controllers/admin_request_controller_spec.rb b/spec/controllers/admin_request_controller_spec.rb index 635d73b9e..ece1fe389 100644 --- a/spec/controllers/admin_request_controller_spec.rb +++ b/spec/controllers/admin_request_controller_spec.rb @@ -2,9 +2,17 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe AdminRequestController, "when administering requests" do integrate_views - fixtures :users, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before { basic_auth_login @request } + before(:each) do + load_raw_emails_data + @old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + end + after do + ActionController::Routing::Routes.filters = @old_filters + end + it "shows the index/list page" do get :index end @@ -41,10 +49,14 @@ end describe AdminRequestController, "when administering the holding pen" do integrate_views - fixtures :users, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do basic_auth_login @request - load_raw_emails_data(raw_emails) + load_raw_emails_data + @old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + end + after do + ActionController::Routing::Routes.filters = @old_filters end it "shows a rejection reason for an incoming message from an invalid address" do diff --git a/spec/controllers/admin_track_controller_spec.rb b/spec/controllers/admin_track_controller_spec.rb index b87ee9f0e..728c79f1f 100644 --- a/spec/controllers/admin_track_controller_spec.rb +++ b/spec/controllers/admin_track_controller_spec.rb @@ -2,7 +2,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe AdminTrackController, "when administering tracks" do integrate_views - fixtures :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things it "shows the list page" do get :list diff --git a/spec/controllers/admin_user_controller_spec.rb b/spec/controllers/admin_user_controller_spec.rb index b2b2d0626..65ecbc37d 100644 --- a/spec/controllers/admin_user_controller_spec.rb +++ b/spec/controllers/admin_user_controller_spec.rb @@ -2,7 +2,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe AdminUserController, "when administering users" do integrate_views - fixtures :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before { basic_auth_login @request } it "shows the index/list page" do diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 875d7d224..f16cee312 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -1,10 +1,47 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'fakeweb' + +describe ApplicationController, "when accessing third party services" do + before (:each) do + FakeWeb.clean_registry + end + after (:each) do + FakeWeb.clean_registry + end + it "should succeed if the service responds OK" do + config = MySociety::Config.load_default() + config['GAZE_URL'] = 'http://denmark.com' + FakeWeb.register_uri(:get, %r|denmark.com|, :body => "DK") + country = self.controller.send :country_from_ip + country.should == "DK" + end + it "should fail silently if the country_from_ip domain doesn't exist" do + config = MySociety::Config.load_default() + config['GAZE_URL'] = 'http://12123sdf14qsd.com' + country = self.controller.send :country_from_ip + country.should == config['ISO_COUNTRY_CODE'] + end + it "should fail silently if the country_from_ip service doesn't exist" do + config = MySociety::Config.load_default() + config['GAZE_URL'] = 'http://www.google.com' + country = self.controller.send :country_from_ip + country.should == config['ISO_COUNTRY_CODE'] + end + it "should fail silently if the country_from_ip service returns an error" do + FakeWeb.register_uri(:get, %r|500.com|, :body => "Error", :status => ["500", "Error"]) + config = MySociety::Config.load_default() + config['GAZE_URL'] = 'http://500.com' + country = self.controller.send :country_from_ip + country.should == config['ISO_COUNTRY_CODE'] + end +end + +describe ApplicationController, "when caching fragments" do + it "should not fail with long filenames" do + long_name = "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah.txt" + path = self.controller.send(:foi_fragment_cache_path, long_name) + self.controller.send(:foi_fragment_cache_write, path, "whassap") + end -describe ApplicationController, "when authenticating user" do - integrate_views - fixtures :users - -# it "blah" do -# end end diff --git a/spec/controllers/comment_controller_spec.rb b/spec/controllers/comment_controller_spec.rb index 4c14b8d24..93752537c 100644 --- a/spec/controllers/comment_controller_spec.rb +++ b/spec/controllers/comment_controller_spec.rb @@ -2,7 +2,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe CommentController, "when commenting on a request" do integrate_views - fixtures :users, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things it "should give an error and render 'new' template when body text is just some whitespace" do post :new, :url_title => info_requests(:naughty_chicken_request).url_title, diff --git a/spec/controllers/general_controller_spec.rb b/spec/controllers/general_controller_spec.rb index 40a676d61..7fc019c64 100644 --- a/spec/controllers/general_controller_spec.rb +++ b/spec/controllers/general_controller_spec.rb @@ -1,23 +1,28 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'fakeweb' + +describe GeneralController, "when trying to show the blog" do + before (:each) do + FakeWeb.clean_registry + end + after (:each) do + FakeWeb.clean_registry + end + + it "should fail silently if the blog is returning an error" do + FakeWeb.register_uri(:get, %r|.*|, :body => "Error", :status => ["500", "Error"]) + get :blog + response.status.should == "200 OK" + assigns[:blog_items].count.should == 0 + end +end describe GeneralController, "when searching" do integrate_views - fixtures [ - :public_bodies, - :public_body_translations, - :public_body_versions, - :users, - :info_requests, - :raw_emails, - :incoming_messages, - :outgoing_messages, - :comments, - :info_request_events, - :track_things, - ] before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should render the front page successfully" do @@ -68,36 +73,58 @@ describe GeneralController, "when searching" do it "should redirect from search query URL to pretty URL" do post :search_redirect, :query => "mouse" # query hidden in POST parameters - response.should redirect_to(:action => 'search', :combined => "mouse", :view => "requests") # URL /search/:query/all + response.should redirect_to(:action => 'search', :combined => "mouse", :view => "all") # URL /search/:query/all end describe "when using different locale settings" do home_link_regex = /href=".*\/en"/ it "should generate URLs with a locale prepended when there's more than one locale set" do - ActionController::Routing::Routes.add_filters('conditionallyprependlocale') get :frontpage response.should have_text(home_link_regex) end it "should generate URLs without a locale prepended when there's only one locale set" do - ActionController::Routing::Routes.add_filters('conditionallyprependlocale') - old_available_locales = FastGettext.default_available_locales - available_locales = ['en'] - FastGettext.default_available_locales = available_locales - I18n.available_locales = available_locales + old_fgt_available_locales = FastGettext.default_available_locales + old_i18n_available_locales = I18n.available_locales + FastGettext.default_available_locales = I18n.available_locales = ['en'] get :frontpage response.should_not have_text(home_link_regex) - FastGettext.default_available_locales = old_available_locales - I18n.available_locales = old_available_locales + FastGettext.default_available_locales = old_fgt_available_locales + I18n.available_locales = old_i18n_available_locales + end + end + + describe 'when constructing the list of recent requests' do + before(:each) do + load_raw_emails_data + rebuild_xapian_index + end + + it 'should list the newest successful request first' do + # Make sure the newest is listed first even if an older one + # has a newer comment or was reclassified more recently: + # https://github.com/sebbacon/alaveteli/issues/370 + # + # This is a deliberate behaviour change, in that the + # previous behaviour (showing more-recently-reclassified + # requests first) was intentional. + get :frontpage + assigns[:request_events].first.info_request.should == info_requests(:another_boring_request) + end + + it 'should coalesce duplicate requests' do + get :frontpage + assigns[:request_events].map(&:info_request).select{|x|x.url_title =~ /^spam/}.length.should == 1 end end describe 'when using xapian search' do # rebuild xapian index after fixtures loaded - before(:all) do + before(:each) do + load_raw_emails_data rebuild_xapian_index end @@ -128,21 +155,31 @@ describe GeneralController, "when searching" do it "should filter results based on end of URL being 'all'" do get :search, :combined => ['"bob"', "all"] - assigns[:xapian_requests].results.size.should == 2 - assigns[:xapian_users].results.size.should == 1 - assigns[:xapian_bodies].results.size.should == 0 + assigns[:xapian_requests].results.map{|x| x[:model]}.should =~ [ + info_request_events(:useless_outgoing_message_event), + info_request_events(:silly_outgoing_message_event), + info_request_events(:useful_incoming_message_event), + info_request_events(:another_useful_incoming_message_event), + ] + assigns[:xapian_users].results.map{|x| x[:model]}.should == [users(:bob_smith_user)] + assigns[:xapian_bodies].results.should == [] end it "should filter results based on end of URL being 'users'" do get :search, :combined => ['"bob"', "users"] assigns[:xapian_requests].should == nil - assigns[:xapian_users].results.size.should == 1 + assigns[:xapian_users].results.map{|x| x[:model]}.should == [users(:bob_smith_user)] assigns[:xapian_bodies].should == nil end it "should filter results based on end of URL being 'requests'" do get :search, :combined => ['"bob"', "requests"] - assigns[:xapian_requests].results.size.should == 2 + assigns[:xapian_requests].results.map{|x|x[:model]}.should =~ [ + info_request_events(:useless_outgoing_message_event), + info_request_events(:silly_outgoing_message_event), + info_request_events(:useful_incoming_message_event), + info_request_events(:another_useful_incoming_message_event), + ] assigns[:xapian_users].should == nil assigns[:xapian_bodies].should == nil end @@ -151,7 +188,7 @@ describe GeneralController, "when searching" do get :search, :combined => ['"quango"', "bodies"] assigns[:xapian_requests].should == nil assigns[:xapian_users].should == nil - assigns[:xapian_bodies].results.size.should == 1 + assigns[:xapian_bodies].results.map{|x|x[:model]}.should == [public_bodies(:geraldine_public_body)] end it "should show help when searching for nothing" do diff --git a/spec/controllers/public_body_controller_spec.rb b/spec/controllers/public_body_controller_spec.rb index 8182e1331..e6eca0781 100644 --- a/spec/controllers/public_body_controller_spec.rb +++ b/spec/controllers/public_body_controller_spec.rb @@ -4,7 +4,11 @@ require 'json' describe PublicBodyController, "when showing a body" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things + + before(:each) do + load_raw_emails_data + rebuild_xapian_index + end it "should be successful" do get :show, :url_name => "dfh", :view => 'all' @@ -21,11 +25,23 @@ describe PublicBodyController, "when showing a body" do assigns[:public_body].should == public_bodies(:humpadink_public_body) end - it "should assign the requests" do + it "should assign the requests (1)" do get :show, :url_name => "tgq", :view => 'all' - assigns[:xapian_requests].results.count.should == 2 + assigns[:xapian_requests].results.map{|x|x[:model].info_request}.should =~ InfoRequest.all( + :conditions => ["public_body_id = ?", public_bodies(:geraldine_public_body).id]) + end + + it "should assign the requests (2)" do get :show, :url_name => "tgq", :view => 'successful' - assigns[:xapian_requests].results.count.should == 0 + assigns[:xapian_requests].results.map{|x|x[:model].info_request}.should =~ InfoRequest.all( + :conditions => ["described_state = ? and public_body_id = ?", + "successful", public_bodies(:geraldine_public_body).id]) + end + + it "should assign the requests (3)" do + get :show, :url_name => "dfh", :view => 'all' + assigns[:xapian_requests].results.map{|x|x[:model].info_request}.should =~ InfoRequest.all( + :conditions => ["public_body_id = ?", public_bodies(:humpadink_public_body).id]) end it "should assign the body using different locale from that used for url_name" do @@ -43,9 +59,13 @@ describe PublicBodyController, "when showing a body" do end it "should redirect use to the relevant locale even when url_name is for a different locale" do - ActionController::Routing::Routes.filters.clear + old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + get :show, {:url_name => "edfh", :view => 'all'} response.should redirect_to "http://test.host/body/dfh" + + ActionController::Routing::Routes.filters = old_filters end it "should redirect to newest name if you use historic name of public body in URL" do @@ -61,7 +81,6 @@ end describe PublicBodyController, "when listing bodies" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things it "should be successful" do get :list @@ -70,25 +89,28 @@ describe PublicBodyController, "when listing bodies" do it "should list all bodies from default locale, even when there are no translations for selected locale" do PublicBody.with_locale(:en) do - english_only = PublicBody.new(:name => 'English only', + @english_only = PublicBody.new(:name => 'English only', :short_name => 'EO', :request_email => 'english@flourish.org', :last_edit_editor => 'test', :last_edit_comment => '') - english_only.save + @english_only.save end PublicBody.with_locale(:es) do get :list - assigns[:public_bodies].length.should == 3 + assigns[:public_bodies].include?(@english_only).should == true end end it "should list bodies in alphabetical order" do + # Note that they are alphabetised by localised name get :list response.should render_template('list') - assigns[:public_bodies].should == [ public_bodies(:humpadink_public_body), public_bodies(:geraldine_public_body) ] + assigns[:public_bodies].should == PublicBody.all( + :conditions => "id <> #{PublicBody.internal_admin_body.id}", + :order => "(select name from public_body_translations where public_body_id=public_bodies.id and locale='en')") assigns[:tag].should == "all" assigns[:description].should == "" end @@ -114,22 +136,23 @@ describe PublicBodyController, "when listing bodies" do end it "should list a tagged thing on the appropriate list page, and others on the other page, and all still on the all page" do + load_test_categories + public_bodies(:humpadink_public_body).tag_string = "foo local_council" get :list, :tag => "local_council" response.should render_template('list') assigns[:public_bodies].should == [ public_bodies(:humpadink_public_body) ] assigns[:tag].should == "local_council" - assigns[:description].should == "Local councils" + assigns[:description].should == "in the category ‘Local councils’" get :list, :tag => "other" response.should render_template('list') - assigns[:public_bodies].should == [ public_bodies(:geraldine_public_body) ] + assigns[:public_bodies].should =~ PublicBody.all(:conditions => "id not in (#{public_bodies(:humpadink_public_body).id}, #{PublicBody.internal_admin_body.id})") get :list response.should render_template('list') - assigns[:public_bodies].count.should == 2 - + assigns[:public_bodies].should =~ PublicBody.all(:conditions => "id <> #{PublicBody.internal_admin_body.id}") end it "should list a machine tagged thing, should get it in both ways" do @@ -157,8 +180,6 @@ end describe PublicBodyController, "when showing JSON version for API" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things - it "should be successful" do get :show, :url_name => "dfh", :format => "json", :view => 'all' @@ -172,38 +193,45 @@ describe PublicBodyController, "when showing JSON version for API" do end describe PublicBodyController, "when doing type ahead searches" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things + + integrate_views + + before(:each) do + load_raw_emails_data + rebuild_xapian_index + end it "should return nothing for the empty query string" do - get :search_typeahead, :q => "" + get :search_typeahead, :query => "" response.should render_template('public_body/_search_ahead') assigns[:xapian_requests].should be_nil end it "should return a body matching the given keyword, but not users with a matching description" do - get :search_typeahead, :q => "Geraldine" + get :search_typeahead, :query => "Geraldine" response.should render_template('public_body/_search_ahead') + response.body.should include('search_ahead') assigns[:xapian_requests].results.size.should == 1 assigns[:xapian_requests].results[0][:model].name.should == public_bodies(:geraldine_public_body).name end it "should return all requests matching any of the given keywords" do - get :search_typeahead, :q => "Geraldine Humpadinking" + get :search_typeahead, :query => "Geraldine Humpadinking" response.should render_template('public_body/_search_ahead') - assigns[:xapian_requests].results.size.should == 2 - assigns[:xapian_requests].results[0][:model].name.should == public_bodies(:humpadink_public_body).name - assigns[:xapian_requests].results[1][:model].name.should == public_bodies(:geraldine_public_body).name + assigns[:xapian_requests].results.map{|x|x[:model]}.should =~ [ + public_bodies(:humpadink_public_body), + public_bodies(:geraldine_public_body), + ] end it "should return requests matching the given keywords in any of their locales" do - get :search_typeahead, :q => "baguette" # part of the spanish notes + get :search_typeahead, :query => "baguette" # part of the spanish notes response.should render_template('public_body/_search_ahead') - assigns[:xapian_requests].results.size.should == 1 - assigns[:xapian_requests].results[0][:model].name.should == public_bodies(:humpadink_public_body).name + assigns[:xapian_requests].results.map{|x|x[:model]}.should =~ [public_bodies(:humpadink_public_body)] end it "should not return matches for short words" do - get :search_typeahead, :q => "b" + get :search_typeahead, :query => "b" response.should render_template('public_body/_search_ahead') assigns[:xapian_requests].should be_nil end diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb index 4994c2a8f..25dce3f22 100644 --- a/spec/controllers/request_controller_spec.rb +++ b/spec/controllers/request_controller_spec.rb @@ -4,58 +4,125 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require 'json' describe RequestController, "when listing recent requests" do - fixtures :users, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data rebuild_xapian_index end it "should be successful" do - get :list, :view => 'recent' + get :list, :view => 'all' response.should be_success end it "should render with 'list' template" do - get :list, :view => 'recent' + get :list, :view => 'all' response.should render_template('list') end it "should filter requests" do get :list, :view => 'all' - assigns[:list_results].size.should == 2 + assigns[:list_results].map(&:info_request).should =~ InfoRequest.all + + # default sort order is the request with the most recently created event first + assigns[:list_results].map(&:info_request).should == InfoRequest.all( + :order => "(select max(info_request_events.created_at) from info_request_events where info_request_events.info_request_id = info_requests.id) DESC") + get :list, :view => 'successful' - assigns[:list_results].size.should == 0 + assigns[:list_results].map(&:info_request).should =~ InfoRequest.all( + :conditions => "id in ( + select info_request_id + from info_request_events + where not exists ( + select * + from info_request_events later_events + where later_events.created_at > info_request_events.created_at + and later_events.info_request_id = info_request_events.info_request_id + and later_events.described_state is not null + ) + and info_request_events.described_state in ('successful', 'partially_successful') + )") end it "should filter requests by date" do + # The semantics of the search are that it finds any InfoRequest + # that has any InfoRequestEvent created in the specified range + get :list, :view => 'all', :request_date_before => '13/10/2007' - assigns[:list_results].size.should == 1 + assigns[:list_results].map(&:info_request).should =~ InfoRequest.all( + :conditions => "id in (select info_request_id from info_request_events where created_at < '2007-10-13'::date)") + get :list, :view => 'all', :request_date_after => '13/10/2007' - assigns[:list_results].size.should == 1 - get :list, :view => 'all', :request_date_after => '10/10/2007', :request_date_before => '01/01/2010' - assigns[:list_results].size.should == 2 + assigns[:list_results].map(&:info_request).should =~ InfoRequest.all( + :conditions => "id in (select info_request_id from info_request_events where created_at > '2007-10-13'::date)") + + get :list, :view => 'all', :request_date_after => '13/10/2007', :request_date_before => '01/11/2007' + assigns[:list_results].map(&:info_request).should =~ InfoRequest.all( + :conditions => "id in (select info_request_id from info_request_events where created_at between '2007-10-13'::date and '2007-11-01'::date)") + end + + it "should make a sane-sized cache tag" do + get :list, :view => 'all', :request_date_after => '13/10/2007', :request_date_before => '01/11/2007' + assigns[:cache_tag].size.should <= 32 + end + + it "should list internal_review requests as unresolved ones" do + get :list, :view => 'awaiting' + + # This doesn’t precisely duplicate the logic of the actual + # query, but it is close enough to give the same result with + # the current set of test data. + assigns[:list_results].should =~ InfoRequestEvent.all( + :conditions => "described_state in ( + 'waiting_response', 'waiting_clarification', + 'internal_review', 'gone_postal', 'error_message', 'requires_admin' + ) and not exists ( + select * + from info_request_events later_events + where later_events.created_at > info_request_events.created_at + and later_events.info_request_id = info_request_events.info_request_id + )") + + + get :list, :view => 'awaiting' + assigns[:list_results].map(&:info_request).include?(info_requests(:fancy_dog_request)).should == false + + event = info_request_events(:useless_incoming_message_event) + event.described_state = event.calculated_state = "internal_review" + event.save! + rebuild_xapian_index + + get :list, :view => 'awaiting' + assigns[:list_results].map(&:info_request).include?(info_requests(:fancy_dog_request)).should == true end it "should assign the first page of results" do xap_results = mock_model(ActsAsXapian::Search, :results => (1..25).to_a.map { |m| { :model => m } }, - :matches_estimated => 103) + :matches_estimated => 1000000) InfoRequest.should_receive(:full_search). - with([InfoRequestEvent]," variety:sent", "created_at", anything, anything, anything, anything). + with([InfoRequestEvent]," (variety:sent OR variety:followup_sent OR variety:response OR variety:comment)", "created_at", anything, anything, anything, anything). and_return(xap_results) - get :list, :view => 'recent' + get :list, :view => 'all' assigns[:list_results].size.should == 25 + assigns[:show_no_more_than].should == RequestController::MAX_RESULTS end + it "should return 404 for pages we don't want to serve up" do + xap_results = mock_model(ActsAsXapian::Search, + :results => (1..25).to_a.map { |m| { :model => m } }, + :matches_estimated => 1000000) + lambda { + get :list, :view => 'all', :page => 100 + }.should raise_error(ActiveRecord::RecordNotFound) + end + end describe RequestController, "when showing one request" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views - before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should be successful" do @@ -80,24 +147,40 @@ describe RequestController, "when showing one request" do describe 'when handling an update_status parameter' do - - before do - mock_request = mock_model(InfoRequest, :url_title => 'test_title', - :title => 'test title', - :null_object => true) - InfoRequest.stub!(:find_by_url_title).and_return(mock_request) - end - it 'should assign the "update status" flag to the view as true if the parameter is present' do - get :show, :url_title => 'test_title', :update_status => 1 + get :show, :url_title => 'why_do_you_have_such_a_fancy_dog', :update_status => 1 assigns[:update_status].should be_true end - it 'should assign the "update status" flag to the view as true if the parameter is present' do - get :show, :url_title => 'test_title' + it 'should assign the "update status" flag to the view as false if the parameter is not present' do + get :show, :url_title => 'why_do_you_have_such_a_fancy_dog' assigns[:update_status].should be_false end + it 'should require login' do + session[:user_id] = nil + get :show, :url_title => 'why_do_you_have_such_a_fancy_dog', :update_status => 1 + post_redirect = PostRedirect.get_last_post_redirect + response.should redirect_to(:controller => 'user', :action => 'signin', :token => post_redirect.token) + end + + it 'should work if logged in as the requester' do + session[:user_id] = users(:bob_smith_user).id + get :show, :url_title => 'why_do_you_have_such_a_fancy_dog', :update_status => 1 + response.should render_template "request/show" + end + + it 'should not work if logged in as not the requester' do + session[:user_id] = users(:silly_name_user).id + get :show, :url_title => 'why_do_you_have_such_a_fancy_dog', :update_status => 1 + response.should render_template "user/wrong_user" + end + + it 'should work if logged in as an admin user' do + session[:user_id] = users(:admin_user).id + get :show, :url_title => 'why_do_you_have_such_a_fancy_dog', :update_status => 1 + response.should render_template "request/show" + end end describe 'when handling incoming mail' do @@ -123,7 +206,8 @@ describe RequestController, "when showing one request" do it "should download attachments" do ir = info_requests(:fancy_dog_request) - ir.incoming_messages.each { |x| x.parse_raw_email! } + ir.incoming_messages.each { |x| x.parse_raw_email!(true) } + get :show, :url_title => 'why_do_you_have_such_a_fancy_dog' response.content_type.should == "text/html" size_before = assigns[:info_request_events].size @@ -134,10 +218,12 @@ describe RequestController, "when showing one request" do get :show, :url_title => 'why_do_you_have_such_a_fancy_dog' (assigns[:info_request_events].size - size_before).should == 1 ir.reload - get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => ['hello.txt'] + + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => ['hello.txt'], :skip_cache => 1 response.content_type.should == "text/plain" response.should have_text(/Second hello/) - get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 3, :file_name => ['hello.txt'] + + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 3, :file_name => ['hello.txt'], :skip_cache => 1 response.content_type.should == "text/plain" response.should have_text(/First hello/) end @@ -149,7 +235,7 @@ describe RequestController, "when showing one request" do response.should have_text(/tënde/u) end - it "should generate valid HTML verson of plain text attachments " do + it "should generate valid HTML verson of plain text attachments" do ir = info_requests(:fancy_dog_request) receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) ir.reload @@ -158,6 +244,71 @@ describe RequestController, "when showing one request" do response.should have_text(/Second hello/) end + # This is a regression test for a bug where URLs of this form were causing 500 errors + # instead of 404s. + # + # (Note that in fact only the integer-prefix of the URL part is used, so there are + # *some* “ugly URLs containing a request id that isn't an integer” that actually return + # a 200 response. The point is that IDs of this sort were triggering an error in the + # error-handling path, causing the wrong sort of error response to be returned in the + # case where the integer prefix referred to the wrong request.) + # + # https://github.com/sebbacon/alaveteli/issues/351 + it "should return 404 for ugly URLs containing a request id that isn't an integer" do + ir = info_requests(:fancy_dog_request) + receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) + ir.reload + ugly_id = "55195" + lambda { + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ugly_id, :part => 2, :file_name => ['hello.txt.html'], :skip_cache => 1 + }.should raise_error(ActiveRecord::RecordNotFound) + + lambda { + get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ugly_id, :part => 2, :file_name => ['hello.txt'], :skip_cache => 1 + }.should raise_error(ActiveRecord::RecordNotFound) + end + it "should return 404 when incoming message and request ids don't match" do + ir = info_requests(:fancy_dog_request) + wrong_id = info_requests(:naughty_chicken_request).id + receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) + ir.reload + lambda { + get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => wrong_id, :part => 2, :file_name => ['hello.txt.html'], :skip_cache => 1 + }.should raise_error(ActiveRecord::RecordNotFound) + end + it "should return 404 for ugly URLs contain a request id that isn't an integer, even if the integer prefix refers to an actual request" do + ir = info_requests(:fancy_dog_request) + receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) + ir.reload + ugly_id = "%d95" % [info_requests(:naughty_chicken_request).id] + + lambda { + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ugly_id, :part => 2, :file_name => ['hello.txt.html'], :skip_cache => 1 + }.should raise_error(ActiveRecord::RecordNotFound) + + lambda { + get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ugly_id, :part => 2, :file_name => ['hello.txt'], :skip_cache => 1 + }.should raise_error(ActiveRecord::RecordNotFound) + end + it "should return 404 when incoming message and request ids don't match" do + ir = info_requests(:fancy_dog_request) + wrong_id = info_requests(:naughty_chicken_request).id + receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) + ir.reload + lambda { + get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => wrong_id, :part => 2, :file_name => ['hello.txt.html'], :skip_cache => 1 + }.should raise_error(ActiveRecord::RecordNotFound) + end + + it "should generate valid HTML verson of PDF attachments " do + ir = info_requests(:fancy_dog_request) + receive_incoming_mail('incoming-request-pdf-attachment.email', ir.incoming_email) + ir.reload + get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => ['fs_50379341.pdf.html'], :skip_cache => 1 + response.content_type.should == "text/html" + response.should have_text(/Walberswick Parish Council/) + end + it "should not cause a reparsing of the raw email, even when the result would be a 404 " do ir = info_requests(:fancy_dog_request) receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) @@ -205,7 +356,7 @@ describe RequestController, "when showing one request" do receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) lambda { - get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => ['http://trying.to.hack'] }.should raise_error(ActiveRecord::RecordNotFound) end @@ -219,12 +370,16 @@ describe RequestController, "when showing one request" do censor_rule.last_edit_editor = "unknown" censor_rule.last_edit_comment = "none" ir.censor_rules << censor_rule - - receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) - - get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => ['hello.txt'] - response.content_type.should == "text/plain" - response.should have_text(/xxxxxx hello/) + + begin + receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) + + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => ['hello.txt'], :skip_cache => 1 + response.content_type.should == "text/plain" + response.should have_text(/xxxxxx hello/) + ensure + ir.censor_rules.clear + end end it "should censor with rules on the user (rather than the request)" do @@ -237,12 +392,16 @@ describe RequestController, "when showing one request" do censor_rule.last_edit_comment = "none" ir.user.censor_rules << censor_rule - receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) - ir.reload + begin + receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) + ir.reload - get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => ['hello.txt'], :skip_cache => 1 - response.content_type.should == "text/plain" - response.should have_text(/xxxxxx hello/) + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => ['hello.txt'], :skip_cache => 1 + response.content_type.should == "text/plain" + response.should have_text(/xxxxxx hello/) + ensure + ir.user.censor_rules.clear + end end it "should censor attachment names" do @@ -273,9 +432,12 @@ describe RequestController, "when showing one request" do censor_rule.last_edit_editor = "unknown" censor_rule.last_edit_comment = "none" ir.censor_rules << censor_rule - - get :show, :url_title => 'why_do_you_have_such_a_fancy_dog' - response.body.should have_tag("p.attachment strong", /goodbye.txt/m) + begin + get :show, :url_title => 'why_do_you_have_such_a_fancy_dog' + response.body.should have_tag("p.attachment strong", /goodbye.txt/m) + ensure + ir.censor_rules.clear + end end it "should make a zipfile available, which has a different URL when it changes" do @@ -287,7 +449,7 @@ describe RequestController, "when showing one request" do old_path = assigns[:url_path] response.location.should have_text(/#{assigns[:url_path]}$/) zipfile = Zip::ZipFile.open(File.join(File.dirname(__FILE__), "../../cache/zips", old_path)) { |zipfile| - zipfile.count.should == 2 + zipfile.count.should == 1 # just the message } receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) get :download_entire_request, :url_title => title @@ -295,25 +457,29 @@ describe RequestController, "when showing one request" do old_path = assigns[:url_path] response.location.should have_text(/#{assigns[:url_path]}$/) zipfile = Zip::ZipFile.open(File.join(File.dirname(__FILE__), "../../cache/zips", old_path)) { |zipfile| - zipfile.count.should == 2 + zipfile.count.should == 3 # the message plus two "hello.txt" files } + + # The path of the zip file is based on the hash of the timestamp of the last request + # in the thread, so we wait for a second to make sure this one will have a different + # timestamp than the previous. + sleep 1 receive_incoming_mail('incoming-request-attachment-unknown-extension.email', ir.incoming_email) get :download_entire_request, :url_title => title assigns[:url_path].should have_text(/#{title}.zip$/) + assigns[:url_path].should_not == old_path response.location.should have_text(/#{assigns[:url_path]}/) zipfile = Zip::ZipFile.open(File.join(File.dirname(__FILE__), "../../cache/zips", assigns[:url_path])) { |zipfile| - zipfile.count.should == 4 + zipfile.count.should == 5 # the message, two hello.txt, the unknown attachment, and its empty message } - assigns[:url_path].should_not == old_path end end end describe RequestController, "when changing prominence of a request" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :info_request_events, :track_things # all needed as integrating views before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should not show hidden requests" do @@ -379,11 +545,11 @@ describe RequestController, "when changing prominence of a request" do ir.save! receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) - get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2 + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :skip_cache => 1 response.content_type.should == "text/html" response.should_not have_text(/Second hello/) response.should render_template('request/hidden') - get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 3 + get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 3, :skip_cache => 1 response.content_type.should == "text/html" response.should_not have_text(/First hello/) response.should render_template('request/hidden') @@ -398,7 +564,6 @@ end # end describe RequestController, "when searching for an authority" do - fixtures :public_bodies, :users # Whether or not sign-in is required for this step is configurable, # so we make sure we're logged in, just in case @@ -411,7 +576,7 @@ describe RequestController, "when searching for an authority" do get :select_authority, :query => "" response.should render_template('select_authority') - assigns[:xapian_requests].results.size == 0 + assigns[:xapian_requests].should == nil end it "should return matching bodies" do @@ -422,11 +587,24 @@ describe RequestController, "when searching for an authority" do assigns[:xapian_requests].results.size == 1 assigns[:xapian_requests].results[0][:model].name.should == public_bodies(:geraldine_public_body).name end + + it "should not give an error when user users unintended search operators" do + for phrase in ["Marketing/PR activities - Aldborough E-Act Free Schoo", + "Request for communications between DCMS/Ed Vaizey and ICO from Jan 1st 2011 - May ", + "Bellevue Road Ryde Isle of Wight PO33 2AR - what is the", + "NHS Ayrshire & Arran", + " cardiff", + "Foo * bax", + "qux ~ quux"] + lambda { + get :select_authority, :query => phrase + }.should_not raise_error(StandardError) + end + end end describe RequestController, "when creating a new request" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @user = users(:bob_smith_user) @@ -555,6 +733,58 @@ describe RequestController, "when creating a new request" do response.should redirect_to(:action => 'show', :url_title => ir2.url_title) end + + it 'should respect the rate limit' do + # Try to create three requests in succession. + # (The limit set in config/test.yml is two.) + session[:user_id] = users(:robin_user) + + post :new, :info_request => { :public_body_id => @body.id, + :title => "What is the answer to the ultimate question?", :tag_string => "" }, + :outgoing_message => { :body => "Please supply the answer from your files." }, + :submitted_new_request => 1, :preview => 0 + response.should redirect_to(:action => 'show', :url_title => 'what_is_the_answer_to_the_ultima') + + + post :new, :info_request => { :public_body_id => @body.id, + :title => "Why did the chicken cross the road?", :tag_string => "" }, + :outgoing_message => { :body => "Please send me all the relevant documents you hold." }, + :submitted_new_request => 1, :preview => 0 + response.should redirect_to(:action => 'show', :url_title => 'why_did_the_chicken_cross_the_ro') + + post :new, :info_request => { :public_body_id => @body.id, + :title => "What's black and white and red all over?", :tag_string => "" }, + :outgoing_message => { :body => "Please send all minutes of meetings and email records that address this question." }, + :submitted_new_request => 1, :preview => 0 + response.should render_template('user/rate_limited') + end + + it 'should ignore the rate limit for specified users' do + # Try to create three requests in succession. + # (The limit set in config/test.yml is two.) + session[:user_id] = users(:robin_user) + users(:robin_user).no_limit = true + users(:robin_user).save! + + post :new, :info_request => { :public_body_id => @body.id, + :title => "What is the answer to the ultimate question?", :tag_string => "" }, + :outgoing_message => { :body => "Please supply the answer from your files." }, + :submitted_new_request => 1, :preview => 0 + response.should redirect_to(:action => 'show', :url_title => 'what_is_the_answer_to_the_ultima') + + + post :new, :info_request => { :public_body_id => @body.id, + :title => "Why did the chicken cross the road?", :tag_string => "" }, + :outgoing_message => { :body => "Please send me all the relevant documents you hold." }, + :submitted_new_request => 1, :preview => 0 + response.should redirect_to(:action => 'show', :url_title => 'why_did_the_chicken_cross_the_ro') + + post :new, :info_request => { :public_body_id => @body.id, + :title => "What's black and white and red all over?", :tag_string => "" }, + :outgoing_message => { :body => "Please send all minutes of meetings and email records that address this question." }, + :submitted_new_request => 1, :preview => 0 + response.should redirect_to(:action => 'show', :url_title => 'whats_black_and_white_and_red_al') + end end @@ -600,6 +830,7 @@ describe RequestController, "when making a new request" do it "should fail if user is banned" do @user.stub!(:can_file_requests?).and_return(false) + @user.stub!(:exceeded_limit?).and_return(false) @user.should_receive(:can_fail_html).and_return('FAIL!') session[:user_id] = @user.id get :new, :public_body_id => @body.id @@ -610,10 +841,9 @@ end describe RequestController, "when viewing an individual response for reply/followup" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should ask for login if you are logged in as wrong person" do @@ -657,13 +887,11 @@ end describe RequestController, "when classifying an information request" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views - before(:each) do @dog_request = info_requests(:fancy_dog_request) @dog_request.stub!(:is_old_unclassified?).and_return(false) InfoRequest.stub!(:find).and_return(@dog_request) - load_raw_emails_data(raw_emails) + load_raw_emails_data end def post_status(status) @@ -901,7 +1129,11 @@ describe RequestController, "when classifying an information request" do session[:user_id] = @request_owner.id @dog_request = info_requests(:fancy_dog_request) InfoRequest.stub!(:find).and_return(@dog_request) - ActionController::Routing::Routes.filters.clear + @old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + end + after do + ActionController::Routing::Routes.filters = @old_filters end def request_url @@ -993,10 +1225,9 @@ end describe RequestController, "when sending a followup message" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should require login" do @@ -1076,10 +1307,9 @@ end describe RequestController, "sending overdue request alerts" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should send an overdue alert mail to creators of overdue requests" do @@ -1090,8 +1320,8 @@ describe RequestController, "sending overdue request alerts" do RequestMailer.alert_overdue_requests deliveries = ActionMailer::Base.deliveries - deliveries.size.should == 1 - mail = deliveries[0] + deliveries.size.should == 2 + mail = deliveries[1] mail.body.should =~ /promptly, as normally/ mail.to_addrs.first.to_s.should == info_requests(:naughty_chicken_request).user.name_and_email @@ -1118,8 +1348,8 @@ describe RequestController, "sending overdue request alerts" do RequestMailer.alert_overdue_requests deliveries = ActionMailer::Base.deliveries - deliveries.size.should == 1 - mail = deliveries[0] + deliveries.size.should == 2 + mail = deliveries[1] mail.body.should =~ /promptly, as normally/ mail.to_addrs.first.to_s.should == info_requests(:naughty_chicken_request).user.name_and_email end @@ -1143,8 +1373,8 @@ describe RequestController, "sending overdue request alerts" do RequestMailer.alert_overdue_requests deliveries = ActionMailer::Base.deliveries - deliveries.size.should == 1 - mail = deliveries[0] + deliveries.size.should == 2 + mail = deliveries[1] mail.body.should =~ /required by law/ mail.to_addrs.first.to_s.should == info_requests(:naughty_chicken_request).user.name_and_email @@ -1164,10 +1394,9 @@ end describe RequestController, "sending unclassified new response reminder alerts" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should send an alert" do @@ -1195,9 +1424,8 @@ end describe RequestController, "clarification required alerts" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should send an alert" do @@ -1249,9 +1477,8 @@ end describe RequestController, "comment alerts" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should send an alert (once and once only)" do @@ -1324,9 +1551,8 @@ end describe RequestController, "when viewing comments" do integrate_views - fixtures :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should link to the user who submitted it" do @@ -1348,7 +1574,6 @@ end describe RequestController, "authority uploads a response from the web interface" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do # domain after the @ is used for authentication of FOI officers, so to test it @@ -1434,11 +1659,9 @@ describe RequestController, "authority uploads a response from the web interface end describe RequestController, "when showing JSON version for API" do - - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should return data in JSON form" do @@ -1455,7 +1678,8 @@ describe RequestController, "when showing JSON version for API" do end describe RequestController, "when doing type ahead searches" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things + + integrate_views it "should return nothing for the empty query string" do get :search_typeahead, :q => "" @@ -1473,16 +1697,44 @@ describe RequestController, "when doing type ahead searches" do it "should return all requests matching any of the given keywords" do get :search_typeahead, :q => "money dog" response.should render_template('request/_search_ahead.rhtml') - assigns[:xapian_requests].results.size.should == 2 - assigns[:xapian_requests].results[0][:model].title.should == info_requests(:fancy_dog_request).title - assigns[:xapian_requests].results[1][:model].title.should == info_requests(:naughty_chicken_request).title + assigns[:xapian_requests].results.map{|x|x[:model].info_request}.should =~ [ + info_requests(:fancy_dog_request), + info_requests(:naughty_chicken_request), + info_requests(:another_boring_request), + ] end - it "should not return matches for short words" do + it "should not return matches for short words" do get :search_typeahead, :q => "a" response.should render_template('request/_search_ahead.rhtml') assigns[:xapian_requests].should be_nil end + + it "should do partial matches for longer words" do + get :search_typeahead, :q => "chick" + response.should render_template('request/_search_ahead.rhtml') + assigns[:xapian_requests].results.size.should ==1 + end + + it "should not give an error when user users unintended search operators" do + for phrase in ["Marketing/PR activities - Aldborough E-Act Free Schoo", + "Request for communications between DCMS/Ed Vaizey and ICO from Jan 1st 2011 - May ", + "Bellevue Road Ryde Isle of Wight PO33 2AR - what is the", + "NHS Ayrshire & Arran", + "uda ( units of dent", + "frob * baz", + "bar ~ qux"] + lambda { + get :search_typeahead, :q => phrase + }.should_not raise_error(StandardError) + end + end + + it "should return all requests matching any of the given keywords" do + get :search_typeahead, :q => "dog -chicken" + assigns[:xapian_requests].results.size.should == 1 + end + end diff --git a/spec/controllers/request_game_controller_spec.rb b/spec/controllers/request_game_controller_spec.rb index cc0808ef3..7247cd388 100644 --- a/spec/controllers/request_game_controller_spec.rb +++ b/spec/controllers/request_game_controller_spec.rb @@ -1,10 +1,8 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe RequestGameController, "when playing the game" do - - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things # all needed as integrating views before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should show the game homepage" do diff --git a/spec/controllers/track_controller_spec.rb b/spec/controllers/track_controller_spec.rb index 90d13495f..38a447640 100644 --- a/spec/controllers/track_controller_spec.rb +++ b/spec/controllers/track_controller_spec.rb @@ -36,17 +36,18 @@ end describe TrackController, "when sending alerts for a track" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things, :track_things_sent_emails include LinkToHelper # for main_url before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data rebuild_xapian_index end it "should send alerts" do # Don't do clever locale-insertion-unto-URL stuff - ActionController::Routing::Routes.filters.clear + old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + # set the time the comment event happened at to within the last week ire = info_request_events(:silly_comment_event) ire.created_at = Time.now - 3.days @@ -91,6 +92,9 @@ describe TrackController, "when sending alerts for a track" do TrackMailer.alert_tracks deliveries = ActionMailer::Base.deliveries deliveries.size.should == 0 + + # Restore the routing filters + ActionController::Routing::Routes.filters = old_filters end it "should send localised alerts" do @@ -110,10 +114,9 @@ end describe TrackController, "when viewing RSS feed for a track" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data rebuild_xapian_index end @@ -136,10 +139,9 @@ end describe TrackController, "when viewing JSON version of a track feed" do integrate_views - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data rebuild_xapian_index end diff --git a/spec/controllers/user_controller_spec.rb b/spec/controllers/user_controller_spec.rb index 399b275a7..1a701ad43 100644 --- a/spec/controllers/user_controller_spec.rb +++ b/spec/controllers/user_controller_spec.rb @@ -8,9 +8,9 @@ require 'json' describe UserController, "when showing a user" do integrate_views - fixtures :users, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should be successful" do @@ -28,6 +28,16 @@ describe UserController, "when showing a user" do response.should render_template('show') end + it "should distinguish between 'my profile' and 'my requests' for logged in users" do + session[:user_id] = users(:bob_smith_user).id + get :show, :url_name => "bob_smith", :view => 'requests' + response.body.should_not include("Change your password") + response.body.should match(/Your [0-9]+ Freedom of Information requests/) + get :show, :url_name => "bob_smith", :view => 'profile' + response.body.should include("Change your password") + response.body.should_not match(/Your [0-9]+ Freedom of Information requests/) + end + it "should assign the user" do get :show, :url_name => "bob_smith" assigns[:display_user].should == users(:bob_smith_user) @@ -35,22 +45,28 @@ describe UserController, "when showing a user" do it "should search the user's contributions" do get :show, :url_name => "bob_smith" - assigns[:xapian_requests].results.count.should == 2 + assigns[:xapian_requests].results.map{|x|x[:model].info_request}.should =~ InfoRequest.all( + :conditions => "user_id = #{users(:bob_smith_user).id}") + get :show, :url_name => "bob_smith", :user_query => "money" - assigns[:xapian_requests].results.count.should == 1 + assigns[:xapian_requests].results.map{|x|x[:model].info_request}.should =~ [ + info_requests(:naughty_chicken_request), + info_requests(:another_boring_request), + ] end -# Error handling not quite good enough for this yet -# it "should not show unconfirmed users" do -# get :show, :url_name => "silly_emnameem" -# assigns[:display_users].should == [ users(:silly_name_user) ] -# end + it "should not show unconfirmed users" do + begin + get :show, :url_name => "unconfirmed_user" + rescue => e + end + e.should be_an_instance_of(ActiveRecord::RecordNotFound) + end end describe UserController, "when signing in" do integrate_views - fixtures :users def get_last_postredirect post_redirects = PostRedirect.find_by_sql("select * from post_redirects order by id desc limit 1") @@ -86,7 +102,9 @@ describe UserController, "when signing in" do end it "should log in when you give right email/password, and redirect to where you were" do - ActionController::Routing::Routes.filters.clear + old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + get :signin, :r => "/list" response.should render_template('sign') post_redirect = get_last_postredirect @@ -97,6 +115,26 @@ describe UserController, "when signing in" do # response doesn't contain /en/ but redirect_to does... response.should redirect_to(:controller => 'request', :action => 'list', :post_redirect => 1) response.should_not send_email + + ActionController::Routing::Routes.filters = old_filters + end + + it "should not log you in if you use an invalid PostRedirect token, and shouldn't give 500 error either" do + old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + + post_redirect = "something invalid" + lambda { + post :signin, { :user_signin => { :email => 'bob@localhost', :password => 'jonespassword' }, + :token => post_redirect + } + }.should_not raise_error(NoMethodError) + post :signin, { :user_signin => { :email => 'bob@localhost', :password => 'jonespassword' }, + :token => post_redirect } + response.should render_template('sign') + assigns[:post_redirect].should == nil + + ActionController::Routing::Routes.filters = old_filters end # No idea how to test this in the test framework :( @@ -120,7 +158,9 @@ describe UserController, "when signing in" do end it "should confirm your email, log you in and redirect you to where you were after you click an email link" do - ActionController::Routing::Routes.filters.clear + old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + get :signin, :r => "/list" post_redirect = get_last_postredirect @@ -146,13 +186,14 @@ describe UserController, "when signing in" do get :confirm, :email_token => post_redirect.email_token session[:user_id].should == users(:unconfirmed_user).id response.should redirect_to(:controller => 'request', :action => 'list', :post_redirect => 1) + + ActionController::Routing::Routes.filters = old_filters end end describe UserController, "when signing up" do integrate_views - fixtures :users it "should be an error if you type the password differently each time" do post :signup, { :user_signup => { :email => 'new@localhost', :name => 'New Person', @@ -210,7 +251,6 @@ end describe UserController, "when signing out" do integrate_views - fixtures :users it "should log you out and redirect to the home page" do session[:user_id] = users(:bob_smith_user).id @@ -220,18 +260,21 @@ describe UserController, "when signing out" do end it "should log you out and redirect you to where you were" do - ActionController::Routing::Routes.filters.clear + old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new + session[:user_id] = users(:bob_smith_user).id get :signout, :r => '/list' session[:user_id].should be_nil response.should redirect_to(:controller => 'request', :action => 'list') + + ActionController::Routing::Routes.filters = old_filters end end describe UserController, "when sending another user a message" do integrate_views - fixtures :users it "should redirect to signin page if you go to the contact form and aren't signed in" do get :contact, :id => users(:silly_name_user) @@ -269,7 +312,6 @@ end describe UserController, "when changing password" do integrate_views - fixtures :users it "should show the email form when not logged in" do get :signchangepassword @@ -340,7 +382,6 @@ end describe UserController, "when changing email address" do integrate_views - fixtures :users it "should require login" do get :signchangeemail @@ -486,7 +527,6 @@ end describe UserController, "when using profile photos" do integrate_views - fixtures :users before do @user = users(:bob_smith_user) @@ -502,6 +542,13 @@ describe UserController, "when using profile photos" do post :set_profile_photo, { :id => @user.id, :file => @uploadedfile, :submitted_draft_profile_photo => 1, :automatically_crop => 1 } end + it "should return a 404 not a 500 when a profile photo has not been set" do + @user.profile_photo.should be_nil + lambda { + get :get_profile_photo, {:url_name => @user.url_name } + }.should raise_error(ActiveRecord::RecordNotFound) + end + it "should let you change profile photo if you're logged in as the user" do @user.profile_photo.should be_nil session[:user_id] = @user.id @@ -535,8 +582,6 @@ describe UserController, "when using profile photos" do end describe UserController, "when showing JSON version for API" do - - fixtures :users it "should be successful" do get :show, :url_name => "bob_smith", :format => "json" diff --git a/spec/fixtures/comments.yml b/spec/fixtures/comments.yml index ed1925bfa..b73385a55 100644 --- a/spec/fixtures/comments.yml +++ b/spec/fixtures/comments.yml @@ -8,4 +8,13 @@ silly_comment: user_id: "2" created_at: 2008-08-13 01:25:17.486939 +sensible_comment: + id: 2 + body: This a wise and helpful annotation. + info_request_id: 106 + visible: t + comment_type: request + user_id: 1 + created_at: 2008-08-13 01:25:17.486939 + updated_at: 2008-08-13 01:25:17.486939 diff --git a/spec/fixtures/files/incoming-request-pdf-attachment.email b/spec/fixtures/files/incoming-request-pdf-attachment.email new file mode 100644 index 000000000..1163725ec --- /dev/null +++ b/spec/fixtures/files/incoming-request-pdf-attachment.email @@ -0,0 +1,543 @@ +MIME-Version: 1.0
+Received: by 10.100.197.1 with HTTP; Sun, 8 Jan 2012 01:16:15 -0800 (PST)
+Reply-To: bar@frob.com
+Date: Sun, 8 Jan 2012 09:16:15 +0000
+Delivered-To: seb.bacon@gmail.com
+Message-ID: <CACwNqZkZDZiLtxD=CmE55kBGCBgnDVS1YGtrdC=tOhz+8dpokA@mail.gmail.com>
+Subject: Hello
+From: EMAIL_FROM
+To: FOI Person <EMAIL_TO>
+Content-Type: multipart/mixed; boundary=0016e644b8d0e3913b04b600bbe4
+
+--0016e644b8d0e3913b04b600bbe4
+Content-Type: text/plain; charset=ISO-8859-1
+
+PDF attachment enclosed.
+
+--0016e644b8d0e3913b04b600bbe4
+Content-Type: application/pdf; name="fs_50379341.pdf"
+Content-Disposition: attachment; filename="fs_50379341.pdf"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_gx5ul6jw0
+
+JVBERi0xLjUNJeLjz9MNCjIwNCAwIG9iag08PC9MaW5lYXJpemVkIDEvTCAyOTYwNy9PIDIwNi9F
+IDExNTE2L04gNS9UIDI5MjMzL0ggWyA0NjUgMjQwXT4+DWVuZG9iag0gICAgICAgICAgICAgICAg
+DQoyMTEgMCBvYmoNPDwvRGVjb2RlUGFybXM8PC9Db2x1bW5zIDQvUHJlZGljdG9yIDEyPj4vRmls
+dGVyL0ZsYXRlRGVjb2RlL0lEWzxBRjMzQjg1MTFGOUJDMjI5RDlFMDM1QzI3RTBFODIyRT48MzM0
+QjkwQThFRDQ0OTg0MUJDMUM3QTE5NkM3NzNDRDg+XS9JbmRleFsyMDQgMjFdL0luZm8gMjAzIDAg
+Ui9MZW5ndGggNTcvUHJldiAyOTIzNC9Sb290IDIwNSAwIFIvU2l6ZSAyMjUvVHlwZS9YUmVmL1db
+MSAyIDFdPj5zdHJlYW0NCmjeYmJkEGBgYmDaCCJ8gQRjCJBgeQ4k2BOAhNA5IHHvLwMTI0M0kMXA
+wEgG8Z+R6StAgAEA0IkH/g0KZW5kc3RyZWFtDWVuZG9iag1zdGFydHhyZWYNCjANCiUlRU9GDQog
+ICAgICAgIA0KMjI0IDAgb2JqDTw8L0MgMTQxL0ZpbHRlci9GbGF0ZURlY29kZS9JIDE2NC9MIDEy
+NS9MZW5ndGggMTQwL08gMTA5L1MgNzQ+PnN0cmVhbQ0KaN5iYGBgYmBglgORrM4MfAwIwMfADIQs
+DBwHHPrW+F8MPsDA9aTIgkHq4Tpx3gL2BvYG9YYKhgq4FFAHFwND1W8gzQrEbCAjGEMYeBkYlpyR
+ZF8yo/UCSISbgaH2MEgGiI6DLWHoug3hM3QBMQ8DQz2IBjqHOxAszrwASAkyMMy5CdXmDhBgAPuw
+HeMNCmVuZHN0cmVhbQ1lbmRvYmoNMjA1IDAgb2JqDTw8L0xhc3RNb2RpZmllZChEOjIwMTExMTIy
+MTUwMTQzKS9NYXJrSW5mbzw8L0xldHRlcnNwYWNlRmxhZ3MgMC9NYXJrZWQgdHJ1ZT4+L01ldGFk
+YXRhIDE1IDAgUi9PQ1Byb3BlcnRpZXM8PC9EPDwvT3JkZXJbXS9SQkdyb3Vwc1tdPj4vT0NHc1sy
+MTIgMCBSXT4+L091dGxpbmVzIDI4IDAgUi9QYWdlTGFiZWxzIDIwMCAwIFIvUGFnZUxheW91dC9P
+bmVDb2x1bW4vUGFnZXMgMjAyIDAgUi9QaWVjZUluZm88PC9NYXJrZWRQREY8PC9MYXN0TW9kaWZp
+ZWQoRDoyMDExMTEyMjE1MDE0Myk+Pj4+L1N0cnVjdFRyZWVSb290IDM3IDAgUi9UeXBlL0NhdGFs
+b2c+Pg1lbmRvYmoNMjA2IDAgb2JqDTw8L0NvbnRlbnRzIDIwOCAwIFIvQ3JvcEJveFswIDAgNTk1
+IDg0Ml0vTWVkaWFCb3hbMCAwIDU5NSA4NDJdL1BhcmVudCAyMDIgMCBSL1Jlc291cmNlczw8L0Nv
+bG9yU3BhY2U8PC9DUzAgMjEzIDAgUj4+L0V4dEdTdGF0ZTw8L0dTMCAyMTQgMCBSPj4vRm9udDw8
+L1RUMCAyMTYgMCBSL1RUMSAyMTggMCBSL1RUMiAyMjAgMCBSL1RUMyAyMjIgMCBSPj4vUHJvY1Nl
+dFsvUERGL1RleHQvSW1hZ2VDXS9Qcm9wZXJ0aWVzPDwvTUMwIDIyMyAwIFI+Pi9YT2JqZWN0PDwv
+SW0wIDIwOSAwIFI+Pj4+L1JvdGF0ZSAwL1N0cnVjdFBhcmVudHMgMC9UYWJzL1MvVHlwZS9QYWdl
+Pj4NZW5kb2JqDTIwNyAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvRmlyc3QgMTAwL0xlbmd0
+aCA5MDEvTiAxMi9UeXBlL09ialN0bT4+c3RyZWFtDQpo3qRW227bOBDVp/AxeXAp3kmgMOA4cRJg
+3QbrtCkQ5IG1GUeoLAWyim1/fncPKduRi6ZtHAv0kHPhcEZnhuKMk5xwJggzDFQS5uJaEa4i1UQq
+B2qIkxbUEsYUhMwRxi04PCdM5QITBlNjMOFRjM04uAo7vn1L3/lVOLoIfhGaSV23oTmm198fA30/
+Pqcf1n4ZoDNugm/r5rK6r59WR//+l42yedZkdfY581mbkewqO80m2RSrL1mAhGQue5Mx0HtoxfVN
+ootjOvv6uY1+Rk37T918GQ7pFZydlWEVqhZetvKLyTD9bunleHzi12GBGHJk5u87aL2/Ive+XOO4
+V1PC6Gy0Wc6mJH+T8y6Us2/t+az1baD1YycfDmE7Ws/hirA8V3TsHy9CsXxIy5yehk424Lmjk9Iv
+10RwOqmr9uSk/nY7wAuAyBAmpUkWd0k48aui/H70MTQLX/njxIv5pRtOYszaJrTzB/qubla+TKyb
+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
+nt6KLkxW8wKXBDiU1bOOPROv9YgGm+pPWzMW/SuZyvylk3QL3YNhFjMyyuLAJnAwSGBG/yUCtz3y
+hNMTXSZpUblPUtR7QIBtfTqer0WN5w7Qi11AIQ8t38bJj6tj0gWFPOk7RN/tLXrpbL9TMGPJ5upv
+ErISAzH6KfWuZuzklTxRqPYpniXq7BWQi7vaGxn7jA28ahgYQTeOM/1OZKCa5thEX8uB0Ywo9fvH
+n4xmNrC9nxPqfD7TJEIH7XwOh/kV/nP8Wrh3KC0Od6lqti3mths5ubCKOdRmmnXNFuzCtVsLHCpk
+4tnYMIk9WguPnBzVbddk7MkudN+u0ypfmn/ATlTiqRK7NVBvWNHRX1N0NJxx/s9tRO1C1GSjbegY
+BAbFzFvRdmlUctZMsZkHrOuLbYE8uFXIeaGTY+Seq/cU1lg420tqVHtlzr/VLVayle5owdBo2OVA
+6VIvJbzIH7pIuHQ1ItpJjXBkX2DxaF0Y1bat2vDh2iR/ozbWi8vsbW2Oy+qRfQIXhOLdFTLGvbqr
+UFUIXdyXHJsoVpZ2pe0r906dGjuWBEYJg9jf6c34Isl6YZ65vzVqb8nzwWqY2OH8y0rlK5EqneJL
+pUS6RncL/6pUqtzCo391Vf6ARfs6gWgpDyAgkcc51gsfU/168eg/7JfqdSfB/1hiH6jAn5u7e/GE
+uTUq7up2dw1YSwLmTrfQY7FG+2TU1XHbVhgnCJvp11xqkkYNV+bOdycgSCSDlHP8P1rA1IS6X3+F
+4tTd0Ul7KCJNNL0XtNjkSf/57PxfgAEA+xIfxA0KZW5kc3RyZWFtDWVuZG9iag0yMDkgMCBvYmoN
+PDwvQml0c1BlckNvbXBvbmVudCA4L0NvbG9yU3BhY2UgMjEzIDAgUi9GaWx0ZXIvRENURGVjb2Rl
+L0hlaWdodCAyMjUvTGVuZ3RoIDQ0MDIvU3VidHlwZS9JbWFnZS9UeXBlL1hPYmplY3QvV2lkdGgg
+MzAxPj5zdHJlYW0NCv/Y/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8V
+GBMTFRMTGBcSFBQUFBIXFxscHhwbFyQkJyckJDUzMzM1Ozs7Ozs7Ozs7OwENCwsNDg0QDg4QFA4P
+DhQUEBEREBQdFBQVFBQdJRoXFxcXGiUgIx4eHiMgKCglJSgoMjIwMjI7Ozs7Ozs7Ozs7/8AAEQgA
+4QEtAwEiAAIRAQMRAf/EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEA
+AAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGh
+sUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0
+lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhED
+ITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2
+dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQAC
+EQMRAD8A9VSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkk
+lKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUp
+JJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkk
+klKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSU
+pJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkk
+kklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSS
+UpJJJJSkkkklKSSSSUpJQstqqbute1jfFxAH4qQIcAWmQeCElLpJJJKUkkkkpSSSSSlJJJJKUkkk
+kpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSodW
+630vo1TLupXiiu12xjiHOl0THtBRAJNDVRIGp0fIPrz1LNzvrHm15L3eni2upoqJ9rWt0BA/lcyu
+k/xT9TzX5OX0173WYjKhawEyGO3BsN8N0o31gu/xbddyPtd+e6jKIAfbS143Acbg6sg/Fb/1KZ9U
+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
+CxG2tqL5vr9Gxx2/RBceGtDZ957KLfq9hsxqMeqy6o42/wBO5j4sAtM2N42wfCNOy1EkOI908I7O
+Ufq10vYK2te2sSAwOMAFtVcfdU1R/wCbHTyaSXWuGMSKGuLXBjCWu9Mbmn2y0efmtdJHjl3Vwx7K
+SSSTUqSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSS
+SSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJ
+SkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKS
+SSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJ
+JSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklK
+SSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJ
+JJSkkkklKSXyqkkp+qkl8qpJKfqpJfKqSSn6qSXyqkkp+qkl8qpJKfqpJfKqSSn6qSXyqkkp+qkl
+8qpJKfqpJfKqSSn6qSXyqkkp+qkl8qpJKfqpJfKqSSn6qSXyqkkp+qkl8qpJKfqpJfKqSSn6qSXy
+qkkp+qkl8qpJKfqpJfKqSSn6qSXyqkkp/9kNCmVuZHN0cmVhbQ1lbmRvYmoNMjEwIDAgb2JqDTw8
+L0FsdGVybmF0ZS9EZXZpY2VSR0IvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCAyNTk3L04gMz4+
+c3RyZWFtDQpo3pyWd1RU1xaHz713eqHNMNIZepMuMID0LiAdBFEYZgYYygDDDE1siKhARBERAUWQ
+oIABo6FIrIhiISioYA9IEFBiMIqoqGRG1kp8eXnv5eX3x73f2mfvc/fZe5+1LgAkTx8uLwWWAiCZ
+J+AHejjTV4VH0LH9AAZ4gAGmADBZ6am+Qe7BQCQvNxd6usgJ/IveDAFI/L5l6OlPp4P/T9KsVL4A
+AMhfxOZsTjpLxPkiTsoUpIrtMyKmxiSKGUaJmS9KUMRyYo5b5KWffRbZUczsZB5bxOKcU9nJbDH3
+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
+/qb8sa0eW5sLJAr4BcPbbLfVbEdt527v32G+Y/+OT4XswmtFJkXlRR+KWcXXvjL9quKrhZ2xO/tL
+LEsO7sLs4u0a2u2w+2ipdGlO6dge3z3tZfSywrLXe6P2Xi1fVl6zj7BPuG+kwqeic7/m/l37P1TG
+V96pcq5qrVaq3lE9d4B9YPCg48GWGuWaopr3h7iH7tZ61LbXadeVH8Yczjj8tD60vvdrxteNDQoN
+RQ0fj/COjBwNPNrTaNXY2KTUVNIMNwubp45FHrv5jes3nS2GLbWttNai4+C48Pizb6O/HTrhfaL7
+JONky3da31W3UdoK26H27PaZjviOkc7wzoFTK051d9l2tX1v9P2R02qnq87Inik5Szibf3bhXM65
+2fOp56cvxF0Y647qfnBx1cXbPQE9/Ze8L1257H75Yq9T77krdldOX7W5euoa41rHdcvr7X0WfW0/
+WPzQ1m/Z337D6kbnTeubXQPLB84OOgxeuOV66/Jtr9vX76y8MzAUMnR3OHJ45C777uS9pHsv72fc
+n3+w6SH6YeEjqUflj5Ue1/2o92PriOXImVHX0b4nQU8ejLHGnv+U/tOH8fyn5KflE6oTjZNmk6en
+3KduPlv9bPx56vP56YKfpX+ufqH74rtfHH/pm1k1M/6S/3Lh1+JX8q+OvF72unvWf/bxm+Q383OF
+b+XfHn3HeNf7Puz9xHzmB+yHio96H7s+eX96uJC8sPCbAAMA94Tz+w0KZW5kc3RyZWFtDWVuZG9i
+ag0xIDAgb2JqDTw8L0NvbnRlbnRzIDIgMCBSL0Nyb3BCb3hbMCAwIDU5NSA4NDJdL01lZGlhQm94
+WzAgMCA1OTUgODQyXS9QYXJlbnQgMjAyIDAgUi9SZXNvdXJjZXM8PC9Db2xvclNwYWNlPDwvQ1Mw
+IDIxMyAwIFI+Pi9FeHRHU3RhdGU8PC9HUzAgMjE0IDAgUj4+L0ZvbnQ8PC9UVDAgMjE2IDAgUi9U
+VDEgMjE4IDAgUi9UVDIgMjAgMCBSL1RUMyAyMjAgMCBSPj4vUHJvY1NldFsvUERGL1RleHQvSW1h
+Z2VDXS9Qcm9wZXJ0aWVzPDwvTUMwIDIyMyAwIFI+Pi9YT2JqZWN0PDwvSW0wIDIwOSAwIFI+Pj4+
+L1JvdGF0ZSAwL1N0cnVjdFBhcmVudHMgMS9UYWJzL1MvVHlwZS9QYWdlPj4NZW5kb2JqDTIgMCBv
+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
+b8f62e11vON51u8m7JtegW7OOnTeWQvI2mrcrCSXdcUYMpHBTk6ScY63jOsiSHPuYhkwbQy5LLaY
+DVGASIyIXdtVSEbbBZQiSbEz9t3yxM5lYUBwglaOvbMlo7mSnez29K3G6/G4dw/x69K5XrsAWZhv
+3oz+ekMgXtDFYLCtptbUWYhtltT11L/2ekJzgFgn12Qxr5cIBVVBFzfFU1MnzywhvasVTUTqL/L+
+pjuiHxTMYLi54p9vrisTpt9dy445jQTWooWp+fBfSl/ihv6CS+oQDa0KI+a0mbr2drphIrcDZM+5
+NqUaXArChsH1DrHnhTbXQgYvLBckrJtKYc8j/0TKh/qy3pEXjhc2GCBf6/4h2ooizStssYxrjKq9
+dPa3tdltqcWGpZqwvtWabqcZrGZ3Umu5h+fZ3Z18hZXdvUJa7OMAd7DIiePQo5XM87EFnmdP1Vqf
+Dhxm76TUCMhsaZ4+sS3y3fClKeDHe8CiumCeaB3s5PDq0hc1zbuvy4xJk9QToylt36S0HH0dBa7n
+hCbSGBcJjA6F2Qlp9/wCxbm7zkl9KHJtNq0XtGhxoj+fR/8TYAA9ZWkRDQplbmRzdHJlYW0NZW5k
+b2JqDTMgMCBvYmoNPDwvQ29udGVudHMgNCAwIFIvQ3JvcEJveFswIDAgNTk1IDg0Ml0vTWVkaWFC
+b3hbMCAwIDU5NSA4NDJdL1BhcmVudCAyMDIgMCBSL1Jlc291cmNlczw8L0NvbG9yU3BhY2U8PC9D
+UzAgMjEzIDAgUj4+L0V4dEdTdGF0ZTw8L0dTMCAyMTQgMCBSPj4vRm9udDw8L1RUMCAyMTYgMCBS
+L1RUMSAyMTggMCBSL1RUMiAyMjAgMCBSL1RUMyAyMCAwIFI+Pi9Qcm9jU2V0Wy9QREYvVGV4dC9J
+bWFnZUNdL1Byb3BlcnRpZXM8PC9NQzAgMjIzIDAgUj4+L1hPYmplY3Q8PC9JbTAgMjA5IDAgUj4+
+Pj4vUm90YXRlIDAvU3RydWN0UGFyZW50cyAyL1RhYnMvUy9UeXBlL1BhZ2U+Pg1lbmRvYmoNNCAw
+IG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDIzMDE+PnN0cmVhbQ0KSIm0V11v27gS
+ffevmEd5sZZJ6sPyYrFAPtrbXqA37Y2BfUj7oEh0rFtbciUqbv79nSEpiXKUbLfAokBrq9Zw5syZ
+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
+2sacOR7HhmG8bj8SkhZUGgKsTzA4VfVX2rp5+tS8PGULHWY0EhQLEzhIvOLkUMwjIwDngGEbajPN
+ernhZjM6If0X9pv4wf2WTO+34Gf3m+i5Lc7X27vqJB9l/evzPuI1T74CG02XGK4KExI30hAk8dRC
+WtgwowViqYBbCGNqfaevg/5POdhu4nmXkuaC4+dz8vMI4bXMjPz3UVaDdwm6nPgoJb0pzL1DUZ0H
+HOQsVZ0ROkBbqjM7oPMRzOfd4CCnOVy0D21D113OfXgnjZk4A3pgub2vmiguQZ1psZh33FN9muY2
+IvOp3S2My1n70brnDaJMM/2iUPSMfLOZLS9qVVBbiZsXSqUZOby75WWlFOr7l+XlZfUd7mJBviVc
++RGPAwhY5MdxyAEfc4Flflnetvfq6Shh+baqSMmWG/3tY/qAJWnOdIS/uQIcAgb62+VmdOHtFxFB
+zegfc3DUwXmwY4EFizXNAdclB7qkzezbLGDcD3WmMYo+oc1DP0TqzP6EcnzccIh9KWKmmv4UjGhw
+or8+zf4vwAAUVrhNDQplbmRzdHJlYW0NZW5kb2JqDTUgMCBvYmoNPDwvQ29udGVudHMgNiAwIFIv
+Q3JvcEJveFswIDAgNTk1IDg0Ml0vTWVkaWFCb3hbMCAwIDU5NSA4NDJdL1BhcmVudCAyMDIgMCBS
+L1Jlc291cmNlczw8L0NvbG9yU3BhY2U8PC9DUzAgMjEzIDAgUj4+L0V4dEdTdGF0ZTw8L0dTMCAy
+MTQgMCBSPj4vRm9udDw8L1RUMCAyMTYgMCBSL1RUMSAyMTggMCBSPj4vUHJvY1NldFsvUERGL1Rl
+eHQvSW1hZ2VDXS9Qcm9wZXJ0aWVzPDwvTUMwIDIyMyAwIFI+Pi9YT2JqZWN0PDwvSW0wIDIwOSAw
+IFI+Pj4+L1JvdGF0ZSAwL1N0cnVjdFBhcmVudHMgMy9UYWJzL1MvVHlwZS9QYWdlPj4NZW5kb2Jq
+DTYgMCBvYmoNPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCA5NjY+PnN0cmVhbQ0KSIl8Vdtu
+2zgQfddXzKO0gCiSom5FUSB2mt0sELTZCNiHNA+KRNvctUVXouPm7zukJMsx0iJAYjPknMsccr57
+FHLBISkSQjmEuWAkT6GT3r9/QOt99yihMdjVIn+zCb/aTW5P9OcDhXXvMSGIYAWlaQwUfxjNQQhK
+4rgoClzLYgH1zotudxSutXfvRVedUauqNvDxY3RlTFVvZAOPUan38BQtFvoHPKac4LGsQMgYa3CW
+kjQXGeQ0JYIjuafo4fBsXvcSor9k1cgOotJ9+1qtVVsZpVv49GlxvQREvPcWpRd9WUJ0t6TgFif2
+UVkiZShXXoiiKcWPNbiPLIHyCIwPojhMlMRAoNx5/j9yJTvZ1vIDwM1DQuOsiAWDoPzP+3yHIJ/L
+4a9FPxM98kJoNkBTC0rfhUtz7MwINxeOvlrv7pa317h5LvdbJTGEnPAYPzfeo7/Uu53qe7RJdgRu
+W6iCUPhN00lcbNdgNsFT+bfnKvG5Uu5IpoSKxBJuPF9CrXf7bYWmt+Yb57yH2vKclRS5UyI4nhqU
+cEGyXAwFHn0dcB897NoeUSU43EGCQwvH3SEjnF2wDxLfCoBN1cOxU8bIFox2ZWp9aOu51si8IGkx
+Eldb2Hf6RTVW7vqgmgpJAMZGtQbJVFtndzgcmcH9Tr4oeeyhahuQP5zywbDK4Jo77Q46BcJ11uLH
+Ft93dYcK0G/0YdvMe/OTzYNwnpJisvnRh2cJFdKT0BvsVeKvgzD2R7vo3KF08Gw4e+4Ziq2xuQNR
+hx1gw4OQ+7CWaGK13b5aEGxnEGb+3v5nKw1ezaMyuHVjF1QLnMJRd/9bzU312hMYKFzmkk25HBRl
+p4iHQxBHN9He91LN4f1LwkgaT8lz/ZlTP8M4RO4MZyROTxaWmzdhxdQEqd9DV6keVVbQHnYXbnLn
+JstInJ2qPGPe9ApNsY/O3lplA4xtx0g864O5DHE898ZRCsdyZ5FyWVXb8fZYhsqFEY3fuHI3X26v
+rNrLPmMpkZ+YEbAKz2+2uxiqXeluJ939du2eb7aYlU7UbL3z2JjJNLxrVcD8wTrjvDNByHxLsJew
+q/D2dT00ai5/9gRl4/Wjs5ENtNrACnMHxyBMfRSL8bJ4fa3xKUeTN6o/C/gQn7HImX2qfZG9UWv3
+6pM3z++vZs1CG6N3F+NGZCRhOG1impA0xYccl9nFsLnR2vxu2LydMvbhPwX08m1PpnhMTzsq44V9
+LZiTJZyQEkdtTBkRjl+a4v+xAsMpNI3iX4CMhxI6aJgHyDiU3K9776cAAwDczA1EDQplbmRzdHJl
+YW0NZW5kb2JqDTcgMCBvYmoNPDwvQW5ub3RzIDE5IDAgUi9Db250ZW50cyA4IDAgUi9Dcm9wQm94
+WzAgMCA1OTUgODQyXS9NZWRpYUJveFswIDAgNTk1IDg0Ml0vUGFyZW50IDIwMiAwIFIvUmVzb3Vy
+Y2VzPDwvQ29sb3JTcGFjZTw8L0NTMCAyMTMgMCBSPj4vRXh0R1N0YXRlPDwvR1MwIDIxNCAwIFI+
+Pi9Gb250PDwvVFQwIDIxNiAwIFIvVFQxIDIxOCAwIFIvVFQyIDIyMCAwIFI+Pi9Qcm9jU2V0Wy9Q
+REYvVGV4dC9JbWFnZUNdL1hPYmplY3Q8PC9JbTAgMjA5IDAgUj4+Pj4vUm90YXRlIDAvU3RydWN0
+UGFyZW50cyA0L1RhYnMvUy9UeXBlL1BhZ2U+Pg1lbmRvYmoNOCAwIG9iag08PC9GaWx0ZXIvRmxh
+dGVEZWNvZGUvTGVuZ3RoIDE1NDI+PnN0cmVhbQ0KSIm0V1uPm0YUfvevOE8VjsJ4btxWUdS9eNNN
+kybdRcpDtw8sxjapDQ7gdfbf98wAZoyxU0WqvNZimPOdy3duXIWjyWVRpfMoruDNm7dvr26uYTR5
+90BhUY4mYUiBQTgf2ZRQSvEyBn3JHAh3wDhQ/OA/lxMhwQskkZwKCNcj6z6ZJ0WSxckFwO2DQ4UX
+CMlgHH5VuKzGpQqRKiwKNicMRW9GVnuItodsJyABdbqHLCA8ALQED8xGlqPuTT+i6dNBh76NBGVE
+SpAecV2UQ5MZ2gpFMvoC2egqNNR1XjVCjjrfeKW1oxKt7E8EpuBLDk7gEMrB9iUjvqthXyEuPiYo
+qO4G/sEh/Nno/jZiEk1hAaWuqDVTH6SkRIggCPCeh6GN16PJ3ZrCTY5a1UeZ/Bk9nHy8vrtBoZa6
+MORN1BRj3p4xxBxiTDhoSstYulhWkM8h2mySaAVgRtWlxMfzzCMORlEQx8cwSu3FvGcMM4w5yB9v
+z3bfDtf3iGjt4JTsNRuw3IA104cRVzgKdnacOb2s9bRuItz2/DStlkkBm6ioXmAZlYA/odBxqPI2
+DgpTI8kOSQP5hNU4f1nRIhq7VpqVKLhMS7hJ4rRM8wz+yKs0ThSagh7/Hb6vsXiHpZmxNZrNCFe1
+pSBv06Ks7CpF+8IifdpmaMqjNZbW3ZhZ2Twv1lGlNCja8E5VogGPYwJ3xrPoKd9WWnPtS1lb0C9n
+6jQl2KjHMtsUeZyUJayjF3hKIH+qojRLZjAv8vWFkRoGQaIlaNBBhomDJf5T7jW+QW19X69s9Yav
+EPfd/TX8Au/ux7a0Pu+hy9cnhJ1WeB+IGLqQIKL1+RNc5d8hEJS+HnbcPcQQneeiBfkwvbuePoTT
++xMQ3gEE61OjPfswZeDfvB/brnXCGX/PgNlXkVTetc6+THBovEGbPEqKMFldAMVAYOUK6WBBDEIy
+ajBi3UbfUYgxF7gMQHJHnJBiB/kj9i4okOk6SlF3J/ewibJOtOsN1zi64rLuLgBlnI06XiXx3LZg
+0y7FqiZHxo71K+bech1XJVlgJpZjbqVkkT+T7T915cKi6Tk4fw6bzpE3Q9XABQkCx6hy60vyVKZV
+cqGH2r4pMmyw0gUHZ1Qg666IlAgMoSF6MhjyR8Fw6yRXeE00drsd+Yr+b8c2t8oqxcsYnU8a5yeL
+rbo1i3CcYyVO4nxbVKUdZTO762g6xLaOsWFlG9xyMuwiRTvc2kVE8eRBC+yElfqOMVu3aIR0LP1A
+WT1LvpNlhb/Wg6XBnK42Fk2VuQ2V0nVbKoe5dA/qyuY+8U8wYQgdlnQ9/FQqczY43pj/k/PNaDdB
+f77dzeEl38IuLZfGQMNhpSdV1B9Tr/XpGJOp7vfd6Ot3c+WIUUKAf8t8Z+pY5dlCy5vrh2+UzQ5n
+bz1vDzNDUg+zHqTvEK9ZCXBuuEEAtiCubFIjwUpdJc9RVoGyotSDqTdj+5Nf1R+j5pS1zGG5H0O7
+uijJiWwIBojVjcoWxoSzOB9eY+j/wPNl9tJuGri/XdYUlMt8u5rh9O5YONrgHeL6bROAMimek9nY
+9izFp4plFxIkC/OB+4MrDA3qFUajGdF9tOJolWSzqMDhPYteSmWdwp1FVaJ07JZpvM+xzjheG4d5
+wFoPl8N7Fd4qk6w6QRVnA3txRxcumkqBwdkwCjfH2fAR8eMj8mAg7BcN7vXH9EO6UJvWI6eO+T2s
+Re2Bb6RA//h//R69bKlUVC8qZ8cb7/YmLCoR/LAdcveYiqONwz3aOG6jlwQeNuo9shjG9Q63vyLf
+bvSG9DHKokVS6HTtudfEzqXnXfRPWex2FnOdSDWWOblUX1EbaNtbrlVV5diz1uu0VAmcFI+c8xI+
+zeeYx9rik6biiAr8s6YGZ01VV6J5y9BQ5gLyEq/S+TyB3/JtmQwlQxMtT5yzQAy8gQ7Ta2so0wLs
+BQV8iLLT2l3ieWe5EgOFbqqvAUyl6WpdrnBiDah0iM/OKuMDXaXTpcUNVdfLpFymxaB3DjlfZkKc
+8ivoN18NZah9+D0A5/L2VEglcb0jzdNw9K8AAwBBYUiqDQplbmRzdHJlYW0NZW5kb2JqDTkgMCBv
+YmoNPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0ZpcnN0IDUvTGVuZ3RoIDI5L04gMS9UeXBlL09ialN0
+bT4+c3RyZWFtDQpo3jK0VDBQiDYyB5JBCkZmEMoURMUCBBgASDEFUQ0KZW5kc3RyZWFtDWVuZG9i
+ag0xMCAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvRmlyc3QgNTYvTGVuZ3RoIDYxNy9OIDgv
+VHlwZS9PYmpTdG0+PnN0cmVhbQ0KaN7EU0tP3DAQ/is+0sPGbydGCHWXdgUSQhVL2UorDibrJi6J
+s3KcAv++thMWikA9cKisscf2PD7PfCYIIEAwYLwAhABRYEAoyGVYGJAoLBxIEUwEwEjmgOQAE8rA
+0RFcqF4vO+vhtXZbZdXszKvGlPCrLbutsRVcGzu3vdnvl8b1/qRWDlACo+cX3ZfO7HznIgQELuG5
+miwwK+BquPWPOw2v3KCvkhKnlHJttr7uN5QTgP4ajLMkUaeC7WdBxV7QNL89REHjLIu04zkf4yaA
+L6wQnawZEHiMJuWb8UIRRagpJ9MqBYi4RSw3paGiDFAWMUZN5jSdCpQn6ygsFD16U8nSHQ/dKXAR
+VpL0t98RLdC7g3EZ5eb4OHRy3pfa+tBgxOGJ2p1qU9Vpi2BsUbybESThslFVH+iQerBYdA+bGaY4
+3gVWMIGTy026XarWNI8HEzU+pbML1erXZInnK++0L2t40blWNeloPUJgAcFoOLdVo8EMc7jyur0O
+1Mwow8+MeKYS/PECf3reCn6/PItyUHu/O4Tw/v4++zX03pQ6q7rf2XAHq8EEWKWGZTc438+U3c68
+M7eDVU0PnzVjf0aU3nR25mKaeLTVD1nt20//NVurTOO7wxcuT3E+123p+6zqzZR/dJ3HDx8/3WKV
+Aq3Gai46t9UOrkEo3rTZJM6ExgIGT+EZvNSl32AsQgsAK2hGAqUZKjJEOWAyz/LiJrTJDaX/plxk
+T77/y+fG3o155tZ2/gkI/QiQnGZMpMRFLuJvyWjgIsckrK+AiH8BYR8HEhMzPlaEIxa+rsgkewWE
+vw/kjwADAFqkkmYNCmVuZHN0cmVhbQ1lbmRvYmoNMTEgMCBvYmoNPDwvRmlsdGVyL0ZsYXRlRGVj
+b2RlL0ZpcnN0IDYwL0xlbmd0aCAzNjAvTiA5L1R5cGUvT2JqU3RtPj5zdHJlYW0NCmjedJJrT8Iw
+FIbPTzkf4dO6djcMISEQjImBRUnwgh8Qa1wcm9kF5c+rbzsgoJBm69m5Pn1XGbFg2WHfZSXYDSNW
+sKR5WPkdVoq9ULDy2BeI+ex34Aw4UBF3u84gr7OKPWeUFGVl+gi+ca4XsNHN2NPNh3YmdZUmmS57
+PZT0TbkJjfVXZRobO14UGo1k1BQlVapb3z80ooI01gvltCLG+xXvK8qw54itaEEVJbAz+Pu0xBeT
+JGEX07yFFhMU9Gnebu/GuydGxoVemzMfjY+ppmdKMWBp29do/2YHJ7A2dAGvWTNgpMjUiJT0afPf
+4Y/hN7klqpgGqKwBuoQnJW5who9SNHLc3T9wIDnwAs7qNH1ybp3LfJrvoNWBZuLsAbzjAwytPnoP
+yuRaXcZAWcO/2kI3mrnknoMKT0P5B1DyHNT2VhxAaatBuf9xGfbKqqZPAUhPcogr+Y/gOM0FTuh7
+f9J+BRgAu1mrqw0KZW5kc3RyZWFtDWVuZG9iag0xMiAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNv
+ZGUvRmlyc3QgODI5L0xlbmd0aCAxODA0L04gMTAwL1R5cGUvT2JqU3RtPj5zdHJlYW0NCmjexFhd
+b9s2FP0rBPa8iZffBIoCbbqiWdImaDrsIciD06iZ19QOFGdY//3OFS8d25Eda+02IM6VyKNzP3lJ
+yUallU2KtFE2K2OMclr5YJUjFZxXzqhMpJxVRM4p/JFJuPeKbAYqKHIEWFQUPOZBFQkyQ+asvFaU
+QlSeFOUMZqMMaae8hS6Lewfpg/IeMiVoVsYSZISMVvmkjON72OZgVdDKeJ1UIEiQBPD5jHvwBRgZ
+wBdSVgF8MXoVwJdgP0wwCeQBfBm+BvBl61UEX45GRVJWa9wjDBo+Q7XV4I1OWfYbVJZgfwzKGhtU
+jJAJuKQsLlTMkAAnRNMZrRAi66AcEOvJqAQ+D2cS+AL8T+ALCFICX4TyBL6UgQdfNpDgyzAOKp0G
+PiPkGsHNnB8o5dAb+JERasM4pMDyfYBEPnOEhLNwwTnoRyqcA440CL01uOAkxpJ5FwzyqjmdPQak
+UfMIJxZRIg3aiJiRBm/Co4QcuGR5BMwJYSMCc+anuFwyyoeg1+XIlYNE6n4KmddcEygZr2EsEXJv
+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
+DeYE5oAdPDmZzPLBUPR98g0Vsnl2OZmfrZfPp6eNXYkpnyGA7okL5kN1t3h6rDNqKoXP6+eTL+vN
+r8UjHLOfi6/9GntLnjH2NWZ+2wJ0BpgekimhGrI6IwMwDl9PoDOymZCPK7oM0P0sXkA2HWcJGRD7
+i/YSeBwQuFzBsk9FeHefplRCnl3LncWslb/J+ajW46Khuy41aMluthEAdEhpGnCz0k2Zm0aJ0HHX
+SbL8Yrf9GagiZ7Zy7jADX1ChjLGMdkBGvhTJ+6MSe3Bd4GYQ3KUCL4mYdDwR3MX9m4IWcXCepBG2
+tYsdtdVyhIrcPsf+KdLN8Q79Wtt9DF1a+fR786aWyRS+Xyu8aPkurXxfve/Xmuae1vSfT9Vi+bC6
+f6eVKU1oe7O23juoVwxNUxrro//Bxwz2iW1zLUmXPtblvF+W4th0tVFaTY8b6RLGu8RBecjb9boC
+aZRnechGOfBYBz3WwYzbRR626+Vd2S0WuwGech3luiXbe2Vzu0rhTa7ckpLr5UoZ499sJfnHRNJ9
+XDr/mEg8sDpw0MNqElPboorrsWcrlgeoiSF3+2QOnq2mum1lWtK+5+oc3pm/9QxH+ffCKe9j8sfD
+yT882+xc16NdDt/2WOzaP66WF6tltapJvVeTr5P54nn9VE8ufy9+VtO7utrk9fx5Vt2tN1XzXS82
+dfFjFbFyVf2pp48P96vGlr8LwHjYf2weajSQz+tlNZlvrm7/k7jakz7HUrXZ1Vad0n8FGABNHxW9
+DQplbmRzdHJlYW0NZW5kb2JqDTE0IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9GaXJzdCAx
+My9MZW5ndGggNDQvTiAyL1R5cGUvT2JqU3RtPj5zdHJlYW0NCmjeMjIwUDBQMDIwBGIFGxt9v9Lc
+4miIgIFCUKydHVAsWN/Fzg4gwAC4WAlEDQplbmRzdHJlYW0NZW5kb2JqDTE1IDAgb2JqDTw8L0xl
+bmd0aCA0MjMzL1N1YnR5cGUvWE1ML1R5cGUvTWV0YWRhdGE+PnN0cmVhbQ0KPD94cGFja2V0IGJl
+Z2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxu
+czp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNC4yLjEtYzA0MyA1
+Mi4zNzI3MjgsIDIwMDkvMDEvMTgtMTU6MDg6MDQgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5z
+OnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAg
+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0
+cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWNy
+b2JhdCBQREZNYWtlciA5LjEgZm9yIFdvcmQ8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHht
+cDpNb2RpZnlEYXRlPjIwMTEtMTEtMjJUMTU6MDE6NDNaPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAg
+ICAgPHhtcDpDcmVhdGVEYXRlPjIwMTEtMTEtMjJUMTU6MDE6MzVaPC94bXA6Q3JlYXRlRGF0ZT4K
+ICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxMS0xMS0yMlQxNTowMTo0M1o8L3htcDpNZXRh
+ZGF0YURhdGU+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgICA8cmRmOkRlc2NyaXB0aW9u
+IHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpwZGY9Imh0dHA6Ly9ucy5hZG9iZS5jb20v
+cGRmLzEuMy8iPgogICAgICAgICA8cGRmOlByb2R1Y2VyPkFjcm9iYXQgRGlzdGlsbGVyIDkuMC4w
+IChXaW5kb3dzKTwvcGRmOlByb2R1Y2VyPgogICAgICAgICA8cGRmOktleXdvcmRzLz4KICAgICAg
+PC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAg
+ICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAg
+ICAgICAgIDxkYzpmb3JtYXQ+YXBwbGljYXRpb24vcGRmPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxk
+YzpjcmVhdG9yPgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaT5t
+aWxib3VybmVqPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC9kYzpj
+cmVhdG9yPgogICAgICAgICA8ZGM6dGl0bGU+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAg
+ICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPkZyZWVkb20gb2YgSW5mb3JtYXRp
+b24gQWN0IDIwMDAgKFNlY3Rpb24gNTApPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOkFsdD4K
+ICAgICAgICAgPC9kYzp0aXRsZT4KICAgICAgICAgPGRjOmRlc2NyaXB0aW9uPgogICAgICAgICAg
+ICA8cmRmOkFsdD4KICAgICAgICAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ii8+
+CiAgICAgICAgICAgIDwvcmRmOkFsdD4KICAgICAgICAgPC9kYzpkZXNjcmlwdGlvbj4KICAgICAg
+PC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAg
+ICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIj4K
+ICAgICAgICAgPHhtcE1NOkRvY3VtZW50SUQ+dXVpZDpkMTA5Zjg5Mi1iMTUyLTRlMjMtOWRjMy1l
+ZjNkZDAxMjdkOGQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlE
+PnV1aWQ6ODY4YWY5ZjUtOWQwMi00ZDg0LTg5NjUtYTg4MDhlY2NjZTljPC94bXBNTTpJbnN0YW5j
+ZUlEPgogICAgICAgICA8eG1wTU06c3ViamVjdD4KICAgICAgICAgICAgPHJkZjpTZXE+CiAgICAg
+ICAgICAgICAgIDxyZGY6bGk+MzwvcmRmOmxpPgogICAgICAgICAgICA8L3JkZjpTZXE+CiAgICAg
+ICAgIDwveG1wTU06c3ViamVjdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6
+RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnBkZng9Imh0dHA6Ly9u
+cy5hZG9iZS5jb20vcGRmeC8xLjMvIj4KICAgICAgICAgPHBkZng6Q29tcGFueT5JbmZvcm1hdGlv
+biBDb21taXNzaW9uZXJzIE9mZmljZTwvcGRmeDpDb21wYW55PgogICAgICAgICA8cGRmeDpTb3Vy
+Y2VNb2RpZmllZD5EOjIwMTExMTIyMTQ1MTE5PC9wZGZ4OlNvdXJjZU1vZGlmaWVkPgogICAgICA8
+L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3
+Ij8+DQplbmRzdHJlYW0NZW5kb2JqDTE2IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9GaXJz
+dCA2L0xlbmd0aCA2NC9OIDEvVHlwZS9PYmpTdG0+PnN0cmVhbQ0KaN4yMjBSMFCwsdF3zi/NK1Ew
+1ffOTCmONjIwA4oGKRiCSWMwaQomzUFkrH5IZUGqfkBiemqxnR1AgAEAHocQmg0KZW5kc3RyZWFt
+DWVuZG9iag0xNyAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvRmlyc3QgNi9MZW5ndGggMjMz
+L04gMS9UeXBlL09ialN0bT4+c3RyZWFtDQpo3mzPTWvDMAwG4L+io32pP9IcOkohLBTGKCt0UBi9
+JLbC3CXWsB1K//3UMlgP00lIL4+Q1RVoWK9VM5dPSmIKY09ziniW6pmm7y5exUscKE1dCRSBZ1PI
+mVtMGd6GITjkZML7uu0KivbJasNlram1qeqP3z3rjUvUdwX27XbXfWGC1cIA43Ck5KV6xeuFmyyk
+2pH/B1tWjO0T+dnhn9aGXMI43jm90HASxxA9XfJJSnXgbxwyF4aA/tFb1sasODD3Z3SFb76HMqLY
+JkRPE9AAj483roDV+qYfOH4b1ZoPbDY/AgwAP1hmow0KZW5kc3RyZWFtDWVuZG9iag0xOCAwIG9i
+ag08PC9EZWNvZGVQYXJtczw8L0NvbHVtbnMgNC9QcmVkaWN0b3IgMTI+Pi9GaWx0ZXIvRmxhdGVE
+ZWNvZGUvSURbPEFGMzNCODUxMUY5QkMyMjlEOUUwMzVDMjdFMEU4MjJFPjwzMzRCOTBBOEVENDQ5
+ODQxQkMxQzdBMTk2Qzc3M0NEOD5dL0luZm8gMjAzIDAgUi9MZW5ndGggMTE0L1Jvb3QgMjA1IDAg
+Ui9TaXplIDIwNC9UeXBlL1hSZWYvV1sxIDIgMV0+PnN0cmVhbQ0KaN5iYgACJkadPwxMDEz+QILz
+I5BgBLOcQWIWQIKFGyRmDiTYfECsSpDECRBxHEhw5AMJ5kcgiS4gIXgdSDDMBcm6A03uA3MZQQQD
+I0GC8Sdx6iCKf5CgeJQYFgTj3NEwIDfoDsFYTP/BWRIgwABfDBGsDQplbmRzdHJlYW0NZW5kb2Jq
+DXN0YXJ0eHJlZg0KMTE2DQolJUVPRg0K
+--0016e644b8d0e3913b04b600bbe4--
\ No newline at end of file diff --git a/spec/fixtures/files/quoted-subject-iso8859-1.email b/spec/fixtures/files/quoted-subject-iso8859-1.email new file mode 100644 index 000000000..6ada69905 --- /dev/null +++ b/spec/fixtures/files/quoted-subject-iso8859-1.email @@ -0,0 +1,462 @@ +From: =?iso-8859-1?Q?Coordena=E7=E3o_de_Relacionamento=2C_Pesquisa_e_Informa=E7?= + =?iso-8859-1?Q?=E3o/CEDI?= <geraldinequango@localhost> +To: FOI Person <EMAIL_TO> +MIME-Version: 1.0 +Content-Type: multipart/related; + type="multipart/alternative"; + boundary="----_=_NextPart_001_01CCB66F.F38B15FC" +Subject: =?iso-8859-1?Q?C=E2mara_Responde=3A__Banco_de_ideias?= +Date: Fri, 9 Dec 2011 10:42:02 -0200 + +This is a multi-part message in MIME format. + +------_=_NextPart_001_01CCB66F.F38B15FC +Content-Type: multipart/alternative; + boundary="----_=_NextPart_002_01CCB66F.F38B15FC" + + +------_=_NextPart_002_01CCB66F.F38B15FC +Content-Type: text/plain; + charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +=20 + +Senhor Benedito, + +=20 + +O Centro de Documenta=E7=E3o e Informa=E7=E3o (Cedi) da C=E2mara dos = +Deputados agradece o seu contato. + +=20 + +Em aten=E7=E3o ao solicitado, informamos que a C=E2mara dos Deputados, = +por iniciativa da Comiss=E3o de Legisla=E7=E3o Participativa - CLP, = +criou um "Banco de Id=E9ias" com o objetivo de registrar e reunir = +id=E9ias de interesse da popula=E7=E3o. As sugest=F5es s=E3o organizadas = +por temas e ficam =E0s disposi=E7=E3o para consulta de entidades da = +sociedade civil e parlamentares, que poder=E3o adot=E1-las, = +aprimorando-as ou n=E3o, para serem transformadas em sugest=E3o de = +iniciativa legislativa, no caso das entidades da sociedade civil, ou em = +proposi=E7=E3o legislativa, no caso dos parlamentares. Cabe ressaltar = +que a Comiss=E3o reserva-se o direito de editar ou resumir os textos = +recebidos. + +A seguir, o endere=E7o eletr=F4nico do Banco de Id=E9ias:=20 + +=20 + +http://www2.camara.gov.br/atividade-legislativa/comissoes/comissoes-perma= +nentes/clp/banideias.htm/banco-de-ideias + +=20 + +Atenciosamente, +***************************************************** +Coordena=E7=E3o de Relacionamento, Pesquisa e Informa=E7=E3o - Corpi +Centro de Documenta=E7=E3o e Informa=E7=E3o - Cedi +C=E2mara dos Deputados - Anexo II +Pra=E7a dos Tr=EAs Poderes - Bras=EDlia - DF=20 +70160-900=20 +Tel.: 0-XX-61- 3216-5777; fax: 0-XX-61- 3216-5757=20 +informa.cedi@camara.gov.br <mailto:informa.cedi@camara.gov.br>=20 +***************************************************** + +mbb + +=20 + +Solicita=E7=E3o:=20 + +=20 + + Prezado(a) C=E2mara dos Deputados, + + =20 + + Gostaria de sugerir que o sal=E1rio de quem trabalha com pol=EDtica + + seja vinculado ao sal=E1rio m=EDnimo. + + =20 + + Atenciosamente, + + Benedito P.B.Neto + + =20 + + ------------------------------------------------------------------- + + =20 + + Por favor use esse endere=E7o de email em todas as repostas para = +este + + pedido: + + leideacesso+request-120-9702221c@queremossaber.org.br + + =20 + + Caso este email - informa.cedi@camara.gov.br - seja o endere=E7o + + errado para fazer acesso a informa=E7=E3o por favor nos contate e + + aponte o endere=E7o correto atrav=E9s desse formul=E1rio: + + http://queremossaber.org.br/pt/help/contact + + =20 + + Aviso: Esta mensagem e todas as respostas ser=E3o publicadas na + + internet. Leia sobre nossa pol=EDtica de privacidade: + + http://queremossaber.org.br/pt/help/officers + + =20 + + Caso voc=EA ache esse servi=E7o =FAtil, por favor entre em contato. + + =20 + + =20 + + ------------------------------------------------------------------- + + +------_=_NextPart_002_01CCB66F.F38B15FC +Content-Type: text/html; + charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +<html xmlns:v=3D"urn:schemas-microsoft-com:vml" = +xmlns:o=3D"urn:schemas-microsoft-com:office:office" = +xmlns:w=3D"urn:schemas-microsoft-com:office:word" = +xmlns:m=3D"http://schemas.microsoft.com/office/2004/12/omml" = +xmlns=3D"http://www.w3.org/TR/REC-html40"><head><meta = +http-equiv=3DContent-Type content=3D"text/html; = +charset=3Diso-8859-1"><meta name=3DGenerator content=3D"Microsoft Word = +14 (filtered medium)"><!--[if !mso]><style>v\:* = +{behavior:url(#default#VML);} +o\:* {behavior:url(#default#VML);} +w\:* {behavior:url(#default#VML);} +.shape {behavior:url(#default#VML);} +</style><![endif]--><style><!-- +/* Font Definitions */ +@font-face + {font-family:Calibri; + panose-1:2 15 5 2 2 2 4 3 2 4;} +@font-face + {font-family:Tahoma; + panose-1:2 11 6 4 3 5 4 4 2 4;} +/* Style Definitions */ +p.MsoNormal, li.MsoNormal, div.MsoNormal + {margin:0cm; + margin-bottom:.0001pt; + font-size:11.0pt; + font-family:"Calibri","sans-serif"; + mso-fareast-language:EN-US;} +a:link, span.MsoHyperlink + {mso-style-priority:99; + color:blue; + text-decoration:underline;} +a:visited, span.MsoHyperlinkFollowed + {mso-style-priority:99; + color:purple; + text-decoration:underline;} +p.MsoPlainText, li.MsoPlainText, div.MsoPlainText + {mso-style-priority:99; + mso-style-link:"Texto sem Formata=E7=E3o Char"; + margin:0cm; + margin-bottom:.0001pt; + font-size:11.0pt; + font-family:"Calibri","sans-serif"; + mso-fareast-language:EN-US;} +p.MsoAcetate, li.MsoAcetate, div.MsoAcetate + {mso-style-priority:99; + mso-style-link:"Texto de bal=E3o Char"; + margin:0cm; + margin-bottom:.0001pt; + font-size:8.0pt; + font-family:"Tahoma","sans-serif"; + mso-fareast-language:EN-US;} +span.TextosemFormataoChar + {mso-style-name:"Texto sem Formata=E7=E3o Char"; + mso-style-priority:99; + mso-style-link:"Texto sem Formata=E7=E3o"; + font-family:"Calibri","sans-serif";} +span.TextodebaloChar + {mso-style-name:"Texto de bal=E3o Char"; + mso-style-priority:99; + mso-style-link:"Texto de bal=E3o"; + font-family:"Tahoma","sans-serif";} +.MsoChpDefault + {mso-style-type:export-only; + font-family:"Calibri","sans-serif"; + mso-fareast-language:EN-US;} +@page WordSection1 + {size:612.0pt 792.0pt; + margin:70.85pt 3.0cm 70.85pt 3.0cm;} +div.WordSection1 + {page:WordSection1;} +--></style><!--[if gte mso 9]><xml> +<o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" /> +</xml><![endif]--><!--[if gte mso 9]><xml> +<o:shapelayout v:ext=3D"edit"> +<o:idmap v:ext=3D"edit" data=3D"1" /> +</o:shapelayout></xml><![endif]--></head><body lang=3DPT-BR link=3Dblue = +vlink=3Dpurple><div class=3DWordSection1><p class=3DMsoNormal><span = +style=3D'mso-fareast-language:PT-BR'><img width=3D566 height=3D58 = +id=3D"Imagem_x0020_1" src=3D"cid:image001.png@01CCB65F.2FD5C970" = +alt=3D"Descri=E7=E3o: Logo Corpi_html_1cd70d8a"><o:p></o:p></span></p><p = +class=3DMsoPlainText> <o:p></o:p></p><p class=3DMsoPlainText>Senhor = +Benedito,<o:p></o:p></p><p class=3DMsoPlainText><o:p> </o:p></p><p = +class=3DMsoPlainText>O Centro de Documenta=E7=E3o e Informa=E7=E3o = +(Cedi) da C=E2mara dos Deputados agradece o seu = +contato.<o:p></o:p></p><p class=3DMsoPlainText><o:p> </o:p></p><p = +class=3DMsoPlainText>Em aten=E7=E3o ao solicitado, informamos que a = +C=E2mara dos Deputados, por iniciativa da Comiss=E3o de Legisla=E7=E3o = +Participativa – CLP, criou um “Banco de Id=E9ias” com = +o objetivo de registrar e reunir id=E9ias de interesse da popula=E7=E3o. = +As sugest=F5es s=E3o organizadas por temas e ficam =E0s disposi=E7=E3o = +para consulta de entidades da sociedade civil e parlamentares, que = +poder=E3o adot=E1-las, aprimorando-as ou n=E3o, para serem transformadas = +em sugest=E3o de iniciativa legislativa, no caso das entidades da = +sociedade civil, ou em proposi=E7=E3o legislativa, no caso dos = +parlamentares. Cabe ressaltar que a Comiss=E3o reserva-se o direito de = +editar ou resumir os textos recebidos.<o:p></o:p></p><p = +class=3DMsoPlainText>A seguir, o endere=E7o eletr=F4nico do Banco de = +Id=E9ias: <o:p></o:p></p><p class=3DMsoPlainText><o:p> </o:p></p><p = +class=3DMsoPlainText>http://www2.camara.gov.br/atividade-legislativa/comi= +ssoes/comissoes-permanentes/clp/banideias.htm/banco-de-ideias<o:p></o:p><= +/p><p class=3DMsoPlainText><o:p> </o:p></p><p = +class=3DMsoNormal><span = +style=3D'font-size:10.0pt;font-family:"Arial","sans-serif";mso-fareast-la= +nguage:PT-BR'>Atenciosamente,<br>****************************************= +*************<br>Coordena=E7=E3o de Relacionamento, Pesquisa e = +Informa=E7=E3o – Corpi<br>Centro de Documenta=E7=E3o e = +Informa=E7=E3o – Cedi<br>C=E2mara dos Deputados – Anexo = +II<br>Pra=E7a dos Tr=EAs Poderes – Bras=EDlia – DF = +<br>70160-900 <br>Tel.: 0-XX-61- 3216-5777; fax: 0-XX-61- 3216-5757 = +<br><a href=3D"mailto:informa.cedi@camara.gov.br"><span = +style=3D'color:windowtext'>informa.cedi@camara.gov.br</span></a><br>*****= +************************************************<o:p></o:p></span></p><p = +class=3DMsoNormal><span = +style=3D'font-size:8.0pt;font-family:"Arial","sans-serif";mso-fareast-lan= +guage:PT-BR'>mbb<o:p></o:p></span></p><p = +class=3DMsoPlainText><o:p> </o:p></p><p = +class=3DMsoPlainText>Solicita=E7=E3o: <o:p></o:p></p><p = +class=3DMsoPlainText><o:p> </o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 Prezado(a) C=E2mara dos = +Deputados,<o:p></o:p></p><p class=3DMsoPlainText>=A0=A0=A0=A0 = +<o:p></o:p></p><p class=3DMsoPlainText>=A0=A0=A0=A0=A0Gostaria de = +sugerir que o sal=E1rio de quem trabalha com pol=EDtica<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 seja vinculado ao sal=E1rio = +m=EDnimo.<o:p></o:p></p><p class=3DMsoPlainText>=A0=A0=A0=A0 = +<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0=A0Atenciosamente,<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 Benedito P.B.Neto<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 <o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0=A0-------------------------------------= +------------------------------<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 <o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0=A0Por favor use esse endere=E7o de = +email em todas as repostas para este<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 pedido:<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 = +leideacesso+request-120-9702221c@queremossaber.org.br<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 <o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0=A0Caso este email - = +informa.cedi@camara.gov.br - seja o endere=E7o<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 errado para fazer acesso a = +informa=E7=E3o por favor nos contate e<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 aponte o endere=E7o correto atrav=E9s = +desse formul=E1rio:<o:p></o:p></p><p class=3DMsoPlainText>=A0=A0=A0=A0 = +http://queremossaber.org.br/pt/help/contact<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 <o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0=A0Aviso: Esta mensagem e todas as = +respostas ser=E3o publicadas na<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 internet. Leia sobre nossa pol=EDtica = +de privacidade:<o:p></o:p></p><p class=3DMsoPlainText>=A0=A0=A0=A0 = +http://queremossaber.org.br/pt/help/officers<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 <o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0=A0Caso voc=EA ache esse servi=E7o = +=FAtil, por favor entre em contato.<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0 <o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0=A0<o:p></o:p></p><p = +class=3DMsoPlainText>=A0=A0=A0=A0=A0-------------------------------------= +------------------------------<o:p></o:p></p></div></body></html> +------_=_NextPart_002_01CCB66F.F38B15FC-- + +------_=_NextPart_001_01CCB66F.F38B15FC +Content-Type: image/png; + name="image001.png" +Content-Transfer-Encoding: base64 +Content-ID: <image001.png@01CCB65F.2FD5C970> +Content-Description: image001.png +Content-Location: image001.png + +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 +SEhISIxR/heHXcIrMvkS1gAAAABJRU5ErkJggg== + +------_=_NextPart_001_01CCB66F.F38B15FC--
\ No newline at end of file diff --git a/spec/fixtures/files/useless_raw_email.email b/spec/fixtures/files/raw_emails/1.email index 2e4585af7..2e4585af7 100644 --- a/spec/fixtures/files/useless_raw_email.email +++ b/spec/fixtures/files/raw_emails/1.email diff --git a/spec/fixtures/files/raw_emails/2.email b/spec/fixtures/files/raw_emails/2.email new file mode 100644 index 000000000..eab0b0f8d --- /dev/null +++ b/spec/fixtures/files/raw_emails/2.email @@ -0,0 +1,20 @@ +From: "FOI Person" <foiperson@localhost> +To: "Bob Smith" <bob@localhost> +Date: Tue, 13 Nov 2007 11:39:55 +0000 +Bcc: +Subject: Re: Your email +Reply-To: +In-Reply-To: <471f1eae5d1cb_7347..fdbe67386164@cat.tmail> +Content-Type: text/plain; charset=utf-8 + +Dear “Bob”, + +In the financial year 2010–2011, this Department spent a +total of nine hundred of your earth pounds on the purchase +and repair of boring equipment. + +Yours most sincerely, + +Quentin Nobble-Boston, +Permanent Under-Secretary, +Department for Humpadinking diff --git a/spec/fixtures/files/raw_emails/3.email b/spec/fixtures/files/raw_emails/3.email new file mode 100644 index 000000000..a6e780fe5 --- /dev/null +++ b/spec/fixtures/files/raw_emails/3.email @@ -0,0 +1,19 @@ +From: "The Minister" <msw@localhost> +To: "Bob Smith" <bob@localhost> +Date: Tue, 13 Nov 2009 11:39:55 +0000 +Bcc: +Subject: Re: Your message +Reply-To: +In-Reply-To: <471f1eae5d1cb_7347..fdbe67386165@cat.tmail> +Content-Type: text/plain; charset=utf-8 + +Dear “Bob”, + +In the financial year 2010–2011, this Ministry spent precisely +no money at all on the purchase or repair of boring equipment. + +Yours most sincerely, + +Martin Kibble-von Scratsching, +Chief Assistant to the Assistant Chief, +Ministry of Silly Walks diff --git a/spec/fixtures/files/raw_emails/4.email b/spec/fixtures/files/raw_emails/4.email new file mode 100644 index 000000000..c778e5ba9 --- /dev/null +++ b/spec/fixtures/files/raw_emails/4.email @@ -0,0 +1,17 @@ +From: "The Minister" <msw@localhost> +To: robin@localhost +Date: Tue, 13 Nov 2008 11:39:55 +0000 +Bcc: +Subject: Re: v1agra +Reply-To: +In-Reply-To: <fdshjksdahhjkfsdahjkfsd@gfh.example.com> +Content-Type: text/plain; charset=utf-8 + +Thank you for your spam, which we have processed with pleasure. Please +accept herewith our order for six packs of the finest cheap v1agra. + +Yours most sincerely, + +Martin Kibble-von Scratsching, +Chief Assistant to the Assistant Chief, +Ministry of Silly Walks diff --git a/spec/fixtures/files/raw_emails/5.email b/spec/fixtures/files/raw_emails/5.email new file mode 100644 index 000000000..16b65610a --- /dev/null +++ b/spec/fixtures/files/raw_emails/5.email @@ -0,0 +1,17 @@ +From: "The Minister" <sensewalk@localhost> +To: robin@localhost +Date: Tue, 13 Nov 2008 12:39:55 +0000 +Bcc: +Subject: Re: v1agra +Reply-To: +In-Reply-To: <fdshjks+hihhjkfsdahjkfsd@gfh.example.com> +Content-Type: text/plain; charset=utf-8 + +Thank you for your spam, which we have processed with pleasure. Please +accept herewith our order for six packs of the finest cheap v1agra. + +Yours most sincerely, + +Martin Scratsching-von Kibble, +Assistant (Second class) to the Chief Assistant to the Assistant Chief, +Ministry of Sensible Walks diff --git a/spec/fixtures/files/track-response-abcmail-oof.email b/spec/fixtures/files/track-response-abcmail-oof.email new file mode 100644 index 000000000..5d1733143 --- /dev/null +++ b/spec/fixtures/files/track-response-abcmail-oof.email @@ -0,0 +1,80 @@ +Delivered-To: mysociety.robin@gmail.com +Received: by 10.216.154.212 with SMTP id h62cs265517wek; + Fri, 30 Dec 2011 02:03:17 -0800 (PST) +Received: by 10.227.208.129 with SMTP id gc1mr47630338wbb.4.1325239396543; + Fri, 30 Dec 2011 02:03:16 -0800 (PST) +Return-Path: <Name.Removed@example.gov.uk> +Received: from wildfire.ukcod.org.uk (wildfire.ukcod.org.uk. [89.238.145.74]) + by mx.google.com with ESMTPS id ei10si9596065wbb.20.2011.12.30.02.03.16 + (version=TLSv1/SSLv3 cipher=OTHER); + Fri, 30 Dec 2011 02:03:16 -0800 (PST) +Received-SPF: neutral (google.com: 89.238.145.74 is neither permitted nor denied by best guess record for domain of Name.Removed@example.gov.uk) client-ip=89.238.145.74; +Authentication-Results: mx.google.com; spf=neutral (google.com: 89.238.145.74 is neither permitted nor denied by best guess record for domain of Name.Removed@example.gov.uk) smtp.mail=Name.Removed@example.gov.uk +Received: from foi by wildfire.ukcod.org.uk with local (Exim 4.72) + (envelope-from <Name.Removed@example.gov.uk>) + id 1RgZIs-0000ME-1T + for team_delivery@whatdotheyknow.com; Fri, 30 Dec 2011 10:03:10 +0000 +Received: from truro.icritical.com ([93.95.13.13]:51540) + by wildfire.ukcod.org.uk with smtp (Exim 4.72) + (envelope-from <Name.Removed@example.gov.uk>) + id 1RgZIq-0000M6-St + for track@whatdotheyknow.com; Fri, 30 Dec 2011 10:03:09 +0000 +Received: (qmail 19136 invoked from network); 30 Dec 2011 10:03:08 -0000 +Received: from localhost (127.0.0.1) + by truro.icritical.com with SMTP; 30 Dec 2011 10:03:08 -0000 +Received: from truro.icritical.com ([127.0.0.1]) + by localhost (truro.icritical.com [127.0.0.1]) (amavisd-new, port 10024) + with SMTP id 19122-01 for <track@whatdotheyknow.com>; + Fri, 30 Dec 2011 10:03:07 +0000 (GMT) +Received: (qmail 19112 invoked by uid 599); 30 Dec 2011 10:03:06 -0000 +Received: from unknown (HELO abcmail.example.gov.uk) (213.185.212.82) + by truro.icritical.com (qpsmtpd/0.28) with ESMTP; Fri, 30 Dec 2011 10:03:06 +0000 +Subject: AUTO: Name Removed is out of the office (returning 03/01/2012) +Auto-Submitted: auto-generated +From: Name.Removed@example.gov.uk +To: track@whatdotheyknow.com +Message-ID: <OFF4E36F18.ED02EFA3-ON80257976.00373794-80257976.00373794@example.gov.uk> +Date: Fri, 30 Dec 2011 10:03:07 +0000 +X-MIMETrack: Serialize by Router on ABCMail/SVR/ABC(Release 8.5.2FP1|November 29, 2010) at + 30/12/2011 10:03:07 +MIME-Version: 1.0 +Content-type: text/plain; charset=US-ASCII +X-Virus-Scanned: by iCritical at truro.icritical.com + + +I am out of the office until 03/01/2012. + +I will be out of the office until 3rd January December 2012. I will deal +with all emails upon my return. If your query is urgent please contact +Colleague Name on colleague.name@example.gov.uk or 01234 567890. + +If you are requesting information under the Freedom of Information Act, the +Environmental Information Regulations or the Data Protection Act, please +forward your enquiry to colleague.name@example.gov.uk The Council +will begin processing your request once it is received by that address. + + +Thanks + +Name + + + + + +Note: This is an automated response to your message "Your WhatDoTheyKnow +email alert" sent on 30/12/2011 06:54:19. + +This is the only notification you will receive while this person is away. + + +This e-mail and any files transmitted with it are confidential and +intended solely for the use of the individual or entity to whom +they are addressed. +If you have received this e-mail in error please notify the +originator of the message. This footer also confirms that this +e-mail message has been scanned for the presence of computer viruses. + +Any views expressed in this message are those of the individual +sender, except where the sender specifies and with authority, +states them to be the views of Organisation Name. diff --git a/spec/fixtures/files/track-response-outlook-oof.email b/spec/fixtures/files/track-response-outlook-oof.email new file mode 100644 index 000000000..ee5a28b15 --- /dev/null +++ b/spec/fixtures/files/track-response-outlook-oof.email @@ -0,0 +1,587 @@ +Delivered-To: mysociety.robin@gmail.com +Received: by 10.152.24.138 with SMTP id u10cs341636laf; + Thu, 8 Dec 2011 02:39:53 -0800 (PST) +Received: by 10.180.103.131 with SMTP id fw3mr4246912wib.57.1323340792168; + Thu, 08 Dec 2011 02:39:52 -0800 (PST) +Return-Path: <peter@kentadvice.co.uk> +Received: from wildfire.ukcod.org.uk (wildfire.ukcod.org.uk. [89.238.145.74]) + by mx.google.com with ESMTPS id ft12si3357577wbb.14.2011.12.08.02.39.51 + (version=TLSv1/SSLv3 cipher=OTHER); + Thu, 08 Dec 2011 02:39:52 -0800 (PST) +Received-SPF: neutral (google.com: 89.238.145.74 is neither permitted nor denied by best guess record for domain of peter@kentadvice.co.uk) client-ip=89.238.145.74; +Authentication-Results: mx.google.com; spf=neutral (google.com: 89.238.145.74 is neither permitted nor denied by best guess record for domain of peter@kentadvice.co.uk) smtp.mail=peter@kentadvice.co.uk +Received: from foi by wildfire.ukcod.org.uk with local (Exim 4.72) + (envelope-from <peter@kentadvice.co.uk>) + id 1RYbOC-00034X-Vm + for team_delivery@whatdotheyknow.com; Thu, 08 Dec 2011 10:39:45 +0000 +Received: from mail-ey0-f173.google.com ([209.85.215.173]:38997) + by wildfire.ukcod.org.uk with esmtp (Exim 4.72) + (envelope-from <peter@kentadvice.co.uk>) + id 1RYbOC-00034L-GF + for track@whatdotheyknow.com; Thu, 08 Dec 2011 10:39:44 +0000 +Received: by eaai10 with SMTP id i10so1168752eaa.32 + for <track@whatdotheyknow.com>; Thu, 08 Dec 2011 02:39:33 -0800 (PST) +Received: by 10.213.21.148 with SMTP id j20mr131258ebb.87.1323340773446; + Thu, 08 Dec 2011 02:39:33 -0800 (PST) +Received: from PRWin7 (cpc2-tilb7-2-0-cust982.basl.cable.virginmedia.com. [94.168.103.215]) + by mx.google.com with ESMTPS id 49sm16411187eec.1.2011.12.08.02.39.31 + (version=TLSv1/SSLv3 cipher=OTHER); + Thu, 08 Dec 2011 02:39:32 -0800 (PST) +From: "Name Removed" <name-removed@example.co.uk> +To: <track@whatdotheyknow.com> +Subject: Out of Office reply +Date: Thu, 8 Dec 2011 10:39:24 -0000 +Message-ID: <00ab01ccb595$aada0070$008e0150$@co.uk> +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_NextPart_000_00AC_01CCB595.AADA0070" +X-Mailer: Microsoft Office Outlook 12.0 +Thread-Index: Acy1laTpNPAp9QuHRu2X59T70yzpQw== +Content-Language: en-gb + +This is a multi-part message in MIME format. + +------=_NextPart_000_00AC_01CCB595.AADA0070 +Content-Type: text/plain; + charset="US-ASCII" +Content-Transfer-Encoding: 7bit + +Thank you for your message. I am currently out of the office, with [limited] +[no] access to e-mail. + +I will be returning on [day, date]. + +If you need assistance before then, you may reach me at [phone number]. +For urgent issues, please contact [name] at [e-mail address] or [telephone +number]. + +[Signature] + +[Optional: Type your favorite quotation or saying here along with the author +or source] + +------=_NextPart_000_00AC_01CCB595.AADA0070 +Content-Type: text/html; + charset="US-ASCII" +Content-Transfer-Encoding: quoted-printable + +<html xmlns:v=3D"urn:schemas-microsoft-com:vml" = +xmlns:o=3D"urn:schemas-microsoft-com:office:office" = +xmlns:w=3D"urn:schemas-microsoft-com:office:word" = +xmlns:x=3D"urn:schemas-microsoft-com:office:excel" = +xmlns:p=3D"urn:schemas-microsoft-com:office:powerpoint" = +xmlns:a=3D"urn:schemas-microsoft-com:office:access" = +xmlns:dt=3D"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" = +xmlns:s=3D"uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" = +xmlns:rs=3D"urn:schemas-microsoft-com:rowset" xmlns:z=3D"#RowsetSchema" = +xmlns:b=3D"urn:schemas-microsoft-com:office:publisher" = +xmlns:ss=3D"urn:schemas-microsoft-com:office:spreadsheet" = +xmlns:c=3D"urn:schemas-microsoft-com:office:component:spreadsheet" = +xmlns:oa=3D"urn:schemas-microsoft-com:office:activation" = +xmlns:html=3D"http://www.w3.org/TR/REC-html40" = +xmlns:q=3D"http://schemas.xmlsoap.org/soap/envelope/" xmlns:D=3D"DAV:" = +xmlns:x2=3D"http://schemas.microsoft.com/office/excel/2003/xml" = +xmlns:ois=3D"http://schemas.microsoft.com/sharepoint/soap/ois/" = +xmlns:dir=3D"http://schemas.microsoft.com/sharepoint/soap/directory/" = +xmlns:ds=3D"http://www.w3.org/2000/09/xmldsig#" = +xmlns:dsp=3D"http://schemas.microsoft.com/sharepoint/dsp" = +xmlns:udc=3D"http://schemas.microsoft.com/data/udc" = +xmlns:xsd=3D"http://www.w3.org/2001/XMLSchema" = +xmlns:sub=3D"http://schemas.microsoft.com/sharepoint/soap/2002/1/alerts/"= + xmlns:ec=3D"http://www.w3.org/2001/04/xmlenc#" = +xmlns:sp=3D"http://schemas.microsoft.com/sharepoint/" = +xmlns:sps=3D"http://schemas.microsoft.com/sharepoint/soap/" = +xmlns:xsi=3D"http://www.w3.org/2001/XMLSchema-instance" = +xmlns:udcxf=3D"http://schemas.microsoft.com/data/udc/xmlfile" = +xmlns:wf=3D"http://schemas.microsoft.com/sharepoint/soap/workflow/" = +xmlns:mver=3D"http://schemas.openxmlformats.org/markup-compatibility/2006= +" xmlns:m=3D"http://schemas.microsoft.com/office/2004/12/omml" = +xmlns:mrels=3D"http://schemas.openxmlformats.org/package/2006/relationshi= +ps" = +xmlns:ex12t=3D"http://schemas.microsoft.com/exchange/services/2006/types"= + = +xmlns:ex12m=3D"http://schemas.microsoft.com/exchange/services/2006/messag= +es" xmlns=3D"http://www.w3.org/TR/REC-html40"> + +<head> +<META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; = +charset=3Dus-ascii"> + + +<meta name=3DProgId content=3DWord.Document> +<meta name=3DGenerator content=3D"Microsoft Word 12"> +<meta name=3DOriginator content=3D"Microsoft Word 12"> +<link rel=3DFile-List href=3D"cid:filelist.xml@01C895B2.35BC4F70"> +<!--[if gte mso 9]><xml> + <o:OfficeDocumentSettings> + <o:AllowPNG/> + <o:TargetScreenSize>1024x768</o:TargetScreenSize> + </o:OfficeDocumentSettings> +</xml><![endif]--> +<link rel=3DthemeData href=3D"~~themedata~~"> +<link rel=3DcolorSchemeMapping href=3D"~~colorschememapping~~"> +<!--[if gte mso 9]><xml> + <w:WordDocument> + <w:SpellingState>Clean</w:SpellingState> + <w:TrackMoves/> + <w:TrackFormatting/> + <w:EnvelopeVis/> + <w:ValidateAgainstSchemas/> + <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> + <w:IgnoreMixedContent>false</w:IgnoreMixedContent> + <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> + <w:DoNotPromoteQF/> + <w:LidThemeOther>EN-US</w:LidThemeOther> + <w:LidThemeAsian>X-NONE</w:LidThemeAsian> + <w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript> + <w:Compatibility> + <w:DoNotExpandShiftReturn/> + <w:BreakWrappedTables/> + <w:SnapToGridInCell/> + <w:WrapTextWithPunct/> + <w:UseAsianBreakRules/> + <w:DontGrowAutofit/> + <w:SplitPgBreakAndParaMark/> + <w:DontVertAlignCellWithSp/> + <w:DontBreakConstrainedForcedTables/> + <w:DontVertAlignInTxbx/> + <w:Word11KerningPairs/> + <w:CachedColBalance/> + </w:Compatibility> + <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> + <m:mathPr> + <m:mathFont m:val=3D"Cambria Math"/> + <m:brkBin m:val=3D"before"/> + <m:brkBinSub m:val=3D"--"/> + <m:smallFrac m:val=3D"off"/> + <m:dispDef/> + <m:lMargin m:val=3D"0"/> + <m:rMargin m:val=3D"0"/> + <m:defJc m:val=3D"centerGroup"/> + <m:wrapIndent m:val=3D"1440"/> + <m:intLim m:val=3D"subSup"/> + <m:naryLim m:val=3D"undOvr"/> + </m:mathPr></w:WordDocument> +</xml><![endif]--><!--[if gte mso 9]><xml> + <w:LatentStyles DefLockedState=3D"false" DefUnhideWhenUsed=3D"true"=20 + DefSemiHidden=3D"true" DefQFormat=3D"false" DefPriority=3D"99"=20 + LatentStyleCount=3D"267"> + <w:LsdException Locked=3D"false" Priority=3D"0" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Normal"/> + <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"heading 1"/> + <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" = +Name=3D"heading 2"/> + <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" = +Name=3D"heading 3"/> + <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" = +Name=3D"heading 4"/> + <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" = +Name=3D"heading 5"/> + <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" = +Name=3D"heading 6"/> + <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" = +Name=3D"heading 7"/> + <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" = +Name=3D"heading 8"/> + <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" = +Name=3D"heading 9"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 1"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 2"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 3"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 4"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 5"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 6"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 7"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 8"/> + <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"toc 9"/> + <w:LsdException Locked=3D"false" Priority=3D"35" QFormat=3D"true" = +Name=3D"caption"/> + <w:LsdException Locked=3D"false" Priority=3D"10" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Title"/> + <w:LsdException Locked=3D"false" Priority=3D"1" Name=3D"Default = +Paragraph Font"/> + <w:LsdException Locked=3D"false" Priority=3D"11" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Subtitle"/> + <w:LsdException Locked=3D"false" Priority=3D"22" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Strong"/> + <w:LsdException Locked=3D"false" Priority=3D"20" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Emphasis"/> + <w:LsdException Locked=3D"false" Priority=3D"59" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Table Grid"/> + <w:LsdException Locked=3D"false" UnhideWhenUsed=3D"false" = +Name=3D"Placeholder Text"/> + <w:LsdException Locked=3D"false" Priority=3D"1" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"No Spacing"/> + <w:LsdException Locked=3D"false" Priority=3D"60" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Shading"/> + <w:LsdException Locked=3D"false" Priority=3D"61" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light List"/> + <w:LsdException Locked=3D"false" Priority=3D"62" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Grid"/> + <w:LsdException Locked=3D"false" Priority=3D"63" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 1"/> + <w:LsdException Locked=3D"false" Priority=3D"64" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 2"/> + <w:LsdException Locked=3D"false" Priority=3D"65" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 1"/> + <w:LsdException Locked=3D"false" Priority=3D"66" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 2"/> + <w:LsdException Locked=3D"false" Priority=3D"67" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 1"/> + <w:LsdException Locked=3D"false" Priority=3D"68" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 2"/> + <w:LsdException Locked=3D"false" Priority=3D"69" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 3"/> + <w:LsdException Locked=3D"false" Priority=3D"70" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Dark List"/> + <w:LsdException Locked=3D"false" Priority=3D"71" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Shading"/> + <w:LsdException Locked=3D"false" Priority=3D"72" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful List"/> + <w:LsdException Locked=3D"false" Priority=3D"73" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Grid"/> + <w:LsdException Locked=3D"false" Priority=3D"60" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Shading Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"61" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light List Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"62" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Grid Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"63" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 1 Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"64" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 2 Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"65" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 1 Accent 1"/> + <w:LsdException Locked=3D"false" UnhideWhenUsed=3D"false" = +Name=3D"Revision"/> + <w:LsdException Locked=3D"false" Priority=3D"34" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"List Paragraph"/> + <w:LsdException Locked=3D"false" Priority=3D"29" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Quote"/> + <w:LsdException Locked=3D"false" Priority=3D"30" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Intense Quote"/> + <w:LsdException Locked=3D"false" Priority=3D"66" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 2 Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"67" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 1 Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"68" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 2 Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"69" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 3 Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"70" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Dark List Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"71" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Shading Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"72" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful List Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"73" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Grid Accent 1"/> + <w:LsdException Locked=3D"false" Priority=3D"60" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Shading Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"61" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light List Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"62" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Grid Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"63" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 1 Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"64" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 2 Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"65" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 1 Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"66" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 2 Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"67" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 1 Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"68" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 2 Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"69" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 3 Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"70" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Dark List Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"71" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Shading Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"72" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful List Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"73" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Grid Accent 2"/> + <w:LsdException Locked=3D"false" Priority=3D"60" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Shading Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"61" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light List Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"62" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Grid Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"63" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 1 Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"64" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 2 Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"65" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 1 Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"66" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 2 Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"67" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 1 Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"68" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 2 Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"69" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 3 Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"70" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Dark List Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"71" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Shading Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"72" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful List Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"73" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Grid Accent 3"/> + <w:LsdException Locked=3D"false" Priority=3D"60" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Shading Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"61" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light List Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"62" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Grid Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"63" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 1 Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"64" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 2 Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"65" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 1 Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"66" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 2 Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"67" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 1 Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"68" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 2 Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"69" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 3 Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"70" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Dark List Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"71" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Shading Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"72" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful List Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"73" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Grid Accent 4"/> + <w:LsdException Locked=3D"false" Priority=3D"60" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Shading Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"61" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light List Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"62" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Grid Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"63" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 1 Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"64" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 2 Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"65" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 1 Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"66" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 2 Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"67" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 1 Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"68" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 2 Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"69" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 3 Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"70" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Dark List Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"71" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Shading Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"72" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful List Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"73" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Grid Accent 5"/> + <w:LsdException Locked=3D"false" Priority=3D"60" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Shading Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"61" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light List Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"62" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Light Grid Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"63" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 1 Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"64" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Shading 2 Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"65" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 1 Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"66" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium List 2 Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"67" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 1 Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"68" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 2 Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"69" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Medium Grid 3 Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"70" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Dark List Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"71" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Shading Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"72" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful List Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"73" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" Name=3D"Colorful Grid Accent 6"/> + <w:LsdException Locked=3D"false" Priority=3D"19" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Subtle Emphasis"/> + <w:LsdException Locked=3D"false" Priority=3D"21" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Intense Emphasis"/> + <w:LsdException Locked=3D"false" Priority=3D"31" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Subtle Reference"/> + <w:LsdException Locked=3D"false" Priority=3D"32" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Intense = +Reference"/> + <w:LsdException Locked=3D"false" Priority=3D"33" SemiHidden=3D"false"=20 + UnhideWhenUsed=3D"false" QFormat=3D"true" Name=3D"Book Title"/> + <w:LsdException Locked=3D"false" Priority=3D"37" = +Name=3D"Bibliography"/> + <w:LsdException Locked=3D"false" Priority=3D"39" QFormat=3D"true" = +Name=3D"TOC Heading"/> + </w:LatentStyles> +</xml><![endif]--> +<style> +<!-- + /* Font Definitions */ + @font-face + {font-family:"Cambria Math"; + panose-1:2 4 5 3 5 4 6 3 2 4; + mso-font-charset:1; + mso-generic-font-family:roman; + mso-font-format:other; + mso-font-pitch:variable; + mso-font-signature:0 0 0 0 0 0;} +@font-face + {font-family:Calibri; + panose-1:2 15 5 2 2 2 4 3 2 4; + mso-font-charset:0; + mso-generic-font-family:swiss; + mso-font-pitch:variable; + mso-font-signature:-1610611985 1073750139 0 0 159 0;} + /* Style Definitions */ + p.MsoNormal, li.MsoNormal, div.MsoNormal + {mso-style-unhide:no; + mso-style-qformat:yes; + mso-style-parent:""; + margin:0in; + margin-bottom:.0001pt; + mso-pagination:widow-orphan; + font-size:11.0pt; + font-family:"Calibri","sans-serif"; + mso-fareast-font-family:Calibri; + mso-fareast-theme-font:minor-latin; + mso-bidi-font-family:"Times New Roman";} +a:link, span.MsoHyperlink + {mso-style-noshow:yes; + mso-style-priority:99; + color:blue; + text-decoration:underline; + text-underline:single;} +a:visited, span.MsoHyperlinkFollowed + {mso-style-noshow:yes; + mso-style-priority:99; + color:purple; + text-decoration:underline; + text-underline:single;} +span.EmailStyle17 + {mso-style-type:personal; + mso-style-noshow:yes; + mso-style-unhide:no; + font-family:"Calibri","sans-serif"; + mso-ascii-font-family:Calibri; + mso-hansi-font-family:Calibri; + color:windowtext;} +span.EmailStyle18 + {mso-style-type:personal-reply; + mso-style-noshow:yes; + mso-style-unhide:no; + mso-ansi-font-size:11.0pt; + mso-bidi-font-size:11.0pt; + font-family:"Calibri","sans-serif"; + mso-ascii-font-family:Calibri; + mso-ascii-theme-font:minor-latin; + mso-hansi-font-family:Calibri; + mso-hansi-theme-font:minor-latin; + mso-bidi-font-family:"Times New Roman"; + mso-bidi-theme-font:minor-bidi; + color:#5F497A; + mso-themecolor:accent4; + mso-themeshade:191;} +.MsoChpDefault + {mso-style-type:export-only; + mso-default-props:yes; + font-size:10.0pt; + mso-ansi-font-size:10.0pt; + mso-bidi-font-size:10.0pt;} +@page Section1 + {size:8.5in 11.0in; + margin:1.0in 1.0in 1.0in 1.0in; + mso-header-margin:.5in; + mso-footer-margin:.5in; + mso-paper-source:0;} +div.Section1 + {page:Section1;} +--> +</style> +<!--[if gte mso 10]> +<style> + /* Style Definitions */=20 + table.MsoNormalTable + {mso-style-name:"Table Normal"; + mso-tstyle-rowband-size:0; + mso-tstyle-colband-size:0; + mso-style-noshow:yes; + mso-style-priority:99; + mso-style-qformat:yes; + mso-style-parent:""; + mso-padding-alt:0in 5.4pt 0in 5.4pt; + mso-para-margin:0in; + mso-para-margin-bottom:.0001pt; + mso-pagination:widow-orphan; + font-size:10.0pt; + font-family:"Times New Roman","serif";} +</style> +<![endif]--><!--[if gte mso 9]><xml> + <o:shapedefaults v:ext=3D"edit" spidmax=3D"1026" /> +</xml><![endif]--><!--[if gte mso 9]><xml> + <o:shapelayout v:ext=3D"edit"> + <o:idmap v:ext=3D"edit" data=3D"1" /> + </o:shapelayout></xml><![endif]--> +</head> + +<body lang=3DEN-US link=3Dblue vlink=3Dpurple = +style=3D'tab-interval:.5in'> + +<div class=3DSection1> + +<p class=3DMsoNormal>Thank you for your message. I am currently out of = +the +office, with [limited] [no] access to e-mail.<o:p></o:p></p> + +<p class=3DMsoNormal><o:p> </o:p></p> + +<p class=3DMsoNormal>I will be returning on [day, date].<o:p></o:p></p> + +<p class=3DMsoNormal><o:p> </o:p></p> + +<p class=3DMsoNormal>If you need assistance before then, you may reach = +me at +[phone number].<o:p></o:p></p> + +<p class=3DMsoNormal>For urgent issues, please contact [name] at [e-mail = +address] +or [telephone number].<o:p></o:p></p> + +<p class=3DMsoNormal><o:p> </o:p></p> + +<p class=3DMsoNormal>[Signature]<o:p></o:p></p> + +<p class=3DMsoNormal><o:p> </o:p></p> + +<p class=3DMsoNormal><i style=3D'mso-bidi-font-style:normal'>[Optional: = +Type your +favorite quotation or saying here along with the author or = +source]<o:p></o:p></i></p> + +</div> + +</body> + +</html> + +------=_NextPart_000_00AC_01CCB595.AADA0070-- + diff --git a/spec/fixtures/has_tag_string_tags.yml b/spec/fixtures/has_tag_string_tags.yml new file mode 100644 index 000000000..fe3d4fd28 --- /dev/null +++ b/spec/fixtures/has_tag_string_tags.yml @@ -0,0 +1,36 @@ +lonely_tag: + name: lonely_agency + id: "1" + created_at: 2007-10-24 10:51:01.161639 + model: PublicBody + model_id: 4 +useless_tag_1: + name: useless_agency + id: "2" + created_at: 2007-10-24 10:51:01.161639 + model: PublicBody + model_id: 3 +useless_tag_2: + name: useless_agency + id: "3" + created_at: 2007-10-24 10:51:01.161639 + model: PublicBody + model_id: 4 +useless_tag_3: + name: useless_agency + id: "4" + created_at: 2007-10-24 10:51:01.161639 + model: PublicBody + model_id: 5 +popular_tag_1: + name: popular_agency + id: "5" + created_at: 2007-10-24 10:51:01.161639 + model: PublicBody + model_id: 2 +popular_tag_2: + name: popular_agency + id: "6" + created_at: 2007-10-24 10:51:01.161639 + model: PublicBody + model_id: 3 diff --git a/spec/fixtures/incoming_messages.yml b/spec/fixtures/incoming_messages.yml index e15a466ca..fca5c716c 100644 --- a/spec/fixtures/incoming_messages.yml +++ b/spec/fixtures/incoming_messages.yml @@ -1,7 +1,33 @@ useless_incoming_message: - id: 1 - info_request_id: 101 - updated_at: 2007-11-13 18:09:20.042061 - raw_email_id: 1 - created_at: 2007-11-13 18:09:20.042061 + id: 1 + info_request_id: 101 + updated_at: 2007-11-13 18:09:20.042061 + raw_email_id: 1 + created_at: 2007-11-13 18:09:20.042061 +useful_incoming_message: + id: 2 + info_request_id: 105 + raw_email_id: 2 + created_at: 2012-01-26 10:19:23 + updated_at: 2012-01-26 10:19:23 + +another_useful_incoming_message: + id: 3 + info_request_id: 106 + raw_email_id: 3 + created_at: 2007-11-13 18:09:20 + updated_at: 2007-11-13 18:09:20 + +spam_1_incoming_message: + id: 4 + info_request_id: 107 + raw_email_id: 4 + created_at: 2008-11-13 18:09:20 + updated_at: 2008-11-13 18:09:20 +spam_2_incoming_message: + id: 5 + info_request_id: 108 + raw_email_id: 5 + created_at: 2008-11-13 19:09:20 + updated_at: 2008-11-13 19:09:20 diff --git a/spec/fixtures/info_request_events.yml b/spec/fixtures/info_request_events.yml index 5e3c13083..3266ec634 100644 --- a/spec/fixtures/info_request_events.yml +++ b/spec/fixtures/info_request_events.yml @@ -1,41 +1,152 @@ useless_outgoing_message_event: + id: 900 params_yaml: "--- \n\ :outgoing_message_id: 1\n" - id: 900 info_request_id: 101 event_type: sent created_at: 2007-10-12 01:56:58.586598 described_state: outgoing_message_id: 1 silly_outgoing_message_event: + id: 901 params_yaml: "--- \n\ :outgoing_message_id: 2\n" - id: 901 info_request_id: 103 event_type: sent created_at: 2007-10-14 10:41:12.686264 described_state: outgoing_message_id: 2 useless_incoming_message_event: + id: 902 params_yaml: "--- \n\ :incoming_message_id: 1\n" - id: 902 info_request_id: 101 event_type: response created_at: 2007-11-13 18:09:20.042061 described_state: incoming_message_id: 1 silly_comment_event: + id: 903 params_yaml: "--- \n\ :comment_id: 1\n" incoming_message_id: last_described_at: described_state: - id: "903" - info_request_id: "101" - comment_id: "1" + info_request_id: 101 + comment_id: 1 calculated_state: event_type: comment outgoing_message_id: created_at: 2008-08-12 23:05:12.500942 +badger_outgoing_message_event: + id: 904 + params_yaml: "--- \n\ + :outgoing_message_id: 3\n" + info_request_id: 104 + event_type: sent + created_at: 2011-10-12 01:56:58.586598 + described_state: waiting_response + calculated_state: waiting_response + outgoing_message_id: 3 + +# These in chronological order +boring_outgoing_message_event: + id: 905 + params_yaml: "--- \n\ + :outgoing_message_id: 4\n" + outgoing_message_id: 4 + info_request_id: 105 + event_type: sent + created_at: 2006-01-12 01:56:58.586598 + described_state: waiting_response + calculated_state: waiting_response +useful_incoming_message_event: + id: 906 + params_yaml: "--- \n\ + :incoming_message_id: 2\n" + incoming_message_id: 2 + info_request_id: 105 + event_type: response + created_at: 2007-11-13 18:00:20 + described_state: successful + calculated_state: successful + +another_boring_outgoing_message_event: + id: 907 + params_yaml: "--- \n\ + :outgoing_message_id: 5\n" + outgoing_message_id: 5 + info_request_id: 106 + event_type: sent + created_at: 2006-01-12 01:56:58.586598 + described_state: waiting_response + calculated_state: waiting_response +another_useful_incoming_message_event: + id: 908 + params_yaml: "--- \n\ + :incoming_message_id: 3\n" + incoming_message_id: 3 + info_request_id: 106 + event_type: response + created_at: 2007-11-13 18:09:20.042061 + described_state: successful + calculated_state: successful + +another_comment_event: + id: 909 + info_request_id: 105 + comment_id: 2 + params_yaml: "--- \n\ + :comment_id: 2\n" + incoming_message_id: + outgoing_message_id: + last_described_at: + described_state: + calculated_state: + event_type: comment + created_at: 2008-08-12 12:05:12.879634 + + +# The spam requests were both successful +spam_1_outgoing_message_event: + id: 910 + params_yaml: "--- \n\ + :outgoing_message_id: 6\n" + outgoing_message_id: 6 + info_request_id: 107 + event_type: sent + created_at: 2001-01-02 01:23:45.6789100 + described_state: waiting_response + calculated_state: waiting_response +spam_1_incoming_message_event: + id: 911 + params_yaml: "--- \n\ + :incoming_message_id: 4\n" + incoming_message_id: 4 + info_request_id: 107 + event_type: response + created_at: 2001-01-03 01:23:45.6789100 + described_state: successful + calculated_state: successful + +spam_2_outgoing_message_event: + id: 912 + params_yaml: "--- \n\ + :outgoing_message_id: 7\n" + outgoing_message_id: 7 + info_request_id: 108 + event_type: sent + created_at: 2001-01-02 02:23:45.6789100 + described_state: waiting_response + calculated_state: waiting_response +spam_2_incoming_message_event: + id: 913 + params_yaml: "--- \n\ + :incoming_message_id: 5\n" + incoming_message_id: 5 + info_request_id: 108 + event_type: response + created_at: 2001-01-03 02:23:45.6789100 + described_state: successful + calculated_state: successful diff --git a/spec/fixtures/info_requests.yml b/spec/fixtures/info_requests.yml index c1e3c1910..33e9a16f2 100644 --- a/spec/fixtures/info_requests.yml +++ b/spec/fixtures/info_requests.yml @@ -18,5 +18,63 @@ naughty_chicken_request: public_body_id: 2 user_id: 1 described_state: waiting_response - awaiting_description: false + awaiting_description: false idhash: e8d18c84 +badger_request: + id: 104 + title: Are you really a badger? + url_title: are_you_really_a_badger + created_at: 2011-10-13 18:15:57 + updated_at: 2011-10-13 18:15:57 + public_body_id: 3 + user_id: 1 + described_state: waiting_response + awaiting_description: false + idhash: e8d18c84 +boring_request: + id: 105 + title: The cost of boring + url_title: the_cost_of_boring + created_at: 2006-01-12 01:56:58.586598 + updated_at: 2008-08-12 12:05:12.879634 + public_body_id: 3 + user_id: 1 + described_state: successful + awaiting_description: false + idhash: 173fd003 +another_boring_request: + id: 106 + title: The cost of boring + url_title: the_cost_of_boring_two # Not _2, because we want to avoid the search collapsing these two + created_at: 2006-01-12 01:56:58.586598 + updated_at: 2007-11-13 18:09:20.042061 + public_body_id: 5 + user_id: 1 + described_state: successful + awaiting_description: false + idhash: 173fd004 + +# A pair of identical requests (with url_title differing only in the numeric suffix) +# used to test the request de-duplication features. +spam_1_request: + id: 107 + title: Cheap v1agra + url_title: spam_1 + created_at: 2010-01-01 01:23:45.6789100 + created_at: 2010-01-01 01:23:45.6789100 + public_body_id: 5 + user_id: 5 + described_state: successful + awaiting_description: false + idhash: 173fd005 +spam_2_request: + id: 108 + title: Cheap v1agra + url_title: spam_2 + created_at: 2010-01-01 02:23:45.6789100 + created_at: 2010-01-01 02:23:45.6789100 + public_body_id: 6 + user_id: 5 + described_state: successful + awaiting_description: false + idhash: 173fd005 diff --git a/spec/fixtures/outgoing_messages.yml b/spec/fixtures/outgoing_messages.yml index b89492aa5..d33ca4292 100644 --- a/spec/fixtures/outgoing_messages.yml +++ b/spec/fixtures/outgoing_messages.yml @@ -33,4 +33,56 @@ silly_outgoing_message: last_sent_at: 2007-10-14 10:41:12.686264 created_at: 2007-10-14 01:56:58.586598 what_doing: normal_sort +badger_outgoing_message: + id: 3 + info_request_id: 104 + message_type: initial_request + status: sent + updated_at: 2011-10-14 01:56:58.586598 + body: "Is it true that you are really a badger, in fact?" + last_sent_at: 2011-10-14 10:41:12.686264 + created_at: 2011-10-14 01:56:58.586598 + what_doing: normal_sort +boring_outgoing_message: + id: 4 + info_request_id: 105 + message_type: initial_request + status: sent + updated_at: 2012-01-14 01:56:58.586598 + body: "How much was spent on boring equipment in the 2010-2011 financial year?" + last_sent_at: 2012-01-14 10:41:12.686264 + created_at: 2012-01-14 01:56:58.586598 + what_doing: normal_sort + +another_boring_outgoing_message: + id: 5 + info_request_id: 106 + message_type: initial_request + status: sent + body: "How much was spent on boring equipment in the 2010-2011 financial year?" + last_sent_at: 2006-01-12 01:57:58.586598 + created_at: 2006-01-12 01:56:58.586598 + updated_at: 2006-01-12 01:56:58.586598 + what_doing: normal_sort + +spam_1_outgoing_message: + id: 6 + info_request_id: 107 + message_type: initial_request + status: sent + body: "Would you like some cheap v1agra?" + last_sent_at: 2007-01-12 01:57:58.586598 + created_at: 2007-01-12 01:56:58.586598 + updated_at: 2007-01-12 01:56:58.586598 + what_doing: normal_sort +spam_2_outgoing_message: + id: 7 + info_request_id: 108 + message_type: initial_request + status: sent + body: "Would you like some cheap v1agra?" + last_sent_at: 2007-01-12 02:57:58.586598 + created_at: 2007-01-12 02:56:58.586598 + updated_at: 2007-01-12 02:56:58.586598 + what_doing: normal_sort diff --git a/spec/fixtures/public_bodies.yml b/spec/fixtures/public_bodies.yml index 191dd68bb..a0893f1e5 100644 --- a/spec/fixtures/public_bodies.yml +++ b/spec/fixtures/public_bodies.yml @@ -16,10 +16,49 @@ humpadink_public_body: updated_at: 2007-10-25 10:51:01.161639 last_edit_comment: Not sure what this new department does. request_email: humpadink-requests@localhost - id: "3" + id: 3 version: "2" last_edit_editor: "francis" short_name: DfH url_name: dfh created_at: 2007-10-25 10:51:01.161639 notes: An albatross told me!!! +forlorn_public_body: + name: "Department of Loneliness" + first_letter: D + updated_at: 2011-01-26 14:11:02.12345 + last_edit_comment: 'Aw, bless.' + request_email: forlorn-requests@localhost + id: 4 + version: 1 + last_edit_editor: "robin" + short_name: DoL + url_name: lonely + created_at: 2011-01-26 14:11:02.12345 + notes: A very lonely public body that no one has corresponded with +silly_walks_public_body: + id: 5 + version: 1 + name: "Ministry of Silly Walks" + first_letter: M + updated_at: 2007-10-25 10:51:01.161639 + last_edit_comment: Is a comment really required? + request_email: silly-walks-requests@localhost + last_edit_editor: robin + short_name: MSW + url_name: msw + created_at: 2007-10-25 10:51:01.161639 + notes: You know the one. +sensible_walks_public_body: + id: 6 + version: 1 + name: "Ministry of Sensible Walks" + first_letter: M + request_email: sensible-walks-requests@localhost + short_name: SenseWalk + url_name: sensible_walks + notes: I bet you’ve never heard of it. + updated_at: 2008-10-25 10:51:01.161639 + last_edit_comment: Another stunning innovation from your friendly national government + last_edit_editor: robin + created_at: 2008-10-25 10:51:01.161639 diff --git a/spec/fixtures/public_body_translations.yml b/spec/fixtures/public_body_translations.yml index b5e947044..cbb55bb0c 100644 --- a/spec/fixtures/public_body_translations.yml +++ b/spec/fixtures/public_body_translations.yml @@ -24,8 +24,8 @@ humpadink_es_public_body_translation: name: "El Department for Humpadinking" first_letter: E request_email: humpadink-requests@localhost - id: "3" - public_body_id: "3" + id: 3 + public_body_id: 3 short_name: eDfH url_name: edfh locale: es @@ -35,9 +35,42 @@ humpadink_en_public_body_translation: name: "Department for Humpadinking" first_letter: D request_email: humpadink-requests@localhost - id: "4" - public_body_id: "3" + id: 4 + public_body_id: 3 short_name: DfH url_name: dfh locale: en notes: An albatross told me!!! + +forlorn_en_public_body_translation: + name: "Department of Loneliness" + first_letter: D + request_email: forlorn-requests@localhost + id: 5 + public_body_id: 4 + short_name: DoL + url_name: lonely + locale: en + notes: A very lonely public body that no one has corresponded with + +silly_walks_en_public_body_translation: + id: 6 + public_body_id: 5 + locale: en + name: "Ministry of Silly Walks" + first_letter: M + request_email: silly-walks-requests@localhost + short_name: MSW + url_name: msw + notes: You know the one. + +sensible_walks_en_public_body_translation: + id: 7 + public_body_id: 6 + locale: en + name: "Ministry of Sensible Walks" + first_letter: M + request_email: sensible-walks-requests@localhost + short_name: SenseWalk + url_name: sensible_walks + notes: I bet you’ve never heard of it. diff --git a/spec/fixtures/raw_emails.yml b/spec/fixtures/raw_emails.yml index 8ef9248a3..ad2bc0a63 100644 --- a/spec/fixtures/raw_emails.yml +++ b/spec/fixtures/raw_emails.yml @@ -1,2 +1,20 @@ -useless_raw_email: +# The actual email messages are in fixtures/files/raw_emails +# +# Note that the words "money" and "bob" are used in some tests +# of the search functions, so if you use either of these words +# in the email text then some tests will have to be updated. + +useless_raw_email: id: 1 + +useful_raw_email: + id: 2 + +another_useful_raw_email: + id: 3 + +spam_1_raw_email: + id: 4 + +spam_2_raw_email: + id: 5 diff --git a/spec/fixtures/users.yml b/spec/fixtures/users.yml index 16ffec034..8620fb3de 100644 --- a/spec/fixtures/users.yml +++ b/spec/fixtures/users.yml @@ -53,3 +53,16 @@ unconfirmed_user: admin_level: 'none' ban_text: '' about_me: '' +robin_user: + id: 5 + name: Robin Houston + url_name: robin_houston + email: robin@localhost + salt: "-6116981980.392287733335677" + hashed_password: 6b7cd45a5f35fd83febc0452a799530398bfb6e8 # jonespassword + updated_at: 2012-01-01 10:39:15.491593 + created_at: 2012-01-01 10:39:15.491593 + email_confirmed: true + admin_level: 'none' + ban_text: '' + about_me: 'I am the best' diff --git a/spec/helpers/link_to_helper_spec.rb b/spec/helpers/link_to_helper_spec.rb index aae00c298..3fa91a8f8 100644 --- a/spec/helpers/link_to_helper_spec.rb +++ b/spec/helpers/link_to_helper_spec.rb @@ -7,9 +7,14 @@ describe LinkToHelper do describe 'when creating a url for a request' do before do - ActionController::Routing::Routes.filters.clear @mock_request = mock_model(InfoRequest, :url_title => 'test_title') + @old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new end + after do + ActionController::Routing::Routes.filters = @old_filters + end + it 'should return a path like /request/test_title' do request_url(@mock_request).should == '/request/test_title' @@ -20,5 +25,17 @@ describe LinkToHelper do end end + + describe "when appending something to a URL" do + it 'should append to things without query strings' do + main_url('/a', '.json').should == 'http://test.localdomain/a.json' + end + it 'should append to things with query strings' do + main_url('/a?z=1', '.json').should == 'http://test.localdomain/a.json?z=1' + end + it 'should fail silently with invalid URLs' do + main_url('/a?z=9%', '.json').should == 'http://test.localdomain/a?z=9%' + end + end end diff --git a/spec/integration/errors_spec.rb b/spec/integration/errors_spec.rb index bfb7e5fb5..ec2e1c376 100644 --- a/spec/integration/errors_spec.rb +++ b/spec/integration/errors_spec.rb @@ -2,22 +2,8 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe "When rendering errors" do - fixtures [ - :users, - :public_bodies, - :public_body_translations, - :public_body_versions, - :info_requests, - :raw_emails, - :outgoing_messages, - :incoming_messages, - :comments, - :info_request_events, - :track_things, - ] - before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data ActionController::Base.consider_all_requests_local = false end @@ -45,5 +31,18 @@ describe "When rendering errors" do get("/request/#{ir.url_title}") response.code.should == "500" end + it "should render a 403 for attempts at directory listing for attachments" do + # make a fake cache + foi_cache_path = File.join(File.dirname(__FILE__), '../../cache') + FileUtils.mkdir_p(File.join(foi_cache_path, "views/en/request/101/101/response/1/attach/html/1")) + get("/request/101/response/1/attach/html/1/" ) + response.code.should == "403" + get("/request/101/response/1/attach/html" ) + response.code.should == "403" + end + it "should render a 404 for non-existent 'details' pages for requests" do + get("/details/request/wobble" ) + response.code.should == "404" + end end diff --git a/spec/integration/search_request_spec.rb b/spec/integration/search_request_spec.rb index 07839af32..b62f0a4c4 100644 --- a/spec/integration/search_request_spec.rb +++ b/spec/integration/search_request_spec.rb @@ -2,23 +2,9 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe "When searching" do - fixtures [ - :users, - :public_bodies, - :public_body_translations, - :public_body_versions, - :info_requests, - :raw_emails, - :outgoing_messages, - :incoming_messages, - :comments, - :info_request_events, - :track_things, - ] - before(:each) do - emails = raw_emails.clone - load_raw_emails_data(emails) + load_raw_emails_data + rebuild_xapian_index end it "should not strip quotes from quoted query" do @@ -34,33 +20,46 @@ describe "When searching" do end it "should correctly filter searches for requests" do - request_via_redirect("post", "/search/bob/requests") + request_via_redirect("post", "/search/bob/requests") response.body.should_not include("One person found") - response.body.should include("FOI requests 1 to 2 of 2") + n = 4 # The number of requests that contain the word "bob" somewhere + # in the email text. At present this is: + # - fancy_dog_request + # - naughty_chicken_request + # - boring_request + # - another_boring_request + # + # In other words it is all requests made by Bob Smith + # except for badger_request, which he did not sign. + response.body.should include("FOI requests 1 to #{n} of #{n}") end it "should correctly filter searches for users" do - request_via_redirect("post", "/search/bob/users") + request_via_redirect("post", "/search/bob/users") response.body.should include("One person found") - response.body.should_not include("FOI requests 1 to 2 of 2") + response.body.should_not include("FOI requests 1 to") end it "should correctly filter searches for successful requests" do - request_via_redirect("post", "/search", + request_via_redirect("post", "/search/requests", :query => "bob", :latest_status => ['successful']) - response.body.should include("no requests matching your query") + n = 2 # The number of *successful* requests that contain the word "bob" somewhere + # in the email text. At present this is: + # - boring_request + # - another_boring_request + response.body.should include("FOI requests 1 to #{n} of #{n}") end it "should correctly filter searches for comments" do - request_via_redirect("post", "/search", + request_via_redirect("post", "/search/requests", :query => "daftest", :request_variety => ['comments']) response.body.should include("One FOI request found") - request_via_redirect("post", "/search", + request_via_redirect("post", "/search/requests", :query => "daftest", :request_variety => ['response','sent']) - response.body.should include("no requests matching your query") + response.body.should include("no results matching your query") end end diff --git a/spec/integration/view_request_spec.rb b/spec/integration/view_request_spec.rb index cf1e4ca6c..442721890 100644 --- a/spec/integration/view_request_spec.rb +++ b/spec/integration/view_request_spec.rb @@ -2,23 +2,8 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe "When viewing requests" do - fixtures [ - :users, - :public_bodies, - :public_body_translations, - :public_body_versions, - :info_requests, - :raw_emails, - :outgoing_messages, - :incoming_messages, - :comments, - :info_request_events, - :track_things, - ] - before(:each) do - emails = raw_emails.clone - load_raw_emails_data(emails) + load_raw_emails_data end it "should not make endlessly recursive JSON <link>s" do diff --git a/spec/lib/sendmail_return_path_spec.rb b/spec/lib/sendmail_return_path_spec.rb index f1a91240b..7708edb35 100644 --- a/spec/lib/sendmail_return_path_spec.rb +++ b/spec/lib/sendmail_return_path_spec.rb @@ -3,6 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe "when sending email with an altered return path" do + before(:each) { ActionMailer::Base.deliveries = [] } it "should default to delivery method test" do ActionMailer::Base.delivery_method.should == :test diff --git a/spec/lib/tmail_extensions_spec.rb b/spec/lib/tmail_extensions_spec.rb index 6a55c34da..bd89e6a84 100644 --- a/spec/lib/tmail_extensions_spec.rb +++ b/spec/lib/tmail_extensions_spec.rb @@ -6,6 +6,10 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe "when using TMail" do + before(:each) do + ActionMailer::Base.deliveries.clear + end + it "should load an email with funny MIME settings" do # just send it to the holding pen InfoRequest.holding_pen_request.incoming_messages.size.should == 0 diff --git a/spec/models/foi_attachment_spec.rb b/spec/models/foi_attachment_spec.rb new file mode 100644 index 000000000..9d44957e4 --- /dev/null +++ b/spec/models/foi_attachment_spec.rb @@ -0,0 +1,35 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe FoiAttachment, " when calculating due date" do + + before(:each) do + load_raw_emails_data + end + + it "sets the body" do + attachment = FoiAttachment.new + attachment.body = "baz" + attachment.body.should == "baz" + end + it "sets the size" do + attachment = FoiAttachment.new + attachment.body = "baz" + attachment.body.should == "baz" + attachment.update_display_size! + attachment.display_size.should == "0K" + end + it "reparses the body if it disappears" do + im = incoming_messages(:useless_incoming_message) + im.extract_attachments! + main = im.get_main_body_text_part + orig_body = main.body + main.delete_cached_file! + lambda { + im.get_main_body_text_part.body + }.should_not raise_error(Errno::ENOENT) + main.delete_cached_file! + main = im.get_main_body_text_part + main.body.should == orig_body + + end +end diff --git a/spec/models/has_tag_string_tag_spec.rb b/spec/models/has_tag_string_tag_spec.rb index 1acd2e27d..57c301471 100644 --- a/spec/models/has_tag_string_tag_spec.rb +++ b/spec/models/has_tag_string_tag_spec.rb @@ -1,7 +1,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe HasTagString::HasTagStringTag, " when fiddling with tag strings " do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things it "should be able to make a new tag and save it" do @tag = HasTagString::HasTagStringTag.new diff --git a/spec/models/holiday_spec.rb b/spec/models/holiday_spec.rb index 973b067d6..00ebc7279 100644 --- a/spec/models/holiday_spec.rb +++ b/spec/models/holiday_spec.rb @@ -1,7 +1,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe Holiday, " when calculating due date" do - fixtures :holidays def due_date(ymd) return Holiday.due_date_from(Date.strptime(ymd), 20).strftime("%F") diff --git a/spec/models/incoming_message_spec.rb b/spec/models/incoming_message_spec.rb index ed31b7c5c..b6fee7898 100644 --- a/spec/models/incoming_message_spec.rb +++ b/spec/models/incoming_message_spec.rb @@ -2,14 +2,19 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe IncomingMessage, " when dealing with incoming mail" do - fixtures :users, :raw_emails, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do @im = incoming_messages(:useless_incoming_message) - load_raw_emails_data(raw_emails) + load_raw_emails_data + end + + after(:all) do + ActionMailer::Base.deliveries.clear end it "should return the mail Date header date for sent at" do + @im.parse_raw_email!(true) + @im.reload @im.sent_at.should == @im.mail.date end @@ -27,6 +32,31 @@ describe IncomingMessage, " when dealing with incoming mail" do end end + it "should ensure cached body text has been parsed correctly" do + ir = info_requests(:fancy_dog_request) + receive_incoming_mail('quoted-subject-iso8859-1.email', ir.incoming_email) + message = ir.incoming_messages[1] + message.get_main_body_text_unfolded.should_not include("Email has no body") + end + + it "should correctly convert HTML even when there's a meta tag asserting that it is iso-8859-1 which would normally confuse elinks" do + ir = info_requests(:fancy_dog_request) + receive_incoming_mail('quoted-subject-iso8859-1.email', ir.incoming_email) + message = ir.incoming_messages[1] + message.parse_raw_email! + message.get_main_body_text_part.charset.should == "iso-8859-1" + message.get_main_body_text_internal.should include("política") + end + + it "should unquote RFC 2047 headers" do + ir = info_requests(:fancy_dog_request) + receive_incoming_mail('quoted-subject-iso8859-1.email', ir.incoming_email) + message = ir.incoming_messages[1] + message.mail_from.should == "Coordenação de Relacionamento, Pesquisa e Informação/CEDI" + message.subject.should == "Câmara Responde: Banco de ideias" + end + + it "should fold multiline sections" do { "foo\n--------\nconfidential" => "foo\nFOLDED_QUOTED_SECTION\n", # basic test @@ -102,16 +132,15 @@ describe IncomingMessage, " folding quoted parts of emails" do end describe IncomingMessage, " checking validity to reply to" do - def test_email(result, email, return_path, autosubmitted) + def test_email(result, email, return_path, autosubmitted = nil) @address = mock(TMail::Address) @address.stub!(:spec).and_return(email) @return_path = mock(TMail::ReturnPathHeader) @return_path.stub!(:addr).and_return(return_path) - - @autosubmitted = mock(TMail::KeywordsHeader) - @autosubmitted.stub!(:keys).and_return(autosubmitted) - + if !autosubmitted.nil? + @autosubmitted = TMail::UnstructuredHeader.new("auto-submitted", autosubmitted) + end @mail = mock(TMail::Mail) @mail.stub!(:from_addrs).and_return( [ @address ] ) @mail.stub!(:[]).with("return-path").and_return(@return_path) @@ -123,45 +152,44 @@ describe IncomingMessage, " checking validity to reply to" do end it "says a valid email is fine" do - test_email(true, "team@mysociety.org", nil, []) + test_email(true, "team@mysociety.org", nil) end it "says postmaster email is bad" do - test_email(false, "postmaster@mysociety.org", nil, []) + test_email(false, "postmaster@mysociety.org", nil) end it "says Mailer-Daemon email is bad" do - test_email(false, "Mailer-Daemon@mysociety.org", nil, []) + test_email(false, "Mailer-Daemon@mysociety.org", nil) end it "says case mangled MaIler-DaemOn email is bad" do - test_email(false, "MaIler-DaemOn@mysociety.org", nil, []) + test_email(false, "MaIler-DaemOn@mysociety.org", nil) end it "says Auto_Reply email is bad" do - test_email(false, "Auto_Reply@mysociety.org", nil, []) + test_email(false, "Auto_Reply@mysociety.org", nil) end it "says DoNotReply email is bad" do - test_email(false, "DoNotReply@tube.tfl.gov.uk", nil, []) + test_email(false, "DoNotReply@tube.tfl.gov.uk", nil) end it "says a filled-out return-path is fine" do - test_email(true, "team@mysociety.org", "Return-path: <foo@baz.com>", []) + test_email(true, "team@mysociety.org", "Return-path: <foo@baz.com>") end it "says an empty return-path is bad" do - test_email(false, "team@mysociety.org", "<>", []) + test_email(false, "team@mysociety.org", "<>") end it "says an auto-submitted keyword is bad" do - test_email(false, "team@mysociety.org", nil, ["auto-replied"]) + test_email(false, "team@mysociety.org", nil, "auto-replied") end end describe IncomingMessage, " checking validity to reply to with real emails" do - fixtures :users, :raw_emails, :public_bodies, :public_body_translations, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things after(:all) do ActionMailer::Base.deliveries.clear @@ -185,7 +213,6 @@ describe IncomingMessage, " checking validity to reply to with real emails" do end describe IncomingMessage, " when censoring data" do - fixtures :users, :raw_emails, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do @test_data = "There was a mouse called Stilton, he wished that he was blue." @@ -206,7 +233,7 @@ describe IncomingMessage, " when censoring data" do @censor_rule_2.last_edit_comment = "none" @im.info_request.censor_rules << @censor_rule_2 - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should do nothing to a JPEG" do @@ -293,7 +320,6 @@ describe IncomingMessage, " when censoring data" do end describe IncomingMessage, " when censoring whole users" do - fixtures :users, :raw_emails, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do @test_data = "There was a mouse called Stilton, he wished that he was blue." @@ -306,7 +332,7 @@ describe IncomingMessage, " when censoring whole users" do @censor_rule_1.last_edit_editor = "unknown" @censor_rule_1.last_edit_comment = "none" @im.info_request.user.censor_rules << @censor_rule_1 - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should apply censor rules to HTML files" do @@ -324,10 +350,9 @@ end describe IncomingMessage, " when uudecoding bad messages" do - fixtures :incoming_messages, :raw_emails, :public_bodies, :public_body_translations, :info_requests, :users, :foi_attachments before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should be able to do it at all" do @@ -368,10 +393,9 @@ describe IncomingMessage, " when uudecoding bad messages" do end describe IncomingMessage, "when messages are attached to messages" do - fixtures :incoming_messages, :raw_emails, :public_bodies, :public_body_translations, :info_requests, :users, :foi_attachments before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should flatten all the attachments out" do @@ -393,10 +417,9 @@ describe IncomingMessage, "when messages are attached to messages" do end describe IncomingMessage, "when Outlook messages are attached to messages" do - fixtures :incoming_messages, :raw_emails, :public_bodies, :public_body_translations, :info_requests, :users, :foi_attachments before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should flatten all the attachments out" do @@ -416,10 +439,9 @@ describe IncomingMessage, "when Outlook messages are attached to messages" do end describe IncomingMessage, "when TNEF attachments are attached to messages" do - fixtures :incoming_messages, :raw_emails, :public_bodies, :public_body_translations, :info_requests, :users, :foi_attachments before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should flatten all the attachments out" do diff --git a/spec/models/info_request_event_spec.rb b/spec/models/info_request_event_spec.rb index 3229284cc..7352f3be0 100644 --- a/spec/models/info_request_event_spec.rb +++ b/spec/models/info_request_event_spec.rb @@ -29,7 +29,8 @@ describe InfoRequestEvent do describe "should know" do it "that it's an incoming message" do - event = InfoRequestEvent.new(:incoming_message => mock_model(IncomingMessage)) + event = InfoRequestEvent.new() + event.stub!(:incoming_message_selective_columns).and_return(1) event.is_incoming_message?.should be_true event.is_outgoing_message?.should be_false event.is_comment?.should be_false @@ -37,6 +38,7 @@ describe InfoRequestEvent do it "that it's an outgoing message" do event = InfoRequestEvent.new(:outgoing_message => mock_model(OutgoingMessage)) + event.id = 1 event.is_incoming_message?.should be_false event.is_outgoing_message?.should be_true event.is_comment?.should be_false @@ -44,6 +46,7 @@ describe InfoRequestEvent do it "that it's a comment" do event = InfoRequestEvent.new(:comment => mock_model(Comment)) + event.id = 1 event.is_incoming_message?.should be_false event.is_outgoing_message?.should be_false event.is_comment?.should be_true @@ -52,10 +55,9 @@ describe InfoRequestEvent do end describe "doing search/index stuff" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data parse_all_incoming_messages end @@ -70,6 +72,14 @@ describe InfoRequestEvent do event.search_text_main.strip.should == "No way! I'm not going to tell you that in a month of Thursdays.\n\nThe Geraldine Quango" end + it 'should get clipped text for incoming messages, and cache it too' do + event = info_request_events(:useless_incoming_message_event) + + event.incoming_message_selective_columns("cached_main_body_text_folded").cached_main_body_text_folded = nil + event.search_text_main(true).strip.should == "No way! I'm not going to tell you that in a month of Thursdays.\n\nThe Geraldine Quango" + event.incoming_message_selective_columns("cached_main_body_text_folded").cached_main_body_text_folded.should_not == nil + end + end diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb index b1baa66a2..a18a4bd1d 100644 --- a/spec/models/info_request_spec.rb +++ b/spec/models/info_request_spec.rb @@ -3,11 +3,10 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe InfoRequest do describe "guessing a request from an email" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do @im = incoming_messages(:useless_incoming_message) - load_raw_emails_data(raw_emails) + load_raw_emails_data end it 'should compute a hash' do @@ -73,8 +72,6 @@ describe InfoRequest do end describe " when emailing" do - - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @info_request = info_requests(:fancy_dog_request) @@ -154,7 +151,6 @@ describe InfoRequest do end describe "when calculating the status" do - fixtures :holidays, :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @ir = info_requests(:naughty_chicken_request) @@ -196,8 +192,6 @@ describe InfoRequest do describe "when using a plugin and calculating the status" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things - before do InfoRequest.send(:require, File.expand_path(File.dirname(__FILE__) + '/customstates')) InfoRequest.send(:include, InfoRequestCustomStates) @@ -231,7 +225,6 @@ describe InfoRequest do describe "when calculating the status for a school" do - fixtures :holidays, :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @ir = info_requests(:naughty_chicken_request) diff --git a/spec/models/outgoing_mailer_spec.rb b/spec/models/outgoing_mailer_spec.rb index 75c8053b4..5d1ea2dfb 100644 --- a/spec/models/outgoing_mailer_spec.rb +++ b/spec/models/outgoing_mailer_spec.rb @@ -4,9 +4,8 @@ describe OutgoingMailer, " when working out follow up addresses" do # This is done with fixtures as the code is a bit tangled with the way it # calls TMail. XXX untangle it and make these tests spread out and using # mocks. Put parts of the tests in spec/lib/tmail_extensions.rb - fixtures :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should parse them right" do @@ -24,6 +23,7 @@ describe OutgoingMailer, " when working out follow up addresses" do im = ir.incoming_messages[0] im.raw_email.data = im.raw_email.data.sub("\"FOI Person\" <foiperson@localhost>", "foiperson@localhost") + im.parse_raw_email! true # check the basic entry in the fixture is fine OutgoingMailer.name_and_email_for_followup(ir, im).should == "foiperson@localhost" @@ -36,6 +36,7 @@ describe OutgoingMailer, " when working out follow up addresses" do im = ir.incoming_messages[0] im.raw_email.data = im.raw_email.data.sub("FOI Person", "FOI [ Person") + im.parse_raw_email! true # check the basic entry in the fixture is fine OutgoingMailer.name_and_email_for_followup(ir, im).should == "\"FOI [ Person\" <foiperson@localhost>" @@ -48,6 +49,7 @@ describe OutgoingMailer, " when working out follow up addresses" do im = ir.incoming_messages[0] im.raw_email.data = im.raw_email.data.sub("FOI Person", "FOI \\\" Person") + im.parse_raw_email! true # check the basic entry in the fixture is fine OutgoingMailer.name_and_email_for_followup(ir, im).should == "\"FOI \\\" Person\" <foiperson@localhost>" @@ -60,6 +62,7 @@ describe OutgoingMailer, " when working out follow up addresses" do im = ir.incoming_messages[0] im.raw_email.data = im.raw_email.data.sub("FOI Person", "FOI @ Person") + im.parse_raw_email! true # check the basic entry in the fixture is fine OutgoingMailer.name_and_email_for_followup(ir, im).should == "\"FOI @ Person\" <foiperson@localhost>" @@ -70,10 +73,9 @@ describe OutgoingMailer, " when working out follow up addresses" do end describe OutgoingMailer, "when working out follow up subjects" do - fixtures :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data end it "should prefix the title with 'Freedom of Information request -' for initial requests" do @@ -116,6 +118,8 @@ describe OutgoingMailer, "when working out follow up subjects" do om.incoming_message_followup = im im.raw_email.data = im.raw_email.data.sub("Subject: Geraldine FOI Code AZXB421", "Subject: re: Geraldine FOI Code AZXB421") + im.parse_raw_email! true + OutgoingMailer.subject_for_followup(ir, om).should == "re: Geraldine FOI Code AZXB421" end @@ -127,6 +131,8 @@ describe OutgoingMailer, "when working out follow up subjects" do im.raw_email.data = im.raw_email.data.sub("foiperson@localhost", "postmaster@localhost") im.raw_email.data = im.raw_email.data.sub("Subject: Geraldine FOI Code AZXB421", "Subject: Delivery Failed") + im.parse_raw_email! true + OutgoingMailer.subject_for_followup(ir, om).should == "Re: Freedom of Information request - Why do you have & such a fancy dog?" end end diff --git a/spec/models/outgoing_message_spec.rb b/spec/models/outgoing_message_spec.rb index 58d9f398e..51bb6fdf5 100644 --- a/spec/models/outgoing_message_spec.rb +++ b/spec/models/outgoing_message_spec.rb @@ -1,7 +1,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe OutgoingMessage, " when making an outgoing message" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @om = outgoing_messages(:useless_outgoing_message) @@ -38,7 +37,6 @@ end describe IncomingMessage, " when censoring data" do - fixtures :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @om = outgoing_messages(:useless_outgoing_message) diff --git a/spec/models/public_body_spec.rb b/spec/models/public_body_spec.rb index 07e8f291d..db0de78b2 100644 --- a/spec/models/public_body_spec.rb +++ b/spec/models/public_body_spec.rb @@ -95,7 +95,6 @@ describe PublicBody, " using machine tags" do end describe PublicBody, "when finding_by_tags" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @geraldine = public_bodies(:geraldine_public_body) @@ -173,7 +172,6 @@ describe PublicBody, " when saving" do end describe PublicBody, "when searching" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things it "should find by existing url name" do body = PublicBody.find_by_url_name_with_historic('dfh') @@ -249,10 +247,8 @@ describe PublicBody, " when loading CSV files" do errors, notes = PublicBody.import_csv("1,aBody", '', 'replace', true, 'someadmin') # true means dry run errors.should == [] notes.size.should == 2 - notes.should == [ - "line 1: creating new authority 'aBody' (locale: en):\n\t{\"name\":\"aBody\"}", - "Notes: Some bodies are in database, but not in CSV file:\n Department for Humpadinking\n Geraldine Quango\nYou may want to delete them manually.\n" - ] + notes[0].should == "line 1: creating new authority 'aBody' (locale: en):\n\t{\"name\":\"aBody\"}" + notes[1].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( [A-Za-z ]+\n)*You may want to delete them manually.\n/ end it "should do a dry run successfully" do @@ -262,12 +258,12 @@ describe PublicBody, " when loading CSV files" do errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', true, 'someadmin') # true means dry run errors.should == [] notes.size.should == 4 - notes.should == [ + notes[0..2].should == [ "line 1: creating new authority 'North West Fake Authority' (locale: en):\n\t\{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\"\}", "line 2: creating new authority 'Scottish Fake Authority' (locale: en):\n\t\{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\"\}", "line 3: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t\{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\"\}", - "Notes: Some bodies are in database, but not in CSV file:\n Department for Humpadinking\n Geraldine Quango\nYou may want to delete them manually.\n" - ] + ] + notes[3].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( [A-Za-z ]+\n)*You may want to delete them manually.\n/ PublicBody.count.should == original_count end @@ -279,12 +275,12 @@ describe PublicBody, " when loading CSV files" do errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', false, 'someadmin') # false means real run errors.should == [] notes.size.should == 4 - notes.should == [ + notes[0..2].should == [ "line 1: creating new authority 'North West Fake Authority' (locale: en):\n\t\{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\"\}", "line 2: creating new authority 'Scottish Fake Authority' (locale: en):\n\t\{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\"\}", "line 3: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t\{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\"\}", - "Notes: Some bodies are in database, but not in CSV file:\n Department for Humpadinking\n Geraldine Quango\nYou may want to delete them manually.\n" - ] + ] + notes[3].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( [A-Za-z ]+\n)*You may want to delete them manually.\n/ PublicBody.count.should == original_count + 3 end @@ -296,12 +292,12 @@ describe PublicBody, " when loading CSV files" do errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', false, 'someadmin') # false means real run errors.should == [] notes.size.should == 4 - notes.should == [ + notes[0..2].should == [ "line 1: creating new authority 'North West Fake Authority' (locale: en):\n\t\{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\"\}", "line 2: creating new authority 'Scottish Fake Authority' (locale: en):\n\t\{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\"\}", "line 3: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t\{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\"\}", - "Notes: Some bodies are in database, but not in CSV file:\n Department for Humpadinking\n Geraldine Quango\nYou may want to delete them manually.\n" - ] + ] + notes[3].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( [A-Za-z ]+\n)*You may want to delete them manually.\n/ PublicBody.count.should == original_count + 3 end @@ -312,12 +308,12 @@ describe PublicBody, " when loading CSV files" do errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', true, 'someadmin') # true means dry run errors.should == [] notes.size.should == 4 - notes.should == [ + notes[0..2].should == [ "line 2: creating new authority 'North West Fake Authority' (locale: en):\n\t\{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\",\"home_page\":\"http://northwest.org\"\}", "line 3: creating new authority 'Scottish Fake Authority' (locale: en):\n\t\{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\",\"home_page\":\"http://scottish.org\",\"tag_string\":\"scottish\"\}", "line 4: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t\{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\",\"tag_string\":\"fake aTag\"\}", - "Notes: Some bodies are in database, but not in CSV file:\n Department for Humpadinking\n Geraldine Quango\nYou may want to delete them manually.\n" - ] + ] + notes[3].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( [A-Za-z ]+\n)*You may want to delete them manually.\n/ PublicBody.count.should == original_count end @@ -366,15 +362,15 @@ describe PublicBody, " when loading CSV files" do errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', false, 'someadmin', [:en, :es]) errors.should == [] notes.size.should == 7 - notes.should == [ + notes[0..5].should == [ "line 2: creating new authority 'North West Fake Authority' (locale: en):\n\t{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\",\"home_page\":\"http://northwest.org\"}", "line 2: creating new authority 'North West Fake Authority' (locale: es):\n\t{\"name\":\"Autoridad del Nordeste\"}", "line 3: creating new authority 'Scottish Fake Authority' (locale: en):\n\t{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\",\"home_page\":\"http://scottish.org\",\"tag_string\":\"scottish\"}", "line 3: creating new authority 'Scottish Fake Authority' (locale: es):\n\t{\"name\":\"Autoridad Escocesa\"}", "line 4: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\",\"tag_string\":\"fake aTag\"}", "line 4: creating new authority 'Fake Authority of Northern Ireland' (locale: es):\n\t{\"name\":\"Autoridad Irlandesa\"}", - "Notes: Some bodies are in database, but not in CSV file:\n Department for Humpadinking\n Geraldine Quango\nYou may want to delete them manually.\n" - ] + ] + notes[6].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( [A-Za-z ]+\n)*You may want to delete them manually.\n/ PublicBody.count.should == original_count + 3 @@ -395,12 +391,12 @@ describe PublicBody, " when loading CSV files" do errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', true, 'someadmin', ['en', :xx]) # true means dry run errors.should == [] notes.size.should == 4 - notes.should == [ + notes[0..2].should == [ "line 2: creating new authority 'North West Fake Authority' (locale: en):\n\t{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\",\"home_page\":\"http://northwest.org\"}", "line 3: creating new authority 'Scottish Fake Authority' (locale: en):\n\t{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\",\"home_page\":\"http://scottish.org\",\"tag_string\":\"scottish\"}", "line 4: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\",\"tag_string\":\"fake aTag\"}", - "Notes: Some bodies are in database, but not in CSV file:\n Department for Humpadinking\n Geraldine Quango\nYou may want to delete them manually.\n" - ] + ] + notes[3].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( [A-Za-z ]+\n)*You may want to delete them manually.\n/ PublicBody.count.should == original_count end diff --git a/spec/models/request_mailer_spec.rb b/spec/models/request_mailer_spec.rb index ef4ed8074..64ac35cf7 100644 --- a/spec/models/request_mailer_spec.rb +++ b/spec/models/request_mailer_spec.rb @@ -1,9 +1,9 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe RequestMailer, " when receiving incoming mail" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + ActionMailer::Base.deliveries = [] end it "should append it to the appropriate request" do diff --git a/spec/models/track_mailer_spec.rb b/spec/models/track_mailer_spec.rb index 67a64ee10..4f5499a90 100644 --- a/spec/models/track_mailer_spec.rb +++ b/spec/models/track_mailer_spec.rb @@ -155,6 +155,7 @@ describe TrackMailer do @post_redirect = mock_model(PostRedirect, :save! => true, :email_token => "token") PostRedirect.stub!(:new).and_return(@post_redirect) + ActionMailer::Base.deliveries = [] end it 'should deliver one email, with right headers' do diff --git a/spec/models/track_thing_spec.rb b/spec/models/track_thing_spec.rb index 4922a96c7..bd122941a 100644 --- a/spec/models/track_thing_spec.rb +++ b/spec/models/track_thing_spec.rb @@ -1,7 +1,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe TrackThing, "when tracking changes" do - fixtures :users, :info_requests, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @track_thing = track_things(:track_fancy_dog_search) @@ -28,6 +27,13 @@ describe TrackThing, "when tracking changes" do found_track.should == @track_thing end + it "can display the description of a deleted track_thing" do + track_thing = TrackThing.create_track_for_search_query('fancy dog') + description = track_thing.track_query_description + track_thing.destroy + track_thing.track_query_description.should == description + end + it "will make some sane descriptions of search-based tracks" do tests = [['bob variety:user', "users matching text 'bob'"], ['bob (variety:sent OR variety:followup_sent OR variety:response OR variety:comment) (latest_status:successful OR latest_status:partially_successful OR latest_status:rejected OR latest_status:not_held)', "requests which are successful or unsuccessful or comments matching text 'bob'"], diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e0a6c649e..03b2f34f9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -29,7 +29,7 @@ describe User, "showing the name" do it 'should show if user has been banned' do @user.ban_text = "Naughty user" - @user.name.should == 'Some Name (Banned)' + @user.name.should == 'Some Name (Account suspended)' end end @@ -193,7 +193,6 @@ describe User, "when reindexing referencing models" do end describe User, "when checking abilities" do - fixtures :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @user = users(:bob_smith_user) @@ -283,7 +282,6 @@ describe User, "when setting a profile photo" do end describe User, "when unconfirmed" do - fixtures :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before do @user = users(:unconfirmed_user) @@ -295,7 +293,6 @@ describe User, "when unconfirmed" do end describe User, "when emails have bounced" do - fixtures :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things it "should record bounces" do User.record_bounce_for_email("bob@localhost", "The reason we think the email bounced (e.g. a bounce message)") diff --git a/spec/models/xapian_spec.rb b/spec/models/xapian_spec.rb index ec11c944b..81c066184 100644 --- a/spec/models/xapian_spec.rb +++ b/spec/models/xapian_spec.rb @@ -1,19 +1,20 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe User, " when indexing users with Xapian" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things - it "should search by name" do - parse_all_incoming_messages + before(:each) do + load_raw_emails_data rebuild_xapian_index - # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page) + end + + it "should search by name" do + # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page) xapian_object = InfoRequest.full_search([User], "Silly", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 1 xapian_object.results[0][:model].should == users(:silly_name_user) end it "should search by 'about me' text" do - rebuild_xapian_index user = users(:bob_smith_user) # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page) @@ -35,59 +36,51 @@ describe User, " when indexing users with Xapian" do end describe PublicBody, " when indexing public bodies with Xapian" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should search index the main name field" do - rebuild_xapian_index - xapian_object = InfoRequest.full_search([PublicBody], "humpadinking", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 1 xapian_object.results[0][:model].should == public_bodies(:humpadink_public_body) end it "should search index the notes field" do - rebuild_xapian_index - xapian_object = InfoRequest.full_search([PublicBody], "albatross", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 1 xapian_object.results[0][:model].should == public_bodies(:humpadink_public_body) end it "should delete public bodies from the index when they are destroyed" do - rebuild_xapian_index - xapian_object = InfoRequest.full_search([PublicBody], "albatross", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 1 xapian_object.results[0][:model].should == public_bodies(:humpadink_public_body) - public_bodies(:humpadink_public_body).destroy + public_bodies(:forlorn_public_body).destroy update_xapian_index - xapian_object = InfoRequest.full_search([PublicBody], "albatross", 'created_at', true, nil, 100, 1) - xapian_object.results.size.should == 0 + xapian_object = InfoRequest.full_search([PublicBody], "lonely", 'created_at', true, nil, 100, 1) + xapian_object.results.should == [] end end describe PublicBody, " when indexing requests by body they are to" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should find requests to the body" do - rebuild_xapian_index xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 4 end it "should update index correctly when URL name of body changes" do # initial search - rebuild_xapian_index xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 4 models_found_before = xapian_object.results.map { |x| x[:model] } @@ -112,8 +105,6 @@ describe PublicBody, " when indexing requests by body they are to" do # if you index via the Xapian TermGenerator, it ignores terms of this length, # this checks we're using Document:::add_term() instead it "should work with URL names that are longer than 64 characters" do - rebuild_xapian_index - # change the URL name of the body body = public_bodies(:geraldine_public_body) body.short_name = 'The Uncensored, Complete Name of the Quasi-Autonomous Public Body Also Known As Geraldine' @@ -133,28 +124,25 @@ describe PublicBody, " when indexing requests by body they are to" do end describe User, " when indexing requests by user they are from" do - fixtures :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should find requests from the user" do - rebuild_xapian_index xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, nil, 100, 1) - xapian_object.results.size.should == 4 + xapian_object.results.map{|x|x[:model]}.should =~ InfoRequestEvent.all(:conditions => "info_request_id in (select id from info_requests where user_id = #{users(:bob_smith_user).id})") end it "should find just the sent message events from a particular user" do - rebuild_xapian_index # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page) xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith variety:sent", 'created_at', true, nil, 100, 1) - xapian_object.results.size.should == 2 - xapian_object.results[1][:model].should == info_request_events(:useless_outgoing_message_event) - xapian_object.results[0][:model].should == info_request_events(:silly_outgoing_message_event) + xapian_object.results.map{|x|x[:model]}.should =~ InfoRequestEvent.all(:conditions => "info_request_id in (select id from info_requests where user_id = #{users(:bob_smith_user).id}) and event_type = 'sent'") + xapian_object.results[2][:model].should == info_request_events(:useless_outgoing_message_event) + xapian_object.results[1][:model].should == info_request_events(:silly_outgoing_message_event) end it "should not find it when one of the request's users is changed" do - rebuild_xapian_index silly_user = users(:silly_name_user) naughty_chicken_request = info_requests(:naughty_chicken_request) naughty_chicken_request.user = silly_user @@ -164,13 +152,10 @@ describe User, " when indexing requests by user they are from" do # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page) xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, 'request_collapse', 100, 1) - xapian_object.results.size.should == 1 - xapian_object.results[0][:model].should == info_request_events(:silly_comment_event) + xapian_object.results.map{|x|x[:model].info_request}.should =~ InfoRequest.all(:conditions => "user_id = #{users(:bob_smith_user).id}") end it "should not get confused searching for requests when one user has a name which has same stem as another" do - rebuild_xapian_index - bob_smith_user = users(:bob_smith_user) bob_smith_user.name = "John King" bob_smith_user.url_name.should == 'john_king' @@ -196,9 +181,8 @@ describe User, " when indexing requests by user they are from" do it "should update index correctly when URL name of user changes" do # initial search - rebuild_xapian_index xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, nil, 100, 1) - xapian_object.results.size.should == 4 + xapian_object.results.map{|x|x[:model]}.should =~ InfoRequestEvent.all(:conditions => "info_request_id in (select id from info_requests where user_id = #{users(:bob_smith_user).id})") models_found_before = xapian_object.results.map { |x| x[:model] } # change the URL name of the body @@ -212,28 +196,24 @@ describe User, " when indexing requests by user they are from" do xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 0 xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:robert_smith", 'created_at', true, nil, 100, 1) - xapian_object.results.size.should == 4 models_found_after = xapian_object.results.map { |x| x[:model] } - models_found_before.should == models_found_after end end describe User, " when indexing comments by user they are by" do - fixtures :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should find requests from the user" do - rebuild_xapian_index xapian_object = InfoRequest.full_search([InfoRequestEvent], "commented_by:silly_emnameem", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 1 end it "should update index correctly when URL name of user changes" do # initial search - rebuild_xapian_index xapian_object = InfoRequest.full_search([InfoRequestEvent], "commented_by:silly_emnameem", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 1 models_found_before = xapian_object.results.map { |x| x[:model] } @@ -257,13 +237,12 @@ describe User, " when indexing comments by user they are by" do end describe InfoRequest, " when indexing requests by their title" do - fixtures :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should find events for the request" do - rebuild_xapian_index xapian_object = InfoRequest.full_search([InfoRequestEvent], "request:how_much_public_money_is_wasted_o", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 1 xapian_object.results[0][:model] == info_request_events(:silly_outgoing_message_event) @@ -271,7 +250,6 @@ describe InfoRequest, " when indexing requests by their title" do it "should update index correctly when URL title of request changes" do # change the URL name of the body - rebuild_xapian_index ir = info_requests(:naughty_chicken_request) ir.title = 'Really naughty' ir.save! @@ -288,13 +266,12 @@ describe InfoRequest, " when indexing requests by their title" do end describe InfoRequest, " when indexing requests by tag" do - fixtures :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should find request by tag, even when changes" do - rebuild_xapian_index ir = info_requests(:naughty_chicken_request) ir.tag_string = 'bunnyrabbit' ir.save! @@ -310,13 +287,12 @@ describe InfoRequest, " when indexing requests by tag" do end describe PublicBody, " when indexing authorities by tag" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should find request by tag, even when changes" do - rebuild_xapian_index body = public_bodies(:geraldine_public_body) body.tag_string = 'mice:3' body.save! @@ -335,13 +311,12 @@ describe PublicBody, " when indexing authorities by tag" do end describe PublicBody, " when only indexing selected things on a rebuild" do - fixtures :public_bodies, :public_body_translations, :public_body_versions, :users, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things before(:each) do - load_raw_emails_data(raw_emails) + load_raw_emails_data + rebuild_xapian_index end it "should only index what we ask it to" do - rebuild_xapian_index body = public_bodies(:geraldine_public_body) body.tag_string = 'mice:3' body.name = 'frobzn' @@ -357,7 +332,7 @@ describe PublicBody, " when only indexing selected things on a rebuild" do xapian_object = InfoRequest.full_search([PublicBody], "frobzn", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 0 xapian_object = InfoRequest.full_search([PublicBody], "variety:authority", 'created_at', true, nil, 100, 1) - xapian_object.results.size.should == 2 + xapian_object.results.map{|x|x[:model]}.should =~ PublicBody.all # only reindex 'tag' and text dropfirst = true terms = "U" @@ -380,7 +355,7 @@ describe PublicBody, " when only indexing selected things on a rebuild" do xapian_object = InfoRequest.full_search([PublicBody], "frobzn", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 1 xapian_object = InfoRequest.full_search([PublicBody], "variety:authority", 'created_at', true, nil, 100, 1) - xapian_object.results.size.should == 2 + xapian_object.results.map{|x|x[:model]}.should =~ PublicBody.all # only reindex 'variety' term, blowing away existing data dropfirst = true rebuild_xapian_index(terms, values, texts, dropfirst) @@ -389,18 +364,7 @@ describe PublicBody, " when only indexing selected things on a rebuild" do xapian_object = InfoRequest.full_search([PublicBody], "frobzn", 'created_at', true, nil, 100, 1) xapian_object.results.size.should == 0 xapian_object = InfoRequest.full_search([PublicBody], "variety:authority", 'created_at', true, nil, 100, 1) - xapian_object.results.size.should == 2 + xapian_object.results.map{|x|x[:model]}.should =~ PublicBody.all end end - - - - - - - - - - - diff --git a/spec/script/handle-mail-replies_spec.rb b/spec/script/handle-mail-replies_spec.rb index eae0b516b..8ed83b31f 100644 --- a/spec/script/handle-mail-replies_spec.rb +++ b/spec/script/handle-mail-replies_spec.rb @@ -54,5 +54,15 @@ describe "When filtering" do r = mail_reply_test("track-response-messageclass-oof.email") r.status.should == 2 end + + it "should detect an Outlook(?)-style out-of-office" do + r = mail_reply_test("track-response-outlook-oof.email") + r.status.should == 2 + end + + it "should detect an ABCMail-style out-of-office" do + r = mail_reply_test("track-response-abcmail-oof.email") + r.status.should == 2 + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5c5cd9a7f..c00da48bc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,6 @@ # This file is copied to ~/spec when you run 'ruby script/generate rspec' # from the project root directory. -ENV["RAILS_ENV"] ||= 'test' +ENV["RAILS_ENV"] = 'test' require File.expand_path(File.join('..', '..', 'config', 'environment'), __FILE__) require 'spec/autorun' require 'spec/rails' @@ -24,6 +24,7 @@ Spec::Runner.configure do |config| # fixture_path must end in a separator config.fixture_path = File.join(Rails.root, 'spec', 'fixtures') + File::SEPARATOR + config.global_fixtures = :users, :public_bodies, :public_body_translations, :public_body_versions, :info_requests, :raw_emails, :incoming_messages, :outgoing_messages, :comments, :info_request_events, :track_things, :foi_attachments, :has_tag_string_tags, :holidays, :track_things_sent_emails # == Fixtures # @@ -78,7 +79,6 @@ def load_file_fixture(file_name) end def rebuild_xapian_index(terms = true, values = true, texts = true, dropfirst = true) - parse_all_incoming_messages if dropfirst begin ActsAsXapian.readable_init @@ -86,7 +86,9 @@ def rebuild_xapian_index(terms = true, values = true, texts = true, dropfirst = rescue RuntimeError end ActsAsXapian.writable_init + ActsAsXapian.writable_db.close end + parse_all_incoming_messages verbose = false # safe_rebuild=true, which involves forking to avoid memory leaks, doesn't work well with rspec. # unsafe is significantly faster, and we can afford possible memory leaks while testing. @@ -96,7 +98,7 @@ end def update_xapian_index verbose = false - ActsAsXapian.update_index(flush_to_disk=true, verbose) + ActsAsXapian.update_index(flush_to_disk=false, verbose) end # Validate an entire HTML page @@ -106,7 +108,7 @@ def validate_html(html) File.open(tempfilename, "w+") do |f| f.puts html end - if not system($html_validation_script, tempfilename) + if not system($html_validation_script, *($html_validation_script_options +[tempfilename])) raise "HTML validation error in " + tempfilename + " HTTP status: " + @response.response_code.to_s end File.unlink(tempfilename) @@ -120,30 +122,47 @@ def validate_as_body(html) end def basic_auth_login(request, username = nil, password = nil) - username = MySociety::Config.get('ADMIN_USERNAME') if username.nil? + username = MySociety::Config.get('ADMIN_USERNAME') if username.nil? password = MySociety::Config.get('ADMIN_PASSWORD') if password.nil? request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("#{username}:#{password}") end # Monkeypatch! Validate HTML in tests. -$html_validation_script = "/usr/bin/validate" # from Debian package wdg-html-validator +utility_search_path = MySociety::Config.get("UTILITY_SEARCH_PATH", ["/usr/bin", "/usr/local/bin"]) +$html_validation_script_found = false +utility_search_path.each do |d| + $html_validation_script = File.join(d, "validate") + $html_validation_script_options = ["--charset=utf-8"] + if File.file? $html_validation_script and File.executable? $html_validation_script + $html_validation_script_found = true + break + end +end if $tempfilecount.nil? $tempfilecount = 0 - if File.exist?($html_validation_script) + if $html_validation_script_found module ActionController module TestProcess # Hook into the process function, so can automatically get HTML after each request alias :original_process :process - + def is_fragment + # XXX there must be a better way of doing this! + return @request.query_parameters["action"] == "search_typeahead" + end def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') self.original_process(action, parameters, session, flash, http_method) - + # don't validate auto-generated HTML + return if @request.query_parameters["action"] == "get_attachment_as_html" # XXX Is there a better way to check this than calling a private method? return unless @response.template.controller.instance_eval { integrate_views? } - # And then if HTML, not a redirect (302, 301) if @response.content_type == "text/html" && ! [301,302,401].include?(@response.response_code) + if !is_fragment validate_html(@response.body) + else + # it's a partial + validate_as_body(@response.body) + end end end end @@ -161,15 +180,22 @@ def safe_mock_model(model, args = {}) mock end -def load_raw_emails_data(raw_emails) - raw_email = raw_emails(:useless_raw_email) - begin - raw_email.destroy_file_representation! - rescue Errno::ENOENT +def load_raw_emails_data + raw_emails_yml = File.join(Spec::Runner.configuration.fixture_path, "raw_emails.yml") + for raw_email_id in YAML::load_file(raw_emails_yml).map{|k,v| v["id"]} do + raw_email = RawEmail.find(raw_email_id) + raw_email.data = load_file_fixture("raw_emails/%d.email" % [raw_email_id]) end - raw_email.data = load_file_fixture("useless_raw_email.email") end def parse_all_incoming_messages IncomingMessage.find(:all).each{|x| x.parse_raw_email!} end + +def load_test_categories + PublicBodyCategories.add(:en, [ + "Local and regional", + [ "local_council", "Local councils", "a local council" ], + "Miscellaneous", + [ "other", "Miscellaneous", "miscellaneous" ],]) +end diff --git a/spec/views/request/list.rhtml_spec.rb b/spec/views/request/list.rhtml_spec.rb index 1f86ec641..c7067294f 100644 --- a/spec/views/request/list.rhtml_spec.rb +++ b/spec/views/request/list.rhtml_spec.rb @@ -33,6 +33,7 @@ describe "when listing recent requests" do it "should be successful" do assigns[:list_results] = [ make_mock_event, make_mock_event ] assigns[:matches_estimated] = 2 + assigns[:show_no_more_than] = 100 render "request/list" response.should have_tag("div.request_listing") response.should_not have_tag("p", /No requests of this sort yet/m) @@ -41,6 +42,7 @@ describe "when listing recent requests" do it "should cope with no results" do assigns[:list_results] = [ ] assigns[:matches_estimated] = 0 + assigns[:show_no_more_than] = 0 render "request/list" response.should have_tag("p", /No requests of this sort yet/m) response.should_not have_tag("div.request_listing") diff --git a/spec/views/request/show.rhtml_spec.rb b/spec/views/request/show.rhtml_spec.rb index adb244f47..ef7d1a47c 100644 --- a/spec/views/request/show.rhtml_spec.rb +++ b/spec/views/request/show.rhtml_spec.rb @@ -84,10 +84,15 @@ describe 'when viewing an information request' do describe 'when there is a last response' do before do - ActionController::Routing::Routes.filters.clear @mock_response = mock_model(IncomingMessage) @mock_request.stub!(:get_last_response).and_return(@mock_response) + @old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new end + after do + ActionController::Routing::Routes.filters = @old_filters + end + it 'should show a link to follow up the last response with clarification' do request_page @@ -100,9 +105,14 @@ describe 'when viewing an information request' do describe 'when there is no last response' do before do - ActionController::Routing::Routes.filters.clear @mock_request.stub!(:get_last_response).and_return(nil) + @old_filters = ActionController::Routing::Routes.filters + ActionController::Routing::Routes.filters = RoutingFilter::Chain.new end + after do + ActionController::Routing::Routes.filters = @old_filters + end + it 'should show a link to follow up the request without reference to a specific response' do request_page |