aboutsummaryrefslogtreecommitdiffstats
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/api_controller_spec.rb2
-rw-r--r--spec/controllers/general_controller_spec.rb14
-rw-r--r--spec/controllers/help_controller_spec.rb27
-rw-r--r--spec/controllers/public_body_controller_spec.rb5
-rw-r--r--spec/controllers/reports_controller_spec.rb104
-rw-r--r--spec/controllers/request_controller_spec.rb454
-rw-r--r--spec/controllers/track_controller_spec.rb13
-rw-r--r--spec/controllers/user_controller_spec.rb11
-rw-r--r--spec/fixtures/files/blog_feed.atom39
-rw-r--r--spec/fixtures/files/incoming-request-two-same-name.email4
-rw-r--r--spec/fixtures/files/inline-uuencode.email27
-rw-r--r--spec/fixtures/files/malformed-to-and-cc.email11
-rw-r--r--spec/fixtures/files/mislabelled-as-iso-8859-1.email20
-rw-r--r--spec/fixtures/files/multipart-no-final-boundary.email21
-rw-r--r--spec/fixtures/files/nested-attachments-premature-end.email110
-rw-r--r--spec/fixtures/files/no-part-charset-random-data.email30
-rw-r--r--spec/fixtures/files/part-without-charset-in-content-type.email38
-rw-r--r--spec/fixtures/files/subject-bad-utf-8-trailing-base64.email5
-rw-r--r--spec/fixtures/files/subject-bad-utf-8-trailing-quoted-printable.email5
-rw-r--r--spec/fixtures/files/tnef-attachment-empty.email196
-rw-r--r--spec/fixtures/files/tnef-attachment-truncated.email34
-rw-r--r--spec/fixtures/files/unrecognized-encoding-mail.email36
-rw-r--r--spec/fixtures/locale/en/app.po145
-rw-r--r--spec/fixtures/locale/en_GB/app.po144
-rw-r--r--spec/fixtures/locale/es/app.po260
-rw-r--r--spec/fixtures/theme_views/theme_one/help/contact.es.html.erb1
-rw-r--r--spec/fixtures/theme_views/theme_one/help/contact.html.erb1
-rw-r--r--spec/integration/errors_spec.rb148
-rw-r--r--spec/integration/request_controller_spec.rb1
-rw-r--r--spec/integration/view_request_spec.rb7
-rw-r--r--spec/lib/basic_encoding_tests.rb157
-rw-r--r--spec/lib/i18n_interpolation.rb28
-rw-r--r--spec/lib/mail_handler/mail_handler_spec.rb108
-rw-r--r--spec/mailers/outgoing_mailer_spec.rb111
-rw-r--r--spec/mailers/track_mailer_spec.rb16
-rw-r--r--spec/models/foi_attachment_spec.rb2
-rw-r--r--spec/models/incoming_message_spec.rb79
-rw-r--r--spec/models/info_request_event_spec.rb8
-rw-r--r--spec/models/info_request_spec.rb43
-rw-r--r--spec/models/outgoing_message_spec.rb19
-rw-r--r--spec/models/user_spec.rb17
-rw-r--r--spec/models/xapian_spec.rb129
-rw-r--r--spec/spec.opts3
-rw-r--r--spec/spec_helper.rb12
-rw-r--r--spec/support/email_helpers.rb2
-rw-r--r--spec/support/load_file_fixtures.rb10
-rw-r--r--spec/views/reports/new.erb_spec.rb29
47 files changed, 2014 insertions, 672 deletions
diff --git a/spec/controllers/api_controller_spec.rb b/spec/controllers/api_controller_spec.rb
index 749be9f85..66b8e33f0 100644
--- a/spec/controllers/api_controller_spec.rb
+++ b/spec/controllers/api_controller_spec.rb
@@ -259,7 +259,7 @@ describe ApiController, "when using the API" do
attachments.size.should == 1
attachment = attachments[0]
attachment.filename.should == "tfl.pdf"
- attachment.body.should == load_file_fixture("tfl.pdf", as_binary=true)
+ attachment.body.should == load_file_fixture("tfl.pdf")
end
it "should show information about a request" do
diff --git a/spec/controllers/general_controller_spec.rb b/spec/controllers/general_controller_spec.rb
index 9a88dbc3a..0eda73c51 100644
--- a/spec/controllers/general_controller_spec.rb
+++ b/spec/controllers/general_controller_spec.rb
@@ -19,8 +19,13 @@ end
describe GeneralController, 'when getting the blog feed' do
- it 'should add a lang param correctly to a url with no querystring' do
+ before do
AlaveteliConfiguration.stub!(:blog_feed).and_return("http://blog.example.com")
+ # Don't call out to external url during tests
+ controller.stub!(:quietly_try_to_open).and_return('')
+ end
+
+ it 'should add a lang param correctly to a url with no querystring' do
get :blog
assigns[:feed_url].should == "http://blog.example.com?lang=en"
end
@@ -31,6 +36,12 @@ describe GeneralController, 'when getting the blog feed' do
assigns[:feed_url].should == "http://blog.example.com?alt=rss&lang=en"
end
+ it 'should parse an item from an example feed' do
+ controller.stub!(:quietly_try_to_open).and_return(load_file_fixture("blog_feed.atom"))
+ get :blog
+ assigns[:blog_items].count.should == 1
+ end
+
end
describe GeneralController, "when showing the frontpage" do
@@ -339,4 +350,3 @@ describe GeneralController, 'when using xapian search' do
end
end
-
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 0f6f75eb9..c47e1abd3 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -1,8 +1,9 @@
+# -*- coding: utf-8 -*-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe HelpController, "when using help" do
render_views
-
+
it "shows the about page" do
get :about
end
@@ -12,7 +13,7 @@ describe HelpController, "when using help" do
end
it "sends a contact message" do
- post :contact, { :contact => {
+ post :contact, { :contact => {
:name => "Vinny Vanilli",
:email => "vinny@localhost",
:subject => "Why do I have such an ace name?",
@@ -27,5 +28,27 @@ describe HelpController, "when using help" do
deliveries.clear
end
+ describe 'when requesting a page in a supported locale ' do
+
+ before do
+ # Allow us to supply the locale manually
+ RoutingFilter.active = false
+ # Prepend our fixture templates
+ fixture_theme_path = File.join(Rails.root, 'spec', 'fixtures', 'theme_views', 'theme_one')
+ controller.prepend_view_path fixture_theme_path
+ end
+
+ after do
+ RoutingFilter.active = true
+ end
+
+ it 'should render the locale-specific template if available' do
+ get :contact, {:locale => 'es'}
+ response.body.should match('contáctenos theme one')
+ end
+
+ end
+
+
end
diff --git a/spec/controllers/public_body_controller_spec.rb b/spec/controllers/public_body_controller_spec.rb
index 22d8418c9..e01bcb0a6 100644
--- a/spec/controllers/public_body_controller_spec.rb
+++ b/spec/controllers/public_body_controller_spec.rb
@@ -183,8 +183,11 @@ describe PublicBodyController, "when listing bodies" do
response.should render_template('list')
assigns[:public_bodies].should == [ public_bodies(:humpadink_public_body) ]
assigns[:tag].should == "eats_cheese:stilton"
+ end
-
+ it 'should return a "406 Not Acceptable" code if asked for a json version of a list' do
+ get :list, :format => 'json'
+ response.code.should == '406'
end
end
diff --git a/spec/controllers/reports_controller_spec.rb b/spec/controllers/reports_controller_spec.rb
new file mode 100644
index 000000000..fa8c72eaa
--- /dev/null
+++ b/spec/controllers/reports_controller_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe ReportsController, "when reporting a request when not logged in" do
+ it "should only allow logged-in users to report requests" do
+ post :create, :request_id => info_requests(:badger_request).url_title, :reason => "my reason"
+
+ flash[:notice].should =~ /You need to be logged in/
+ response.should redirect_to show_request_path(:url_title => info_requests(:badger_request).url_title)
+ end
+end
+
+describe ReportsController, "when reporting a request (logged in)" do
+ render_views
+
+ before do
+ @user = users(:robin_user)
+ session[:user_id] = @user.id
+ end
+
+ it "should 404 for non-existent requests" do
+ lambda {
+ post :create, :request_id => "hjksfdhjk_louytu_qqxxx"
+ }.should raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it "should mark a request as having been reported" do
+ ir = info_requests(:badger_request)
+ title = ir.url_title
+ ir.attention_requested.should == false
+
+ post :create, :request_id => title, :reason => "my reason"
+ response.should redirect_to show_request_path(:url_title => title)
+
+ ir.reload
+ ir.attention_requested.should == true
+ ir.described_state.should == "attention_requested"
+ end
+
+ it "should pass on the reason and message" do
+ info_request = mock_model(InfoRequest, :url_title => "foo", :attention_requested= => nil, :save! => nil)
+ InfoRequest.should_receive(:find_by_url_title!).with("foo").and_return(info_request)
+ info_request.should_receive(:report!).with("Not valid request", "It's just not", @user)
+ post :create, :request_id => "foo", :reason => "Not valid request", :message => "It's just not"
+ end
+
+ it "should not allow a request to be reported twice" do
+ title = info_requests(:badger_request).url_title
+
+ post :create, :request_id => title, :reason => "my reason"
+ response.should redirect_to show_request_url(:url_title => title)
+
+ post :create, :request_id => title, :reason => "my reason"
+ response.should redirect_to show_request_url(:url_title => title)
+ flash[:notice].should =~ /has already been reported/
+ end
+
+ it "should send an email from the reporter to admins" do
+ ir = info_requests(:badger_request)
+ title = ir.url_title
+ post :create, :request_id => title, :reason => "my reason"
+ deliveries = ActionMailer::Base.deliveries
+ deliveries.size.should == 1
+ mail = deliveries[0]
+ mail.subject.should =~ /attention_requested/
+ mail.from.should include(@user.email)
+ mail.body.should include(@user.name)
+ end
+
+ it "should force the user to pick a reason" do
+ info_request = mock_model(InfoRequest, :report! => nil, :url_title => "foo",
+ :report_reasons => ["Not FOIish enough"])
+ InfoRequest.should_receive(:find_by_url_title!).with("foo").and_return(info_request)
+
+ post :create, :request_id => "foo", :reason => ""
+ response.should render_template("new")
+ flash[:error].should == "Please choose a reason"
+ end
+end
+
+describe ReportsController, "#new_report_request" do
+ let(:info_request) { mock_model(InfoRequest, :url_title => "foo") }
+ before :each do
+ InfoRequest.should_receive(:find_by_url_title!).with("foo").and_return(info_request)
+ end
+
+ context "not logged in" do
+ it "should require the user to be logged in" do
+ get :new, :request_id => "foo"
+ response.should_not render_template("new")
+ end
+ end
+
+ context "logged in" do
+ before :each do
+ session[:user_id] = users(:bob_smith_user).id
+ end
+ it "should show the form" do
+ get :new, :request_id => "foo"
+ response.should render_template("new")
+ end
+ end
+end
+
+
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 4e889e2a6..387b040d6 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -93,8 +93,10 @@ describe RequestController, "when listing recent requests" do
:results => (1..25).to_a.map { |m| { :model => m } },
:matches_estimated => 1000000)
- InfoRequest.should_receive(:full_search).
- with([InfoRequestEvent]," (variety:sent OR variety:followup_sent OR variety:response OR variety:comment)", "created_at", anything, anything, anything, anything).
+ ActsAsXapian::Search.should_receive(:new).
+ with([InfoRequestEvent]," (variety:sent OR variety:followup_sent OR variety:response OR variety:comment)",
+ :sort_by_prefix => "created_at", :offset => 0, :limit => 25, :sort_by_ascending => true,
+ :collapse_by_prefix => "request_collapse").
and_return(xap_results)
get :list, :view => 'all'
assigns[:list_results].size.should == 25
@@ -134,7 +136,7 @@ describe RequestController, "when changing things that appear on the request pag
it "should purge the downstream cache when a followup is made" do
session[:user_id] = users(:bob_smith_user).id
ir = info_requests(:fancy_dog_request)
- post :show_response, :outgoing_message => { :body => "What a useless response! You suck.", :what_doing => 'normal_sort' }, :id => ir.id, :incoming_message_id => incoming_messages(:useless_incoming_message), :submitted_followup => 1
+ post :show_response, :outgoing_message => { :body => "What a useless response! You suck.", :what_doing => 'normal_sort' }, :id => ir.id, :submitted_followup => 1
PurgeRequest.all().first.model_id.should == ir.id
end
it "should purge the downstream cache when the request is categorised" do
@@ -239,6 +241,36 @@ describe RequestController, "when showing one request" do
end
end
+ context "when the request has not yet been reported" do
+ it "should allow the user to report" do
+ title = info_requests(:badger_request).url_title
+ get :show, :url_title => title
+ response.should_not contain("This request has been reported")
+ response.should contain("Offensive?")
+ end
+ end
+
+ context "when the request has been reported for admin attention" do
+ before :each do
+ info_requests(:fancy_dog_request).report!("", "", nil)
+ end
+ it "should inform the user" do
+ get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
+ response.should contain("This request has been reported")
+ response.should_not contain("Offensive?")
+ end
+
+ context "and then deemed okay and left to complete" do
+ before :each do
+ info_requests(:fancy_dog_request).set_described_state("successful")
+ end
+ it "should let the user know that the administrators have not hidden this request" do
+ get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
+ response.body.should =~ (/the site administrators.*have not hidden it/)
+ end
+ end
+ end
+
describe 'when the request is being viewed by an admin' do
describe 'if the request is awaiting description' do
@@ -477,11 +509,11 @@ describe RequestController, "when showing one request" do
(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', :skip_cache => 1
+ get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt', :skip_cache => 1
response.content_type.should == "text/plain"
response.should contain "Second hello"
- get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 3, :file_name => 'hello.txt', :skip_cache => 1
+ get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 3, :file_name => 'hello world.txt', :skip_cache => 1
response.content_type.should == "text/plain"
response.should contain "First hello"
end
@@ -494,7 +526,7 @@ describe RequestController, "when showing one request" do
get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id,
:id => ir.id,
:part => 2,
- :file_name => 'hello.txt'
+ :file_name => 'hello world.txt'
end
it "should convert message body to UTF8" do
@@ -508,7 +540,7 @@ describe RequestController, "when showing one request" do
ir = info_requests(:fancy_dog_request)
receive_incoming_mail('incoming-request-two-same-name.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 => 'hello.txt.html', :skip_cache => 1
+ get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt.html', :skip_cache => 1
response.content_type.should == "text/html"
response.should contain "Second hello"
end
@@ -529,11 +561,11 @@ describe RequestController, "when showing one request" do
ir.reload
ugly_id = "55195"
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
+ get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ugly_id, :part => 2, :file_name => 'hello world.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
+ get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ugly_id, :part => 2, :file_name => 'hello world.txt', :skip_cache => 1
}.should raise_error(ActiveRecord::RecordNotFound)
end
it "should return 404 when incoming message and request ids don't match" do
@@ -542,7 +574,7 @@ describe RequestController, "when showing one request" do
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
+ get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => wrong_id, :part => 2, :file_name => 'hello world.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
@@ -552,11 +584,11 @@ describe RequestController, "when showing one request" do
ugly_id = "%d95" % [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
+ get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ugly_id, :part => 2, :file_name => 'hello world.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
+ get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ugly_id, :part => 2, :file_name => 'hello world.txt', :skip_cache => 1
}.should raise_error(ActiveRecord::RecordNotFound)
end
it "should return 404 when incoming message and request ids don't match" do
@@ -565,7 +597,7 @@ describe RequestController, "when showing one request" do
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
+ get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => wrong_id, :part => 2, :file_name => 'hello world.txt.html', :skip_cache => 1
}.should raise_error(ActiveRecord::RecordNotFound)
end
@@ -573,44 +605,66 @@ describe RequestController, "when showing one request" 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
+ 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 contain "Walberswick Parish Council"
end
- it "should not cause a reparsing of the raw email, even when the result would be a 404" do
+ it "should not cause a reparsing of the raw email, even when the attachment can't be found" do
ir = info_requests(:fancy_dog_request)
receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
ir.reload
- attachment = IncomingMessage.get_attachment_by_url_part_number(ir.incoming_messages[1].get_attachments_for_display, 2)
+ attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(ir.incoming_messages[1].get_attachments_for_display, 2, 'hello world.txt')
attachment.body.should contain "Second hello"
# change the raw_email associated with the message; this only be reparsed when explicitly asked for
ir.incoming_messages[1].raw_email.data = ir.incoming_messages[1].raw_email.data.sub("Second", "Third")
- # asking for an attachment by the wrong filename results
- # in a 404 for browsing users. This shouldn't cause a
- # re-parse...
- lambda {
- get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello.txt.baz.html', :skip_cache => 1
- }.should raise_error(ActiveRecord::RecordNotFound)
+ # asking for an attachment by the wrong filename should result in redirecting
+ # back to the incoming message, but shouldn't cause a reparse:
+ get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt.baz.html', :skip_cache => 1
+ response.status.should == 303
- attachment = IncomingMessage.get_attachment_by_url_part_number(ir.incoming_messages[1].get_attachments_for_display, 2)
+ attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(ir.incoming_messages[1].get_attachments_for_display, 2, 'hello world.txt')
attachment.body.should contain "Second hello"
# ...nor should asking for it by its correct filename...
- get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello.txt.html', :skip_cache => 1
+ get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt.html', :skip_cache => 1
response.should_not contain "Third hello"
# ...but if we explicitly ask for attachments to be extracted, then they should be
force = true
ir.incoming_messages[1].parse_raw_email!(force)
ir.reload
- attachment = IncomingMessage.get_attachment_by_url_part_number(ir.incoming_messages[1].get_attachments_for_display, 2)
+ attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(ir.incoming_messages[1].get_attachments_for_display, 2, 'hello world.txt')
attachment.body.should contain "Third hello"
- get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello.txt.html', :skip_cache => 1
+ get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt.html', :skip_cache => 1
response.should contain "Third hello"
end
+ it "should redirect to the incoming message if there's a wrong part number and an ambiguous filename" do
+ ir = info_requests(:fancy_dog_request)
+ receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
+ ir.reload
+
+ im = ir.incoming_messages[1]
+
+ attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(im.get_attachments_for_display, 5, 'hello world.txt')
+ attachment.should be_nil
+
+ get :get_attachment_as_html, :incoming_message_id => im.id, :id => ir.id, :part => 5, :file_name => 'hello world.txt', :skip_cache => 1
+ response.status.should == 303
+ new_location = response.header['Location']
+ new_location.should match(/request\/#{ir.url_title}#incoming-#{im.id}/)
+ end
+
+ it "should find a uniquely named filename even if the URL part number was wrong" do
+ ir = info_requests(:fancy_dog_request)
+ receive_incoming_mail('incoming-request-pdf-attachment.email', ir.incoming_email)
+ ir.reload
+ get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 5, :file_name => 'fs 50379341.pdf', :skip_cache => 1
+ response.content_type.should == "application/pdf"
+ end
+
it "should treat attachments with unknown extensions as binary" do
ir = info_requests(:fancy_dog_request)
receive_incoming_mail('incoming-request-attachment-unknown-extension.email', ir.incoming_email)
@@ -625,10 +679,8 @@ describe RequestController, "when showing one request" do
ir = info_requests(:fancy_dog_request)
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,
- :file_name => 'http://trying.to.hack'
- }.should raise_error(ActiveRecord::RecordNotFound)
+ get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'http://trying.to.hack'
+ response.status.should == 303
end
it "should censor attachments downloaded as binary" do
@@ -644,7 +696,7 @@ describe RequestController, "when showing one request" do
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
+ get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt', :skip_cache => 1
response.content_type.should == "text/plain"
response.should contain "xxxxxx hello"
ensure
@@ -666,7 +718,7 @@ describe RequestController, "when showing one request" do
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
+ get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt', :skip_cache => 1
response.content_type.should == "text/plain"
response.should contain "xxxxxx hello"
ensure
@@ -695,11 +747,13 @@ describe RequestController, "when showing one request" do
# so at this point, assigns[:info_request].incoming_messages[1].get_attachments_for_display is returning stuff, but the equivalent thing in the template isn't.
# but something odd is that the above is return a whole load of attachments which aren't there in the controller
response.body.should have_selector("p.attachment strong") do |s|
- s.should contain /hello.txt/m
+ s.should contain /hello world.txt/m
end
censor_rule = CensorRule.new()
- censor_rule.text = "hello.txt"
+ # Note that the censor rule applies to the original filename,
+ # not the display_filename:
+ censor_rule.text = "hello-world.txt"
censor_rule.replacement = "goodbye.txt"
censor_rule.last_edit_editor = "unknown"
censor_rule.last_edit_comment = "none"
@@ -743,7 +797,7 @@ describe RequestController, "when showing one request" do
old_path = assigns[:url_path]
response.location.should contain /#{assigns[:url_path]}$/
zipfile = Zip::ZipFile.open(File.join(File.dirname(__FILE__), "../../cache/zips", old_path)) { |zipfile|
- zipfile.count.should == 3 # the message plus two "hello.txt" files
+ zipfile.count.should == 3 # the message plus two "hello-world.txt" files
}
# The path of the zip file is based on the hash of the timestamp of the last request
@@ -756,7 +810,7 @@ describe RequestController, "when showing one request" do
assigns[:url_path].should_not == old_path
response.location.should contain assigns[:url_path]
zipfile = Zip::ZipFile.open(File.join(File.dirname(__FILE__), "../../cache/zips", assigns[:url_path])) { |zipfile|
- zipfile.count.should == 4 # the message, two hello.txt plus the unknown attachment
+ zipfile.count.should == 4 # the message, two hello-world.txt plus the unknown attachment
}
end
@@ -794,6 +848,16 @@ describe RequestController, "when changing prominence of a request" do
response.should render_template('hidden')
end
+ it 'should not show hidden requests if requested using json' do
+ ir = info_requests(:fancy_dog_request)
+ ir.prominence = 'hidden'
+ ir.save!
+
+ session[:user_id] = ir.user.id # bob_smith_user
+ get :show, :url_title => 'why_do_you_have_such_a_fancy_dog', :format => 'json'
+ response.code.should == '410'
+ end
+
it "should show hidden requests if logged in as super user" do
ir = info_requests(:fancy_dog_request)
ir.prominence = 'hidden'
@@ -875,7 +939,7 @@ describe RequestController, "when changing prominence of a request" do
get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id,
:id => ir.id,
:part => 2,
- :file_name => 'hello.txt'
+ :file_name => 'hello world.txt'
end.should raise_error(ActiveRecord::RecordNotFound)
end
@@ -890,7 +954,7 @@ describe RequestController, "when changing prominence of a request" do
get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id,
:id => ir.id,
:part => 2,
- :file_name => 'hello.txt'
+ :file_name => 'hello world.txt'
end.should raise_error(ActiveRecord::RecordNotFound)
end
@@ -907,6 +971,7 @@ describe RequestController, "when searching for an authority" do
# so we make sure we're logged in, just in case
before do
@user = users(:bob_smith_user)
+ get_fixtures_xapian_index
end
it "should return nothing for the empty query string" do
@@ -918,7 +983,6 @@ describe RequestController, "when searching for an authority" do
end
it "should return matching bodies" do
- get_fixtures_xapian_index
session[:user_id] = @user.id
get :select_authority, :query => "Quango"
@@ -1214,14 +1278,29 @@ describe RequestController, "when viewing an individual response for reply/follo
response.body.should have_selector("div#other_recipients ul li", :content => "Frob")
end
- it "should not show individual responses if request hidden, even if request owner" do
- ir = info_requests(:fancy_dog_request)
- ir.prominence = 'hidden'
- ir.save!
+ context 'when a request is hidden' do
+
+ before do
+ ir = info_requests(:fancy_dog_request)
+ ir.prominence = 'hidden'
+ ir.save!
+
+ session[:user_id] = users(:bob_smith_user).id
+ end
+
+ it "should not show individual responses, even if request owner" do
+ get :show_response, :id => info_requests(:fancy_dog_request).id, :incoming_message_id => incoming_messages(:useless_incoming_message)
+ response.should render_template('request/hidden')
+ end
+
+ it 'should respond to a json request for a hidden request with a 410 code and no body' do
+ get :show_response, :id => info_requests(:fancy_dog_request).id,
+ :incoming_message_id => incoming_messages(:useless_incoming_message),
+ :format => 'json'
+
+ response.code.should == '410'
+ end
- session[:user_id] = users(:bob_smith_user).id
- get :show_response, :id => info_requests(:fancy_dog_request).id, :incoming_message_id => incoming_messages(:useless_incoming_message)
- response.should render_template('request/hidden')
end
describe 'when viewing a response for an external request' do
@@ -1559,7 +1638,7 @@ describe RequestController, "when classifying an information request" do
end
end
- describe 'when redirecting after a successful status update by the request owner' do
+ describe 'after a successful status update by the request owner' do
before do
@request_owner = users(:bob_smith_user)
@@ -1586,87 +1665,161 @@ describe RequestController, "when classifying an information request" do
response.should redirect_to("http://test.host/#{redirect_path}")
end
- it 'should redirect to the "request url" with a message in the right tense when status is updated to "waiting response" and the response is not overdue' do
- @dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date+1)
- @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date+40)
+ context 'when status is updated to "waiting_response"' do
- expect_redirect("waiting_response", "request/#{@dog_request.url_title}")
- flash[:notice].should match(/should get a response/)
- end
+ it 'should redirect to the "request url" with a message in the right tense when
+ the response is not overdue' do
+ @dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date+1)
+ @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date+40)
- it 'should redirect to the "request url" with a message in the right tense when status is updated to "waiting response" and the response is overdue' do
- @dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date-1)
- @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date+40)
- expect_redirect('waiting_response', request_url)
- flash[:notice].should match(/should have got a response/)
- end
+ expect_redirect("waiting_response", "request/#{@dog_request.url_title}")
+ flash[:notice].should match(/should get a response/)
+ end
- it 'should redirect to the "request url" with a message in the right tense when status is updated to "waiting response" and the response is overdue' do
- @dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date-2)
- @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date-1)
- expect_redirect('waiting_response', unhappy_url)
- flash[:notice].should match(/is long overdue/)
- flash[:notice].should match(/by more than 40 working days/)
- flash[:notice].should match(/within 20 working days/)
- end
+ it 'should redirect to the "request url" with a message in the right tense when
+ the response is overdue' do
+ @dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date-1)
+ @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date+40)
+ expect_redirect('waiting_response', request_url)
+ flash[:notice].should match(/should have got a response/)
+ end
- it 'should redirect to the "request url" when status is updated to "not held"' do
- expect_redirect('not_held', request_url)
+ it 'should redirect to the "request url" with a message in the right tense when
+ the response is overdue' do
+ @dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date-2)
+ @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date-1)
+ expect_redirect('waiting_response', unhappy_url)
+ flash[:notice].should match(/is long overdue/)
+ flash[:notice].should match(/by more than 40 working days/)
+ flash[:notice].should match(/within 20 working days/)
+ end
end
- it 'should redirect to the "request url" when status is updated to "successful"' do
- expect_redirect('successful', request_url)
- end
+ context 'when status is updated to "not held"' do
+
+ it 'should redirect to the "request url"' do
+ expect_redirect('not_held', request_url)
+ end
- it 'should redirect to the "unhappy url" when status is updated to "rejected"' do
- expect_redirect('rejected', "help/unhappy/#{@dog_request.url_title}")
end
- it 'should redirect to the "unhappy url" when status is updated to "partially successful"' do
- expect_redirect('partially_successful', "help/unhappy/#{@dog_request.url_title}")
+ context 'when status is updated to "successful"' do
+
+ it 'should redirect to the "request url"' do
+ expect_redirect('successful', request_url)
+ end
+
+ it 'should show a message including the donation url if there is one' do
+ AlaveteliConfiguration.stub!(:donation_url).and_return('http://donations.example.com')
+ post_status('successful')
+ flash[:notice].should match('make a donation')
+ flash[:notice].should match('http://donations.example.com')
+ end
+
+ it 'should show a message without reference to donations if there is no
+ donation url' do
+ AlaveteliConfiguration.stub!(:donation_url).and_return('')
+ post_status('successful')
+ flash[:notice].should_not match('make a donation')
+ end
+
end
- it 'should redirect to the "response url" when status is updated to "waiting clarification" and there is a last response' do
- incoming_message = mock_model(IncomingMessage)
- @dog_request.stub!(:get_last_response).and_return(incoming_message)
- expect_redirect('waiting_clarification', "request/#{@dog_request.id}/response/#{incoming_message.id}")
+ context 'when status is updated to "waiting clarification"' do
+
+ it 'should redirect to the "response url" when there is a last response' do
+ incoming_message = mock_model(IncomingMessage)
+ @dog_request.stub!(:get_last_response).and_return(incoming_message)
+ expect_redirect('waiting_clarification', "request/#{@dog_request.id}/response/#{incoming_message.id}")
+ end
+
+ it 'should redirect to the "response no followup url" when there are no events
+ needing description' do
+ @dog_request.stub!(:get_last_response).and_return(nil)
+ expect_redirect('waiting_clarification', "request/#{@dog_request.id}/response")
+ end
+
end
- it 'should redirect to the "response no followup url" when status is updated to "waiting clarification" and there are no events needing description' do
- @dog_request.stub!(:get_last_response).and_return(nil)
- expect_redirect('waiting_clarification', "request/#{@dog_request.id}/response")
+ context 'when status is updated to "rejected"' do
+
+ it 'should redirect to the "unhappy url"' do
+ expect_redirect('rejected', "help/unhappy/#{@dog_request.url_title}")
+ end
+
end
- it 'should redirect to the "respond to last url" when status is updated to "gone postal"' do
- expect_redirect('gone_postal', "request/#{@dog_request.id}/response/#{@dog_request.get_last_response.id}?gone_postal=1")
+ context 'when status is updated to "partially successful"' do
+
+ it 'should redirect to the "unhappy url"' do
+ expect_redirect('partially_successful', "help/unhappy/#{@dog_request.url_title}")
+ end
+
+ it 'should show a message including the donation url if there is one' do
+ AlaveteliConfiguration.stub!(:donation_url).and_return('http://donations.example.com')
+ post_status('successful')
+ flash[:notice].should match('make a donation')
+ flash[:notice].should match('http://donations.example.com')
+ end
+
+ it 'should show a message without reference to donations if there is no
+ donation url' do
+ AlaveteliConfiguration.stub!(:donation_url).and_return('')
+ post_status('successful')
+ flash[:notice].should_not match('make a donation')
+ end
+
end
- it 'should redirect to the "request url" when status is updated to "internal review"' do
- expect_redirect('internal_review', request_url)
+ context 'when status is updated to "gone postal"' do
+
+ it 'should redirect to the "respond to last url"' do
+ expect_redirect('gone_postal', "request/#{@dog_request.id}/response/#{@dog_request.get_last_response.id}?gone_postal=1")
+ end
+
end
- it 'should redirect to the "request url" when status is updated to "requires admin"' do
- post :describe_state, :incoming_message => {
- :described_state => 'requires_admin',
- :message => "A message" },
- :id => @dog_request.id,
- :last_info_request_event_id => @dog_request.last_event_id_needing_description
- response.should redirect_to show_request_url(:url_title => @dog_request.url_title)
+ context 'when status updated to "internal review"' do
+
+ it 'should redirect to the "request url"' do
+ expect_redirect('internal_review', request_url)
+ end
+
end
- it 'should redirect to the "request url" when status is updated to "error message"' do
- post :describe_state, :incoming_message => {
- :described_state => 'error_message',
- :message => "A message" },
- :id => @dog_request.id,
- :last_info_request_event_id => @dog_request.last_event_id_needing_description
- response.should redirect_to show_request_url(:url_title => @dog_request.url_title)
+ context 'when status is updated to "requires admin"' do
+
+ it 'should redirect to the "request url"' do
+ post :describe_state, :incoming_message => {
+ :described_state => 'requires_admin',
+ :message => "A message" },
+ :id => @dog_request.id,
+ :last_info_request_event_id => @dog_request.last_event_id_needing_description
+ response.should redirect_to show_request_url(:url_title => @dog_request.url_title)
+ end
+
end
- it 'should redirect to the "respond to last url url" when status is updated to "user_withdrawn"' do
- expect_redirect('user_withdrawn', "request/#{@dog_request.id}/response/#{@dog_request.get_last_response.id}")
+ context 'when status is updated to "error message"' do
+
+ it 'should redirect to the "request url"' do
+ post :describe_state, :incoming_message => {
+ :described_state => 'error_message',
+ :message => "A message" },
+ :id => @dog_request.id,
+ :last_info_request_event_id => @dog_request.last_event_id_needing_description
+ response.should redirect_to show_request_url(:url_title => @dog_request.url_title)
+ end
+
end
+ context 'when status is updated to "user_withdrawn"' do
+
+ it 'should redirect to the "respond to last url url" ' do
+ expect_redirect('user_withdrawn', "request/#{@dog_request.id}/response/#{@dog_request.get_last_response.id}")
+ end
+
+ end
end
end
@@ -2262,6 +2415,11 @@ end
describe RequestController, "when showing similar requests" do
render_views
+ before do
+ get_fixtures_xapian_index
+ load_raw_emails_data
+ end
+
it "should work" do
get :similar, :url_title => info_requests(:badger_request).url_title
response.should render_template("request/similar")
@@ -2269,8 +2427,6 @@ describe RequestController, "when showing similar requests" do
end
it "should show similar requests" do
- get_fixtures_xapian_index
-
badger_request = info_requests(:badger_request)
get :similar, :url_title => badger_request.url_title
@@ -2294,91 +2450,6 @@ describe RequestController, "when showing similar requests" do
end
-
-describe RequestController, "when reporting a request when not logged in" do
- it "should only allow logged-in users to report requests" do
- get :report_request, :url_title => info_requests(:badger_request).url_title
- post_redirect = PostRedirect.get_last_post_redirect
- response.should redirect_to(:controller => 'user', :action => 'signin', :token => post_redirect.token)
- end
-end
-
-describe RequestController, "when reporting a request (logged in)" do
- render_views
-
- before do
- @user = users(:robin_user)
- session[:user_id] = @user.id
- end
-
- it "should 404 for non-existent requests" do
- lambda {
- post :report_request, :url_title => "hjksfdhjk_louytu_qqxxx"
- }.should raise_error(ActiveRecord::RecordNotFound)
- end
-
- it "should mark a request as having been reported" do
- ir = info_requests(:badger_request)
- title = ir.url_title
- get :show, :url_title => title
- assigns[:info_request].attention_requested.should == false
-
- post :report_request, :url_title => title
- response.should redirect_to(:action => :show, :url_title => title)
-
- get :show, :url_title => title
- response.should be_success
- assigns[:info_request].attention_requested.should == true
- assigns[:info_request].described_state.should == "attention_requested"
- end
-
- it "should not allow a request to be reported twice" do
- title = info_requests(:badger_request).url_title
-
- post :report_request, :url_title => title
- response.should redirect_to(:action => :show, :url_title => title)
- get :show, :url_title => title
- response.should be_success
- response.body.should include("has been reported")
-
- post :report_request, :url_title => title
- response.should redirect_to(:action => :show, :url_title => title)
- get :show, :url_title => title
- response.should be_success
- response.body.should include("has already been reported")
- end
-
- it "should let users know a request has been reported" do
- title = info_requests(:badger_request).url_title
- get :show, :url_title => title
- response.body.should include("Offensive?")
-
- post :report_request, :url_title => title
- response.should redirect_to(:action => :show, :url_title => title)
-
- get :show, :url_title => title
- response.body.should_not include("Offensive?")
- response.body.should include("This request has been reported")
-
- info_requests(:badger_request).set_described_state("successful")
- get :show, :url_title => title
- response.body.should_not include("This request has been reported")
- response.body.should =~ (/the site administrators.*have not hidden it/)
- end
-
- it "should send an email from the reporter to admins" do
- ir = info_requests(:badger_request)
- title = ir.url_title
- post :report_request, :url_title => title
- deliveries = ActionMailer::Base.deliveries
- deliveries.size.should == 1
- mail = deliveries[0]
- mail.subject.should =~ /attention_requested/
- mail.from.should include(@user.email)
- mail.body.should include(@user.name)
- end
-end
-
describe RequestController, "when caching fragments" do
it "should not fail with long filenames" do
long_name = "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah.txt"
@@ -2393,7 +2464,7 @@ describe RequestController, "when caching fragments" do
attachment = mock(FoiAttachment, :display_filename => long_name,
:body_as_html => ['some text', 'wrapper'])
IncomingMessage.stub!(:find).with("44").and_return(incoming_message)
- IncomingMessage.stub!(:get_attachment_by_url_part_number).and_return(attachment)
+ IncomingMessage.stub!(:get_attachment_by_url_part_number_and_filename).and_return(attachment)
InfoRequest.stub!(:find).with("132").and_return(info_request)
params = { :file_name => long_name,
:controller => "request",
@@ -2406,4 +2477,3 @@ describe RequestController, "when caching fragments" do
end
-
diff --git a/spec/controllers/track_controller_spec.rb b/spec/controllers/track_controller_spec.rb
index e9501b1ed..1575bc84e 100644
--- a/spec/controllers/track_controller_spec.rb
+++ b/spec/controllers/track_controller_spec.rb
@@ -144,6 +144,7 @@ describe TrackController, "when viewing RSS feed for a track" do
get :track_request, :feed => 'feed', :url_title => track_thing.info_request.url_title
response.should render_template('track/atom_feed')
+ response.content_type.should == 'application/atom+xml'
# XXX should check it is an atom.builder type being rendered, not sure how to
assigns[:xapian_object].matches_estimated.should == 3
@@ -158,6 +159,18 @@ describe TrackController, "when viewing RSS feed for a track" do
get :track_user, :feed => 'feed', :url_name => "there_is_no_such_user"
}.should raise_error(ActiveRecord::RecordNotFound)
end
+
+ it 'should return atom/xml for a feed url without format specified, even if the
+ requester prefers json' do
+
+ request.env['HTTP_ACCEPT'] = 'application/json,text/xml'
+ track_thing = track_things(:track_fancy_dog_request)
+
+ get :track_request, :feed => 'feed', :url_title => track_thing.info_request.url_title
+ response.should render_template('track/atom_feed')
+ response.content_type.should == 'application/atom+xml'
+ end
+
end
describe TrackController, "when viewing JSON version of a track feed" do
diff --git a/spec/controllers/user_controller_spec.rb b/spec/controllers/user_controller_spec.rb
index a18554114..b09594b9c 100644
--- a/spec/controllers/user_controller_spec.rb
+++ b/spec/controllers/user_controller_spec.rb
@@ -66,6 +66,11 @@ end
describe UserController, "when signing in" do
render_views
+ before do
+ # Don't call out to external url during tests
+ controller.stub!(:country_from_ip).and_return('gb')
+ end
+
def get_last_postredirect
post_redirects = PostRedirect.find_by_sql("select * from post_redirects order by id desc limit 1")
post_redirects.size.should == 1
@@ -226,6 +231,11 @@ end
describe UserController, "when signing up" do
render_views
+ before do
+ # Don't call out to external url during tests
+ controller.stub!(:country_from_ip).and_return('gb')
+ end
+
it "should be an error if you type the password differently each time" do
post :signup, { :user_signup => { :email => 'new@localhost', :name => 'New Person',
:password => 'sillypassword', :password_confirmation => 'sillypasswordtwo' }
@@ -626,6 +636,7 @@ describe UserController, "when viewing the wall" do
render_views
before(:each) do
+ load_raw_emails_data
get_fixtures_xapian_index
end
diff --git a/spec/fixtures/files/blog_feed.atom b/spec/fixtures/files/blog_feed.atom
new file mode 100644
index 000000000..f49693938
--- /dev/null
+++ b/spec/fixtures/files/blog_feed.atom
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0"
+ xmlns:content="http://purl.org/rss/1.0/modules/content/"
+ xmlns:wfw="http://wellformedweb.org/CommentAPI/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:atom="http://www.w3.org/2005/Atom"
+ xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
+ xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
+ >
+
+<channel>
+ <title>A Blog Feed</title>
+ <atom:link href="http://example.com/feed/" rel="self" type="application/rss+xml" />
+ <link>http://www.example.com</link>
+ <description>Stuff</description>
+ <lastBuildDate>Tue, 30 Apr 2013 14:34:15 +0000</lastBuildDate>
+ <language>en</language>
+ <sy:updatePeriod>hourly</sy:updatePeriod>
+ <sy:updateFrequency>1</sy:updateFrequency>
+ <generator>http://wordpress.org/?v=3.3.2</generator>
+ <item>
+ <title>Example Post</title>
+ <link>http://www.example.com/example-post</link>
+ <comments>http://www.example.com/example-post#comments</comments>
+ <pubDate>Mon, 01 Apr 2013 19:26:08 +0000</pubDate>
+ <dc:creator>Example Blogger</dc:creator>
+ <category><![CDATA[FOI]]></category>
+
+ <guid isPermaLink="false">http://www.example.com/?id=333</guid>
+ <description><![CDATA[An example post [...]]]></description>
+ <content:encoded><![CDATA[<h3>A blog post</h3>
+<p>Example post</p>
+]]></content:encoded>
+ <wfw:commentRss>http://www.example.com/feed/</wfw:commentRss>
+ <slash:comments>2</slash:comments>
+ </item>
+
+ </channel>
+</rss>
diff --git a/spec/fixtures/files/incoming-request-two-same-name.email b/spec/fixtures/files/incoming-request-two-same-name.email
index f1024d607..ecd322fe4 100644
--- a/spec/fixtures/files/incoming-request-two-same-name.email
+++ b/spec/fixtures/files/incoming-request-two-same-name.email
@@ -13,13 +13,13 @@ Content-Disposition: inline
--Q68bSM7Ycu6FN28Q
Content-Type: text/plain; charset=us-ascii
-Content-Disposition: attachment; filename="hello.txt"
+Content-Disposition: attachment; filename="hello-world.txt"
Second hello
--Q68bSM7Ycu6FN28Q
Content-Type: text/plain; charset=us-ascii
-Content-Disposition: attachment; filename="hello.txt"
+Content-Disposition: attachment; filename="hello-world.txt"
First hello
diff --git a/spec/fixtures/files/inline-uuencode.email b/spec/fixtures/files/inline-uuencode.email
new file mode 100644
index 000000000..3134ba3ad
--- /dev/null
+++ b/spec/fixtures/files/inline-uuencode.email
@@ -0,0 +1,27 @@
+From foo@bar Mon Jun 01 17:14:44 2009
+Return-path: <foo@bar>
+Envelope-to: foi@quux
+Delivery-date: Mon, 01 Jun 2009 17:14:44 +0100
+From: <foo@bar>
+To: <request-whatever@quux>
+Subject: something or other
+Date: Mon, 1 Jun 2009 17:14:37 +0100
+X-MimeOLE: Produced By Microsoft MimeOLE V6.00.3790.181
+Message-ID: <baz@xyzzy>
+
+Thanks for your email - here's a truncated attachment
+for you:
+
+**********************************************************************
+
+begin 666 ResponseT7363 9.doc
+MT,\1X*&Q&N$`````````````````````/@`#`/[_"0`&```````````````"
+M````) ``````````$ ``+@````$```#^____`````",```!L````________
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+#````
+`
+end
+
+The original of this email was scanned for viruses or something
+like that.
diff --git a/spec/fixtures/files/malformed-to-and-cc.email b/spec/fixtures/files/malformed-to-and-cc.email
new file mode 100644
index 000000000..4fbb6e21e
--- /dev/null
+++ b/spec/fixtures/files/malformed-to-and-cc.email
@@ -0,0 +1,11 @@
+From foo@bar Wed Mar 12 14:58:26 2008
+Return-path: <foo@bar>
+Subject: example email
+To: <bar@example.org
+Cc: baz@example.org>
+From: quux@example.org
+Date: Mon, 7 May 2012 12:47:06 +0100
+Mime-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+
+A very basic email, but with malformed To: and Cc: lines
diff --git a/spec/fixtures/files/mislabelled-as-iso-8859-1.email b/spec/fixtures/files/mislabelled-as-iso-8859-1.email
new file mode 100644
index 000000000..6c8e6109e
--- /dev/null
+++ b/spec/fixtures/files/mislabelled-as-iso-8859-1.email
@@ -0,0 +1,20 @@
+From foo@bar Thu Mar 01 15:02:33 2012
+Return-path: <foo@bar>
+Envelope-to: foi@quux
+Delivery-date: Thu, 01 Mar 2012 15:02:33 +0000
+Date: Thu, 01 Mar 2012 15:01:58 +0000
+Subject: some FOI request
+To: foi@quux
+From: foo@bar
+MIME-Version: 1.0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Transfer-Encoding: 7bit
+Message-Id: <2468@bar.local>
+
+Dear Whoever,
+
+THERE'S A DASH NEXT REQUEST FOR INFORMATION
+
+Best regards,
+Other Person
+
diff --git a/spec/fixtures/files/multipart-no-final-boundary.email b/spec/fixtures/files/multipart-no-final-boundary.email
new file mode 100644
index 000000000..9c16dad52
--- /dev/null
+++ b/spec/fixtures/files/multipart-no-final-boundary.email
@@ -0,0 +1,21 @@
+From foo@bar Thu Sep 13 10:34:44 2012
+Return-path: <foo@bar>
+Envelope-to: foi@example.org
+Delivery-date: Thu, 13 Sep 2012 10:34:44 +0100
+From: foo@bar
+To: foi@example.org
+Subject: an acknowledgement email
+Date: Thu, 13 Sep 2012 10:08:03 +0100
+Message-ID: <987654@foo.local>
+Content-Type: multipart/mixed; boundary="-----7D81B75CCC90D2974F7A1CBD"
+
+This is a multi-part message in MIME format.
+-------7D81B75CCC90D2974F7A1CBD
+Content-Type: text/html
+
+<div>
+ <p>
+ This is an acknowledgement of your email, that irritatingly
+ leaves out the final MIME boundary.
+ </p>
+<div>
diff --git a/spec/fixtures/files/nested-attachments-premature-end.email b/spec/fixtures/files/nested-attachments-premature-end.email
new file mode 100644
index 000000000..6b13808dc
--- /dev/null
+++ b/spec/fixtures/files/nested-attachments-premature-end.email
@@ -0,0 +1,110 @@
+From someone@example.org Mon May 15 13:10:29 2012
+Return-path: <someone@example.org>
+Envelope-to: foi@example.org
+Delivery-date: Mon, 15 May 2012 13:10:29 +0100
+Message-Id: <abcde@baz.local>
+Date: Mon, 15 May 2012 09:48:48 +0100
+From: "Example Person" <someone@example.org>
+To: <request@example.org>
+Subject: some FOI request or other
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="=__outer__="
+
+This is a MIME message. If you are reading this text, you may want to
+consider changing to a mail reader or gateway that understands how to
+properly handle MIME multipart messages.
+
+--=__outer__=
+Content-Type: multipart/alternative; boundary="=__inner__="
+
+--=__inner__=
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+X-MIME-Autoconverted: from 8bit to quoted-printable by something
+
+Hello
+=20
+Please find some information attached.
+=20
+
+--=__inner__=
+Content-Description: HTML
+Content-Type: text/html; charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+
+<html>
+ <head>
+ <title>some title text</title>
+ </head>
+ <body>
+ <p>blah blah blah</p>
+ </body>
+</html>
+
+--=__inner__=--
+
+--=__outer__=
+Content-Type: message/rfc822
+
+Return-path: <foo@bar>
+Date: Mon, 7 May 2012 12:47:06 +0100
+From: someone-else@example.org
+To: foi@example.org
+Message-Id: <56789@quux.local>
+Subject: a freedom of information requests
+Mime-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+
+ Dear Whoever,
+
+ Please could you let me know, um, whatever ...
+
+ Yours faithfully,
+
+ Whoever I Am
+
+--=__outer__=
+Content-Type: text/plain; charset=US-ASCII
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+ Dear Whowever,
+ =20
+ Please could you let me know, um, whatever ...
+ =20
+ Yours faithfully,
+ =20
+ Whoever I Am
+ =20
+
+--=__outer__=--
+
+--=__outer__=
+Content-Type: application/png; name="maroon-square.png"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="maroon-square.png"
+
+iVBORw0KGgoAAAANSUhEUgAAAEEAAABCCAYAAAAIY7vrAAAABmJLR0QA/wD/AP+g
+vaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QQeDSEx8qultwAAABl0
+RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAMzSURBVHja7VtL2psw
+DNS4rPv1Gj1Kt71Az9ZT9F7dN9MFGGThB/YfKDX2Kp8DRBpLowcKvn/5ShERiAgl
+srh8aT93tJzWdae8XR0CEICwUx59K54H4QFKp0Eg5alrAwEYIDx5DRAGCAOEAcIA
+QaUFfDoIHJawpEbOPd0dRPjJDWIUiEwt933+8es2Ovz++a3dCkREXmwD4ZbsVln6
+cLkef14duAMqAGCkY0A+jBNgXGFZU/eKa3fhZjlQqLhHKF9oFbpulE2Z/oFrXTd+
+nlOWkn1dMHXrAiWguq0iG9uk/REjBggPtgQOED781my4wwBhgDBAmPmUAwR0X0UO
+dxggnA8CO5xocU8HoAoEDwA6nOyCH+ZMKQ4zy+QbNBoUirquMPBJcgPyJkOi+c7S
+ohhn6ZctzDIrcFalIspYILG1et9WABUtt6WztLq+/0Amp9sCnsCBUhfvK4FLiRCA
+QwC7JABGTngrIIPnIjf6R5We0uxz3j+FbCvdy2nlY/IgcfrMRQuFHIC9Sap3AW8n
+2gZ+cZYCVn4LzBxxnykNgJpWN8lt7yw+QCMxan2s8lQXcNlDlpAW7YmIXMszTgoH
+rU91+8OFYXN9ikz/LyLgExSCDlaO+cdGsIEQkyUAIgFMKRTEn3vDjFFHwWSIzEQC
+cmN4IHVNGG2PQXhhsuRl3jihwQyB6H1274gV1BhKLKNt4ZEpkygeeoC+xytdK1cr
+oX0EACphnTZXbbLMmL/YBGo9lSU1OmBONMnTlQUqTa4y1VgAddg0hdTR04lyT0Xq
+8RYAyHVyBX6ET/9wTBD6TWVCMH5Qo3yhXju3bNY/BBMdsoLYBMmnzQdOP56O36s5
+40r1D7UWYV5dNT2nbxVBAHb43Y36CdbXfTii6isU/U7ZXLQ4w/V/wotFoilVF2kl
+w7YCDrIPkj4/G9fao7q0rYSSJdgeSqmQrCU+r/j8rOv/gpuKPm5Lffen5eN+ljeo
+rcfW0Om2Enm9KwDZAgrG98txX9cMe6X2E5SGU29VTE17lFAUkMybsXclndu31BGX
+hcgWv8oxonYtkf/jhc10WPGgm2IZncKlu+sg8vLm7hDSwk3f2/wFEzN3v6aAXQ0A
+AAAASUVORK5CYII=
+
+--=__outer__=--
+
diff --git a/spec/fixtures/files/no-part-charset-random-data.email b/spec/fixtures/files/no-part-charset-random-data.email
new file mode 100644
index 000000000..d51fd3f38
--- /dev/null
+++ b/spec/fixtures/files/no-part-charset-random-data.email
@@ -0,0 +1,30 @@
+From xxxx@yahoo.cn Mon Oct 08 14:01:34 2012
+Return-path: <xxxx@yahoo.cn>
+Envelope-to: foi@atlas.ukcod.org.uk
+Delivery-date: Mon, 08 Oct 2012 14:01:34 +0100
+Received: (qmail 63864 invoked from network); 8 Oct 2012 13:01:12 -0000
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.cn; s=s1024; t=1349701272; bh=T/mtlIYvhB/L5RO+CvTazeAdGf1n1zsGXBoA8EKGT9M=; h=Message-ID:X-Yahoo-Newman-Property:X-YMail-OSG:X-Yahoo-SMTP:Received:X-mailer:From:Subject:To:Content-Transfer-Encoding:Content-Type:Date; b=LYI/PXvA7DA746bmyprChUg7N8YDvN9XE/bhfTt5MW7siOmxHHzn1w+s5X33PvLI0x0UfJLo+MCkTnGPKnG5BYY38US8PkocJYyphrvF/eaUl3ALf8UvxHBOJX1iIi89Xp2NnfbS8lz9kZAWifb9GOnOA5/kLDcL5/WJXliit2k=
+Message-ID: <xxxx@xxxx.yahoo.com>
+X-Yahoo-Newman-Property: ymail-5
+X-YMail-OSG: nPs5jgsVM1myUoKjeEPTxxalz4BM6BZMEUYu.E8NPMPQyo_
+ Yej8T2WCTurn767NOwhuDIqNxC2QGZINqfjmKcdyW7a1P_Zxqr9GsjgxODci
+ ihwr7qYAGDDbcsrB.PX4epnJZHl3yAwoGW.1ReEZnXQANFcNep7.zNEbZ_2k
+ RU1IhI9aHYvxPxt5RWugwOoFRh9P8Ym35A88IMazNtVaBiBEXF6Vk8Aqr9XP
+ 3Vh9xOT9Pn6X8qOUjNXkdb3xB4S5AAIRSE9mqhL1KzHBwdVQs25IoM_2FV2b
+ gPsQGgL4_mwBH0WcEMhdj7Kn6Nfb44L.50E_V3DH.8P7KzDK8zNVXSbAqohX
+ Qi6MzUK2frr8IyZyYzHb.ekff7kAcJgUoHvhnyPar8tRYxhQT3_xsUTzsx8N
+ oWckVPh_i3OT7U4ObgekqgtteMoYqPH2eF1SZXamGBAs-
+X-Yahoo-SMTP: YUQHwRWswBDjbw_M.D6EP4KpT9khlJErDRBQi4ySZQ--
+X-mailer: MIME::Lite 3.027 (F2.74; T1.31; A2.07; B3.13; Q3.13)
+From: =?GB2312?B?zsJKaWFu?= Bing <xxxx@yahoo.cn>
+Subject: =?GB2312?B?yM7A1svJ?=
+To: FOI Person <EMAIL_TO>
+Content-Transfer-Encoding: base64
+Content-Type: text/plain
+Date: Tue, 9 Oct 2012 20:53:06 +0800
+
+HPBSqsndNBX+ER4hyBoPhhnclcWKVFgbevdD5cJvfI/ARbxRYqA28hZ49Pf6A/ks
+NdVh4N5VPgRs/7SHYPfw5625pZJYTLj6nVdYk76sxnjiiAmwCJWGjPoWvO7nHUBv
+fuLXtNVq5HmD0bWWjAbSk2n74PW7v5izbNO2fjHyiyX2CIof0rriXDmOldJqoebO
+ejybrjG+Tahpu3FF1Mw98HfswzkdB46u/izLCzdUQVM=
+
diff --git a/spec/fixtures/files/part-without-charset-in-content-type.email b/spec/fixtures/files/part-without-charset-in-content-type.email
new file mode 100644
index 000000000..439d52cc3
--- /dev/null
+++ b/spec/fixtures/files/part-without-charset-in-content-type.email
@@ -0,0 +1,38 @@
+From example@example.com Wed Sep 15 17:55:40 2010
+Return-path: <example@example.com>
+Envelope-to: example@example.com
+Delivery-date: Wed, 15 Sep 2010 17:55:40 +0100
+From: <example@example.com>
+To: <request-xxxxx@whatdotheyknow.com>
+Date: Wed, 15 Sep 2010 17:56:03 +0100
+Subject: FOI Internal Review response
+Thread-Topic: FOI Internal Review response
+Thread-Index: xxxxx
+Message-ID: <xxxxxx>
+Accept-Language: en-US, en-GB
+Content-Language: en-US
+X-MS-Has-Attach: yes
+X-MS-TNEF-Correlator:
+acceptlanguage: en-US, en-GB
+Content-Type: multipart/mixed;
+ boundary="_002_E6527350F565F54A88C36C23F6C2B86702618AD0DF95SDCCPMSXMB5_"
+MIME-Version: 1.0
+
+--_002_E6527350F565F54A88C36C23F6C2B86702618AD0DF95SDCCPMSXMB5_
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+
+someencodedtext=
+
+--_002_E6527350F565F54A88C36C23F6C2B86702618AD0DF95SDCCPMSXMB5_
+Content-Type: document/pdf; name="document.pdf"
+Content-Description: document.pdf
+Content-Disposition: attachment; filename="document.pdf";
+ size=62103; creation-date="Wed, 15 Sep 2010 17:54:27 GMT";
+ modification-date="Wed, 15 Sep 2010 17:54:27 GMT"
+Content-Transfer-Encoding: base64
+
+somemoreencodedtext=
+
+--_002_E6527350F565F54A88C36C23F6C2B86702618AD0DF95SDCCPMSXMB5_--
+
diff --git a/spec/fixtures/files/subject-bad-utf-8-trailing-base64.email b/spec/fixtures/files/subject-bad-utf-8-trailing-base64.email
new file mode 100644
index 000000000..dad621877
--- /dev/null
+++ b/spec/fixtures/files/subject-bad-utf-8-trailing-base64.email
@@ -0,0 +1,5 @@
+From: foo@bar
+To: baz@quux
+Subject: =?UTF-8?B?aGVsbG/w?=
+
+Hello, this is the text of the email.
diff --git a/spec/fixtures/files/subject-bad-utf-8-trailing-quoted-printable.email b/spec/fixtures/files/subject-bad-utf-8-trailing-quoted-printable.email
new file mode 100644
index 000000000..b80deb4e8
--- /dev/null
+++ b/spec/fixtures/files/subject-bad-utf-8-trailing-quoted-printable.email
@@ -0,0 +1,5 @@
+From: foo@bar
+To: baz@quux
+Subject: =?UTF-8?Q?hello=F0=?=
+
+Hello, this is the text of the email.
diff --git a/spec/fixtures/files/tnef-attachment-empty.email b/spec/fixtures/files/tnef-attachment-empty.email
new file mode 100644
index 000000000..7967aa95b
--- /dev/null
+++ b/spec/fixtures/files/tnef-attachment-empty.email
@@ -0,0 +1,196 @@
+From hello@blah.local Fri Feb 21 16:23:14 2013
+Return-path: <bar@example.org>
+Envelope-to: foo@example.org
+Delivery-date: Fri, 21 Feb 2013 16:23:14 +0000
+Content-Type: multipart/mixed;
+ boundary="_000_553468B23EE29B4F8836CBD0E1B2A15A275C3AA855POLNIEXMBV2po_"
+From: <bar@example.org>
+To: <foo@example.org>
+Sender: <hello@blah.local>
+Date: Fri, 21 Feb 2013 16:23:04 +0000
+Subject: here's a useless email
+Message-ID: <12345@blah.local>
+Accept-Language: en-US, en-GB
+Content-Language: en-US
+X-MS-Has-Attach:
+X-MS-TNEF-Correlator: <12345@blah.local>
+acceptlanguage: en-US, en-GB
+MIME-Version: 1.0
+
+--_000_553468B23EE29B4F8836CBD0E1B2A15A275C3AA855POLNIEXMBV2po_
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: quoted-printable
+
+This attachment just has a body from one of the tests
+in the tnef package in Debian.
+
+--_000_553468B23EE29B4F8836CBD0E1B2A15A275C3AA855POLNIEXMBV2po_
+Content-Disposition: attachment; filename="winmail.dat"
+Content-Transfer-Encoding: base64
+Content-Type: application/ms-tnef; name="winmail.dat"
+
+eJ8+IiURAQaQCAAEAAAAAAABAAEAAQeQBgAIAAAA5AQAAAAAAADoAAENgAQAAgAA
+AAIAAgABBYADAA4AAADVBwQAGQAKAA8AIwABADYBASCAAwAOAAAA1QcEABkACgAP
+ACQAAQA3AQEJgAEAIQAAADBEREEwRkNCQ0MwN0MxNDE5MkVFODZGQzQyRDE1Qjk1
+AGYHAQSQBgBkAgAAAQAAAA8AAAAfAAEwAQAAABAAAAAzAGsAdQBzAGUAcgAyAAAA
+HwACMAEAAAAGAAAARQBYAAAAAAAfAAMwAQAAAI4AAAAvAE8APQBCAFIALQBFAFgA
+QwBIAC0AVABFAFMAVAAvAE8AVQA9AEYASQBSAFMAVAAgAEEARABNAEkATgBJAFMA
+VABSAEEAVABJAFYARQAgAEcAUgBPAFUAUAAvAEMATgA9AFIARQBDAEkAUABJAEUA
+TgBUAFMALwBDAE4APQAzAGsAdQBzAGUAcgAyAAAAAAADAAAwAAAAAAMA/18AAAAA
+AwAVDAEAAAACAQswAQAAAEoAAABFWDovTz1CUi1FWENILVRFU1QvT1U9RklSU1Qg
+QURNSU5JU1RSQVRJVkUgR1JPVVAvQ049UkVDSVBJRU5UUy9DTj0zS1VTRVIyAAAA
+HwAgOgEAAAAQAAAAMwBrAHUAcwBlAHIAMgAAAAMA/V8BAAAACwBAOgAA+T8CAfdf
+AQAAAGMAAAAAAAAA3KdAyMBCEBq0uQgAKy/hggEAAAAAAAAAL289QlItRVhDSC1U
+RVNUL291PUZpcnN0IEFkbWluaXN0cmF0aXZlIEdyb3VwL2NuPVJlY2lwaWVudHMv
+Y249M2t1c2VyMgAAAwAAOQAAAAAfAP45AQAAAEoAAAAzAGsAdQBzAGUAcgAyAEAA
+YgByAGUAeABjAGgAYQBuAGcAZQAuAGQAbwBsAHAAaABpAG4AcwBlAGEAcgBjAGgA
+LgBjAG8AbQAAAAAAAwBxOgAAAAAfAPZfAQAAABAAAAAzAGsAdQBzAGUAcgAyAAAA
+m2sBA5AGAEwbAAAzAAAACwACAAEAAAAfABoAAQAAABIAAABJAFAATQAuAE4AbwB0
+AGUAAAAAAAMAJgAAAAAAAwA2AAAAAAAfADcAAQAAAB4AAABCAGkAbABsACAAbwBm
+ACAAUgBpAGcAaAB0AHMAAAAAAEAAOQBgQvtkuknFAR8APQABAAAAAgAAAAAAAAAC
+AUcAAQAAADgAAABjPXVzO2E9IDtwPUJSLUVYQ0gtVEVTVDtsPUJSLUVYQ0gtREVW
+MS0wNTA0MjUxNzE1MzZaLTE0AB8AcAABAAAAHgAAAEIAaQBsAGwAIABvAGYAIABS
+AGkAZwBoAHQAcwAAAAAAAgFxAAEAAAAWAAAAAcVJumT7yarjal9+TnmqsNvwaipi
+/QAAHwAaDAEAAAAQAAAAMwBrAHIAZQBsAGEAeQAAAB8AHQ4BAAAAHgAAAEIAaQBs
+AGwAIABvAGYAIABSAGkAZwBoAHQAcwAAAAAAAgETEAEAAADuFAAAPCFET0NUWVBF
+IEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMCBUcmFuc2l0aW9uYWwv
+L0VOIj4NCjxIVE1MPjxIRUFEPg0KPE1FVEEgaHR0cC1lcXVpdj1Db250ZW50LVR5
+cGUgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXVzLWFzY2lpIj4NCjxNRVRB
+IGNvbnRlbnQ9Ik1TSFRNTCA2LjAwLjM3OTAuMTgzMCIgbmFtZT1HRU5FUkFUT1I+
+PC9IRUFEPg0KPEJPRFk+DQo8RElWPg0KPERJVj48Rk9OVCBmYWNlPUFyaWFsIHNp
+emU9Mj5USEUgQklMTCBPRiBSSUdIVFM8QlI+QW1lbmRtZW50cyAxLTEwIG9mIHRo
+ZSANCkNvbnN0aXR1dGlvbjwvRk9OVD48L0RJVj4NCjxESVY+Jm5ic3A7PC9ESVY+
+DQo8RElWPjxGT05UIGZhY2U9QXJpYWwgc2l6ZT0yPlRoZSBDb252ZW50aW9ucyBv
+ZiBhIG51bWJlciBvZiB0aGUgU3RhdGVzIGhhdmluZywgDQphdCB0aGUgdGltZSBv
+ZiBhZG9wdGluZyB0aGUgQ29uc3RpdHV0aW9uLCBleHByZXNzZWQgYSBkZXNpcmUs
+IGluIG9yZGVyIHRvIA0KcHJldmVudCBtaXNjb25zdHJ1Y3Rpb24gb3IgYWJ1c2Ug
+b2YgaXRzIHBvd2VycywgdGhhdCBmdXJ0aGVyIGRlY2xhcmF0b3J5IGFuZCANCnJl
+c3RyaWN0aXZlIGNsYXVzZXMgc2hvdWxkIGJlIGFkZGVkLCBhbmQgYXMgZXh0ZW5k
+aW5nIHRoZSBncm91bmQgb2YgcHVibGljIA0KY29uZmlkZW5jZSBpbiB0aGUgR292
+ZXJubWVudCB3aWxsIGJlc3QgaW5zdXJlIHRoZSBiZW5lZmljZW50IGVuZHMgb2Yg
+aXRzIA0KaW5zdGl0dXRpb247IDxCUj5SZXNvbHZlZCwgYnkgdGhlIFNlbmF0ZSBh
+bmQgSG91c2Ugb2YgUmVwcmVzZW50YXRpdmVzIG9mIHRoZSANClVuaXRlZCBTdGF0
+ZXMgb2YgQW1lcmljYSwgaW4gQ29uZ3Jlc3MgYXNzZW1ibGVkLCB0d28tdGhpcmRz
+IG9mIGJvdGggSG91c2VzIA0KY29uY3VycmluZywgdGhhdCB0aGUgZm9sbG93aW5n
+IGFydGljbGVzIGJlIHByb3Bvc2VkIHRvIHRoZSBMZWdpc2xhdHVyZXMgb2YgdGhl
+IA0Kc2V2ZXJhbCBTdGF0ZXMsIGFzIGFtZW5kbWVudHMgdG8gdGhlIENvbnN0aXR1
+dGlvbiBvZiB0aGUgVW5pdGVkIFN0YXRlczsgYWxsIG9yIA0KYW55IG9mIHdoaWNo
+IGFydGljbGVzLCB3aGVuIHJhdGlmaWVkIGJ5IHRocmVlLWZvdXJ0aHMgb2YgdGhl
+IHNhaWQgTGVnaXNsYXR1cmVzLCANCnRvIGJlIHZhbGlkIHRvIGFsbCBpbnRlbnRz
+IGFuZCBwdXJwb3NlcyBhcyBwYXJ0IG9mIHRoZSBzYWlkIENvbnN0aXR1dGlvbiwg
+DQpuYW1lbHk6IDwvRk9OVD48L0RJVj4NCjxESVY+Jm5ic3A7PC9ESVY+DQo8RElW
+PjxGT05UIGZhY2U9QXJpYWwgc2l6ZT0yPkFtZW5kbWVudCBJPC9GT05UPjwvRElW
+Pg0KPERJVj4mbmJzcDs8L0RJVj4NCjxESVY+PEZPTlQgZmFjZT1BcmlhbCBzaXpl
+PTI+Q29uZ3Jlc3Mgc2hhbGwgbWFrZSBubyBsYXcgcmVzcGVjdGluZyBhbiANCmVz
+dGFibGlzaG1lbnQgb2YgcmVsaWdpb24sIG9yIHByb2hpYml0aW5nIHRoZSBmcmVl
+IGV4ZXJjaXNlIHRoZXJlb2Y7IG9yIA0KYWJyaWRnaW5nIHRoZSBmcmVlZG9tIG9m
+IHNwZWVjaCwgb3Igb2YgdGhlIHByZXNzOyBvciB0aGUgcmlnaHQgb2YgdGhlIHBl
+b3BsZSANCnBlYWNlYWJseSB0byBhc3NlbWJsZSwgYW5kIHRvIHBldGl0aW9uIHRo
+ZSBnb3Zlcm5tZW50IGZvciBhIHJlZHJlc3Mgb2YgDQpncmlldmFuY2VzLiA8L0ZP
+TlQ+PC9ESVY+DQo8RElWPiZuYnNwOzwvRElWPg0KPERJVj48Rk9OVCBmYWNlPUFy
+aWFsIHNpemU9Mj5BbWVuZG1lbnQgSUk8L0ZPTlQ+PC9ESVY+DQo8RElWPiZuYnNw
+OzwvRElWPg0KPERJVj48Rk9OVCBmYWNlPUFyaWFsIHNpemU9Mj5BIHdlbGwgcmVn
+dWxhdGVkIG1pbGl0aWEsIGJlaW5nIG5lY2Vzc2FyeSB0byB0aGUgDQpzZWN1cml0
+eSBvZiBhIGZyZWUgc3RhdGUsIHRoZSByaWdodCBvZiB0aGUgcGVvcGxlIHRvIGtl
+ZXAgYW5kIGJlYXIgYXJtcywgc2hhbGwgDQpub3QgYmUgaW5mcmluZ2VkLiA8L0ZP
+TlQ+PC9ESVY+DQo8RElWPiZuYnNwOzwvRElWPg0KPERJVj48Rk9OVCBmYWNlPUFy
+aWFsIHNpemU9Mj5BbWVuZG1lbnQgSUlJPC9GT05UPjwvRElWPg0KPERJVj4mbmJz
+cDs8L0RJVj4NCjxESVY+PEZPTlQgZmFjZT1BcmlhbCBzaXplPTI+Tm8gc29sZGll
+ciBzaGFsbCwgaW4gdGltZSBvZiBwZWFjZSBiZSBxdWFydGVyZWQgaW4gDQphbnkg
+aG91c2UsIHdpdGhvdXQgdGhlIGNvbnNlbnQgb2YgdGhlIG93bmVyLCBub3IgaW4g
+dGltZSBvZiB3YXIsIGJ1dCBpbiBhIG1hbm5lciANCnRvIGJlIHByZXNjcmliZWQg
+YnkgbGF3LiA8L0ZPTlQ+PC9ESVY+DQo8RElWPiZuYnNwOzwvRElWPg0KPERJVj48
+Rk9OVCBmYWNlPUFyaWFsIHNpemU9Mj5BbWVuZG1lbnQgSVY8L0ZPTlQ+PC9ESVY+
+DQo8RElWPiZuYnNwOzwvRElWPg0KPERJVj48Rk9OVCBmYWNlPUFyaWFsIHNpemU9
+Mj5UaGUgcmlnaHQgb2YgdGhlIHBlb3BsZSB0byBiZSBzZWN1cmUgaW4gdGhlaXIg
+DQpwZXJzb25zLCBob3VzZXMsIHBhcGVycywgYW5kIGVmZmVjdHMsIGFnYWluc3Qg
+dW5yZWFzb25hYmxlIHNlYXJjaGVzIGFuZCANCnNlaXp1cmVzLCBzaGFsbCBub3Qg
+YmUgdmlvbGF0ZWQsIGFuZCBubyB3YXJyYW50cyBzaGFsbCBpc3N1ZSwgYnV0IHVw
+b24gcHJvYmFibGUgDQpjYXVzZSwgc3VwcG9ydGVkIGJ5IG9hdGggb3IgYWZmaXJt
+YXRpb24sIGFuZCBwYXJ0aWN1bGFybHkgZGVzY3JpYmluZyB0aGUgcGxhY2UgDQp0
+byBiZSBzZWFyY2hlZCwgYW5kIHRoZSBwZXJzb25zIG9yIHRoaW5ncyB0byBiZSBz
+ZWl6ZWQuIDwvRk9OVD48L0RJVj4NCjxESVY+Jm5ic3A7PC9ESVY+DQo8RElWPjxG
+T05UIGZhY2U9QXJpYWwgc2l6ZT0yPkFtZW5kbWVudCBWPC9GT05UPjwvRElWPg0K
+PERJVj4mbmJzcDs8L0RJVj4NCjxESVY+PEZPTlQgZmFjZT1BcmlhbCBzaXplPTI+
+Tm8gcGVyc29uIHNoYWxsIGJlIGhlbGQgdG8gYW5zd2VyIGZvciBhIGNhcGl0YWws
+IG9yIA0Kb3RoZXJ3aXNlIGluZmFtb3VzIGNyaW1lLCB1bmxlc3Mgb24gYSBwcmVz
+ZW50bWVudCBvciBpbmRpY3RtZW50IG9mIGEgZ3JhbmQganVyeSwgDQpleGNlcHQg
+aW4gY2FzZXMgYXJpc2luZyBpbiB0aGUgbGFuZCBvciBuYXZhbCBmb3JjZXMsIG9y
+IGluIHRoZSBtaWxpdGlhLCB3aGVuIGluIA0KYWN0dWFsIHNlcnZpY2UgaW4gdGlt
+ZSBvZiB3YXIgb3IgcHVibGljIGRhbmdlcjsgbm9yIHNoYWxsIGFueSBwZXJzb24g
+YmUgc3ViamVjdCANCmZvciB0aGUgc2FtZSBvZmZlbnNlIHRvIGJlIHR3aWNlIHB1
+dCBpbiBqZW9wYXJkeSBvZiBsaWZlIG9yIGxpbWI7IG5vciBzaGFsbCBiZSANCmNv
+bXBlbGxlZCBpbiBhbnkgY3JpbWluYWwgY2FzZSB0byBiZSBhIHdpdG5lc3MgYWdh
+aW5zdCBoaW1zZWxmLCBub3IgYmUgZGVwcml2ZWQgDQpvZiBsaWZlLCBsaWJlcnR5
+LCBvciBwcm9wZXJ0eSwgd2l0aG91dCBkdWUgcHJvY2VzcyBvZiBsYXc7IG5vciBz
+aGFsbCBwcml2YXRlIA0KcHJvcGVydHkgYmUgdGFrZW4gZm9yIHB1YmxpYyB1c2Us
+IHdpdGhvdXQganVzdCBjb21wZW5zYXRpb24uIDwvRk9OVD48L0RJVj4NCjxESVY+
+Jm5ic3A7PC9ESVY+DQo8RElWPjxGT05UIGZhY2U9QXJpYWwgc2l6ZT0yPkFtZW5k
+bWVudCBWSTwvRk9OVD48L0RJVj4NCjxESVY+Jm5ic3A7PC9ESVY+DQo8RElWPjxG
+T05UIGZhY2U9QXJpYWwgc2l6ZT0yPkluIGFsbCBjcmltaW5hbCBwcm9zZWN1dGlv
+bnMsIHRoZSBhY2N1c2VkIHNoYWxsIA0KZW5qb3kgdGhlIHJpZ2h0IHRvIGEgc3Bl
+ZWR5IGFuZCBwdWJsaWMgdHJpYWwsIGJ5IGFuIGltcGFydGlhbCBqdXJ5IG9mIHRo
+ZSBzdGF0ZSANCmFuZCBkaXN0cmljdCB3aGVyZWluIHRoZSBjcmltZSBzaGFsbCBo
+YXZlIGJlZW4gY29tbWl0dGVkLCB3aGljaCBkaXN0cmljdCBzaGFsbCANCmhhdmUg
+YmVlbiBwcmV2aW91c2x5IGFzY2VydGFpbmVkIGJ5IGxhdywgYW5kIHRvIGJlIGlu
+Zm9ybWVkIG9mIHRoZSBuYXR1cmUgYW5kIA0KY2F1c2Ugb2YgdGhlIGFjY3VzYXRp
+b247IHRvIGJlIGNvbmZyb250ZWQgd2l0aCB0aGUgd2l0bmVzc2VzIGFnYWluc3Qg
+aGltOyB0byANCmhhdmUgY29tcHVsc29yeSBwcm9jZXNzIGZvciBvYnRhaW5pbmcg
+d2l0bmVzc2VzIGluIGhpcyBmYXZvciwgYW5kIHRvIGhhdmUgdGhlIA0KYXNzaXN0
+YW5jZSBvZiBjb3Vuc2VsIGZvciBoaXMgZGVmZW5zZS4gPC9GT05UPjwvRElWPg0K
+PERJVj4mbmJzcDs8L0RJVj4NCjxESVY+PEZPTlQgZmFjZT1BcmlhbCBzaXplPTI+
+QW1lbmRtZW50IFZJSTwvRk9OVD48L0RJVj4NCjxESVY+Jm5ic3A7PC9ESVY+DQo8
+RElWPjxGT05UIGZhY2U9QXJpYWwgc2l6ZT0yPkluIHN1aXRzIGF0IGNvbW1vbiBs
+YXcsIHdoZXJlIHRoZSB2YWx1ZSBpbiANCmNvbnRyb3ZlcnN5IHNoYWxsIGV4Y2Vl
+ZCB0d2VudHkgZG9sbGFycywgdGhlIHJpZ2h0IG9mIHRyaWFsIGJ5IGp1cnkgc2hh
+bGwgYmUgDQpwcmVzZXJ2ZWQsIGFuZCBubyBmYWN0IHRyaWVkIGJ5IGEganVyeSwg
+c2hhbGwgYmUgb3RoZXJ3aXNlIHJlZXhhbWluZWQgaW4gYW55IA0KY291cnQgb2Yg
+dGhlIFVuaXRlZCBTdGF0ZXMsIHRoYW4gYWNjb3JkaW5nIHRvIHRoZSBydWxlcyBv
+ZiB0aGUgY29tbW9uIGxhdy4gDQo8L0ZPTlQ+PC9ESVY+DQo8RElWPiZuYnNwOzwv
+RElWPg0KPERJVj48Rk9OVCBmYWNlPUFyaWFsIHNpemU9Mj5BbWVuZG1lbnQgVklJ
+STwvRk9OVD48L0RJVj4NCjxESVY+Jm5ic3A7PC9ESVY+DQo8RElWPjxGT05UIGZh
+Y2U9QXJpYWwgc2l6ZT0yPkV4Y2Vzc2l2ZSBiYWlsIHNoYWxsIG5vdCBiZSByZXF1
+aXJlZCwgbm9yIGV4Y2Vzc2l2ZSANCmZpbmVzIGltcG9zZWQsIG5vciBjcnVlbCBh
+bmQgdW51c3VhbCBwdW5pc2htZW50cyBpbmZsaWN0ZWQuIDwvRk9OVD48L0RJVj4N
+CjxESVY+Jm5ic3A7PC9ESVY+DQo8RElWPjxGT05UIGZhY2U9QXJpYWwgc2l6ZT0y
+PkFtZW5kbWVudCBJWDwvRk9OVD48L0RJVj4NCjxESVY+Jm5ic3A7PC9ESVY+DQo8
+RElWPjxGT05UIGZhY2U9QXJpYWwgc2l6ZT0yPlRoZSBlbnVtZXJhdGlvbiBpbiB0
+aGUgQ29uc3RpdHV0aW9uLCBvZiBjZXJ0YWluIA0KcmlnaHRzLCBzaGFsbCBub3Qg
+YmUgY29uc3RydWVkIHRvIGRlbnkgb3IgZGlzcGFyYWdlIG90aGVycyByZXRhaW5l
+ZCBieSB0aGUgDQpwZW9wbGUuIDwvRk9OVD48L0RJVj4NCjxESVY+Jm5ic3A7PC9E
+SVY+DQo8RElWPjxGT05UIGZhY2U9QXJpYWwgc2l6ZT0yPkFtZW5kbWVudCBYPC9G
+T05UPjwvRElWPg0KPERJVj4mbmJzcDs8L0RJVj4NCjxESVY+PEZPTlQgZmFjZT1B
+cmlhbCBzaXplPTI+VGhlIHBvd2VycyBub3QgZGVsZWdhdGVkIHRvIHRoZSBVbml0
+ZWQgU3RhdGVzIGJ5IA0KdGhlIENvbnN0aXR1dGlvbiwgbm9yIHByb2hpYml0ZWQg
+YnkgaXQgdG8gdGhlIHN0YXRlcywgYXJlIHJlc2VydmVkIHRvIHRoZSBzdGF0ZXMg
+DQpyZXNwZWN0aXZlbHksIG9yIHRvIHRoZSBwZW9wbGUuIDwvRk9OVD48L0RJVj48
+L0RJVj48L0JPRFk+PC9IVE1MPg0KAAAfADUQAQAAAKIAAAA8ADQANQAyADAARgA2
+ADEANQAxAEQAQQBGADIAQQA0ADQAQgBBADgANwA4AEIARgAyAEYAMwA4ADAAMwA0
+ADgARQAyADYARQA1AEAAYgByAC0AZQB4AGMAaAAtAGQAZQB2ADEALgBiAHIAZQB4
+AGMAaABhAG4AZwBlAC4AZABvAGwAcABoAGkAbgBzAGUAYQByAGMAaAAuAGMAbwBt
+AD4AAAAAAAMAgBD/////HwDzEAEAAAAmAAAAQgBpAGwAbAAgAG8AZgAgAFIAaQBn
+AGgAdABzAC4ARQBNAEwAAAAAAAsA9BAAAAAACwD1EAAAAAALAPYQAAAAAEAABzBR
+lpFluknFAUAACDBRlpFluknFAQMA3j+fTgAAAwDxPwkEAAAfAPg/AQAAABAAAAAz
+AGsAcgBlAGwAYQB5AAAAAgH5PwEAAABjAAAAAAAAANynQMjAQhAatLkIACsv4YIB
+AAAAAAAAAC9PPUJSLUVYQ0gtVEVTVC9PVT1GSVJTVCBBRE1JTklTVFJBVElWRSBH
+Uk9VUC9DTj1SRUNJUElFTlRTL0NOPTNLUkVMQVkAAB8A+j8BAAAAEAAAADMAawBy
+AGUAbABhAHkAAAACAfs/AQAAAGMAAAAAAAAA3KdAyMBCEBq0uQgAKy/hggEAAAAA
+AAAAL089QlItRVhDSC1URVNUL09VPUZJUlNUIEFETUlOSVNUUkFUSVZFIEdST1VQ
+L0NOPVJFQ0lQSUVOVFMvQ049M0tSRUxBWQAAAwD9P+QEAAADABlAAAAAAAMAGkAA
+AAAAHwAwQAEAAAAQAAAAMwBLAFIARQBMAEEAWQAAAB8AMUABAAAAEAAAADMASwBS
+AEUATABBAFkAAAAfADhAAQAAABAAAAAzAEsAUgBFAEwAQQBZAAAAHwA5QAEAAAAQ
+AAAAMwBLAFIARQBMAEEAWQAAAAMAdkD/////AwACWQAAFgADAAlZAgAAAAsAhYEI
+IAYAAAAAAMAAAAAAAABGAAAAAA6FAAAAAAAAAwCdgQggBgAAAAAAwAAAAAAAAEYA
+AAAAUoUAAJjDAQAfAJ6BCCAGAAAAAADAAAAAAAAARgAAAABUhQAAAQAAAAoAAAAx
+ADEALgAwAAAAAAADAOmBCCAGAAAAAADAAAAAAAAARgAAAAABhQAAAAAAAAsA7oEI
+IAYAAAAAAMAAAAAAAABGAAAAAAOFAAAAAAAAAwD4gQggBgAAAAAAwAAAAAAAAEYA
+AAAAEIUAAAAAAAADAP+BCCAGAAAAAADAAAAAAAAARgAAAAAYhQAAAAAAAAsAIIII
+IAYAAAAAAMAAAAAAAABGAAAAAAaFAAAAAAAACwAkggggBgAAAAAAwAAAAAAAAEYA
+AAAAgoUAAAAAAAAfACaCCCAGAAAAAADAAAAAAAAARgAAAACDhQAAAQAAACYAAAA0
+ADAANQAxADMAMQA1ADEANwAtADIANQAwADQAMgAwADAANQAAAAAAAwBxggggBgAA
+AAAAwAAAAAAAAEYAAAAAk4UAAAAAAAALACkAAAAAAAsAIwAAAAAAAgF/AAEAAABR
+AAAAPDQ1MjBGNjE1MURBRjJBNDRCQTg3OEJGMkYzODAzNDhFMjZFNUBici1leGNo
+LWRldjEuYnJleGNoYW5nZS5kb2xwaGluc2VhcmNoLmNvbT4AAAAAC/o=
+
+--_000_553468B23EE29B4F8836CBD0E1B2A15A275C3AA855POLNIEXMBV2po_--
+
diff --git a/spec/fixtures/files/tnef-attachment-truncated.email b/spec/fixtures/files/tnef-attachment-truncated.email
new file mode 100644
index 000000000..365a5a442
--- /dev/null
+++ b/spec/fixtures/files/tnef-attachment-truncated.email
@@ -0,0 +1,34 @@
+From hello@blah.local Fri Feb 21 16:23:14 2013
+Return-path: <bar@example.org>
+Envelope-to: foo@example.org
+Delivery-date: Fri, 21 Feb 2013 16:23:14 +0000
+Content-Type: multipart/mixed;
+ boundary="_000_553468B23EE29B4F8836CBD0E1B2A15A275C3AA855POLNIEXMBV2po_"
+From: <bar@example.org>
+To: <foo@example.org>
+Sender: <hello@blah.local>
+Date: Fri, 21 Feb 2013 16:23:04 +0000
+Subject: here's a useless email
+Message-ID: <12345@blah.local>
+Accept-Language: en-US, en-GB
+Content-Language: en-US
+X-MS-Has-Attach:
+X-MS-TNEF-Correlator: <12345@blah.local>
+acceptlanguage: en-US, en-GB
+MIME-Version: 1.0
+
+--_000_553468B23EE29B4F8836CBD0E1B2A15A275C3AA855POLNIEXMBV2po_
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: quoted-printable
+
+Some introductory text here, before the malformed TNEF attachment.
+
+--_000_553468B23EE29B4F8836CBD0E1B2A15A275C3AA855POLNIEXMBV2po_
+Content-Disposition: attachment; filename="winmail.dat"
+Content-Transfer-Encoding: base64
+Content-Type: application/ms-tnef; name="winmail.dat"
+
+eJ8+IkV9AQaQCAAEAAAAAAABAAEAAQeQBgAIAAAA5AQAAAAAAADoAAEJgAEAIQAAAEMyRUUzRUYx
+
+--_000_553468B23EE29B4F8836CBD0E1B2A15A275C3AA855POLNIEXMBV2po_--
+
diff --git a/spec/fixtures/files/unrecognized-encoding-mail.email b/spec/fixtures/files/unrecognized-encoding-mail.email
new file mode 100644
index 000000000..266a90fbc
--- /dev/null
+++ b/spec/fixtures/files/unrecognized-encoding-mail.email
@@ -0,0 +1,36 @@
+From xxx@example.com Fri Jun 21 07:50:52 2013
+Return-path: <xxx@example.com>
+Envelope-to: xxx@example.com
+Delivery-date: Fri, 21 Jun 2013 07:50:52 +0100
+Message-ID: <185C0D48380D7AE612DD38A527D5EAF2@tmvbalem>
+From: "cttlqvx" <xxx@example.com>
+To: <xxx@example.com>
+Subject: =?hz-gb-2312?B?fntPck9ISXpWQn59c3J5dW95d3MoQUQpICAgIA==?=
+Date: Fri, 21 Jun 2013 14:48:20 +0800
+MIME-Version: 1.0
+Content-Type: multipart/related;
+ type="multipart/alternative";
+ boundary="----=_NextPart_000_02ED_01A0462A.178683F0"
+X-Priority: 1
+X-MSMail-Priority: High
+X-Mailer: Microsoft Outlook Express 6.00.2900.5512
+X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.5512
+
+This is a multi-part message in MIME format.
+
+------=_NextPart_000_02ED_01A0462A.178683F0
+Content-Type: multipart/alternative;
+ boundary="----=_NextPart_001_09EC_01A0462A.178683F0"
+
+------=_NextPart_001_09EC_01A0462A.178683F0
+Content-Type: text/plain;
+ charset="hz-gb-2312"
+Content-Transfer-Encoding: base64
+
+
+------=_NextPart_001_09EC_01A0462A.178683F0
+Content-Type: text/html;
+ charset="hz-gb-2312"
+Content-Transfer-Encoding: base64
+
+------=_NextPart_001_09EC_01A0462A.178683F0--
diff --git a/spec/fixtures/locale/en/app.po b/spec/fixtures/locale/en/app.po
index 3fe8a569b..ee5c8d9c8 100644
--- a/spec/fixtures/locale/en/app.po
+++ b/spec/fixtures/locale/en/app.po
@@ -31,7 +31,7 @@ msgid ""
msgstr ""
#: app/views/comment/_comment_form.rhtml:16
-msgid " (<strong>no ranty</strong> politics, read our <a href=\"%s\">moderation policy</a>)"
+msgid " (<strong>no ranty</strong> politics, read our <a href=\"{{url}}\">moderation policy</a>)"
msgstr ""
#: app/views/request/upload_response.rhtml:40
@@ -71,7 +71,7 @@ msgstr ""
#: app/views/public_body/view_email.rhtml:30
msgid ""
-" If you know the address to use, then please <a href=\"%s\">send it to us</a>.\n"
+" If you know the address to use, then please <a href=\"{{url}}\">send it to us</a>.\n"
" You may be able to find the address on their website, or by phoning them up and asking."
msgstr ""
@@ -123,20 +123,20 @@ msgid " when you send this message."
msgstr ""
#: app/views/public_body/show.rhtml:87
-msgid "%d Freedom of Information request to %s"
-msgid_plural "%d Freedom of Information requests to %s"
+msgid "{{count}} Freedom of Information request to {{public_body_name}}"
+msgid_plural "{{count}} Freedom of Information requests to {{public_body_name}}"
msgstr[0] ""
msgstr[1] ""
#: app/views/general/frontpage.rhtml:43
-msgid "%d request"
-msgid_plural "%d requests"
+msgid "{{count}} request"
+msgid_plural "{{count}} requests"
msgstr[0] ""
msgstr[1] ""
#: app/views/public_body/_body_listing_single.rhtml:21
-msgid "%d request made."
-msgid_plural "%d requests made."
+msgid "{{count}} request made."
+msgid_plural "{{count}} requests made."
msgstr[0] ""
msgstr[1] ""
@@ -187,43 +187,37 @@ msgstr ""
msgid "3. Now check your request"
msgstr ""
-#: app/views/public_body/show.rhtml:56
-msgid "<a class=\"link_button_green\" href=\"{{url}}\">{{text}}</a>"
-msgstr ""
-
#: app/views/request/_after_actions.rhtml:9
-msgid "<a href=\"%s\">Add an annotation</a> (to help the requester or others)"
+msgid "<a href=\"{{url}}\">Add an annotation</a> (to help the requester or others)"
msgstr ""
#: app/views/public_body/list.rhtml:29
-msgid "<a href=\"%s\">Are we missing a public authority?</a>."
+msgid "Are we missing a public authority?"
msgstr ""
#: app/views/request/_sidebar.rhtml:39
-msgid ""
-"<a href=\"%s\">Are you the owner of\n"
-" any commercial copyright on this page?</a>"
+msgid "Are you the owner of any commercial copyright on this page?"
msgstr ""
#: app/views/general/search.rhtml:168
-msgid "<a href=\"%s\">Browse all</a> or <a href=\"%s\">ask us to add one</a>."
+msgid "<a href=\"{{browse_url}}\">Browse all</a> or <a href=\"{{add_url}}\">ask us to add one</a>."
msgstr ""
#: app/views/public_body/list.rhtml:51
-msgid "<a href=\"%s\">Can't find the one you want?</a>"
+msgid "Can't find the one you want?"
msgstr ""
#: app/views/user/show.rhtml:118
-msgid "<a href=\"%s\">Sign in</a> to change password, subscriptions and more ({{user_name}} only)"
+msgid "<a href=\"{{url}}\">Sign in</a> to change password, subscriptions and more ({{user_name}} only)"
msgstr ""
#: app/views/request/_followup.rhtml:66 app/views/request/_followup.rhtml:73
#: app/views/request/show.rhtml:83 app/views/request/show.rhtml:87
-msgid "<a href=\"%s\">details</a>"
+msgid "details"
msgstr ""
#: app/views/request/_followup.rhtml:101
-msgid "<a href=\"%s\">what's that?</a>"
+msgid "what's that?"
msgstr ""
#: app/controllers/request_game_controller.rb:23
@@ -293,11 +287,11 @@ msgid "<p>We're glad you got some of the information that you wanted. If you fou
msgstr ""
#: app/controllers/request_controller.rb:318
-msgid "<p>You do not need to include your email in the request in order to get a reply (<a href=\"%s\">details</a>).</p>"
+msgid "<p>You do not need to include your email in the request in order to get a reply (<a href=\"{{url}}\">details</a>).</p>"
msgstr ""
#: app/controllers/request_controller.rb:316
-msgid "<p>You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (<a href=\"%s\">details</a>).</p>"
+msgid "<p>You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (<a href=\"{{url}}\">details</a>).</p>"
msgstr ""
#: app/controllers/request_controller.rb:324
@@ -327,7 +321,7 @@ msgstr ""
#: app/views/request/new.rhtml:135
msgid ""
"<strong> Can I request information about myself?</strong>\n"
-"\t\t\t<a href=\"%s\">No! (Click here for details)</a>"
+"\t\t\t<a href=\"{{url}}\">No! (Click here for details)</a>"
msgstr ""
#: app/views/general/_advanced_search_tips.rhtml:12
@@ -413,7 +407,7 @@ msgstr ""
#: app/views/request/preview.rhtml:31
msgid ""
"<strong>Privacy note:</strong> If you want to request private information about\n"
-" yourself then <a href=\"%s\">click here</a>."
+" yourself then <a href=\"{{url}}\">click here</a>."
msgstr ""
#: app/views/user/set_crop_profile_photo.rhtml:35
@@ -576,7 +570,7 @@ msgstr ""
#: app/views/request/show_response.rhtml:29
msgid ""
"At the bottom of this page, write a reply to them trying to persuade them to scan it in\n"
-" (<a href=\"%s\">more details</a>)."
+" (<a href=\"{{url}}\">more details</a>)."
msgstr ""
#: app/views/request/upload_response.rhtml:33
@@ -921,7 +915,7 @@ msgstr ""
#: app/views/request/upload_response.rhtml:23
msgid ""
"Enter your response below. You may attach one file (use email, or \n"
-"<a href=\"%s\">contact us</a> if you need more)."
+"<a href=\"{{url}}\">contact us</a> if you need more)."
msgstr ""
#: app/models/info_request.rb:259 app/models/info_request.rb:277
@@ -952,14 +946,14 @@ msgstr ""
msgid ""
"Everything that you enter on this page\n"
" will be <strong>displayed publicly</strong> on\n"
-" this website forever (<a href=\"%s\">why?</a>)."
+" this website forever (<a href=\"{{url}}\">why?</a>)."
msgstr ""
#: app/views/request/new.rhtml:120
msgid ""
"Everything that you enter on this page, including <strong>your name</strong>,\n"
" will be <strong>displayed publicly</strong> on\n"
-" this website forever (<a href=\"%s\">why?</a>)."
+" this website forever (<a href=\"{{url}}\">why?</a>)."
msgstr ""
#: locale/model_attributes.rb:68
@@ -1007,7 +1001,7 @@ msgid "Failed to convert image to a PNG"
msgstr ""
#: app/models/profile_photo.rb:105
-msgid "Failed to convert image to the correct size: at %{cols}x%{rows}, need %{width}x%{height}"
+msgid "Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}"
msgstr ""
#: app/views/general/search.rhtml:117
@@ -1018,7 +1012,7 @@ msgstr ""
msgid ""
"First, type in the <strong>name of the UK public authority</strong> you'd \n"
" like information from. <strong>By law, they have to respond</strong>\n"
-" (<a href=\"%s#%s\">why?</a>)."
+" (<a href=\"{{url}}\">why?</a>)."
msgstr ""
#: locale/model_attributes.rb:88
@@ -1147,7 +1141,7 @@ msgstr ""
msgid ""
"From the request page, try replying to a particular message, rather than sending\n"
" a general followup. If you need to make a general followup, and know\n"
-" an email which will go to the right place, please <a href=\"%s\">send it to us</a>."
+" an email which will go to the right place, please <a href=\"{{url}}\">send it to us</a>."
msgstr ""
#: app/views/request/_correspondence.rhtml:12
@@ -1278,7 +1272,7 @@ msgid "I've received an <strong>error message</strong>"
msgstr ""
#: app/views/public_body/view_email.rhtml:28
-msgid "If the address is wrong, or you know a better address, please <a href=\"%s\">contact us</a>."
+msgid "If the address is wrong, or you know a better address, please <a href=\"{{url}}\">contact us</a>."
msgstr ""
#: app/views/request_mailer/stopped_responses.rhtml:10
@@ -1292,21 +1286,21 @@ msgstr ""
msgid ""
"If you are dissatisfied by the response you got from\n"
" the public authority, you have the right to\n"
-" complain (<a href=\"%s\">details</a>)."
+" complain (<a href=\"{{url}}\">details</a>)."
msgstr ""
#: app/views/user/no_cookies.rhtml:20
-msgid "If you are still having trouble, please <a href=\"%s\">contact us</a>."
+msgid "If you are still having trouble, please <a href=\"{{url}}\">contact us</a>."
msgstr ""
#: app/views/request/hidden.rhtml:15
-msgid "If you are the requester, then you may <a href=\"%s\">sign in</a> to view the request."
+msgid "If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the request."
msgstr ""
#: app/views/request/new.rhtml:123
msgid ""
"If you are thinking of using a pseudonym,\n"
-" please <a href=\"%s\">read this first</a>."
+" please <a href=\"{{url}}\">read this first</a>."
msgstr ""
#: app/views/request/show.rhtml:105
@@ -1497,7 +1491,7 @@ msgid "Joined {{site_name}} in"
msgstr ""
#: app/views/request/new.rhtml:106
-msgid "Keep it <strong>focused</strong>, you'll be more likely to get what you want (<a href=\"%s\">why?</a>)."
+msgid "Keep it <strong>focused</strong>, you'll be more likely to get what you want (<a href=\"{{url}}\">why?</a>)."
msgstr ""
#: app/views/request/_request_filter_form.rhtml:6
@@ -1837,7 +1831,7 @@ msgid "Please"
msgstr ""
#: app/views/user/no_cookies.rhtml:15
-msgid "Please <a href=\"%s\">get in touch</a> with us so we can fix it."
+msgid "Please <a href=\"{{url}}\">get in touch</a> with us so we can fix it."
msgstr ""
#: app/views/request/show.rhtml:52
@@ -1990,7 +1984,7 @@ msgid ""
msgstr ""
#: app/models/outgoing_message.rb:157
-msgid "Please sign at the bottom with your name, or alter the \"%{signoff}\" signature"
+msgid "Please sign at the bottom with your name, or alter the \"{{signoff}}\" signature"
msgstr ""
#: app/views/user/sign.rhtml:8
@@ -2337,8 +2331,9 @@ msgid "Search the site to find what you were looking for."
msgstr ""
#: app/views/public_body/show.rhtml:85
-msgid "Search within the %d Freedom of Information requests to %s"
-msgid_plural "Search within the %d Freedom of Information requests made to %s"
+msgid "Search within the {{count}} Freedom of Information requests to {{public_body_name}}"
+msgid_plural "Search within the {{count}} Freedom of Information requests to {{public_body_name}}"
+
msgstr[0] ""
msgstr[1] ""
@@ -2680,10 +2675,7 @@ msgid "The request was refused by the public authority"
msgstr ""
#: app/views/request/hidden.rhtml:9
-msgid ""
-"The request you have tried to view has been removed. There are\n"
-"various reasons why we might have done this, sorry we can't be more specific here. Please <a\n"
-" href=\"%s\">contact us</a> if you have any questions."
+msgid "The request you have tried to view has been removed. There are\\nvarious reasons why we might have done this, sorry we can't be more specific here. Please <a\\n href=\"{{url}}\">contact us</a> if you have any questions."
msgstr ""
#: app/views/general/_advanced_search_tips.rhtml:36
@@ -2692,14 +2684,14 @@ msgstr ""
#: app/views/request/_followup.rhtml:59
msgid ""
-"The response to your request has been <strong>delayed</strong>. You can say that, \n"
+"The response to your request has been <strong>delayed</strong>. You can say that,\n"
" by law, the authority should normally have responded\n"
" <strong>promptly</strong> and"
msgstr ""
#: app/views/request/_followup.rhtml:71
msgid ""
-"The response to your request is <strong>long overdue</strong>. You can say that, by \n"
+"The response to your request is <strong>long overdue</strong>. You can say that, by\n"
" law, under all circumstances, the authority should have responded\n"
" by now"
msgstr ""
@@ -2804,15 +2796,9 @@ msgstr ""
msgid "There are {{count}} new annotations on your {{info_request}} request. Follow this link to see what they wrote."
msgstr ""
-#: app/views/public_body/show.rhtml:7
-msgid "There is %d person following this authority"
-msgid_plural "There are %d people following this authority"
-msgstr[0] ""
-msgstr[1] ""
-
#: app/views/request/_sidebar.rhtml:5
-msgid "There is %d person following this request"
-msgid_plural "There are %d people following this request"
+msgid "There is {{count}} person following this request"
+msgid_plural "There are {{count}} people following this request"
msgstr[0] ""
msgstr[1] ""
@@ -2876,7 +2862,7 @@ msgstr ""
#: app/views/request/_hidden_correspondence.rhtml:23
msgid ""
"This comment has been hidden. See annotations to\n"
-" find out why. If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
+" find out why. If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
msgstr ""
#: app/views/request/new.rhtml:63
@@ -2906,7 +2892,7 @@ msgstr ""
#: app/views/request/_hidden_correspondence.rhtml:17
msgid ""
"This outgoing message has been hidden. See annotations to\n"
-"\t\t\t\t\t\tfind out why. If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
+"\t\t\t\t\t\tfind out why. If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
msgstr ""
#: app/views/request/_describe_state.rhtml:44
@@ -2919,14 +2905,14 @@ msgid "This person has made no Freedom of Information requests using this site."
msgstr ""
#: app/views/user/show.rhtml:149
-msgid "This person's %d Freedom of Information request"
-msgid_plural "This person's %d Freedom of Information requests"
+msgid "This person's {{count}} Freedom of Information request"
+msgid_plural "This person's {{count}} Freedom of Information requests"
msgstr[0] ""
msgstr[1] ""
#: app/views/user/show.rhtml:179
-msgid "This person's %d annotation"
-msgid_plural "This person's %d annotations"
+msgid "This person's {{count}} annotation"
+msgid_plural "This person's {{count}} annotations"
msgstr[0] ""
msgstr[1] ""
@@ -2965,7 +2951,7 @@ msgstr ""
#: app/views/request/show.rhtml:11
msgid ""
"This request is hidden, so that only you the requester can see it. Please\n"
-" <a href=\"%s\">contact us</a> if you are not sure why."
+" <a href=\"{{url}}\">contact us</a> if you are not sure why."
msgstr ""
#: app/views/request/_describe_state.rhtml:7
@@ -2976,7 +2962,7 @@ msgstr ""
#: app/views/request/_hidden_correspondence.rhtml:10
msgid ""
"This response has been hidden. See annotations to find out why.\n"
-" If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
+" If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
msgstr ""
#: app/views/request/details.rhtml:6
@@ -3202,7 +3188,7 @@ msgstr ""
msgid ""
"Unfortunately we don't know the FOI\n"
"email address for that authority, so we can't validate this.\n"
-"Please <a href=\"%s\">contact us</a> to sort it out."
+"Please <a href=\"{{url}}\">contact us</a> to sort it out."
msgstr ""
#: app/views/request/new_bad_contact.rhtml:5
@@ -3439,7 +3425,7 @@ msgid ""
msgstr ""
#: app/views/request/new_please_describe.rhtml:16
-msgid "When you're done, <strong>come back here</strong>, <a href=\"%s\">reload this page</a> and file your new request."
+msgid "When you're done, <strong>come back here</strong>, <a href=\"{{url}}\">reload this page</a> and file your new request."
msgstr ""
#: app/views/request/show_response.rhtml:13
@@ -3571,7 +3557,7 @@ msgstr ""
msgid ""
"You may be able to find\n"
" one on their website, or by phoning them up and asking. If you manage\n"
-" to find one, then please <a href=\"%s\">send it to us</a>."
+" to find one, then please <a href=\"{{url}}\">send it to us</a>."
msgstr ""
#: app/views/request/new_bad_contact.rhtml:6
@@ -3642,24 +3628,19 @@ msgid "You've now cleared your profile photo"
msgstr ""
#: app/views/user/show.rhtml:149
-msgid "Your %d Freedom of Information request"
-msgid_plural "Your %d Freedom of Information requests"
+msgid "Your {{count}} Freedom of Information request"
+msgid_plural "Your {{count}} Freedom of Information requests"
msgstr[0] ""
msgstr[1] ""
#: app/views/user/show.rhtml:179
-msgid "Your %d annotation"
-msgid_plural "Your %d annotations"
+msgid "Your {{count}} annotation"
+msgid_plural "Your {{count}} annotations"
msgstr[0] ""
msgstr[1] ""
#: app/views/user/_signup.rhtml:22
-msgid ""
-"Your <strong>name will appear publicly</strong> \n"
-" (<a href=\"%s\">why?</a>)\n"
-" on this website and in search engines. If you\n"
-" are thinking of using a pseudonym, please \n"
-" <a href=\"%s\">read this first</a>."
+msgid "Your <strong>name will appear publicly</strong>\\n (<a href=\"{{why_url}}\">why?</a>)\\n on this website and in search engines. If you\\n are thinking of using a pseudonym, please\\n <a href=\"{{help_url}}\">read this first</a>."
msgstr ""
#: app/views/user/show.rhtml:172
@@ -3682,7 +3663,7 @@ msgid "Your email subscriptions"
msgstr ""
#: app/controllers/request_controller.rb:598
-msgid "Your follow up has not been sent because this request has been stopped to prevent spam. Please <a href=\"%s\">contact us</a> if you really want to send a follow up message."
+msgid "Your follow up has not been sent because this request has been stopped to prevent spam. Please <a href=\"{{url}}\">contact us</a> if you really want to send a follow up message."
msgstr ""
#: app/controllers/request_controller.rb:626
@@ -3712,7 +3693,7 @@ msgstr ""
#: app/views/request/preview.rhtml:8
msgid ""
"Your name, request and any responses will appear in <strong>search engines</strong>\n"
-" (<a href=\"%s\">details</a>)."
+" (<a href=\"{{url}}\">details</a>)."
msgstr ""
#: app/views/user/_signup.rhtml:18
@@ -3746,7 +3727,7 @@ msgid "Your request:"
msgstr ""
#: app/views/request/upload_response.rhtml:8
-msgid "Your response will <strong>appear on the Internet</strong>, <a href=\"%s\">read why</a> and answers to other questions."
+msgid "Your response will <strong>appear on the Internet</strong>, <a href=\"{{url}}\">read why</a> and answers to other questions."
msgstr ""
#: app/views/comment/new.rhtml:63
@@ -3990,7 +3971,7 @@ msgid ""
"no longer exists. If you are trying to make\n"
" From the request page, try replying to a particular message, rather than sending\n"
" a general followup. If you need to make a general followup, and know\n"
-" an email which will go to the right place, please <a href=\"%s\">send it to us</a>."
+" an email which will go to the right place, please <a href=\"{{url}}\">send it to us</a>."
msgstr ""
#: app/views/request/show.rhtml:72
diff --git a/spec/fixtures/locale/en_GB/app.po b/spec/fixtures/locale/en_GB/app.po
index 3fe8a569b..84997a319 100644
--- a/spec/fixtures/locale/en_GB/app.po
+++ b/spec/fixtures/locale/en_GB/app.po
@@ -31,7 +31,7 @@ msgid ""
msgstr ""
#: app/views/comment/_comment_form.rhtml:16
-msgid " (<strong>no ranty</strong> politics, read our <a href=\"%s\">moderation policy</a>)"
+msgid " (<strong>no ranty</strong> politics, read our <a href=\"{{url}}\">moderation policy</a>)"
msgstr ""
#: app/views/request/upload_response.rhtml:40
@@ -71,7 +71,7 @@ msgstr ""
#: app/views/public_body/view_email.rhtml:30
msgid ""
-" If you know the address to use, then please <a href=\"%s\">send it to us</a>.\n"
+" If you know the address to use, then please <a href=\"{{url}}\">send it to us</a>.\n"
" You may be able to find the address on their website, or by phoning them up and asking."
msgstr ""
@@ -123,20 +123,20 @@ msgid " when you send this message."
msgstr ""
#: app/views/public_body/show.rhtml:87
-msgid "%d Freedom of Information request to %s"
-msgid_plural "%d Freedom of Information requests to %s"
+msgid "{{count}} Freedom of Information request to {{public_body_name}}"
+msgid_plural "{{count}} Freedom of Information requests to {{public_body_name}}"
msgstr[0] ""
msgstr[1] ""
#: app/views/general/frontpage.rhtml:43
-msgid "%d request"
-msgid_plural "%d requests"
+msgid "{{count}} request"
+msgid_plural "{{count}} requests"
msgstr[0] ""
msgstr[1] ""
#: app/views/public_body/_body_listing_single.rhtml:21
-msgid "%d request made."
-msgid_plural "%d requests made."
+msgid "{{count}} request made."
+msgid_plural "{{count}} requests made."
msgstr[0] ""
msgstr[1] ""
@@ -187,43 +187,37 @@ msgstr ""
msgid "3. Now check your request"
msgstr ""
-#: app/views/public_body/show.rhtml:56
-msgid "<a class=\"link_button_green\" href=\"{{url}}\">{{text}}</a>"
-msgstr ""
-
#: app/views/request/_after_actions.rhtml:9
-msgid "<a href=\"%s\">Add an annotation</a> (to help the requester or others)"
+msgid "<a href=\"{{url}}\">Add an annotation</a> (to help the requester or others)"
msgstr ""
#: app/views/public_body/list.rhtml:29
-msgid "<a href=\"%s\">Are we missing a public authority?</a>."
+msgid "Are we missing a public authority?"
msgstr ""
#: app/views/request/_sidebar.rhtml:39
-msgid ""
-"<a href=\"%s\">Are you the owner of\n"
-" any commercial copyright on this page?</a>"
+msgid "Are you the owner of any commercial copyright on this page?"
msgstr ""
#: app/views/general/search.rhtml:168
-msgid "<a href=\"%s\">Browse all</a> or <a href=\"%s\">ask us to add one</a>."
+msgid "<a href=\"{{browse_url}}\">Browse all</a> or <a href=\"{{add_url}}\">ask us to add one</a>."
msgstr ""
#: app/views/public_body/list.rhtml:51
-msgid "<a href=\"%s\">Can't find the one you want?</a>"
+msgid "Can't find the one you want?"
msgstr ""
#: app/views/user/show.rhtml:118
-msgid "<a href=\"%s\">Sign in</a> to change password, subscriptions and more ({{user_name}} only)"
+msgid "<a href=\"{{url}}\">Sign in</a> to change password, subscriptions and more ({{user_name}} only)"
msgstr ""
#: app/views/request/_followup.rhtml:66 app/views/request/_followup.rhtml:73
#: app/views/request/show.rhtml:83 app/views/request/show.rhtml:87
-msgid "<a href=\"%s\">details</a>"
+msgid "details"
msgstr ""
#: app/views/request/_followup.rhtml:101
-msgid "<a href=\"%s\">what's that?</a>"
+msgid "what's that?"
msgstr ""
#: app/controllers/request_game_controller.rb:23
@@ -293,11 +287,11 @@ msgid "<p>We're glad you got some of the information that you wanted. If you fou
msgstr ""
#: app/controllers/request_controller.rb:318
-msgid "<p>You do not need to include your email in the request in order to get a reply (<a href=\"%s\">details</a>).</p>"
+msgid "<p>You do not need to include your email in the request in order to get a reply (<a href=\"{{url}}\">details</a>).</p>"
msgstr ""
#: app/controllers/request_controller.rb:316
-msgid "<p>You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (<a href=\"%s\">details</a>).</p>"
+msgid "<p>You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (<a href=\"{{url}}\">details</a>).</p>"
msgstr ""
#: app/controllers/request_controller.rb:324
@@ -327,7 +321,7 @@ msgstr ""
#: app/views/request/new.rhtml:135
msgid ""
"<strong> Can I request information about myself?</strong>\n"
-"\t\t\t<a href=\"%s\">No! (Click here for details)</a>"
+"\t\t\t<a href=\"{{url}}\">No! (Click here for details)</a>"
msgstr ""
#: app/views/general/_advanced_search_tips.rhtml:12
@@ -413,7 +407,7 @@ msgstr ""
#: app/views/request/preview.rhtml:31
msgid ""
"<strong>Privacy note:</strong> If you want to request private information about\n"
-" yourself then <a href=\"%s\">click here</a>."
+" yourself then <a href=\"{{url}}\">click here</a>."
msgstr ""
#: app/views/user/set_crop_profile_photo.rhtml:35
@@ -576,7 +570,7 @@ msgstr ""
#: app/views/request/show_response.rhtml:29
msgid ""
"At the bottom of this page, write a reply to them trying to persuade them to scan it in\n"
-" (<a href=\"%s\">more details</a>)."
+" (<a href=\"{{url}}\">more details</a>)."
msgstr ""
#: app/views/request/upload_response.rhtml:33
@@ -921,7 +915,7 @@ msgstr ""
#: app/views/request/upload_response.rhtml:23
msgid ""
"Enter your response below. You may attach one file (use email, or \n"
-"<a href=\"%s\">contact us</a> if you need more)."
+"<a href=\"{{url}}\">contact us</a> if you need more)."
msgstr ""
#: app/models/info_request.rb:259 app/models/info_request.rb:277
@@ -952,14 +946,14 @@ msgstr ""
msgid ""
"Everything that you enter on this page\n"
" will be <strong>displayed publicly</strong> on\n"
-" this website forever (<a href=\"%s\">why?</a>)."
+" this website forever (<a href=\"{{url}}\">why?</a>)."
msgstr ""
#: app/views/request/new.rhtml:120
msgid ""
"Everything that you enter on this page, including <strong>your name</strong>,\n"
" will be <strong>displayed publicly</strong> on\n"
-" this website forever (<a href=\"%s\">why?</a>)."
+" this website forever (<a href=\"{{url}}\">why?</a>)."
msgstr ""
#: locale/model_attributes.rb:68
@@ -1007,7 +1001,7 @@ msgid "Failed to convert image to a PNG"
msgstr ""
#: app/models/profile_photo.rb:105
-msgid "Failed to convert image to the correct size: at %{cols}x%{rows}, need %{width}x%{height}"
+msgid "Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}"
msgstr ""
#: app/views/general/search.rhtml:117
@@ -1018,7 +1012,7 @@ msgstr ""
msgid ""
"First, type in the <strong>name of the UK public authority</strong> you'd \n"
" like information from. <strong>By law, they have to respond</strong>\n"
-" (<a href=\"%s#%s\">why?</a>)."
+" (<a href=\"{{url}}\">why?</a>)."
msgstr ""
#: locale/model_attributes.rb:88
@@ -1147,7 +1141,7 @@ msgstr ""
msgid ""
"From the request page, try replying to a particular message, rather than sending\n"
" a general followup. If you need to make a general followup, and know\n"
-" an email which will go to the right place, please <a href=\"%s\">send it to us</a>."
+" an email which will go to the right place, please <a href=\"{{url}}\">send it to us</a>."
msgstr ""
#: app/views/request/_correspondence.rhtml:12
@@ -1278,7 +1272,7 @@ msgid "I've received an <strong>error message</strong>"
msgstr ""
#: app/views/public_body/view_email.rhtml:28
-msgid "If the address is wrong, or you know a better address, please <a href=\"%s\">contact us</a>."
+msgid "If the address is wrong, or you know a better address, please <a href=\"{{url}}\">contact us</a>."
msgstr ""
#: app/views/request_mailer/stopped_responses.rhtml:10
@@ -1292,21 +1286,21 @@ msgstr ""
msgid ""
"If you are dissatisfied by the response you got from\n"
" the public authority, you have the right to\n"
-" complain (<a href=\"%s\">details</a>)."
+" complain (<a href=\"{{url}}\">details</a>)."
msgstr ""
#: app/views/user/no_cookies.rhtml:20
-msgid "If you are still having trouble, please <a href=\"%s\">contact us</a>."
+msgid "If you are still having trouble, please <a href=\"{{url}}\">contact us</a>."
msgstr ""
#: app/views/request/hidden.rhtml:15
-msgid "If you are the requester, then you may <a href=\"%s\">sign in</a> to view the request."
+msgid "If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the request."
msgstr ""
#: app/views/request/new.rhtml:123
msgid ""
"If you are thinking of using a pseudonym,\n"
-" please <a href=\"%s\">read this first</a>."
+" please <a href=\"{{url}}\">read this first</a>."
msgstr ""
#: app/views/request/show.rhtml:105
@@ -1497,7 +1491,7 @@ msgid "Joined {{site_name}} in"
msgstr ""
#: app/views/request/new.rhtml:106
-msgid "Keep it <strong>focused</strong>, you'll be more likely to get what you want (<a href=\"%s\">why?</a>)."
+msgid "Keep it <strong>focused</strong>, you'll be more likely to get what you want (<a href=\"{{url}}\">why?</a>)."
msgstr ""
#: app/views/request/_request_filter_form.rhtml:6
@@ -1837,7 +1831,7 @@ msgid "Please"
msgstr ""
#: app/views/user/no_cookies.rhtml:15
-msgid "Please <a href=\"%s\">get in touch</a> with us so we can fix it."
+msgid "Please <a href=\"{{url}}\">get in touch</a> with us so we can fix it."
msgstr ""
#: app/views/request/show.rhtml:52
@@ -1990,7 +1984,7 @@ msgid ""
msgstr ""
#: app/models/outgoing_message.rb:157
-msgid "Please sign at the bottom with your name, or alter the \"%{signoff}\" signature"
+msgid "Please sign at the bottom with your name, or alter the \"{{signoff}}\" signature"
msgstr ""
#: app/views/user/sign.rhtml:8
@@ -2337,8 +2331,8 @@ msgid "Search the site to find what you were looking for."
msgstr ""
#: app/views/public_body/show.rhtml:85
-msgid "Search within the %d Freedom of Information requests to %s"
-msgid_plural "Search within the %d Freedom of Information requests made to %s"
+msgid "Search within the {{count}} Freedom of Information requests to {{public_body_name}}"
+msgid_plural "Search within the {{count}} Freedom of Information requests to {{public_body_name}}"
msgstr[0] ""
msgstr[1] ""
@@ -2680,10 +2674,7 @@ msgid "The request was refused by the public authority"
msgstr ""
#: app/views/request/hidden.rhtml:9
-msgid ""
-"The request you have tried to view has been removed. There are\n"
-"various reasons why we might have done this, sorry we can't be more specific here. Please <a\n"
-" href=\"%s\">contact us</a> if you have any questions."
+msgid "The request you have tried to view has been removed. There are\\nvarious reasons why we might have done this, sorry we can't be more specific here. Please <a\\n href=\"{{url}}\">contact us</a> if you have any questions."
msgstr ""
#: app/views/general/_advanced_search_tips.rhtml:36
@@ -2692,14 +2683,14 @@ msgstr ""
#: app/views/request/_followup.rhtml:59
msgid ""
-"The response to your request has been <strong>delayed</strong>. You can say that, \n"
+"The response to your request has been <strong>delayed</strong>. You can say that,\n"
" by law, the authority should normally have responded\n"
" <strong>promptly</strong> and"
msgstr ""
#: app/views/request/_followup.rhtml:71
msgid ""
-"The response to your request is <strong>long overdue</strong>. You can say that, by \n"
+"The response to your request is <strong>long overdue</strong>. You can say that, by\n"
" law, under all circumstances, the authority should have responded\n"
" by now"
msgstr ""
@@ -2804,15 +2795,9 @@ msgstr ""
msgid "There are {{count}} new annotations on your {{info_request}} request. Follow this link to see what they wrote."
msgstr ""
-#: app/views/public_body/show.rhtml:7
-msgid "There is %d person following this authority"
-msgid_plural "There are %d people following this authority"
-msgstr[0] ""
-msgstr[1] ""
-
#: app/views/request/_sidebar.rhtml:5
-msgid "There is %d person following this request"
-msgid_plural "There are %d people following this request"
+msgid "There is {{count}} person following this request"
+msgid_plural "There are {{count}} people following this request"
msgstr[0] ""
msgstr[1] ""
@@ -2876,7 +2861,7 @@ msgstr ""
#: app/views/request/_hidden_correspondence.rhtml:23
msgid ""
"This comment has been hidden. See annotations to\n"
-" find out why. If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
+" find out why. If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
msgstr ""
#: app/views/request/new.rhtml:63
@@ -2906,7 +2891,7 @@ msgstr ""
#: app/views/request/_hidden_correspondence.rhtml:17
msgid ""
"This outgoing message has been hidden. See annotations to\n"
-"\t\t\t\t\t\tfind out why. If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
+"\t\t\t\t\t\tfind out why. If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
msgstr ""
#: app/views/request/_describe_state.rhtml:44
@@ -2919,14 +2904,14 @@ msgid "This person has made no Freedom of Information requests using this site."
msgstr ""
#: app/views/user/show.rhtml:149
-msgid "This person's %d Freedom of Information request"
-msgid_plural "This person's %d Freedom of Information requests"
+msgid "This person's {{count}} Freedom of Information request"
+msgid_plural "This person's {{count}} Freedom of Information requests"
msgstr[0] ""
msgstr[1] ""
#: app/views/user/show.rhtml:179
-msgid "This person's %d annotation"
-msgid_plural "This person's %d annotations"
+msgid "This person's {{count}} annotation"
+msgid_plural "This person's {{count}} annotations"
msgstr[0] ""
msgstr[1] ""
@@ -2965,7 +2950,7 @@ msgstr ""
#: app/views/request/show.rhtml:11
msgid ""
"This request is hidden, so that only you the requester can see it. Please\n"
-" <a href=\"%s\">contact us</a> if you are not sure why."
+" <a href=\"{{url}}\">contact us</a> if you are not sure why."
msgstr ""
#: app/views/request/_describe_state.rhtml:7
@@ -2976,7 +2961,7 @@ msgstr ""
#: app/views/request/_hidden_correspondence.rhtml:10
msgid ""
"This response has been hidden. See annotations to find out why.\n"
-" If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
+" If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
msgstr ""
#: app/views/request/details.rhtml:6
@@ -3202,7 +3187,7 @@ msgstr ""
msgid ""
"Unfortunately we don't know the FOI\n"
"email address for that authority, so we can't validate this.\n"
-"Please <a href=\"%s\">contact us</a> to sort it out."
+"Please <a href=\"{{url}}\">contact us</a> to sort it out."
msgstr ""
#: app/views/request/new_bad_contact.rhtml:5
@@ -3439,7 +3424,7 @@ msgid ""
msgstr ""
#: app/views/request/new_please_describe.rhtml:16
-msgid "When you're done, <strong>come back here</strong>, <a href=\"%s\">reload this page</a> and file your new request."
+msgid "When you're done, <strong>come back here</strong>, <a href=\"{{url}}\">reload this page</a> and file your new request."
msgstr ""
#: app/views/request/show_response.rhtml:13
@@ -3571,7 +3556,7 @@ msgstr ""
msgid ""
"You may be able to find\n"
" one on their website, or by phoning them up and asking. If you manage\n"
-" to find one, then please <a href=\"%s\">send it to us</a>."
+" to find one, then please <a href=\"{{url}}\">send it to us</a>."
msgstr ""
#: app/views/request/new_bad_contact.rhtml:6
@@ -3642,24 +3627,19 @@ msgid "You've now cleared your profile photo"
msgstr ""
#: app/views/user/show.rhtml:149
-msgid "Your %d Freedom of Information request"
-msgid_plural "Your %d Freedom of Information requests"
+msgid "Your {{count}} Freedom of Information request"
+msgid_plural "Your {{count}} Freedom of Information requests"
msgstr[0] ""
msgstr[1] ""
#: app/views/user/show.rhtml:179
-msgid "Your %d annotation"
-msgid_plural "Your %d annotations"
+msgid "Your {{count}} annotation"
+msgid_plural "Your {{count}} annotations"
msgstr[0] ""
msgstr[1] ""
#: app/views/user/_signup.rhtml:22
-msgid ""
-"Your <strong>name will appear publicly</strong> \n"
-" (<a href=\"%s\">why?</a>)\n"
-" on this website and in search engines. If you\n"
-" are thinking of using a pseudonym, please \n"
-" <a href=\"%s\">read this first</a>."
+msgid "Your <strong>name will appear publicly</strong>\\n (<a href=\"{{why_url}}\">why?</a>)\\n on this website and in search engines. If you\\n are thinking of using a pseudonym, please\\n <a href=\"{{help_url}}\">read this first</a>."
msgstr ""
#: app/views/user/show.rhtml:172
@@ -3682,7 +3662,7 @@ msgid "Your email subscriptions"
msgstr ""
#: app/controllers/request_controller.rb:598
-msgid "Your follow up has not been sent because this request has been stopped to prevent spam. Please <a href=\"%s\">contact us</a> if you really want to send a follow up message."
+msgid "Your follow up has not been sent because this request has been stopped to prevent spam. Please <a href=\"{{url}}\">contact us</a> if you really want to send a follow up message."
msgstr ""
#: app/controllers/request_controller.rb:626
@@ -3712,7 +3692,7 @@ msgstr ""
#: app/views/request/preview.rhtml:8
msgid ""
"Your name, request and any responses will appear in <strong>search engines</strong>\n"
-" (<a href=\"%s\">details</a>)."
+" (<a href=\"{{url}}\">details</a>)."
msgstr ""
#: app/views/user/_signup.rhtml:18
@@ -3746,7 +3726,7 @@ msgid "Your request:"
msgstr ""
#: app/views/request/upload_response.rhtml:8
-msgid "Your response will <strong>appear on the Internet</strong>, <a href=\"%s\">read why</a> and answers to other questions."
+msgid "Your response will <strong>appear on the Internet</strong>, <a href=\"{{url}}\">read why</a> and answers to other questions."
msgstr ""
#: app/views/comment/new.rhtml:63
@@ -3990,7 +3970,7 @@ msgid ""
"no longer exists. If you are trying to make\n"
" From the request page, try replying to a particular message, rather than sending\n"
" a general followup. If you need to make a general followup, and know\n"
-" an email which will go to the right place, please <a href=\"%s\">send it to us</a>."
+" an email which will go to the right place, please <a href=\"{{url}}\">send it to us</a>."
msgstr ""
#: app/views/request/show.rhtml:72
diff --git a/spec/fixtures/locale/es/app.po b/spec/fixtures/locale/es/app.po
index dbc7ab65b..d45d9b3b1 100644
--- a/spec/fixtures/locale/es/app.po
+++ b/spec/fixtures/locale/es/app.po
@@ -35,9 +35,9 @@ msgstr " Esto aparecerá en tu perfil de {{site_name}}, para facilitar\n
#: app/views/comment/_comment_form.rhtml:16
msgid ""
-" (<strong>no ranty</strong> politics, read our <a href=\"%s\">moderation "
+" (<strong>no ranty</strong> politics, read our <a href=\"{{url}}\">moderation "
"policy</a>)"
-msgstr " (<strong>sin ataques políticos</strong>, lea nuestra <a href=\"%s\">política de moderación</a>)"
+msgstr " (<strong>sin ataques políticos</strong>, lea nuestra <a href=\"{{url}}\">política de moderación</a>)"
#: app/views/request/upload_response.rhtml:40
msgid ""
@@ -80,9 +80,9 @@ msgstr " Ideas sobre <strong>qué otra información pedir</strong> que el organi
#: app/views/public_body/view_email.rhtml:30
msgid ""
-" If you know the address to use, then please <a href=\"%s\">send it to us</a>.\n"
+" If you know the address to use, then please <a href=\"{{url}}\">send it to us</a>.\n"
" You may be able to find the address on their website, or by phoning them up and asking."
-msgstr " Si conoces la dirección a utilizar, entonces por favor <a href=\"%s\">envíanosla</a>.\n Puede que la encuentres en su página web, o llamándoles por teléfono y preguntando."
+msgstr " Si conoces la dirección a utilizar, entonces por favor <a href=\"{{url}}\">envíanosla</a>.\n Puede que la encuentres en su página web, o llamándoles por teléfono y preguntando."
#: app/views/user/set_profile_about_me.rhtml:26
msgid ""
@@ -140,22 +140,22 @@ msgid " when you send this message."
msgstr " cuando envió este mensaje."
#: app/views/public_body/show.rhtml:87
-msgid "%d Freedom of Information request to %s"
-msgid_plural "%d Freedom of Information requests to %s"
-msgstr[0] "%d solicitud de información a %s"
-msgstr[1] "%d solicitudes de información a %s"
+msgid "{{count}} Freedom of Information request to {{public_body_name}}"
+msgid_plural "{{count}} Freedom of Information requests to {{public_body_name}}"
+msgstr[0] "{{count}} solicitud de información a {{public_body_name}}"
+msgstr[1] "{{count}} solicitudes de información a {{public_body_name}}"
#: app/views/general/frontpage.rhtml:43
-msgid "%d request"
-msgid_plural "%d requests"
-msgstr[0] "%d solicitud"
-msgstr[1] "%d solicitudes"
+msgid "{{count}} request"
+msgid_plural "{{count}} requests"
+msgstr[0] "{{count}} solicitud"
+msgstr[1] "{{count}} solicitudes"
#: app/views/public_body/_body_listing_single.rhtml:21
-msgid "%d request made."
-msgid_plural "%d requests made."
-msgstr[0] "%d solicitud enviada."
-msgstr[1] "%d solicitudes enviadas."
+msgid "{{count}} request made."
+msgid_plural "{{count}} requests made."
+msgstr[0] "{{count}} solicitud enviada."
+msgstr[1] "{{count}} solicitudes enviadas."
#: app/views/request/new.rhtml:92
msgid "'Crime statistics by ward level for Wales'"
@@ -204,46 +204,40 @@ msgstr "2. Solicite información"
msgid "3. Now check your request"
msgstr "3. Revisa tu solicitud"
-#: app/views/public_body/show.rhtml:56
-msgid "<a class=\"link_button_green\" href=\"{{url}}\">{{text}}</a>"
-msgstr "<a class=\"link_button_green\" href=\"{{url}}\">{{text}}</a>"
-
#: app/views/request/_after_actions.rhtml:9
-msgid "<a href=\"%s\">Add an annotation</a> (to help the requester or others)"
-msgstr "<a href=\"%s\">Añade un comentario</a> (para ayudar al solicitante o a otros)"
+msgid "<a href=\"{{url}}\">Add an annotation</a> (to help the requester or others)"
+msgstr "<a href=\"{{url}}\">Añade un comentario</a> (para ayudar al solicitante o a otros)"
#: app/views/public_body/list.rhtml:29
-msgid "<a href=\"%s\">Are we missing a public authority?</a>."
-msgstr "<a href=\"%s\">¿Nos falta algún organismo público?</a>."
+msgid "Are we missing a public authority?"
+msgstr "¿Nos falta algún organismo público?."
#: app/views/request/_sidebar.rhtml:39
-msgid ""
-"<a href=\"%s\">Are you the owner of\n"
-" any commercial copyright on this page?</a>"
-msgstr "<a href=\"%s\">¿Posee el copyright\n de alguna información de esta página?</a>"
+msgid "Are you the owner of any commercial copyright on this page?"
+msgstr "¿Posee el copyright de alguna información de esta página?"
#: app/views/general/search.rhtml:168
-msgid "<a href=\"%s\">Browse all</a> or <a href=\"%s\">ask us to add one</a>."
-msgstr "<a href=\"%s\">Ver todas</a> o <a href=\"%s\">pídanos que añadamos una</a>."
+msgid "<a href=\"{{browse_url}}\">Browse all</a> or <a href=\"{{add_url}}\">ask us to add one</a>."
+msgstr "<a href=\"{{browse_url}}\">Ver todas</a> o <a href=\"{{add_url}}\">pídanos que añadamos una</a>."
#: app/views/public_body/list.rhtml:51
-msgid "<a href=\"%s\">Can't find the one you want?</a>"
-msgstr "<a href=\"%s\">¿No encuentra el que busca?</a>"
+msgid "Can't find the one you want?"
+msgstr "¿No encuentra el que busca?"
#: app/views/user/show.rhtml:118
msgid ""
-"<a href=\"%s\">Sign in</a> to change password, subscriptions and more "
+"<a href=\"{{url}}\">Sign in</a> to change password, subscriptions and more "
"({{user_name}} only)"
-msgstr "<a href=\"%s\">Abre una sesión</a> para cambiar tu contraseña, suscripciones... (sólo {{user_name}})"
+msgstr "<a href=\"{{url}}\">Abre una sesión</a> para cambiar tu contraseña, suscripciones... (sólo {{user_name}})"
#: app/views/request/_followup.rhtml:66 app/views/request/_followup.rhtml:73
#: app/views/request/show.rhtml:83 app/views/request/show.rhtml:87
-msgid "<a href=\"%s\">details</a>"
-msgstr "<a href=\"%s\">detalles</a>"
+msgid "details"
+msgstr "detalles"
#: app/views/request/_followup.rhtml:101
-msgid "<a href=\"%s\">what's that?</a>"
-msgstr "<a href=\"%s\">¿Qué es eso?</a>"
+msgid "what's that?"
+msgstr "¿Qué es eso?"
#: app/controllers/request_game_controller.rb:23
msgid ""
@@ -382,8 +376,8 @@ msgstr "<small>Si usas correo web o tiene filtros \"anti spam\", por favor compr
#: app/views/request/new.rhtml:135
msgid ""
"<strong> Can I request information about myself?</strong>\n"
-"\t\t\t<a href=\"%s\">No! (Click here for details)</a>"
-msgstr "<strong> ¿Puedo pedir información sobre mí?</strong>\n\t\t\t<a href=\"%s\">¡No! (Pulse aquí para más detalles)</a>"
+"\t\t\t<a href=\"{{url}}\">No! (Click here for details)</a>"
+msgstr "<strong> ¿Puedo pedir información sobre mí?</strong>\n\t\t\t<a href=\"{{url}}\">¡No! (Pulse aquí para más detalles)</a>"
#: app/views/general/_advanced_search_tips.rhtml:12
msgid ""
@@ -486,8 +480,8 @@ msgstr "<strong>Nota:</strong> Te estás enviando un mensaje a ti mismo, suponem
#: app/views/request/preview.rhtml:31
msgid ""
"<strong>Privacy note:</strong> If you want to request private information about\n"
-" yourself then <a href=\"%s\">click here</a>."
-msgstr "<strong>Nota sobre privacidad:</strong> Si quiere solicitar información privada\n sobre sí mismo entonces <a href=\"%s\">siga este enlace</a>."
+" yourself then <a href=\"{{url}}\">click here</a>."
+msgstr "<strong>Nota sobre privacidad:</strong> Si quiere solicitar información privada\n sobre sí mismo entonces <a href=\"{{url}}\">siga este enlace</a>."
#: app/views/user/set_crop_profile_photo.rhtml:35
msgid ""
@@ -667,8 +661,8 @@ msgstr "Pide documentos o información <strong>específica</strong>, esta web no
#: app/views/request/show_response.rhtml:29
msgid ""
"At the bottom of this page, write a reply to them trying to persuade them to scan it in\n"
-" (<a href=\"%s\">more details</a>)."
-msgstr "Al final de esta página, escribe una respuesta intentando convencerles de que lo escaneen\n (<a href=\"%s\">más detalles</a>)."
+" (<a href=\"{{url}}\">more details</a>)."
+msgstr "Al final de esta página, escribe una respuesta intentando convencerles de que lo escaneen\n (<a href=\"{{url}}\">más detalles</a>)."
#: app/views/request/upload_response.rhtml:33
msgid "Attachment (optional):"
@@ -1037,8 +1031,8 @@ msgstr "Introduzca las palabras que desee separadas por espacio, es decir <stron
#: app/views/request/upload_response.rhtml:23
msgid ""
"Enter your response below. You may attach one file (use email, or \n"
-"<a href=\"%s\">contact us</a> if you need more)."
-msgstr "Escribe tu solicitud a continuación. Puedes adjuntar un fichero (manda un correo,\n o <a href=\"%s\">contáctanos</a>, si necesita más)."
+"<a href=\"{{url}}\">contact us</a> if you need more)."
+msgstr "Escribe tu solicitud a continuación. Puedes adjuntar un fichero (manda un correo,\n o <a href=\"{{url}}\">contáctanos</a>, si necesita más)."
#: app/models/info_request.rb:259 app/models/info_request.rb:277
msgid "Environmental Information Regulations"
@@ -1068,15 +1062,15 @@ msgstr "Historial de eventos"
msgid ""
"Everything that you enter on this page\n"
" will be <strong>displayed publicly</strong> on\n"
-" this website forever (<a href=\"%s\">why?</a>)."
-msgstr "Todo lo que escriba en esta página \n estará <strong>disponible públicamente</strong> en\n está web para siempre (<a href=\"%s\">¿por qué?</a>)."
+" this website forever (<a href=\"{{url}}\">why?</a>)."
+msgstr "Todo lo que escriba en esta página \n estará <strong>disponible públicamente</strong> en\n está web para siempre (<a href=\"{{url}}\">¿por qué?</a>)."
#: app/views/request/new.rhtml:120
msgid ""
"Everything that you enter on this page, including <strong>your name</strong>,\n"
" will be <strong>displayed publicly</strong> on\n"
-" this website forever (<a href=\"%s\">why?</a>)."
-msgstr "Todo lo que escribas en esta página, incluyendo <strong>tu nombre</strong>, \n estará <strong>disponible públicamente</strong> en\n está web para siempre (<a href=\"%s\">¿por qué?</a>)."
+" this website forever (<a href=\"{{url}}\">why?</a>)."
+msgstr "Todo lo que escribas en esta página, incluyendo <strong>tu nombre</strong>, \n estará <strong>disponible públicamente</strong> en\n está web para siempre (<a href=\"{{url}}\">¿por qué?</a>)."
#: locale/model_attributes.rb:68
msgid "EximLogDone|Filename"
@@ -1123,10 +1117,8 @@ msgid "Failed to convert image to a PNG"
msgstr "Error al convertir la imagen a PNG"
#: app/models/profile_photo.rb:105
-msgid ""
-"Failed to convert image to the correct size: at %{cols}x%{rows}, need "
-"%{width}x%{height}"
-msgstr "Error al convertir la imagen al tamaño adecuado: es %{cols}x%{rows}, debería ser %{width}x%{height}"
+msgid "Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}"
+msgstr "Error al convertir la imagen al tamaño adecuado: es {{cols}}x{{rows}}, debería ser {{width}}x{{height}}"
#: app/views/general/search.rhtml:117
msgid "Filter"
@@ -1136,8 +1128,8 @@ msgstr "Filtrar"
msgid ""
"First, type in the <strong>name of the UK public authority</strong> you'd \n"
" like information from. <strong>By law, they have to respond</strong>\n"
-" (<a href=\"%s#%s\">why?</a>)."
-msgstr "Primero, escribe el <strong>nombre de la institución</strong> a la que quieres pedir información. <strong>Están obligados a responder</strong> (<a href=\"%s#%s\">¿por qué?</a>)."
+" (<a href=\"{{url}}\">why?</a>)."
+msgstr "Primero, escribe el <strong>nombre de la institución</strong> a la que quieres pedir información. <strong>Están obligados a responder</strong> (<a href=\"{{url}}\">¿por qué?</a>)."
#: locale/model_attributes.rb:88
msgid "FoiAttachment|Charset"
@@ -1272,8 +1264,8 @@ msgstr "Solicitudes de información a"
msgid ""
"From the request page, try replying to a particular message, rather than sending\n"
" a general followup. If you need to make a general followup, and know\n"
-" an email which will go to the right place, please <a href=\"%s\">send it to us</a>."
-msgstr "Desde la página de la solicitud, intenta responder a un mensaje en concreto, en vez de\n responder a la solicitud en general. Si necesitas hacerlo y tienes una dirección de\n correo válida, por favor <a href=\"%s\">mándanosla</a>."
+" an email which will go to the right place, please <a href=\"{{url}}\">send it to us</a>."
+msgstr "Desde la página de la solicitud, intenta responder a un mensaje en concreto, en vez de\n responder a la solicitud en general. Si necesitas hacerlo y tienes una dirección de\n correo válida, por favor <a href=\"{{url}}\">mándanosla</a>."
#: app/views/request/_correspondence.rhtml:12
#: app/views/request/_correspondence.rhtml:36
@@ -1409,8 +1401,8 @@ msgstr "He recibido un <strong>mensaje de error</strong>"
#: app/views/public_body/view_email.rhtml:28
msgid ""
"If the address is wrong, or you know a better address, please <a "
-"href=\"%s\">contact us</a>."
-msgstr "Si la dirección es incorrecta, o conoce una más actualizada, por favor <a href=\"%s\">contáctenos</a>."
+"href=\"{{url}}\">contact us</a>."
+msgstr "Si la dirección es incorrecta, o conoce una más actualizada, por favor <a href=\"{{url}}\">contáctenos</a>."
#: app/views/request_mailer/stopped_responses.rhtml:10
msgid ""
@@ -1423,24 +1415,24 @@ msgstr "Si no es correcto, o te gustaría enviar una respuesta a la solicitud\no
msgid ""
"If you are dissatisfied by the response you got from\n"
" the public authority, you have the right to\n"
-" complain (<a href=\"%s\">details</a>)."
-msgstr "Si no estás satisfecho con la respuesta que has recibido del\n organismo público, tienes derecho a\n apelar (<a href=\"%s\">detalles</a>)."
+" complain (<a href=\"{{url}}\">details</a>)."
+msgstr "Si no estás satisfecho con la respuesta que has recibido del\n organismo público, tienes derecho a\n apelar (<a href=\"{{url}}\">detalles</a>)."
#: app/views/user/no_cookies.rhtml:20
-msgid "If you are still having trouble, please <a href=\"%s\">contact us</a>."
-msgstr "Si aún tienes problemas, por favor <a href=\"%s\">contáctanos</a>."
+msgid "If you are still having trouble, please <a href=\"{{url}}\">contact us</a>."
+msgstr "Si aún tienes problemas, por favor <a href=\"{{url}}\">contáctanos</a>."
#: app/views/request/hidden.rhtml:15
msgid ""
-"If you are the requester, then you may <a href=\"%s\">sign in</a> to view "
+"If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view "
"the request."
-msgstr "Si la solicitud es tuya, puedes <a href=\"%s\">abrir una sesión</a> para verla."
+msgstr "Si la solicitud es tuya, puedes <a href=\"{{url}}\">abrir una sesión</a> para verla."
#: app/views/request/new.rhtml:123
msgid ""
"If you are thinking of using a pseudonym,\n"
-" please <a href=\"%s\">read this first</a>."
-msgstr "Si está pensando en utilizar un pseudónimo,\n por favor <a href=\"%s\">lea esto primero</a>."
+" please <a href=\"{{url}}\">read this first</a>."
+msgstr "Si está pensando en utilizar un pseudónimo,\n por favor <a href=\"{{url}}\">lea esto primero</a>."
#: app/views/request/show.rhtml:105
msgid "If you are {{user_link}}, please"
@@ -1639,8 +1631,8 @@ msgstr "Registrado en {{site_name}} el"
#: app/views/request/new.rhtml:106
msgid ""
"Keep it <strong>focused</strong>, you'll be more likely to get what you want"
-" (<a href=\"%s\">why?</a>)."
-msgstr "Sea <strong>específico</strong>, tendrá más probabilidades de conseguir lo que quiere (<a href=\"%s\">¿por qué?</a>)."
+" (<a href=\"{{url}}\">why?</a>)."
+msgstr "Sea <strong>específico</strong>, tendrá más probabilidades de conseguir lo que quiere (<a href=\"{{url}}\">¿por qué?</a>)."
#: app/views/request/_request_filter_form.rhtml:6
msgid "Keywords"
@@ -1990,8 +1982,8 @@ msgid "Please"
msgstr "Por favor"
#: app/views/user/no_cookies.rhtml:15
-msgid "Please <a href=\"%s\">get in touch</a> with us so we can fix it."
-msgstr "Por favor <a href=\"%s\">contacta</a> con nosotros para que podamos arreglarlo."
+msgid "Please <a href=\"{{url}}\">get in touch</a> with us so we can fix it."
+msgstr "Por favor <a href=\"{{url}}\">contacta</a> con nosotros para que podamos arreglarlo."
#: app/views/request/show.rhtml:52
msgid ""
@@ -2154,10 +2146,8 @@ msgid ""
msgstr "Por favor elije estas solicitudes una a una, y <strong>haz que se sepa</strong>\nsi han tenido éxito o no."
#: app/models/outgoing_message.rb:157
-msgid ""
-"Please sign at the bottom with your name, or alter the \"%{signoff}\" "
-"signature"
-msgstr "Por favor, firma con tu nombre en la parte inferior, o cambia la firma \"%{signoff}\""
+msgid "Please sign at the bottom with your name, or alter the \"{{signoff}}\" signature"
+msgstr "Por favor, firma con tu nombre en la parte inferior, o cambia la firma \"{{signoff}}\""
#: app/views/user/sign.rhtml:8
msgid "Please sign in as "
@@ -2519,10 +2509,10 @@ msgid "Search the site to find what you were looking for."
msgstr "Buscar en esta web para encontrar lo que busca."
#: app/views/public_body/show.rhtml:85
-msgid "Search within the %d Freedom of Information requests to %s"
-msgid_plural "Search within the %d Freedom of Information requests made to %s"
-msgstr[0] "Busca en la %d solicitud de información hecha a %s"
-msgstr[1] "Busca en las %d solicitudes de información hechas a %s"
+msgid "Search within the {{count}} Freedom of Information requests to {{public_body_name}}"
+msgid_plural "Search within the {{count}} Freedom of Information requests to {{public_body_name}}"
+msgstr[0] "Busca en la {{count}} solicitud de información hecha a {{public_body_name}}"
+msgstr[1] "Busca en las {{count}} solicitudes de información hechas a {{public_body_name}}"
#: app/views/user/show.rhtml:132
msgid "Search your contributions"
@@ -2878,11 +2868,8 @@ msgid "The request was refused by the public authority"
msgstr "La solicitud ha sido rechazada por el organismo"
#: app/views/request/hidden.rhtml:9
-msgid ""
-"The request you have tried to view has been removed. There are\n"
-"various reasons why we might have done this, sorry we can't be more specific here. Please <a\n"
-" href=\"%s\">contact us</a> if you have any questions."
-msgstr "La solicitud que has intentado ver ha sido eliminada. Hay\nvarios posibles motivos para esto, pero no podemos ser más específicos aquí. Por favor <a\n href=\"%s\">contáctanos</a> si tiene cualquier pregunta."
+msgid "The request you have tried to view has been removed. There are\\nvarious reasons why we might have done this, sorry we can't be more specific here. Please <a\\n href=\"{{url}}\">contact us</a> if you have any questions."
+msgstr "La solicitud que has intentado ver ha sido eliminada. Hay\nvarios posibles motivos para esto, pero no podemos ser más específicos aquí. Por favor <a\n href=\"{{url}}\">contáctanos</a> si tiene cualquier pregunta."
#: app/views/general/_advanced_search_tips.rhtml:36
msgid "The requester has abandoned this request for some reason"
@@ -2890,14 +2877,14 @@ msgstr "El creador de la solicitud la ha cancelado por algún motivo"
#: app/views/request/_followup.rhtml:59
msgid ""
-"The response to your request has been <strong>delayed</strong>. You can say that, \n"
+"The response to your request has been <strong>delayed</strong>. You can say that,\n"
" by law, the authority should normally have responded\n"
" <strong>promptly</strong> and"
msgstr "La respuesta a tu solicitud ha sido <strong>retrasada</strong>.\n Por ley, el organismo debería normalmente haber respondido\n <strong>rápidamente</strong> y"
#: app/views/request/_followup.rhtml:71
msgid ""
-"The response to your request is <strong>long overdue</strong>. You can say that, by \n"
+"The response to your request is <strong>long overdue</strong>. You can say that, by\n"
" law, under all circumstances, the authority should have responded\n"
" by now"
msgstr "La respuesta a tu solicitud ha sido <strong>muy retrasada</strong>.\n Por ley, bajo cualquier circunstancia, el organismo ya debería\n haber respondido"
@@ -3016,17 +3003,11 @@ msgid ""
" this link to see what they wrote."
msgstr "Hay {{count}} comentarios en tu solicitud {{info_request}}. Sigue este enlace para leer lo que dicen."
-#: app/views/public_body/show.rhtml:7
-msgid "There is %d person following this authority"
-msgid_plural "There are %d people following this authority"
-msgstr[0] "Hay %d persona siguiendo a este organismo."
-msgstr[1] "Hay %d personas siguiendo a este organismo."
-
#: app/views/request/_sidebar.rhtml:5
-msgid "There is %d person following this request"
-msgid_plural "There are %d people following this request"
-msgstr[0] "Hay %d persona siguiendo esta solicitud."
-msgstr[1] "Hay %d personas siguiendo esta solicitud."
+msgid "There is {{count}} person following this request"
+msgid_plural "There are {{count}} people following this request"
+msgstr[0] "Hay {{count}} persona siguiendo esta solicitud."
+msgstr[1] "Hay {{count}} personas siguiendo esta solicitud."
#: app/views/user/show.rhtml:8
msgid ""
@@ -3099,8 +3080,8 @@ msgstr "Este organismo ya no existe, no pueden realizarse solicitudes de informa
#: app/views/request/_hidden_correspondence.rhtml:23
msgid ""
"This comment has been hidden. See annotations to\n"
-" find out why. If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
-msgstr "Este respuesta está oculta. Revisa los comentarios\n para descubrir por qué. Si es tu solicitud, <a href=\"%s\">abre una sesión</a> para ver la respuesta."
+" find out why. If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
+msgstr "Este respuesta está oculta. Revisa los comentarios\n para descubrir por qué. Si es tu solicitud, <a href=\"{{url}}\">abre una sesión</a> para ver la respuesta."
#: app/views/request/new.rhtml:63
msgid ""
@@ -3140,8 +3121,8 @@ msgstr "Esta es tu solicitud, por lo que recibirás correos automáticamente cua
#: app/views/request/_hidden_correspondence.rhtml:17
msgid ""
"This outgoing message has been hidden. See annotations to\n"
-"\t\t\t\t\t\tfind out why. If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
-msgstr "Este mensaje está oculto. Lee los comentarios\n\t\t\t\t\t\tpara descubrir por qué. Si es tu solicitud, <a href=\"%s\">abra una sesión</a> para ver la respuesta."
+"\t\t\t\t\t\tfind out why. If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
+msgstr "Este mensaje está oculto. Lee los comentarios\n\t\t\t\t\t\tpara descubrir por qué. Si es tu solicitud, <a href=\"{{url}}\">abra una sesión</a> para ver la respuesta."
#: app/views/request/_describe_state.rhtml:44
#: app/views/request/_other_describe_state.rhtml:40
@@ -3154,16 +3135,16 @@ msgid ""
msgstr "Esta persona no ha realizado solicitudes de información usando esta web."
#: app/views/user/show.rhtml:149
-msgid "This person's %d Freedom of Information request"
-msgid_plural "This person's %d Freedom of Information requests"
-msgstr[0] "Tu %d solicitud de información"
-msgstr[1] "Tus %d solicitudes de información"
+msgid "This person's {{count}} Freedom of Information request"
+msgid_plural "This person's {{count}} Freedom of Information requests"
+msgstr[0] "Tu {{count}} solicitud de información"
+msgstr[1] "Tus {{count}} solicitudes de información"
#: app/views/user/show.rhtml:179
-msgid "This person's %d annotation"
-msgid_plural "This person's %d annotations"
-msgstr[0] "Tu %d comentario"
-msgstr[1] "Tus %d comentarios"
+msgid "This person's {{count}} annotation"
+msgid_plural "This person's {{count}} annotations"
+msgstr[0] "Tu {{count}} comentario"
+msgstr[1] "Tus {{count}} comentarios"
#: app/views/user/show.rhtml:172
msgid "This person's annotations"
@@ -3204,8 +3185,8 @@ msgstr "Esta solicitud tiene visibilidad 'oculta'. Puedes verla sólo porque est
#: app/views/request/show.rhtml:11
msgid ""
"This request is hidden, so that only you the requester can see it. Please\n"
-" <a href=\"%s\">contact us</a> if you are not sure why."
-msgstr "Esta solicitud está oculta, por lo que sólo tú como creador puedes verla. Por favor\n <a href=\"%s\">contáctanos</a> si no estás seguro de por qué."
+" <a href=\"{{url}}\">contact us</a> if you are not sure why."
+msgstr "Esta solicitud está oculta, por lo que sólo tú como creador puedes verla. Por favor\n <a href=\"{{url}}\">contáctanos</a> si no estás seguro de por qué."
#: app/views/request/_describe_state.rhtml:7
#: app/views/request/_other_describe_state.rhtml:10
@@ -3215,8 +3196,8 @@ msgstr "Esta solicitud está todavía en proceso:"
#: app/views/request/_hidden_correspondence.rhtml:10
msgid ""
"This response has been hidden. See annotations to find out why.\n"
-" If you are the requester, then you may <a href=\"%s\">sign in</a> to view the response."
-msgstr "Este respuesta está oculta. Revisa los comentarios\n para descubrir por qué. Si es tu solicitud, <a href=\"%s\">abre una sesión</a> para ver la respuesta."
+" If you are the requester, then you may <a href=\"{{url}}\">sign in</a> to view the response."
+msgstr "Este respuesta está oculta. Revisa los comentarios\n para descubrir por qué. Si es tu solicitud, <a href=\"{{url}}\">abre una sesión</a> para ver la respuesta."
#: app/views/request/details.rhtml:6
msgid ""
@@ -3450,8 +3431,8 @@ msgstr "Se encontró un tipo de resultado inesperado "
msgid ""
"Unfortunately we don't know the FOI\n"
"email address for that authority, so we can't validate this.\n"
-"Please <a href=\"%s\">contact us</a> to sort it out."
-msgstr "Desgraciadamente no tenemos la dirección\nde correo para este organismo, así que no podemos validarlo.\nPor favor <a href=\"%s\">contáctenos</a> para arreglarlo."
+"Please <a href=\"{{url}}\">contact us</a> to sort it out."
+msgstr "Desgraciadamente no tenemos la dirección\nde correo para este organismo, así que no podemos validarlo.\nPor favor <a href=\"{{url}}\">contáctenos</a> para arreglarlo."
#: app/views/request/new_bad_contact.rhtml:5
msgid ""
@@ -3699,9 +3680,9 @@ msgstr "Cuando reciba la respuesta en papel, por favor ayude\n a que
#: app/views/request/new_please_describe.rhtml:16
msgid ""
-"When you're done, <strong>come back here</strong>, <a href=\"%s\">reload "
+"When you're done, <strong>come back here</strong>, <a href=\"{{url}}\">reload "
"this page</a> and file your new request."
-msgstr "Cuando esté listo, <strong>vuelva aquí</strong>, <a href=\"%s\">recargue esta página</a> y cree una nueva solicitud."
+msgstr "Cuando esté listo, <strong>vuelva aquí</strong>, <a href=\"{{url}}\">recargue esta página</a> y cree una nueva solicitud."
#: app/views/request/show_response.rhtml:13
msgid "Which of these is happening?"
@@ -3840,8 +3821,8 @@ msgstr "Puede <strong>adjuntar ficheros</strong>. Si quiere adjuntar un fichero\
msgid ""
"You may be able to find\n"
" one on their website, or by phoning them up and asking. If you manage\n"
-" to find one, then please <a href=\"%s\">send it to us</a>."
-msgstr "Puede que encuentres una\n en su página web, o preguntando por teléfono. Si la consigues\n por favor <a href=\"%s\">envíanosla</a>."
+" to find one, then please <a href=\"{{url}}\">send it to us</a>."
+msgstr "Puede que encuentres una\n en su página web, o preguntando por teléfono. Si la consigues\n por favor <a href=\"{{url}}\">envíanosla</a>."
#: app/views/request/new_bad_contact.rhtml:6
msgid ""
@@ -3914,25 +3895,20 @@ msgid "You've now cleared your profile photo"
msgstr "Has borrado la foto de tu perfil"
#: app/views/user/show.rhtml:149
-msgid "Your %d Freedom of Information request"
-msgid_plural "Your %d Freedom of Information requests"
-msgstr[0] "Tu %d solicitud de información"
-msgstr[1] "Tus %d solicitudes de información"
+msgid "Your {{count}} Freedom of Information request"
+msgid_plural "Your {{count}} Freedom of Information requests"
+msgstr[0] "Tu {{count}} solicitud de información"
+msgstr[1] "Tus {{count}} solicitudes de información"
#: app/views/user/show.rhtml:179
-msgid "Your %d annotation"
-msgid_plural "Your %d annotations"
-msgstr[0] "Tu %d comentario"
-msgstr[1] "Tus %d comentarios"
+msgid "Your {{count}} annotation"
+msgid_plural "Your {{count}} annotations"
+msgstr[0] "Tu {{count}} comentario"
+msgstr[1] "Tus {{count}} comentarios"
#: app/views/user/_signup.rhtml:22
-msgid ""
-"Your <strong>name will appear publicly</strong> \n"
-" (<a href=\"%s\">why?</a>)\n"
-" on this website and in search engines. If you\n"
-" are thinking of using a pseudonym, please \n"
-" <a href=\"%s\">read this first</a>."
-msgstr "<strong>Tu nombre aparecerá públicamente</strong> \n (<a href=\"%s\">¿por qué?</a>)\n en esta web y en motores de búsqueda. Si estás\n pensando en utilizar un seudónimo, por favor \n <a href=\"%s\">lee esto primero</a>."
+msgid "Your <strong>name will appear publicly</strong> \\n (<a href=\"{{why_url}}\">why?</a>)\\n on this website and in search engines. If you\\n are thinking of using a pseudonym, please \\n <a href=\"{{help_url}}\">read this first</a>."
+msgstr "<strong>Tu nombre aparecerá públicamente</strong> \n (<a href=\"{{why_url}}\">¿por qué?</a>)\n en esta web y en motores de búsqueda. Si estás\n pensando en utilizar un seudónimo, por favor \n <a href=\"{{help_url}}\">lee esto primero</a>."
#: app/views/user/show.rhtml:172
msgid "Your annotations"
@@ -3956,9 +3932,9 @@ msgstr "Tus suscripciones de correo"
#: app/controllers/request_controller.rb:598
msgid ""
"Your follow up has not been sent because this request has been stopped to "
-"prevent spam. Please <a href=\"%s\">contact us</a> if you really want to "
+"prevent spam. Please <a href=\"{{url}}\">contact us</a> if you really want to "
"send a follow up message."
-msgstr "Tu respuesta no ha sido enviada porque esta solicitud ha sido bloqueada para evitar spam. Por favor <a href=\"%s\">contáctanos</a> si realmente quieres enviar una respuesta."
+msgstr "Tu respuesta no ha sido enviada porque esta solicitud ha sido bloqueada para evitar spam. Por favor <a href=\"{{url}}\">contáctanos</a> si realmente quieres enviar una respuesta."
#: app/controllers/request_controller.rb:626
msgid "Your follow up message has been sent on its way."
@@ -3990,8 +3966,8 @@ msgstr "Tu nombre y su comentario aparecerán en los <strong>motores de búsqued
#: app/views/request/preview.rhtml:8
msgid ""
"Your name, request and any responses will appear in <strong>search engines</strong>\n"
-" (<a href=\"%s\">details</a>)."
-msgstr "Tu nombre, tu solicitud y cualquier respuesta aparecerán en los <strong>motores de búsqueda</strong>\n (<a href=\"%s\">detalles</a>)."
+" (<a href=\"{{url}}\">details</a>)."
+msgstr "Tu nombre, tu solicitud y cualquier respuesta aparecerán en los <strong>motores de búsqueda</strong>\n (<a href=\"{{url}}\">detalles</a>)."
#: app/views/user/_signup.rhtml:18
msgid "Your name:"
@@ -4028,8 +4004,8 @@ msgstr "Tu solicitud:"
#: app/views/request/upload_response.rhtml:8
msgid ""
"Your response will <strong>appear on the Internet</strong>, <a "
-"href=\"%s\">read why</a> and answers to other questions."
-msgstr "Tu respuesta <strong>aparecerá en Internet</strong>, <a href=\"%s\">lee por qué</a> y respuestas a otras preguntas."
+"href=\"{{url}}\">read why</a> and answers to other questions."
+msgstr "Tu respuesta <strong>aparecerá en Internet</strong>, <a href=\"{{url}}\">lee por qué</a> y respuestas a otras preguntas."
#: app/views/comment/new.rhtml:63
msgid ""
@@ -4276,8 +4252,8 @@ msgid ""
"no longer exists. If you are trying to make\n"
" From the request page, try replying to a particular message, rather than sending\n"
" a general followup. If you need to make a general followup, and know\n"
-" an email which will go to the right place, please <a href=\"%s\">send it to us</a>."
-msgstr "ya no existe. \nDesde la página de la solicitud, intenta responder a un mensaje en concreto, en vez de\n responder a la solicitud en general. Si necesitas hacerlo y tienes una dirección de\n correo válida, por favor <a href=\"%s\">mándanosla</a>."
+" an email which will go to the right place, please <a href=\"{{url}}\">send it to us</a>."
+msgstr "ya no existe. \nDesde la página de la solicitud, intenta responder a un mensaje en concreto, en vez de\n responder a la solicitud en general. Si necesitas hacerlo y tienes una dirección de\n correo válida, por favor <a href=\"{{url}}\">mándanosla</a>."
#: app/views/request/show.rhtml:72
msgid "normally"
diff --git a/spec/fixtures/theme_views/theme_one/help/contact.es.html.erb b/spec/fixtures/theme_views/theme_one/help/contact.es.html.erb
new file mode 100644
index 000000000..a294c8aa1
--- /dev/null
+++ b/spec/fixtures/theme_views/theme_one/help/contact.es.html.erb
@@ -0,0 +1 @@
+contáctenos theme one
diff --git a/spec/fixtures/theme_views/theme_one/help/contact.html.erb b/spec/fixtures/theme_views/theme_one/help/contact.html.erb
new file mode 100644
index 000000000..428c7368d
--- /dev/null
+++ b/spec/fixtures/theme_views/theme_one/help/contact.html.erb
@@ -0,0 +1 @@
+Contact us
diff --git a/spec/integration/errors_spec.rb b/spec/integration/errors_spec.rb
index edf570182..17a0153c2 100644
--- a/spec/integration/errors_spec.rb
+++ b/spec/integration/errors_spec.rb
@@ -1,40 +1,130 @@
+# -*- coding: utf-8 -*-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
-describe "When rendering errors" do
+describe "When errors occur" do
- before(:each) do
- load_raw_emails_data
+ def set_consider_all_requests_local(value)
+ @requests_local = Rails.application.config.consider_all_requests_local
+ Rails.application.config.consider_all_requests_local = value
end
- it "should render a 404 for users that don't exist" do
- get("/user/wobsnasm")
- response.code.should == "404"
+ def restore_consider_all_requests_local
+ Rails.application.config.consider_all_requests_local = @requests_local
end
- it "should render a 404 for bodies that don't exist" do
- get("/body/wobsnasm")
- response.code.should == "404"
+
+ before(:each) do
+ # This should happen automatically before each test but doesn't with these integration
+ # tests for some reason.
+ ActionMailer::Base.deliveries = []
end
- it "should render a 500 for general errors" do
- ir = info_requests(:naughty_chicken_request)
- # Set an invalid state for the request. Note that update_attribute doesn't run the validations
- ir.update_attribute(:described_state, "crotchety")
- get("/request/#{ir.url_title}")
- response.code.should == "500"
+
+ after(:each) do
+ restore_consider_all_requests_local
end
- it "should render a 403 for attempts at directory listing for attachments" do
- # make a fake cache
- foi_cache_path = File.expand_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.body.should include("Directory listing not allowed")
- response.code.should == "403"
- get("/request/101/response/1/attach/html" )
- response.body.should include("Directory listing not allowed")
- response.code.should == "403"
+
+ context 'when considering all requests local (by default all in development)' do
+
+ before(:each) { set_consider_all_requests_local(true) }
+
+ it 'should show a full trace for general errors' do
+ InfoRequest.stub!(:find_by_url_title!).and_raise("An example error")
+ get("/request/example")
+ response.body.should have_selector('div[id=traces]')
+ response.body.should match('An example error')
+ end
+
end
- it "should render a 404 for non-existent 'details' pages for requests" do
- get("/details/request/wobble" )
- response.code.should == "404"
+
+ context 'when not considering all requests local' do
+
+ before(:each) { set_consider_all_requests_local(false) }
+
+ it "should render a 404 for unrouteable URLs using the general/exception_caught template" do
+ get("/frobsnasm")
+ response.should render_template('general/exception_caught')
+ response.code.should == "404"
+ end
+
+ it "should render a 404 for users or bodies that don't exist using the general/exception_caught
+ template" do
+ ['/user/wobsnasm', '/body/wobsnasm'].each do |non_existent_url|
+ get(non_existent_url)
+ response.should render_template('general/exception_caught')
+ response.code.should == "404"
+ end
+ end
+
+ it "should render a 500 for general errors using the general/exception_caught template" do
+ InfoRequest.stub!(:find_by_url_title!).and_raise("An example error")
+ get("/request/example")
+ response.should render_template('general/exception_caught')
+ response.body.should match('An example error')
+ response.code.should == "500"
+ end
+
+ it 'should render a 500 for json errors' do
+ InfoRequest.stub!(:find_by_url_title!).and_raise("An example error")
+ get("/request/example.json")
+ response.code.should == '500'
+ end
+
+ it 'should render a 404 for a non-found xml request' do
+ get("/frobsnasm.xml")
+ response.code.should == '404'
+ end
+
+ it 'should notify of a general error' do
+ InfoRequest.stub!(:find_by_url_title!).and_raise("An example error")
+ get("/request/example")
+ deliveries = ActionMailer::Base.deliveries
+ deliveries.size.should == 1
+ mail = deliveries[0]
+ mail.body.should =~ /An example error/
+ end
+
+ it 'should log a general error' do
+ Rails.logger.should_receive(:fatal)
+ InfoRequest.stub!(:find_by_url_title!).and_raise("An example error")
+ get("/request/example")
+ end
+
+ it 'should assign the locale for the general/exception_caught template' do
+ InfoRequest.stub!(:find_by_url_title!).and_raise("An example error")
+ get("/es/request/example")
+ response.should render_template('general/exception_caught')
+ response.body.should match('Lo sentimos, hubo un problema procesando esta página')
+ response.body.should match('An example error')
+ end
+
+ it "should render a 403 with text body for attempts at directory listing for attachments" do
+ # make a fake cache
+ foi_cache_path = File.expand_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.body.should include("Directory listing not allowed")
+ response.code.should == "403"
+ get("/request/101/response/1/attach/html" )
+ response.body.should include("Directory listing not allowed")
+ response.code.should == "403"
+ end
+
+ it "return a 403 for a JSON PermissionDenied error" do
+ InfoRequest.stub!(:find_by_url_title!).and_raise(ApplicationController::PermissionDenied)
+ get("/request/example.json")
+ response.code.should == '403'
+ end
+
+ context "in the admin interface" do
+
+ it 'should show a full trace for general errors' do
+ InfoRequest.stub!(:find).and_raise("An example error")
+ get("/admin/request/show/333")
+ response.body.should have_selector('div[id=traces]')
+ response.body.should match('An example error')
+ end
+
+ end
+
end
-end
+end
diff --git a/spec/integration/request_controller_spec.rb b/spec/integration/request_controller_spec.rb
index 24667bdf1..9e585448b 100644
--- a/spec/integration/request_controller_spec.rb
+++ b/spec/integration/request_controller_spec.rb
@@ -6,6 +6,7 @@ describe RequestController, "when classifying an information request" do
describe 'when the request is internal' do
before(:each) do
+ load_raw_emails_data
@dog_request = info_requests(:fancy_dog_request)
# This should happen automatically before each test but doesn't with these integration
# tests for some reason.
diff --git a/spec/integration/view_request_spec.rb b/spec/integration/view_request_spec.rb
index 442721890..3d646cfe7 100644
--- a/spec/integration/view_request_spec.rb
+++ b/spec/integration/view_request_spec.rb
@@ -13,5 +13,12 @@ describe "When viewing requests" do
response.body.should include("dog.json?unfold=1")
end
+ it 'should not raise a routing error when making a json link for a request with an
+ "action" querystring param' do
+ @dog_request = info_requests(:fancy_dog_request)
+ get "request/#{@dog_request.url_title}?action=add"
+ response.should be_success
+ end
+
end
diff --git a/spec/lib/basic_encoding_tests.rb b/spec/lib/basic_encoding_tests.rb
new file mode 100644
index 000000000..35d35fd4a
--- /dev/null
+++ b/spec/lib/basic_encoding_tests.rb
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+def bytes_to_binary_string( bytes, claimed_encoding = nil )
+ claimed_encoding ||= 'ASCII-8BIT'
+ bytes_string = bytes.pack('c*')
+ if RUBY_VERSION.to_f >= 1.9
+ bytes_string.force_encoding! claimed_encoding
+ end
+ bytes_string
+end
+
+random_string = bytes_to_binary_string [ 0x0f, 0x58, 0x1c, 0x8f, 0xa4, 0xcf,
+ 0xf6, 0x8c, 0x9d, 0xa7, 0x06, 0xd9,
+ 0xf7, 0x90, 0x6c, 0x6f]
+
+windows_1252_string = bytes_to_binary_string [ 0x44, 0x41, 0x53, 0x48, 0x20,
+ 0x96, 0x20, 0x44, 0x41, 0x53,
+ 0x48 ]
+
+# It's a shame this example is so long, but if we don't take enough it
+# gets misinterpreted as Shift_JIS
+
+gb_18030_bytes = [ 0xb9, 0xf3, 0xb9, 0xab, 0xcb, 0xbe, 0xb8, 0xba, 0xd4, 0xf0,
+ 0xc8, 0xcb, 0x28, 0xbe, 0xad, 0xc0, 0xed, 0x2f, 0xb2, 0xc6,
+ 0xce, 0xf1, 0x29, 0xc4, 0xfa, 0xba, 0xc3, 0xa3, 0xba, 0x0d,
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0xb1, 0xbe, 0xb9, 0xab, 0xcb, 0xbe, 0xd4,
+ 0xda, 0x31, 0x39, 0x39, 0x37, 0xc4, 0xea, 0xb3, 0xc9, 0xc1,
+ 0xa2, 0xb9, 0xfa, 0xbc, 0xd2, 0xb9, 0xa4, 0xc9, 0xcc, 0xd7,
+ 0xa2, 0xb2, 0xe1, 0x2e, 0xca, 0xb5, 0xc1, 0xa6, 0xd0, 0xdb,
+ 0xba, 0xf1, 0xa1, 0xa3, 0xd3, 0xd0, 0xb6, 0xc0, 0xc1, 0xa2,
+ 0xcb, 0xb0, 0xce, 0xf1, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xd7, 0xa8, 0xd2, 0xb5,
+ 0xc8, 0xcb, 0xd4, 0xb1, 0x3b, 0xd4, 0xda, 0xc8, 0xab, 0xb9,
+ 0xfa, 0xb8, 0xf7, 0xb3, 0xc7, 0xca, 0xd0, 0xc9, 0xe8, 0xc1,
+ 0xa2, 0xb7, 0xd6, 0xb9, 0xab, 0xcb, 0xbe, 0xa3, 0xa8, 0xd5,
+ 0xe3, 0xbd, 0xad, 0xa1, 0xa2, 0xc9, 0xcf, 0xba, 0xa3, 0xa1,
+ 0xa2, 0xb9, 0xe3, 0xd6, 0xdd, 0xa1, 0xa2, 0xbd, 0xad, 0xcb,
+ 0xd5, 0xb5, 0xc8, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0xb5, 0xd8, 0xb7, 0xbd, 0xa3,
+ 0xa9, 0xd2, 0xf2, 0xbd, 0xf8, 0xcf, 0xee, 0xbd, 0xcf, 0xb6,
+ 0xe0, 0xcf, 0xd6, 0xcd, 0xea, 0xb3, 0xc9, 0xb2, 0xbb, 0xc1,
+ 0xcb, 0xc3, 0xbf, 0xd4, 0xc2, 0xcf, 0xfa, 0xca, 0xdb, 0xb6,
+ 0xee, 0xb6, 0xc8, 0xa1, 0xa3, 0xc3, 0xbf, 0xd4, 0xc2, 0xd3,
+ 0xd0, 0xd2, 0xbb, 0xb2, 0xbf, 0xb7, 0xd6, 0x0d, 0x0a, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xd4,
+ 0xf6, 0xd6, 0xb5, 0xb6, 0x90, 0xa3, 0xa8, 0x36, 0x2d, 0x37,
+ 0x25, 0xd7, 0xf3, 0xd3, 0xd2, 0x29, 0xba, 0xcd, 0xc6, 0xd5,
+ 0xc6, 0xb1, 0xa3, 0xa8, 0x30, 0x2e, 0x35, 0x25, 0x2d, 0x32,
+ 0x25, 0x20, 0xd7, 0xf3, 0xd3, 0xd2, 0xa3, 0xa9, 0xd3, 0xc5,
+ 0xbb, 0xdd, 0xb4, 0xfa, 0xbf, 0xaa, 0xbb, 0xf2, 0xba, 0xcf,
+ 0xd7, 0xf7, 0xa3, 0xac, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xb5, 0xe3, 0xca, 0xfd,
+ 0xbd, 0xcf, 0xb5, 0xcd, 0xa1, 0xa3, 0xb4, 0xfa, 0xc0, 0xed,
+ 0xb7, 0xb6, 0xce, 0xa7, 0xc8, 0xe7, 0xcf, 0xc2, 0xa3, 0xba,
+ 0x0d, 0x0a ]
+
+gb_18030_spam_string = bytes_to_binary_string gb_18030_bytes
+
+describe "normalize_string_to_utf8" do
+
+ describe "when passed uniterpretable character data" do
+
+ it "should reject it as invalid" do
+
+ expect {
+ normalize_string_to_utf8 random_string
+ }.to raise_error(EncodingNormalizationError)
+
+ expect {
+ normalize_string_to_utf8 random_string, 'UTF-8'
+ }.to raise_error(EncodingNormalizationError)
+
+ end
+ end
+
+ describe "when passed unlabelled Windows 1252 data" do
+
+ it "should correctly convert it to UTF-8" do
+
+ normalized = normalize_string_to_utf8 windows_1252_string
+
+ normalized.should == "DASH – DASH"
+
+ end
+
+ end
+
+ describe "when passed GB 18030 data" do
+
+ it "should correctly convert it to UTF-8 if unlabelled" do
+
+ normalized = normalize_string_to_utf8 gb_18030_spam_string
+
+ normalized.should start_with("贵公司负责人")
+
+ end
+
+ end
+
+end
+
+describe "convert_string_to_utf8_or_binary" do
+
+ describe "when passed uniterpretable character data" do
+
+ it "should return it as a binary string" do
+
+ converted = convert_string_to_utf8_or_binary random_string
+ converted.should == random_string
+
+ if RUBY_VERSION.to_f >= 1.9
+ converted.encoding.should == 'ASCII-8BIT'
+ end
+
+ converted = convert_string_to_utf8_or_binary random_string,'UTF-8'
+ converted.should == random_string
+
+ if RUBY_VERSION.to_f >= 1.9
+ converted.encoding.should == 'ASCII-8BIT'
+ end
+
+ end
+ end
+
+ describe "when passed unlabelled Windows 1252 data" do
+
+ it "should correctly convert it to UTF-8" do
+
+ converted = convert_string_to_utf8_or_binary windows_1252_string
+
+ converted.should == "DASH – DASH"
+
+ if RUBY_VERSION.to_f >= 1.9
+ converted.encoding.should == 'UTF-8'
+ end
+ end
+
+ end
+
+ describe "when passed GB 18030 data" do
+
+ it "should correctly convert it to UTF-8 if unlabelled" do
+
+ converted = convert_string_to_utf8_or_binary gb_18030_spam_string
+
+ converted.should start_with("贵公司负责人")
+
+ if RUBY_VERSION.to_f >= 1.9
+ converted.encoding.should == 'UTF-8'
+ end
+ end
+
+ end
+
+end
diff --git a/spec/lib/i18n_interpolation.rb b/spec/lib/i18n_interpolation.rb
index e8d046757..b07cf1e9a 100644
--- a/spec/lib/i18n_interpolation.rb
+++ b/spec/lib/i18n_interpolation.rb
@@ -12,7 +12,35 @@ describe "when using i18n" do
it "should assume that simple translations are always html safe" do
_("Hello").should be_html_safe
end
+end
+
+describe "n_" do
+ it "should return the translated singular" do
+ FastGettext.should_receive(:n_).with("Apple", "Apples", 1).and_return("Apfel")
+ n_("Apple", "Apples", 1).should == "Apfel"
+ end
+
+ it "should return the translated plural" do
+ FastGettext.should_receive(:n_).with("Apple", "Apples", 3).and_return("Äpfel")
+ n_("Apple", "Apples", 3).should == "Äpfel"
+ end
+ it "should return the translated singular interpolated" do
+ FastGettext.should_receive(:n_).with("I eat {{count}} apple", "I eat {{count}} apples", 1).
+ and_return("Ich esse {{count}} Apfel")
+ n_("I eat {{count}} apple", "I eat {{count}} apples", 1, :count => 1).should == "Ich esse 1 Apfel"
+ end
+
+ it "should return the translated plural interpolated" do
+ FastGettext.should_receive(:n_).with("I eat {{count}} apple", "I eat {{count}} apples", 3).
+ and_return("Ich esse {{count}} Äpfel")
+ n_("I eat {{count}} apple", "I eat {{count}} apples", 3, :count => 3).should == "Ich esse 3 Äpfel"
+ end
+
+ it "should always be html safe when there is no interpolation" do
+ FastGettext.should_receive(:n_).with("Apple", "Apples", 1).and_return("Apfel")
+ n_("Apple", "Apples", 1).should be_html_safe
+ end
end
describe "gettext_interpolate" do
diff --git a/spec/lib/mail_handler/mail_handler_spec.rb b/spec/lib/mail_handler/mail_handler_spec.rb
index 79b779687..241ce06c1 100644
--- a/spec/lib/mail_handler/mail_handler_spec.rb
+++ b/spec/lib/mail_handler/mail_handler_spec.rb
@@ -20,12 +20,51 @@ describe 'when creating a mail object from raw data' do
mail.to.should == ["request-66666-caa77777@whatdotheyknow.com", "foi@example.com"]
end
+ it 'should return nil for malformed To: and Cc: lines' do
+ mail = get_fixture_mail('malformed-to-and-cc.email')
+ mail.to.should == nil
+ mail.cc.should == nil
+ end
+
it 'should convert an iso8859 email to utf8' do
mail = get_fixture_mail('iso8859_2_raw_email.email')
mail.subject.should match /gjatë/u
MailHandler.get_part_body(mail).is_utf8?.should == true
end
+ it 'should not be confused by subject lines with malformed UTF-8 at the end' do
+ # The base64 subject line was generated with:
+ # printf "hello\360" | base64
+ # ... and wrapping the result in '=?UTF-8?B?' and '?='
+ mail = get_fixture_mail('subject-bad-utf-8-trailing-base64.email')
+ mail.subject.should == 'hello'
+ # The quoted printable subject line was generated with:
+ # printf "hello\360" | qprint -b -e
+ # ... and wrapping the result in '=?UTF-8?Q?' and '?='
+ mail = get_fixture_mail('subject-bad-utf-8-trailing-quoted-printable.email')
+ mail.subject.should == 'hello'
+ end
+
+ it 'should convert a Windows-1252 body mislabelled as ISO-8859-1 to UTF-8' do
+ mail = get_fixture_mail('mislabelled-as-iso-8859-1.email')
+ body = MailHandler.get_part_body(mail)
+ body.is_utf8?.should == true
+ # This email is broken in at least these two ways:
+ # 1. It contains a top bit set character (0x96) despite the
+ # "Content-Transfer-Encoding: 7bit"
+ # 2. The charset in the Content-Type header is "iso-8859-1"
+ # but 0x96 is actually a Windows-1252 en dash, which would
+ # be Unicode codepoint 2013. It should be possible to
+ # spot the mislabelling, since 0x96 isn't a valid
+ # ISO-8859-1 character.
+ body.should match(/ \xe2\x80\x93 /)
+ end
+
+ it 'should not error on a subject line with an encoding encoding not recognized by iconv' do
+ mail = get_fixture_mail('unrecognized-encoding-mail.email')
+ lambda{ mail.subject }.should_not raise_error
+ end
+
end
describe 'when asked for the from name' do
@@ -189,7 +228,7 @@ describe 'when deriving a name, email and formatted address from a message from
it 'should quote a name with quotes in it' do
should_render_from_address('"FOI \" Person" <foiperson@localhost>',
- ['FOI " Person',
+ ['FOI \" Person',
'foiperson@localhost',
'"FOI \" Person" <foiperson@localhost>'])
end
@@ -275,6 +314,12 @@ end
describe 'when getting attachment attributes' do
+ it 'should handle a mail with a non-multipart part with no charset in the Content-Type header' do
+ mail = get_fixture_mail('part-without-charset-in-content-type.email')
+ attributes = MailHandler.get_attachment_attributes(mail)
+ attributes.size.should == 2
+ end
+
it 'should get two attachment parts from a multipart mail with text and html alternatives
and an image' do
mail = get_fixture_mail('quoted-subject-iso8859-1.email')
@@ -282,6 +327,13 @@ describe 'when getting attachment attributes' do
attributes.size.should == 2
end
+ it 'should get one attachment from a multipart mail with text and HTML alternatives, which should be UTF-8' do
+ mail = get_fixture_mail('iso8859_2_raw_email.email')
+ attributes = MailHandler.get_attachment_attributes(mail)
+ attributes.length.should == 1
+ attributes[0][:body].is_utf8?.should == true
+ end
+
it 'should expand a mail attached as text' do
# Note that this spec will only pass using Tmail in the timezone set as datetime headers
# are rendered out in the local time - using the Mail gem this is not necessary
@@ -304,6 +356,52 @@ describe 'when getting attachment attributes' do
attributes = MailHandler.get_attachment_attributes(mail)
end
+ it 'should ignore truncated TNEF attachment' do
+ mail = get_fixture_mail('tnef-attachment-truncated.email')
+ attributes = MailHandler.get_attachment_attributes(mail)
+ attributes.length.should == 2
+ end
+
+ it 'should ignore anything beyond the final MIME boundary' do
+ pending do
+ # This example raw email has a premature closing boundary for
+ # the outer multipart/mixed - my reading of RFC 1521 is that
+ # the "epilogue" beyond that should be ignored.
+ # See https://github.com/mysociety/alaveteli/issues/922 for
+ # more discussion.
+ mail = get_fixture_mail('nested-attachments-premature-end.email')
+ attributes = MailHandler.get_attachment_attributes(mail)
+ attributes.length.should == 3
+ end
+ end
+
+ it 'should cope with a missing final MIME boundary' do
+ mail = get_fixture_mail('multipart-no-final-boundary.email')
+ attributes = MailHandler.get_attachment_attributes(mail)
+ attributes.length.should == 1
+ attributes[0][:body].should match(/This is an acknowledgement of your email/)
+ attributes[0][:content_type].should == "text/plain"
+ attributes[0][:url_part_number].should == 1
+ end
+
+ it 'should ignore a TNEF attachment with no usable contents' do
+ # FIXME: "no usable contents" is slightly misleading. The
+ # attachment in this example email does have usable content in
+ # the body of the TNEF attachment, but the invocation of tnef
+ # historically used to unpack these attachments doesn't add
+ # the --save-body parameter, so that they have been ignored so
+ # far. We probably should include the body from such
+ # attachments, but, at the moment, with the pending upgrade to
+ # Rails 3, we just want to check that the behaviour is the
+ # same as before.
+ mail = get_fixture_mail('tnef-attachment-empty.email')
+ attributes = MailHandler.get_attachment_attributes(mail)
+ attributes.length.should == 2
+ # This is the size of the TNEF-encoded attachment; currently,
+ # we expect the code just to return this without decoding:
+ attributes[1][:body].length.should == 7769
+ end
+
it 'should produce a consistent set of url_part_numbers, content_types, within_rfc822_subjects
and filenames from an example mail with lots of attachments' do
mail = get_fixture_mail('many-attachments-date-header.email')
@@ -385,3 +483,11 @@ describe 'when getting attachment attributes' do
end
end
end
+
+describe 'when getting the address part from an address string' do
+
+ it 'should handle non-ascii characters in the name input' do
+ address = "\"Someone’s name\" <test@example.com>"
+ MailHandler.address_from_string(address).should == 'test@example.com'
+ end
+end
diff --git a/spec/mailers/outgoing_mailer_spec.rb b/spec/mailers/outgoing_mailer_spec.rb
index 5d1ea2dfb..a11d56dd3 100644
--- a/spec/mailers/outgoing_mailer_spec.rb
+++ b/spec/mailers/outgoing_mailer_spec.rb
@@ -1,73 +1,66 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
-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
- before(:each) do
- load_raw_emails_data
+describe OutgoingMailer, " when working out follow up names and addresses" do
+
+ before do
+ @info_request = mock_model(InfoRequest,
+ :recipient_name_and_email => 'test <test@example.com>',
+ :recipient_email => 'test@example.com')
+ @info_request.stub_chain(:public_body, :name).and_return("Test Authority")
+ @incoming_message = mock_model(IncomingMessage,
+ :from_email => 'specific@example.com',
+ :safe_mail_from => 'Specific Person')
end
- it "should parse them right" do
- ir = info_requests(:fancy_dog_request)
- im = ir.incoming_messages[0]
-
- # check the basic entry in the fixture is fine
- OutgoingMailer.name_and_email_for_followup(ir, im).should == "FOI Person <foiperson@localhost>"
- OutgoingMailer.name_for_followup(ir, im).should == "FOI Person"
- OutgoingMailer.email_for_followup(ir, im).should == "foiperson@localhost"
+ def expect_address(info_request, incoming_message, expected_result)
+ mail = create_message_from(from_line)
+ name = MailHandler.get_from_name(mail)
+ email = MailHandler.get_from_address(mail)
+ address = MailHandler.address_from_name_and_email(name, email).to_s
+ [name, email, address].should == expected_result
end
- it "should work when there is only an email address" do
- ir = info_requests(:fancy_dog_request)
- im = ir.incoming_messages[0]
+ describe 'if there is no incoming message being replied to' do
- im.raw_email.data = im.raw_email.data.sub("\"FOI Person\" <foiperson@localhost>", "foiperson@localhost")
- im.parse_raw_email! true
+ it 'should return the name and email address of the public body' do
+ OutgoingMailer.name_and_email_for_followup(@info_request, nil).should == 'test <test@example.com>'
+ OutgoingMailer.name_for_followup(@info_request, nil).should == 'Test Authority'
+ OutgoingMailer.email_for_followup(@info_request, nil).should == 'test@example.com'
+ end
- # check the basic entry in the fixture is fine
- OutgoingMailer.name_and_email_for_followup(ir, im).should == "foiperson@localhost"
- OutgoingMailer.name_for_followup(ir, im).should == "Geraldine Quango"
- OutgoingMailer.email_for_followup(ir, im).should == "foiperson@localhost"
end
- it "should quote funny characters" do
- ir = info_requests(:fancy_dog_request)
- im = ir.incoming_messages[0]
+ describe 'if the incoming message being replied to is not valid to reply to' do
- im.raw_email.data = im.raw_email.data.sub("FOI Person", "FOI [ Person")
- im.parse_raw_email! true
+ before do
+ @incoming_message.stub!(:valid_to_reply_to?).and_return(false)
+ end
- # check the basic entry in the fixture is fine
- OutgoingMailer.name_and_email_for_followup(ir, im).should == "\"FOI [ Person\" <foiperson@localhost>"
- OutgoingMailer.name_for_followup(ir, im).should == "FOI [ Person"
- OutgoingMailer.email_for_followup(ir, im).should == "foiperson@localhost"
+ it 'should return the safe name and email address of the public body' do
+ OutgoingMailer.name_and_email_for_followup(@info_request, @incoming_message).should == 'test <test@example.com>'
+ OutgoingMailer.name_for_followup(@info_request, @incoming_message).should == 'Test Authority'
+ OutgoingMailer.email_for_followup(@info_request, @incoming_message).should == 'test@example.com'
+ end
end
- it "should quote quotes" do
- ir = info_requests(:fancy_dog_request)
- im = ir.incoming_messages[0]
+ describe 'if the incoming message is valid to reply to' do
- im.raw_email.data = im.raw_email.data.sub("FOI Person", "FOI \\\" Person")
- im.parse_raw_email! true
+ before do
+ @incoming_message.stub!(:valid_to_reply_to?).and_return(true)
+ end
- # check the basic entry in the fixture is fine
- OutgoingMailer.name_and_email_for_followup(ir, im).should == "\"FOI \\\" Person\" <foiperson@localhost>"
- OutgoingMailer.name_for_followup(ir, im).should == "FOI \" Person"
- OutgoingMailer.email_for_followup(ir, im).should == "foiperson@localhost"
- end
+ it 'should return the name and email address from the incoming message' do
+ OutgoingMailer.name_and_email_for_followup(@info_request, @incoming_message).should == 'Specific Person <specific@example.com>'
+ OutgoingMailer.name_for_followup(@info_request, @incoming_message).should == 'Specific Person'
+ OutgoingMailer.email_for_followup(@info_request, @incoming_message).should == 'specific@example.com'
+ end
- it "should quote @ signs" do
- ir = info_requests(:fancy_dog_request)
- im = ir.incoming_messages[0]
+ it 'should return the name of the public body if the incoming message does not have
+ a safe name' do
+ @incoming_message.stub!(:safe_mail_from).and_return(nil)
+ OutgoingMailer.name_for_followup(@info_request, @incoming_message).should == 'Test Authority'
+ end
- 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>"
- OutgoingMailer.name_for_followup(ir, im).should == "FOI @ Person"
- OutgoingMailer.email_for_followup(ir, im).should == "foiperson@localhost"
end
end
@@ -79,21 +72,21 @@ describe OutgoingMailer, "when working out follow up subjects" do
end
it "should prefix the title with 'Freedom of Information request -' for initial requests" do
- ir = info_requests(:fancy_dog_request)
+ ir = info_requests(:fancy_dog_request)
im = ir.incoming_messages[0]
ir.email_subject_request.should == "Freedom of Information request - Why do you have & such a fancy dog?"
end
it "should use 'Re:' and inital request subject for followups which aren't replies to particular messages" do
- ir = info_requests(:fancy_dog_request)
+ ir = info_requests(:fancy_dog_request)
om = outgoing_messages(:useless_outgoing_message)
OutgoingMailer.subject_for_followup(ir, om).should == "Re: Freedom of Information request - Why do you have & such a fancy dog?"
end
it "should prefix with Re: the subject of the message being replied to" do
- ir = info_requests(:fancy_dog_request)
+ ir = info_requests(:fancy_dog_request)
im = ir.incoming_messages[0]
om = outgoing_messages(:useless_outgoing_message)
om.incoming_message_followup = im
@@ -102,7 +95,7 @@ describe OutgoingMailer, "when working out follow up subjects" do
end
it "should not add Re: prefix if there already is such a prefix" do
- ir = info_requests(:fancy_dog_request)
+ ir = info_requests(:fancy_dog_request)
im = ir.incoming_messages[0]
om = outgoing_messages(:useless_outgoing_message)
om.incoming_message_followup = im
@@ -112,19 +105,19 @@ describe OutgoingMailer, "when working out follow up subjects" do
end
it "should not add Re: prefix if there already is a lower case re: prefix" do
- ir = info_requests(:fancy_dog_request)
+ ir = info_requests(:fancy_dog_request)
im = ir.incoming_messages[0]
om = outgoing_messages(:useless_outgoing_message)
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
it "should use 'Re:' and initial request subject when replying to failed delivery notifications" do
- ir = info_requests(:fancy_dog_request)
+ ir = info_requests(:fancy_dog_request)
im = ir.incoming_messages[0]
om = outgoing_messages(:useless_outgoing_message)
om.incoming_message_followup = im
diff --git a/spec/mailers/track_mailer_spec.rb b/spec/mailers/track_mailer_spec.rb
index 9537e6b14..e8094b692 100644
--- a/spec/mailers/track_mailer_spec.rb
+++ b/spec/mailers/track_mailer_spec.rb
@@ -13,7 +13,7 @@ describe TrackMailer do
it 'should ask for all the users whose last daily track email was sent more than a day ago' do
expected_conditions = [ "last_daily_track_email < ?", Time.utc(2007, 11, 11, 23, 59)]
- User.should_receive(:find).with(:all, :conditions => expected_conditions).and_return([])
+ User.should_receive(:find_each).with(:conditions => expected_conditions)
TrackMailer.alert_tracks
end
@@ -26,7 +26,7 @@ describe TrackMailer do
:url_name => 'test-name',
:get_locale => 'en',
:should_be_emailed? => true)
- User.stub!(:find).and_return([@user])
+ User.stub!(:find_each).and_yield(@user)
@user.stub!(:receive_email_alerts).and_return(true)
@user.stub!(:no_xapian_reindex=)
end
@@ -69,11 +69,15 @@ describe TrackMailer do
@xapian_search = mock('xapian search', :results => [])
@found_event = mock_model(InfoRequestEvent, :described_at => @track_thing.created_at + 1.day)
@search_result = {:model => @found_event}
- InfoRequest.stub!(:full_search).and_return(@xapian_search)
+ ActsAsXapian::Search.stub!(:new).and_return(@xapian_search)
end
it 'should ask for the events returned by the tracking query' do
- InfoRequest.should_receive(:full_search).with([InfoRequestEvent], 'test query', 'described_at', true, nil, 100, 1).and_return(@xapian_search)
+ ActsAsXapian::Search.should_receive(:new).with([InfoRequestEvent], 'test query',
+ :sort_by_prefix => 'described_at',
+ :sort_by_ascending => true,
+ :collapse_by_prefix => nil,
+ :limit => 100).and_return(@xapian_search)
TrackMailer.alert_tracks
end
@@ -124,7 +128,7 @@ describe TrackMailer do
:save! => true,
:url_name => 'test-name',
:should_be_emailed? => false)
- User.stub!(:find).and_return([@user])
+ User.stub!(:find_each).and_yield(@user)
@user.stub!(:receive_email_alerts).and_return(true)
@user.stub!(:no_xapian_reindex=)
end
@@ -195,7 +199,7 @@ describe TrackMailer do
context "force ssl is off" do
# Force SSL is off in the tests. Since the code that selectively switches the protocols
# is in the initialiser for Rails it's hard to test. Hmmm...
- # We could Configuration.stub!(:force_ssl).and_return(true) but the config/environment.rb
+ # We could AlaveteliConfiguration.stub!(:force_ssl).and_return(true) but the config/environment.rb
# wouldn't get reloaded
it "should have http links in the email" do
diff --git a/spec/models/foi_attachment_spec.rb b/spec/models/foi_attachment_spec.rb
index 537a3363c..9b0115c44 100644
--- a/spec/models/foi_attachment_spec.rb
+++ b/spec/models/foi_attachment_spec.rb
@@ -1,6 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
-describe FoiAttachment, " when calculating due date" do
+describe FoiAttachment do
before(:each) do
load_raw_emails_data
diff --git a/spec/models/incoming_message_spec.rb b/spec/models/incoming_message_spec.rb
index e22235298..3c924dcb3 100644
--- a/spec/models/incoming_message_spec.rb
+++ b/spec/models/incoming_message_spec.rb
@@ -59,12 +59,19 @@ describe IncomingMessage, " when dealing with incoming mail" do
message.subject.should == "Câmara Responde: Banco de ideias"
end
- it 'should not error on display of a message which has no charset set on the body part and
- is not good utf-8' do
+ it 'should deal with GB18030 text even if the charset is missing' do
ir = info_requests(:fancy_dog_request)
receive_incoming_mail('no-part-charset-bad-utf8.email', ir.incoming_email)
message = ir.incoming_messages[1]
message.parse_raw_email!
+ message.get_main_body_text_internal.should include("贵公司负责人")
+ end
+
+ it 'should not error on display of a message which has no charset set on the body part and is not good UTF-8' do
+ ir = info_requests(:fancy_dog_request)
+ receive_incoming_mail('no-part-charset-random-data.email', ir.incoming_email)
+ message = ir.incoming_messages[1]
+ message.parse_raw_email!
message.get_main_body_text_internal.should include("The above text was badly encoded")
end
@@ -108,6 +115,16 @@ describe IncomingMessage, " when dealing with incoming mail" do
end
+ it 'should handle a main body part that is just quoted content in an email that has
+ no subject' do
+ i = IncomingMessage.new
+ i.stub!(:get_main_body_text_unfolded).and_return("some quoting")
+ i.stub!(:get_main_body_text_folded).and_return("FOLDED_QUOTED_SECTION")
+ i.stub!(:subject).and_return(nil)
+ i.get_body_for_html_display
+ end
+
+
end
describe IncomingMessage, " display attachments" do
@@ -412,6 +429,17 @@ describe IncomingMessage, " when uudecoding bad messages" do
im.get_attachments_for_display.size.should == 1
end
+ it "should still work when parsed from the raw email" do
+ raw_email = load_file_fixture 'inline-uuencode.email'
+ mail = MailHandler.mail_from_raw_email(raw_email)
+ im = incoming_messages :useless_incoming_message
+ im.stub!(:raw_email).and_return(raw_email)
+ im.stub!(:mail).and_return(mail)
+ im.parse_raw_email!
+ attachments = im.foi_attachments
+ attachments.size.should == 2
+ end
+
it "should apply censor rules" do
mail = get_fixture_mail('incoming-request-bad-uuencoding.email')
@@ -524,3 +552,50 @@ describe IncomingMessage, "when TNEF attachments are attached to messages" do
end
end
+describe IncomingMessage, "when extracting attachments" do
+
+ before do
+ load_raw_emails_data
+ end
+
+ it 'handles the case where reparsing changes the body of the main part
+ and the cached attachment has been deleted' do
+ # original set of attachment attributes
+ attachment_attributes = { :url_part_number => 1,
+ :within_rfc822_subject => nil,
+ :content_type => "text/plain",
+ :charset => nil,
+ :body => "No way!\n",
+ :hexdigest => "0c8b1b0f5cb9c94ed15a180e73b5c7d1",
+ :filename => nil }
+
+ # Make a small change in the body returned for the attachment
+ new_attachment_attributes = attachment_attributes.merge(:body => "No way!",
+ :hexdigest => "74d2c0a41e074f9cebe49324d5b47414")
+
+
+ # Simulate parsing with the original attachments
+ MailHandler.stub!(:get_attachment_attributes).and_return([attachment_attributes])
+ incoming_message = incoming_messages(:useless_incoming_message)
+
+ # Extract the attachments
+ incoming_message.extract_attachments!
+
+ # delete the cached file for the main body part
+ main = incoming_message.get_main_body_text_part
+ main.delete_cached_file!
+
+ # Simulate reparsing with the slightly changed body
+ MailHandler.stub!(:get_attachment_attributes).and_return([new_attachment_attributes])
+
+ # Re-extract the attachments
+ incoming_message.extract_attachments!
+
+ attachments = incoming_message.foi_attachments
+ attachments.size.should == 1
+ attachments.first.hexdigest.should == "74d2c0a41e074f9cebe49324d5b47414"
+ attachments.first.body.should == 'No way!'
+ end
+
+end
+
diff --git a/spec/models/info_request_event_spec.rb b/spec/models/info_request_event_spec.rb
index 842246fd8..eb0de8c86 100644
--- a/spec/models/info_request_event_spec.rb
+++ b/spec/models/info_request_event_spec.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe InfoRequestEvent do
@@ -118,6 +119,13 @@ describe InfoRequestEvent do
@info_request_event.same_email_as_previous_send?.should be_true
end
+ it 'should handle non-ascii characters in the name input' do
+ address = "\"Someone’s name\" <test@example.com>"
+ @info_request_event.stub!(:params).and_return(:email => address)
+ @info_request_event.stub_chain(:info_request, :get_previous_email_sent_to).and_return(address)
+ @info_request_event.same_email_as_previous_send?.should be_true
+ end
+
end
end
diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb
index b877db4ef..0ba8abd75 100644
--- a/spec/models/info_request_spec.rb
+++ b/spec/models/info_request_spec.rb
@@ -562,9 +562,50 @@ describe InfoRequest do
@info_request.prominence = 'requester_only'
@info_request.all_can_view?.should == false
end
+ end
+
+ describe 'when generating json for the api' do
+
+ before do
+ @user = mock_model(User, :json_for_api => { :id => 20,
+ :url_name => 'alaveteli_user',
+ :name => 'Alaveteli User',
+ :ban_text => '',
+ :about_me => 'Hi' })
+ end
+ it 'should return full user info for an internal request' do
+ @info_request = InfoRequest.new(:user => @user)
+ @info_request.user_json_for_api.should == { :id => 20,
+ :url_name => 'alaveteli_user',
+ :name => 'Alaveteli User',
+ :ban_text => '',
+ :about_me => 'Hi' }
+ end
end
+ describe 'when working out a subject for a followup emails' do
+
+ it "should not be confused by an nil subject in the incoming message" do
+ ir = info_requests(:fancy_dog_request)
+ im = mock_model(IncomingMessage,
+ :subject => nil,
+ :valid_to_reply_to? => true)
+ subject = ir.email_subject_followup im
+ subject.should match(/^Re: Freedom of Information request.*fancy dog/)
+ end
+
+ it "should return a hash with the user's name for an external request" do
+ @info_request = InfoRequest.new(:external_url => 'http://www.example.com',
+ :external_user_name => 'External User')
+ @info_request.user_json_for_api.should == {:name => 'External User'}
+ end
+
+ it 'should return "Anonymous user" for an anonymous external user' do
+ @info_request = InfoRequest.new(:external_url => 'http://www.example.com')
+ @info_request.user_json_for_api.should == {:name => 'Anonymous user'}
+ end
+ end
describe "#set_described_state and #log_event" do
context "a request" do
let(:request) { InfoRequest.create!(:title => "my request",
@@ -765,7 +806,7 @@ describe InfoRequest do
events[1].described_state.should == "successful"
events[1].calculated_state.should == "successful"
end
- end
+ end
end
end
end
diff --git a/spec/models/outgoing_message_spec.rb b/spec/models/outgoing_message_spec.rb
index 51bb6fdf5..60164fb31 100644
--- a/spec/models/outgoing_message_spec.rb
+++ b/spec/models/outgoing_message_spec.rb
@@ -16,7 +16,7 @@ describe OutgoingMessage, " when making an outgoing message" do
it "should not index the email addresses" do
# also used for track emails
@outgoing_message.get_text_for_indexing.should_not include("foo@bar.com")
- end
+ end
it "should not display email addresses on page" do
@outgoing_message.get_body_for_html_display.should_not include("foo@bar.com")
@@ -33,6 +33,23 @@ describe OutgoingMessage, " when making an outgoing message" do
it "should work out a salutation" do
@om.get_salutation.should == "Dear Geraldine Quango,"
end
+
+ it 'should produce the expected text for an internal review request' do
+ public_body = mock_model(PublicBody, :name => 'A test public body')
+ info_request = mock_model(InfoRequest, :public_body => public_body,
+ :url_title => 'a_test_title',
+ :title => 'A test title',
+ :apply_censor_rules_to_text! => nil)
+ outgoing_message = OutgoingMessage.new({
+ :status => 'ready',
+ :message_type => 'followup',
+ :what_doing => 'internal_review',
+ :info_request => info_request
+ })
+ expected_text = "I am writing to request an internal review of A test public body's handling of my FOI request 'A test title'."
+ outgoing_message.body.should include(expected_text)
+ end
+
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 651ba4b65..96c169604 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -27,11 +27,22 @@ describe User, "showing the name" do
@user.name.should == 'Some Name'
end
- it 'should show if user has been banned' do
- @user.ban_text = "Naughty user"
- @user.name.should == 'Some Name (Account suspended)'
+ describe 'if user has been banned' do
+
+ before do
+ @user.ban_text = "Naughty user"
+ end
+
+ it 'should show an "Account suspended" suffix' do
+ @user.name.should == 'Some Name (Account suspended)'
+ end
+
+ it 'should return a string when the user has been banned, not a SafeBuffer' do
+ @user.name.class.should == String
+ end
end
+
end
describe User, " when authenticating" do
diff --git a/spec/models/xapian_spec.rb b/spec/models/xapian_spec.rb
index 8c99d550f..c40334142 100644
--- a/spec/models/xapian_spec.rb
+++ b/spec/models/xapian_spec.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe User, " when indexing users with Xapian" do
@@ -8,8 +9,7 @@ describe User, " when indexing users with Xapian" do
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 = ActsAsXapian::Search.new([User], "Silly", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model].should == users(:silly_name_user)
end
@@ -17,8 +17,7 @@ describe User, " when indexing users with Xapian" do
it "should search by 'about me' text" do
user = users(:bob_smith_user)
- # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page)
- xapian_object = InfoRequest.full_search([User], "stuff", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([User], "stuff", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model].should == user
@@ -26,10 +25,10 @@ describe User, " when indexing users with Xapian" do
user.save!
update_xapian_index
- xapian_object = InfoRequest.full_search([User], "stuff", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([User], "stuff", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([User], "aardvark", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([User], "aardvark", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model].should == user
end
@@ -42,26 +41,26 @@ describe PublicBody, " when indexing public bodies with Xapian" do
end
it "should search index the main name field" do
- xapian_object = InfoRequest.full_search([PublicBody], "humpadinking", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "humpadinking", :limit => 100)
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
- xapian_object = InfoRequest.full_search([PublicBody], "albatross", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "albatross", :limit => 100)
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
- xapian_object = InfoRequest.full_search([PublicBody], "albatross", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "albatross", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model].should == public_bodies(:humpadink_public_body)
public_bodies(:forlorn_public_body).destroy
update_xapian_index
- xapian_object = InfoRequest.full_search([PublicBody], "lonely", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "lonely", :limit => 100)
xapian_object.results.should == []
end
@@ -75,13 +74,13 @@ describe PublicBody, " when indexing requests by body they are to" do
end
it "should find requests to the body" do
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_from:tgq", :limit => 100)
xapian_object.results.size.should == PublicBody.find_by_url_name("tgq").info_requests.map(&:info_request_events).flatten.size
end
it "should update index correctly when URL name of body changes" do
# initial search
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_from:tgq", :limit => 100)
xapian_object.results.size.should == PublicBody.find_by_url_name("tgq").info_requests.map(&:info_request_events).flatten.size
models_found_before = xapian_object.results.map { |x| x[:model] }
@@ -93,9 +92,9 @@ describe PublicBody, " when indexing requests by body they are to" do
update_xapian_index
# check we get results expected
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_from:tgq", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:gq", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_from:gq", :limit => 100)
xapian_object.results.size.should == PublicBody.find_by_url_name("gq").info_requests.map(&:info_request_events).flatten.size
models_found_after = xapian_object.results.map { |x| x[:model] }
@@ -113,11 +112,11 @@ describe PublicBody, " when indexing requests by body they are to" do
update_xapian_index
# check we get results expected
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_from:tgq", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:gq", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_from:gq", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:" + body.url_name, 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_from:#{body.url_name}", :limit => 100)
xapian_object.results.size.should == public_bodies(:geraldine_public_body).info_requests.map(&:info_request_events).flatten.size
models_found_after = xapian_object.results.map { |x| x[:model] }
end
@@ -130,13 +129,14 @@ describe User, " when indexing requests by user they are from" do
end
it "should find requests from the user" do
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_by:bob_smith",
+ :sort_by_prefix => 'created_at', :sort_by_ascending => true, :limit => 100)
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
- # 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 = ActsAsXapian::Search.new([InfoRequestEvent], "requested_by:bob_smith variety:sent",
+ :sort_by_prefix => 'created_at', :sort_by_ascending => true, :limit => 100)
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)
@@ -150,8 +150,9 @@ describe User, " when indexing requests by user they are from" do
update_xapian_index
- # 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 = ActsAsXapian::Search.new([InfoRequestEvent], "requested_by:bob_smith",
+ :sort_by_prefix => 'created_at', :sort_by_ascending => true,
+ :collapse_by_prefix => 'request_collapse', :limit => 100)
xapian_object.results.map{|x|x[:model].info_request}.should =~ InfoRequest.all(:conditions => "user_id = #{users(:bob_smith_user).id}")
end
@@ -172,8 +173,7 @@ describe User, " when indexing requests by user they are from" do
update_xapian_index
- # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page)
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:john_k", 'created_at', true, 'request_collapse', 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_by:john_k", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model].should == info_request_events(:silly_outgoing_message_event)
end
@@ -181,7 +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
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_by:bob_smith",
+ :sort_by_prefix => 'created_at', :sort_by_ascending => true, :limit => 100)
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] }
@@ -193,9 +194,10 @@ describe User, " when indexing requests by user they are from" do
update_xapian_index
# check we get results expected
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_by:bob_smith", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:robert_smith", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "requested_by:robert_smith",
+ :sort_by_prefix => 'created_at', :sort_by_ascending => true, :limit => 100)
models_found_after = xapian_object.results.map { |x| x[:model] }
models_found_before.should == models_found_after
end
@@ -208,13 +210,13 @@ describe User, " when indexing comments by user they are by" do
end
it "should find requests from the user" do
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "commented_by:silly_emnameem", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "commented_by:silly_emnameem", :limit => 100)
xapian_object.results.size.should == 1
end
it "should update index correctly when URL name of user changes" do
# initial search
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "commented_by:silly_emnameem", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "commented_by:silly_emnameem", :limit => 100)
xapian_object.results.size.should == 1
models_found_before = xapian_object.results.map { |x| x[:model] }
@@ -226,9 +228,9 @@ describe User, " when indexing comments by user they are by" do
update_xapian_index
# check we get results expected
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "commented_by:silly_emnameem", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "commented_by:silly_emnameem", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "commented_by:silly_name", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "commented_by:silly_name", :limit => 100)
xapian_object.results.size.should == 1
models_found_after = xapian_object.results.map { |x| x[:model] }
@@ -243,7 +245,7 @@ describe InfoRequest, " when indexing requests by their title" do
end
it "should find events for the request" do
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "request:how_much_public_money_is_wasted_o", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "request:how_much_public_money_is_wasted_o", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model] == info_request_events(:silly_outgoing_message_event)
end
@@ -257,9 +259,9 @@ describe InfoRequest, " when indexing requests by their title" do
update_xapian_index
# check we get results expected
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "request:how_much_public_money_is_wasted_o", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "request:how_much_public_money_is_wasted_o", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "request:really_naughty", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "request:really_naughty", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model] == info_request_events(:silly_outgoing_message_event)
end
@@ -277,11 +279,11 @@ describe InfoRequest, " when indexing requests by tag" do
ir.save!
update_xapian_index
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "tag:bunnyrabbit", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "tag:bunnyrabbit", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model] == info_request_events(:silly_outgoing_message_event)
- xapian_object = InfoRequest.full_search([InfoRequestEvent], "tag:orangeaardvark", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], "tag:orangeaardvark", :limit => 100)
xapian_object.results.size.should == 0
end
end
@@ -298,14 +300,14 @@ describe PublicBody, " when indexing authorities by tag" do
body.save!
update_xapian_index
- xapian_object = InfoRequest.full_search([PublicBody], "tag:mice", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "tag:mice", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model] == public_bodies(:geraldine_public_body)
- xapian_object = InfoRequest.full_search([PublicBody], "tag:mice:3", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "tag:mice:3", :limit => 100)
xapian_object.results.size.should == 1
xapian_object.results[0][:model] == public_bodies(:geraldine_public_body)
- xapian_object = InfoRequest.full_search([PublicBody], "tag:orangeaardvark", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "tag:orangeaardvark", :limit => 100)
xapian_object.results.size.should == 0
end
end
@@ -327,11 +329,11 @@ describe PublicBody, " when only indexing selected things on a rebuild" do
values = false
texts = false
rebuild_xapian_index(terms, values, texts, dropfirst)
- xapian_object = InfoRequest.full_search([PublicBody], "tag:mice", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "tag:mice", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([PublicBody], "frobzn", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "frobzn", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([PublicBody], "variety:authority", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "variety:authority", :limit => 100)
xapian_object.results.map{|x|x[:model]}.should =~ PublicBody.all
# only reindex 'tag' and text
dropfirst = true
@@ -339,32 +341,57 @@ describe PublicBody, " when only indexing selected things on a rebuild" do
values = false
texts = true
rebuild_xapian_index(terms, values, texts, dropfirst)
- xapian_object = InfoRequest.full_search([PublicBody], "tag:mice", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "tag:mice", :limit => 100)
xapian_object.results.size.should == 1
- xapian_object = InfoRequest.full_search([PublicBody], "frobzn", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "frobzn", :limit => 100)
xapian_object.results.size.should == 1
- xapian_object = InfoRequest.full_search([PublicBody], "variety:authority", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "variety:authority", :limit => 100)
xapian_object.results.size.should == 0
# only reindex 'variety' term, but keeping the existing data in-place
dropfirst = false
terms = "V"
texts = false
rebuild_xapian_index(terms, values, texts, dropfirst)
- xapian_object = InfoRequest.full_search([PublicBody], "tag:mice", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "tag:mice", :limit => 100)
xapian_object.results.size.should == 1
- xapian_object = InfoRequest.full_search([PublicBody], "frobzn", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "frobzn", :limit => 100)
xapian_object.results.size.should == 1
- xapian_object = InfoRequest.full_search([PublicBody], "variety:authority", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "variety:authority", :limit => 100)
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)
- xapian_object = InfoRequest.full_search([PublicBody], "tag:mice", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "tag:mice", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([PublicBody], "frobzn", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "frobzn", :limit => 100)
xapian_object.results.size.should == 0
- xapian_object = InfoRequest.full_search([PublicBody], "variety:authority", 'created_at', true, nil, 100, 1)
+ xapian_object = ActsAsXapian::Search.new([PublicBody], "variety:authority", :limit => 100)
xapian_object.results.map{|x|x[:model]}.should =~ PublicBody.all
end
end
+# I would expect ActsAsXapian to have some tests under vendor/plugins/acts_as_xapian, but
+# it looks like this is not the case. Putting a test here instead.
+describe ActsAsXapian::Search, "#words_to_highlight" do
+ it "should return a list of words used in the search" do
+ s = ActsAsXapian::Search.new([PublicBody], "albatross words", :limit => 100)
+ s.words_to_highlight.should == ["albatross", "words"]
+ end
+
+ it "should remove any operators" do
+ s = ActsAsXapian::Search.new([PublicBody], "albatross words tag:mice", :limit => 100)
+ s.words_to_highlight.should == ["albatross", "words"]
+ end
+
+ # This is the current behaviour but it seems a little simplistic to me
+ it "should separate punctuation" do
+ s = ActsAsXapian::Search.new([PublicBody], "The doctor's patient", :limit => 100)
+ s.words_to_highlight.should == ["The", "doctor", "s", "patient"]
+ end
+
+ it "should handle non-ascii characters" do
+ s = ActsAsXapian::Search.new([PublicBody], "adatigénylés words tag:mice", :limit => 100)
+ s.words_to_highlight.should == ["adatigénylés", "words"]
+ end
+
+end
diff --git a/spec/spec.opts b/spec/spec.opts
deleted file mode 100644
index ff5051c37..000000000
--- a/spec/spec.opts
+++ /dev/null
@@ -1,3 +0,0 @@
---colour
---format
-specdoc
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 57ab88da2..a3b06cea8 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,7 +1,19 @@
require 'rubygems'
require 'spork'
+
#uncomment the following line to use spork with the debugger
#require 'spork/ext/ruby-debug'
+require 'simplecov'
+require 'coveralls'
+# Generate coverage locally in html as well as in coveralls.io
+SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
+ SimpleCov::Formatter::HTMLFormatter,
+ Coveralls::SimpleCov::Formatter
+]
+SimpleCov.start('rails') do
+ add_filter 'commonlib'
+ add_filter 'vendor/plugins'
+end
Spork.prefork do
# Loading more in this block will cause your tests to run faster. However,
diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb
index 7e98c39f6..252b1f137 100644
--- a/spec/support/email_helpers.rb
+++ b/spec/support/email_helpers.rb
@@ -8,7 +8,7 @@ end
def receive_incoming_mail(email_name, email_to, email_from = 'geraldinequango@localhost')
email_name = file_fixture_name(email_name)
- content = File.read(email_name)
+ content = File.open(email_name, 'rb') { |f| f.read }
content.gsub!('EMAIL_TO', email_to)
content.gsub!('EMAIL_FROM', email_from)
RequestMailer.receive(content)
diff --git a/spec/support/load_file_fixtures.rb b/spec/support/load_file_fixtures.rb
index 08079f654..a54505e99 100644
--- a/spec/support/load_file_fixtures.rb
+++ b/spec/support/load_file_fixtures.rb
@@ -2,13 +2,7 @@ def file_fixture_name(file_name)
return File.join(RSpec.configuration.fixture_path, "files", file_name)
end
-def load_file_fixture(file_name, as_binary=false)
+def load_file_fixture(file_name)
file_name = file_fixture_name(file_name)
- content = File.open(file_name, 'r') do |file|
- if as_binary
- file.set_encoding(Encoding::BINARY) if file.respond_to?(:set_encoding)
- end
- file.read
- end
- return content
+ return File.open(file_name, 'rb') { |f| f.read }
end
diff --git a/spec/views/reports/new.erb_spec.rb b/spec/views/reports/new.erb_spec.rb
new file mode 100644
index 000000000..66b738261
--- /dev/null
+++ b/spec/views/reports/new.erb_spec.rb
@@ -0,0 +1,29 @@
+require File.expand_path(File.join('..', '..', '..', 'spec_helper'), __FILE__)
+
+describe 'reports/new.html.erb' do
+ let(:info_request) { mock_model(InfoRequest, :url_title => "foo", :report_reasons => ["Weird"]) }
+ before :each do
+ assign(:info_request, info_request)
+ end
+
+ it "should show a form" do
+ render
+ rendered.should have_selector("form")
+ end
+
+ context "request has already been reported" do
+ before :each do
+ info_request.stub!(:attention_requested).and_return(true)
+ end
+
+ it "should not show a form" do
+ render
+ rendered.should_not have_selector("form")
+ end
+
+ it "should say it's already been reported" do
+ render
+ rendered.should contain("This request has already been reported")
+ end
+ end
+end