From 8002200321139053ed4787074ea1c2b80db6a8a4 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Thu, 22 Dec 2016 09:27:24 +0000
Subject: [PATCH 001/311] Render status text from ruby.
---
app/views/widgets/show.html.erb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/views/widgets/show.html.erb b/app/views/widgets/show.html.erb
index a3c8b72173..91f82a2f17 100644
--- a/app/views/widgets/show.html.erb
+++ b/app/views/widgets/show.html.erb
@@ -20,7 +20,7 @@
<%= link_to @info_request.title, request_path(@info_request), :target => "_top" %>
-
_("Status")
+
<%=_("Status") %>
<%= status_description(@info_request, @status) %>
From 21a03aa91e716d78a26e3084beb2434b031f1a6c Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 23 Dec 2016 10:58:39 +0000
Subject: [PATCH 002/311] Remove deprecated stub syntax.
---
spec/controllers/user_controller_spec.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/spec/controllers/user_controller_spec.rb b/spec/controllers/user_controller_spec.rb
index 67a34eb4d5..a5601fb7f0 100644
--- a/spec/controllers/user_controller_spec.rb
+++ b/spec/controllers/user_controller_spec.rb
@@ -755,7 +755,7 @@ def do_signin(email, password)
limiter = double
allow(limiter).to receive(:record)
allow(limiter).to receive(:limit?).and_return(true)
- controller.stub(:ip_rate_limiter).and_return(limiter)
+ allow(controller).to receive(:ip_rate_limiter).and_return(limiter)
end
it 'blocks the signup' do
From 56e553696dd8e0628b8fc6eb9326b99565bb3f8f Mon Sep 17 00:00:00 2001
From: Gareth Rees
Date: Wed, 11 Jan 2017 10:02:53 +0000
Subject: [PATCH 003/311] Clarify admin info message
9e215bd added a note to address the problem where we can't fall back to
the default locale's request_email attribute. What I didn't understand
at the time was that it _does_ fall back when there are _no_
translations.
This is because when there are no translations for a locale, there's no
PublicBody::Translation record. When there are translations, it just
finds an empty field from the record and returns that.
Ticket https://github.com/mysociety/alaveteli/issues/3134.
---
app/views/admin_public_body/edit.html.erb | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/views/admin_public_body/edit.html.erb b/app/views/admin_public_body/edit.html.erb
index 921204919f..11a573820f 100644
--- a/app/views/admin_public_body/edit.html.erb
+++ b/app/views/admin_public_body/edit.html.erb
@@ -31,7 +31,8 @@
<% if @public_body.ordered_translations.many? %>
- Make sure you add the request email for all locales.
+ Make sure you add the request email for all locales that have a
+ translation.
<% end %>
From aa317212a616557f4cc59ecc0d184007cb4f7776 Mon Sep 17 00:00:00 2001
From: Gareth Rees
Date: Thu, 19 Jan 2017 13:40:09 +0000
Subject: [PATCH 004/311] Prevent body name overflowing on stats axis labels
For the axis labels prefer the body short name (full name appears in the
tooltip, and the HTML table), and truncate it if its still too long.
Fixes https://github.com/mysociety/alaveteli/issues/1485
Fixes https://github.com/mysociety/asktheeu-theme/issues/60
---
app/models/statistics.rb | 2 +-
doc/CHANGES.md | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/models/statistics.rb b/app/models/statistics.rb
index e278a01195..a8ce2a9cc7 100644
--- a/app/models/statistics.rb
+++ b/app/models/statistics.rb
@@ -89,7 +89,7 @@ def simplify_stats_for_graphs(data,
# tooltips, and so on:
data['public_bodies'].each_with_index do |pb, i|
result['x_values'] << i
- result['x_ticks'] << [i, pb.name]
+ result['x_ticks'] << [i, pb.short_or_long_name.truncate(30)]
result['tooltips'] << "#{pb.name} (#{result['totals'][i]})"
result['public_bodies'] << {
'name' => pb.name,
diff --git a/doc/CHANGES.md b/doc/CHANGES.md
index 005cb72a62..bf08e4073b 100644
--- a/doc/CHANGES.md
+++ b/doc/CHANGES.md
@@ -2,6 +2,8 @@
## Highlighted Features
+* Prevent long authority names overflowing on statistics page (Gareth Rees)
+
## Upgrade Notes
### Changed Templates
From 8247253e3aa4be394c076d98979a5e15a2d00702 Mon Sep 17 00:00:00 2001
From: lizconlan
Date: Mon, 23 Jan 2017 10:41:22 +0000
Subject: [PATCH 005/311] Force visually-hidden elements to act as block-level
elements
---
app/assets/stylesheets/responsive/_utils.scss | 1 +
doc/CHANGES.md | 2 ++
2 files changed, 3 insertions(+)
diff --git a/app/assets/stylesheets/responsive/_utils.scss b/app/assets/stylesheets/responsive/_utils.scss
index 80d95b1aec..5011dacbd9 100644
--- a/app/assets/stylesheets/responsive/_utils.scss
+++ b/app/assets/stylesheets/responsive/_utils.scss
@@ -64,6 +64,7 @@ $lte-ie7: false !default;
height: 1px !important;
width: 1px !important;
overflow: hidden;
+ display: block;
}
body:hover .visually-hidden a, body:hover .visually-hidden input, body:hover .visually-hidden button { display: none !important; }
diff --git a/doc/CHANGES.md b/doc/CHANGES.md
index 8d21b559c5..1b98c103be 100644
--- a/doc/CHANGES.md
+++ b/doc/CHANGES.md
@@ -4,6 +4,8 @@
## Highlighted Features
* Prevent long authority names overflowing on statistics page (Gareth Rees)
+* Fix css bug which allowed some "visually-hidden" elements to affect page
+ length (Liz Conlan)
## Upgrade Notes
From 5a679aa60074b600cf0ccf21f4616f20b5f66588 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 6 Feb 2017 15:17:59 +0000
Subject: [PATCH 006/311] Add translators' note for string which can't contain
double quotes
This was added to app.po in 63055c520c48ec271b0e8d57352e683902824464,
but that is erased when updating translation files from Transifex.
This should persist.
---
app/models/info_request.rb | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 27209399f8..013046f351 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -740,6 +740,8 @@ def recipient_email_valid_for_followup?
def recipient_name_and_email
MailHandler.address_from_name_and_email(
+ # TRANSLATORS: Please don't use double quotes (") in this translation
+ # or it will break the site's ability to send emails to authorities!
_("{{law_used}} requests at {{public_body}}",
:law_used => law_used_human(:short),
:public_body => public_body.short_or_long_name),
From f9f0d30a94defb49353bf6bc0bbba5fd92dc760f Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 6 Feb 2017 14:31:27 +0000
Subject: [PATCH 007/311] Update newrelic_rpm.
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index f5ab771a2f..856072b324 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -215,7 +215,7 @@ GEM
net-ssh (2.6.7)
net-ssh-gateway (1.2.0)
net-ssh (>= 2.6.5)
- newrelic_rpm (3.17.1.326)
+ newrelic_rpm (3.18.0.329)
nokogiri (1.6.8)
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
From af672d19851df126ea101f899b5ab544070c6508 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 14 Oct 2016 14:49:37 +0100
Subject: [PATCH 008/311] Add some specs for InfoRequest#make_zip_cache_path
---
spec/models/info_request_spec.rb | 138 +++++++++++++++++++++++++++++++
1 file changed, 138 insertions(+)
diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb
index d103964455..5b9aabbaa8 100644
--- a/spec/models/info_request_spec.rb
+++ b/spec/models/info_request_spec.rb
@@ -2616,6 +2616,143 @@ def apply_filters(filters)
end
+ describe "making a zip cache path for a user" do
+ let(:non_owner) { FactoryGirl.create(:user) }
+ let(:owner) { request.user }
+ let(:admin) { FactoryGirl.create(:admin_user) }
+
+ let(:base_path) do
+ File.join(Rails.root, "cache", "zips", "test", "download", "123",
+ "123456", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3")
+ end
+ let(:path) { File.join(base_path, "test.zip") }
+ let(:hidden_path) { File.join(base_path, "test_hidden.zip") }
+ let(:requester_only_path) { File.join(base_path, "test_requester_only.zip") }
+
+ # Slightly confusing - this runs *after* the let(:request) in each context
+ # below, so it's ok
+ before do
+ # Digest::SHA1.hexdigest("test")
+ test_hash = "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"
+ allow(request).to receive(:last_update_hash).and_return(test_hash)
+ end
+
+ shared_examples_for "a situation when everything is public" do
+ it "doesn't add a suffix for anyone" do
+ expect(request.make_zip_cache_path(nil)).to eq (path)
+ expect(request.make_zip_cache_path(non_owner)).to eq (path)
+ expect(request.make_zip_cache_path(admin)).to eq (path)
+ expect(request.make_zip_cache_path(owner)).to eq (path)
+ end
+ end
+
+ shared_examples_for "a situation when anything is not public" do
+ it "doesn't add a suffix for anonymous users" do
+ expect(request.make_zip_cache_path(nil)).to eq (path)
+ end
+
+ it "doesn't add a suffix for non owner users" do
+ expect(request.make_zip_cache_path(non_owner)).to eq (path)
+ end
+
+ it "adds a _hidden suffix for admin users" do
+ expect(request.make_zip_cache_path(admin)).to eq (hidden_path)
+ end
+
+ it "adds a requester_only suffix for owner users" do
+ expect(request.make_zip_cache_path(owner)).to eq (requester_only_path)
+ end
+ end
+
+ shared_examples_for "a request when any correspondence is not public" do
+ context "when an incoming message is hidden" do
+ before do
+ incoming = request.incoming_messages.first
+ incoming.prominence = "hidden"
+ incoming.save!
+ end
+
+ it_behaves_like "a situation when anything is not public"
+ end
+
+ context "when an incoming message is requester_only" do
+ before do
+ incoming = request.incoming_messages.first
+ incoming.prominence = "requester_only"
+ incoming.save!
+ end
+
+ it_behaves_like "a situation when anything is not public"
+ end
+
+ context "when an outgoing message is hidden" do
+ before do
+ outgoing = request.outgoing_messages.first
+ outgoing.prominence = "hidden"
+ outgoing.save!
+ end
+
+ it_behaves_like "a situation when anything is not public"
+ end
+
+ context "when an outgoing message is requester_only" do
+ before do
+ outgoing = request.outgoing_messages.first
+ outgoing.prominence = "requester_only"
+ outgoing.save!
+ end
+
+ it_behaves_like "a situation when anything is not public"
+ end
+ end
+
+ shared_examples_for "a request when anything is not public" do
+ context "when the request is not public but the correspondence is" do
+ it_behaves_like "a situation when anything is not public"
+ end
+
+ context "when the request is not public and neither is the correspondence" do
+ it_behaves_like "a request when any correspondence is not public"
+ end
+ end
+
+ context "when the request is public" do
+ let(:request) do
+ FactoryGirl.create(:info_request_with_incoming, id: 123456,
+ title: "test")
+ end
+
+ context "when all correspondence is public" do
+ it_behaves_like "a situation when everything is public"
+ end
+
+ it_behaves_like "a request when any correspondence is not public"
+ end
+
+ context "when the request is hidden" do
+ let(:request) do
+ FactoryGirl.create(:info_request_with_incoming, id: 123456,
+ title: "test",
+ prominence: "hidden")
+ end
+
+ it_behaves_like "a request when anything is not public"
+ end
+
+ context "when the request is requester_only" do
+ let(:request) do
+ FactoryGirl.create(
+ :info_request_with_incoming,
+ id: 123456,
+ title: "test",
+ prominence: "requester_only"
+ )
+ end
+
+ it_behaves_like "a request when anything is not public"
+ end
+ end
+
def email_and_raw_email(opts = {})
raw_email = opts[:raw_email] || <<-EOF.strip_heredoc
From: EMAIL_FROM
@@ -2634,4 +2771,5 @@ def email_and_raw_email(opts = {})
[email, raw_email]
end
+
end
From ab9734ce6ee48a3d725bee3a72ef189335dcef80 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 14 Oct 2016 18:00:35 +0100
Subject: [PATCH 009/311] Update request controller specs for authorization
Switching session ids during a single spec breaks CanCanCan, but these are
better as single-expectation specs anyway, so I've separated them
---
spec/controllers/request_controller_spec.rb | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index dec1a6b758..422c55093c 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -828,21 +828,25 @@ def expect_hidden(hidden_template)
expect_hidden('hidden')
end
- it "should show request to requester and admin if logged in" do
+ it "should not show request if logged in but not the requester" do
session[:user_id] = FactoryGirl.create(:user).id
get :show, :url_title => @info_request.url_title
expect_hidden('hidden')
+ end
+ it "should show request to requester" do
session[:user_id] = @info_request.user.id
get :show, :url_title => @info_request.url_title
expect(response).to render_template('show')
+ end
+ it "shouild show request to admin" do
session[:user_id] = FactoryGirl.create(:admin_user).id
get :show, :url_title => @info_request.url_title
expect(response).to render_template('show')
end
- it 'should not cache an attachment when showing an attachment to the requester or admin' do
+ it 'should not cache an attachment when showing an attachment to the requester' do
session[:user_id] = @info_request.user.id
incoming_message = @info_request.incoming_messages.first
expect(@controller).not_to receive(:foi_fragment_cache_write)
@@ -851,6 +855,16 @@ def expect_hidden(hidden_template)
:part => 2,
:file_name => 'interesting.pdf'
end
+
+ it 'should not cache an attachment when showing an attachment to the admin' do
+ session[:user_id] = FactoryGirl.create(:admin_user).id
+ incoming_message = @info_request.incoming_messages.first
+ expect(@controller).not_to receive(:foi_fragment_cache_write)
+ get :get_attachment, :incoming_message_id => incoming_message.id,
+ :id => @info_request.id,
+ :part => 2,
+ :file_name => 'interesting.pdf'
+ end
end
context 'when the incoming message has prominence hidden' do
From ce5588139a779ef9b9ce9a5203f4492dde06e1ed Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 17 Oct 2016 16:38:06 +0100
Subject: [PATCH 010/311] Improve comments in request download spec
---
spec/integration/download_request_spec.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/spec/integration/download_request_spec.rb b/spec/integration/download_request_spec.rb
index b075e0cd0d..fdffcd30da 100644
--- a/spec/integration/download_request_spec.rb
+++ b/spec/integration/download_request_spec.rb
@@ -343,7 +343,7 @@ def sleep_and_receive_mail(name, info_request)
it 'should not include the outgoing message in a download of the entire request
by a non-request owner but should retain them for owner and admin' do
- # Non-owner can download zip with incoming and attachments
+ # Non-owner can download zip with original message initially
non_owner = login(FactoryGirl.create(:user))
info_request = FactoryGirl.create(:info_request)
@@ -352,7 +352,7 @@ def sleep_and_receive_mail(name, info_request)
expect(zip.read('correspondence.txt')).to match('Some information please')
end
- # Admin makes the incoming message requester only
+ # Admin makes the outgoing message requester only
admin = login(FactoryGirl.create(:admin_user))
using_session(admin) do
From 68d85fc8f4880630fcbdf83d54a7891cecb989a9 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 17 Oct 2016 17:02:42 +0100
Subject: [PATCH 011/311] Fix bug in old_unclassified_request factory
The request this created wouldn't actually pass the
is_old_unclassified_request? test because the incoming message isn't old
enough.
---
spec/factories/info_requests.rb | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/spec/factories/info_requests.rb b/spec/factories/info_requests.rb
index c961624e5d..7c06af4184 100644
--- a/spec/factories/info_requests.rb
+++ b/spec/factories/info_requests.rb
@@ -73,8 +73,20 @@
factory :old_unclassified_request do
after(:create) do |info_request, evaluator|
- incoming_message = FactoryGirl.create(:incoming_message, :info_request => info_request)
- info_request.log_event("response", {:incoming_message_id => incoming_message.id})
+ incoming_message = FactoryGirl.create(
+ :incoming_message,
+ :info_request => info_request,
+ :created_at => Time.now - 31.days
+ )
+ info_request.info_request_events = [
+ FactoryGirl.create(
+ :info_request_event,
+ :info_request => info_request,
+ :event_type => "response",
+ :incoming_message_id => incoming_message.id,
+ :created_at => Time.now - 31.days
+ )
+ ]
info_request.last_public_response_at = Time.now - 31.days
info_request.awaiting_description = true
info_request.save!
From fa65048da5cde2cb863f53ed415abb7038c91c72 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Wed, 12 Oct 2016 08:45:09 +0100
Subject: [PATCH 012/311] Add CanCanCan gem an current_user method for it
---
Gemfile | 1 +
Gemfile.lock | 2 ++
app/controllers/application_controller.rb | 3 +++
3 files changed, 6 insertions(+)
diff --git a/Gemfile b/Gemfile
index e6d0706ec0..c72580e06a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,7 @@ gem 'pg', '~> 0.18.4'
# New gem releases aren't being done. master is newer and supports Rails > 3.0
gem 'acts_as_versioned', :git => 'https://github.com/technoweenie/acts_as_versioned.git', :ref => '63b1fc8529d028'
gem 'active_model_otp', :git => 'https://github.com/heapsource/active_model_otp.git', :ref => 'c342283fe564bf'
+gem 'cancancan', '1.12' # Pinned because 1.13 onwards don't support Ruby 1.9
gem 'charlock_holmes', '~> 0.7.3'
gem 'dynamic_form', '~> 1.1.4'
# 4.1.0 has a bug in it which is fixed in a later version which does not have Ruby 1.9.3 support
diff --git a/Gemfile.lock b/Gemfile.lock
index 856072b324..b4347e933c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -83,6 +83,7 @@ GEM
bullet (5.1.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
+ cancancan (1.12.0)
capistrano (2.15.4)
highline
net-scp (>= 1.0.0)
@@ -373,6 +374,7 @@ DEPENDENCIES
annotate (~> 2.7.1)
bootstrap-sass (~> 2.3.2.2)
bullet (~> 5.1.0)
+ cancancan (= 1.12)
capistrano (~> 2.15.4)
capybara (~> 2.7.0)
charlock_holmes (~> 0.7.3)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 40974b3068..401f5241c8 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -294,6 +294,9 @@ def authenticated_user
end
end
+ # For CanCanCan and other libs which need a Devise-like current_user method
+ alias_method :current_user, :authenticated_user
+
# Do a POST redirect. This is a nasty hack - we store the posted values in
# the session, and when the GET redirect with "?post_redirect=1" happens,
# load them in.
From 7ae22470de386f8171e35b6fc764ad72b09e2fc2 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 17 Oct 2016 16:45:03 +0100
Subject: [PATCH 013/311] Refactor lib/ability.rb into a CanCanCan Ability
This turns all uses of the Ability class' methods into instances of can? or
cannot? from CanCanCan. In addition, a couple of specs had to be modified
because CanCanCan needs to see what class an object is (so it doesn't work
with simple doubles fro RSpec).
---
app/controllers/followups_controller.rb | 2 +-
.../delivery_statuses_controller.rb | 4 +-
app/controllers/request_controller.rb | 18 +-
app/models/ability.rb | 66 ++++++++
app/models/info_request.rb | 36 ++--
.../request/_incoming_correspondence.html.erb | 4 +-
.../request/_incoming_correspondence.text.erb | 2 +-
.../request/_outgoing_correspondence.html.erb | 2 +-
.../request/_outgoing_correspondence.text.erb | 2 +-
lib/ability.rb | 17 --
lib/message_prominence.rb | 4 -
.../delivery_statuses_controller_spec.rb | 44 ++---
spec/controllers/request_controller_spec.rb | 6 +-
spec/lib/ability_spec.rb | 51 ------
spec/models/ability_spec.rb | 156 ++++++++++++++++++
spec/models/incoming_message_spec.rb | 57 -------
spec/models/outgoing_message_spec.rb | 52 ------
17 files changed, 287 insertions(+), 236 deletions(-)
create mode 100644 app/models/ability.rb
delete mode 100644 lib/ability.rb
delete mode 100644 spec/lib/ability_spec.rb
create mode 100644 spec/models/ability_spec.rb
diff --git a/app/controllers/followups_controller.rb b/app/controllers/followups_controller.rb
index fb1011a51f..8bcfc3ab0d 100644
--- a/app/controllers/followups_controller.rb
+++ b/app/controllers/followups_controller.rb
@@ -91,7 +91,7 @@ def check_user_credentials
render :template => 'user/banned'
return
end
- if authenticated_user && !@info_request.user_can_view?(authenticated_user)
+ if authenticated_user && cannot?(:read, @info_request)
return render_hidden
end
end
diff --git a/app/controllers/outgoing_messages/delivery_statuses_controller.rb b/app/controllers/outgoing_messages/delivery_statuses_controller.rb
index 14c32f91be..91be638bc2 100644
--- a/app/controllers/outgoing_messages/delivery_statuses_controller.rb
+++ b/app/controllers/outgoing_messages/delivery_statuses_controller.rb
@@ -26,8 +26,8 @@ def set_outgoing_message
end
def check_prominence
- unless @outgoing_message.user_can_view?(@user) &&
- @outgoing_message.info_request.user_can_view?(@user)
+ unless can?(:read, @outgoing_message) && \
+ can?(:read, @outgoing_message.info_request)
return render_hidden('request/_hidden_correspondence',
:locals => { :message => @outgoing_message })
end
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 4fc24e80f0..5e3edf441e 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -90,7 +90,7 @@ def show
@info_request = InfoRequest.find_by_url_title!(params[:url_title])
# Test for whole request being hidden
- if !@info_request.user_can_view?(authenticated_user)
+ if cannot?(:read, @info_request)
return render_hidden
end
@@ -139,7 +139,7 @@ def show
def details
long_cache
@info_request = InfoRequest.find_by_url_title!(params[:url_title])
- if !@info_request.user_can_view?(authenticated_user)
+ if cannot?(:read, @info_request)
return render_hidden
end
@columns = ['id', 'event_type', 'created_at', 'described_state', 'last_described_at', 'calculated_state' ]
@@ -158,7 +158,7 @@ def similar
@info_request = InfoRequest.find_by_url_title!(params[:url_title])
raise ActiveRecord::RecordNotFound.new("Request not found") if @info_request.nil?
- if !@info_request.user_can_view?(authenticated_user)
+ if cannot?(:read, @info_request)
return render_hidden
end
@xapian_object = ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events,
@@ -447,7 +447,7 @@ def describe_state
end
# Check authenticated, and parameters set.
- unless Ability::can_update_request_state?(authenticated_user, info_request)
+ unless can?(:update_request_state, info_request)
authenticated_as_user?(
info_request.user,
:web => _("To classify the response to this FOI request"),
@@ -573,11 +573,11 @@ def authenticate_attachment
# Test for hidden
incoming_message = IncomingMessage.find(params[:incoming_message_id])
raise ActiveRecord::RecordNotFound.new("Message not found") if incoming_message.nil?
- if !incoming_message.info_request.user_can_view?(authenticated_user)
+ if cannot?(:read, incoming_message.info_request)
@info_request = incoming_message.info_request # used by view
return render_hidden
end
- if !incoming_message.user_can_view?(authenticated_user)
+ if cannot?(:read, incoming_message)
@incoming_message = incoming_message # used by view
return render_hidden('request/hidden_correspondence')
end
@@ -696,7 +696,7 @@ def get_attachment_internal(html_conversion)
end
# check permissions
- raise "internal error, pre-auth filter should have caught this" if !@info_request.user_can_view?(authenticated_user)
+ raise "internal error, pre-auth filter should have caught this" if cannot?(:read, @info_request)
@attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(@incoming_message.get_attachments_for_display, @part_number, @original_filename)
# If we can't find the right attachment, redirect to the incoming message:
unless @attachment
@@ -805,7 +805,7 @@ def download_entire_request
:info_request_title=>@info_request.title)
)
# Test for whole request being hidden or requester-only
- if !@info_request.user_can_view?(@user)
+ if cannot?(:read, @info_request)
return render_hidden
end
cache_file_path = @info_request.make_zip_cache_path(@user)
@@ -854,7 +854,7 @@ def make_request_zip(info_request, file_path)
zipfile.get_output_stream(file_info[:filename]) { |f| f.puts(file_info[:data]) }
message_index = 0
info_request.incoming_messages.each do |message|
- next unless message.user_can_view?(authenticated_user)
+ next unless can?(:read, message)
message_index += 1
message.get_attachments_for_display.each do |attachment|
filename = "#{message_index}_#{attachment.url_part_number}_#{attachment.display_filename}"
diff --git a/app/models/ability.rb b/app/models/ability.rb
new file mode 100644
index 0000000000..79350423c4
--- /dev/null
+++ b/app/models/ability.rb
@@ -0,0 +1,66 @@
+class Ability
+ include CanCan::Ability
+
+ def initialize(user)
+ # Define abilities for the passed in user here. For example:
+ #
+ # user ||= User.new # guest user (not logged in)
+ # if user.admin?
+ # can :manage, :all
+ # else
+ # can :read, :all
+ # end
+ #
+ # The first argument to `can` is the action you are giving the user
+ # permission to do.
+ # If you pass :manage it will apply to every action. Other common actions
+ # here are :read, :create, :update and :destroy.
+ #
+ # The second argument is the resource the user can perform the action on.
+ # If you pass :all it will apply to every resource. Otherwise pass a Ruby
+ # class of the resource.
+ #
+ # The third argument is an optional hash of conditions to further filter the
+ # objects.
+ # For example, here the user can only update published articles.
+ #
+ # can :update, Article, :published => true
+ #
+ # See the wiki for details:
+ # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
+
+ # Updating request status
+ can :update_request_state, InfoRequest do |request|
+ self.class.can_update_request_state?(user, request)
+ end
+
+ # Viewing messages with prominence
+ can :read, [IncomingMessage, OutgoingMessage] do |msg|
+ self.class.can_view_with_prominence?(msg.prominence,
+ msg.info_request,
+ user)
+ end
+
+ # Viewing requests with prominence
+ can :read, InfoRequest do |request|
+ self.class.can_view_with_prominence?(request.prominence, request, user)
+ end
+ end
+
+ private
+
+ def self.can_update_request_state?(user, request)
+ (user && request.is_old_unclassified?) || request.is_owning_user?(user)
+ end
+
+ def self.can_view_with_prominence?(prominence, info_request, user)
+ if prominence == 'hidden'
+ return User.view_hidden?(user)
+ end
+ if prominence == 'requester_only'
+ return info_request.is_owning_user?(user)
+ end
+ return true
+ end
+
+end
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 013046f351..0b768ec489 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -1018,22 +1018,36 @@ def download_zip_dir
end
def make_zip_cache_path(user)
+ # The zip file varies depending on user because it can include different
+ # messages depending on whether the user can access hidden or
+ # requester_only messages. We name it appropriately, so that every user
+ # with the right permissions gets a file with only the right things in.
cache_file_dir = File.join(InfoRequest.download_zip_dir,
"download",
request_dirs,
last_update_hash)
- cache_file_suffix = if all_can_view_all_correspondence?
- ""
- elsif Ability.can_view_with_prominence?('hidden', self, user)
- "_hidden"
- elsif Ability.can_view_with_prominence?('requester_only', self, user)
- "_requester_only"
- else
- ""
- end
+ cache_file_suffix = zip_cache_file_suffix(user)
File.join(cache_file_dir, "#{url_title}#{cache_file_suffix}.zip")
end
+ def zip_cache_file_suffix(user)
+ # Simple short circuit for requests where everything is public
+ if all_can_view_all_correspondence?
+ ""
+ # If the user can view hidden things, they can view anything, so no need
+ # to go any further
+ elsif User.view_hidden?(user)
+ "_hidden"
+ # If the user can't view hidden things, but owns the request, they can
+ # see more than the public, so they get requester_only
+ elsif is_owning_user?(user)
+ "_requester_only"
+ # Everyone else can only see public stuff, which is the default case
+ else
+ ""
+ end
+ end
+
def is_old_unclassified?
!is_external? && awaiting_description && url_title != 'holding_pen' && get_last_public_response_event &&
Time.now > get_last_public_response_event.created_at + OLD_AGE_IN_DAYS
@@ -1120,10 +1134,6 @@ def is_actual_owning_user?(user)
user.id == user_id
end
- def user_can_view?(user)
- Ability.can_view_with_prominence?(prominence, self, user)
- end
-
# Is this request visible to everyone?
def all_can_view?
%w(normal backpage).include?(prominence)
diff --git a/app/views/request/_incoming_correspondence.html.erb b/app/views/request/_incoming_correspondence.html.erb
index cfafb484fd..d993e0ad0d 100644
--- a/app/views/request/_incoming_correspondence.html.erb
+++ b/app/views/request/_incoming_correspondence.html.erb
@@ -15,7 +15,7 @@
- <%- if not incoming_message.user_can_view?(@user) %>
+ <%- if cannot?(:read, incoming_message) %>
<%= render :partial => 'request/hidden_correspondence', :locals => { :message => incoming_message } %>
<%- else %>
<%= render :partial => 'request/restricted_correspondence', :locals => {:message => incoming_message } %>
@@ -36,6 +36,6 @@
-
+
<%- end %>
diff --git a/app/views/request/_incoming_correspondence.text.erb b/app/views/request/_incoming_correspondence.text.erb
index 0d85f9246c..5ec57af7eb 100644
--- a/app/views/request/_incoming_correspondence.text.erb
+++ b/app/views/request/_incoming_correspondence.text.erb
@@ -1,4 +1,4 @@
-<%- if not incoming_message.user_can_view?(@user) %>
+<%- if cannot?(:read, incoming_message) %>
<%= render :partial => 'request/hidden_correspondence', :formats => 'text', :locals => { :message => incoming_message }%>
<%- else %>
<%= _('From:') %><% if incoming_message.specific_from_name? %> <%= incoming_message.safe_mail_from %><% end %><% if incoming_message.from_public_body? %>, <%= @info_request.public_body.name %><% end %>
diff --git a/app/views/request/_outgoing_correspondence.html.erb b/app/views/request/_outgoing_correspondence.html.erb
index 087418d461..3ca1823e0f 100644
--- a/app/views/request/_outgoing_correspondence.html.erb
+++ b/app/views/request/_outgoing_correspondence.html.erb
@@ -1,5 +1,5 @@
- <%- if not outgoing_message.user_can_view?(@user) %>
+ <%- if cannot?(:read, outgoing_message) %>
<%= render :partial => 'request/hidden_correspondence', :locals => { :message => outgoing_message } %>
<%- else %>
<%= render :partial => 'request/restricted_correspondence', :locals => {:message => outgoing_message } %>
diff --git a/app/views/request/_outgoing_correspondence.text.erb b/app/views/request/_outgoing_correspondence.text.erb
index 221e359e2d..1a41db311d 100644
--- a/app/views/request/_outgoing_correspondence.text.erb
+++ b/app/views/request/_outgoing_correspondence.text.erb
@@ -1,4 +1,4 @@
-<%- if not outgoing_message.user_can_view?(@user) %>
+<%- if cannot?(:read, outgoing_message) %>
<%= render :partial => 'request/hidden_correspondence', :formats => 'text', :locals => { :message => outgoing_message }%>
<%- else %>
<%= _('From:') %> <% if @info_request.user_name %><%= @info_request.user_name %><% else %><%= "[#{_('An anonymous user')}]"%><% end %>
diff --git a/lib/ability.rb b/lib/ability.rb
deleted file mode 100644
index c58950c0da..0000000000
--- a/lib/ability.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- encoding : utf-8 -*-
-module Ability
- def self.can_update_request_state?(user, request)
- (user && request.is_old_unclassified?) || request.is_owning_user?(user)
- end
-
- def self.can_view_with_prominence?(prominence, info_request, user)
- if prominence == 'hidden'
- return User.view_hidden?(user)
- end
- if prominence == 'requester_only'
- return info_request.is_owning_user?(user)
- end
- return true
- end
-
-end
diff --git a/lib/message_prominence.rb b/lib/message_prominence.rb
index af7f5a48ec..84b7934709 100644
--- a/lib/message_prominence.rb
+++ b/lib/message_prominence.rb
@@ -10,10 +10,6 @@ def has_prominence
module InstanceMethods
- def user_can_view?(user)
- Ability.can_view_with_prominence?(self.prominence, self.info_request, user)
- end
-
def indexed_by_search?
self.prominence == 'normal'
end
diff --git a/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb b/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb
index e0f4c7b68d..f50daf849e 100644
--- a/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb
+++ b/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb
@@ -21,8 +21,8 @@
it 'assigns the outgoing message' do
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -36,8 +36,8 @@
it 'renders hidden when the message cannot be viewed' do
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
- :user_can_view? => false,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'hidden',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => false,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -51,8 +51,8 @@
it 'renders hidden when the request cannot be viewed' do
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => false),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'hidden'}),
:is_owning_user? => false,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -66,8 +66,8 @@
it 'sets the title' do
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -87,8 +87,8 @@
session[:user_id] = FactoryGirl.create(:admin_user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -102,8 +102,8 @@
it 'sets show_mail_server_logs to true if the user is an owner' do
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -117,8 +117,8 @@
it 'sets show_mail_server_logs to false if the user is not an owner' do
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => false,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -137,8 +137,8 @@
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -157,8 +157,8 @@
session[:user_id] = FactoryGirl.create(:admin_user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -171,8 +171,8 @@
it 'does not assign mail server logs for a regular user' do
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => false,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -186,8 +186,8 @@
it 'renders the show template' do
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
- :user_can_view? => true,
- :info_request => double(:user_can_view? => true),
+ :prominence => 'normal',
+ :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 422c55093c..679bd307e8 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2305,15 +2305,15 @@ def expect_redirect(status, redirect_path)
describe RequestController, "when caching fragments" do
it "should not fail with long filenames" do
long_name = "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah.txt"
- info_request = double(InfoRequest, :user_can_view? => true,
- :all_can_view? => true)
+ info_request = double(InfoRequest, :prominence => 'normal',
+ :all_can_view? => true)
incoming_message = double(IncomingMessage, :info_request => info_request,
:parse_raw_email! => true,
:info_request_id => 132,
:id => 44,
:get_attachments_for_display => nil,
:apply_masks => nil,
- :user_can_view? => true,
+ :prominence => 'normal',
:all_can_view? => true)
attachment = FactoryGirl.build(:body_text, :filename => long_name)
allow(IncomingMessage).to receive(:find).with("44").and_return(incoming_message)
diff --git a/spec/lib/ability_spec.rb b/spec/lib/ability_spec.rb
deleted file mode 100644
index 86337cc96d..0000000000
--- a/spec/lib/ability_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- encoding : utf-8 -*-
-require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
-
-describe Ability do
- describe ".can_update_request_state?" do
- context "old and unclassified request" do
- let(:request) { mock_model(InfoRequest, :is_old_unclassified? => true) }
-
- context "logged out" do
- let(:user) { nil }
- before(:each) { allow(request).to receive(:is_owning_user?).and_return(false) }
- it { expect(Ability::can_update_request_state?(user, request)).to be false }
- end
-
- context "logged in but not owner of request" do
- let(:user) { mock_model(User) }
- before(:each) { allow(request).to receive(:is_owning_user?).and_return(false) }
-
- it { expect(Ability::can_update_request_state?(user, request)).to be true }
- end
- end
-
- context "new request" do
- let(:request) { mock_model(InfoRequest, :is_old_unclassified? => false) }
-
- context "logged out" do
- let(:user) { nil }
- before(:each) { allow(request).to receive(:is_owning_user?).and_return(false) }
-
- it { expect(Ability::can_update_request_state?(user, request)).to be false }
- end
-
- context "logged in" do
- let(:user) { mock_model(User) }
-
- # An owner of a request can also be someone with admin powers
- context "as owner of request" do
- before(:each) { allow(request).to receive(:is_owning_user?).and_return(true) }
-
- it { expect(Ability::can_update_request_state?(user, request)).to be true }
- end
-
- context "but not owner of request" do
- before(:each) { allow(request).to receive(:is_owning_user?).and_return(false) }
-
- it { expect(Ability::can_update_request_state?(user, request)).to be false }
- end
- end
- end
- end
-end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
new file mode 100644
index 0000000000..ead2eef5b8
--- /dev/null
+++ b/spec/models/ability_spec.rb
@@ -0,0 +1,156 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+require "cancan/matchers"
+
+shared_examples_for "a class with message prominence" do
+ let(:admin_ability) { Ability.new(FactoryGirl.create(:admin_user)) }
+ let(:other_user_ability) { Ability.new(FactoryGirl.create(:user)) }
+
+ context 'if the prominence is hidden' do
+ before do
+ resource.prominence = 'hidden'
+ end
+
+ it 'should return true for an admin user' do
+ expect(admin_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return false for a non-admin user' do
+ expect(other_user_ability).not_to be_able_to(:read, resource)
+ end
+
+ it 'should return false for the owner' do
+ expect(owner_ability).not_to be_able_to(:read, resource)
+ end
+ end
+
+ context 'if the prominence is requester_only' do
+ before do
+ resource.prominence = 'requester_only'
+ end
+
+ it 'should return true if the user owns the right resource' do
+ expect(owner_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return true for an admin user' do
+ expect(admin_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return false if the user does not own the right resource' do
+ expect(other_user_ability).not_to be_able_to(:read, resource)
+ end
+ end
+
+ context 'if the prominence is normal' do
+ before do
+ resource.prominence = 'normal'
+ end
+
+ it 'should return true for a non-admin user' do
+ expect(other_user_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return true for an admin user' do
+ expect(admin_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return true if the user owns the right resource' do
+ expect(owner_ability).to be_able_to(:read, resource)
+ end
+ end
+end
+
+describe Ability do
+ describe "reading IncomingMessages" do
+ let(:info_request) { FactoryGirl.create(:info_request_with_incoming) }
+ let!(:resource) { info_request.incoming_messages.first }
+ let!(:owner_ability) { Ability.new(info_request.user) }
+
+ it_behaves_like "a class with message prominence"
+ end
+
+ describe "reading OutgoingMessages" do
+ let(:info_request) { FactoryGirl.create(:info_request) }
+ let!(:resource) { info_request.outgoing_messages.first }
+ let!(:owner_ability) { Ability.new(info_request.user) }
+
+ it_behaves_like "a class with message prominence"
+ end
+
+ describe "reading InfoRequests" do
+ let!(:resource) { FactoryGirl.create(:info_request) }
+ let!(:owner_ability) { Ability.new(resource.user) }
+
+ it_behaves_like "a class with message prominence"
+ end
+
+ describe "updating request state of InfoRequests" do
+ context "given an old and unclassified request" do
+ let(:request) { FactoryGirl.create(:old_unclassified_request) }
+
+ context "when logged out" do
+ let(:ability) { Ability.new(nil) }
+
+ it "should return false" do
+ expect(ability).not_to be_able_to(:update_request_state, request)
+ end
+ end
+
+ context "when logged in but not owner of request" do
+ let(:user) { FactoryGirl.create(:user) }
+ let(:ability) { Ability.new(user) }
+
+ it "should return true" do
+ expect(ability).to be_able_to(:update_request_state, request)
+ end
+ end
+
+ context "when owner of request" do
+ let(:user) { request.user }
+ let(:ability) { Ability.new(user) }
+
+ it "should return true" do
+ expect(ability).to be_able_to(:update_request_state, request)
+ end
+ end
+ end
+
+ context "given a new request" do
+ let(:request) { FactoryGirl.create(:info_request) }
+
+ context "when logged out" do
+ let(:ability) { Ability.new(nil) }
+
+ it "should return false" do
+ expect(ability).not_to be_able_to(:update_request_state, request)
+ end
+ end
+
+ context "when logged in" do
+ context "as owner of request" do
+ let(:ability) { Ability.new(request.user) }
+
+ it "should return true" do
+ expect(ability).to be_able_to(:update_request_state, request)
+ end
+ end
+
+ context "as an admin" do
+ let(:ability) { Ability.new(FactoryGirl.create(:admin_user)) }
+
+ it "should return true" do
+ expect(ability).to be_able_to(:update_request_state, request)
+ end
+ end
+
+ context "but not owner of request" do
+ let(:ability) { Ability.new(FactoryGirl.create(:user)) }
+
+ it "should return false" do
+ expect(ability).not_to be_able_to(:update_request_state, request)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/incoming_message_spec.rb b/spec/models/incoming_message_spec.rb
index eea1db2b7e..95b9d3fe0a 100644
--- a/spec/models/incoming_message_spec.rb
+++ b/spec/models/incoming_message_spec.rb
@@ -400,63 +400,6 @@
end
-describe IncomingMessage, 'when asked if a user can view it' do
-
- before do
- @user = mock_model(User)
- @info_request = mock_model(InfoRequest)
- @incoming_message = IncomingMessage.new(:info_request => @info_request)
- end
-
- context 'if the prominence is hidden' do
-
- before do
- @incoming_message.prominence = 'hidden'
- end
-
- it 'should return true if the user can view hidden things' do
- allow(User).to receive(:view_hidden?).with(@user).and_return(true)
- expect(@incoming_message.user_can_view?(@user)).to be true
- end
-
- it 'should return false if the user cannot view hidden things' do
- allow(User).to receive(:view_hidden?).with(@user).and_return(false)
- expect(@incoming_message.user_can_view?(@user)).to be false
- end
-
- end
-
- context 'if the prominence is requester_only' do
-
- before do
- @incoming_message.prominence = 'requester_only'
- end
-
- it 'should return true if the user owns the associated request' do
- allow(@info_request).to receive(:is_owning_user?).with(@user).and_return(true)
- expect(@incoming_message.user_can_view?(@user)).to be true
- end
-
- it 'should return false if the user does not own the associated request' do
- allow(@info_request).to receive(:is_owning_user?).with(@user).and_return(false)
- expect(@incoming_message.user_can_view?(@user)).to be false
- end
- end
-
- context 'if the prominence is normal' do
-
- before do
- @incoming_message.prominence = 'normal'
- end
-
- it 'should return true' do
- expect(@incoming_message.user_can_view?(@user)).to be true
- end
-
- end
-
-end
-
describe 'when destroying a message' do
let(:incoming_message) { FactoryGirl.create(:plain_incoming_message) }
diff --git a/spec/models/outgoing_message_spec.rb b/spec/models/outgoing_message_spec.rb
index a528fff1d1..f8f9e580dc 100644
--- a/spec/models/outgoing_message_spec.rb
+++ b/spec/models/outgoing_message_spec.rb
@@ -842,58 +842,6 @@
end
- describe '#user_can_view?' do
-
- before do
- @info_request = FactoryGirl.create(:info_request)
- @outgoing_message = @info_request.outgoing_messages.first
- end
-
- context 'if the prominence is hidden' do
-
- before do
- @outgoing_message.prominence = 'hidden'
- end
-
- it 'should return true for an admin user' do
- expect(@outgoing_message.user_can_view?(FactoryGirl.create(:admin_user))).to be true
- end
-
- it 'should return false for a non-admin user' do
- expect(@outgoing_message.user_can_view?(FactoryGirl.create(:user))).to be false
- end
-
- end
-
- context 'if the prominence is requester_only' do
-
- before do
- @outgoing_message.prominence = 'requester_only'
- end
-
- it 'should return true if the user owns the associated request' do
- expect(@outgoing_message.user_can_view?(@info_request.user)).to be true
- end
-
- it 'should return false if the user does not own the associated request' do
- expect(@outgoing_message.user_can_view?(FactoryGirl.create(:user))).to be false
- end
- end
-
- context 'if the prominence is normal' do
-
- before do
- @outgoing_message.prominence = 'normal'
- end
-
- it 'should return true for a non-admin user' do
- expect(@outgoing_message.user_can_view?(FactoryGirl.create(:user))).to be true
- end
-
- end
-
- end
-
describe '#smtp_message_ids' do
context 'a sent message' do
From e0518c4f63bda8f02531bbcb6c23503fb023dc38 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 7 Oct 2016 09:31:40 +0100
Subject: [PATCH 014/311] Add an alaveteli_features gem
This adds a library module which provides access to a Flipper instance for
controlling feature flags, and a helper method feature_enabled? which allows
you to check if a feature is enabled in views and controllers.
---
gems/alaveteli_features/.gitignore | 17 +++++
gems/alaveteli_features/.rspec | 2 +
gems/alaveteli_features/Gemfile | 4 ++
gems/alaveteli_features/LICENSE.txt | 22 +++++++
gems/alaveteli_features/README.md | 62 +++++++++++++++++++
gems/alaveteli_features/Rakefile | 6 ++
.../alaveteli_features.gemspec | 32 ++++++++++
.../lib/alaveteli_features.rb | 28 +++++++++
.../install/install_generator.rb | 44 +++++++++++++
.../initializers/alaveteli_features.rb.erb | 8 +++
.../lib/alaveteli_features/helpers.rb | 7 +++
.../lib/alaveteli_features/railtie.rb | 17 +++++
.../lib/alaveteli_features/version.rb | 3 +
.../spec/alaveteli_features_spec.rb | 18 ++++++
.../spec/helpers/feature_enabled_spec.rb | 62 +++++++++++++++++++
gems/alaveteli_features/spec/spec_helper.rb | 2 +
16 files changed, 334 insertions(+)
create mode 100644 gems/alaveteli_features/.gitignore
create mode 100644 gems/alaveteli_features/.rspec
create mode 100644 gems/alaveteli_features/Gemfile
create mode 100644 gems/alaveteli_features/LICENSE.txt
create mode 100644 gems/alaveteli_features/README.md
create mode 100644 gems/alaveteli_features/Rakefile
create mode 100644 gems/alaveteli_features/alaveteli_features.gemspec
create mode 100644 gems/alaveteli_features/lib/alaveteli_features.rb
create mode 100644 gems/alaveteli_features/lib/alaveteli_features/generators/alaveteli_features/install/install_generator.rb
create mode 100644 gems/alaveteli_features/lib/alaveteli_features/generators/alaveteli_features/install/templates/config/initializers/alaveteli_features.rb.erb
create mode 100644 gems/alaveteli_features/lib/alaveteli_features/helpers.rb
create mode 100644 gems/alaveteli_features/lib/alaveteli_features/railtie.rb
create mode 100644 gems/alaveteli_features/lib/alaveteli_features/version.rb
create mode 100644 gems/alaveteli_features/spec/alaveteli_features_spec.rb
create mode 100644 gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb
create mode 100644 gems/alaveteli_features/spec/spec_helper.rb
diff --git a/gems/alaveteli_features/.gitignore b/gems/alaveteli_features/.gitignore
new file mode 100644
index 0000000000..d87d4be66f
--- /dev/null
+++ b/gems/alaveteli_features/.gitignore
@@ -0,0 +1,17 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
diff --git a/gems/alaveteli_features/.rspec b/gems/alaveteli_features/.rspec
new file mode 100644
index 0000000000..8c18f1abdd
--- /dev/null
+++ b/gems/alaveteli_features/.rspec
@@ -0,0 +1,2 @@
+--format documentation
+--color
diff --git a/gems/alaveteli_features/Gemfile b/gems/alaveteli_features/Gemfile
new file mode 100644
index 0000000000..9d6f7f43f9
--- /dev/null
+++ b/gems/alaveteli_features/Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in alaveteli_features.gemspec
+gemspec
diff --git a/gems/alaveteli_features/LICENSE.txt b/gems/alaveteli_features/LICENSE.txt
new file mode 100644
index 0000000000..63ec041349
--- /dev/null
+++ b/gems/alaveteli_features/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2016 TODO: Write your name
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/gems/alaveteli_features/README.md b/gems/alaveteli_features/README.md
new file mode 100644
index 0000000000..770aa6d165
--- /dev/null
+++ b/gems/alaveteli_features/README.md
@@ -0,0 +1,62 @@
+# AlaveteliFeatures
+
+This is a small gem for Alaveteli sites which adds a feature flipping library.
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+ gem 'alaveteli_features'
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install alaveteli_features
+
+Then run:
+
+ $ rails g alaveteli_features:install
+
+to install the necessary migrations and an example initializer file.
+
+## Usage
+
+Configure your features in `config/initializers/alaveteli_features.rb` with
+something like:
+
+ AlaveteliFeatures.backend.enable(:feature_name)
+
+`AlaveteliFeatures.backend` is an instance of [Flipper](https://github.com/jnunemaker/flipper/)
+so you can use [any of the DSL methods](https://github.com/jnunemaker/flipper/blob/master/docs/Gates.md)
+it provides.
+
+By default, AlaveteliFeatures uses the active record backend, you can also
+swap the backend for a different one:
+
+ require 'flipper/adapters/memory'
+
+ memory_backend = Flipper.new(Flipper::Adapters::Memory.new)
+ AlaveteliFeatures.backend = memory_backend
+
+
+To check for enabled features in your backend, use the `feature_enabled?`
+helper. This is lazily-included in `ActionController::Base` and
+`ActionView::Base` so you can use it in your controllers and views
+automatically. To use it elsewhere just include the `Helpers` module in any
+class that needs it:
+
+ class SomeClass
+ include AlaveteliFeatures::Helpers
+ end
+
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
diff --git a/gems/alaveteli_features/Rakefile b/gems/alaveteli_features/Rakefile
new file mode 100644
index 0000000000..b7e9ed549b
--- /dev/null
+++ b/gems/alaveteli_features/Rakefile
@@ -0,0 +1,6 @@
+require "bundler/gem_tasks"
+require "rspec/core/rake_task"
+
+RSpec::Core::RakeTask.new(:spec)
+
+task :default => :spec
diff --git a/gems/alaveteli_features/alaveteli_features.gemspec b/gems/alaveteli_features/alaveteli_features.gemspec
new file mode 100644
index 0000000000..30ebbb30ee
--- /dev/null
+++ b/gems/alaveteli_features/alaveteli_features.gemspec
@@ -0,0 +1,32 @@
+# coding: utf-8
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'alaveteli_features/version'
+
+Gem::Specification.new do |spec|
+ spec.name = "alaveteli_features"
+ spec.version = AlaveteliFeatures::VERSION
+ spec.authors = ["mySociety"]
+ spec.email = ["alaveteli@mysociety.org"]
+ spec.description = "Feature flags for Alaveteli"
+ spec.summary = "Feature flags for Alaveteli"
+ spec.homepage = "https://alaveteli.org"
+ spec.license = "MIT"
+
+ spec.files = `git ls-files`.split($/)
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
+ spec.require_paths = ["lib"]
+
+ spec.add_dependency "rails", "~> 3.2.22"
+ spec.add_dependency "flipper"
+ spec.add_dependency "flipper-active_record"
+ # Mime types 3 needs Ruby 2.0.0 or greater, but we need to support 1.9.3 so
+ # force a lower version
+ spec.add_dependency "mime-types", "< 3.0.0"
+
+ spec.add_development_dependency "bundler", "~> 1.3"
+ spec.add_development_dependency "rake"
+ spec.add_development_dependency "rspec"
+ spec.add_development_dependency "rspec-rails"
+end
diff --git a/gems/alaveteli_features/lib/alaveteli_features.rb b/gems/alaveteli_features/lib/alaveteli_features.rb
new file mode 100644
index 0000000000..89793fc865
--- /dev/null
+++ b/gems/alaveteli_features/lib/alaveteli_features.rb
@@ -0,0 +1,28 @@
+require "alaveteli_features/version"
+require "alaveteli_features/helpers"
+require "alaveteli_features/railtie" if defined?(Rails)
+require "flipper"
+require "flipper-active_record"
+
+module AlaveteliFeatures
+ def self.backend
+ return @backend if @backend
+ if ActiveRecord::Base.connection.table_exists? :flipper_features
+ @backend = Flipper.new(Flipper::Adapters::ActiveRecord.new)
+ else
+ Rails.logger.warn "No database tables found for feature flags, you " \
+ "might need to set a backend explicitly if you " \
+ "don't want them stored in a database, or run" \
+ "rake db:migrate to create the table."
+ Rails.logger.warn "Using memory-based feature storage instead."
+ require 'flipper/adapters/memory'
+ @backend = Flipper.new(Flipper::Adapters::Memory.new)
+ end
+ @backend
+ end
+
+ # for overriding with memory adapter in tests
+ def self.backend=(backend)
+ @backend = backend
+ end
+end
diff --git a/gems/alaveteli_features/lib/alaveteli_features/generators/alaveteli_features/install/install_generator.rb b/gems/alaveteli_features/lib/alaveteli_features/generators/alaveteli_features/install/install_generator.rb
new file mode 100644
index 0000000000..9c5db8ac0d
--- /dev/null
+++ b/gems/alaveteli_features/lib/alaveteli_features/generators/alaveteli_features/install/install_generator.rb
@@ -0,0 +1,44 @@
+require 'rails/generators'
+
+module AlaveteliFeatures
+ class InstallGenerator < Rails::Generators::Base
+ class_option :migrate, type: :boolean, default: true, banner: 'Run AlaveteliFeatures migrations'
+
+ def self.source_paths
+ paths = self.superclass.source_paths
+ paths << File.expand_path('../templates', "../../#{__FILE__}")
+ paths << File.expand_path('../templates', "../#{__FILE__}")
+ paths << File.expand_path('../templates', __FILE__)
+ paths.flatten
+ end
+
+ def prepare_options
+ @run_migrations = options[:migrate]
+ end
+
+ def install_migrations
+ say_status :copying, "migrations"
+ generate 'flipper:active_record'
+ end
+
+ def run_migrations
+ if @run_migrations
+ say_status :running, "migrations"
+ rake 'db:migrate'
+ else
+ say_status :skipping, "migrations (don't forget to run rake db:migrate)"
+ end
+ end
+
+ def add_files
+ template 'config/initializers/alaveteli_features.rb.erb', 'config/initializers/alaveteli_features.rb'
+ end
+
+ def complete
+ puts "*" * 50
+ puts "AlaveteliFeatures has been installed successfully. You're all ready to go!"
+ puts " "
+ puts "Enjoy!"
+ end
+ end
+end
\ No newline at end of file
diff --git a/gems/alaveteli_features/lib/alaveteli_features/generators/alaveteli_features/install/templates/config/initializers/alaveteli_features.rb.erb b/gems/alaveteli_features/lib/alaveteli_features/generators/alaveteli_features/install/templates/config/initializers/alaveteli_features.rb.erb
new file mode 100644
index 0000000000..31e85b0241
--- /dev/null
+++ b/gems/alaveteli_features/lib/alaveteli_features/generators/alaveteli_features/install/templates/config/initializers/alaveteli_features.rb.erb
@@ -0,0 +1,8 @@
+# Set up our available features and (optionally) get some defaults for
+# them from the config/general.yml configuration.
+
+# See Flipper's documentation for further examples of how you can enable
+# and disable features, noting that (depending on the adapter used) there
+# might well be settings stored in other places (the db, caches, etc) that
+# you need to respect.
+# https://github.com/jnunemaker/flipper/blob/master/lib/flipper/dsl.rb
diff --git a/gems/alaveteli_features/lib/alaveteli_features/helpers.rb b/gems/alaveteli_features/lib/alaveteli_features/helpers.rb
new file mode 100644
index 0000000000..6e9f769c7d
--- /dev/null
+++ b/gems/alaveteli_features/lib/alaveteli_features/helpers.rb
@@ -0,0 +1,7 @@
+module AlaveteliFeatures
+ module Helpers
+ def feature_enabled?(feature, *args)
+ AlaveteliFeatures.backend.enabled?(feature, *args)
+ end
+ end
+end
\ No newline at end of file
diff --git a/gems/alaveteli_features/lib/alaveteli_features/railtie.rb b/gems/alaveteli_features/lib/alaveteli_features/railtie.rb
new file mode 100644
index 0000000000..138bedadee
--- /dev/null
+++ b/gems/alaveteli_features/lib/alaveteli_features/railtie.rb
@@ -0,0 +1,17 @@
+require 'alaveteli_features/helpers'
+
+module AlaveteliFeatures
+ class Railtie < Rails::Railtie
+ initializer "alaveteli_features.helpers" do
+ ActiveSupport.on_load :action_view do
+ include AlaveteliFeatures::Helpers
+ end
+ ActiveSupport.on_load :action_controller do
+ include AlaveteliFeatures::Helpers
+ end
+ end
+ generators do
+ require 'alaveteli_features/generators/alaveteli_features/install/install_generator'
+ end
+ end
+end
\ No newline at end of file
diff --git a/gems/alaveteli_features/lib/alaveteli_features/version.rb b/gems/alaveteli_features/lib/alaveteli_features/version.rb
new file mode 100644
index 0000000000..83540981c1
--- /dev/null
+++ b/gems/alaveteli_features/lib/alaveteli_features/version.rb
@@ -0,0 +1,3 @@
+module AlaveteliFeatures
+ VERSION = "0.0.1"
+end
diff --git a/gems/alaveteli_features/spec/alaveteli_features_spec.rb b/gems/alaveteli_features/spec/alaveteli_features_spec.rb
new file mode 100644
index 0000000000..c20de94e52
--- /dev/null
+++ b/gems/alaveteli_features/spec/alaveteli_features_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+require 'flipper/adapters/memory'
+
+describe AlaveteliFeatures do
+ it 'should have a version number' do
+ expect(AlaveteliFeatures::VERSION).not_to be_nil
+ end
+
+ it 'should allow you to access the backend' do
+ expect(AlaveteliFeatures.backend).not_to be_nil
+ end
+
+ it 'should allow you to set the backend' do
+ test_backend = Flipper.new(Flipper::Adapters::Memory.new)
+ AlaveteliFeatures.backend = test_backend
+ expect(AlaveteliFeatures.backend).to be test_backend
+ end
+end
diff --git a/gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb b/gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb
new file mode 100644
index 0000000000..234de2b96f
--- /dev/null
+++ b/gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+require 'flipper/adapters/memory'
+require 'alaveteli_features/helpers'
+
+describe AlaveteliFeatures::Helpers do
+ let(:instance) { Class.new { include AlaveteliFeatures::Helpers }.new }
+ let(:test_backend) { Flipper.new(Flipper::Adapters::Memory.new) }
+ let(:user_class) do
+ # A test class to let us test the actor-based feature flipping
+ class User
+ attr_reader :id
+
+ def initialize(id, admin)
+ @id = id
+ @admin = admin
+ end
+
+ def admin?
+ @admin
+ end
+
+ # Must respond to flipper_id
+ alias_method :flipper_id, :id
+ end
+ end
+
+ before do
+ AlaveteliFeatures.backend = test_backend
+ # Seems to be the only way to make sure we don't register a group twice
+ begin
+ AlaveteliFeatures.backend.group(:admins)
+ rescue Flipper::GroupNotRegistered
+ Flipper.register :admins do |actor|
+ actor.respond_to?(:admin?) && actor.admin?
+ end
+ end
+ end
+
+ describe "#feature_enabled?" do
+ it "should respond true when a feature is enabled" do
+ AlaveteliFeatures.backend.enable(:test_feature)
+ expect(instance.feature_enabled?(:test_feature)).to eq true
+ end
+
+ it "should respond false when a feature is disabled" do
+ AlaveteliFeatures.backend.disable(:test_feature)
+ expect(instance.feature_enabled?(:test_feature)).to eq false
+ end
+
+ it "should pass on other arguments to the backend" do
+ user1 = user_class.new(1, true)
+
+ mock_backend = double("backend")
+ AlaveteliFeatures.backend = mock_backend
+
+ expect(mock_backend).to(
+ receive(:enabled?).with(:test_feature, user1)
+ )
+ instance.feature_enabled?(:test_feature, user1)
+ end
+ end
+end
\ No newline at end of file
diff --git a/gems/alaveteli_features/spec/spec_helper.rb b/gems/alaveteli_features/spec/spec_helper.rb
new file mode 100644
index 0000000000..05f00bc912
--- /dev/null
+++ b/gems/alaveteli_features/spec/spec_helper.rb
@@ -0,0 +1,2 @@
+$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
+require 'alaveteli_features'
From 4c5f0a3ece8886e79d994b8f4cb815c8bf333c2e Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 7 Oct 2016 10:08:23 +0100
Subject: [PATCH 015/311] Install and use AlaveteliFeatures for enabling
annotations
---
Gemfile | 3 +++
Gemfile.lock | 14 ++++++++++++
app/controllers/application_controller.rb | 2 +-
app/controllers/comment_controller.rb | 2 +-
app/views/followups/new.html.erb | 2 +-
.../general/_advanced_search_tips.html.erb | 4 ++--
app/views/general/search.html.erb | 2 +-
app/views/request/_after_actions.html.erb | 2 +-
app/views/request/_request_sent.html.erb | 2 +-
app/views/request/_wall_listing.html.erb | 2 +-
.../describe_notices/_successful.html.erb | 2 +-
app/views/user/_user_listing_single.html.erb | 2 +-
app/views/user/banned.html.erb | 2 +-
app/views/user/show.html.erb | 4 ++--
config/initializers/alaveteli_features.rb | 16 ++++++++++++++
.../20161006142352_create_flipper_tables.rb | 22 +++++++++++++++++++
spec/controllers/comment_controller_spec.rb | 2 +-
spec/controllers/request_controller_spec.rb | 2 +-
.../request/_after_actions.html.erb_spec.rb | 2 +-
19 files changed, 72 insertions(+), 17 deletions(-)
create mode 100644 config/initializers/alaveteli_features.rb
create mode 100644 db/migrate/20161006142352_create_flipper_tables.rb
diff --git a/Gemfile b/Gemfile
index c72580e06a..a8b4d94e17 100644
--- a/Gemfile
+++ b/Gemfile
@@ -67,6 +67,9 @@ group :assets do
gem 'therubyracer', '~> 0.12.2'
end
+# Feature flags
+gem 'alaveteli_features', :path => 'gems/alaveteli_features'
+
group :test do
gem 'fakeweb', '~> 1.3.0'
gem 'coveralls', :require => false
diff --git a/Gemfile.lock b/Gemfile.lock
index b4347e933c..5c1b2b83a7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -42,6 +42,15 @@ GIT
acts_as_versioned (0.6.0)
activerecord (>= 3.0.9)
+PATH
+ remote: gems/alaveteli_features
+ specs:
+ alaveteli_features (0.0.1)
+ flipper
+ flipper-active_record
+ mime-types (< 3.0.0)
+ rails (~> 4.0.13)
+
GEM
remote: https://rubygems.org/
specs:
@@ -159,6 +168,10 @@ GEM
railties (>= 3.1.0)
fast_gettext (1.1.0)
ffi (1.9.10)
+ flipper (0.9.2)
+ flipper-active_record (0.9.2)
+ activerecord (>= 3.2, < 6)
+ flipper (~> 0.9.2)
foundation-rails (5.5.3.2)
railties (>= 3.1.0)
sass (>= 3.3.0, < 3.5)
@@ -371,6 +384,7 @@ PLATFORMS
DEPENDENCIES
active_model_otp!
acts_as_versioned!
+ alaveteli_features!
annotate (~> 2.7.1)
bootstrap-sass (~> 2.3.2.2)
bullet (~> 5.1.0)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 401f5241c8..a60667f604 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -346,7 +346,7 @@ def authentication_check
#
def check_read_only
if !AlaveteliConfiguration::read_only.empty?
- if AlaveteliConfiguration::enable_annotations
+ if feature_enabled?(:annotations)
flash[:notice] = _("
{{site_name}} is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.
{{read_only}}
",
:site_name => site_name,
:read_only => AlaveteliConfiguration::read_only)
diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb
index c4b16f9fbf..759524e8fb 100644
--- a/app/controllers/comment_controller.rb
+++ b/app/controllers/comment_controller.rb
@@ -108,7 +108,7 @@ def create_track_thing
# not usually hit this unless they are explicitly attempting to avoid the
# comment block.
def reject_unless_comments_allowed
- unless AlaveteliConfiguration.enable_annotations && @info_request.comments_allowed?
+ unless feature_enabled?(:annotations) && @info_request.comments_allowed?
redirect_to request_url(@info_request), :notice => "Comments are not allowed on this request"
end
end
diff --git a/app/views/followups/new.html.erb b/app/views/followups/new.html.erb
index fd94479027..0c40eefc83 100644
--- a/app/views/followups/new.html.erb
+++ b/app/views/followups/new.html.erb
@@ -46,7 +46,7 @@
<%= _('When you receive the paper response, please help others find ' \
'out what it says:') %>
- <% if AlaveteliConfiguration.enable_annotations %>
+ <% if feature_enabled?(:annotations) %>
<%= _('Add an annotation to your request with choice quotes, ' \
'or a summary of the response.') %>
<% end %>
diff --git a/app/views/general/_advanced_search_tips.html.erb b/app/views/general/_advanced_search_tips.html.erb
index 8a30a07a8d..70a9a497b2 100644
--- a/app/views/general/_advanced_search_tips.html.erb
+++ b/app/views/general/_advanced_search_tips.html.erb
@@ -9,7 +9,7 @@
<%= _('variety: to select type of thing to search for, see the table of varieties below.', :varieties_url => "#varieties") %>
<%= _('requested_from:home_office to search requests from the Home Office, typing the name as in the URL.')%>
<%= _('requested_by:julian_todd to search requests made by Julian Todd, typing the name as in the URL.') %>
- <% if AlaveteliConfiguration.enable_annotations %>
+ <% if feature_enabled?(:annotations) %>
<%= _('commented_by:tony_bowden to search annotations made by Tony Bowden, typing the name as in the URL.')%>
<% end %>
<%= _('request: to restrict to a specific request, typing the title as in the URL.')%>
@@ -54,7 +54,7 @@
# the initial request
_('Follow up message sent by requester') %>
<%=search_link('variety:response')%>
<%= _('Response from a public authority') %>
- <% if AlaveteliConfiguration.enable_annotations %>
+ <% if feature_enabled?(:annotations) %>
- <% if AlaveteliConfiguration.enable_annotations %>
+ <% if feature_enabled?(:annotations) %>
<%= _('If you write about this request ' \
'(for example in a forum or a blog) ' \
diff --git a/app/views/request/_wall_listing.html.erb b/app/views/request/_wall_listing.html.erb
index ff7f1df83b..161f713ce8 100644
--- a/app/views/request/_wall_listing.html.erb
+++ b/app/views/request/_wall_listing.html.erb
@@ -11,7 +11,7 @@ end %>
<%= _('A follow up to {{request_title}} was sent to {{public_body_name}} by {{info_request_user}} on {{date}}.',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>request_user_link_absolute(info_request),:date=>simple_date(event.created_at),:request_url=>outgoing_message_path(event.outgoing_message),:request_title=>info_request.title) %>
<% elsif event.event_type == 'response' %>
<%= _('A response to {{request_title}} was sent by {{public_body_name}} to {{info_request_user}} on {{date}}. The request status is: {{request_status}}',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>request_user_link_absolute(info_request),:date=>simple_date(event.created_at),:request_url=>incoming_message_path(event.incoming_message_selective_columns("incoming_messages.id")),:request_title=>info_request.title,:request_status=>info_request.display_status) %>
- <% elsif event.event_type == 'comment' && AlaveteliConfiguration::enable_annotations %>
+ <% elsif event.event_type == 'comment' && feature_enabled?(:annotations) %>
<%= _('An annotation to {{request_title}} was made by {{event_comment_user}} on {{date}}',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>request_user_link_absolute(info_request),:event_comment_user=>user_link_absolute(event.comment.user),:date=>simple_date(event.created_at),:request_url=>comment_url(event.comment),:request_title=>info_request.title) %>
<% else %>
<%# Events of other types will not be indexed: see InfoRequestEvent#indexed_by_search?
diff --git a/app/views/request/describe_notices/_successful.html.erb b/app/views/request/describe_notices/_successful.html.erb
index 8e660a0bb8..4c7befefc1 100644
--- a/app/views/request/describe_notices/_successful.html.erb
+++ b/app/views/request/describe_notices/_successful.html.erb
@@ -1,4 +1,4 @@
-<% if AlaveteliConfiguration.enable_annotations %>
+<% if feature_enabled?(:annotations) %>
<%= _("We're glad you got all the information that you wanted. If you " \
"write about or make use of the information, please come back and " \
diff --git a/app/views/user/_user_listing_single.html.erb b/app/views/user/_user_listing_single.html.erb
index 302bcf4da3..2c065adc85 100644
--- a/app/views/user/_user_listing_single.html.erb
+++ b/app/views/user/_user_listing_single.html.erb
@@ -21,7 +21,7 @@
'{{count}} requests made.',
request_count,
:count => request_count) %>
- <% if AlaveteliConfiguration::enable_annotations %>
+ <% if feature_enabled?(:annotations) %>
<% annotation_count = display_user.comments.visible.size %>
<%= n_('{{count}} annotation made.',
'{{count}} annotations made.',
diff --git a/app/views/user/banned.html.erb b/app/views/user/banned.html.erb
index 25d68a5d5c..cd72bac415 100644
--- a/app/views/user/banned.html.erb
+++ b/app/views/user/banned.html.erb
@@ -6,7 +6,7 @@
<%= @details %>
-<% if AlaveteliConfiguration::enable_annotations %>
+<% if feature_enabled?(:annotations) %>
<%= _('You will be unable to make new requests, send follow ups, add ' \
'annotations or send messages to other users. You may continue ' \
diff --git a/app/views/user/show.html.erb b/app/views/user/show.html.erb
index dc50738a77..5be4134276 100644
--- a/app/views/user/show.html.erb
+++ b/app/views/user/show.html.erb
@@ -51,7 +51,7 @@
<% if @xapian_requests %>
<%= _('On this page') %>
<%= _('FOI requests') %>
- <% if AlaveteliConfiguration::enable_annotations %>
+ <% if feature_enabled?(:annotations) %>
<%= _('Annotations') %>
<% end %>
<% end %>
@@ -211,7 +211,7 @@
<% end %>
<% end %>
- <% if @xapian_comments && AlaveteliConfiguration::enable_annotations %>
+ <% if @xapian_comments && feature_enabled?(:annotations) %>
<% if @xapian_comments.results.empty? %>
<% if @page == 1 %>
diff --git a/config/initializers/alaveteli_features.rb b/config/initializers/alaveteli_features.rb
new file mode 100644
index 0000000000..44efe5c637
--- /dev/null
+++ b/config/initializers/alaveteli_features.rb
@@ -0,0 +1,16 @@
+# Set up our available features and (optionally) get some defaults for
+# them from the config/general.yml configuration.
+
+# See Flipper's documentation for further examples of how you can enable
+# and disable features, noting that (depending on the adapter used) there
+# might well be settings stored in other places (the db, caches, etc) that
+# you need to respect.
+# https://github.com/jnunemaker/flipper/blob/master/lib/flipper/dsl.rb
+
+# Annotations
+# We enable annotations globally based on the ENABLE_ANNOTATIONS config
+if AlaveteliConfiguration.enable_annotations
+ AlaveteliFeatures.backend.enable(:annotations)
+else
+ AlaveteliFeatures.backend.disable(:annotations)
+end
diff --git a/db/migrate/20161006142352_create_flipper_tables.rb b/db/migrate/20161006142352_create_flipper_tables.rb
new file mode 100644
index 0000000000..5041e1f31e
--- /dev/null
+++ b/db/migrate/20161006142352_create_flipper_tables.rb
@@ -0,0 +1,22 @@
+class CreateFlipperTables < ActiveRecord::Migration
+ def self.up
+ create_table :flipper_features do |t|
+ t.string :key, null: false
+ t.timestamps null: false
+ end
+ add_index :flipper_features, :key, unique: true
+
+ create_table :flipper_gates do |t|
+ t.string :feature_key, null: false
+ t.string :key, null: false
+ t.string :value
+ t.timestamps null: false
+ end
+ add_index :flipper_gates, [:feature_key, :key, :value], unique: true
+ end
+
+ def self.down
+ drop_table :flipper_gates
+ drop_table :flipper_features
+ end
+end
diff --git a/spec/controllers/comment_controller_spec.rb b/spec/controllers/comment_controller_spec.rb
index d6618c9358..21552fbc93 100644
--- a/spec/controllers/comment_controller_spec.rb
+++ b/spec/controllers/comment_controller_spec.rb
@@ -71,7 +71,7 @@
end
it "should not allow comments if comments are not allowed globally" do
- allow(AlaveteliConfiguration).to receive(:enable_annotations).and_return(false)
+ allow(controller).to receive(:feature_enabled?).with(:annotations).and_return(false)
session[:user_id] = users(:silly_name_user).id
info_request = info_requests(:fancy_dog_request)
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 679bd307e8..4291dacbb3 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2625,7 +2625,7 @@ def make_request
context "when annotations are disabled" do
before do
- allow(AlaveteliConfiguration).to receive(:enable_annotations).and_return(false)
+ allow(controller).to receive(:feature_enabled?).with(:annotations).and_return(false)
end
it "doesn't mention annotations in the flash message" do
diff --git a/spec/views/request/_after_actions.html.erb_spec.rb b/spec/views/request/_after_actions.html.erb_spec.rb
index 2ade3a10d6..c09cedcdb3 100644
--- a/spec/views/request/_after_actions.html.erb_spec.rb
+++ b/spec/views/request/_after_actions.html.erb_spec.rb
@@ -99,7 +99,7 @@
end
it "should not display a link to annotate the request if comments are disabled globally" do
- allow(AlaveteliConfiguration).to receive(:enable_annotations).and_return(false)
+ allow(view).to receive(:feature_enabled?).with(:annotations).and_return(false)
render :partial => 'request/after_actions'
expect(response.body).to have_css('ul.anyone_actions') do |div|
expect(div).not_to have_css('a', :text => 'Add an annotation (to help the requester or others)')
From 1ec55a7c33afc91e938b07f2c9a94c7bcde0920c Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 31 Oct 2016 17:23:09 +0000
Subject: [PATCH 016/311] Build the AP branch.
---
.travis.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.travis.yml b/.travis.yml
index 37ef771153..3c7aef1cc5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ branches:
only:
- master
- develop
+ - alaveteli-pro-develop
rvm:
- 1.9.3
- 2.0.0
From 46be9db26f429193ad772dc17df09429e3d05c19 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 1 Nov 2016 12:46:27 +0000
Subject: [PATCH 017/311] Add a ProAccount model
---
app/models/pro_account.rb | 14 ++++++++++++++
db/migrate/20161101110656_create_pro_accounts.rb | 10 ++++++++++
spec/factories/pro_accounts.rb | 16 ++++++++++++++++
spec/models/pro_account_spec.rb | 14 ++++++++++++++
4 files changed, 54 insertions(+)
create mode 100644 app/models/pro_account.rb
create mode 100644 db/migrate/20161101110656_create_pro_accounts.rb
create mode 100644 spec/factories/pro_accounts.rb
create mode 100644 spec/models/pro_account_spec.rb
diff --git a/app/models/pro_account.rb b/app/models/pro_account.rb
new file mode 100644
index 0000000000..f1e983c1b8
--- /dev/null
+++ b/app/models/pro_account.rb
@@ -0,0 +1,14 @@
+# == Schema Information
+#
+# Table name: pro_accounts
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# default_embargo_duration :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class ProAccount < ActiveRecord::Base
+ belongs_to :user
+end
diff --git a/db/migrate/20161101110656_create_pro_accounts.rb b/db/migrate/20161101110656_create_pro_accounts.rb
new file mode 100644
index 0000000000..549bc995e1
--- /dev/null
+++ b/db/migrate/20161101110656_create_pro_accounts.rb
@@ -0,0 +1,10 @@
+class CreateProAccounts < ActiveRecord::Migration
+ def change
+ create_table :pro_accounts do |t|
+ t.column :user_id, :integer, null: false
+ t.column :default_embargo_duration, :string
+
+ t.timestamps
+ end
+ end
+end
diff --git a/spec/factories/pro_accounts.rb b/spec/factories/pro_accounts.rb
new file mode 100644
index 0000000000..e5e6421e11
--- /dev/null
+++ b/spec/factories/pro_accounts.rb
@@ -0,0 +1,16 @@
+# == Schema Information
+#
+# Table name: pro_accounts
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# default_embargo_duration :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+FactoryGirl.define do
+ factory :pro_account do
+ user
+ end
+end
diff --git a/spec/models/pro_account_spec.rb b/spec/models/pro_account_spec.rb
new file mode 100644
index 0000000000..ecf6bd52d5
--- /dev/null
+++ b/spec/models/pro_account_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+RSpec.describe ProAccount, :type => :model do
+ let(:account) { FactoryGirl.create(:pro_account) }
+
+ it "belongs to a user" do
+ expect(account.user).not_to be_nil
+ end
+
+ it "has a default_embargo_duration field" do
+ account.default_embargo_duration = "3_months"
+ expect(account.default_embargo_duration).to eq "3_months"
+ end
+end
From 794bf3c9dc0d23b6f9fff032833c6980a9e249dc Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 1 Nov 2016 12:46:39 +0000
Subject: [PATCH 018/311] Add a pro_user factory
---
spec/factories/users.rb | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index b79e5dd878..34625f35fa 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -47,6 +47,12 @@
name 'Admin User'
admin_level 'super'
end
+ factory :pro_user do
+ name 'Pro User'
+ after(:create) do |user, evaluator|
+ create(:pro_account, :user => user)
+ end
+ end
end
end
From 559b86f1805171ccd6e7ee6783276459fe6cfbfe Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 1 Nov 2016 12:47:05 +0000
Subject: [PATCH 019/311] Update Gemfile.lock to move alaveteli_features to
rails 3
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 5c1b2b83a7..6ecca79494 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -49,7 +49,7 @@ PATH
flipper
flipper-active_record
mime-types (< 3.0.0)
- rails (~> 4.0.13)
+ rails (~> 3.2.22)
GEM
remote: https://rubygems.org/
From ffe8d210b31b84fc2bd0bed09d0cc0f6d3e6146a Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 1 Nov 2016 12:51:50 +0000
Subject: [PATCH 020/311] Add a pro? method to User
---
app/models/user.rb | 6 ++++++
spec/models/user_spec.rb | 12 ++++++++++++
2 files changed, 18 insertions(+)
diff --git a/app/models/user.rb b/app/models/user.rb
index 68b2e6855d..6dd86fd681 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -70,6 +70,8 @@ class User < ActiveRecord::Base
:dependent => :destroy
has_many :request_classifications,
:dependent => :destroy
+ has_one :pro_account,
+ :dependent => :destroy
scope :not_banned, -> { where(ban_text: "") }
@@ -543,6 +545,10 @@ def for_admin_column(complete = false)
end
end
+ def pro?
+ pro_account.present?
+ end
+
private
def create_new_salt
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0f168ed216..22975d118b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -999,4 +999,16 @@
end
+ describe '#pro?' do
+ it 'returns true if the user has a pro account' do
+ user = FactoryGirl.create(:pro_user)
+ expect(user.pro?).to be true
+ end
+
+ it 'returns false if the user doesnt have a pro account' do
+ user = FactoryGirl.create(:user)
+ expect(user.pro?).to be false
+ end
+ end
+
end
From 41507e0e239d00665f45c4468ce361ddf78f3725 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 1 Nov 2016 13:16:21 +0000
Subject: [PATCH 021/311] Add a pro scope to User
---
app/models/user.rb | 4 ++++
spec/models/user_spec.rb | 9 +++++++++
2 files changed, 13 insertions(+)
diff --git a/app/models/user.rb b/app/models/user.rb
index 6dd86fd681..1ef8770c23 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -109,6 +109,10 @@ class User < ActiveRecord::Base
has_one_time_password :counter_based => true
+ def self.pro
+ includes(:pro_account).where("pro_accounts.id IS NOT NULL")
+ end
+
# Return user given login email, password and other form parameters (e.g. name)
#
# The specific_user_login parameter says that login as a particular user is
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 22975d118b..601bb836ef 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1011,4 +1011,13 @@
end
end
+ describe 'pro scope' do
+ it "only includes pro user" do
+ pro_user = FactoryGirl.create(:pro_user)
+ user = FactoryGirl.create(:user)
+ expect(User.pro.include?(pro_user)).to be true
+ expect(User.pro.include?(user)).to be false
+ end
+ end
+
end
From 4dbabe34843c0295ab41d87799c80a6d0686121e Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 7 Oct 2016 10:08:23 +0100
Subject: [PATCH 022/311] Install and use AlaveteliFeatures for enabling
annotations
---
app/views/request/_after_actions.html.erb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/views/request/_after_actions.html.erb b/app/views/request/_after_actions.html.erb
index 79425145ae..c403653f5a 100644
--- a/app/views/request/_after_actions.html.erb
+++ b/app/views/request/_after_actions.html.erb
@@ -65,7 +65,7 @@
<% end %>
- <% if if feature_enabled?(:annotations) && @info_request.comments_allowed? %>
+ <% if feature_enabled?(:annotations) && @info_request.comments_allowed? %>
<%= link_to _('Add an annotation'), new_comment_path(:url_title => @info_request.url_title) %>
From c3cf8bc0a8049b644f8d70d113867404bb7de8e2 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 1 Nov 2016 16:20:29 +0000
Subject: [PATCH 023/311] Add a model for storing embargo information.
---
app/models/embargo.rb | 16 ++++++++++++
app/models/info_request.rb | 1 +
config/initializers/inflections.rb | 6 +++--
db/migrate/20161101151318_create_embargoes.rb | 10 +++++++
spec/factories/embargos.rb | 18 +++++++++++++
spec/models/embargo_spec.rb | 26 +++++++++++++++++++
6 files changed, 75 insertions(+), 2 deletions(-)
create mode 100644 app/models/embargo.rb
create mode 100644 db/migrate/20161101151318_create_embargoes.rb
create mode 100644 spec/factories/embargos.rb
create mode 100644 spec/models/embargo_spec.rb
diff --git a/app/models/embargo.rb b/app/models/embargo.rb
new file mode 100644
index 0000000000..5f91c1d762
--- /dev/null
+++ b/app/models/embargo.rb
@@ -0,0 +1,16 @@
+# -*- encoding : utf-8 -*-
+# == Schema Information
+#
+# Table name: embargos
+#
+# id :integer not null, primary key
+# info_request_id :integer not null
+# publish_at :datetime not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class Embargo < ActiveRecord::Base
+ belongs_to :info_request
+ validates_presence_of :info_request
+end
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 0b768ec489..184b9f3f83 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -76,6 +76,7 @@ class InfoRequest < ActiveRecord::Base
has_many :comments, :order => 'created_at', :dependent => :destroy
has_many :censor_rules, :order => 'created_at desc', :dependent => :destroy
has_many :mail_server_logs, :order => 'mail_server_log_done_id, "order"', :dependent => :destroy
+ has_one :embargo
attr_accessor :is_batch_request_template
attr_reader :followup_bad_reason
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index e3c680d365..5cdf141a7a 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -3,9 +3,11 @@
# Add new inflection rules using the following format
# (all these examples are active by default):
-# ActiveSupport::Inflector.inflections do |inflect|
+ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
-# end
+ inflect.plural /^(embargo)$/i, '\1es'
+ inflect.singular /^(embargo)es/i, '\1'
+end
diff --git a/db/migrate/20161101151318_create_embargoes.rb b/db/migrate/20161101151318_create_embargoes.rb
new file mode 100644
index 0000000000..4cd2e1e737
--- /dev/null
+++ b/db/migrate/20161101151318_create_embargoes.rb
@@ -0,0 +1,10 @@
+# -*- encoding : utf-8 -*-
+class CreateEmbargoes < ActiveRecord::Migration
+ def change
+ create_table :embargoes do |t|
+ t.belongs_to :info_request, index: true
+ t.column :publish_at, :datetime, null: false
+ t.timestamps
+ end
+ end
+end
diff --git a/spec/factories/embargos.rb b/spec/factories/embargos.rb
new file mode 100644
index 0000000000..e87b104f52
--- /dev/null
+++ b/spec/factories/embargos.rb
@@ -0,0 +1,18 @@
+# -*- encoding : utf-8 -*-
+# == Schema Information
+#
+# Table name: embargos
+#
+# id :integer not null, primary key
+# info_request_id :integer not null
+# publish_at :datetime not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+FactoryGirl.define do
+ factory :embargo do
+ info_request
+ publish_at Time.now + 3.months
+ end
+end
diff --git a/spec/models/embargo_spec.rb b/spec/models/embargo_spec.rb
new file mode 100644
index 0000000000..f0c44c4069
--- /dev/null
+++ b/spec/models/embargo_spec.rb
@@ -0,0 +1,26 @@
+# -*- encoding : utf-8 -*-
+# == Schema Information
+#
+# Table name: embargos
+#
+# id :integer not null, primary key
+# info_request_id :integer not null
+# publish_at :datetime not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+require 'spec_helper'
+
+describe Embargo, :type => :model do
+ let(:embargo) { FactoryGirl.create(:embargo) }
+
+ it 'belongs to an info_request' do
+ expect(embargo.info_request).not_to be_nil
+ end
+
+ it 'has a publish_at field' do
+ expect(embargo.publish_at).to be_a(ActiveSupport::TimeWithZone)
+ end
+
+end
From a366acbc6b8fc52a6f9b1c9d6d6e528c0b33ee43 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 1 Nov 2016 17:47:18 +0000
Subject: [PATCH 024/311] Make handling of prominence a bit more consistent and
isolated.
---
app/controllers/request_controller.rb | 3 +-
app/controllers/widget_votes_controller.rb | 2 +-
app/controllers/widgets_controller.rb | 2 +-
app/models/concerns/message_prominence.rb | 24 ++++
app/models/incoming_message.rb | 4 +-
app/models/info_request.rb | 59 +++++----
app/models/info_request/prominence.rb | 11 ++
.../info_request/prominence/calculator.rb | 41 ++++++
.../info_request/prominence/visible_query.rb | 14 ++
app/models/outgoing_message.rb | 3 +-
app/views/admin_request/edit.html.erb | 2 +-
app/views/admin_request/show.html.erb | 4 +-
app/views/request/_bubble.html.erb | 2 +-
app/views/request/_sidebar.html.erb | 2 +-
app/views/request/hidden.html.erb | 2 +-
app/views/request/show.html.erb | 6 +-
config/initializers/alaveteli.rb | 1 -
lib/message_prominence.rb | 22 ----
spec/controllers/request_controller_spec.rb | 4 +-
.../prominence/calculator_spec.rb | 121 ++++++++++++++++++
.../prominence/visible_query_spec.rb | 16 +++
spec/models/info_request_spec.rb | 34 +++--
.../request/_after_actions.html.erb_spec.rb | 3 +-
23 files changed, 297 insertions(+), 85 deletions(-)
create mode 100644 app/models/concerns/message_prominence.rb
create mode 100644 app/models/info_request/prominence.rb
create mode 100644 app/models/info_request/prominence/calculator.rb
create mode 100644 app/models/info_request/prominence/visible_query.rb
delete mode 100644 lib/message_prominence.rb
create mode 100644 spec/models/info_request/prominence/calculator_spec.rb
create mode 100644 spec/models/info_request/prominence/visible_query_spec.rb
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 5e3edf441e..5436fd1060 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -583,7 +583,8 @@ def authenticate_attachment
end
# Is this a completely public request that we can cache attachments for
# to be served up without authentication?
- if incoming_message.info_request.all_can_view? && incoming_message.all_can_view?
+ if incoming_message.info_request.prominence(:decorate => true).is_public? &&
+ incoming_message.is_public?
@files_can_be_cached = true
end
end
diff --git a/app/controllers/widget_votes_controller.rb b/app/controllers/widget_votes_controller.rb
index 089827f844..27ea4326c7 100644
--- a/app/controllers/widget_votes_controller.rb
+++ b/app/controllers/widget_votes_controller.rb
@@ -43,7 +43,7 @@ def find_info_request
end
def check_prominence
- unless @info_request.prominence == 'normal'
+ unless @info_request.prominence(:decorate => true).is_searchable?
render :nothing => true, :status => :forbidden
end
end
diff --git a/app/controllers/widgets_controller.rb b/app/controllers/widgets_controller.rb
index 3ea14bd10a..e915b10815 100644
--- a/app/controllers/widgets_controller.rb
+++ b/app/controllers/widgets_controller.rb
@@ -50,7 +50,7 @@ def find_info_request
end
def check_prominence
- unless @info_request.prominence == 'normal'
+ unless @info_request.prominence(:decorate => true).is_searchable?
render :nothing => true, :status => :forbidden
end
end
diff --git a/app/models/concerns/message_prominence.rb b/app/models/concerns/message_prominence.rb
new file mode 100644
index 0000000000..3e59d69c26
--- /dev/null
+++ b/app/models/concerns/message_prominence.rb
@@ -0,0 +1,24 @@
+# -*- encoding : utf-8 -*-
+module MessageProminence
+
+ extend ActiveSupport::Concern
+
+ included do
+ validates_inclusion_of :prominence, :in => self.prominence_states
+ end
+
+ def indexed_by_search?
+ is_public?
+ end
+
+ def is_public?
+ self.prominence == 'normal'
+ end
+
+ module ClassMethods
+ def prominence_states
+ ['normal', 'hidden','requester_only']
+ end
+ end
+
+end
diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb
index bff989f822..feb993f2ad 100644
--- a/app/models/incoming_message.rb
+++ b/app/models/incoming_message.rb
@@ -38,7 +38,7 @@
class IncomingMessage < ActiveRecord::Base
include AdminColumn
- extend MessageProminence
+ include MessageProminence
MAX_ATTACHMENT_TEXT_CLIPPED = 1000000 # 1Mb ish
@@ -57,8 +57,6 @@ class IncomingMessage < ActiveRecord::Base
belongs_to :raw_email, :dependent => :destroy
- has_prominence
-
after_destroy :update_request
after_update :update_request
before_destroy :destroy_email_file
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 184b9f3f83..f5b262ceed 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -82,17 +82,11 @@ class InfoRequest < ActiveRecord::Base
has_tag_string
- scope :visible, -> { where(prominence: "normal") }
+ scope :visible, Prominence::VisibleQuery.new
# user described state (also update in info_request_event, admin_request/edit.rhtml)
validate :must_be_valid_state
-
- validates_inclusion_of :prominence, :in => [
- 'normal',
- 'backpage',
- 'hidden',
- 'requester_only'
- ]
+ validates_inclusion_of :prominence, :in => Prominence::VALUES
validates_inclusion_of :law_used, :in => [
'foi', # Freedom of Information Act
@@ -170,6 +164,23 @@ def report_reasons
_("Other")]
end
+
+ # Public: Overrides the ActiveRecord attribute accessor
+ #
+ # opts = Hash of options (default: {})
+ # :decorate - Wrap the string in a ProminenceCalculator decorator that
+ # has methods indicating whether the InfoRequest is public, searchable
+ # etc.
+ # Returns a String or ProminenceCalculator
+ def prominence(opts = {})
+ decorate = opts.fetch(:decorate, false)
+ if decorate
+ Prominence::Calculator.new(self)
+ else
+ read_attribute(:prominence)
+ end
+ end
+
def must_be_valid_state
unless InfoRequest.enumerate_states.include?(described_state)
errors.add(:described_state, "is not a valid state")
@@ -180,6 +191,10 @@ def is_batch_request_template?
is_batch_request_template == true
end
+ def indexed_by_search?
+ prominence(:decorate => true).is_searchable?
+ end
+
# The request must either be internal, in which case it has
# a foreign key reference to a User object and no external_url or external_user_name,
# or else be external in which case it has no user_id but does have an external_url,
@@ -779,7 +794,7 @@ def get_last_public_response
end
def public_outgoing_events
- info_request_events.select{|e| e.outgoing? && e.outgoing_message.all_can_view? }
+ info_request_events.select{|e| e.outgoing? && e.outgoing_message.is_public? }
end
# The last public outgoing message
@@ -792,7 +807,7 @@ def initial_request_text
return '' if outgoing_messages.empty?
body_opts = { :censor_rules => applicable_censor_rules }
first_message = outgoing_messages.first
- first_message.all_can_view? ? first_message.get_text_for_indexing(true, body_opts) : ''
+ first_message.is_public? ? first_message.get_text_for_indexing(true, body_opts) : ''
end
# Returns index of last event which is described or nil if none described.
@@ -1033,7 +1048,7 @@ def make_zip_cache_path(user)
def zip_cache_file_suffix(user)
# Simple short circuit for requests where everything is public
- if all_can_view_all_correspondence?
+ if all_correspondence_is_public?
""
# If the user can view hidden things, they can view anything, so no need
# to go any further
@@ -1064,7 +1079,7 @@ def who_can_followup_to(skip_message = nil)
end
incoming_message.safe_mail_from
- next if ! incoming_message.all_can_view?
+ next if ! incoming_message.is_public?
email = OutgoingMailer.email_for_followup(self, incoming_message)
name = OutgoingMailer.name_for_followup(self, incoming_message)
@@ -1135,22 +1150,22 @@ def is_actual_owning_user?(user)
user.id == user_id
end
- # Is this request visible to everyone?
def all_can_view?
- %w(normal backpage).include?(prominence)
+ warn %q([DEPRECATION] InfoRequest#all_can_view? will be removed in
+ 0.27. It has been replaced by InfoRequest.prominence#is_public?).squish
+ prominence(:decorate => true).is_public?
end
def all_can_view_all_correspondence?
- all_can_view? &&
- incoming_messages.all?{ |message| message.all_can_view? } &&
- outgoing_messages.all?{ |message| message.all_can_view? }
+ warn %q([DEPRECATION] InfoRequest#all_can_view_all_correspondence? will be removed in
+ 0.27. It has been replaced by InfoRequest#all_correspondence_is_public?).squish
+ all_correspondence_is_public?
end
- def indexed_by_search?
- if prominence == 'backpage' || prominence == 'hidden' || prominence == 'requester_only'
- return false
- end
- true
+ def all_correspondence_is_public?
+ prominence(:decorate => true).is_public? &&
+ incoming_messages.all?{ |message| message.is_public? } &&
+ outgoing_messages.all?{ |message| message.is_public? }
end
def self.reject_incoming_at_mta(options)
diff --git a/app/models/info_request/prominence.rb b/app/models/info_request/prominence.rb
new file mode 100644
index 0000000000..a0b2de4c0c
--- /dev/null
+++ b/app/models/info_request/prominence.rb
@@ -0,0 +1,11 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module Prominence
+
+ VALUES = [ 'normal',
+ 'backpage',
+ 'hidden',
+ 'requester_only' ]
+
+ end
+end
diff --git a/app/models/info_request/prominence/calculator.rb b/app/models/info_request/prominence/calculator.rb
new file mode 100644
index 0000000000..0457861146
--- /dev/null
+++ b/app/models/info_request/prominence/calculator.rb
@@ -0,0 +1,41 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module Prominence
+ class Calculator
+
+ def initialize(info_request)
+ @info_request = info_request
+ end
+
+ # Is this request visible to everyone?
+ def is_public?
+ %w(normal backpage).include?(to_s)
+ end
+
+ # Is this request findable via search?
+ def is_searchable?
+ to_s == 'normal'
+ end
+
+ # Is this request hidden from some people?
+ def is_private?
+ %w(hidden requester_only).include?(to_s)
+ end
+
+ # Is this request visible only to admins and the requester?
+ def is_requester_only?
+ to_s == 'requester_only'
+ end
+
+ # Is this request visible only to admins?
+ def is_hidden?
+ to_s == 'hidden'
+ end
+
+ def to_s
+ @info_request.read_attribute(:prominence)
+ end
+
+ end
+ end
+end
diff --git a/app/models/info_request/prominence/visible_query.rb b/app/models/info_request/prominence/visible_query.rb
new file mode 100644
index 0000000000..948def73ae
--- /dev/null
+++ b/app/models/info_request/prominence/visible_query.rb
@@ -0,0 +1,14 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module Prominence
+ class VisibleQuery
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ @relation.where(prominence: 'normal')
+ end
+ end
+ end
+end
diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb
index 2655fd0721..9437203099 100644
--- a/app/models/outgoing_message.rb
+++ b/app/models/outgoing_message.rb
@@ -27,7 +27,7 @@
class OutgoingMessage < ActiveRecord::Base
include AdminColumn
- extend MessageProminence
+ include MessageProminence
include Rails.application.routes.url_helpers
include LinkToHelper
@@ -62,7 +62,6 @@ class OutgoingMessage < ActiveRecord::Base
after_update :xapian_reindex_after_update
strip_attributes :allow_empty => true
- has_prominence
self.default_url_options[:host] = AlaveteliConfiguration.domain
diff --git a/app/views/admin_request/edit.html.erb b/app/views/admin_request/edit.html.erb
index c29d03cdef..51d344bfef 100644
--- a/app/views/admin_request/edit.html.erb
+++ b/app/views/admin_request/edit.html.erb
@@ -8,7 +8,7 @@
<%= text_field 'info_request', 'title', :size => 50 %>
- <%= select( 'info_request', "prominence", [ "normal", "backpage", "requester_only", "hidden" ]) %>
+ <%= select( 'info_request', "prominence", InfoRequest::Prominence::VALUES) %>
(backpage means hidden from lists/search; hidden means completely hidden; super users can see anything)
diff --git a/app/views/admin_request/show.html.erb b/app/views/admin_request/show.html.erb
index 636beb67e5..08608cc29c 100644
--- a/app/views/admin_request/show.html.erb
+++ b/app/views/admin_request/show.html.erb
@@ -137,7 +137,7 @@
<% end %>
- <% if ['hidden', 'requester_only'].include? @info_request.prominence %>
+ <% if @info_request.prominence(:decorate => true).is_private? %>
This request has already been hidden
<% else %>
- <% if !['hidden', 'requester_only'].include? @info_request.prominence %>
+ <% if !@info_request.prominence(:decorate => true).is_private? %>
<% if ! @info_request.is_external? %>
<%= a.display_size %>
<%= link_to "Download", attachment_path %>
- <% if a.has_body_as_html? && incoming_message.info_request.all_can_view? %>
+ <% if a.has_body_as_html? && incoming_message.info_request.prominence(:decorate => true).is_public? %>
<%= link_to "View as HTML", attachment_as_html_path %>
<% end %>
diff --git a/app/views/request/_sidebar.html.erb b/app/views/request/_sidebar.html.erb
index 7713054157..76abff5ce8 100644
--- a/app/views/request/_sidebar.html.erb
+++ b/app/views/request/_sidebar.html.erb
@@ -11,7 +11,7 @@
<%= _("This request has prominence 'hidden'. You can only see it " \
"because you are logged in as a super user.") %>
<%# The eccentric formatting of the following string is in order that it be identical
to the corresponding string in request/show.html.erb %>
diff --git a/app/views/request/hidden.html.erb b/app/views/request/hidden.html.erb
index 643041d5fd..d06e188785 100644
--- a/app/views/request/hidden.html.erb
+++ b/app/views/request/hidden.html.erb
@@ -11,7 +11,7 @@
"any questions.",
:url => help_contact_path.html_safe) %>
-<% if @info_request.prominence == 'requester_only' %>
+<% if @info_request.prominence(:decorate => true).is_requester_only? %>
<%= _('If you are the requester, then you may sign in ' \
'to view the request.',
diff --git a/app/views/request/show.html.erb b/app/views/request/show.html.erb
index b003b36e36..9226c13a49 100644
--- a/app/views/request/show.html.erb
+++ b/app/views/request/show.html.erb
@@ -35,7 +35,7 @@
<% end %>
-<% if @info_request.all_can_view? %>
+<% if @info_request.prominence(:decorate => true).is_public? %>
<% content_for :open_graph_tags do %>
@@ -54,14 +54,14 @@
<% end %>
<% end %>
-<% if @info_request.prominence == 'hidden' %>
+<% if @info_request.prominence(:decorate => true).is_hidden? %>
<%= _("This request has prominence 'hidden'. You can only see it because " \
"you are logged in as a super user.") %>
<% end %>
-<% if @info_request.prominence == 'requester_only' %>
+<% if @info_request.prominence(:decorate => true).is_requester_only? %>
<%= _("This request is hidden, so that only you the requester can see it. " \
"Please contact us if you are not sure why.",
diff --git a/config/initializers/alaveteli.rb b/config/initializers/alaveteli.rb
index 12a1225465..3fb8532c7f 100644
--- a/config/initializers/alaveteli.rb
+++ b/config/initializers/alaveteli.rb
@@ -46,7 +46,6 @@
require 'normalize_string'
require 'alaveteli_file_types'
require 'alaveteli_localization'
-require 'message_prominence'
require 'theme'
require 'xapian_queries'
require 'date_quarter'
diff --git a/lib/message_prominence.rb b/lib/message_prominence.rb
deleted file mode 100644
index 84b7934709..0000000000
--- a/lib/message_prominence.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- encoding : utf-8 -*-
-module MessageProminence
-
- def has_prominence
- send :include, InstanceMethods
- cattr_accessor :prominence_states
- self.prominence_states = ['normal', 'hidden','requester_only']
- validates_inclusion_of :prominence, :in => self.prominence_states
- end
-
- module InstanceMethods
-
- def indexed_by_search?
- self.prominence == 'normal'
- end
-
- def all_can_view?
- self.prominence == 'normal'
- end
-
- end
-end
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 4291dacbb3..91ba4b6208 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2306,7 +2306,7 @@ def expect_redirect(status, redirect_path)
it "should not fail with long filenames" do
long_name = "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah.txt"
info_request = double(InfoRequest, :prominence => 'normal',
- :all_can_view? => true)
+ :is_public? => true)
incoming_message = double(IncomingMessage, :info_request => info_request,
:parse_raw_email! => true,
:info_request_id => 132,
@@ -2314,7 +2314,7 @@ def expect_redirect(status, redirect_path)
:get_attachments_for_display => nil,
:apply_masks => nil,
:prominence => 'normal',
- :all_can_view? => true)
+ :is_public? => true)
attachment = FactoryGirl.build(:body_text, :filename => long_name)
allow(IncomingMessage).to receive(:find).with("44").and_return(incoming_message)
allow(IncomingMessage).to receive(:get_attachment_by_url_part_number_and_filename).and_return(attachment)
diff --git a/spec/models/info_request/prominence/calculator_spec.rb b/spec/models/info_request/prominence/calculator_spec.rb
new file mode 100644
index 0000000000..349e0aa71e
--- /dev/null
+++ b/spec/models/info_request/prominence/calculator_spec.rb
@@ -0,0 +1,121 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
+
+describe InfoRequest::Prominence::Calculator do
+
+ let(:info_request){ FactoryGirl.build(:info_request) }
+
+ def expect_value(prominence, method, value)
+ info_request.prominence = prominence
+ expect(described_class.new(info_request).send(method)).to eq(value)
+ end
+
+ describe '#is_public?' do
+
+ it 'returns true if its prominence is normal' do
+ expect_value('normal', :is_public?, true)
+ end
+
+ it 'returns true if its prominence is backpage' do
+ expect_value('backpage', :is_public?, true)
+ end
+
+ it 'returns false if its prominence is hidden' do
+ expect_value('hidden', :is_public?, false)
+ end
+
+ it 'returns false if its prominence is requester_only' do
+ expect_value('requester_only', :is_public?, false)
+ end
+
+ end
+
+ describe '#is_searchable?' do
+
+ it 'returns true if its prominence is normal' do
+ expect_value('normal', :is_searchable?, true)
+ end
+
+ it 'returns false if its prominence is backpage' do
+ expect_value('backpage', :is_searchable?, false)
+ end
+
+ it 'returns false if its prominence is hidden' do
+ expect_value('hidden', :is_searchable?, false)
+ end
+
+ it 'returns false if its prominence is requester_only' do
+ expect_value('requester_only', :is_searchable?, false)
+ end
+
+ end
+
+ describe '#is_private?' do
+
+ it 'returns false if its prominence is normal' do
+ expect_value('normal', :is_private?, false)
+ end
+
+ it 'returns false if its prominence is backpage' do
+ expect_value('backpage', :is_private?, false)
+ end
+
+ it 'returns true if its prominence is hidden' do
+ expect_value('hidden', :is_private?, true)
+ end
+
+ it 'returns true if its prominence is requester_only' do
+ expect_value('requester_only', :is_private?, true)
+ end
+
+ end
+
+ describe '#is_requester_only?' do
+
+ it 'returns false if its prominence is normal' do
+ expect_value('normal', :is_requester_only?, false)
+ end
+
+ it 'returns false if its prominence is backpage' do
+ expect_value('backpage', :is_requester_only?, false)
+ end
+
+ it 'returns false if its prominence is hidden' do
+ expect_value('hidden', :is_requester_only?, false)
+ end
+
+ it 'returns true if its prominence is requester_only' do
+ expect_value('requester_only', :is_requester_only?, true)
+ end
+
+ end
+
+ describe '#is_hidden?' do
+
+ it 'returns false if its prominence is normal' do
+ expect_value('normal', :is_hidden?, false)
+ end
+
+ it 'returns false if its prominence is backpage' do
+ expect_value('backpage', :is_hidden?, false)
+ end
+
+ it 'returns true if its prominence is hidden' do
+ expect_value('hidden', :is_hidden?, true)
+ end
+
+ it 'returns false if its prominence is requester_only' do
+ expect_value('requester_only', :is_hidden?, false)
+ end
+
+ end
+
+ describe '#to_s' do
+
+ it 'returns the prominence of the request' do
+ expect_value('normal', :to_s, 'normal')
+ end
+
+ end
+
+end
diff --git a/spec/models/info_request/prominence/visible_query_spec.rb b/spec/models/info_request/prominence/visible_query_spec.rb
new file mode 100644
index 0000000000..31dbd19b08
--- /dev/null
+++ b/spec/models/info_request/prominence/visible_query_spec.rb
@@ -0,0 +1,16 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
+
+describe InfoRequest::Prominence::VisibleQuery do
+
+ describe '#call' do
+
+ it 'returns only results with a normal prominence' do
+ normal_request = FactoryGirl.create(:info_request)
+ hidden_request = FactoryGirl.create(:info_request, :prominence => 'hidden')
+ expect(described_class.new.call).to include(normal_request)
+ expect(described_class.new.call).not_to include(hidden_request)
+ end
+ end
+
+end
diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb
index 5b9aabbaa8..0f0aa388af 100644
--- a/spec/models/info_request_spec.rb
+++ b/spec/models/info_request_spec.rb
@@ -1129,6 +1129,12 @@
expect(info_request.errors[:public_body_id]).to be_empty
end
+ it 'rejects an invalid prominence' do
+ info_request = InfoRequest.new(:prominence => 'something')
+ info_request.valid?
+ expect(info_request.errors[:prominence]).to include("is not included in the list")
+ end
+
end
describe 'when generating a user name slug' do
@@ -1809,31 +1815,21 @@ def create_old_unclassified_holding_pen
end
+ describe '#prominence' do
- describe 'when an instance is asked if all can view it' do
-
- before do
- @info_request = InfoRequest.new
- end
+ let(:info_request){ FactoryGirl.build(:info_request) }
- it 'returns true if its prominence is normal' do
- @info_request.prominence = 'normal'
- expect(@info_request.all_can_view?).to eq(true)
+ it 'returns the prominence of the request' do
+ expect(info_request.prominence).to eq("normal")
end
- it 'returns true if its prominence is backpage' do
- @info_request.prominence = 'backpage'
- expect(@info_request.all_can_view?).to eq(true)
- end
+ context ':decorate option is true' do
- it 'returns false if its prominence is hidden' do
- @info_request.prominence = 'hidden'
- expect(@info_request.all_can_view?).to eq(false)
- end
+ it 'returns a prominence calculator' do
+ expect(InfoRequest.new.prominence(:decorate => true))
+ .to be_a(InfoRequest::Prominence::Calculator)
+ end
- it 'returns false if its prominence is requester_only' do
- @info_request.prominence = 'requester_only'
- expect(@info_request.all_can_view?).to eq(false)
end
end
diff --git a/spec/views/request/_after_actions.html.erb_spec.rb b/spec/views/request/_after_actions.html.erb_spec.rb
index c09cedcdb3..7a9b000842 100644
--- a/spec/views/request/_after_actions.html.erb_spec.rb
+++ b/spec/views/request/_after_actions.html.erb_spec.rb
@@ -15,11 +15,10 @@
:public_body => @mock_body,
:comments_allowed? => true,
:url_title => 'test_request',
- :all_can_view? => true)
+ :is_public? => true)
@mock_track = mock_model(TrackThing, :track_type => 'request_updates',
:info_request => @mock_request)
-
assign :info_request, @mock_request
assign :track_thing, @mock_track
end
From e8df9ca613add54d74e954f230c045d1abae297b Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 4 Nov 2016 11:53:05 +0000
Subject: [PATCH 025/311] Add scopes for embargoed and not embargoed.
---
app/models/info_request.rb | 2 ++
.../prominence/embargoed_query.rb | 14 ++++++++++++++
.../prominence/not_embargoed_query.rb | 19 +++++++++++++++++++
spec/factories/info_requests.rb | 6 ++++++
.../prominence/embargoed_query_spec.rb | 15 +++++++++++++++
.../prominence/not_embargoed_query_spec.rb | 14 ++++++++++++++
6 files changed, 70 insertions(+)
create mode 100644 app/models/info_request/prominence/embargoed_query.rb
create mode 100644 app/models/info_request/prominence/not_embargoed_query.rb
create mode 100644 spec/models/info_request/prominence/embargoed_query_spec.rb
create mode 100644 spec/models/info_request/prominence/not_embargoed_query_spec.rb
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index f5b262ceed..20a5ecabc8 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -83,6 +83,8 @@ class InfoRequest < ActiveRecord::Base
has_tag_string
scope :visible, Prominence::VisibleQuery.new
+ scope :embargoed, Prominence::EmbargoedQuery.new
+ scope :not_embargoed, Prominence::NotEmbargoedQuery.new
# user described state (also update in info_request_event, admin_request/edit.rhtml)
validate :must_be_valid_state
diff --git a/app/models/info_request/prominence/embargoed_query.rb b/app/models/info_request/prominence/embargoed_query.rb
new file mode 100644
index 0000000000..51dc79838b
--- /dev/null
+++ b/app/models/info_request/prominence/embargoed_query.rb
@@ -0,0 +1,14 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module Prominence
+ class EmbargoedQuery
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ @relation.includes(:embargo).where('embargoes.id IS NOT NULL')
+ end
+ end
+ end
+end
diff --git a/app/models/info_request/prominence/not_embargoed_query.rb b/app/models/info_request/prominence/not_embargoed_query.rb
new file mode 100644
index 0000000000..9623ddd234
--- /dev/null
+++ b/app/models/info_request/prominence/not_embargoed_query.rb
@@ -0,0 +1,19 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module Prominence
+ class NotEmbargoedQuery
+
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ # Specify an outer join as the default inner join
+ # will not retrieve NULL records which is what we want here
+ @relation.joins('LEFT OUTER JOIN embargoes
+ ON embargoes.info_request_id = info_requests.id')
+ .where('embargoes.id IS NULL')
+ end
+ end
+ end
+end
diff --git a/spec/factories/info_requests.rb b/spec/factories/info_requests.rb
index 7c06af4184..5555c67497 100644
--- a/spec/factories/info_requests.rb
+++ b/spec/factories/info_requests.rb
@@ -65,6 +65,12 @@
end
end
+ factory :embargoed_request do
+ after(:create) do |info_request, evaluator|
+ embargo = create(:embargo, :info_request => info_request)
+ end
+ end
+
factory :external_request do
user nil
external_user_name 'External User'
diff --git a/spec/models/info_request/prominence/embargoed_query_spec.rb b/spec/models/info_request/prominence/embargoed_query_spec.rb
new file mode 100644
index 0000000000..6b2643789c
--- /dev/null
+++ b/spec/models/info_request/prominence/embargoed_query_spec.rb
@@ -0,0 +1,15 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
+
+describe InfoRequest::Prominence::EmbargoedQuery do
+
+ describe '#call' do
+
+ it 'limits the requests to those that have embargoes' do
+ info_request = FactoryGirl.create(:info_request)
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect(described_class.new.call).to eq([embargoed_request])
+ end
+
+ end
+end
diff --git a/spec/models/info_request/prominence/not_embargoed_query_spec.rb b/spec/models/info_request/prominence/not_embargoed_query_spec.rb
new file mode 100644
index 0000000000..91d6c9cf7c
--- /dev/null
+++ b/spec/models/info_request/prominence/not_embargoed_query_spec.rb
@@ -0,0 +1,14 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
+
+describe InfoRequest::Prominence::NotEmbargoedQuery do
+
+ describe '#call' do
+
+ it 'limits the requests to those that do not have embargoes' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect(described_class.new.call).not_to include(embargoed_request)
+ end
+
+ end
+end
From e81bbdf89db965018ecc52c8fb765cf3f8b7a231 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 4 Nov 2016 11:53:28 +0000
Subject: [PATCH 026/311] Visible scope includes not embargoed.
---
app/models/info_request/prominence/visible_query.rb | 2 +-
spec/models/info_request/prominence/visible_query_spec.rb | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/app/models/info_request/prominence/visible_query.rb b/app/models/info_request/prominence/visible_query.rb
index 948def73ae..d6ab6bef9d 100644
--- a/app/models/info_request/prominence/visible_query.rb
+++ b/app/models/info_request/prominence/visible_query.rb
@@ -7,7 +7,7 @@ def initialize(relation = InfoRequest)
end
def call
- @relation.where(prominence: 'normal')
+ @relation.where(prominence: 'normal').not_embargoed
end
end
end
diff --git a/spec/models/info_request/prominence/visible_query_spec.rb b/spec/models/info_request/prominence/visible_query_spec.rb
index 31dbd19b08..399ba4fe78 100644
--- a/spec/models/info_request/prominence/visible_query_spec.rb
+++ b/spec/models/info_request/prominence/visible_query_spec.rb
@@ -11,6 +11,11 @@
expect(described_class.new.call).to include(normal_request)
expect(described_class.new.call).not_to include(hidden_request)
end
+
+ it 'does not return an embargoed request with normal prominence' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect(described_class.new.call).not_to include(embargoed_request)
+ end
end
end
From 9a28df172da42da10950d2952de41eabd986dd68 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 4 Nov 2016 11:54:49 +0000
Subject: [PATCH 027/311] Calculator methods include embargo status.
---
.../info_request/prominence/calculator.rb | 7 +-
.../delivery_statuses_controller_spec.rb | 28 +++++---
.../prominence/calculator_spec.rb | 67 +++++++++++++++++++
3 files changed, 87 insertions(+), 15 deletions(-)
diff --git a/app/models/info_request/prominence/calculator.rb b/app/models/info_request/prominence/calculator.rb
index 0457861146..0f7fc74364 100644
--- a/app/models/info_request/prominence/calculator.rb
+++ b/app/models/info_request/prominence/calculator.rb
@@ -7,19 +7,18 @@ def initialize(info_request)
@info_request = info_request
end
- # Is this request visible to everyone?
def is_public?
- %w(normal backpage).include?(to_s)
+ %w(normal backpage).include?(to_s) && !@info_request.embargo
end
# Is this request findable via search?
def is_searchable?
- to_s == 'normal'
+ to_s == 'normal' && !@info_request.embargo
end
# Is this request hidden from some people?
def is_private?
- %w(hidden requester_only).include?(to_s)
+ return %w(hidden requester_only).include?(to_s) || @info_request.embargo.present?
end
# Is this request visible only to admins and the requester?
diff --git a/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb b/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb
index f50daf849e..5a76ddc910 100644
--- a/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb
+++ b/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb
@@ -16,13 +16,18 @@
@status = MailServerLog::EximDeliveryStatus.new(:normal_message_delivery)
end
+ def visible_info_request
+ mock_model(InfoRequest, { :prominence => 'normal',
+ :embargo => nil })
+ end
+
describe 'GET show' do
it 'assigns the outgoing message' do
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -37,7 +42,7 @@
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
:prominence => 'hidden',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => false,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -52,7 +57,8 @@
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'hidden'}),
+ :info_request => mock_model(InfoRequest, { :prominence => 'hidden',
+ :embargo => nil }),
:is_owning_user? => false,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -67,7 +73,7 @@
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -88,7 +94,7 @@
session[:user_id] = FactoryGirl.create(:admin_user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -103,7 +109,7 @@
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -118,7 +124,7 @@
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => false,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -138,7 +144,7 @@
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -158,7 +164,7 @@
session[:user_id] = FactoryGirl.create(:admin_user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -172,7 +178,7 @@
it 'does not assign mail server logs for a regular user' do
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => false,
:mail_server_logs => @logs,
:delivery_status => @status }
@@ -187,7 +193,7 @@
session[:user_id] = FactoryGirl.create(:user).id
attrs = { :id => '1',
:prominence => 'normal',
- :info_request => mock_model(InfoRequest, {:prominence => 'normal'}),
+ :info_request => visible_info_request,
:is_owning_user? => true,
:mail_server_logs => @logs,
:delivery_status => @status }
diff --git a/spec/models/info_request/prominence/calculator_spec.rb b/spec/models/info_request/prominence/calculator_spec.rb
index 349e0aa71e..2e18e301c9 100644
--- a/spec/models/info_request/prominence/calculator_spec.rb
+++ b/spec/models/info_request/prominence/calculator_spec.rb
@@ -4,12 +4,18 @@
describe InfoRequest::Prominence::Calculator do
let(:info_request){ FactoryGirl.build(:info_request) }
+ let(:embargoed_request){ FactoryGirl.create(:embargoed_request) }
def expect_value(prominence, method, value)
info_request.prominence = prominence
expect(described_class.new(info_request).send(method)).to eq(value)
end
+ def expect_embargoed_value(prominence, method, value)
+ embargoed_request.prominence = prominence
+ expect(described_class.new(embargoed_request).send(method)).to eq(value)
+ end
+
describe '#is_public?' do
it 'returns true if its prominence is normal' do
@@ -28,6 +34,26 @@ def expect_value(prominence, method, value)
expect_value('requester_only', :is_public?, false)
end
+ context 'when there is an embargo' do
+
+ it 'returns false if its prominence is normal' do
+ expect_embargoed_value('normal', :is_public?, false)
+ end
+
+ it 'returns false if its prominence is backpage' do
+ expect_embargoed_value('backpage', :is_public?, false)
+ end
+
+ it 'returns false if its prominence is hidden' do
+ expect_embargoed_value('hidden', :is_public?, false)
+ end
+
+ it 'returns false if its prominence is requester_only' do
+ expect_embargoed_value('requester_only', :is_public?, false)
+ end
+
+ end
+
end
describe '#is_searchable?' do
@@ -48,6 +74,26 @@ def expect_value(prominence, method, value)
expect_value('requester_only', :is_searchable?, false)
end
+ context 'when there is an embargo' do
+
+ it 'returns false if its prominence is normal' do
+ expect_embargoed_value('normal', :is_searchable?, false)
+ end
+
+ it 'returns false if its prominence is backpage' do
+ expect_embargoed_value('backpage', :is_searchable?, false)
+ end
+
+ it 'returns false if its prominence is hidden' do
+ expect_embargoed_value('hidden', :is_searchable?, false)
+ end
+
+ it 'returns false if its prominence is requester_only' do
+ expect_embargoed_value('requester_only', :is_searchable?, false)
+ end
+
+ end
+
end
describe '#is_private?' do
@@ -68,6 +114,27 @@ def expect_value(prominence, method, value)
expect_value('requester_only', :is_private?, true)
end
+
+ context 'when there is an embargo' do
+
+ it 'returns true if its prominence is normal' do
+ expect_embargoed_value('normal', :is_private?, true)
+ end
+
+ it 'returns true if its prominence is backpage' do
+ expect_embargoed_value('backpage', :is_private?, true)
+ end
+
+ it 'returns true if its prominence is hidden' do
+ expect_embargoed_value('hidden', :is_private?, true)
+ end
+
+ it 'returns true if its prominence is requester_only' do
+ expect_embargoed_value('requester_only', :is_private?, true)
+ end
+
+ end
+
end
describe '#is_requester_only?' do
From 53c359470ba0f944e9899046ad4e57f31d179caf Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 7 Nov 2016 17:32:02 +0000
Subject: [PATCH 028/311] Add some methods to indicate user permissions.
Currently these all revert back to checking super user status.
In a later pull request we intend to back them with finer-grained
user permissions.
---
app/models/user.rb | 8 ++++++++
spec/models/user_spec.rb | 42 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 50 insertions(+)
diff --git a/app/models/user.rb b/app/models/user.rb
index 1ef8770c23..c7e1de0354 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -174,6 +174,14 @@ def self.view_hidden?(user)
!user.nil? && user.super?
end
+ def self.view_embargoed?(user)
+ self.view_hidden?(user)
+ end
+
+ def self.view_hidden_and_embargoed?(user)
+ view_hidden?(user) && view_embargoed?(user)
+ end
+
# Should the user be kept logged into their own account
# if they follow a /c/ redirect link belonging to another user?
def self.stay_logged_in_on_redirect?(user)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 601bb836ef..27181c85f2 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1020,4 +1020,46 @@
end
end
+ describe '.view_hidden?' do
+ it 'returns false if there is no user' do
+ expect(User.view_hidden?(nil)).to be false
+ end
+
+ it 'returns false if the user is not a superuser' do
+ expect(User.view_hidden?(FactoryGirl.build(:user))).to be false
+ end
+
+ it 'returns true if the user is an admin user' do
+ expect(User.view_hidden?(FactoryGirl.build(:admin_user))).to be true
+ end
+ end
+
+ describe '.view_embargoed' do
+ it 'returns false if there is no user' do
+ expect(User.view_embargoed?(nil)).to be false
+ end
+
+ it 'returns false if the user is not a superuser' do
+ expect(User.view_embargoed?(FactoryGirl.build(:user))).to be false
+ end
+
+ it 'returns true if the user is an admin user' do
+ expect(User.view_embargoed?(FactoryGirl.build(:admin_user))).to be true
+ end
+ end
+
+ describe '.view_hidden_and_embargoed' do
+ it 'returns false if there is no user' do
+ expect(User.view_hidden_and_embargoed?(nil)).to be false
+ end
+
+ it 'returns false if the user is not a superuser' do
+ expect(User.view_hidden_and_embargoed?(FactoryGirl.build(:user))).to be false
+ end
+
+ it 'returns true if the user is an admin user' do
+ expect(User.view_hidden_and_embargoed?(FactoryGirl.build(:admin_user))).to be true
+ end
+ end
+
end
From 4ce36d5bf086909b392408e19603f2952b58397b Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 8 Nov 2016 11:59:23 +0000
Subject: [PATCH 029/311] Add embargo to logic used in
can_view_with_prominence?
---
app/models/ability.rb | 25 ++++++++++-----
spec/models/ability_spec.rb | 62 +++++++++++++++++++++++++++++++++++++
2 files changed, 80 insertions(+), 7 deletions(-)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 79350423c4..d534618358 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -54,13 +54,24 @@ def self.can_update_request_state?(user, request)
end
def self.can_view_with_prominence?(prominence, info_request, user)
- if prominence == 'hidden'
- return User.view_hidden?(user)
+ if info_request.embargo
+ case prominence
+ when 'hidden'
+ User.view_hidden_and_embargoed?(user)
+ when 'requester_only'
+ info_request.is_actual_owning_user?(user) || User.view_hidden_and_embargoed?(user)
+ else
+ info_request.is_actual_owning_user?(user) || User.view_embargoed?(user)
+ end
+ else
+ case prominence
+ when 'hidden'
+ User.view_hidden?(user)
+ when 'requester_only'
+ info_request.is_actual_owning_user?(user) || User.view_hidden?(user)
+ else
+ true
+ end
end
- if prominence == 'requester_only'
- return info_request.is_owning_user?(user)
- end
- return true
end
-
end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index ead2eef5b8..aae7edfc32 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -82,6 +82,68 @@
let!(:owner_ability) { Ability.new(resource.user) }
it_behaves_like "a class with message prominence"
+
+ context 'when the request is embargoed' do
+ let!(:resource) { FactoryGirl.create(:embargoed_request) }
+ let(:admin_ability) { Ability.new(FactoryGirl.create(:admin_user)) }
+ let(:other_user_ability) { Ability.new(FactoryGirl.create(:user)) }
+
+ context 'if the prominence is hidden' do
+ before do
+ resource.prominence = 'hidden'
+ end
+
+ it 'should return true for an admin user' do
+ expect(admin_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return false for a non-admin user' do
+ expect(other_user_ability).not_to be_able_to(:read, resource)
+ end
+
+ it 'should return false for the owner' do
+ expect(owner_ability).not_to be_able_to(:read, resource)
+ end
+ end
+
+ context 'if the prominence is requester_only' do
+ before do
+ resource.prominence = 'requester_only'
+ end
+
+ it 'should return true if the user owns the right resource' do
+ expect(owner_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return true for an admin user' do
+ expect(admin_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return false if the user does not own the right resource' do
+ expect(other_user_ability).not_to be_able_to(:read, resource)
+ end
+ end
+
+ context 'if the prominence is normal' do
+ before do
+ resource.prominence = 'normal'
+ end
+
+ it 'should return false for a non-admin user' do
+ expect(other_user_ability).not_to be_able_to(:read, resource)
+ end
+
+ it 'should return true for an admin user' do
+ expect(admin_ability).to be_able_to(:read, resource)
+ end
+
+ it 'should return true if the user owns the right resource' do
+ expect(owner_ability).to be_able_to(:read, resource)
+ end
+ end
+
+ end
+
end
describe "updating request state of InfoRequests" do
From d20f9c43e4d02a4d90b4e8466542bfffcba7903e Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 8 Nov 2016 13:07:23 +0000
Subject: [PATCH 030/311] Make scope names match prominence methods
---
app/controllers/admin_general_controller.rb | 2 +-
app/controllers/general_controller.rb | 4 ++--
.../info_request_batch_controller.rb | 2 +-
app/controllers/public_body_controller.rb | 2 +-
app/models/comment.rb | 2 +-
app/models/info_request.rb | 9 ++++++-
.../info_request/prominence/public_query.rb | 14 +++++++++++
.../{visible_query.rb => searchable_query.rb} | 3 ++-
.../general/_frontpage_search_box.html.erb | 2 +-
app/views/info_request_batch/show.html.erb | 2 +-
.../public_body/_body_listing_single.html.erb | 4 ++--
lib/tasks/stats.rake | 2 +-
.../public_body_controller_spec.rb | 2 +-
.../prominence/public_query_spec.rb | 24 +++++++++++++++++++
...query_spec.rb => searchable_query_spec.rb} | 3 ++-
spec/views/public_body/list.html.erb_spec.rb | 2 +-
16 files changed, 63 insertions(+), 16 deletions(-)
create mode 100644 app/models/info_request/prominence/public_query.rb
rename app/models/info_request/prominence/{visible_query.rb => searchable_query.rb} (90%)
create mode 100644 spec/models/info_request/prominence/public_query_spec.rb
rename spec/models/info_request/prominence/{visible_query_spec.rb => searchable_query_spec.rb} (92%)
diff --git a/app/controllers/admin_general_controller.rb b/app/controllers/admin_general_controller.rb
index f1bb5f884f..c509c88063 100644
--- a/app/controllers/admin_general_controller.rb
+++ b/app/controllers/admin_general_controller.rb
@@ -19,7 +19,7 @@ def index
select { |pb| !pb.defunct? }
@old_unclassified = InfoRequest.where_old_unclassified.
limit(20).
- where(:prominence => 'normal')
+ is_searchable
@holding_pen_messages = InfoRequest.
includes(:incoming_messages => :raw_email).
holding_pen_request.
diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb
index ed3f929b98..c5b6941541 100644
--- a/app/controllers/general_controller.rb
+++ b/app/controllers/general_controller.rb
@@ -19,7 +19,7 @@ def frontpage
successful_query = InfoRequestEvent.make_query_from_params( :latest_status => ['successful'] )
@request_events, @request_events_all_successful = InfoRequest.recent_requests
@track_thing = TrackThing.create_track_for_search_query(successful_query)
- @number_of_requests = InfoRequest.visible.count
+ @number_of_requests = InfoRequest.is_searchable.count
@number_of_authorities = PublicBody.visible.count
@feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'),
:title => _('Successful requests'),
@@ -220,7 +220,7 @@ def version
:alaveteli_version => ALAVETELI_VERSION,
:ruby_version => RUBY_VERSION,
:visible_public_body_count => PublicBody.visible.count,
- :visible_request_count => InfoRequest.visible.count,
+ :visible_request_count => InfoRequest.is_searchable.count,
:confirmed_user_count => User.not_banned.
where(:email_confirmed => true).count,
:visible_comment_count => Comment.visible.count,
diff --git a/app/controllers/info_request_batch_controller.rb b/app/controllers/info_request_batch_controller.rb
index 1727533ec6..6432bc734a 100644
--- a/app/controllers/info_request_batch_controller.rb
+++ b/app/controllers/info_request_batch_controller.rb
@@ -6,7 +6,7 @@ def show
@per_page = 25
@page = get_search_page_from_params
if @info_request_batch.sent_at
- @info_requests = @info_request_batch.info_requests.visible.all(:offset => (@page - 1) * @per_page,
+ @info_requests = @info_request_batch.info_requests.is_searchable.all(:offset => (@page - 1) * @per_page,
:limit => @per_page)
else
@public_bodies = @info_request_batch.public_bodies.all(:offset => (@page - 1) * @per_page,
diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb
index 8fcf57960e..b2c3f85436 100644
--- a/app/controllers/public_body_controller.rb
+++ b/app/controllers/public_body_controller.rb
@@ -45,7 +45,7 @@ def show
set_last_body(@public_body)
- @number_of_visible_requests = @public_body.info_requests.visible.count
+ @number_of_visible_requests = @public_body.info_requests.is_searchable.count
@searched_to_send_request = false
referrer = request.env['HTTP_REFERER']
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 29e14c5470..29682a6178 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -33,7 +33,7 @@ class Comment < ActiveRecord::Base
:check_body_uses_mixed_capitals
scope :visible, -> {
- joins(:info_request).merge(InfoRequest.visible).where(:visible => true)
+ joins(:info_request).merge(InfoRequest.is_searchable).where(:visible => true)
}
after_save :event_xapian_update
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 20a5ecabc8..278aa8dcce 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -82,10 +82,17 @@ class InfoRequest < ActiveRecord::Base
has_tag_string
- scope :visible, Prominence::VisibleQuery.new
+ scope :is_public, Prominence::PublicQuery.new
+ scope :is_searchable, Prominence::SearchableQuery.new
scope :embargoed, Prominence::EmbargoedQuery.new
scope :not_embargoed, Prominence::NotEmbargoedQuery.new
+ def self.visible
+ warn %q([DEPRECATION] InfoRequest#visible will be removed in
+ 0.27. It has been replaced by InfoRequest#is_public).squish
+ self.is_public
+ end
+
# user described state (also update in info_request_event, admin_request/edit.rhtml)
validate :must_be_valid_state
validates_inclusion_of :prominence, :in => Prominence::VALUES
diff --git a/app/models/info_request/prominence/public_query.rb b/app/models/info_request/prominence/public_query.rb
new file mode 100644
index 0000000000..8a7bdcb3af
--- /dev/null
+++ b/app/models/info_request/prominence/public_query.rb
@@ -0,0 +1,14 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module Prominence
+ class PublicQuery
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ @relation.where(prominence: ['normal', 'backpage']).not_embargoed
+ end
+ end
+ end
+end
diff --git a/app/models/info_request/prominence/visible_query.rb b/app/models/info_request/prominence/searchable_query.rb
similarity index 90%
rename from app/models/info_request/prominence/visible_query.rb
rename to app/models/info_request/prominence/searchable_query.rb
index d6ab6bef9d..7b55d818f0 100644
--- a/app/models/info_request/prominence/visible_query.rb
+++ b/app/models/info_request/prominence/searchable_query.rb
@@ -1,7 +1,7 @@
# -*- encoding : utf-8 -*-
class InfoRequest
module Prominence
- class VisibleQuery
+ class SearchableQuery
def initialize(relation = InfoRequest)
@relation = relation
end
@@ -12,3 +12,4 @@ def call
end
end
end
+
diff --git a/app/views/general/_frontpage_search_box.html.erb b/app/views/general/_frontpage_search_box.html.erb
index 747a725d5e..77af362d6f 100644
--- a/app/views/general/_frontpage_search_box.html.erb
+++ b/app/views/general/_frontpage_search_box.html.erb
@@ -3,7 +3,7 @@
"{{number_of_requests}} requests " \
"and " \
"{{number_of_authorities}} authorities",
- :number_of_requests => number_with_delimiter(InfoRequest.visible.count),
+ :number_of_requests => number_with_delimiter(InfoRequest.is_searchable.count),
:number_of_authorities => number_with_delimiter(PublicBody.visible.count)) %>
<% if request_link && !public_body.special_not_requestable_reason? %>
diff --git a/lib/tasks/stats.rake b/lib/tasks/stats.rake
index 10a307537f..1869fa3d36 100644
--- a/lib/tasks/stats.rake
+++ b/lib/tasks/stats.rake
@@ -278,7 +278,7 @@ namespace :stats do
puts CSV.generate_line(["public_body_id", "public_body_name", "request_created_timestamp", "request_title", "request_body"])
PublicBody.limit(20).order('info_requests_visible_count DESC').each do |body|
- body.info_requests.where(:prominence => 'normal').find_each do |request|
+ body.info_requests.is_searchable.find_each do |request|
puts CSV.generate_line([request.public_body.id, request.public_body.name, request.created_at, request.url_title, request.initial_request_text.gsub("\r\n", " ").gsub("\n", " ")])
end
end
diff --git a/spec/controllers/public_body_controller_spec.rb b/spec/controllers/public_body_controller_spec.rb
index 2e8b5df4cc..dfa1f5d122 100644
--- a/spec/controllers/public_body_controller_spec.rb
+++ b/spec/controllers/public_body_controller_spec.rb
@@ -338,7 +338,7 @@ def make_single_language_example(locale)
visible_request = FactoryGirl.create(:info_request, :public_body => fake_pb)
fake_pb.reload
expect(fake_pb.info_requests.size).to eq(2)
- expect(fake_pb.info_requests.visible.size).to eq(1)
+ expect(fake_pb.info_requests.is_searchable.size).to eq(1)
fake_list = [fake_pb]
allow(fake_list).to receive(:joins).and_return(fake_list)
allow(fake_list).to receive(:paginate).and_return(fake_list)
diff --git a/spec/models/info_request/prominence/public_query_spec.rb b/spec/models/info_request/prominence/public_query_spec.rb
new file mode 100644
index 0000000000..a7391b7020
--- /dev/null
+++ b/spec/models/info_request/prominence/public_query_spec.rb
@@ -0,0 +1,24 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
+
+describe InfoRequest::Prominence::PublicQuery do
+
+ describe '#call' do
+
+ it 'returns only results with a normal or backpaged prominence' do
+ normal_request = FactoryGirl.create(:info_request)
+ backpaged_request = FactoryGirl.create(:info_request, :prominence => 'backpage')
+ hidden_request = FactoryGirl.create(:info_request, :prominence => 'hidden')
+ requests = described_class.new.call
+ expect(requests).to include(normal_request)
+ expect(requests).to include(backpaged_request)
+ expect(requests).not_to include(hidden_request)
+ end
+
+ it 'does not return an embargoed request' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect(described_class.new.call).not_to include(embargoed_request)
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/spec/models/info_request/prominence/visible_query_spec.rb b/spec/models/info_request/prominence/searchable_query_spec.rb
similarity index 92%
rename from spec/models/info_request/prominence/visible_query_spec.rb
rename to spec/models/info_request/prominence/searchable_query_spec.rb
index 399ba4fe78..2f99b54b5e 100644
--- a/spec/models/info_request/prominence/visible_query_spec.rb
+++ b/spec/models/info_request/prominence/searchable_query_spec.rb
@@ -1,7 +1,8 @@
+
# -*- encoding : utf-8 -*-
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
-describe InfoRequest::Prominence::VisibleQuery do
+describe InfoRequest::Prominence::SearchableQuery do
describe '#call' do
diff --git a/spec/views/public_body/list.html.erb_spec.rb b/spec/views/public_body/list.html.erb_spec.rb
index c095a924d9..29a43a983b 100644
--- a/spec/views/public_body/list.html.erb_spec.rb
+++ b/spec/views/public_body/list.html.erb_spec.rb
@@ -14,7 +14,7 @@
:eir_only? => nil,
:publication_scheme => '')
pb_info_requests = [1, 2, 3, 4]
- allow(pb_info_requests).to receive(:visible).and_return([2, 3, 4])
+ allow(pb_info_requests).to receive(:is_searchable).and_return([2, 3, 4])
allow(@pb).to receive(:info_requests).and_return(pb_info_requests)
From 1acc981b6f55b8cdc19c8caf2f49c5d91c6db2fe Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 8 Nov 2016 13:23:02 +0000
Subject: [PATCH 031/311] Use searchable scope rather than referencing
prominence directly.
---
app/controllers/request_game_controller.rb | 4 ++--
app/models/info_request.rb | 11 ++++++-----
lib/tasks/stats.rake | 3 +--
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/app/controllers/request_game_controller.rb b/app/controllers/request_game_controller.rb
index b48f99708f..7c4a180c84 100644
--- a/app/controllers/request_game_controller.rb
+++ b/app/controllers/request_game_controller.rb
@@ -12,7 +12,7 @@ def play
@missing = InfoRequest.
where_old_unclassified.
- where(:prominence => 'normal').
+ is_searchable.
count
@total = InfoRequest.count
@done = @total - @missing
@@ -21,7 +21,7 @@ def play
includes(:public_body, :user).
where_old_unclassified.
limit(3).
- where(:prominence => 'normal').
+ is_searchable.
order('random()')
if @missing == 0
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 278aa8dcce..06f3d31f11 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -1272,14 +1272,15 @@ def update_counter_cache
success_states = ['successful', 'partially_successful']
basic_params = {
:public_body_id => public_body_id,
- :prominence => 'normal'
}
- [['info_requests_not_held_count', {:awaiting_description => false, :described_state => 'not_held'}],
- ['info_requests_successful_count', {:awaiting_description => false, :described_state => success_states}],
- ['info_requests_visible_classified_count', {:awaiting_description => false}],
+ [['info_requests_not_held_count', { :awaiting_description => false,
+ :described_state => 'not_held' }],
+ ['info_requests_successful_count', { :awaiting_description => false,
+ :described_state => success_states }],
+ ['info_requests_visible_classified_count', { :awaiting_description => false }],
['info_requests_visible_count', {}]].each do |column, extra_params|
params = basic_params.clone.update extra_params
- public_body.send "#{column}=", InfoRequest.where(params).count
+ public_body.send "#{column}=", InfoRequest.where(params).is_searchable.count
end
public_body.without_revision do
public_body.no_xapian_reindex = true
diff --git a/lib/tasks/stats.rake b/lib/tasks/stats.rake
index 1869fa3d36..6df0df0546 100644
--- a/lib/tasks/stats.rake
+++ b/lib/tasks/stats.rake
@@ -212,11 +212,10 @@ namespace :stats do
# described_state column, and instead need to be calculated:
overdue_count = 0
very_overdue_count = 0
- InfoRequest.find_each(:batch_size => 200,
+ InfoRequest.is_searchable.find_each(:batch_size => 200,
:conditions => {
:public_body_id => public_body.id,
:awaiting_description => false,
- :prominence => 'normal'
}) do |ir|
case ir.calculate_status
when 'waiting_response_very_overdue'
From 1cbbd8d55f40868c026facfb2930bf423fca80d1 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 8 Nov 2016 13:46:38 +0000
Subject: [PATCH 032/311] Don't send classification reminders for embargoed
requests.
---
app/mailers/request_mailer.rb | 5 +++--
spec/mailers/request_mailer_spec.rb | 10 ++++++++++
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/app/mailers/request_mailer.rb b/app/mailers/request_mailer.rb
index 296d989b13..210ecf0e86 100644
--- a/app/mailers/request_mailer.rb
+++ b/app/mailers/request_mailer.rb
@@ -345,8 +345,9 @@ def self.alert_new_response_reminders
def self.alert_new_response_reminders_internal(days_since, type_code)
info_requests = InfoRequest.
where_old_unclassified(days_since).
- order('info_requests.id').
- includes(:user)
+ not_embargoed.
+ order('info_requests.id').
+ includes(:user)
info_requests.each do |info_request|
alert_event_id = info_request.get_last_public_response_event_id
diff --git a/spec/mailers/request_mailer_spec.rb b/spec/mailers/request_mailer_spec.rb
index 81bc32b57c..d4bc4989dd 100644
--- a/spec/mailers/request_mailer_spec.rb
+++ b/spec/mailers/request_mailer_spec.rb
@@ -283,6 +283,16 @@ def sent_alert_params(request, type)
expect{ send_alerts }.to raise_error(expected_message)
end
+ context 'if the request is embargoed' do
+
+ it 'does not send the reminder' do
+ old_request.create_embargo(:publish_at => Time.now + 3.days)
+ expect(RequestMailer).not_to receive(:new_response_reminder_alert)
+ send_alerts
+ end
+
+ end
+
context 'if an alert matching the attributes of the reminder to be sent has already been sent' do
it 'should not send the reminder' do
From 7dcffcb48a06ebff3c67511e7f7efa8f914a6ad6 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 8 Nov 2016 13:47:28 +0000
Subject: [PATCH 033/311] Update graph SQL - looks for normal, not embargoed
requests.
---
lib/tasks/graphs.rake | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/lib/tasks/graphs.rake b/lib/tasks/graphs.rake
index 8a70603061..571bc16467 100644
--- a/lib/tasks/graphs.rake
+++ b/lib/tasks/graphs.rake
@@ -151,10 +151,13 @@ namespace :graphs do
def assemble_sql(where_clause="")
"SELECT DATE(created_at), COUNT(*) " \
"FROM info_requests " \
+ "LEFT OUTER JOIN embargoes " \
+ "ON embargoes.info_request_id = info_requests.id " \
"WHERE #{where_clause} " \
- "AND PROMINENCE != 'backpage' " \
- "GROUP BY DATE(created_at)" \
- "ORDER BY DATE(created_at)"
+ "AND PROMINENCE = 'normal' " \
+ "AND (embargoes.id IS NULL) " \
+ "GROUP BY DATE(info_requests.created_at)" \
+ "ORDER BY DATE(info_requests.created_at)"
end
def state_exclusion_sql(states)
From ee322b7d88d2f47c782fe689711e92003557100f Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 15 Nov 2016 14:41:32 +0000
Subject: [PATCH 034/311] Add some release notes.
---
doc/CHANGES.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/doc/CHANGES.md b/doc/CHANGES.md
index 8038e4248b..a5f84dd002 100644
--- a/doc/CHANGES.md
+++ b/doc/CHANGES.md
@@ -163,6 +163,9 @@
page (Louise Crow)
* Added strong parameters gem for better mass assignment security (Gareth Rees)
* Added experimental Xapian database replication (Hazel Smith, Louise Crow)
+* Request prominence logic more consistent, embargoed requests introduced as an
+ Alaveteli Pro feature (Louise Crow)
+
## Upgrade Notes
From c7b5939984878c59896fbb21b6cf8ce46cbe465d Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Thu, 17 Nov 2016 14:43:53 +0000
Subject: [PATCH 035/311] Line length fixes.
---
app/controllers/request_controller.rb | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 5436fd1060..8382e49fe2 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -142,7 +142,12 @@ def details
if cannot?(:read, @info_request)
return render_hidden
end
- @columns = ['id', 'event_type', 'created_at', 'described_state', 'last_described_at', 'calculated_state' ]
+ @columns = ['id',
+ 'event_type',
+ 'created_at',
+ 'described_state',
+ 'last_described_at',
+ 'calculated_state' ]
end
# Requests similar to this one
@@ -156,13 +161,15 @@ def similar
raise ActiveRecord::RecordNotFound.new("Sorry. No pages after #{MAX_RESULTS / PER_PAGE}.")
end
@info_request = InfoRequest.find_by_url_title!(params[:url_title])
- raise ActiveRecord::RecordNotFound.new("Request not found") if @info_request.nil?
if cannot?(:read, @info_request)
return render_hidden
end
- @xapian_object = ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events,
- :offset => (@page - 1) * @per_page, :limit => @per_page, :collapse_by_prefix => 'request_collapse')
+ @xapian_object = ActsAsXapian::Similar.new([InfoRequestEvent],
+ @info_request.info_request_events,
+ :offset => (@page - 1) * @per_page,
+ :limit => @per_page,
+ :collapse_by_prefix => 'request_collapse')
@matches_estimated = @xapian_object.matches_estimated
@show_no_more_than = (@matches_estimated > MAX_RESULTS) ? MAX_RESULTS : @matches_estimated
end
From 4686fb3e24cd2c3f2e64d166e32ff047c011ec47 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Thu, 17 Nov 2016 14:45:26 +0000
Subject: [PATCH 036/311] Cleanup spec a bit.
Line length, clarity of description, repetition.
---
spec/controllers/request_controller_spec.rb | 24 +++++++++++++--------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 91ba4b6208..a765666175 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2265,36 +2265,42 @@ def expect_redirect(status, redirect_path)
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
+ let(:badger_request){ info_requests(:badger_request) }
+
+ it "renders the 'similar' template" do
get :similar, :url_title => info_requests(:badger_request).url_title
expect(response).to render_template("request/similar")
+ end
+
+ it 'assigns the request' do
+ get :similar, :url_title => info_requests(:badger_request).url_title
expect(assigns[:info_request]).to eq(info_requests(:badger_request))
end
- it "should show similar requests" do
- badger_request = info_requests(:badger_request)
+ it "assigns a xapian object with similar requests" do
get :similar, :url_title => badger_request.url_title
# Xapian seems to think *all* the requests are similar
- expect(assigns[:xapian_object].results.map{|x|x[:model].info_request}).to match_array(InfoRequest.all.reject {|x| x == badger_request})
+ results = assigns[:xapian_object].results
+ expected = InfoRequest.all.reject{ |request| request == badger_request }
+ expect(results.map{ |result| result[:model].info_request })
+ .to match_array(expected)
end
- it "should 404 for non-existent paths" do
+ it "raises ActiveRecord::RecordNotFound for non-existent paths" do
expect {
get :similar, :url_title => "there_is_really_no_such_path_owNAFkHR"
}.to raise_error(ActiveRecord::RecordNotFound)
end
-
- it "should return 404 for pages we don't want to serve up" do
- badger_request = info_requests(:badger_request)
+ it "raises ActiveRecord::RecordNotFound for pages beyond the last
+ page we want to show" do
expect {
get :similar, :url_title => badger_request.url_title, :page => 100
}.to raise_error(ActiveRecord::RecordNotFound)
From 22e75784b73b654d86f3687b83c985ebf45eb638 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Wed, 2 Nov 2016 07:13:44 +0000
Subject: [PATCH 037/311] Add an ENABLE_ALAVETELI_PRO setting and feature
---
config/general.yml-example | 15 +++++++++++++++
config/initializers/alaveteli_features.rb | 8 ++++++++
lib/configuration.rb | 1 +
3 files changed, 24 insertions(+)
diff --git a/config/general.yml-example b/config/general.yml-example
index 11aef065fa..76e345adf7 100644
--- a/config/general.yml-example
+++ b/config/general.yml-example
@@ -957,3 +957,18 @@ RESTRICTED_COUNTRIES: ''
#
# ---
NEW_REQUEST_RECAPTCHA: false
+
+# Enable AlaveteliProfessional
+# If ENABLE_ALAVETELI_PRO is set to true, Alaveteli will include extra
+# functionality and account levels for professional FOI users, e.g.
+# journalists. Professional users have access to a new dashboard, a more
+# streamlined request process, and crucially, the ability to embargo their
+# requests so that they remain private.
+#
+# Enabling this is a large change, and this is still in Alpha development, so
+# you may want to contact the Alaveteli team before doing so.
+#
+# ENABLE_ALAVETELI_PRO – Boolean (default: false)
+#
+# ---
+ENABLE_ALAVETELI_PRO: false
diff --git a/config/initializers/alaveteli_features.rb b/config/initializers/alaveteli_features.rb
index 44efe5c637..376dcb91ec 100644
--- a/config/initializers/alaveteli_features.rb
+++ b/config/initializers/alaveteli_features.rb
@@ -14,3 +14,11 @@
else
AlaveteliFeatures.backend.disable(:annotations)
end
+
+# AlaveteliPro
+# We enable alaveteli_pro globally based on the ENABLE_ALAVETELI_PRO config
+if AlaveteliConfiguration.enable_alaveteli_pro
+ AlaveteliFeatures.backend.enable(:alaveteli_pro)
+else
+ AlaveteliFeatures.backend.disable(:alaveteli_pro)
+end
diff --git a/lib/configuration.rb b/lib/configuration.rb
index 2b2b7ff92c..62dbd37f32 100644
--- a/lib/configuration.rb
+++ b/lib/configuration.rb
@@ -38,6 +38,7 @@ module AlaveteliConfiguration
:ENABLE_ANTI_SPAM => false,
:ENABLE_TWO_FACTOR_AUTH => false,
:ENABLE_WIDGETS => false,
+ :ENABLE_ALAVETELI_PRO => false,
:EXCEPTION_NOTIFICATIONS_FROM => 'errors@localhost',
:EXCEPTION_NOTIFICATIONS_TO => 'user-support@localhost',
:FORCE_REGISTRATION_ON_NEW_REQUEST => false,
From 0e8936528d2c3ace34650d5876744b8d5396bab3 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Thu, 17 Nov 2016 14:49:54 +0000
Subject: [PATCH 038/311] Raise ActiveRecord::RecordNotFound for embargoed
requests.
This applies to the `show` action and should cause it to
behave just like a request for a non-existent url_title.
---
app/controllers/request_controller.rb | 10 ++++++++++
spec/controllers/request_controller_spec.rb | 18 +++++++++++++++++-
2 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 8382e49fe2..2c09896b02 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -829,6 +829,16 @@ def download_entire_request
private
+ def render_hidden(template='request/hidden', opts = {})
+ # An embargoed is totally hidden - no indication that anything exists there
+ # to see
+ if @info_request && @info_request.embargo
+ raise ActiveRecord::RecordNotFound
+ else
+ return super(template, opts)
+ end
+ end
+
def info_request_params(batch = false)
if batch
unless params[:info_request].nil? || params[:info_request].empty?
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index a765666175..6b4a9ee36d 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -239,6 +239,14 @@
end
end
+ context 'when the request is embargoed' do
+ it 'raises ActiveRecord::RecordNotFound' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect{ get :show, :url_title => embargoed_request.url_title }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
describe 'when the request is being viewed by an admin' do
describe 'if the request is awaiting description' do
@@ -2306,13 +2314,21 @@ def expect_redirect(status, redirect_path)
}.to raise_error(ActiveRecord::RecordNotFound)
end
+ it 'raises ActiveRecord::RecordNotFound if the request is embargoed' do
+ badger_request.create_embargo(:publish_at => Time.now + 3.days)
+ expect {
+ get :similar, :url_title => badger_request.url_title
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
end
describe RequestController, "when caching fragments" do
it "should not fail with long filenames" do
long_name = "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah.txt"
info_request = double(InfoRequest, :prominence => 'normal',
- :is_public? => true)
+ :is_public? => true,
+ :embargo => nil)
incoming_message = double(IncomingMessage, :info_request => info_request,
:parse_raw_email! => true,
:info_request_id => 132,
From 1bf530d866153e2260ab2bf3a6e24b0b07ef55b8 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Thu, 17 Nov 2016 14:50:41 +0000
Subject: [PATCH 039/311] Add specs for details action.
---
spec/controllers/request_controller_spec.rb | 63 +++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 6b4a9ee36d..81a0c3346b 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2660,3 +2660,66 @@ def make_request
end
end
end
+
+describe RequestController do
+
+ describe 'GET details' do
+
+ let(:info_request){ FactoryGirl.create(:info_request)}
+
+ it 'renders the details template' do
+ get :details, :url_title => info_request.url_title
+ expect(response).to render_template('details')
+ end
+
+ it 'assigns the info_request' do
+ get :details, :url_title => info_request.url_title
+ expect(assigns[:info_request]).to eq(info_request)
+ end
+
+ it 'assigns columns' do
+ get :details, :url_title => info_request.url_title
+ expected_columns = ['id',
+ 'event_type',
+ 'created_at',
+ 'described_state',
+ 'last_described_at',
+ 'calculated_state' ]
+ expect(assigns[:columns]).to eq expected_columns
+ end
+
+ context 'when the request is hidden' do
+
+ before do
+ info_request.prominence = 'hidden'
+ info_request.save!
+ end
+
+ it 'returns a 403' do
+ get :details, :url_title => info_request.url_title
+ expect(response.code).to eq("403")
+ end
+
+ it 'shows the hidden request template' do
+ get :details, :url_title => info_request.url_title
+ expect(response).to render_template("request/hidden")
+ end
+
+ end
+
+ context 'when the request is embargoed' do
+
+ before do
+ info_request.create_embargo(:publish_at => Time.now + 3.days)
+ end
+
+ it 'raises an ActiveRecord::RecordNotFound error' do
+ expect{ get :details, :url_title => info_request.url_title }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+
+ end
+
+end
From a3ec901986faf2686a1a2746637e4008f643af58 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 18 Nov 2016 16:58:45 +0000
Subject: [PATCH 040/311] Cleanup specs for describe_state
Move away from fixtures and mocking lots to factories.
---
spec/controllers/request_controller_spec.rb | 844 ++++++++++----------
1 file changed, 438 insertions(+), 406 deletions(-)
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 81a0c3346b..8e4c57559c 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -1525,537 +1525,569 @@ def expect_hidden(hidden_template)
end
+describe RequestController do
+ describe "POST describe_state" do
+ describe 'if the request is external' do
+ let(:external_request){ FactoryGirl.create(:external_request) }
-describe RequestController, "when classifying an information request" do
-
- describe 'if the request is external' do
-
- before do
- @external_request = info_requests(:external_request)
- end
-
- it 'should redirect to the request page' do
- post :describe_state, :id => @external_request.id
- expect(response).to redirect_to(:action => 'show',
- :controller => 'request',
- :url_title => @external_request.url_title)
- end
-
- end
-
- describe 'when the request is internal' do
-
- before(:each) do
- @dog_request = info_requests(:fancy_dog_request)
- allow(@dog_request).to receive(:is_old_unclassified?).and_return(false)
- allow(InfoRequest).to receive(:find).and_return(@dog_request)
- load_raw_emails_data
- end
+ it 'should redirect to the request page' do
+ post :describe_state, :id => external_request.id
+ expect(response)
+ .to redirect_to(show_request_path(external_request.url_title))
+ end
- def post_status(status)
- post :describe_state, :incoming_message => { :described_state => status },
- :id => @dog_request.id,
- :last_info_request_event_id => @dog_request.last_event_id_needing_description
end
- it "should require login" do
- post_status('rejected')
- expect(response).to redirect_to(:controller => 'user',
- :action => 'signin',
- :token => get_last_post_redirect.token)
- end
+ describe 'when the request is internal' do
- it 'should ask whether the request is old and unclassified' do
- session[:user_id] = users(:silly_name_user).id
- expect(@dog_request).to receive(:is_old_unclassified?)
- post_status('rejected')
- end
+ let(:info_request){ FactoryGirl.create(:info_request) }
- it "should not classify the request if logged in as the wrong user" do
- session[:user_id] = users(:silly_name_user).id
- post_status('rejected')
- expect(response).to render_template('user/wrong_user')
- end
-
- describe 'when the request is old and unclassified' do
+ def post_status(status, info_request)
+ post :describe_state, :incoming_message => { :described_state => status },
+ :id => info_request.id,
+ :last_info_request_event_id => info_request.last_event_id_needing_description
+ end
- before do
- allow(@dog_request).to receive(:is_old_unclassified?).and_return(true)
- mail_mock = double("mail")
- allow(mail_mock).to receive(:deliver)
- allow(RequestMailer).to receive(:old_unclassified_updated).and_return(mail_mock)
+ it "should require login" do
+ post_status('rejected', info_request)
+ expect(response).to redirect_to(:controller => 'user',
+ :action => 'signin',
+ :token => get_last_post_redirect.token)
end
- describe 'when the user is not logged in' do
+ it "should not classify the request if logged in as the wrong user" do
+ session[:user_id] = FactoryGirl.create(:user).id
+ post_status('rejected', info_request)
+ expect(response).to render_template('user/wrong_user')
+ end
- it 'should require login' do
- session[:user_id] = nil
- post_status('rejected')
- expect(response).to redirect_to(:controller => 'user',
- :action => 'signin',
- :token => get_last_post_redirect.token)
- end
+ describe 'when the request is old and unclassified' do
- end
+ let(:info_request){ FactoryGirl.create(:old_unclassified_request)}
- describe 'when the user is logged in as a different user' do
+ describe 'when the user is not logged in' do
- before do
- @other_user = mock_model(User)
- session[:user_id] = users(:silly_name_user).id
- end
+ it 'should require login' do
+ session[:user_id] = nil
+ post_status('rejected', info_request)
+ expect(response)
+ .to redirect_to(signin_path(:token => get_last_post_redirect.token))
+ end
- it 'should classify the request' do
- allow(@dog_request).to receive(:calculate_status).and_return('rejected')
- expect(@dog_request).to receive(:set_described_state).with('rejected', users(:silly_name_user), nil)
- post_status('rejected')
end
- it 'should log a status update event' do
- expected_params = {:user_id => users(:silly_name_user).id,
- :old_described_state => 'waiting_response',
- :described_state => 'rejected'}
- event = mock_model(InfoRequestEvent)
- expect(@dog_request).to receive(:log_event).with("status_update", expected_params).and_return(event)
- post_status('rejected')
- end
+ describe 'when the user is logged in as a different user' do
- it 'should send an email to the requester letting them know someone has updated the status of their request' do
- expect(RequestMailer).to receive(:old_unclassified_updated)
- post_status('rejected')
- end
+ let(:other_user){ FactoryGirl.create(:user) }
- it 'should redirect to the request page' do
- post_status('rejected')
- expect(response).to redirect_to(:action => 'show', :controller => 'request', :url_title => @dog_request.url_title)
- end
+ before do
+ session[:user_id] = other_user
+ end
- it 'should show a message thanking the user for a good deed' do
- post_status('rejected')
- expect(flash[:notice]).to eq('Thank you for updating this request!')
- end
+ it 'should classify the request' do
+ post_status('rejected', info_request)
+ expect(info_request.reload.described_state).to eq('rejected')
+ end
- context "playing the classification game" do
- before :each do
- session[:request_game] = true
+ it 'should log a status update event' do
+ expected_params = {:user_id => other_user.id,
+ :old_described_state => 'waiting_response',
+ :described_state => 'rejected'}
+ post_status('rejected', info_request)
+ last_event = info_request.reload.info_request_events.last
+ expect(last_event.params).to eq expected_params
end
- it "should continue the game after classifying a request" do
- post_status("rejected")
- expect(flash[:notice]).to match(/There are some more requests below for you to classify/)
- expect(response).to redirect_to categorise_play_url
+ it 'should send an email to the requester letting them know someone
+ has updated the status of their request' do
+ post_status('rejected', info_request)
+ deliveries = ActionMailer::Base.deliveries
+ expect(deliveries.size).to eq(1)
+ expect(deliveries.first.subject)
+ .to match("Someone has updated the status of your request")
end
- end
- it "should send a mail from the user who changed the state to requires_admin" do
- post :describe_state, :incoming_message =>
- { :described_state => "requires_admin",
- :message => "a message" },
- :id => @dog_request.id,
- :incoming_message_id =>
- incoming_messages(:useless_incoming_message),
- :last_info_request_event_id =>
- @dog_request.last_event_id_needing_description
+ it 'should redirect to the request page' do
+ post_status('rejected', info_request)
+ expect(response).to redirect_to(show_request_path(info_request.url_title))
+ end
- deliveries = ActionMailer::Base.deliveries
- expect(deliveries.size).to eq(1)
- mail = deliveries[0]
- expect(mail.header['Reply-To'].to_s).to match(users(:silly_name_user).email)
- end
- end
- end
+ it 'should show a message thanking the user for a good deed' do
+ post_status('rejected', info_request)
+ expect(flash[:notice]).to eq('Thank you for updating this request!')
+ end
- describe 'when logged in as an admin user who is not the actual requester' do
+ context "playing the classification game" do
+ before :each do
+ session[:request_game] = true
+ end
- before do
- @admin_user = users(:admin_user)
- session[:user_id] = @admin_user.id
- @dog_request = info_requests(:fancy_dog_request)
- allow(InfoRequest).to receive(:find).and_return(@dog_request)
- allow(@dog_request).to receive(:each).and_return([@dog_request])
- end
+ it "should continue the game after classifying a request" do
+ post_status("rejected", info_request)
+ expect(flash[:notice]).to match(/There are some more requests below for you to classify/)
+ expect(response).to redirect_to categorise_play_url
+ end
+ end
- it 'should update the status of the request' do
- allow(@dog_request).to receive(:calculate_status).and_return('rejected')
- expect(@dog_request).to receive(:set_described_state).with('rejected', @admin_user, nil)
- post_status('rejected')
+ context 'when the new status is "requires_admin"' do
+ it "should send a mail to admins saying that the response requires admin
+ and one to the requester noting the status change" do
+ post :describe_state, :incoming_message =>
+ { :described_state => "requires_admin",
+ :message => "a message" },
+ :id => info_request.id,
+ :incoming_message_id =>
+ info_request.incoming_messages.last,
+ :last_info_request_event_id =>
+ info_request.last_event_id_needing_description
+
+ deliveries = ActionMailer::Base.deliveries
+ expect(deliveries.size).to eq(2)
+ requires_admin_mail = deliveries.first
+ status_update_mail = deliveries.second
+ expect(requires_admin_mail.subject)
+ .to match(/FOI response requires admin/)
+ expect(requires_admin_mail.to)
+ .to match([AlaveteliConfiguration::contact_email])
+ expect(status_update_mail.subject)
+ .to match('Someone has updated the status of your request')
+ expect(status_update_mail.to)
+ .to match([info_request.user.email])
+ end
+ end
+ end
end
- it 'should log a status update event' do
- event = mock_model(InfoRequestEvent)
- expected_params = {:user_id => @admin_user.id,
- :old_described_state => 'waiting_response',
- :described_state => 'rejected'}
- expect(@dog_request).to receive(:log_event).with("status_update", expected_params).and_return(event)
- post_status('rejected')
- end
+ describe 'when logged in as an admin user who is not the actual requester' do
- it 'should record a classification' do
- event = mock_model(InfoRequestEvent)
- allow(@dog_request).to receive(:log_event).with("status_update", anything).and_return(event)
- expect(RequestClassification).to receive(:create!).with(:user_id => @admin_user.id,
- :info_request_event_id => event.id)
- post_status('rejected')
- end
+ let(:admin_user){ FactoryGirl.create(:admin_user) }
+ let(:info_request){ FactoryGirl.create(:info_request) }
- it 'should send an email to the requester letting them know someone has updated the status of their request' do
- mail_mock = double("mail")
- allow(mail_mock).to receive :deliver
- expect(RequestMailer).to receive(:old_unclassified_updated).and_return(mail_mock)
- post_status('rejected')
- end
+ before do
+ session[:user_id] = admin_user.id
+ end
- it 'should redirect to the request page' do
- post_status('rejected')
- expect(response).to redirect_to(:action => 'show', :controller => 'request', :url_title => @dog_request.url_title)
- end
+ it 'should update the status of the request' do
+ post_status('rejected', info_request)
+ expect(info_request.reload.described_state).to eq('rejected')
+ end
- it 'should show a message thanking the user for a good deed' do
- post_status('rejected')
- expect(flash[:notice]).to eq('Thank you for updating this request!')
- end
- end
+ it 'should log a status update event' do
+ expected_params = {:user_id => admin_user.id,
+ :old_described_state => 'waiting_response',
+ :described_state => 'rejected'}
+ post_status('rejected', info_request)
+ last_event = info_request.reload.info_request_events.last
+ expect(last_event.params).to eq expected_params
+ end
- describe 'when logged in as an admin user who is also the actual requester' do
+ it 'should record a classification' do
+ post_status('rejected', info_request)
+ last_event = info_request.reload.info_request_events.last
+ classification = RequestClassification.order('created_at DESC').last
+ expect(classification.user_id).to eq(admin_user.id)
+ expect(classification.info_request_event).to eq(last_event)
+ end
- render_views
+ it 'should send an email to the requester letting them know someone has
+ updated the status of their request' do
+ mail_mock = double("mail")
+ allow(mail_mock).to receive :deliver
+ expect(RequestMailer).to receive(:old_unclassified_updated).and_return(mail_mock)
+ post_status('rejected', info_request)
+ end
- before do
- @admin_user = users(:admin_user)
- session[:user_id] = @admin_user.id
- @dog_request = info_requests(:fancy_dog_request)
- @dog_request.user = @admin_user
- @dog_request.save!
- allow(InfoRequest).to receive(:find).and_return(@dog_request)
- allow(@dog_request).to receive(:each).and_return([@dog_request])
- end
+ it 'should redirect to the request page' do
+ post_status('rejected', info_request)
+ expect(response)
+ .to redirect_to(show_request_path(info_request.url_title))
+ end
- it 'should update the status of the request' do
- allow(@dog_request).to receive(:calculate_status).and_return('rejected')
- expect(@dog_request).to receive(:set_described_state).with('rejected', @admin_user, nil)
- post_status('rejected')
+ it 'should show a message thanking the user for a good deed' do
+ post_status('rejected', info_request)
+ expect(flash[:notice]).to eq('Thank you for updating this request!')
+ end
end
- it 'should log a status update event' do
- expect(@dog_request).to receive(:log_event)
- post_status('rejected')
- end
+ describe 'when logged in as an admin user who is also the actual requester' do
- it 'should not send an email to the requester letting them know someone has updated the status of their request' do
- expect(RequestMailer).not_to receive(:old_unclassified_updated)
- post_status('rejected')
- end
+ let(:admin_user){ FactoryGirl.create(:admin_user) }
+ let(:info_request){ FactoryGirl.create(:info_request, :user => admin_user) }
- it 'should say it is showing advice as to what to do next' do
- post_status('rejected')
- expect(flash[:notice]).to match(/Here is what to do now/)
- end
+ before do
+ session[:user_id] = admin_user.id
+ end
- it 'should redirect to the unhappy page' do
- post_status('rejected')
- expect(response).to redirect_to(:controller => 'help', :action => 'unhappy', :url_title => @dog_request.url_title)
- end
+ it 'should update the status of the request' do
+ post_status('rejected', info_request)
+ expect(info_request.reload.described_state).to eq('rejected')
+ end
- end
+ it 'should log a status update event' do
+ expected_params = { :user_id => admin_user.id,
+ :old_described_state => 'waiting_response',
+ :described_state => 'rejected' }
+ post_status('rejected', info_request)
+ last_event = info_request.reload.info_request_events.last
+ expect(last_event.params).to eq expected_params
+ end
- describe 'when logged in as the requestor' do
+ it 'should not send an email to the requester letting them know someone
+ has updated the status of their request' do
+ expect(RequestMailer).not_to receive(:old_unclassified_updated)
+ post_status('rejected', info_request)
+ end
- render_views
+ it 'should show advice for the new state' do
+ expect(controller)
+ .to receive(:render_to_string)
+ .with(:partial => 'request/describe_notices/rejected',
+ :locals => {:info_request => info_request})
+ .and_return('')
+ post_status('rejected', info_request)
+ end
- before do
- @request_owner = users(:bob_smith_user)
- session[:user_id] = @request_owner.id
- expect(@dog_request.awaiting_description).to eq(true)
- allow(@dog_request).to receive(:each).and_return([@dog_request])
- end
+ it 'should redirect to the unhappy page' do
+ post_status('rejected', info_request)
+ expect(response)
+ .to redirect_to(help_unhappy_path(info_request.url_title))
+ end
- it "should let you know when you forget to select a status" do
- post :describe_state, :id => @dog_request.id,
- :last_info_request_event_id => @dog_request.last_event_id_needing_description
- expect(response).to redirect_to show_request_url(:url_title => @dog_request.url_title)
- expect(flash[:error]).to eq(_("Please choose whether or not you got some of the information that you wanted."))
end
- it "should not change the status if the request has changed while viewing it" do
- allow(@dog_request).to receive(:last_event_id_needing_description).and_return(2)
+ describe 'when logged in as the requestor' do
- post :describe_state, :incoming_message => { :described_state => "rejected" },
- :id => @dog_request.id, :last_info_request_event_id => 1
- expect(response).to redirect_to show_request_url(:url_title => @dog_request.url_title)
- expect(flash[:error]).to match(/The request has been updated since you originally loaded this page/)
- end
+ let(:info_request) do
+ FactoryGirl.create(:info_request, :awaiting_description => true)
+ end
- it "should successfully classify response if logged in as user controlling request" do
- post_status('rejected')
- expect(response).to redirect_to(:controller => 'help', :action => 'unhappy', :url_title => @dog_request.url_title)
- @dog_request.reload
- expect(@dog_request.awaiting_description).to eq(false)
- expect(@dog_request.described_state).to eq('rejected')
- expect(@dog_request.get_last_public_response_event).to eq(info_request_events(:useless_incoming_message_event))
- expect(@dog_request.info_request_events.last.event_type).to eq("status_update")
- expect(@dog_request.info_request_events.last.calculated_state).to eq('rejected')
- end
+ before do
+ session[:user_id] = info_request.user_id
+ end
- it 'should log a status update event' do
- expect(@dog_request).to receive(:log_event)
- post_status('rejected')
- end
+ it "should let you know when you forget to select a status" do
+ post :describe_state, :id => info_request.id,
+ :last_info_request_event_id =>
+ info_request.last_event_id_needing_description
+ expect(response).to redirect_to show_request_url(:url_title => info_request.url_title)
+ expect(flash[:error])
+ .to eq("Please choose whether or not you got some of the information that you wanted.")
+ end
- it 'should not send an email to the requester letting them know someone has updated the status of their request' do
- expect(RequestMailer).not_to receive(:old_unclassified_updated)
- post_status('rejected')
- end
+ it "should not change the status if the request has changed while viewing it" do
+ post :describe_state, :incoming_message => { :described_state => "rejected" },
+ :id => info_request.id,
+ :last_info_request_event_id => 1
+ expect(response)
+ .to redirect_to show_request_url(:url_title => info_request.url_title)
+ expect(flash[:error])
+ .to match(/The request has been updated since you originally loaded this page/)
+ end
- it "should go to the page asking for more information when classified as requires_admin" do
- post :describe_state, :incoming_message => { :described_state => "requires_admin" }, :id => @dog_request.id, :incoming_message_id => incoming_messages(:useless_incoming_message), :last_info_request_event_id => @dog_request.last_event_id_needing_description
- expect(response).to redirect_to describe_state_message_url(:url_title => @dog_request.url_title, :described_state => "requires_admin")
+ it "should successfully classify response" do
+ post_status('rejected', info_request)
+ expect(response)
+ .to redirect_to(help_unhappy_path(info_request.url_title))
+ info_request.reload
+ expect(info_request.awaiting_description).to eq(false)
+ expect(info_request.described_state).to eq('rejected')
+ expect(info_request.info_request_events.last.event_type).to eq("status_update")
+ expect(info_request.info_request_events.last.calculated_state).to eq('rejected')
+ end
- @dog_request.reload
- expect(@dog_request.described_state).not_to eq('requires_admin')
+ it 'should log a status update event' do
+ expected_params = {:user_id => info_request.user_id,
+ :old_described_state => 'waiting_response',
+ :described_state => 'rejected'}
+ post_status('rejected', info_request)
+ last_event = info_request.reload.info_request_events.last
+ expect(last_event.params).to eq expected_params
+ end
- expect(ActionMailer::Base.deliveries).to be_empty
- end
+ it 'should not send an email to the requester letting them know someone
+ has updated the status of their request' do
+ expect(RequestMailer).not_to receive(:old_unclassified_updated)
+ post_status('rejected', info_request)
+ end
- context "message is included when classifying as requires_admin" do
- it "should send an email including the message" do
+ it "should go to the page asking for more information when classified
+ as requires_admin" do
post :describe_state,
- :incoming_message => {
- :described_state => "requires_admin",
- :message => "Something weird happened" },
- :id => @dog_request.id,
- :last_info_request_event_id => @dog_request.last_event_id_needing_description
+ :incoming_message => { :described_state => "requires_admin" },
+ :id => info_request.id,
+ :incoming_message_id => info_request.incoming_messages.last,
+ :last_info_request_event_id => info_request.last_event_id_needing_description
+ expect(response)
+ .to redirect_to describe_state_message_url(:url_title => info_request.url_title,
+ :described_state => "requires_admin")
- deliveries = ActionMailer::Base.deliveries
- expect(deliveries.size).to eq(1)
- mail = deliveries[0]
- expect(mail.body).to match(/as needing admin/)
- expect(mail.body).to match(/Something weird happened/)
+ info_request.reload
+ expect(info_request.described_state).not_to eq('requires_admin')
+ expect(ActionMailer::Base.deliveries).to be_empty
end
- end
+ context "message is included when classifying as requires_admin" do
+ it "should send an email including the message" do
+ post :describe_state,
+ :incoming_message => {
+ :described_state => "requires_admin",
+ :message => "Something weird happened" },
+ :id => info_request.id,
+ :last_info_request_event_id => info_request.last_event_id_needing_description
+
+ deliveries = ActionMailer::Base.deliveries
+ expect(deliveries.size).to eq(1)
+ mail = deliveries[0]
+ expect(mail.body).to match(/as needing admin/)
+ expect(mail.body).to match(/Something weird happened/)
+ end
+ end
- it 'should say it is showing advice as to what to do next' do
- post_status('rejected')
- expect(flash[:notice]).to match(/Here is what to do now/)
- end
+ it 'should show advice for the new state' do
+ expect(controller)
+ .to receive(:render_to_string)
+ .with(:partial => 'request/describe_notices/rejected',
+ :locals => {:info_request => info_request})
+ .and_return('')
+ post_status('rejected', info_request)
+ end
- it 'should redirect to the unhappy page' do
- post_status('rejected')
- expect(response).to redirect_to(:controller => 'help', :action => 'unhappy', :url_title => @dog_request.url_title)
- end
+ it 'should redirect to the unhappy page' do
+ post_status('rejected', info_request)
+ expect(response).to redirect_to(help_unhappy_path(info_request.url_title))
+ end
- it "knows about extended states" do
- InfoRequest.send(:require, File.expand_path(File.join(File.dirname(__FILE__), '..', 'models', 'customstates')))
- InfoRequest.send(:include, InfoRequestCustomStates)
- InfoRequest.class_eval('@@custom_states_loaded = true')
- RequestController.send(:require, File.expand_path(File.join(File.dirname(__FILE__), '..', 'models', 'customstates')))
- RequestController.send(:include, RequestControllerCustomStates)
- RequestController.class_eval('@@custom_states_loaded = true')
- allow(Time).to receive(:now).and_return(Time.utc(2007, 11, 10, 00, 01))
- post_status('deadline_extended')
- expect(flash[:notice]).to eq('Authority has requested extension of the deadline.')
+ it "knows about extended states" do
+ InfoRequest.send(:require, File.expand_path(File.join(File.dirname(__FILE__), '..', 'models', 'customstates')))
+ InfoRequest.send(:include, InfoRequestCustomStates)
+ InfoRequest.class_eval('@@custom_states_loaded = true')
+ RequestController.send(:require, File.expand_path(File.join(File.dirname(__FILE__), '..', 'models', 'customstates')))
+ RequestController.send(:include, RequestControllerCustomStates)
+ RequestController.class_eval('@@custom_states_loaded = true')
+ allow(Time).to receive(:now).and_return(Time.utc(2007, 11, 10, 00, 01))
+ post_status('deadline_extended', info_request)
+ expect(flash[:notice]).to eq('Authority has requested extension of the deadline.')
+ end
end
- end
- describe 'after a successful status update by the request owner' do
+ describe 'after a successful status update by the request owner' do
- render_views
+ render_views
- before do
- @request_owner = users(:bob_smith_user)
- session[:user_id] = @request_owner.id
- @dog_request = info_requests(:fancy_dog_request)
- allow(@dog_request).to receive(:each).and_return([@dog_request])
- allow(InfoRequest).to receive(:find).and_return(@dog_request)
- end
+ let(:info_request){ FactoryGirl.create(:info_request) }
- def request_url
- "request/#{@dog_request.url_title}"
- end
+ before do
+ session[:user_id] = info_request.user_id
+ end
- def unhappy_url
- "help/unhappy/#{@dog_request.url_title}"
- end
+ def expect_redirect(status, redirect_path)
+ post_status(status, info_request)
+ expect(response).
+ to redirect_to("http://" + "test.host/#{redirect_path}".squeeze("/"))
+ end
- def expect_redirect(status, redirect_path)
- post_status(status)
- expect(response).
- to redirect_to("http://" + "test.host/#{redirect_path}".squeeze("/"))
- end
+ context 'when status is updated to "waiting_response"' do
- context 'when status is updated to "waiting_response"' do
+ it 'should redirect to the "request url" with a message in the right tense when
+ the response is not overdue' do
+ expect_redirect("waiting_response",
+ show_request_path(info_request.url_title))
+ expect(flash[:notice]).to 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
- allow(@dog_request).to receive(:date_response_required_by).and_return(Time.now.to_date+1)
- allow(@dog_request).to receive(: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
+ the response is overdue' do
+ # Create the request with today's date
+ info_request
+ time_travel_to(1.month.from_now) do
+ expect_redirect('waiting_response',
+ show_request_path(info_request.url_title))
+ expect(flash[:notice]).to match(/should have got a response/)
+ end
+ end
- expect_redirect("waiting_response", "request/#{@dog_request.url_title}")
- expect(flash[:notice]).to match(/should get a response/)
+ it 'should redirect to the "request url" with a message in the right tense when
+ response is very overdue' do
+ # Create the request with today's date
+ info_request
+ time_travel_to(2.month.from_now) do
+ expect_redirect('waiting_response',
+ help_unhappy_path(info_request.url_title))
+ expect(flash[:notice]).to match(/is long overdue/)
+ expect(flash[:notice]).to match(/by more than 40 working days/)
+ expect(flash[:notice]).to match(/within 20 working days/)
+ end
+ end
end
- it 'should redirect to the "request url" with a message in the right tense when
- the response is overdue' do
- allow(@dog_request).to receive(:date_response_required_by).and_return(Time.now.to_date-1)
- allow(@dog_request).to receive(:date_very_overdue_after).and_return(Time.now.to_date+40)
- expect_redirect('waiting_response', request_url)
- expect(flash[:notice]).to match(/should have got a response/)
- end
+ context 'when status is updated to "not held"' do
+
+ it 'should redirect to the "request url"' do
+ expect_redirect('not_held',
+ show_request_path(info_request.url_title))
+ end
- it 'should redirect to the "request url" with a message in the right tense when
- the response is overdue' do
- allow(@dog_request).to receive(:date_response_required_by).and_return(Time.now.to_date-2)
- allow(@dog_request).to receive(:date_very_overdue_after).and_return(Time.now.to_date-1)
- expect_redirect('waiting_response', unhappy_url)
- expect(flash[:notice]).to match(/is long overdue/)
- expect(flash[:notice]).to match(/by more than 40 working days/)
- expect(flash[:notice]).to match(/within 20 working days/)
end
- end
- context 'when status is updated to "not held"' do
+ context 'when status is updated to "successful"' do
- it 'should redirect to the "request url"' do
- expect_redirect('not_held', request_url)
- end
+ it 'should redirect to the "request url"' do
+ expect_redirect('successful',
+ show_request_path(info_request.url_title))
+ end
- end
+ it 'should show a message including the donation url if there is one' do
+ allow(AlaveteliConfiguration).to receive(:donation_url).and_return('http://donations.example.com')
+ post_status('successful', info_request)
+ expect(flash[:notice]).to match('make a donation')
+ expect(flash[:notice]).to match('http://donations.example.com')
+ end
- context 'when status is updated to "successful"' do
+ it 'should show a message without reference to donations if there is no
+ donation url' do
+ allow(AlaveteliConfiguration).to receive(:donation_url).and_return('')
+ post_status('successful', info_request)
+ expect(flash[:notice]).not_to match('make a donation')
+ end
- 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
- allow(AlaveteliConfiguration).to receive(:donation_url).and_return('http://donations.example.com')
- post_status('successful')
- expect(flash[:notice]).to match('make a donation')
- expect(flash[:notice]).to match('http://donations.example.com')
- end
+ context 'when status is updated to "waiting clarification"' do
- it 'should show a message without reference to donations if there is no
- donation url' do
- allow(AlaveteliConfiguration).to receive(:donation_url).and_return('')
- post_status('successful')
- expect(flash[:notice]).not_to match('make a donation')
- end
+ context 'when there is a last response' do
- end
+ let(:info_request){ FactoryGirl.create(:info_request_with_incoming) }
- context 'when status is updated to "waiting clarification"' do
+ it 'should redirect to the "response url"' do
+ session[:user_id] = info_request.user_id
+ expected_url = new_request_incoming_followup_path(
+ :request_id => info_request.id,
+ :incoming_message_id =>
+ info_request.get_last_public_response.id)
+ expect_redirect('waiting_clarification', expected_url)
+ end
+ end
- it 'should redirect to the "response url" when there is a last response' do
- incoming_message = mock_model(IncomingMessage)
- allow(@dog_request).to receive(:get_last_public_response).and_return(incoming_message)
- expected_url = new_request_incoming_followup_path(
- :request_id => @dog_request.id,
- :incoming_message_id => @dog_request.get_last_public_response.id)
- expect_redirect('waiting_clarification', expected_url)
- end
+ context 'when there are no events needing description' do
+ it 'should redirect to the "followup no incoming url"' do
+ expected_url = new_request_followup_path(
+ :request_id => info_request.id,
+ :incoming_message_id => nil)
+ expect_redirect('waiting_clarification', expected_url)
+ end
+ end
- it 'should redirect to the "followup no incoming url" when there are no events
- needing description' do
- allow(@dog_request).to receive(:get_last_public_response).and_return(nil)
- expected_url = new_request_followup_path(
- :request_id => @dog_request.id,
- :incoming_message_id => nil)
- expect_redirect('waiting_clarification', expected_url)
end
- end
+ context 'when status is updated to "rejected"' do
- context 'when status is updated to "rejected"' do
+ it 'should redirect to the "unhappy url"' do
+ expect_redirect('rejected', help_unhappy_path(info_request.url_title))
+ end
- it 'should redirect to the "unhappy url"' do
- expect_redirect('rejected', "help/unhappy/#{@dog_request.url_title}")
end
- end
+ context 'when status is updated to "partially successful"' do
- context 'when status is updated to "partially successful"' do
+ it 'should redirect to the "unhappy url"' do
+ expect_redirect('partially_successful',
+ help_unhappy_path(info_request.url_title))
+ end
- 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
+ allow(AlaveteliConfiguration).to receive(:donation_url).and_return('http://donations.example.com')
+ post_status('successful', info_request)
+ expect(flash[:notice]).to match('make a donation')
+ expect(flash[:notice]).to match('http://donations.example.com')
+ end
- it 'should show a message including the donation url if there is one' do
- allow(AlaveteliConfiguration).to receive(:donation_url).and_return('http://donations.example.com')
- post_status('successful')
- expect(flash[:notice]).to match('make a donation')
- expect(flash[:notice]).to match('http://donations.example.com')
- end
+ it 'should show a message without reference to donations if there is no
+ donation url' do
+ allow(AlaveteliConfiguration).to receive(:donation_url).and_return('')
+ post_status('successful', info_request)
+ expect(flash[:notice]).not_to match('make a donation')
+ end
- it 'should show a message without reference to donations if there is no
- donation url' do
- allow(AlaveteliConfiguration).to receive(:donation_url).and_return('')
- post_status('successful')
- expect(flash[:notice]).not_to match('make a donation')
end
- end
+ context 'when status is updated to "gone postal"' do
+
+ let(:info_request){ FactoryGirl.create(:info_request_with_incoming) }
- context 'when status is updated to "gone postal"' do
+ it 'should redirect to the "respond to last" url' do
+ session[:user_id] = info_request.user_id
+ expected_url = new_request_incoming_followup_path(
+ :request_id => info_request.id,
+ :incoming_message_id =>
+ info_request.get_last_public_response.id,
+ :gone_postal => 1)
+ expect_redirect('gone_postal', expected_url)
+ end
- it 'should redirect to the "respond to last url"' do
- expected_url = new_request_incoming_followup_path(
- :request_id => @dog_request.id,
- :incoming_message_id => @dog_request.get_last_public_response.id)
- expect_redirect('gone_postal', "#{expected_url}?gone_postal=1")
end
- end
+ context 'when status updated to "internal review"' do
- context 'when status updated to "internal review"' do
+ it 'should redirect to the "request url"' do
+ expect_redirect('internal_review',
+ show_request_path(info_request.url_title))
+ end
- it 'should redirect to the "request url"' do
- expect_redirect('internal_review', request_url)
end
- end
-
- context 'when status is updated to "requires admin"' do
+ 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 => info_request.id,
+ :last_info_request_event_id =>
+ info_request.last_event_id_needing_description
+ expect(response)
+ .to redirect_to show_request_url(:url_title => info_request.url_title)
+ end
- 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
- expect(response).to redirect_to show_request_url(:url_title => @dog_request.url_title)
end
- end
-
- context 'when status is updated to "error message"' do
+ 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 => info_request.id,
+ :last_info_request_event_id =>
+ info_request.last_event_id_needing_description
+ expect(response)
+ .to redirect_to(
+ show_request_url(:url_title => info_request.url_title)
+ )
+ end
- 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
- expect(response).to redirect_to show_request_url(:url_title => @dog_request.url_title)
end
- end
+ context 'when status is updated to "user_withdrawn"' do
- context 'when status is updated to "user_withdrawn"' do
+ let(:info_request){ FactoryGirl.create(:info_request_with_incoming) }
+
+ it 'should redirect to the "respond to last" url' do
+ session[:user_id] = info_request.user_id
+ expected_url = new_request_incoming_followup_path(
+ :request_id => info_request.id,
+ :incoming_message_id =>
+ info_request.get_last_public_response.id)
+ expect_redirect('user_withdrawn', expected_url)
+ end
- it 'should redirect to the "respond to last url url" ' do
- expected_url = new_request_incoming_followup_path(
- :request_id => @dog_request.id,
- :incoming_message_id => @dog_request.get_last_public_response.id)
- expect_redirect('user_withdrawn', expected_url)
end
end
-
end
-
end
-
end
describe RequestController, "when viewing comments" do
From 788f017f12f4f1459602a30f46f827305349351d Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 18 Nov 2016 17:24:33 +0000
Subject: [PATCH 041/311] Handle case of embargoed request when describing
state.
Any post to the `describe_state` action for an embargoed request
should return a 404 not found response as we are now looking
for the request within the scope of those that are not embargoed.
Add select statement to scope so that records returned by the
scope aren't read only.
http://stackoverflow.com/questions/7667052/proper-way-to-prevent-activerecordreadonlyrecord
---
app/controllers/request_controller.rb | 2 +-
.../info_request/prominence/not_embargoed_query.rb | 8 +++++---
spec/controllers/request_controller_spec.rb | 10 ++++++++++
3 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 2c09896b02..f64ee05084 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -443,7 +443,7 @@ def new
# Submitted to the describing state of messages form
def describe_state
- info_request = InfoRequest.find(params[:id].to_i)
+ info_request = InfoRequest.not_embargoed.find(params[:id].to_i)
set_last_request(info_request)
# If this is an external request, go to the request page - we don't allow
diff --git a/app/models/info_request/prominence/not_embargoed_query.rb b/app/models/info_request/prominence/not_embargoed_query.rb
index 9623ddd234..38844e9b10 100644
--- a/app/models/info_request/prominence/not_embargoed_query.rb
+++ b/app/models/info_request/prominence/not_embargoed_query.rb
@@ -10,9 +10,11 @@ def initialize(relation = InfoRequest)
def call
# Specify an outer join as the default inner join
# will not retrieve NULL records which is what we want here
- @relation.joins('LEFT OUTER JOIN embargoes
- ON embargoes.info_request_id = info_requests.id')
- .where('embargoes.id IS NULL')
+ @relation
+ .select("info_requests.*")
+ .joins('LEFT OUTER JOIN embargoes
+ ON embargoes.info_request_id = info_requests.id')
+ .where('embargoes.id IS NULL')
end
end
end
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 8e4c57559c..a80be091d1 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -1549,6 +1549,16 @@ def post_status(status, info_request)
:last_info_request_event_id => info_request.last_event_id_needing_description
end
+ context 'when the request is embargoed' do
+
+ let(:info_request){ FactoryGirl.create(:embargoed_request) }
+
+ it 'should raise ActiveRecord::NotFound' do
+ expect{ post_status('rejected', info_request) }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
+ end
+
it "should require login" do
post_status('rejected', info_request)
expect(response).to redirect_to(:controller => 'user',
From 8ed25cea72cfb1857f2019cd59359fdbdc08c0fb Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 18 Nov 2016 17:24:55 +0000
Subject: [PATCH 042/311] Line length fixes.
---
app/controllers/request_controller.rb | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index f64ee05084..071c1ff3f4 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -474,7 +474,8 @@ def describe_state
end
if params[:last_info_request_event_id].to_i != info_request.last_event_id_needing_description
- flash[:error] = _("The request has been updated since you originally loaded this page. Please check for any new incoming messages below, and try again.")
+ flash[:error] = _("The request has been updated since you originally loaded this page. " \
+ "Please check for any new incoming messages below, and try again.")
redirect_to request_url(info_request)
return
end
@@ -485,7 +486,8 @@ def describe_state
# the administrators.
# If this message hasn't been included then ask for it
if ["error_message", "requires_admin"].include?(described_state) && message.nil?
- redirect_to describe_state_message_url(:url_title => info_request.url_title, :described_state => described_state)
+ redirect_to describe_state_message_url(:url_title => info_request.url_title,
+ :described_state => described_state)
return
end
From d75578b5e4d5806d0648ef3bd9e16632e4664f64 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 18 Nov 2016 17:44:49 +0000
Subject: [PATCH 043/311] Specs for describe_state_message action.
---
spec/controllers/request_controller_spec.rb | 68 +++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index a80be091d1..1521c4579d 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -1665,6 +1665,24 @@ def post_status(status, info_request)
expect(status_update_mail.to)
.to match([info_request.user.email])
end
+
+ context "if the params don't include a message" do
+
+ it 'redirects to the message url' do
+ post :describe_state, :incoming_message =>
+ { :described_state => "requires_admin" },
+ :id => info_request.id,
+ :incoming_message_id =>
+ info_request.incoming_messages.last,
+ :last_info_request_event_id =>
+ info_request.last_event_id_needing_description
+ expected_url = describe_state_message_url(
+ :url_title => info_request.url_title,
+ :described_state => 'requires_admin')
+ expect(response).to redirect_to(expected_url)
+ end
+
+ end
end
end
end
@@ -2078,6 +2096,24 @@ def expect_redirect(status, redirect_path)
)
end
+ context "if the params don't include a message" do
+
+ it 'redirects to the message url' do
+ post :describe_state, :incoming_message =>
+ { :described_state => "error_message" },
+ :id => info_request.id,
+ :incoming_message_id =>
+ info_request.incoming_messages.last,
+ :last_info_request_event_id =>
+ info_request.last_event_id_needing_description
+ expected_url = describe_state_message_url(
+ :url_title => info_request.url_title,
+ :described_state => 'error_message')
+ expect(response).to redirect_to(expected_url)
+ end
+
+ end
+
end
context 'when status is updated to "user_withdrawn"' do
@@ -2765,3 +2801,35 @@ def make_request
end
end
+
+describe RequestController do
+ describe 'GET describe_state_message' do
+ let(:info_request){ FactoryGirl.create(:info_request_with_incoming) }
+
+ it 'assigns the info_request to the view' do
+ get :describe_state_message, :url_title => info_request.url_title,
+ :described_state => 'error_message'
+ expect(assigns[:info_request]).to eq info_request
+ end
+
+ it 'assigns the described state to the view' do
+ get :describe_state_message, :url_title => info_request.url_title,
+ :described_state => 'error_message'
+ expect(assigns[:described_state]).to eq 'error_message'
+ end
+
+ it 'assigns the last info request event id to the view' do
+ get :describe_state_message, :url_title => info_request.url_title,
+ :described_state => 'error_message'
+ expect(assigns[:last_info_request_event_id])
+ .to eq info_request.last_event_id_needing_description
+ end
+
+ it 'assigns the title to the view' do
+ get :describe_state_message, :url_title => info_request.url_title,
+ :described_state => 'error_message'
+ expect(assigns[:title]).to eq "I've received an error message"
+ end
+
+ end
+end
From 84de25104717b23944056ba80cd974953d0f7c88 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 18 Nov 2016 17:45:38 +0000
Subject: [PATCH 044/311] Handle embargo case in the `describe_state_message`
action.
This action is called when someone has tried to report a
request to admins without giving an accompanying message.
By scoping with the not_embargoed scope when finding
the request, this action should now 404 for any embargoed
request in production.
---
app/controllers/request_controller.rb | 2 +-
spec/controllers/request_controller_spec.rb | 12 ++++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 071c1ff3f4..2b4dee48a7 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -550,7 +550,7 @@ def describe_state
# Collect a message to include with the change of state
def describe_state_message
- @info_request = InfoRequest.find_by_url_title!(params[:url_title])
+ @info_request = InfoRequest.not_embargoed.find_by_url_title!(params[:url_title])
@described_state = params[:described_state]
@last_info_request_event_id = @info_request.last_event_id_needing_description
@title = case @described_state
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 1521c4579d..af85ef95a5 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2831,5 +2831,17 @@ def make_request
expect(assigns[:title]).to eq "I've received an error message"
end
+ context 'when the request is embargoed' do
+ let(:info_request){ FactoryGirl.create(:embargoed_request) }
+
+ it 'raises ActiveRecord::RecordNotFound' do
+ expect{ get :describe_state_message,
+ :url_title => info_request.url_title,
+ :described_state => 'error_message' }
+ .to raise_error(ActiveRecord::RecordNotFound)
+
+ end
+
+ end
end
end
From 4bb8433320834b2bd97890405bd00483ef74957e Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 21 Nov 2016 10:09:32 +0000
Subject: [PATCH 045/311] Amend scope for Comment.visible to exclude
InfoRequest.is_public select
The select on InfoRequest.is_public was added in 9e022ada so that
the InfoRequest records returned would not be read-only (which they
otherwise would be due to the use of :join rather than :include to
get the outer join for non-embargoed requests). If carried over here,
it results in the InfoRequest, not the Comment, being returned.
---
app/models/comment.rb | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 29682a6178..41422236d8 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -33,7 +33,9 @@ class Comment < ActiveRecord::Base
:check_body_uses_mixed_capitals
scope :visible, -> {
- joins(:info_request).merge(InfoRequest.is_searchable).where(:visible => true)
+ joins(:info_request)
+ .merge(InfoRequest.is_searchable.except(:select))
+ .where(:visible => true)
}
after_save :event_xapian_update
From e4884dd7df3174e5ff8bab760ce9a4680e8c8477 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 21 Nov 2016 10:50:24 +0000
Subject: [PATCH 046/311] Handle embargoed requests in followup actions.
Any request that requires an info_request via the `set_info_request`
filter should now raise an ActiveRecord::RecordNotFound error for
an embargoed request, resulting in a 404 not found response in
production.
---
app/controllers/followups_controller.rb | 2 +-
spec/controllers/followups_controller_spec.rb | 20 +++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/app/controllers/followups_controller.rb b/app/controllers/followups_controller.rb
index 8bcfc3ab0d..cc8e50a98d 100644
--- a/app/controllers/followups_controller.rb
+++ b/app/controllers/followups_controller.rb
@@ -165,7 +165,7 @@ def set_incoming_message
end
def set_info_request
- @info_request = InfoRequest.find(params[:request_id].to_i)
+ @info_request = InfoRequest.not_embargoed.find(params[:request_id].to_i)
end
def set_last_request_data
diff --git a/spec/controllers/followups_controller_spec.rb b/spec/controllers/followups_controller_spec.rb
index bd85e06d03..8cfeacd013 100644
--- a/spec/controllers/followups_controller_spec.rb
+++ b/spec/controllers/followups_controller_spec.rb
@@ -10,6 +10,12 @@
describe "GET new" do
+ it 'raises an ActiveRecord::RecordNotFound error for an embargoed request' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect{ get :new, :request_id => embargoed_request.id }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+
it "displays 'wrong user' message when not logged in as the request owner" do
session[:user_id] = FactoryGirl.create(:user)
get :new, :request_id => request.id,
@@ -127,6 +133,13 @@
:what_doing => 'normal_sort' }
end
+ it 'raises an ActiveRecord::RecordNotFound error for an embargoed request' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect{ post :preview, :outgoing_message => dummy_message,
+ :request_id => embargoed_request.id }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+
it "redirects to the signin page if not logged in" do
post :preview, :outgoing_message => dummy_message,
:request_id => request.id,
@@ -191,6 +204,13 @@
session[:user_id] = request_user.id
end
+ it 'raises an ActiveRecord::RecordNotFound error for an embargoed request' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect{ post :create, :outgoing_message => dummy_message,
+ :request_id => embargoed_request.id }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+
it "redirects to the signin page if not logged in" do
session[:user_id] = nil
post :create, :outgoing_message => dummy_message,
From 3e569aef269634556a57e282f82d262994df43bc Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 21 Nov 2016 11:31:43 +0000
Subject: [PATCH 047/311] Handle embargo in the download action.
Any request to download an embargoed request should result in an
ActiveRecord::RecordNotFound error, returning a 404 not found response
in production.
---
app/controllers/request_controller.rb | 2 +-
spec/controllers/request_controller_spec.rb | 14 ++++++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 2b4dee48a7..efc76b77c3 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -806,7 +806,7 @@ def search_typeahead
def download_entire_request
@locale = I18n.locale.to_s
I18n.with_locale(@locale) do
- @info_request = InfoRequest.find_by_url_title!(params[:url_title])
+ @info_request = InfoRequest.not_embargoed.find_by_url_title!(params[:url_title])
if authenticated?(
:web => _("To download the zip file"),
:email => _("Then you can download a zip file of {{info_request_title}}.",
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index af85ef95a5..4d96ee5609 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2845,3 +2845,17 @@ def make_request
end
end
end
+describe RequestController do
+
+ describe 'GET download_entire_request' do
+ context 'when the request is embargoed' do
+ let(:info_request){ FactoryGirl.create(:embargoed_request) }
+
+ it 'raises ActiveRecord::RecordNotFound' do
+ expect{ get :download_entire_request,
+ :url_title => info_request.url_title }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
\ No newline at end of file
From e390afbb07eba2228e3eb4bab41f7afaad1b4fe4 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 21 Nov 2016 11:45:22 +0000
Subject: [PATCH 048/311] Remove unneeded 'render_views'
---
spec/controllers/request_controller_spec.rb | 1 -
1 file changed, 1 deletion(-)
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 4d96ee5609..86c2924b46 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2164,7 +2164,6 @@ def expect_redirect(status, redirect_path)
describe RequestController, "authority uploads a response from the web interface" do
- render_views
before(:each) do
# domain after the @ is used for authentication of FOI officers, so to test it
From 26d380568940a36eb9f1469f304a3e4d657b1913 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 21 Nov 2016 11:45:44 +0000
Subject: [PATCH 049/311] Handle embargo in response uploading.
Scopes the finder for the upload_response action to non
embargoed requests, so should result in ActiveRecord::RecordNotFound
being raised for an embargoed request, and a 404 not found response
being returned in production.
---
app/controllers/request_controller.rb | 2 +-
spec/controllers/request_controller_spec.rb | 10 ++++++++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index efc76b77c3..34acb33ac1 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -733,7 +733,7 @@ def get_attachment_internal(html_conversion)
def upload_response
@locale = I18n.locale.to_s
I18n.with_locale(@locale) do
- @info_request = InfoRequest.find_by_url_title!(params[:url_title])
+ @info_request = InfoRequest.not_embargoed.find_by_url_title!(params[:url_title])
@reason_params = {
:web => _("To upload a response, you must be logged in using an " \
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 86c2924b46..5b808b2e4f 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2177,6 +2177,16 @@ def expect_redirect(status, redirect_path)
@foi_officer_user.save!
end
+ context 'when the request is embargoed' do
+ let(:embargoed_request){ FactoryGirl.create(:embargoed_request)}
+
+ it 'raises an ActiveRecord::RecordNotFound error' do
+ expect{get :upload_response, :url_title => embargoed_request.url_title }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ end
+
it "should require login to view the form to upload" do
@ir = info_requests(:fancy_dog_request)
expect(@ir.public_body.is_foi_officer?(@normal_user)).to eq(false)
From fa93786355d450287516c2f1bf2b9630ec69443f Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 21 Nov 2016 13:35:38 +0000
Subject: [PATCH 050/311] Add specs for show_request_event action.
---
...dmin_info_request_event_controller_spec.rb | 2 +-
spec/controllers/request_controller_spec.rb | 57 ++++++++++++++++++-
spec/factories/info_request_events.rb | 21 ++++++-
spec/models/info_request_event_spec.rb | 6 +-
spec/spec_helper.rb | 1 +
.../event_digest.text.erb_spec.rb | 4 +-
6 files changed, 82 insertions(+), 9 deletions(-)
diff --git a/spec/controllers/admin_info_request_event_controller_spec.rb b/spec/controllers/admin_info_request_event_controller_spec.rb
index d7a5240aa5..a9e71b6ecd 100644
--- a/spec/controllers/admin_info_request_event_controller_spec.rb
+++ b/spec/controllers/admin_info_request_event_controller_spec.rb
@@ -8,7 +8,7 @@
describe 'when handling valid data' do
before do
- @info_request_event = FactoryGirl.create(:info_request_event)
+ @info_request_event = FactoryGirl.create(:response_event)
put :update, :id => @info_request_event
end
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 5b808b2e4f..9fa84a24c1 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2854,6 +2854,7 @@ def make_request
end
end
end
+
describe RequestController do
describe 'GET download_entire_request' do
@@ -2867,4 +2868,58 @@ def make_request
end
end
end
-end
\ No newline at end of file
+end
+
+describe RequestController do
+
+ describe 'GET show_request_event' do
+
+ context 'when the event is an incoming message' do
+ let(:event){ FactoryGirl.create(:response_event) }
+
+ it 'returns a 301 status' do
+ get :show_request_event, :info_request_event_id => event.id
+ expect(response.status).to eq(301)
+ end
+
+ it 'redirects to the incoming message path' do
+ get :show_request_event, :info_request_event_id => event.id
+ expect(response)
+ .to redirect_to(incoming_message_path(event.incoming_message))
+ end
+
+ end
+
+ context 'when the event is an outgoing message' do
+ let(:event){ FactoryGirl.create(:sent_event) }
+
+ it 'returns a 301 status' do
+ get :show_request_event, :info_request_event_id => event.id
+ expect(response.status).to eq(301)
+ end
+
+ it 'redirects to the outgoing message path' do
+ get :show_request_event, :info_request_event_id => event.id
+ expect(response)
+ .to redirect_to(outgoing_message_path(event.outgoing_message))
+ end
+ end
+
+ context 'for any other kind of event' do
+ let(:event){ FactoryGirl.create(:info_request_event) }
+
+ it 'returns a 301 status' do
+ get :show_request_event, :info_request_event_id => event.id
+ expect(response.status).to eq(301)
+ end
+
+ it 'redirects to the request path' do
+ get :show_request_event, :info_request_event_id => event.id
+ expect(response)
+ .to redirect_to(show_request_path(event.info_request.url_title))
+ end
+ end
+
+ end
+
+end
diff --git a/spec/factories/info_request_events.rb b/spec/factories/info_request_events.rb
index 5d01e26ef4..78e5e4ccc5 100644
--- a/spec/factories/info_request_events.rb
+++ b/spec/factories/info_request_events.rb
@@ -20,14 +20,33 @@
factory :info_request_event do
info_request
- event_type 'response'
+ event_type 'edit'
params_yaml ''
+
factory :sent_event do
event_type 'sent'
+ association :outgoing_message, :factory => :initial_request
+ end
+
+ factory :response_event do
+ event_type 'response'
+ incoming_message
+ end
+
+ factory :followup_sent_event do
+ event_type 'followup_sent'
+ association :outgoing_message, :factory => :new_information_followup
end
+
+ factory :comment_event do
+ event_type 'comment'
+ association :comment
+ end
+
factory :edit_event do
event_type 'edit'
end
+
factory :hide_event do
event_type 'hide'
end
diff --git a/spec/models/info_request_event_spec.rb b/spec/models/info_request_event_spec.rb
index 258760a7d5..def07a58fd 100644
--- a/spec/models/info_request_event_spec.rb
+++ b/spec/models/info_request_event_spec.rb
@@ -238,18 +238,16 @@
describe '#filetype' do
context 'a response event' do
- let(:ire) { ire = FactoryGirl.create(:info_request_event) }
+ let(:ire) { ire = FactoryGirl.create(:response_event) }
it 'should raise an error if there is not incoming_message' do
+ ire.incoming_message = nil
expect { ire.filetype }.to raise_error.
with_message(/event type is 'response' but no incoming message for event/)
end
it 'should return a blank string if there are no attachments' do
info_request = ire.info_request
- incoming = FactoryGirl.create(:plain_incoming_message,
- :info_request => info_request)
- ire.incoming_message = incoming
expect(ire.filetype).to eq('')
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 9f904e3173..08b2cdbbe4 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -48,6 +48,7 @@
config.include Capybara::DSL, :type => :request
config.include Delorean
+ config.include LinkToHelper
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
diff --git a/spec/views/track_mailer/event_digest.text.erb_spec.rb b/spec/views/track_mailer/event_digest.text.erb_spec.rb
index 597b103535..a54c0d52c5 100644
--- a/spec/views/track_mailer/event_digest.text.erb_spec.rb
+++ b/spec/views/track_mailer/event_digest.text.erb_spec.rb
@@ -22,7 +22,7 @@
describe "tracking a response" do
let(:event) do
- FactoryGirl.create(:info_request_event,
+ FactoryGirl.create(:response_event,
:incoming_message => request.incoming_messages.last,
:info_request => request)
end
@@ -51,7 +51,7 @@
describe "tracking a followup" do
let(:event) do
- FactoryGirl.create(:info_request_event,
+ FactoryGirl.create(:response_event,
:outgoing_message => request.outgoing_messages.last,
:info_request => request,
:event_type => 'followup_sent')
From aa177954452be12efcd104d53e8bb3ccf2ad8409 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 21 Nov 2016 13:46:49 +0000
Subject: [PATCH 051/311] Handle embargo case in the show_request_event action.
Check that the info request to which an event refers is not embargoed.
If it is, raise ActiveRecord::RecordNotFound which should result in a
404 not found response in production.
---
app/controllers/request_controller.rb | 3 +++
spec/controllers/request_controller_spec.rb | 19 +++++++++++++++++--
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 34acb33ac1..e939eefb9b 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -567,6 +567,9 @@ def describe_state_message
# proper URL for the message the event refers to
def show_request_event
@info_request_event = InfoRequestEvent.find(params[:info_request_event_id])
+ if @info_request_event.info_request.embargo
+ raise ActiveRecord::RecordNotFound
+ end
if @info_request_event.is_incoming_message?
redirect_to incoming_message_url(@info_request_event.incoming_message), :status => :moved_permanently
elsif @info_request_event.is_outgoing_message?
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 9fa84a24c1..d7f452838a 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2888,6 +2888,11 @@ def make_request
.to redirect_to(incoming_message_path(event.incoming_message))
end
+ it 'raises ActiveRecord::RecordNotFound when the request is embargoed' do
+ event.info_request.create_embargo(:publish_at => Time.now + 1.day)
+ expect{ get :show_request_event, :info_request_event_id => event.id }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
end
context 'when the event is an outgoing message' do
@@ -2903,6 +2908,12 @@ def make_request
expect(response)
.to redirect_to(outgoing_message_path(event.outgoing_message))
end
+
+ it 'raises ActiveRecord::RecordNotFound when the request is embargoed' do
+ event.info_request.create_embargo(:publish_at => Time.now + 1.day)
+ expect{ get :show_request_event, :info_request_event_id => event.id }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
end
context 'for any other kind of event' do
@@ -2918,8 +2929,12 @@ def make_request
expect(response)
.to redirect_to(show_request_path(event.info_request.url_title))
end
- end
+ it 'raises ActiveRecord::RecordNotFound when the request is embargoed' do
+ event.info_request.create_embargo(:publish_at => Time.now + 1.day)
+ expect{ get :show_request_event, :info_request_event_id => event.id }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
+ end
end
-
end
From a7de5a7a356366c95e32b66a3e6b5f7f674a1bc6 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Mon, 21 Nov 2016 18:28:04 +0000
Subject: [PATCH 052/311] Cleanup specs for getting attachments.
Move those that are testing the process of loading incoming
messages into an integration spec. Rewrite the remainder using
factories and separate them by controller action.
---
spec/controllers/request_controller_spec.rb | 383 +++++++-------------
spec/factories/info_requests.rb | 7 +
spec/integration/incoming_mail_spec.rb | 147 ++++++++
3 files changed, 280 insertions(+), 257 deletions(-)
create mode 100644 spec/integration/incoming_mail_spec.rb
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index d7f452838a..73dbb52045 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -239,6 +239,24 @@
end
end
+ it "should censor attachment names" do
+ info_request = FactoryGirl.create(:info_request_with_html_attachment)
+ get :show, :url_title => info_request.url_title
+ expect(response.body).to have_css('.attachment .attachment__name') do |s|
+ expect(s).to contain /interesting.pdf/m
+ end
+ # Note that the censor rule applies to the original filename,
+ # not the display_filename:
+ info_request.censor_rules.create!(:text => 'interesting.pdf',
+ :replacement => "Mouse.pdf",
+ :last_edit_editor => 'unknown',
+ :last_edit_comment => 'none')
+ get :show, :url_title => info_request.url_title
+ expect(response.body).to have_css('.attachment .attachment__name') do |s|
+ expect(s).to contain /Mouse.pdf/m
+ end
+ end
+
context 'when the request is embargoed' do
it 'raises ActiveRecord::RecordNotFound' do
embargoed_request = FactoryGirl.create(:embargoed_request)
@@ -449,77 +467,25 @@ def make_request
expect(response).to render_template "request/show"
end
end
+end
- describe 'when handling incoming mail' do
-
- render_views
-
- it "should receive incoming messages, send email to creator, and show them" do
- ir = info_requests(:fancy_dog_request)
- ir.incoming_messages.each { |x| x.parse_raw_email! }
-
- get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
- size_before = assigns[:info_request_events].size
-
- receive_incoming_mail('incoming-request-plain.email', ir.incoming_email)
- deliveries = ActionMailer::Base.deliveries
- expect(deliveries.size).to eq(1)
- mail = deliveries[0]
- expect(mail.body).to match(/You have a new response to the Freedom of Information request/)
-
- get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
- expect(assigns[:info_request_events].size - size_before).to eq(1)
- end
-
- it "should download attachments" do
- ir = info_requests(:fancy_dog_request)
- ir.incoming_messages.each { |x| x.parse_raw_email!(true) }
-
- get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
- expect(response.content_type).to eq("text/html")
- size_before = assigns[:info_request_events].size
-
- ir = info_requests(:fancy_dog_request)
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
-
- get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
- expect(assigns[:info_request_events].size - size_before).to eq(1)
- ir.reload
+describe RequestController do
+ describe 'GET get_attachment' do
- get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt', :skip_cache => 1
- expect(response.content_type).to eq("text/plain")
- expect(response.body).to have_content "Second hello"
+ let(:info_request){ FactoryGirl.create(:info_request_with_incoming_attachments) }
- get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 3, :file_name => 'hello world.txt', :skip_cache => 1
- expect(response.content_type).to eq("text/plain")
- expect(response.body).to have_content "First hello"
+ def get_attachment(params = {})
+ default_params = { :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'interesting.pdf' }
+ get :get_attachment, default_params.merge(params)
end
it 'should cache an attachment on a request with normal prominence' do
- ir = info_requests(:fancy_dog_request)
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
- ir.reload
expect(@controller).to receive(:foi_fragment_cache_write)
- get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id,
- :id => ir.id,
- :part => 2,
- :file_name => 'hello world.txt'
- end
-
- it "should convert message body to UTF8" do
- ir = info_requests(:fancy_dog_request)
- receive_incoming_mail('iso8859_2_raw_email.email', ir.incoming_email)
- get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
- expect(response.body).to have_content "tënde"
- end
-
- it "should generate valid HTML verson of plain text attachments" do
- ir = info_requests(:fancy_dog_request)
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
- ir.reload
- 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
- expect(response.content_type).to eq("text/html")
- expect(response.body).to have_content "Second hello"
+ get_attachment
end
# This is a regression test for a bug where URLs of this form were causing 500 errors
@@ -533,231 +499,134 @@ def make_request
#
# https://github.com/mysociety/alaveteli/issues/351
it "should return 404 for ugly URLs containing a request id that isn't an integer" do
- ir = info_requests(:fancy_dog_request)
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
- ir.reload
ugly_id = "55195"
- expect {
- 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
- }.to raise_error(ActiveRecord::RecordNotFound)
-
- expect {
- 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
- }.to raise_error(ActiveRecord::RecordNotFound)
- end
- it "should return 404 when incoming message and request ids don't match" do
- ir = info_requests(:fancy_dog_request)
- wrong_id = info_requests(:naughty_chicken_request).id
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
- ir.reload
- expect {
- 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
- }.to raise_error(ActiveRecord::RecordNotFound)
- end
- it "should return 404 for ugly URLs contain a request id that isn't an integer, even if the integer prefix refers to an actual request" do
- ir = info_requests(:fancy_dog_request)
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
- ir.reload
- ugly_id = "%d95" % [info_requests(:naughty_chicken_request).id]
-
- expect {
- 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
- }.to raise_error(ActiveRecord::RecordNotFound)
-
- expect {
- 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
- }.to raise_error(ActiveRecord::RecordNotFound)
- end
- it "should return 404 when incoming message and request ids don't match" do
- ir = info_requests(:fancy_dog_request)
- wrong_id = info_requests(:naughty_chicken_request).id
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
- ir.reload
- expect {
- 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
- }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { get_attachment(:id => ugly_id) }
+ .to raise_error(ActiveRecord::RecordNotFound)
end
- it "should generate valid HTML verson of PDF attachments" do
- ir = info_requests(:fancy_dog_request)
- receive_incoming_mail('incoming-request-pdf-attachment.email', ir.incoming_email)
- ir.reload
- get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'fs 50379341.pdf.html', :skip_cache => 1
- expect(response.content_type).to eq("text/html")
- expect(response.body).to have_content "Walberswick Parish Council"
+ it "should return 404 when incoming message and request ids
+ don't match" do
+ expect { get_attachment(:id => info_request.id + 1) }
+ .to raise_error(ActiveRecord::RecordNotFound)
end
- 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_and_filename(ir.incoming_messages[1].get_attachments_for_display, 2, 'hello world.txt')
- expect(attachment.body).to have_content "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 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
- expect(response.status).to eq(303)
-
- attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(ir.incoming_messages[1].get_attachments_for_display, 2, 'hello world.txt')
- expect(attachment.body).to have_content "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 world.txt.html', :skip_cache => 1
- expect(response.body).not_to have_content "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_and_filename(ir.incoming_messages[1].get_attachments_for_display, 2, 'hello world.txt')
- expect(attachment.body).to have_content "Third hello"
- 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
- expect(response.body).to have_content "Third hello"
+ 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
+ ugly_id = "#{FactoryGirl.create(:info_request).id}95"
+ expect { get_attachment(:id => ugly_id) }
+ .to raise_error(ActiveRecord::RecordNotFound)
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')
+ it "should redirect to the incoming message if there's a wrong part number
+ and an ambiguous filename" do
+ incoming_message = info_request.incoming_messages.first
+ attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(
+ incoming_message.get_attachments_for_display,
+ 5,
+ 'interesting.pdf'
+ )
expect(attachment).to 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
+ get_attachment(:part => 5)
expect(response.status).to eq(303)
new_location = response.header['Location']
- expect(new_location).to match(/request\/#{ir.url_title}#incoming-#{im.id}/)
+ expect(new_location)
+ .to match incoming_message_path(incoming_message)
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
- expect(response.content_type).to eq("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)
- ir.reload
-
- get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello.qwglhm', :skip_cache => 1
- expect(response.content_type).to eq("application/octet-stream")
- expect(response.body).to have_content "an unusual sort of file"
+ info_request = FactoryGirl.create(:info_request_with_html_attachment)
+ get :get_attachment, :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 5,
+ :file_name => 'interesting.html',
+ :skip_cache => 1
+ expect(response.body).to match('dull')
end
it "should not download attachments with wrong file name" do
- ir = info_requests(:fancy_dog_request)
- 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 => 'http://trying.to.hack'
+ info_request = FactoryGirl.create(:info_request_with_html_attachment)
+ get :get_attachment, :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'http://trying.to.hack',
+ :skip_cache => 1
expect(response.status).to eq(303)
end
it "should sanitise HTML attachments" do
- incoming_message = FactoryGirl.create(:incoming_message_with_html_attachment)
- get :get_attachment, :incoming_message_id => incoming_message.id,
- :id => incoming_message.info_request.id,
- :part => 2,
- :file_name => 'interesting.html',
- :skip_cache => 1
+ info_request = FactoryGirl.create(:info_request_with_html_attachment)
+ get :get_attachment, :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'interesting.html',
+ :skip_cache => 1
expect(response.body).not_to match("script")
expect(response.body).not_to match("interesting")
expect(response.body).to match('dull')
end
- it "should censor attachments downloaded directly" do
- ir = info_requests(:fancy_dog_request)
+ it "censors attachments downloaded directly" do
+ info_request = FactoryGirl.create(:info_request_with_html_attachment)
+ info_request.censor_rules.create!(:text => 'dull',
+ :replacement => "Mouse",
+ :last_edit_editor => 'unknown',
+ :last_edit_comment => 'none')
+ get :get_attachment, :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'interesting.html',
+ :skip_cache => 1
+ expect(response.content_type).to eq("text/html")
+ expect(response.body).to have_content "Mouse"
+ end
- censor_rule = CensorRule.new
- censor_rule.text = "Second"
- censor_rule.replacement = "Mouse"
- censor_rule.last_edit_editor = "unknown"
- censor_rule.last_edit_comment = "none"
- ir.censor_rules << censor_rule
+ it "should censor with rules on the user (rather than the request)" do
+ info_request = FactoryGirl.create(:info_request_with_html_attachment)
+ info_request.user.censor_rules.create!(:text => 'dull',
+ :replacement => "Mouse",
+ :last_edit_editor => 'unknown',
+ :last_edit_comment => 'none')
+ get :get_attachment, :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'interesting.html',
+ :skip_cache => 1
+ expect(response.content_type).to eq("text/html")
+ expect(response.body).to have_content "Mouse"
+ end
+ end
+end
- begin
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
+describe RequestController do
+ describe 'GET get_attachment_as_html' do
+ let(:info_request){ FactoryGirl.create(:info_request_with_incoming_attachments) }
- get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt', :skip_cache => 1
- expect(response.content_type).to eq("text/plain")
- expect(response.body).to have_content "Mouse hello"
- ensure
- ir.censor_rules.clear
- end
+ def get_html_attachment(params = {})
+ default_params = { :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'interesting.pdf.html' }
+ get :get_attachment_as_html, default_params.merge(params)
end
- it "should censor with rules on the user (rather than the request)" do
- ir = info_requests(:fancy_dog_request)
-
- censor_rule = CensorRule.new
- censor_rule.text = "Second"
- censor_rule.replacement = "Mouse"
- censor_rule.last_edit_editor = "unknown"
- censor_rule.last_edit_comment = "none"
- ir.user.censor_rules << censor_rule
-
- begin
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
- ir.reload
-
- get :get_attachment, :incoming_message_id => ir.incoming_messages[1].id, :id => ir.id, :part => 2, :file_name => 'hello world.txt', :skip_cache => 1
- expect(response.content_type).to eq("text/plain")
- expect(response.body).to have_content "Mouse hello"
- ensure
- ir.user.censor_rules.clear
- end
- end
-
- it "should censor attachment names" do
- ir = info_requests(:fancy_dog_request)
- receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email)
- attachment_name_selector = '.attachment .attachment__name'
-
- # TODO: this is horrid, but don't know a better way. If we
- # don't do this, the info_request_event to which the
- # info_request is attached still uses the unmodified
- # version from the fixture.
- #event = info_request_events(:useless_incoming_message_event)
- ir.reload
- assert ir.info_request_events[3].incoming_message.get_attachments_for_display.count == 2
- ir.save!
- ir.incoming_messages.last.save!
- get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
- assert assigns[:info_request].info_request_events[3].incoming_message.get_attachments_for_display.count == 2
- # the issue is that the info_request_events have got cached on them the old info_requests.
- # where i'm at: trying to replace those fields that got re-read from the raw email. however tests are failing in very strange ways. currently I don't appear to be getting any attachments parsed in at all when in the template (see "*****" in _correspondence.html.erb) but do when I'm in the code.
-
- # 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
- expect(response.body).to have_css(attachment_name_selector) do |s|
- expect(s).to contain /hello world.txt/m
- end
-
- censor_rule = CensorRule.new
- # 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"
- ir.censor_rules << censor_rule
- begin
- get :show, :url_title => 'why_do_you_have_such_a_fancy_dog'
- expect(response.body).to have_css(attachment_name_selector) do |s|
- expect(s).to contain /goodbye.txt/m
- end
- ensure
- ir.censor_rules.clear
- end
+ it "should return 404 for ugly URLs containing a request id that isn't an integer" do
+ ugly_id = "55195"
+ expect { get_html_attachment(:id => ugly_id) }
+ .to 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
+ ugly_id = "#{FactoryGirl.create(:info_request).id}95"
+ expect { get_html_attachment(:id => ugly_id) }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
end
end
diff --git a/spec/factories/info_requests.rb b/spec/factories/info_requests.rb
index 5555c67497..b32e4bbd96 100644
--- a/spec/factories/info_requests.rb
+++ b/spec/factories/info_requests.rb
@@ -52,6 +52,13 @@
end
end
+ factory :info_request_with_html_attachment do
+ after(:create) do |info_request, evaluator|
+ incoming_message = create(:incoming_message_with_html_attachment, :info_request => info_request)
+ info_request.log_event("response", {:incoming_message_id => incoming_message.id})
+ end
+ end
+
factory :info_request_with_incoming_attachments do
after(:create) do |info_request, evaluator|
incoming_message = create(:incoming_message_with_attachments, :info_request => info_request)
diff --git a/spec/integration/incoming_mail_spec.rb b/spec/integration/incoming_mail_spec.rb
new file mode 100644
index 0000000000..701b966d54
--- /dev/null
+++ b/spec/integration/incoming_mail_spec.rb
@@ -0,0 +1,147 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+require File.expand_path(File.dirname(__FILE__) + '/alaveteli_dsl')
+
+describe 'when handling incoming mail' do
+ let(:info_request){ FactoryGirl.create(:info_request) }
+
+ it "receives incoming messages, sends email to requester, and shows them" do
+ receive_incoming_mail('incoming-request-plain.email',
+ info_request.incoming_email)
+ deliveries = ActionMailer::Base.deliveries
+ expect(deliveries.size).to eq(1)
+ mail = deliveries[0]
+ expect(mail.to).to eq([info_request.user.email])
+ expect(mail.body).to match(/You have a new response to the Freedom of Information request/)
+ visit show_request_path :url_title => info_request.url_title
+ expect(page).to have_content("No way!")
+ end
+
+ it "makes attachments available for download" do
+ receive_incoming_mail('incoming-request-two-same-name.email',
+ info_request.incoming_email)
+ visit get_attachment_path(
+ :incoming_message_id => info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'hello world.txt',
+ :skip_cache => 1)
+ expect(page.response_headers['Content-Type']).to eq("text/plain; charset=utf-8")
+ expect(page).to have_content "Second hello"
+
+ visit get_attachment_path(
+ :incoming_message_id => info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 3,
+ :file_name => 'hello world.txt',
+ :skip_cache => 1)
+ expect(page.response_headers['Content-Type']).to eq("text/plain; charset=utf-8")
+ expect(page).to have_content "First hello"
+ end
+
+ it "converts message body to UTF8" do
+ receive_incoming_mail('iso8859_2_raw_email.email',
+ info_request.incoming_email)
+ visit show_request_path :url_title => info_request.url_title
+ expect(page).to have_content "tënde"
+ end
+
+ it "generates a valid HTML verson of plain text attachments" do
+ receive_incoming_mail('incoming-request-two-same-name.email',
+ info_request.incoming_email)
+ visit get_attachment_as_html_path(
+ :incoming_message_id => info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'hello world.txt.html',
+ :skip_cache => 1)
+ expect(page.response_headers['Content-Type']).to eq("text/html; charset=utf-8")
+ expect(page).to have_content "Second hello"
+ end
+
+ it "generates a valid HTML verson of PDF attachments" do
+ receive_incoming_mail('incoming-request-pdf-attachment.email',
+ info_request.incoming_email)
+ visit get_attachment_as_html_path(
+ :incoming_message_id => info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'fs 50379341.pdf.html',
+ :skip_cache => 1)
+ expect(page.response_headers['Content-Type']).to eq("text/html; charset=utf-8")
+ expect(page).to have_content "Walberswick Parish Council"
+ end
+
+ it "does not cause a reparsing of the raw email, even when the attachment can't be found" do
+ receive_incoming_mail('incoming-request-two-same-name.email',
+ info_request.incoming_email)
+ incoming_message = info_request.incoming_messages.first
+ attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(
+ incoming_message.get_attachments_for_display,
+ 2,
+ 'hello world.txt')
+ expect(attachment.body).to match "Second hello"
+
+ # change the raw_email associated with the message; this should only be
+ # reparsed when explicitly asked for
+ incoming_message.raw_email.data = incoming_message.raw_email.data.sub("Second", "Third")
+ incoming_message.save!
+ # asking for an attachment by the wrong filename should result in redirecting
+ # back to the incoming message, but shouldn't cause a reparse:
+ visit get_attachment_as_html_path(
+ :incoming_message_id => incoming_message.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'hello world.txt.baz.html',
+ :skip_cache => 1
+ )
+
+ attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(
+ incoming_message.get_attachments_for_display,
+ 2,
+ 'hello world.txt')
+ expect(attachment.body).to match "Second hello"
+
+ # ...nor should asking for it by its correct filename...
+ visit get_attachment_as_html_path(
+ :incoming_message_id => incoming_message.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'hello world.txt.html',
+ :skip_cache => 1
+ )
+ expect(page).not_to have_content "Third hello"
+
+ # ...but if we explicitly ask for attachments to be extracted, then they should be
+ force = true
+ incoming_message.parse_raw_email!(force)
+ attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(
+ incoming_message.get_attachments_for_display,
+ 2,
+ 'hello world.txt')
+ expect(attachment.body).to match "Third hello"
+ visit get_attachment_as_html_path(
+ :incoming_message_id => incoming_message.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'hello world.txt.html',
+ :skip_cache => 1
+ )
+ expect(page).to have_content "Third hello"
+ end
+
+ it "treats attachments with unknown extensions as binary" do
+ receive_incoming_mail('incoming-request-attachment-unknown-extension.email',
+ info_request.incoming_email)
+ visit get_attachment_path(
+ :incoming_message_id => info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'hello.qwglhm',
+ :skip_cache => 1
+ )
+ expect(page.response_headers['Content-Type']).to eq("application/octet-stream; charset=utf-8")
+ expect(page).to have_content "an unusual sort of file"
+ end
+
+end
\ No newline at end of file
From dd615ffaf4c6d0de0f9efbff9d12e9ccd721efb5 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 22 Nov 2016 13:45:23 +0000
Subject: [PATCH 053/311] Add spec for returning a 404 for embargoed requests
from attachments.
---
spec/controllers/request_controller_spec.rb | 21 +++++++++++++++++++++
spec/factories/info_requests.rb | 2 ++
2 files changed, 23 insertions(+)
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index 73dbb52045..a2da80ec7c 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -599,6 +599,17 @@ def get_attachment(params = {})
expect(response.content_type).to eq("text/html")
expect(response.body).to have_content "Mouse"
end
+
+ it 'returns an ActiveRecord::RecordNotFound error for an embargoed request' do
+ info_request = FactoryGirl.create(:embargoed_request)
+ expect{ get :get_attachment, :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'interesting.pdf',
+ :skip_cache => 1 }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
end
end
@@ -628,6 +639,16 @@ def get_html_attachment(params = {})
.to raise_error(ActiveRecord::RecordNotFound)
end
+ it 'returns an ActiveRecord::RecordNotFound error for an embargoed request' do
+ info_request = FactoryGirl.create(:embargoed_request)
+ expect{ get :get_attachment_as_html, :incoming_message_id =>
+ info_request.incoming_messages.first.id,
+ :id => info_request.id,
+ :part => 2,
+ :file_name => 'interesting.pdf.html' }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
+
end
end
diff --git a/spec/factories/info_requests.rb b/spec/factories/info_requests.rb
index b32e4bbd96..7d506d46a0 100644
--- a/spec/factories/info_requests.rb
+++ b/spec/factories/info_requests.rb
@@ -75,6 +75,8 @@
factory :embargoed_request do
after(:create) do |info_request, evaluator|
embargo = create(:embargo, :info_request => info_request)
+ incoming_message = create(:incoming_message_with_attachments, :info_request => info_request)
+ info_request.log_event("response", {:incoming_message_id => incoming_message.id})
end
end
From 63f9d382b9afb5ab6e33486444d1e9f1ef1ec54f Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 22 Nov 2016 14:14:15 +0000
Subject: [PATCH 054/311] Handle the case of a comment on an embargoed request.
Scope the finder for the info_request to be commented on to
non embargoed requests, so that a request for an embargoed request
should result in an ActiveRecord::RecordNotFound error and return
a 404 not found response in production.
---
app/controllers/comment_controller.rb | 3 ++-
spec/controllers/comment_controller_spec.rb | 10 ++++++++++
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb
index 759524e8fb..ecb61a262e 100644
--- a/app/controllers/comment_controller.rb
+++ b/app/controllers/comment_controller.rb
@@ -92,7 +92,8 @@ def comment_params
def find_info_request
if params[:type] == 'request'
- @info_request = InfoRequest.find_by_url_title!(params[:url_title])
+ @info_request =
+ InfoRequest.not_embargoed.find_by_url_title!(params[:url_title])
else
raise "Unknown type #{ params[:type] }"
end
diff --git a/spec/controllers/comment_controller_spec.rb b/spec/controllers/comment_controller_spec.rb
index 21552fbc93..cf91601a40 100644
--- a/spec/controllers/comment_controller_spec.rb
+++ b/spec/controllers/comment_controller_spec.rb
@@ -4,6 +4,16 @@
describe CommentController, "when commenting on a request" do
render_views
+ it 'returns a 404 when the info request is embargoed' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect{ post :new, :url_title => embargoed_request.url_title,
+ :comment => { :body => "Some content" },
+ :type => 'request',
+ :submitted_comment => 1,
+ :preview => 1 }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
+
it "should give an error and render 'new' template when body text is just some whitespace" do
post :new, :url_title => info_requests(:naughty_chicken_request).url_title,
:comment => { :body => " " },
From 156ac4b3da641b5367b3249ebe295e5399529ed6 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 11:02:35 +0000
Subject: [PATCH 055/311] Standard format spec descriptions.
---
spec/controllers/help_controller_spec.rb | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 838407c261..7a5368b0dd 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -4,7 +4,7 @@
describe HelpController do
render_views
- describe 'GET index' do
+ describe 'GET #index' do
it 'redirects to the about page' do
get :index
@@ -13,7 +13,7 @@
end
- describe 'GET about' do
+ describe 'GET #about' do
it 'shows the about page' do
get :about
@@ -23,7 +23,7 @@
end
- describe 'GET contact' do
+ describe 'GET #contact' do
it 'shows contact form' do
get :contact
@@ -48,7 +48,7 @@
end
- describe 'POST contact' do
+ describe 'POST #contact' do
it 'sends a contact message' do
post :contact, { :contact => {
From c7eb874a728ad4b95f7559a700f07094693820b1 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 11:16:24 +0000
Subject: [PATCH 056/311] Add some specs for HelpController#unhappy.
---
spec/controllers/help_controller_spec.rb | 30 ++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 7a5368b0dd..2eb3d48f02 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -13,6 +13,36 @@
end
+ describe 'GET #unhappy' do
+ let(:info_request){ FactoryGirl.create(:info_request) }
+
+ it 'shows the unhappy template' do
+ get :unhappy
+ expect(response).to render_template('help/unhappy')
+ end
+
+ it 'does not assign an info_request' do
+ get :unhappy
+ expect(assigns[:info_request]).to be nil
+ end
+
+ context 'when a url_title param is supplied' do
+
+ it 'assigns the info_request' do
+ get :unhappy, :url_title => info_request.url_title
+ expect(assigns[:info_request]).to eq info_request
+ end
+
+ it 'raises an ActiveRecord::RecordNotFound error if the InfoRequest
+ is not found' do
+ expect{ get :unhappy, :url_title => 'something_not_existing' }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
+
+ end
+
+ end
+
describe 'GET #about' do
it 'shows the about page' do
From 86e60362ca90d309f34809280c2b903ecbaefc2f Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 11:16:44 +0000
Subject: [PATCH 057/311] Raise RecordNotFound for unhappy action with
embargoed request.
This should be the same behaviour as if you passed the url_title of
a non-existent request as a param.
---
app/controllers/help_controller.rb | 4 +++-
spec/controllers/help_controller_spec.rb | 6 ++++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index b20714ae19..49fc0f43db 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -20,7 +20,9 @@ def unhappy
@country_code = AlaveteliConfiguration.iso_country_code
@info_request = nil
if params[:url_title]
- @info_request = InfoRequest.find_by_url_title!(params[:url_title])
+ @info_request = InfoRequest
+ .not_embargoed
+ .find_by_url_title!(params[:url_title])
end
end
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 2eb3d48f02..73d36521cc 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -39,6 +39,12 @@
.to raise_error ActiveRecord::RecordNotFound
end
+ it 'raises an ActiveRecord::RecordNotFound error if the InfoRequest
+ is embargoed' do
+ info_request = FactoryGirl.create(:embargoed_request)
+ expect{ get :unhappy, :url_title => info_request.url_title }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
end
end
From f85d2ca8a3495a76868fd7f8a0b0e857a06d40f7 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 11:32:55 +0000
Subject: [PATCH 058/311] Add some more specs to HelpController#unhappy to
cover last_request_id
---
spec/controllers/help_controller_spec.rb | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 73d36521cc..3b103661c8 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -82,6 +82,24 @@
end
+ context 'when a url_title param is supplied' do
+ let(:info_request){ FactoryGirl.create(:info_request) }
+
+ it 'assigns the last request' do
+ request.cookies["last_request_id"] = info_request.id
+ get :contact
+ expect(assigns[:last_request]).to eq info_request
+ end
+
+ it 'raises an ActiveRecord::RecordNotFound error if the InfoRequest
+ is not found' do
+ request.cookies["last_request_id"] = InfoRequest.maximum(:id)+1
+ expect{ get :contact }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
+
+ end
+
end
describe 'POST #contact' do
From f43d64533065272974412c0ab1d51f88baea738b Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 11:33:38 +0000
Subject: [PATCH 059/311] Raise RecordNotFound if last_request_id is an
embargoed request.
This _should_ never happen as we're only setting last request id
on actions that handle public requests, but an embargo could be
added in the meantime, and it seems sensible to guard this action
consistently with others.
---
app/controllers/help_controller.rb | 2 +-
spec/controllers/help_controller_spec.rb | 8 ++++++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 49fc0f43db..ab51ee5dc5 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -39,7 +39,7 @@ def contact
# look up link to request/body
last_request_id = cookies["last_request_id"].to_i
if last_request_id > 0
- @last_request = InfoRequest.find(last_request_id)
+ @last_request = InfoRequest.not_embargoed.find(last_request_id)
else
@last_request = nil
end
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 3b103661c8..36ba22f903 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -98,6 +98,14 @@
.to raise_error ActiveRecord::RecordNotFound
end
+ it 'raises an ActiveRecord::RecordNotFound error if the InfoRequest
+ is embargoed' do
+ info_request = FactoryGirl.create(:embargoed_request)
+ request.cookies["last_request_id"] = info_request.id
+ expect{ get :contact }
+ .to raise_error ActiveRecord::RecordNotFound
+ end
+
end
end
From e11287f690bc1772372bc819b68ec74d7094edf7 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 11:38:37 +0000
Subject: [PATCH 060/311] Remove unneeded view rendering.
---
spec/controllers/reports_controller_spec.rb | 1 -
1 file changed, 1 deletion(-)
diff --git a/spec/controllers/reports_controller_spec.rb b/spec/controllers/reports_controller_spec.rb
index f173f6707e..a9d8dadcb9 100644
--- a/spec/controllers/reports_controller_spec.rb
+++ b/spec/controllers/reports_controller_spec.rb
@@ -11,7 +11,6 @@
end
describe ReportsController, "when reporting a request (logged in)" do
- render_views
before do
@user = users(:robin_user)
From 75b4f6a3b92bf8946ab838b8f3ac1b2a6d875724 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 11:42:14 +0000
Subject: [PATCH 061/311] Standardise spec format.
---
spec/controllers/reports_controller_spec.rb | 179 ++++++++++----------
1 file changed, 94 insertions(+), 85 deletions(-)
diff --git a/spec/controllers/reports_controller_spec.rb b/spec/controllers/reports_controller_spec.rb
index a9d8dadcb9..92ac9a1872 100644
--- a/spec/controllers/reports_controller_spec.rb
+++ b/spec/controllers/reports_controller_spec.rb
@@ -1,102 +1,111 @@
# -*- encoding : utf-8 -*-
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"
+describe ReportsController do
- expect(flash[:notice]).to match(/You need to be logged in/)
- expect(response).to redirect_to show_request_path(:url_title => info_requests(:badger_request).url_title)
- end
-end
-
-describe ReportsController, "when reporting a request (logged in)" do
-
- before do
- @user = users(:robin_user)
- session[:user_id] = @user.id
- end
-
- it "should 404 for non-existent requests" do
- expect {
- post :create, :request_id => "hjksfdhjk_louytu_qqxxx"
- }.to raise_error(ActiveRecord::RecordNotFound)
- end
-
- it "should mark a request as having been reported" do
- ir = info_requests(:badger_request)
- title = ir.url_title
- expect(ir.attention_requested).to eq(false)
-
- post :create, :request_id => title, :reason => "my reason"
- expect(response).to redirect_to show_request_path(:url_title => title)
-
- ir.reload
- expect(ir.attention_requested).to eq(true)
- expect(ir.described_state).to eq("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)
- expect(InfoRequest).to receive(:find_by_url_title!).with("foo").and_return(info_request)
- expect(info_request).to 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
+ describe 'POST #create' do
+ context "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"
- 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"
- expect(response).to redirect_to show_request_url(:url_title => title)
+ expect(flash[:notice]).to match(/You need to be logged in/)
+ expect(response).to redirect_to show_request_path(:url_title => info_requests(:badger_request).url_title)
+ end
+ end
- post :create, :request_id => title, :reason => "my reason"
- expect(response).to redirect_to show_request_url(:url_title => title)
- expect(flash[:notice]).to match(/has already been reported/)
+ context "when reporting a request (logged in)" do
+ before do
+ @user = users(:robin_user)
+ session[:user_id] = @user.id
+ end
+
+ it "should 404 for non-existent requests" do
+ expect {
+ post :create, :request_id => "hjksfdhjk_louytu_qqxxx"
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it "should mark a request as having been reported" do
+ ir = info_requests(:badger_request)
+ title = ir.url_title
+ expect(ir.attention_requested).to eq(false)
+
+ post :create, :request_id => title, :reason => "my reason"
+ expect(response).to redirect_to show_request_path(:url_title => title)
+
+ ir.reload
+ expect(ir.attention_requested).to eq(true)
+ expect(ir.described_state).to eq("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)
+ expect(InfoRequest).to receive(:find_by_url_title!).with("foo").and_return(info_request)
+ expect(info_request).to 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"
+ expect(response).to redirect_to show_request_url(:url_title => title)
+
+ post :create, :request_id => title, :reason => "my reason"
+ expect(response).to redirect_to show_request_url(:url_title => title)
+ expect(flash[:notice]).to match(/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
+ expect(deliveries.size).to eq(1)
+ mail = deliveries[0]
+ expect(mail.subject).to match(/attention_requested/)
+ expect(mail.header['Reply-To'].to_s).to include(@user.email)
+ expect(mail.body).to 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"])
+ expect(InfoRequest).to receive(:find_by_url_title!).with("foo").and_return(info_request)
+
+ post :create, :request_id => "foo", :reason => ""
+ expect(response).to render_template("new")
+ expect(flash[:error]).to eq("Please choose a reason")
+ end
+ end
end
+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
- expect(deliveries.size).to eq(1)
- mail = deliveries[0]
- expect(mail.subject).to match(/attention_requested/)
- expect(mail.header['Reply-To'].to_s).to include(@user.email)
- expect(mail.body).to include(@user.name)
- end
+describe ReportsController do
- 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"])
- expect(InfoRequest).to receive(:find_by_url_title!).with("foo").and_return(info_request)
+ describe "GET #new" do
- post :create, :request_id => "foo", :reason => ""
- expect(response).to render_template("new")
- expect(flash[:error]).to eq("Please choose a reason")
- end
-end
+ let(:info_request) { mock_model(InfoRequest, :url_title => "foo") }
-describe ReportsController, "#new_report_request" do
- let(:info_request) { mock_model(InfoRequest, :url_title => "foo") }
- before :each do
- expect(InfoRequest).to 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"
- expect(response).not_to render_template("new")
+ before :each do
+ expect(InfoRequest).to receive(:find_by_url_title!).with("foo").and_return(info_request)
end
- end
- context "logged in" do
- before :each do
- session[:user_id] = users(:bob_smith_user).id
+ context "not logged in" do
+ it "should require the user to be logged in" do
+ get :new, :request_id => "foo"
+ expect(response).not_to render_template("new")
+ end
end
- it "should show the form" do
- get :new, :request_id => "foo"
- expect(response).to render_template("new")
+
+ 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"
+ expect(response).to render_template("new")
+ end
end
end
end
From 0e43a8c38c6bb8b76875063313242e5513980bd7 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 12:03:04 +0000
Subject: [PATCH 062/311] Cleanup specs
Use factories, not fixtures and mocks. Remove spec testing action
internals in favour of testing results.
---
spec/controllers/reports_controller_spec.rb | 93 +++++++++++----------
1 file changed, 47 insertions(+), 46 deletions(-)
diff --git a/spec/controllers/reports_controller_spec.rb b/spec/controllers/reports_controller_spec.rb
index 92ac9a1872..c83296c3b8 100644
--- a/spec/controllers/reports_controller_spec.rb
+++ b/spec/controllers/reports_controller_spec.rb
@@ -4,19 +4,23 @@
describe ReportsController do
describe 'POST #create' do
+ let(:info_request){ FactoryGirl.create(:info_request) }
+ let(:user){ FactoryGirl.create(:user) }
+
context "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"
-
+ post :create, :request_id => info_request.url_title,
+ :reason => "my reason"
expect(flash[:notice]).to match(/You need to be logged in/)
- expect(response).to redirect_to show_request_path(:url_title => info_requests(:badger_request).url_title)
+ expect(response)
+ .to redirect_to show_request_path(:url_title =>
+ info_request.url_title)
end
end
context "when reporting a request (logged in)" do
before do
- @user = users(:robin_user)
- session[:user_id] = @user.id
+ session[:user_id] = user.id
end
it "should 404 for non-existent requests" do
@@ -26,54 +30,51 @@
end
it "should mark a request as having been reported" do
- ir = info_requests(:badger_request)
- title = ir.url_title
- expect(ir.attention_requested).to eq(false)
+ expect(info_request.attention_requested).to eq(false)
- post :create, :request_id => title, :reason => "my reason"
- expect(response).to redirect_to show_request_path(:url_title => title)
-
- ir.reload
- expect(ir.attention_requested).to eq(true)
- expect(ir.described_state).to eq("attention_requested")
- end
+ post :create, :request_id => info_request.url_title,
+ :reason => "my reason"
+ expect(response)
+ .to redirect_to show_request_path(:url_title =>
+ info_request.url_title)
- it "should pass on the reason and message" do
- info_request = mock_model(InfoRequest, :url_title => "foo", :attention_requested= => nil, :save! => nil)
- expect(InfoRequest).to receive(:find_by_url_title!).with("foo").and_return(info_request)
- expect(info_request).to 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"
+ info_request.reload
+ expect(info_request.attention_requested).to eq(true)
+ expect(info_request.described_state).to eq("attention_requested")
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"
- expect(response).to redirect_to show_request_url(:url_title => title)
-
- post :create, :request_id => title, :reason => "my reason"
- expect(response).to redirect_to show_request_url(:url_title => title)
+ post :create, :request_id => info_request.url_title,
+ :reason => "my reason"
+ expect(response)
+ .to redirect_to show_request_url(:url_title =>
+ info_request.url_title)
+
+ post :create, :request_id => info_request.url_title,
+ :reason => "my reason"
+ expect(response)
+ .to redirect_to show_request_url(:url_title =>
+ info_request.url_title)
expect(flash[:notice]).to match(/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"
+ post :create, :request_id => info_request.url_title,
+ :reason => "my reason",
+ :message => "It's just not"
deliveries = ActionMailer::Base.deliveries
expect(deliveries.size).to eq(1)
mail = deliveries[0]
expect(mail.subject).to match(/attention_requested/)
- expect(mail.header['Reply-To'].to_s).to include(@user.email)
- expect(mail.body).to include(@user.name)
+ expect(mail.header['Reply-To'].to_s).to include(user.email)
+ expect(mail.body).to include(user.name)
+ expect(mail.body)
+ .to include("Reason: my reason\n\nIt's just not")
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"])
- expect(InfoRequest).to receive(:find_by_url_title!).with("foo").and_return(info_request)
-
- post :create, :request_id => "foo", :reason => ""
+ post :create, :request_id => info_request.url_title,
+ :reason => ""
expect(response).to render_template("new")
expect(flash[:error]).to eq("Please choose a reason")
end
@@ -84,28 +85,28 @@
describe ReportsController do
describe "GET #new" do
-
- let(:info_request) { mock_model(InfoRequest, :url_title => "foo") }
-
- before :each do
- expect(InfoRequest).to receive(:find_by_url_title!).with("foo").and_return(info_request)
- end
+ let(:info_request){ FactoryGirl.create(:info_request) }
+ let(:user){ FactoryGirl.create(:user) }
context "not logged in" do
it "should require the user to be logged in" do
- get :new, :request_id => "foo"
+ get :new, :request_id => info_request.url_title
expect(response).not_to render_template("new")
end
end
context "logged in" do
before :each do
- session[:user_id] = users(:bob_smith_user).id
+ session[:user_id] = user.id
end
+
it "should show the form" do
- get :new, :request_id => "foo"
+ get :new, :request_id => info_request.url_title
expect(response).to render_template("new")
end
+
end
+
end
+
end
From c0d38efea99ea1bbce57da6a9e7dddd65acc1d87 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 12:09:26 +0000
Subject: [PATCH 063/311] Raise RecordNotFound when reporting an embargoed
request.
This should be the same behaviour as when reporting a non existent
request.
---
app/controllers/reports_controller.rb | 9 ++++++---
spec/controllers/reports_controller_spec.rb | 21 +++++++++++++++++++++
2 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb
index 80a31a9e06..09ec37c2e3 100644
--- a/app/controllers/reports_controller.rb
+++ b/app/controllers/reports_controller.rb
@@ -1,7 +1,9 @@
# -*- encoding : utf-8 -*-
class ReportsController < ApplicationController
def create
- @info_request = InfoRequest.find_by_url_title!(params[:request_id])
+ @info_request = InfoRequest
+ .not_embargoed
+ .find_by_url_title!(params[:request_id])
@reason = params[:reason]
@message = params[:message]
if @reason.empty?
@@ -9,7 +11,6 @@ def create
render "new"
return
end
-
if !authenticated_user
flash[:notice] = _("You need to be logged in to report a request for administrator attention")
elsif @info_request.attention_requested
@@ -22,7 +23,9 @@ def create
end
def new
- @info_request = InfoRequest.find_by_url_title!(params[:request_id])
+ @info_request = InfoRequest
+ .not_embargoed
+ .find_by_url_title!(params[:request_id])
if authenticated?(
:web => _("To report this request"),
:email => _("Then you can report the request '{{title}}'", :title => @info_request.title),
diff --git a/spec/controllers/reports_controller_spec.rb b/spec/controllers/reports_controller_spec.rb
index c83296c3b8..f175a3d134 100644
--- a/spec/controllers/reports_controller_spec.rb
+++ b/spec/controllers/reports_controller_spec.rb
@@ -29,6 +29,13 @@
}.to raise_error(ActiveRecord::RecordNotFound)
end
+ it 'should 404 for embargoed requests' do
+ info_request = FactoryGirl.create(:embargoed_request)
+ expect {
+ post :create, :request_id => info_request.url_title
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
it "should mark a request as having been reported" do
expect(info_request.attention_requested).to eq(false)
@@ -78,6 +85,7 @@
expect(response).to render_template("new")
expect(flash[:error]).to eq("Please choose a reason")
end
+
end
end
end
@@ -105,6 +113,19 @@
expect(response).to render_template("new")
end
+ it "should 404 for non-existent requests" do
+ expect {
+ get :new, :request_id => "hjksfdhjk_louytu_qqxxx"
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'should 404 for embargoed requests' do
+ info_request = FactoryGirl.create(:embargoed_request)
+ expect {
+ get :new, :request_id => info_request.url_title
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
end
end
From bf17b37a97e1b8d5009558b4438a568104a91233 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 14:29:13 +0000
Subject: [PATCH 064/311] Clean up track_controller specs
Group by action, with standard descriptions. Use factories rather
than fixtures where easy to do, move integration specs to separate
file.
---
spec/controllers/track_controller_spec.rb | 355 +++++++++-------------
spec/factories/track_things.rb | 27 +-
spec/integration/alaveteli_dsl.rb | 2 +-
spec/integration/track_alerts_spec.rb | 84 +++++
4 files changed, 248 insertions(+), 220 deletions(-)
create mode 100644 spec/integration/track_alerts_spec.rb
diff --git a/spec/controllers/track_controller_spec.rb b/spec/controllers/track_controller_spec.rb
index 1054f71671..cd67b56272 100644
--- a/spec/controllers/track_controller_spec.rb
+++ b/spec/controllers/track_controller_spec.rb
@@ -4,43 +4,42 @@
describe TrackController do
let(:mock_cookie) { '0300fd3e1177127cebff' }
- describe 'GET track_request' do
- it 'clears widget votes for the request' do
- allow(AlaveteliConfiguration).to receive(:enable_widgets).and_return(true)
- @info_request = FactoryGirl.create(:info_request)
- @info_request.widget_votes.create(:cookie => mock_cookie)
-
- session[:user_id] = FactoryGirl.create(:user).id
- request.cookies['widget_vote'] = mock_cookie
-
- get :track_request, :url_title => @info_request.url_title, :feed => 'track'
- expect(@info_request.reload.widget_votes).to be_empty
- end
- end
-
- describe "when making a new track on a request" do
- let(:ir) do
+ describe 'GET #track_request' do
+ let(:info_request) do
FactoryGirl.create(:info_request,
:title => 'My request',
:url_title => 'myrequest')
end
-
let(:track_thing) do
FactoryGirl.create(:request_update_track,
- :info_request => ir,
+ :info_request => info_request,
:track_medium => 'email_daily',
:track_query => 'example')
end
-
let(:user) { FactoryGirl.create(:user, :locale => 'en', :name => 'bob') }
+ it 'clears widget votes for the request' do
+ allow(AlaveteliConfiguration).to receive(:enable_widgets).and_return(true)
+ info_request.widget_votes.create(:cookie => mock_cookie)
+
+ session[:user_id] = user.id
+ request.cookies['widget_vote'] = mock_cookie
+
+ get :track_request, :url_title => info_request.url_title,
+ :feed => 'track'
+ expect(info_request.reload.widget_votes).to be_empty
+ end
+
it "should require login when making new track" do
- get :track_request, :url_title => ir.url_title, :feed => 'track'
- expect(response).to redirect_to(signin_path(:token => get_last_post_redirect.token))
+ get :track_request, :url_title => info_request.url_title,
+ :feed => 'track'
+ expect(response)
+ .to redirect_to(signin_path(:token => get_last_post_redirect.token))
end
it "should set no-cache headers on the login redirect" do
- get :track_request, :url_title => ir.url_title, :feed => 'track'
+ get :track_request, :url_title => info_request.url_title,
+ :feed => 'track'
expect(response.headers["Cache-Control"]).
to eq('no-cache, no-store, max-age=0, must-revalidate')
expect(response.headers['Pragma']).to eq('no-cache')
@@ -51,19 +50,98 @@
session[:user_id] = user.id
allow(TrackThing).to receive(:create_track_for_request).and_return(track_thing)
expect(track_thing).to receive(:save).and_call_original
- get :track_request, :url_title => ir.url_title, :feed => 'track'
+ get :track_request, :url_title => info_request.url_title,
+ :feed => 'track'
expect(response).to redirect_to(:controller => 'request',
- :action => 'show', :url_title => ir.url_title)
+ :action => 'show',
+ :url_title => info_request.url_title)
end
it "should 404 for non-existent requests" do
session[:user_id] = user.id
- expect { get :track_request, :url_title => "hjksfdh_louytu_qqxxx", :feed => 'track' }.
- to raise_error(ActiveRecord::RecordNotFound)
+ expect { get :track_request, :url_title => "hjksfdh_louytu_qqxxx",
+ :feed => 'track' }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ context 'when getting feeds' do
+
+ before do
+ load_raw_emails_data
+ get_fixtures_xapian_index
+ end
+
+ it "should get the RSS feed" do
+
+ track_thing = track_things(:track_fancy_dog_request)
+
+ get :track_request, :feed => 'feed',
+ :url_title => track_thing.info_request.url_title
+ expect(response).to render_template('track/atom_feed')
+ expect(response.content_type).to eq('application/atom+xml')
+ # TODO: should check it is an atom.builder type being rendered,
+ # not sure how to
+ expect(assigns[:xapian_object].matches_estimated).to eq(3)
+ expect(assigns[:xapian_object].results.size).to eq(3)
+ expect(assigns[:xapian_object].results[0][:model])
+ .to eq(info_request_events(:silly_comment_event))
+ expect(assigns[:xapian_object].results[1][:model])
+ .to eq(info_request_events(:useless_incoming_message_event))
+ expect(assigns[:xapian_object].results[2][:model])
+ .to eq(info_request_events(:useless_outgoing_message_event))
+ end
+ it "should get JSON version of the feed" do
+ track_thing = track_things(:track_fancy_dog_request)
+
+ get :track_request, :feed => 'feed',
+ :url_title => track_thing.info_request.url_title,
+ :format => "json"
+
+ a = JSON.parse(response.body)
+ expect(a.class.to_s).to eq('Array')
+ expect(a.size).to eq(3)
+
+ expect(a[0]['id'])
+ .to eq(info_request_events(:silly_comment_event).id)
+ expect(a[1]['id'])
+ .to eq(info_request_events(:useless_incoming_message_event).id)
+ expect(a[2]['id'])
+ .to eq(info_request_events(:useless_outgoing_message_event).id)
+
+ expect(a[0]['info_request']['url_title'])
+ .to eq('why_do_you_have_such_a_fancy_dog')
+ expect(a[1]['info_request']['url_title'])
+ .to eq('why_do_you_have_such_a_fancy_dog')
+ expect(a[2]['info_request']['url_title'])
+ .to eq('why_do_you_have_such_a_fancy_dog')
+
+ expect(a[0]['public_body']['url_name']).to eq('tgq')
+ expect(a[1]['public_body']['url_name']).to eq('tgq')
+ expect(a[2]['public_body']['url_name']).to eq('tgq')
+
+ expect(a[0]['user']['url_name']).to eq('bob_smith')
+ expect(a[1]['user']['url_name']).to eq('bob_smith')
+ expect(a[2]['user']['url_name']).to eq('bob_smith')
+
+ expect(a[0]['event_type']).to eq('comment')
+ expect(a[1]['event_type']).to eq('response')
+ expect(a[2]['event_type']).to eq('sent')
+ 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 = FactoryGirl.create(:request_update_track)
+ get :track_request, :feed => 'feed',
+ :url_title => track_thing.info_request.url_title
+ expect(response).to render_template('track/atom_feed')
+ expect(response.content_type).to eq('application/atom+xml')
+ end
end
+
end
- describe "when making a search track" do
+ describe "GET #track_search_query" do
let(:track_thing) do
FactoryGirl.create(:search_track,
:track_medium => 'email_daily',
@@ -93,7 +171,7 @@
end
end
- describe "when making a new track on a public body" do
+ describe "GET #track_public_body" do
let(:public_body) { FactoryGirl.create(:public_body) }
let(:user) { FactoryGirl.create(:user, :locale => 'en', :name => 'bob') }
@@ -119,9 +197,34 @@
expect(flash[:error]).to match('too long')
expect(response).to redirect_to("/body/#{public_body.url_name}")
end
+
+ it "should work" do
+ get :track_public_body, :feed => 'feed',
+ :url_name => public_body.url_name
+ expect(response).to be_success
+ expect(response).to render_template('track/atom_feed')
+ tt = assigns[:track_thing]
+ expect(tt.public_body).to eq(public_body)
+ expect(tt.track_type).to eq('public_body_updates')
+ expect(tt.track_query).to eq("requested_from:" + public_body.url_name)
+ end
+
+ it "should filter by event type" do
+ get :track_public_body, :feed => 'feed',
+ :url_name => public_body.url_name,
+ :event_type => 'sent'
+ expect(response).to be_success
+ expect(response).to render_template('track/atom_feed')
+ tt = assigns[:track_thing]
+ expect(tt.public_body).to eq(public_body)
+ expect(tt.track_type).to eq('public_body_updates')
+ expect(tt.track_query)
+ .to eq("requested_from:#{public_body.url_name} variety:sent")
+ end
+
end
- describe "when making a new track on a user" do
+ describe "GET #track_user" do
let(:target_user) { FactoryGirl.create(:user) }
let(:user) { FactoryGirl.create(:user) }
@@ -146,9 +249,15 @@
expect(flash[:error]).to match('too long')
expect(response).to redirect_to("/user/#{target_user.url_name}")
end
+
+ it "should return NotFound for a non-existent user" do
+ expect { get :track_user, :feed => 'feed', :url_name => "there_is_no_such_user" }.
+ to raise_error(ActiveRecord::RecordNotFound)
+ end
+
end
- describe "when making a new track on a list" do
+ describe "GET #track_list" do
let(:user) { FactoryGirl.create(:user) }
it "should save a list track and redirect to the right place" do
@@ -174,7 +283,7 @@
end
end
- describe "when unsubscribing from a track" do
+ describe "PUT #update" do
let(:track_thing) { FactoryGirl.create(:search_track) }
it 'should destroy the track thing' do
@@ -203,189 +312,7 @@
end
end
- describe "when sending alerts for a track" do
- render_views
-
- before(:each) do
- load_raw_emails_data
- get_fixtures_xapian_index
- end
-
- it "should send alerts" do
- # set the time the comment event happened at to within the last week
- ire = info_request_events(:silly_comment_event)
- ire.created_at = Time.now - 3.days
- ire.save!
-
- TrackMailer.alert_tracks
-
- deliveries = ActionMailer::Base.deliveries
- expect(deliveries.size).to eq(1)
- mail = deliveries[0]
- expect(mail.body).to match(/Alter your subscription/)
- expect(mail.to_addrs.first.to_s).to include(users(:silly_name_user).email)
- mail.body.to_s =~ /(http:\/\/.*\/c\/(.*))/
- mail_url = $1
- mail_token = $2
-
- expect(mail.body).not_to match(/&/)
-
- expect(mail.body).not_to include('sent a request') # request not included
- expect(mail.body).not_to include('sent a response') # response not included
- expect(mail.body).to include('added an annotation') # comment included
-
- expect(mail.body).to match(/This a the daftest comment the world has ever seen/) # comment text included
- # Check subscription managing link
- # TODO: We can't do this, as it is redirecting to another controller. I'm
- # apparently meant to be writing controller unit tests here, not functional
- # tests. Bah, I so don't care, bit of an obsessive constraint.
- # session[:user_id].should be_nil
- # controller.test_code_redirect_by_email_token(mail_token, self) # TODO: hack to avoid having to call User controller for email link
- # session[:user_id].should == users(:silly_name_user).id
- #
- # response.should render_template('users/show')
- # assigns[:display_user].should == users(:silly_name_user)
-
- # Given we can't click the link, check the token is right instead
- post_redirect = PostRedirect.find_by_email_token(mail_token)
- expected_url = show_user_url(:url_name => users(:silly_name_user).url_name,
- :anchor => "email_subscriptions")
- expect(post_redirect.uri).to eq(expected_url)
-
- # Check nothing more is delivered if we try again
- deliveries.clear
- TrackMailer.alert_tracks
- deliveries = ActionMailer::Base.deliveries
- expect(deliveries.size).to eq(0)
- end
-
- it "should send localised alerts" do
- # set the time the comment event happened at to within the last week
- ire = info_request_events(:silly_comment_event)
- ire.created_at = Time.now - 3.days
- ire.save!
- user = users(:silly_name_user)
- user.locale = "es"
- user.save!
- TrackMailer.alert_tracks
- deliveries = ActionMailer::Base.deliveries
- mail = deliveries[0]
- expect(mail.body).to include('el equipo de ')
- end
- end
-
- describe "when viewing RSS feed for a track" do
- render_views
-
- before(:each) do
- load_raw_emails_data
- get_fixtures_xapian_index
- end
-
- it "should get the RSS feed" do
- track_thing = track_things(:track_fancy_dog_request)
-
- get :track_request, :feed => 'feed', :url_title => track_thing.info_request.url_title
- expect(response).to render_template('track/atom_feed')
- expect(response.content_type).to eq('application/atom+xml')
- # TODO: should check it is an atom.builder type being rendered, not sure how to
-
- expect(assigns[:xapian_object].matches_estimated).to eq(3)
- expect(assigns[:xapian_object].results.size).to eq(3)
- expect(assigns[:xapian_object].results[0][:model]).to eq(info_request_events(:silly_comment_event)) # created_at 2008-08-12 23:05:12.500942
- expect(assigns[:xapian_object].results[1][:model]).to eq(info_request_events(:useless_incoming_message_event)) # created_at 2007-11-13 18:09:20.042061
- expect(assigns[:xapian_object].results[2][:model]).to eq(info_request_events(:useless_outgoing_message_event)) # created_at 2007-10-14 10:41:12.686264
- end
-
- it "should return NotFound for a non-existent user" do
- expect { get :track_user, :feed => 'feed', :url_name => "there_is_no_such_user" }.
- to 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
- expect(response).to render_template('track/atom_feed')
- expect(response.content_type).to eq('application/atom+xml')
- end
- end
-
- describe "when viewing JSON version of a track feed" do
- render_views
-
- before(:each) do
- load_raw_emails_data
- get_fixtures_xapian_index
- end
-
- it "should get the feed" do
- track_thing = track_things(:track_fancy_dog_request)
-
- get :track_request, :feed => 'feed', :url_title => track_thing.info_request.url_title, :format => "json"
-
- a = JSON.parse(response.body)
- expect(a.class.to_s).to eq('Array')
- expect(a.size).to eq(3)
-
- expect(a[0]['id']).to eq(info_request_events(:silly_comment_event).id)
- expect(a[1]['id']).to eq(info_request_events(:useless_incoming_message_event).id)
- expect(a[2]['id']).to eq(info_request_events(:useless_outgoing_message_event).id)
-
- expect(a[0]['info_request']['url_title']).to eq('why_do_you_have_such_a_fancy_dog')
- expect(a[1]['info_request']['url_title']).to eq('why_do_you_have_such_a_fancy_dog')
- expect(a[2]['info_request']['url_title']).to eq('why_do_you_have_such_a_fancy_dog')
-
- expect(a[0]['public_body']['url_name']).to eq('tgq')
- expect(a[1]['public_body']['url_name']).to eq('tgq')
- expect(a[2]['public_body']['url_name']).to eq('tgq')
-
- expect(a[0]['user']['url_name']).to eq('bob_smith')
- expect(a[1]['user']['url_name']).to eq('bob_smith')
- expect(a[2]['user']['url_name']).to eq('bob_smith')
-
- expect(a[0]['event_type']).to eq('comment')
- expect(a[1]['event_type']).to eq('response')
- expect(a[2]['event_type']).to eq('sent')
- end
- end
-
- describe "when tracking a public body" do
- render_views
-
- before(:each) do
- load_raw_emails_data
- get_fixtures_xapian_index
- end
-
- it "should work" do
- geraldine = public_bodies(:geraldine_public_body)
- get :track_public_body, :feed => 'feed', :url_name => geraldine.url_name
- expect(response).to be_success
- expect(response).to render_template('track/atom_feed')
- tt = assigns[:track_thing]
- expect(tt.public_body).to eq(geraldine)
- expect(tt.track_type).to eq('public_body_updates')
- expect(tt.track_query).to eq("requested_from:" + geraldine.url_name)
- end
-
- it "should filter by event type" do
- geraldine = public_bodies(:geraldine_public_body)
- get :track_public_body,
- :feed => 'feed', :url_name => geraldine.url_name, :event_type => 'sent'
- expect(response).to be_success
- expect(response).to render_template('track/atom_feed')
- tt = assigns[:track_thing]
- expect(tt.public_body).to eq(geraldine)
- expect(tt.track_type).to eq('public_body_updates')
- expect(tt.track_query).to eq("requested_from:" + geraldine.url_name + " variety:sent")
- end
- end
-
- describe 'POST delete_all_type' do
+ describe 'POST #delete_all_type' do
let(:track_thing) { FactoryGirl.create(:search_track) }
diff --git a/spec/factories/track_things.rb b/spec/factories/track_things.rb
index 67d33491fd..ae8a940286 100644
--- a/spec/factories/track_things.rb
+++ b/spec/factories/track_things.rb
@@ -18,29 +18,46 @@
FactoryGirl.define do
factory :track_thing do
+ track_medium 'email_daily'
+ track_query 'Example Query'
+ track_type 'search_query'
association :tracking_user, :factory => :user
- factory :search_track do
- track_medium 'email_daily'
- track_type 'search_query'
- track_query 'Example Query'
- end
+ factory :search_track
factory :user_track do
association :tracked_user, :factory => :user
track_type 'user_updates'
+ after(:create) do |track_thing, evaluator|
+ track_thing.track_query = "requested_by:#{ user.url_name }" \
+ " OR commented_by: #{ user.url_name }"
+ track_thing.save
+ end
end
factory :public_body_track do
association :public_body, :factory => :public_body
track_type 'public_body_updates'
+ after(:create) do |track_thing, evaluator|
+ track_thing.track_query = "requested_from:" \
+ "#{ track_thing.public_body.url_name }"
+ track_thing.save
+ end
end
factory :request_update_track do
association :info_request, :factory => :info_request
track_type 'request_updates'
+ after(:create) do |track_thing, evaluator|
+ track_thing.track_query = "request:" \
+ "#{ track_thing.info_request.url_title }"
+ track_thing.save
+ end
end
factory :successful_request_track do
track_type 'all_successful_requests'
+ track_query 'variety:response ' \
+ '(status:successful OR status:partially_successful)'
end
factory :new_request_track do
track_type 'all_new_requests'
+ track_query 'variety:sent'
end
end
diff --git a/spec/integration/alaveteli_dsl.rb b/spec/integration/alaveteli_dsl.rb
index b3a82cc364..b8ff483c85 100644
--- a/spec/integration/alaveteli_dsl.rb
+++ b/spec/integration/alaveteli_dsl.rb
@@ -48,7 +48,7 @@ def alaveteli_session(session_id)
def login(user)
u = user.is_a?(User) ? user : users(user)
alaveteli_session(u.id) do
- visit signin_path
+ visit 'en/profile/sign_in'
within '#signin_form' do
fill_in "Your e-mail:", :with => u.email
fill_in "Password:", :with => "jonespassword"
diff --git a/spec/integration/track_alerts_spec.rb b/spec/integration/track_alerts_spec.rb
new file mode 100644
index 0000000000..89b2a1f4ca
--- /dev/null
+++ b/spec/integration/track_alerts_spec.rb
@@ -0,0 +1,84 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+require File.expand_path(File.dirname(__FILE__) + '/alaveteli_dsl')
+
+describe "When sending track alerts" do
+
+ it "should send alerts" do
+
+ info_request = FactoryGirl.create(:info_request)
+ user = FactoryGirl.create(:user, :last_daily_track_email => 3.days.ago)
+ user_session = login(user)
+ using_session(user_session) do
+ visit "track/request/#{info_request.url_title}"
+ end
+
+ other_user = FactoryGirl.create(:user)
+ other_user_session = login(other_user)
+ using_session(other_user_session) do
+ visit "en/annotate/request/#{info_request.url_title}"
+ fill_in "comment[body]", :with => 'test comment'
+ click_button 'Preview your annotation'
+ click_button 'Post annotation'
+ end
+
+ rebuild_xapian_index
+
+ TrackMailer.alert_tracks
+
+ deliveries = ActionMailer::Base.deliveries
+ expect(deliveries.size).to eq(1)
+ mail = deliveries[0]
+ expect(mail.body).to match(/Alter your subscription/)
+ expect(mail.to_addrs.first.to_s).to include(user.email)
+ mail.body.to_s =~ /(http:\/\/.*\/c\/(.*))/
+ mail_url = $1
+ mail_token = $2
+
+ expect(mail.body).not_to match(/&/)
+
+ expect(mail.body).not_to include('sent a request') # request not included
+ expect(mail.body).not_to include('sent a response') # response not included
+ expect(mail.body).to include('added an annotation') # comment included
+
+ expect(mail.body).to match(/test comment/) # comment text included
+
+ post_redirect = PostRedirect.find_by_email_token(mail_token)
+ expected_path = show_user_path(:url_name => user.url_name,
+ :anchor => "email_subscriptions")
+ expect(post_redirect.uri).to match(expected_path)
+
+ # Check nothing more is delivered if we try again
+ deliveries.clear
+ TrackMailer.alert_tracks
+ deliveries = ActionMailer::Base.deliveries
+ expect(deliveries.size).to eq(0)
+ end
+
+ it "should send localised alerts" do
+ info_request = FactoryGirl.create(:info_request)
+ user = FactoryGirl.create(:user, :last_daily_track_email => 3.days.ago,
+ :locale => 'es')
+ user_session = login(user)
+ using_session(user_session) do
+ visit "es/track/request/#{info_request.url_title}"
+ end
+
+ other_user = FactoryGirl.create(:user, :locale => 'en')
+ other_user_session = login(other_user)
+ using_session(other_user_session) do
+ visit "annotate/request/#{info_request.url_title}"
+ fill_in "comment[body]", :with => 'test comment'
+ click_button 'Preview your annotation'
+ click_button 'Post annotation'
+ end
+
+ rebuild_xapian_index
+
+ TrackMailer.alert_tracks
+ deliveries = ActionMailer::Base.deliveries
+ mail = deliveries[0]
+ expect(mail.body).to include('el equipo de ')
+ end
+end
+
From 0338f6b5ddbb4949243000c38be4c093dfbd25bb Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 14:35:08 +0000
Subject: [PATCH 065/311] Raise RecordNotFound on attempt to track embargoed
requests.
This should be the same behaviour as happens on an attempt to track
a non-existent request.
---
app/controllers/track_controller.rb | 4 +++-
spec/controllers/track_controller_spec.rb | 8 ++++++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/app/controllers/track_controller.rb b/app/controllers/track_controller.rb
index 721d95546d..f9133332b0 100644
--- a/app/controllers/track_controller.rb
+++ b/app/controllers/track_controller.rb
@@ -11,7 +11,9 @@ class TrackController < ApplicationController
# Track all updates to a particular request
def track_request
- @info_request = InfoRequest.find_by_url_title!(params[:url_title])
+ @info_request = InfoRequest
+ .not_embargoed
+ .find_by_url_title!(params[:url_title])
@track_thing = TrackThing.create_track_for_request(@info_request)
return atom_feed_internal if params[:feed] == 'feed'
diff --git a/spec/controllers/track_controller_spec.rb b/spec/controllers/track_controller_spec.rb
index cd67b56272..62a9a8a8a1 100644
--- a/spec/controllers/track_controller_spec.rb
+++ b/spec/controllers/track_controller_spec.rb
@@ -64,6 +64,14 @@
.to raise_error(ActiveRecord::RecordNotFound)
end
+ it "should 404 for embargoed requests" do
+ session[:user_id] = user.id
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect { get :track_request, :url_title => embargoed_request.url_title,
+ :feed => 'track' }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+
context 'when getting feeds' do
before do
From 114c57dff27512427fe805147d96e7b3214e8e91 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 14:55:40 +0000
Subject: [PATCH 066/311] Cleanup specs, use standard descriptions.
---
.../widget_votes_controller_spec.rb | 32 +++++++++----------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/spec/controllers/widget_votes_controller_spec.rb b/spec/controllers/widget_votes_controller_spec.rb
index 04d96f6e29..4ab91ee000 100644
--- a/spec/controllers/widget_votes_controller_spec.rb
+++ b/spec/controllers/widget_votes_controller_spec.rb
@@ -5,21 +5,21 @@
include LinkToHelper
- describe 'POST create' do
+ describe 'POST #create' do
+ let(:info_request){ FactoryGirl.create(:info_request) }
before do
- @info_request = FactoryGirl.create(:info_request)
allow(AlaveteliConfiguration).to receive(:enable_widgets).and_return(true)
end
it 'should find the info request' do
- post :create, :request_id => @info_request.id
- expect(assigns[:info_request]).to eq(@info_request)
+ post :create, :request_id => info_request.id
+ expect(assigns[:info_request]).to eq(info_request)
end
it 'should redirect to the track path for the info request' do
- post :create, :request_id => @info_request.id
- track_thing = TrackThing.create_track_for_request(@info_request)
+ post :create, :request_id => info_request.id
+ track_thing = TrackThing.create_track_for_request(info_request)
expect(response).to redirect_to(do_track_path(track_thing))
end
@@ -27,17 +27,17 @@
it 'sets a tracking cookie' do
allow(SecureRandom).to receive(:hex).and_return(mock_cookie)
- post :create, :request_id => @info_request.id
+ post :create, :request_id => info_request.id
expect(cookies[:widget_vote]).to eq(mock_cookie)
end
it 'creates a widget vote' do
allow(SecureRandom).to receive(:hex).and_return(mock_cookie)
- votes = @info_request.
+ votes = info_request.
widget_votes.
where(:cookie => mock_cookie)
- post :create, :request_id => @info_request.id
+ post :create, :request_id => info_request.id
expect(votes.size).to eq(1)
end
@@ -48,17 +48,17 @@
it 'retains the existing tracking cookie' do
request.cookies['widget_vote'] = mock_cookie
- post :create, :request_id => @info_request.id
+ post :create, :request_id => info_request.id
expect(cookies[:widget_vote]).to eq(mock_cookie)
end
it 'creates a widget vote' do
request.cookies['widget_vote'] = mock_cookie
- votes = @info_request.
+ votes = info_request.
widget_votes.
where(:cookie => mock_cookie)
- post :create, :request_id => @info_request.id
+ post :create, :request_id => info_request.id
expect(votes.size).to eq(1)
end
@@ -69,7 +69,7 @@
it 'raises ActiveRecord::RecordNotFound' do
allow(AlaveteliConfiguration).to receive(:enable_widgets).and_return(false)
- expect{ post :create, :request_id => @info_request.id }.
+ expect{ post :create, :request_id => info_request.id }.
to raise_error(ActiveRecord::RecordNotFound)
end
@@ -78,9 +78,9 @@
context "when the request's prominence is not 'normal'" do
it 'should return a 403' do
- @info_request.prominence = 'hidden'
- @info_request.save!
- post :create, :request_id => @info_request.id
+ info_request.prominence = 'hidden'
+ info_request.save!
+ post :create, :request_id => info_request.id
expect(response.code).to eq("403")
end
From 42b7b11884cedc6deaf8f606e7625cd423e1abdf Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 15:07:40 +0000
Subject: [PATCH 067/311] Raise RecordNotFound on WidgetVoteController#create
with an embargoed request.
This should be the same response as when a non-existent request
id is used.
---
app/controllers/widget_votes_controller.rb | 2 +-
spec/controllers/widget_votes_controller_spec.rb | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/app/controllers/widget_votes_controller.rb b/app/controllers/widget_votes_controller.rb
index 27ea4326c7..360bf9ea56 100644
--- a/app/controllers/widget_votes_controller.rb
+++ b/app/controllers/widget_votes_controller.rb
@@ -39,7 +39,7 @@ def check_widget_config
end
def find_info_request
- @info_request = InfoRequest.find(params[:request_id])
+ @info_request = InfoRequest.not_embargoed.find(params[:request_id])
end
def check_prominence
diff --git a/spec/controllers/widget_votes_controller_spec.rb b/spec/controllers/widget_votes_controller_spec.rb
index 4ab91ee000..86c4033b15 100644
--- a/spec/controllers/widget_votes_controller_spec.rb
+++ b/spec/controllers/widget_votes_controller_spec.rb
@@ -86,6 +86,15 @@
end
+ context 'when the request is embargoed' do
+
+ it 'should raise an ActiveRecord::RecordNotFound error' do
+ embargoed_request = FactoryGirl.create(:embargoed_request)
+ expect{
+ post :create, :request_id => embargoed_request.id
+ }.to raise_error ActiveRecord::RecordNotFound
+ end
+ end
end
end
From 0ee6fbe1ff35cefeff200ccfde0c8487e33cafd6 Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 15:12:37 +0000
Subject: [PATCH 068/311] Standard controller action descriptions.
---
spec/controllers/followups_controller_spec.rb | 6 +++---
spec/controllers/request_controller_spec.rb | 8 ++++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/spec/controllers/followups_controller_spec.rb b/spec/controllers/followups_controller_spec.rb
index 8cfeacd013..50b25d3c27 100644
--- a/spec/controllers/followups_controller_spec.rb
+++ b/spec/controllers/followups_controller_spec.rb
@@ -8,7 +8,7 @@
let(:request) { FactoryGirl.create(:info_request_with_incoming, :user => request_user) }
let(:message_id) { request.incoming_messages[0].id }
- describe "GET new" do
+ describe "GET #new" do
it 'raises an ActiveRecord::RecordNotFound error for an embargoed request' do
embargoed_request = FactoryGirl.create(:embargoed_request)
@@ -126,7 +126,7 @@
end
- describe "POST preview" do
+ describe "POST #preview" do
let(:dummy_message) do
{ :body => "What a useless response! You suck.",
@@ -193,7 +193,7 @@
end
- describe "POST create" do
+ describe "POST #create" do
let(:dummy_message) do
{ :body => "What a useless response! You suck.",
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index a2da80ec7c..ef381258a3 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -2640,7 +2640,7 @@ def make_request
describe RequestController do
- describe 'GET details' do
+ describe 'GET #details' do
let(:info_request){ FactoryGirl.create(:info_request)}
@@ -2702,7 +2702,7 @@ def make_request
end
describe RequestController do
- describe 'GET describe_state_message' do
+ describe 'GET #describe_state_message' do
let(:info_request){ FactoryGirl.create(:info_request_with_incoming) }
it 'assigns the info_request to the view' do
@@ -2747,7 +2747,7 @@ def make_request
describe RequestController do
- describe 'GET download_entire_request' do
+ describe 'GET #download_entire_request' do
context 'when the request is embargoed' do
let(:info_request){ FactoryGirl.create(:embargoed_request) }
@@ -2762,7 +2762,7 @@ def make_request
describe RequestController do
- describe 'GET show_request_event' do
+ describe 'GET #show_request_event' do
context 'when the event is an incoming message' do
let(:event){ FactoryGirl.create(:response_event) }
From f513163a6c05dade93d164e2728ce460edd075d8 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 7 Nov 2016 12:07:49 +0000
Subject: [PATCH 069/311] Provide a hook in the signin process for overriding
pro PostRedirects
---
app/controllers/application_controller.rb | 25 ++++++++++++++++++---
app/controllers/user_controller.rb | 4 ++--
spec/controllers/request_controller_spec.rb | 4 ++++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a60667f604..8fc0454ead 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -300,11 +300,17 @@ def authenticated_user
# Do a POST redirect. This is a nasty hack - we store the posted values in
# the session, and when the GET redirect with "?post_redirect=1" happens,
# load them in.
- def do_post_redirect(post_redirect)
+ def do_post_redirect(post_redirect, user=nil)
uri = URI.parse(post_redirect.uri).path
-
+ if feature_enabled?(:alaveteli_pro) && user && user.pro?
+ uri = override_post_redirect_for_pro(uri, post_redirect, user)
+ end
session[:post_redirect_token] = post_redirect.token
+ uri = add_post_redirect_param_to_uri(uri)
+ redirect_to uri
+ end
+ def add_post_redirect_param_to_uri(uri)
# TODO: what is the built in Ruby URI munging function that can do this
# choice of & vs. ? more elegantly than this dumb if statement?
if uri.include?("?")
@@ -322,7 +328,20 @@ def do_post_redirect(post_redirect)
uri += "?post_redirect=1"
end
end
- redirect_to uri
+ return uri
+ end
+
+ # A hook for us to override certain post redirects for pro users, e.g.
+ # if they start making a request, then we realise they're a pro when they
+ # log in, so we want to send them into the pro system
+ def override_post_redirect_for_pro(uri, post_redirect, user)
+ case uri
+ when "/new"
+ # TODO: Send this to the new 'draft' controller action for pros
+ # instead. I guess we could just update the uri, but we might need to
+ # tweak the saved params too.
+ end
+ return uri
end
# If we are in a faked redirect to POST request, then set post params.
diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb
index e2b31e38ff..59a00b19d9 100644
--- a/app/controllers/user_controller.rb
+++ b/app/controllers/user_controller.rb
@@ -127,7 +127,7 @@ def signin
if is_modal_dialog
render :action => 'signin_successful'
else
- do_post_redirect @post_redirect
+ do_post_redirect @post_redirect, @user_signin
end
else
send_confirmation_mail @user_signin
@@ -231,7 +231,7 @@ def confirm
session[:user_circumstance] = post_redirect.circumstance
- do_post_redirect post_redirect
+ do_post_redirect post_redirect, @user
end
def signout
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index ef381258a3..3f18768efc 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -1375,6 +1375,10 @@ def expect_hidden(hidden_template)
end
+ describe 'when a pro user uses the request form and then logs in' do
+ it 'should do what we decide is appropriate'
+ end
+
end
# These go with the previous set, but use mocks instead of fixtures.
From cada4623386403bf255c757988028f544648061a Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 8 Nov 2016 12:51:35 +0000
Subject: [PATCH 070/311] Add a base controller for alaveteli_pro with auth
checking
---
.../alaveteli_pro/base_controller.rb | 34 +++++++++++
.../alaveteli_pro/base_controller_spec.rb | 58 +++++++++++++++++++
2 files changed, 92 insertions(+)
create mode 100644 app/controllers/alaveteli_pro/base_controller.rb
create mode 100644 spec/controllers/alaveteli_pro/base_controller_spec.rb
diff --git a/app/controllers/alaveteli_pro/base_controller.rb b/app/controllers/alaveteli_pro/base_controller.rb
new file mode 100644
index 0000000000..1a6d8e584d
--- /dev/null
+++ b/app/controllers/alaveteli_pro/base_controller.rb
@@ -0,0 +1,34 @@
+# -*- encoding : utf-8 -*-
+# app/controllers/alaveteli_pro/base_controller.rb
+# Base controller for other controllers in the alaveteli_pro module.
+#
+# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved.
+# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
+
+class AlaveteliPro::BaseController < ApplicationController
+ before_filter :pro_user_authenticated?
+
+ # A pro-specific version of user_authenticated? that pro controller actions
+ # can use to check for (or force a login for) an authenticated pro user
+ def pro_user_authenticated?(reason_params = nil)
+ if reason_params.nil?
+ reason_params = {
+ web: _("To access Alaveteli Professional"),
+ email: _("Then you can access Alaveteli Professional")
+ }
+ end
+ if authenticated?(reason_params)
+ unless current_user.pro?
+ redirect_to(
+ frontpage_path,
+ flash: {
+ notice: _("This page is only accessible to Alaveteli " \
+ "Professional users")
+ }
+ )
+ end
+ return true
+ end
+ return false
+ end
+end
diff --git a/spec/controllers/alaveteli_pro/base_controller_spec.rb b/spec/controllers/alaveteli_pro/base_controller_spec.rb
new file mode 100644
index 0000000000..158a2ff218
--- /dev/null
+++ b/spec/controllers/alaveteli_pro/base_controller_spec.rb
@@ -0,0 +1,58 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe AlaveteliPro::BaseController do
+ controller(AlaveteliPro::BaseController) do
+ def index
+ render :nothing => true
+ end
+ end
+
+ describe "#pro_user_authenticated?" do
+ # Testing the fact that every controller action inherits the before_filter
+ # of pro_user_authenticated?
+
+ before do
+ allow(controller).to receive(:feature_enabled?).with(:alaveteli_pro).and_return(true)
+ end
+
+ context "when the user is not logged in" do
+ it "redirects to the signin path" do
+ get :index
+ expect(@response.redirect_url).to match(/http:\/\/test\.host\/profile\/sign_in/)
+ end
+ end
+
+ context "when the user is logged in but not a pro" do
+ let(:user) { FactoryGirl.create(:user) }
+
+ before do
+ session[:user_id] = user.id
+ end
+
+ it "redirects to the homepage" do
+ get :index
+ expect(@response).to redirect_to frontpage_path
+ end
+
+ it "sets a flash notice to inform the user they're not a pro" do
+ get :index
+ expect(flash[:notice]).to eq "This page is only accessible to " \
+ "Alaveteli Professional users"
+ end
+ end
+
+ context "when the user is logged in and is a pro" do
+ let(:user) { FactoryGirl.create(:pro_user) }
+
+ before do
+ session[:user_id] = user.id
+ end
+
+ it "doesn't redirect anywhere" do
+ get :index
+ expect(@response.status).to be 200
+ end
+ end
+ end
+end
From fa17179c41a574c9e328fa651e1ad62d05381178 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 8 Nov 2016 12:52:03 +0000
Subject: [PATCH 071/311] Add a really basic dashboard controller for alaveteli
pro
---
.../alaveteli_pro/dashboard_controller.rb | 12 ++++++++++
.../alaveteli_pro/dashboard/index.html.erb | 2 ++
config/routes.rb | 12 ++++++++++
.../dashboard_controller_spec.rb | 22 +++++++++++++++++++
4 files changed, 48 insertions(+)
create mode 100644 app/controllers/alaveteli_pro/dashboard_controller.rb
create mode 100644 app/views/alaveteli_pro/dashboard/index.html.erb
create mode 100644 spec/controllers/alaveteli_pro/dashboard_controller_spec.rb
diff --git a/app/controllers/alaveteli_pro/dashboard_controller.rb b/app/controllers/alaveteli_pro/dashboard_controller.rb
new file mode 100644
index 0000000000..3b59af0767
--- /dev/null
+++ b/app/controllers/alaveteli_pro/dashboard_controller.rb
@@ -0,0 +1,12 @@
+# -*- encoding : utf-8 -*-
+# app/controllers/alaveteli_pro/dashboard_controller.rb
+# Dashboard controller, for pro user dashboards.
+#
+# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved.
+# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
+
+class AlaveteliPro::DashboardController < AlaveteliPro::BaseController
+ def index
+ @user = current_user
+ end
+end
diff --git a/app/views/alaveteli_pro/dashboard/index.html.erb b/app/views/alaveteli_pro/dashboard/index.html.erb
new file mode 100644
index 0000000000..9435f616be
--- /dev/null
+++ b/app/views/alaveteli_pro/dashboard/index.html.erb
@@ -0,0 +1,2 @@
+
Your dashboard
+
Welcome to your dashboard <%= @user.name %>
diff --git a/config/routes.rb b/config/routes.rb
index 988286ef80..c50d6ddd34 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -5,6 +5,8 @@
# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved.
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
+include AlaveteliFeatures::Constraints
+
# Allow easy extension from themes. Note these will have the highest priority.
$alaveteli_route_extensions.each do |f|
load File.join('config', f)
@@ -592,5 +594,15 @@
:feed_type => '^(json|atom)$'
####
+ #### Alaveteli Pro
+ constraints FeatureConstraint.new(:alaveteli_pro) do
+ namespace :alaveteli_pro do
+ match '/' => 'dashboard#index',
+ :as => 'dashboard',
+ :via => :get
+ end
+ end
+ ####
+
filter :conditionallyprependlocale
end
diff --git a/spec/controllers/alaveteli_pro/dashboard_controller_spec.rb b/spec/controllers/alaveteli_pro/dashboard_controller_spec.rb
new file mode 100644
index 0000000000..cd402c1d27
--- /dev/null
+++ b/spec/controllers/alaveteli_pro/dashboard_controller_spec.rb
@@ -0,0 +1,22 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe AlaveteliPro::DashboardController do
+ describe "#index" do
+ let(:user) { FactoryGirl.create(:pro_user) }
+
+ before do
+ session[:user_id] = user.id
+ end
+
+ it "exists" do
+ get :index
+ expect(@response.status).to be 200
+ end
+
+ it "sets @user" do
+ get :index
+ expect(assigns[:user]).to eq user
+ end
+ end
+end
From df8a20359b791d189a3773478dd975cfa1658660 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 8 Nov 2016 13:52:57 +0000
Subject: [PATCH 072/311] Redirect pro users to dashboard from the homepage
---
app/controllers/general_controller.rb | 10 ++++++++++
spec/controllers/general_controller_spec.rb | 13 +++++++++++++
2 files changed, 23 insertions(+)
diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb
index c5b6941541..20d46dfba8 100644
--- a/app/controllers/general_controller.rb
+++ b/app/controllers/general_controller.rb
@@ -12,6 +12,8 @@ class GeneralController < ApplicationController
MAX_RESULTS = 500
+ before_filter :redirect_pros_to_dashboard, only: :frontpage
+
# New, improved front page!
def frontpage
medium_cache
@@ -234,4 +236,12 @@ def version
}}
end
end
+
+ private
+
+ def redirect_pros_to_dashboard
+ if feature_enabled?(:alaveteli_pro) && current_user && current_user.pro?
+ redirect_to alaveteli_pro_dashboard_path
+ end
+ end
end
diff --git a/spec/controllers/general_controller_spec.rb b/spec/controllers/general_controller_spec.rb
index a0b4e9681b..60164f03c3 100644
--- a/spec/controllers/general_controller_spec.rb
+++ b/spec/controllers/general_controller_spec.rb
@@ -256,6 +256,19 @@
end
+ describe 'when handling pro users' do
+ before do
+ @user = FactoryGirl.create(:pro_user)
+ session[:user_id] = @user.id
+ allow(controller).to receive(:feature_enabled?).with(:alaveteli_pro).and_return(true)
+ end
+
+ it 'should redirect pro users to the pro dashboard' do
+ get :frontpage
+ expect(@response).to redirect_to alaveteli_pro_dashboard_path
+ end
+ end
+
end
From a1377b4042e09da449ef06e6f3311cf27fb00b5d Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Tue, 15 Nov 2016 21:49:34 +0000
Subject: [PATCH 073/311] Add a routing constraint object to AlaveteliFeatures
---
.../lib/alaveteli_features.rb | 16 ++++++++-----
.../lib/alaveteli_features/constraints.rb | 15 ++++++++++++
.../constraints/feature_constraint_spec.rb | 23 +++++++++++++++++++
3 files changed, 48 insertions(+), 6 deletions(-)
create mode 100644 gems/alaveteli_features/lib/alaveteli_features/constraints.rb
create mode 100644 gems/alaveteli_features/spec/constraints/feature_constraint_spec.rb
diff --git a/gems/alaveteli_features/lib/alaveteli_features.rb b/gems/alaveteli_features/lib/alaveteli_features.rb
index 89793fc865..c10c6b1395 100644
--- a/gems/alaveteli_features/lib/alaveteli_features.rb
+++ b/gems/alaveteli_features/lib/alaveteli_features.rb
@@ -1,5 +1,6 @@
require "alaveteli_features/version"
require "alaveteli_features/helpers"
+require "alaveteli_features/constraints"
require "alaveteli_features/railtie" if defined?(Rails)
require "flipper"
require "flipper-active_record"
@@ -7,14 +8,17 @@
module AlaveteliFeatures
def self.backend
return @backend if @backend
- if ActiveRecord::Base.connection.table_exists? :flipper_features
+ if ActiveRecord::Base.connected? && \
+ ActiveRecord::Base.connection.table_exists?(:flipper_features)
@backend = Flipper.new(Flipper::Adapters::ActiveRecord.new)
else
- Rails.logger.warn "No database tables found for feature flags, you " \
- "might need to set a backend explicitly if you " \
- "don't want them stored in a database, or run" \
- "rake db:migrate to create the table."
- Rails.logger.warn "Using memory-based feature storage instead."
+ if defined?(Rails)
+ Rails.logger.warn "No database tables found for feature flags, you " \
+ "might need to set a backend explicitly if you " \
+ "don't want them stored in a database, or run" \
+ "rake db:migrate to create the table."
+ Rails.logger.warn "Using memory-based feature storage instead."
+ end
require 'flipper/adapters/memory'
@backend = Flipper.new(Flipper::Adapters::Memory.new)
end
diff --git a/gems/alaveteli_features/lib/alaveteli_features/constraints.rb b/gems/alaveteli_features/lib/alaveteli_features/constraints.rb
new file mode 100644
index 0000000000..ba091e0aa1
--- /dev/null
+++ b/gems/alaveteli_features/lib/alaveteli_features/constraints.rb
@@ -0,0 +1,15 @@
+module AlaveteliFeatures
+ module Constraints
+ class FeatureConstraint
+ include AlaveteliFeatures::Helpers
+
+ def initialize(feature)
+ @feature = feature
+ end
+
+ def matches?(request)
+ return feature_enabled? @feature
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/gems/alaveteli_features/spec/constraints/feature_constraint_spec.rb b/gems/alaveteli_features/spec/constraints/feature_constraint_spec.rb
new file mode 100644
index 0000000000..00f957a811
--- /dev/null
+++ b/gems/alaveteli_features/spec/constraints/feature_constraint_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe AlaveteliFeatures::Constraints::FeatureConstraint do
+ let(:test_backend) { Flipper.new(Flipper::Adapters::Memory.new) }
+
+ before do
+ AlaveteliFeatures.backend = test_backend
+ end
+
+ describe "#matches?" do
+ it "should return true when a feature is enabled" do
+ constraint = AlaveteliFeatures::Constraints::FeatureConstraint.new(:feature)
+ allow(constraint).to receive(:feature_enabled?).with(:feature).and_return(true)
+ expect(constraint.matches?(nil)).to be true
+ end
+
+ it "should return false when a feature is disabled" do
+ constraint = AlaveteliFeatures::Constraints::FeatureConstraint.new(:feature)
+ allow(constraint).to receive(:feature_enabled?).with(:feature).and_return(false)
+ expect(constraint.matches?(nil)).to be false
+ end
+ end
+end
\ No newline at end of file
From 9268e655fd4348095ca25dd468b5cdd50db3826c Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Thu, 17 Nov 2016 18:07:32 +0000
Subject: [PATCH 074/311] Ruby style fixups in the AlaveteliFeatures gem
---
gems/alaveteli_features/lib/alaveteli_features/constraints.rb | 2 +-
gems/alaveteli_features/lib/alaveteli_features/helpers.rb | 2 +-
gems/alaveteli_features/lib/alaveteli_features/railtie.rb | 2 +-
.../spec/constraints/feature_constraint_spec.rb | 2 +-
gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/gems/alaveteli_features/lib/alaveteli_features/constraints.rb b/gems/alaveteli_features/lib/alaveteli_features/constraints.rb
index ba091e0aa1..177a15d685 100644
--- a/gems/alaveteli_features/lib/alaveteli_features/constraints.rb
+++ b/gems/alaveteli_features/lib/alaveteli_features/constraints.rb
@@ -12,4 +12,4 @@ def matches?(request)
end
end
end
-end
\ No newline at end of file
+end
diff --git a/gems/alaveteli_features/lib/alaveteli_features/helpers.rb b/gems/alaveteli_features/lib/alaveteli_features/helpers.rb
index 6e9f769c7d..cd12a8d6c8 100644
--- a/gems/alaveteli_features/lib/alaveteli_features/helpers.rb
+++ b/gems/alaveteli_features/lib/alaveteli_features/helpers.rb
@@ -4,4 +4,4 @@ def feature_enabled?(feature, *args)
AlaveteliFeatures.backend.enabled?(feature, *args)
end
end
-end
\ No newline at end of file
+end
diff --git a/gems/alaveteli_features/lib/alaveteli_features/railtie.rb b/gems/alaveteli_features/lib/alaveteli_features/railtie.rb
index 138bedadee..7046f1ba00 100644
--- a/gems/alaveteli_features/lib/alaveteli_features/railtie.rb
+++ b/gems/alaveteli_features/lib/alaveteli_features/railtie.rb
@@ -14,4 +14,4 @@ class Railtie < Rails::Railtie
require 'alaveteli_features/generators/alaveteli_features/install/install_generator'
end
end
-end
\ No newline at end of file
+end
diff --git a/gems/alaveteli_features/spec/constraints/feature_constraint_spec.rb b/gems/alaveteli_features/spec/constraints/feature_constraint_spec.rb
index 00f957a811..a0bba6b5f7 100644
--- a/gems/alaveteli_features/spec/constraints/feature_constraint_spec.rb
+++ b/gems/alaveteli_features/spec/constraints/feature_constraint_spec.rb
@@ -20,4 +20,4 @@
expect(constraint.matches?(nil)).to be false
end
end
-end
\ No newline at end of file
+end
diff --git a/gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb b/gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb
index 234de2b96f..b40a15a7fb 100644
--- a/gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb
+++ b/gems/alaveteli_features/spec/helpers/feature_enabled_spec.rb
@@ -59,4 +59,4 @@ def admin?
instance.feature_enabled?(:test_feature, user1)
end
end
-end
\ No newline at end of file
+end
From caabe61dd75ad3336a3a7021bcf928b955f177f7 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Wed, 9 Nov 2016 09:59:28 +0000
Subject: [PATCH 075/311] Add a cancan ability for accessing alaveteli pro
generally
---
app/models/ability.rb | 8 +++++++
spec/models/ability_spec.rb | 43 +++++++++++++++++++++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index d534618358..7597f73fe0 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,5 +1,6 @@
class Ability
include CanCan::Ability
+ include AlaveteliFeatures::Helpers
def initialize(user)
# Define abilities for the passed in user here. For example:
@@ -45,6 +46,13 @@ def initialize(user)
can :read, InfoRequest do |request|
self.class.can_view_with_prominence?(request.prominence, request, user)
end
+
+ # Accessing alaveteli professional
+ if feature_enabled? :alaveteli_pro
+ if user && (user.super? || user.pro?)
+ can :access, :alaveteli_pro
+ end
+ end
end
private
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index aae7edfc32..bac4098c1d 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -215,4 +215,47 @@
end
end
end
+
+ describe "accessing Alaveteli Pro" do
+ subject(:ability) { Ability.new(user) }
+
+ context "when the user is a pro" do
+ let(:user) { FactoryGirl.create(:pro_user) }
+ it "should return true" do
+ with_feature_enabled(:alaveteli_pro) do
+ expect(ability).to be_able_to(:access, :alaveteli_pro)
+ end
+ end
+ end
+
+ context "when the user is an admin" do
+ let(:user) { FactoryGirl.create(:admin_user) }
+
+ it "should return true" do
+ with_feature_enabled(:alaveteli_pro) do
+ expect(ability).to be_able_to(:access, :alaveteli_pro)
+ end
+ end
+ end
+
+ context "when the user is a normal user" do
+ let(:user) { FactoryGirl.create(:user) }
+
+ it "should return false" do
+ with_feature_enabled(:alaveteli_pro) do
+ expect(ability).not_to be_able_to(:access, :alaveteli_pro)
+ end
+ end
+ end
+
+ context "when the user is nil" do
+ let(:user) { nil }
+
+ it "should return false" do
+ with_feature_enabled(:alaveteli_pro) do
+ expect(ability).not_to be_able_to(:access, :alaveteli_pro)
+ end
+ end
+ end
+ end
end
From 05ddb9051fbe6f63163775d48f4306d9b0266eb7 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Wed, 16 Nov 2016 10:39:08 +0000
Subject: [PATCH 076/311] Add some spec helper methods for testing with
features
---
.../lib/alaveteli_features/spec_helpers.rb | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 gems/alaveteli_features/lib/alaveteli_features/spec_helpers.rb
diff --git a/gems/alaveteli_features/lib/alaveteli_features/spec_helpers.rb b/gems/alaveteli_features/lib/alaveteli_features/spec_helpers.rb
new file mode 100644
index 0000000000..63fceac4ee
--- /dev/null
+++ b/gems/alaveteli_features/lib/alaveteli_features/spec_helpers.rb
@@ -0,0 +1,21 @@
+module AlaveteliFeatures
+ module SpecHelpers
+ def with_feature_enabled(feature)
+ allow(AlaveteliFeatures.backend).
+ to receive(:enabled?).with(feature).and_return(true)
+ yield
+ ensure
+ allow(AlaveteliFeatures.backend).
+ to receive(:enabled?).with(feature).and_call_original
+ end
+
+ def with_feature_disabled(feature)
+ allow(AlaveteliFeatures.backend).
+ to receive(:enabled?).with(feature).and_return(false)
+ yield
+ ensure
+ allow(AlaveteliFeatures.backend).
+ to receive(:enabled?).with(feature).and_call_original
+ end
+ end
+end
\ No newline at end of file
From 16abae1b51ab9fad9c81835e34bda00f6d36a364 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Wed, 16 Nov 2016 10:40:13 +0000
Subject: [PATCH 077/311] Include AlaveteliFeatures::SpecHelpers in spec helper
---
spec/spec_helper.rb | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 08b2cdbbe4..aa99d40b29 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -2,6 +2,7 @@
require 'rubygems'
require 'simplecov'
require 'coveralls'
+require "alaveteli_features/spec_helpers"
cov_formats = [Coveralls::SimpleCov::Formatter]
cov_formats << SimpleCov::Formatter::HTMLFormatter if ENV['COVERAGE'] == 'local'
@@ -126,6 +127,9 @@
end
end
+# Helper with_xxx methods for working with feature flags
+include AlaveteliFeatures::SpecHelpers
+
# Use the before create job hook to simulate a race condition with
# another process by creating an acts_as_xapian_job record for the
# same model:
From 36978fef0f99f2ddd6fac4f3b9d2e9f26b4a2bed Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 25 Nov 2016 20:42:41 +0000
Subject: [PATCH 078/311] Only override the named feature in spec helper
Before, checking for other features would raise an error in rspec because they
hadn't been explicitly allowed, now anything other feature will be passed
through to the original method on the backend.
---
.../lib/alaveteli_features/spec_helpers.rb | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/gems/alaveteli_features/lib/alaveteli_features/spec_helpers.rb b/gems/alaveteli_features/lib/alaveteli_features/spec_helpers.rb
index 63fceac4ee..7fb5e65568 100644
--- a/gems/alaveteli_features/lib/alaveteli_features/spec_helpers.rb
+++ b/gems/alaveteli_features/lib/alaveteli_features/spec_helpers.rb
@@ -1,21 +1,17 @@
module AlaveteliFeatures
module SpecHelpers
def with_feature_enabled(feature)
+ allow(AlaveteliFeatures.backend).to receive(:enabled?).and_call_original
allow(AlaveteliFeatures.backend).
to receive(:enabled?).with(feature).and_return(true)
yield
- ensure
- allow(AlaveteliFeatures.backend).
- to receive(:enabled?).with(feature).and_call_original
end
def with_feature_disabled(feature)
+ allow(AlaveteliFeatures.backend).to receive(:enabled?).and_call_original
allow(AlaveteliFeatures.backend).
to receive(:enabled?).with(feature).and_return(false)
yield
- ensure
- allow(AlaveteliFeatures.backend).
- to receive(:enabled?).with(feature).and_call_original
end
end
-end
\ No newline at end of file
+end
From aa725c2a6e2835fee69d66ab0d728fa52a7267e9 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 28 Nov 2016 15:06:07 +0000
Subject: [PATCH 079/311] Annotate MailServerLog model and factory
---
app/models/mail_server_log.rb | 1 +
spec/factories/mail_server_logs.rb | 1 +
2 files changed, 2 insertions(+)
diff --git a/app/models/mail_server_log.rb b/app/models/mail_server_log.rb
index 18d3ddfff9..072abb56fa 100644
--- a/app/models/mail_server_log.rb
+++ b/app/models/mail_server_log.rb
@@ -1,5 +1,6 @@
# -*- encoding : utf-8 -*-
# == Schema Information
+# Schema version: 20161128095350
#
# Table name: mail_server_logs
#
diff --git a/spec/factories/mail_server_logs.rb b/spec/factories/mail_server_logs.rb
index 7642cb85e3..ec3abbff1a 100644
--- a/spec/factories/mail_server_logs.rb
+++ b/spec/factories/mail_server_logs.rb
@@ -1,5 +1,6 @@
# -*- encoding : utf-8 -*-
# == Schema Information
+# Schema version: 20161128095350
#
# Table name: mail_server_logs
#
From fee0ccc1093d677680911fd1ee4c7a493f7a65e0 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 25 Nov 2016 20:42:32 +0000
Subject: [PATCH 080/311] Tidy up formatting in User factory
---
spec/factories/users.rb | 2 ++
1 file changed, 2 insertions(+)
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 34625f35fa..625c05db82 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -43,10 +43,12 @@
hashed_password '6b7cd45a5f35fd83febc0452a799530398bfb6e8' # jonespassword
email_confirmed true
ban_text ""
+
factory :admin_user do
name 'Admin User'
admin_level 'super'
end
+
factory :pro_user do
name 'Pro User'
after(:create) do |user, evaluator|
From fb11609ca3bc0d8373a54ae040ed66f2033370ef Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 28 Nov 2016 18:18:00 +0000
Subject: [PATCH 081/311] Add a duration field to Embargo
---
app/models/embargo.rb | 40 ++++++++++++++++---
.../20161128095350_add_duration_to_embargo.rb | 5 +++
spec/factories/embargos.rb | 2 +
spec/models/embargo_spec.rb | 39 ++++++++++++++++++
4 files changed, 80 insertions(+), 6 deletions(-)
create mode 100644 db/migrate/20161128095350_add_duration_to_embargo.rb
diff --git a/app/models/embargo.rb b/app/models/embargo.rb
index 5f91c1d762..7887e08e40 100644
--- a/app/models/embargo.rb
+++ b/app/models/embargo.rb
@@ -1,16 +1,44 @@
# -*- encoding : utf-8 -*-
# == Schema Information
+# Schema version: 20161128095350
#
-# Table name: embargos
+# Table name: embargoes
#
-# id :integer not null, primary key
-# info_request_id :integer not null
-# publish_at :datetime not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# id :integer not null, primary key
+# info_request_id :integer
+# publish_at :datetime not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# embargo_duration :string(255)
#
class Embargo < ActiveRecord::Base
belongs_to :info_request
validates_presence_of :info_request
+ validates_presence_of :publish_at
+ validates_inclusion_of :embargo_duration,
+ in: lambda { |e| e.allowed_durations },
+ allow_nil: true
+ after_initialize :set_publish_at_from_duration
+
+ DURATIONS = {
+ "3_months" => Proc.new { 3.months },
+ "6_months" => Proc.new { 6.months },
+ "12_months" => Proc.new { 12.months }
+ }.freeze
+
+ def allowed_durations
+ DURATIONS.keys
+ end
+
+ def duration_as_duration
+ DURATIONS[self.embargo_duration].call
+ end
+
+ def set_publish_at_from_duration
+ unless self.publish_at.present? || self.embargo_duration.blank?
+ self.publish_at = Time.zone.today + duration_as_duration
+ end
+ end
+
end
diff --git a/db/migrate/20161128095350_add_duration_to_embargo.rb b/db/migrate/20161128095350_add_duration_to_embargo.rb
new file mode 100644
index 0000000000..6abd2655e4
--- /dev/null
+++ b/db/migrate/20161128095350_add_duration_to_embargo.rb
@@ -0,0 +1,5 @@
+class AddDurationToEmbargo < ActiveRecord::Migration
+ def change
+ add_column :embargoes, :embargo_duration, :string
+ end
+end
diff --git a/spec/factories/embargos.rb b/spec/factories/embargos.rb
index e87b104f52..79e86e1063 100644
--- a/spec/factories/embargos.rb
+++ b/spec/factories/embargos.rb
@@ -8,11 +8,13 @@
# publish_at :datetime not null
# created_at :datetime not null
# updated_at :datetime not null
+# embargo_duration :string(255)
#
FactoryGirl.define do
factory :embargo do
info_request
publish_at Time.now + 3.months
+ embargo_duration "3_months"
end
end
diff --git a/spec/models/embargo_spec.rb b/spec/models/embargo_spec.rb
index f0c44c4069..7d519d6f94 100644
--- a/spec/models/embargo_spec.rb
+++ b/spec/models/embargo_spec.rb
@@ -23,4 +23,43 @@
expect(embargo.publish_at).to be_a(ActiveSupport::TimeWithZone)
end
+ it 'requires a publish_at field' do
+ embargo.publish_at = nil
+ expect(embargo).not_to be_valid
+ end
+
+ it 'has an embargo_duration field' do
+ expect(embargo.embargo_duration).to be_a(String)
+ end
+
+ it 'validates embargo_duration field is in list' do
+ embargo.allowed_durations.each do |duration|
+ embargo.embargo_duration = duration
+ expect(embargo).to be_valid
+ end
+ embargo.embargo_duration = "not_in_list"
+ expect(embargo).not_to be_valid
+ end
+
+ it 'allows embargo_duration to be nil' do
+ embargo.embargo_duration = nil
+ expect(embargo).to be_valid
+ end
+
+ describe 'setting publish_at' do
+ let(:info_request) { FactoryGirl.create(:info_request) }
+
+ it 'sets publish_at from duration during creation' do
+ embargo = Embargo.create(info_request: info_request,
+ embargo_duration: "3_months")
+ expect(embargo.publish_at).to eq Time.zone.today + 3.months
+ end
+
+ it 'doesnt set publish_at from duration if its already set' do
+ embargo = Embargo.create(info_request: info_request,
+ publish_at: Time.zone.today,
+ embargo_duration: "3_months")
+ expect(embargo.publish_at).to eq Time.zone.today
+ end
+ end
end
From d4bee681042bc4c73aea1b95379869acce6d0679 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 25 Nov 2016 20:39:31 +0000
Subject: [PATCH 082/311] Add a draft info request model
---
app/models/draft_info_request.rb | 23 +++++++++++++++
...161116121007_create_draft_info_requests.rb | 13 +++++++++
spec/factories/draft_info_requests.rb | 28 +++++++++++++++++++
spec/models/draft_info_request_spec.rb | 28 +++++++++++++++++++
4 files changed, 92 insertions(+)
create mode 100644 app/models/draft_info_request.rb
create mode 100644 db/migrate/20161116121007_create_draft_info_requests.rb
create mode 100644 spec/factories/draft_info_requests.rb
create mode 100644 spec/models/draft_info_request_spec.rb
diff --git a/app/models/draft_info_request.rb b/app/models/draft_info_request.rb
new file mode 100644
index 0000000000..a46362c0e1
--- /dev/null
+++ b/app/models/draft_info_request.rb
@@ -0,0 +1,23 @@
+# == Schema Information
+# Schema version: 20161128095350
+#
+# Table name: draft_info_requests
+#
+# id :integer not null, primary key
+# title :string(255)
+# user_id :integer
+# public_body_id :integer
+# body :text
+# embargo_duration :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class DraftInfoRequest < ActiveRecord::Base
+ validates_presence_of :user
+
+ belongs_to :user
+ belongs_to :public_body
+
+ strip_attributes
+end
diff --git a/db/migrate/20161116121007_create_draft_info_requests.rb b/db/migrate/20161116121007_create_draft_info_requests.rb
new file mode 100644
index 0000000000..b63ec1d436
--- /dev/null
+++ b/db/migrate/20161116121007_create_draft_info_requests.rb
@@ -0,0 +1,13 @@
+class CreateDraftInfoRequests < ActiveRecord::Migration
+ def change
+ create_table :draft_info_requests do |t|
+ t.string :title
+ t.integer :user_id
+ t.integer :public_body_id
+ t.text :body
+ t.string :embargo_duration
+
+ t.timestamps
+ end
+ end
+end
diff --git a/spec/factories/draft_info_requests.rb b/spec/factories/draft_info_requests.rb
new file mode 100644
index 0000000000..1b5ba939d8
--- /dev/null
+++ b/spec/factories/draft_info_requests.rb
@@ -0,0 +1,28 @@
+# == Schema Information
+# Schema version: 20161128095350
+#
+# Table name: draft_info_requests
+#
+# id :integer not null, primary key
+# title :string(255)
+# user_id :integer
+# public_body_id :integer
+# body :text
+# embargo_duration :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+FactoryGirl.define do
+ factory :draft_info_request do
+ sequence(:title) { |n| "Draft: Example Title #{n}" }
+ public_body
+ user
+ sequence(:body) { |n| "Do you have information about record #{n}?" }
+ embargo_duration "3_months"
+
+ factory :draft_with_no_duration do
+ embargo_duration nil
+ end
+ end
+end
diff --git a/spec/models/draft_info_request_spec.rb b/spec/models/draft_info_request_spec.rb
new file mode 100644
index 0000000000..9ca44cea20
--- /dev/null
+++ b/spec/models/draft_info_request_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe DraftInfoRequest do
+ let(:draft) { FactoryGirl.create(:draft_info_request) }
+
+ it "belongs to a public body" do
+ expect(draft.public_body).to be_a(PublicBody)
+ end
+
+ it "belongs to a user" do
+ expect(draft.user).to be_a(User)
+ end
+
+ it "has a title" do
+ expect(draft.title).to be_a(String)
+ end
+
+ it "has a body" do
+ expect(draft.body).to be_a(String)
+ end
+
+ it "requires a user" do
+ draft_request = DraftInfoRequest.new
+ expect(draft_request.valid?).to be false
+ draft_request.user = FactoryGirl.create(:user)
+ expect(draft_request.valid?).to be true
+ end
+end
From d2400d6e1b714dc325d6eeeb719de3088c432992 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 25 Nov 2016 20:40:30 +0000
Subject: [PATCH 083/311] Add a .from_draft class method to InfoRequest
So that we can create an InfoRequest and the associated OutgoingMessage and
Embargo objects directly from a draft.
---
app/models/info_request.rb | 18 +++++++++++++
spec/models/info_request_spec.rb | 44 ++++++++++++++++++++++++++++++++
2 files changed, 62 insertions(+)
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 06f3d31f11..7d6a41a320 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -948,6 +948,24 @@ def self.create_from_attributes(info_request_atts, outgoing_message_atts, user=n
info_request
end
+ def self.from_draft(draft)
+ info_request = new(:title => draft.title,
+ :user => draft.user,
+ :public_body => draft.public_body)
+ info_request.outgoing_messages.new(:body => draft.body,
+ :status => 'ready',
+ :message_type => 'initial_request',
+ :what_doing => 'normal_sort',
+ :info_request => info_request)
+ if draft.embargo_duration
+ info_request.embargo = Embargo.new(
+ :embargo_duration => draft.embargo_duration,
+ :info_request => info_request
+ )
+ end
+ info_request
+ end
+
def self.hash_from_id(id)
Digest::SHA1.hexdigest(id.to_s + AlaveteliConfiguration::incoming_email_secret)[0,8]
end
diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb
index 0f0aa388af..9472a9dadf 100644
--- a/spec/models/info_request_spec.rb
+++ b/spec/models/info_request_spec.rb
@@ -2749,6 +2749,50 @@ def apply_filters(filters)
end
end
+ describe ".from_draft" do
+ let(:draft) { FactoryGirl.create(:draft_info_request) }
+ let(:info_request) { InfoRequest.from_draft(draft) }
+
+ it "builds an info_request from the draft" do
+ expect(info_request.title).to eq draft.title
+ expect(info_request.public_body).to eq draft.public_body
+ expect(info_request.user).to eq draft.user
+ expect(info_request).not_to be_persisted
+ end
+
+ it "builds an initial outgoing message" do
+ expect(info_request.outgoing_messages.length).to eq 1
+ outgoing_message = info_request.outgoing_messages.first
+ expect(outgoing_message.body).to eq draft.body
+ expect(outgoing_message.info_request).to eq info_request
+ expect(outgoing_message.info_request).not_to be_persisted
+ end
+
+ context "when the draft has a duration" do
+ it "builds an embargo" do
+ expect(info_request.embargo).not_to be nil
+ embargo = info_request.embargo
+ expect(embargo.embargo_duration).to eq draft.embargo_duration
+ expect(embargo.info_request).to eq info_request
+ expect(embargo).not_to be_persisted
+ end
+ end
+
+ context "when the draft doesnt have a duration" do
+ let(:draft_with_no_duration) do
+ FactoryGirl.create(:draft_with_no_duration)
+ end
+
+ let(:request_with_no_embargo) do
+ InfoRequest.from_draft(draft_with_no_duration)
+ end
+
+ it "doesnt build an embargo" do
+ expect(request_with_no_embargo.embargo).to be nil
+ end
+ end
+ end
+
def email_and_raw_email(opts = {})
raw_email = opts[:raw_email] || <<-EOF.strip_heredoc
From: EMAIL_FROM
From a2d959ce06d92827d306bf2b6bed14a8f07eda81 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Fri, 25 Nov 2016 20:41:58 +0000
Subject: [PATCH 084/311] Add a has_many :draft_info_requests to the User model
---
app/models/user.rb | 3 +++
1 file changed, 3 insertions(+)
diff --git a/app/models/user.rb b/app/models/user.rb
index c7e1de0354..421b212a58 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -45,6 +45,9 @@ class User < ActiveRecord::Base
has_many :info_requests,
:order => 'created_at desc',
:dependent => :destroy
+ has_many :draft_info_requests,
+ :order => 'created_at desc',
+ :dependent => :destroy
has_many :user_info_request_sent_alerts,
:dependent => :destroy
has_many :post_redirects,
From c50f3e228a28215518ac701434edecf39d9293e5 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 28 Nov 2016 17:06:06 +0000
Subject: [PATCH 085/311] Add a DURATION_LABELS constant to Embargo
For use in printing the list of duration options in views
---
app/models/embargo.rb | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/app/models/embargo.rb b/app/models/embargo.rb
index 7887e08e40..dfc493c8ad 100644
--- a/app/models/embargo.rb
+++ b/app/models/embargo.rb
@@ -27,6 +27,12 @@ class Embargo < ActiveRecord::Base
"12_months" => Proc.new { 12.months }
}.freeze
+ DURATION_LABELS = {
+ "3_months" => _("3 Months"),
+ "6_months" => _("6 Months"),
+ "12_months" => _("12 Months")
+ }.freeze
+
def allowed_durations
DURATIONS.keys
end
@@ -35,6 +41,10 @@ def duration_as_duration
DURATIONS[self.embargo_duration].call
end
+ def duration_label
+ DURATION_LABELS[self.embargo_duration]
+ end
+
def set_publish_at_from_duration
unless self.publish_at.present? || self.embargo_duration.blank?
self.publish_at = Time.zone.today + duration_as_duration
From 096ecaca4bf3d11e889e156f557f03f495e72d39 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 28 Nov 2016 17:06:45 +0000
Subject: [PATCH 086/311] Add routes for info requests and drafts
---
config/routes.rb | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/config/routes.rb b/config/routes.rb
index c50d6ddd34..cc1030945e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -597,9 +597,11 @@
#### Alaveteli Pro
constraints FeatureConstraint.new(:alaveteli_pro) do
namespace :alaveteli_pro do
- match '/' => 'dashboard#index',
- :as => 'dashboard',
- :via => :get
+ match '/' => 'dashboard#index', :as => 'dashboard', :via => :get
+ resources :draft_info_requests, :only => [:create, :update]
+ resources :info_requests, :only => [:new, :create] do
+ get :preview, on: :new # /info_request/new/preview
+ end
end
end
####
From b7e35fda30a63bb8b733f0f686450b5734c561e1 Mon Sep 17 00:00:00 2001
From: Steven Day
Date: Mon, 28 Nov 2016 17:31:57 +0000
Subject: [PATCH 087/311] Add pages for writing, previewing and sending
InfoRequests
---
.../draft_info_requests_controller.rb | 46 ++++
.../alaveteli_pro/info_requests_controller.rb | 96 +++++++++
.../alaveteli_pro/info_requests_helper.rb | 7 +
.../info_requests/_embargo_info.html | 9 +
.../alaveteli_pro/info_requests/new.html.erb | 78 +++++++
.../info_requests/preview.html.erb | 42 ++++
.../info_requests_controller_spec.rb | 40 ++++
spec/integration/alaveteli_dsl.rb | 8 +
.../alaveteli_pro/request_creation_spec.rb | 197 ++++++++++++++++++
9 files changed, 523 insertions(+)
create mode 100644 app/controllers/alaveteli_pro/draft_info_requests_controller.rb
create mode 100644 app/controllers/alaveteli_pro/info_requests_controller.rb
create mode 100644 app/helpers/alaveteli_pro/info_requests_helper.rb
create mode 100644 app/views/alaveteli_pro/info_requests/_embargo_info.html
create mode 100644 app/views/alaveteli_pro/info_requests/new.html.erb
create mode 100644 app/views/alaveteli_pro/info_requests/preview.html.erb
create mode 100644 spec/controllers/alaveteli_pro/info_requests_controller_spec.rb
create mode 100644 spec/integration/alaveteli_pro/request_creation_spec.rb
diff --git a/app/controllers/alaveteli_pro/draft_info_requests_controller.rb b/app/controllers/alaveteli_pro/draft_info_requests_controller.rb
new file mode 100644
index 0000000000..fbfa870615
--- /dev/null
+++ b/app/controllers/alaveteli_pro/draft_info_requests_controller.rb
@@ -0,0 +1,46 @@
+# -*- encoding : utf-8 -*-
+# app/controllers/alaveteli_pro/draft_info_requests_controller.rb
+# Controller for draft info requests
+#
+# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved.
+# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
+
+class AlaveteliPro::DraftInfoRequestsController < AlaveteliPro::BaseController
+ def create
+ @draft = current_user.draft_info_requests.create(draft_params)
+ redirect_after_create_or_update
+ end
+
+ def update
+ @draft = current_user.draft_info_requests.find(params[:id])
+ @draft.update_attributes(draft_params)
+ redirect_after_create_or_update
+ end
+
+ private
+
+ def redirect_after_create_or_update
+ if params[:preview]
+ redirect_to preview_new_alaveteli_pro_info_request_path(draft_id: @draft.id)
+ else
+ redirect_to new_alaveteli_pro_info_request_path(draft_id: @draft.id),
+ notice: _("Your draft has been saved!")
+ end
+ end
+
+ def draft_params
+ info_request_params.merge(outgoing_message_params.merge(embargo_params))
+ end
+
+ def info_request_params
+ params.require(:info_request).permit(:title, :public_body_id)
+ end
+
+ def outgoing_message_params
+ params.require(:outgoing_message).permit(:body)
+ end
+
+ def embargo_params
+ params.require(:embargo).permit(:embargo_duration)
+ end
+end
diff --git a/app/controllers/alaveteli_pro/info_requests_controller.rb b/app/controllers/alaveteli_pro/info_requests_controller.rb
new file mode 100644
index 0000000000..91109c3776
--- /dev/null
+++ b/app/controllers/alaveteli_pro/info_requests_controller.rb
@@ -0,0 +1,96 @@
+# -*- encoding : utf-8 -*-
+# app/controllers/alaveteli_pro/info_requests_controller.rb
+# Controller for info requests
+#
+# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved.
+# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
+
+class AlaveteliPro::InfoRequestsController < AlaveteliPro::BaseController
+ before_filter :set_draft
+ before_filter :load_data_from_draft, only: [:preview, :create]
+
+ def new
+ if @draft_info_request
+ load_data_from_draft
+ else
+ create_initial_objects
+ end
+ end
+
+ def preview
+ if all_models_valid?
+ render "preview"
+ else
+ show_errors
+ end
+ end
+
+ def create
+ if all_models_valid?
+ @info_request.save # Saves @outgoing_message too
+ @embargo.save if @embargo.present?
+ send_initial_message(@outgoing_message)
+ destroy_draft
+ redirect_to show_request_path(url_title: @info_request.url_title)
+ else
+ show_errors
+ end
+ end
+
+ private
+
+ def show_errors
+ # There'll be a duplicate error if the outgoing_message is invalid, so
+ # delete it
+ @info_request.errors.delete(:outgoing_messages)
+ render "new"
+ end
+
+
+ def all_models_valid?
+ @info_request.valid? && @outgoing_message.valid? && \
+ (@embargo.nil? || @embargo.present? && @embargo.valid?)
+ end
+
+ def set_draft
+ if params[:draft_id]
+ @draft_info_request = current_user.draft_info_requests.find(
+ params[:draft_id]
+ )
+ end
+ end
+
+ def load_data_from_draft
+ @info_request = InfoRequest.from_draft(@draft_info_request)
+ @outgoing_message = @info_request.outgoing_messages.first
+ @embargo = @info_request.embargo
+ end
+
+ def create_initial_objects
+ @draft_info_request = DraftInfoRequest.new
+ @info_request = InfoRequest.new
+ @outgoing_message = OutgoingMessage.new(info_request: @info_request)
+ # TODO: set duration based on current user's account settings
+ @embargo = Embargo.new(info_request: @info_request)
+ end
+
+ def destroy_draft
+ if params[:draft_id]
+ current_user.draft_info_requests.destroy(params[:draft_id])
+ end
+ end
+
+ def send_initial_message(outgoing_message)
+ if outgoing_message.sendable?
+ mail_message = OutgoingMailer.initial_request(
+ outgoing_message.info_request,
+ outgoing_message
+ ).deliver
+
+ outgoing_message.record_email_delivery(
+ mail_message.to_addrs.join(', '),
+ mail_message.message_id
+ )
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/helpers/alaveteli_pro/info_requests_helper.rb b/app/helpers/alaveteli_pro/info_requests_helper.rb
new file mode 100644
index 0000000000..4040797902
--- /dev/null
+++ b/app/helpers/alaveteli_pro/info_requests_helper.rb
@@ -0,0 +1,7 @@
+# -*- encoding : utf-8 -*-
+module AlaveteliPro::InfoRequestsHelper
+ def publish_at_options
+ options = { _("Publish immediately") => '' }
+ options.merge(Embargo::DURATION_LABELS.invert)
+ end
+end
diff --git a/app/views/alaveteli_pro/info_requests/_embargo_info.html b/app/views/alaveteli_pro/info_requests/_embargo_info.html
new file mode 100644
index 0000000000..f5c8f72fa8
--- /dev/null
+++ b/app/views/alaveteli_pro/info_requests/_embargo_info.html
@@ -0,0 +1,9 @@
+
Embargo
+<% if @embargo && @embargo.publish_at %>
+
+ <%= _("This request will be embargoed until {{embargo_publish_at}}",
+ embargo_publish_at: @embargo.publish_at.to_date) %>
+
+<% else %>
+
<%= _("This request will be published immediately") %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/alaveteli_pro/info_requests/new.html.erb b/app/views/alaveteli_pro/info_requests/new.html.erb
new file mode 100644
index 0000000000..ebea57d476
--- /dev/null
+++ b/app/views/alaveteli_pro/info_requests/new.html.erb
@@ -0,0 +1,78 @@
+<% @title = _("Make an {{law_used_short}} request",
+ :law_used_short => h(@info_request.law_used_human(:short))) %>
+
+<%= form_tag([:alaveteli_pro, @draft_info_request],
+ method: @draft_info_request.id ? :put : :post,
+ html: { id: 'write_form', :class => 'new_info_request' }) do %>
+
+ <% fields_for @info_request do |f| %>
+
+
+ <%= _("Note: Because we're testing, requests are being sent to {{email}} rather than to the actual authority.", :email => AlaveteliConfiguration::override_all_public_body_request_emails) %>
+
\ No newline at end of file
diff --git a/spec/controllers/alaveteli_pro/info_requests_controller_spec.rb b/spec/controllers/alaveteli_pro/info_requests_controller_spec.rb
new file mode 100644
index 0000000000..923a843aa5
--- /dev/null
+++ b/spec/controllers/alaveteli_pro/info_requests_controller_spec.rb
@@ -0,0 +1,40 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe AlaveteliPro::InfoRequestsController do
+ let(:pro_user) { FactoryGirl.create(:pro_user) }
+
+ describe "#preview" do
+ let(:draft) do
+ FactoryGirl.create(:draft_info_request, body: nil, user: pro_user)
+ end
+
+ context "when there are errors on the outgoing message" do
+ it "removes duplicate errors from the info_request" do
+ session[:user_id] = pro_user.id
+ with_feature_enabled(:alaveteli_pro) do
+ post :preview, draft_id: draft
+ expect(assigns[:info_request].errors[:outgoing_messages]).to be_empty
+ expect(assigns[:outgoing_message].errors).not_to be_empty
+ end
+ end
+ end
+ end
+
+ describe "#create" do
+ let(:draft) do
+ FactoryGirl.create(:draft_info_request, body: nil, user: pro_user)
+ end
+
+ context "when there are errors on the outgoing message" do
+ it "removes duplicate errors from the info_request" do
+ session[:user_id] = pro_user.id
+ with_feature_enabled(:alaveteli_pro) do
+ post :create, draft_id: draft
+ expect(assigns[:info_request].errors[:outgoing_messages]).to be_empty
+ expect(assigns[:outgoing_message].errors).not_to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/integration/alaveteli_dsl.rb b/spec/integration/alaveteli_dsl.rb
index b8ff483c85..64ae8b0856 100644
--- a/spec/integration/alaveteli_dsl.rb
+++ b/spec/integration/alaveteli_dsl.rb
@@ -45,6 +45,14 @@ def alaveteli_session(session_id)
end
end
+def using_pro_session(session_id)
+ with_feature_enabled(:alaveteli_pro) do
+ using_session(session_id) do
+ yield
+ end
+ end
+end
+
def login(user)
u = user.is_a?(User) ? user : users(user)
alaveteli_session(u.id) do
diff --git a/spec/integration/alaveteli_pro/request_creation_spec.rb b/spec/integration/alaveteli_pro/request_creation_spec.rb
new file mode 100644
index 0000000000..ca2fdd3778
--- /dev/null
+++ b/spec/integration/alaveteli_pro/request_creation_spec.rb
@@ -0,0 +1,197 @@
+# -*- encoding : utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+require File.expand_path(File.dirname(__FILE__) + '/../alaveteli_dsl')
+
+describe "creating requests in alaveteli_pro" do
+ context "when writing a new request from scratch" do
+ let!(:public_body) { FactoryGirl.create(:public_body) }
+ let!(:pro_user) { FactoryGirl.create(:pro_user) }
+ let!(:pro_user_session) { login(pro_user) }
+
+ it "allows us to save a draft" do
+ using_pro_session(pro_user_session) do
+ # New request form
+ visit new_alaveteli_pro_info_request_path
+ expect(page).to have_content "Make a request"
+ select public_body.name, from: "To:"
+ fill_in "Summary:", with: "Does the pro request form work?"
+ fill_in "Your request:", with: "A very short letter."
+ select "3 Months", from: "Set embargo:"
+ click_button "Save draft"
+
+ # Redirected back to new request form
+ drafts = DraftInfoRequest.where(title: "Does the pro request form work?")
+ expect(drafts).to exist
+ draft = drafts.first
+ expect(draft.body).to eq "A very short letter."
+ expect(draft.embargo_duration).to eq "3_months"
+
+ expect(page).to have_content("Your draft has been saved!")
+ expect(page).to have_content("This request will be embargoed " \
+ "until #{Time.zone.today + 3.months}")
+
+ # The page should pre-fill the form with data from the draft
+ expect(page).to have_select("To:", selected: public_body.name)
+ expect(page).to have_field("Summary:",
+ with: "Does the pro request form work?")
+ expect(page).to have_field("Your request:",
+ with: "A very short letter.")
+ expect(page).to have_select("Set embargo:", selected: "3 Months")
+ end
+ end
+
+ it "allows us to preview the request" do
+ using_pro_session(pro_user_session) do
+ # New request form
+ visit new_alaveteli_pro_info_request_path
+ expect(page).to have_content "Make a request"
+ select public_body.name, from: "To:"
+ fill_in "Summary:", with: "Does the pro request form work?"
+ fill_in "Your request:", with: "A very short letter."
+ select "3 Months", from: "Set embargo:"
+ click_button "Preview and send request"
+
+ # Preview page
+ drafts = DraftInfoRequest.where(title: "Does the pro request form work?")
+ expect(drafts).to exist
+
+ expect(page).to have_content("Preview your request")
+ # The fact there's a draft should be hidden from the user
+ expect(page).not_to have_content("Your draft has been saved!")
+
+ expect(page).to have_content("To #{public_body.name}")
+ expect(page).to have_content("Subject Does the pro request form " \
+ "work?")
+ expect(page).to have_content("A very short letter.")
+ expect(page).to have_content("This request will be embargoed " \
+ "until #{Time.zone.today + 3.months}")
+ end
+ end
+
+ it "allows us to send the request" do
+ using_pro_session(pro_user_session) do
+ # New request form
+ visit new_alaveteli_pro_info_request_path
+ expect(page).to have_content "Make a request"
+ select public_body.name, from: "To:"
+ fill_in "Summary:", with: "Does the pro request form work?"
+ fill_in "Your request:", with: "A very short letter."
+ select "3 Months", from: "Set embargo:"
+ click_button "Preview and send request"
+
+ # Preview page
+ click_button "Send request"
+
+ # Request page
+ expect(page).to have_selector("h1", text: "Does the pro request form work?")
+
+ drafts = DraftInfoRequest.where(title: "Does the pro request form work?")
+ expect(drafts).not_to exist
+
+ info_requests = InfoRequest.where(title: "Does the pro request form work?")
+ expect(info_requests).to exist
+
+ info_request = info_requests.first
+
+ embargo = info_request.embargo
+ expect(embargo).not_to be_nil
+ expect(embargo.publish_at).to eq Time.zone.today + 3.months
+
+ expect(info_request.outgoing_messages.length).to eq 1
+ expect(info_request.outgoing_messages.first.body).to eq "A very short letter."
+
+ deliveries = ActionMailer::Base.deliveries
+ expect(deliveries.size).to eq(1)
+ mail = deliveries[0]
+ expect(mail.body).to match(/A very short letter\./)
+ expect(mail.subject).to match(/Freedom of Information request - Does the pro request form work\?/)
+ expect(mail.to).to eq([public_body.request_email])
+ end
+ end
+
+ it "allow us to edit a request after previewing" do
+ using_pro_session(pro_user_session) do
+ # New request form
+ visit new_alaveteli_pro_info_request_path
+ expect(page).to have_content "Make a request"
+ select public_body.name, from: "To:"
+ fill_in "Summary:", with: "Does the pro request form work?"
+ fill_in "Your request:", with: "A very short letter."
+ select "3 Months", from: "Set embargo:"
+ click_button "Preview and send request"
+
+ # Preview page
+ click_link "Edit your request"
+
+ # New request form again
+ # The page should pre-fill the form with data from the draft
+ expect(page).to have_select("To:", selected: public_body.name)
+ expect(page).to have_field("Summary:",
+ with: "Does the pro request form work?")
+ expect(page).to have_field("Your request:",
+ with: "A very short letter.")
+ expect(page).to have_select("Set embargo:", selected: "3 Months")
+
+ fill_in "Your request:", with: "A very short letter, edited."
+ click_button "Save draft"
+
+ # Redirected back to new request form
+ drafts = DraftInfoRequest.where(title: "Does the pro request form work?")
+ expect(drafts).to exist
+ draft = drafts.first
+ expect(draft.body).to eq "A very short letter, edited."
+ expect(draft.embargo_duration).to eq "3_months"
+
+ expect(page).to have_content("Your draft has been saved!")
+
+ click_button "Preview and send request"
+
+ # Preview page again
+ expect(page).to have_content("Preview your request")
+ # The fact there's a draft should be hidden from the user
+ expect(page).not_to have_content("Your draft has been saved!")
+
+ expect(page).to have_content("To #{public_body.name}")
+ expect(page).to have_content("Subject Does the pro request form " \
+ "work?")
+ expect(page).to have_content("A very short letter, edited.")
+ expect(page).to have_content("This request will be embargoed " \
+ "until #{Time.zone.today + 3.months}")
+ end
+ end
+
+ it "shows errors if we leave fields blank" do
+ using_pro_session(pro_user_session) do
+ # New request form
+ visit new_alaveteli_pro_info_request_path
+ expect(page).to have_content "Make a request"
+ select public_body.name, from: "To:"
+ click_button "Preview and send request"
+
+ # New request form with errors
+ expect(page).to have_content "Please enter a summary of your " \
+ "request"
+ expect(page).to have_content 'Please sign at the bottom with ' \
+ 'your name, or alter the "Yours ' \
+ 'faithfully," signature'
+ end
+ end
+
+ it "saves the draft even if we leave fields blank" do
+ using_pro_session(pro_user_session) do
+ # New request form
+ visit new_alaveteli_pro_info_request_path
+ expect(page).to have_content "Make a request"
+ select public_body.name, from: "To:"
+ click_button "Save draft"
+
+ # New request form with errors
+ expect(page).not_to have_content "Please enter a summary of your " \
+ "request"
+ expect(page).not_to have_content 'Please sign at the bottom with ' \
+ 'your name, or alter the "Yours ' \
+ 'faithfully," signature'
+ end
+ end
+ end
+end
From 66d0c9d36b3fc4103212a8564ada5888eb5fde4b Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 2 Dec 2016 12:25:57 +0000
Subject: [PATCH 088/311] Fix for bug introduced in
773d5776d1085928d81c2ef3700f8a04ff1df74a.
---
spec/integration/track_alerts_spec.rb | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/spec/integration/track_alerts_spec.rb b/spec/integration/track_alerts_spec.rb
index 89b2a1f4ca..a8e524e64a 100644
--- a/spec/integration/track_alerts_spec.rb
+++ b/spec/integration/track_alerts_spec.rb
@@ -4,6 +4,14 @@
describe "When sending track alerts" do
+ before do
+ # TODO: required to make sure xapian index can find files for raw emails
+ # associated with fixtures - can be removed when fixtures no longer
+ # automatically loaded for all specs
+ load_raw_emails_data
+ get_fixtures_xapian_index
+ end
+
it "should send alerts" do
info_request = FactoryGirl.create(:info_request)
From 4207084e2345386af428670c84460e1988d2453f Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Fri, 25 Nov 2016 15:38:00 +0000
Subject: [PATCH 089/311] Add InfoRequestsController#index
Basic view templates, styles and idea of request phases.
---
.../alaveteli_pro/_dashboard_layout.scss | 123 ++++++++++++++++++
.../alaveteli_pro/_dashboard_style.scss | 90 +++++++++++++
.../alaveteli_pro/_requests_layout.scss | 79 +++++++++++
.../alaveteli_pro/_requests_style.scss | 26 ++++
app/assets/stylesheets/responsive/all.scss | 6 +
.../alaveteli_pro/info_requests_controller.rb | 4 +
app/models/info_request.rb | 36 +++--
app/models/info_request/state.rb | 43 ++++++
.../state/awaiting_response_query.rb | 15 +++
.../state/clarification_needed_query.rb | 15 +++
.../info_request/state/complete_query.rb | 19 +++
app/models/info_request/state/other_query.rb | 21 +++
.../state/response_received_query.rb | 14 ++
app/models/info_request_event.rb | 2 +-
app/views/admin_request/edit.html.erb | 2 +-
.../dashboard/_projects.html.erb | 30 +++++
.../info_requests/_info_request.html.erb | 41 ++++++
.../info_requests/index.html.erb | 48 +++++++
config/routes.rb | 2 +-
.../info_requests_controller_spec.rb | 19 +++
spec/factories/info_requests.rb | 12 ++
.../state/awaiting_response_query_spec.rb | 21 +++
.../state/clarification_needed_query_spec.rb | 22 ++++
.../info_request/state/complete_query_spec.rb | 22 ++++
.../info_request/state/other_query_spec.rb | 22 ++++
.../state/response_received_query_spec.rb | 21 +++
spec/models/info_request/state_spec.rb | 27 ++++
27 files changed, 758 insertions(+), 24 deletions(-)
create mode 100644 app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_layout.scss
create mode 100644 app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_style.scss
create mode 100644 app/assets/stylesheets/responsive/alaveteli_pro/_requests_layout.scss
create mode 100644 app/assets/stylesheets/responsive/alaveteli_pro/_requests_style.scss
create mode 100644 app/models/info_request/state.rb
create mode 100644 app/models/info_request/state/awaiting_response_query.rb
create mode 100644 app/models/info_request/state/clarification_needed_query.rb
create mode 100644 app/models/info_request/state/complete_query.rb
create mode 100644 app/models/info_request/state/other_query.rb
create mode 100644 app/models/info_request/state/response_received_query.rb
create mode 100644 app/views/alaveteli_pro/dashboard/_projects.html.erb
create mode 100644 app/views/alaveteli_pro/info_requests/_info_request.html.erb
create mode 100644 app/views/alaveteli_pro/info_requests/index.html.erb
create mode 100644 spec/models/info_request/state/awaiting_response_query_spec.rb
create mode 100644 spec/models/info_request/state/clarification_needed_query_spec.rb
create mode 100644 spec/models/info_request/state/complete_query_spec.rb
create mode 100644 spec/models/info_request/state/other_query_spec.rb
create mode 100644 spec/models/info_request/state/response_received_query_spec.rb
create mode 100644 spec/models/info_request/state_spec.rb
diff --git a/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_layout.scss b/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_layout.scss
new file mode 100644
index 0000000000..fb6cde49a3
--- /dev/null
+++ b/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_layout.scss
@@ -0,0 +1,123 @@
+/* Layout for the dashboard */
+
+.dashboard-header,
+.dashboard-body {
+ @include flexbox;
+ padding: 1em;
+}
+
+.dashboard-left-column {
+ width: 30%;
+ padding-left: 2em;
+ padding-right: 1.5em;
+}
+
+.dashboard-right-column {
+ width: 70%;
+ padding-right: 2em;
+}
+
+.dashboard-header {
+ margin-top: 2em;
+ padding-left: 2em;
+ h1 {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+ display:block;
+ width:100%;
+ font-weight: bold;
+ }
+}
+
+.dashboard-profile__image {
+ float:left;
+ margin-right: 1em;
+ border-radius: 5px;
+}
+
+.dashboard-nav {
+ padding: 0;
+ margin: 0;
+ @include clearfix;
+}
+
+.dashboard-nav__item {
+ float: left;
+ padding-bottom: 0.5em;
+ padding-right: 1em;
+}
+
+.dashboard-nav__item--right {
+ float: right;
+}
+
+h1 {
+ margin-left: 1em;
+ margin-bottom: 1em;
+}
+
+.dashboard-folders,
+.dashboard-sub-folders {
+ margin: 0;
+ padding: 0;
+}
+
+.dashboard-sub-folders {
+ margin: 1em 0 2em 0;
+}
+
+.dashboard-folder {
+ margin-bottom: 0.5em;
+ font-size: 0.9em;
+}
+
+.dashboard-folder a,
+.dashboard-folder a:hover,
+.dashboard-folder--disabled .fake-link {
+ display: block;
+ padding: 0.5em 1em 0.5em 1em;
+ width: 100%;
+}
+
+.dashboard-folder--selected {
+ margin-bottom: 0;
+}
+
+.dashboard-folder__count {
+ float: right;
+}
+
+.dashboard-todo {
+ margin-bottom: 3em;
+}
+
+.dashboard-todo__list {
+ margin: 0;
+ padding: 0;
+}
+
+.dashboard-todo__title,
+.dashboard-activity__title {
+ padding-bottom: 1em;
+ margin-bottom: 0;
+ margin-top: 0;
+}
+
+.dashboard-todo__list__item,
+.dashboard-activity__item {
+ padding: 1em 0 1em 0;
+}
+
+.dashboard-activity__item {
+ @include flexbox;
+}
+
+.dashboard-activity__item__time {
+ flex: 0 0 25%;
+ padding-right: 2em;
+}
+
+.dashboard-activity__item__dashboard {
+ flex: 0 0 75%;
+ padding-left: 2em;
+}
diff --git a/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_style.scss b/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_style.scss
new file mode 100644
index 0000000000..6e80a995d7
--- /dev/null
+++ b/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_style.scss
@@ -0,0 +1,90 @@
+/* Style for the dashboard */
+
+.dashboard {
+ background-color: white;
+}
+
+.dashboard-header {
+ background-color: #e6e4df;
+ border: 1px solid #e1dfda;
+}
+
+.dashboard-nav {
+ list-style-type: none;
+ list-style: none;
+ border-bottom: 1px solid #DDDDDD;
+}
+
+.dashboard-nav__item {
+ border-bottom: 1px solid #DDDDDD;
+ // To overlap the bottom border with that of the containing list, making it
+ // appear continuous
+ margin-bottom: -2px;
+ a,
+ a:hover {
+ font-weight: normal;
+ color: white;
+ text-decoration: none;
+ }
+}
+
+.dashboard-nav__item:hover,
+.dashboard-nav__item--highlighted {
+ border-bottom: 2px solid orange;
+ cursor: pointer;
+}
+
+.dashboard-folders,
+.dashboard-sub-folders {
+ list-style: none;
+ list-style-type: none;
+}
+
+.dashboard-folder a,
+.dashboard-folder a:hover,
+.dashboard-folder--disabled .fake-link {
+ color: black;
+ border-bottom: 1px solid #ccc;
+ text-decoration: none;
+}
+.dashboard-folder a:hover {
+ color: black;
+ border-bottom-color: orange;
+}
+
+.dashboard-folder--disabled {
+ color: #999;
+}
+
+.dashboard-folder--top-level {
+ background-color: #E9E9E9;
+ margin-bottom: 1em;
+ border-bottom: transparent;
+}
+
+.dashboard-folder--selected {
+ font-weight: bold;
+ font-size: 1em;
+}
+
+.dashboard-todo__list {
+ list-style: none;
+ list-style-type: none;
+}
+
+.dashboard-activity__title,
+.dashboard-todo__title {
+ font-size: 1em;
+ font-weight: bold;
+}
+
+.dashboard-todo__title,
+.dashboard-todo__list__item,
+.dashboard-activity__title,
+.dashboard-activity__item {
+ border-bottom: 1px solid #ccc;
+}
+
+.dashboard-activity__item__time {
+ color: #999;
+}
diff --git a/app/assets/stylesheets/responsive/alaveteli_pro/_requests_layout.scss b/app/assets/stylesheets/responsive/alaveteli_pro/_requests_layout.scss
new file mode 100644
index 0000000000..d63054ccad
--- /dev/null
+++ b/app/assets/stylesheets/responsive/alaveteli_pro/_requests_layout.scss
@@ -0,0 +1,79 @@
+/* Layout for the requests page */
+
+.requests__folder-title {
+ h2 {
+ margin-top: 0;
+ }
+}
+.requests__header {
+ @include flexbox;
+ padding: 0.5em;
+}
+
+.requests__search,
+.requests__sort {
+ @include flex(0 0 33%);
+ margin-right: 1em;
+
+ input, select, label {
+ display: inline-block;
+ margin-bottom: 0;
+ }
+ select {
+ max-width: 60%;
+ }
+}
+
+.requests__list {
+ margin-top: 0.5em;
+}
+
+.request {
+ padding-top: 1em;
+ padding-bottom: 1em;
+ margin-bottom: 1em;
+}
+
+.request__title {
+ margin-top: 0;
+}
+
+.request__meta {
+ @include flexbox;
+}
+
+.request__recipient,
+.request__created,
+.request__updated,
+.request__status,
+.request__actions {
+ margin-right: 1em;
+}
+
+// TODO - the flex() rules for these add up to way less than 100%, yet the
+// elements still overflow their container. Not sure why. I've hacked it by
+// reducing the width of the select in the last element and floating it right
+.request__recipient,
+.request__status {
+ @include flex(0 0 15%);
+}
+
+.request__created,
+.request__updated {
+ @include flex(0 0 15%);
+}
+
+.request__actions {
+ @include flex(0 0 31%); // No idea why this has to be 31% in particular
+ @include clearfix;
+ margin-right: 0;
+ .actions {
+ max-width: 90%;
+ float: right;
+ }
+
+}
+
+.request__meta__label {
+ display: block;
+}
diff --git a/app/assets/stylesheets/responsive/alaveteli_pro/_requests_style.scss b/app/assets/stylesheets/responsive/alaveteli_pro/_requests_style.scss
new file mode 100644
index 0000000000..fd8833898a
--- /dev/null
+++ b/app/assets/stylesheets/responsive/alaveteli_pro/_requests_style.scss
@@ -0,0 +1,26 @@
+/* Style for the requests page */
+
+.requests__header {
+ background-color: #f3f1eb;
+}
+
+.request {
+ border-bottom: 1px solid #ccc;
+}
+
+.request__title {
+ font-size: 1.25em;
+}
+
+.request__recipient,
+.request__created,
+.request__updated,
+.request__status,
+.request__actions {
+ font-size: 0.9em;
+}
+
+.request__meta__label {
+ font-size: 0.8em;
+ color: #999;
+}
diff --git a/app/assets/stylesheets/responsive/all.scss b/app/assets/stylesheets/responsive/all.scss
index 79d3880e47..5280a24f8a 100644
--- a/app/assets/stylesheets/responsive/all.scss
+++ b/app/assets/stylesheets/responsive/all.scss
@@ -72,4 +72,10 @@
@import "_time_series";
+@import "alaveteli_pro/_dashboard_layout";
+@import "alaveteli_pro/_dashboard_style";
+
+@import "alaveteli_pro/_requests_layout";
+@import "alaveteli_pro/_requests_style";
+
@import "custom";
diff --git a/app/controllers/alaveteli_pro/info_requests_controller.rb b/app/controllers/alaveteli_pro/info_requests_controller.rb
index 91109c3776..dc0244a581 100644
--- a/app/controllers/alaveteli_pro/info_requests_controller.rb
+++ b/app/controllers/alaveteli_pro/info_requests_controller.rb
@@ -9,6 +9,10 @@ class AlaveteliPro::InfoRequestsController < AlaveteliPro::BaseController
before_filter :set_draft
before_filter :load_data_from_draft, only: [:preview, :create]
+ def index
+ @info_requests = current_user.info_requests
+ end
+
def new
if @draft_info_request
load_data_from_draft
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 7d6a41a320..c2ed61e6ee 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -87,6 +87,13 @@ class InfoRequest < ActiveRecord::Base
scope :embargoed, Prominence::EmbargoedQuery.new
scope :not_embargoed, Prominence::NotEmbargoedQuery.new
+
+ scope :awaiting_response, State::AwaitingResponseQuery.new
+ scope :response_received, State::ResponseReceivedQuery.new
+ scope :clarification_needed, State::ClarificationNeededQuery.new
+ scope :complete, State::CompleteQuery.new
+ scope :other, State::OtherQuery.new
+
def self.visible
warn %q([DEPRECATION] InfoRequest#visible will be removed in
0.27. It has been replaced by InfoRequest#is_public).squish
@@ -131,26 +138,9 @@ def self.visible
before_validation :compute_idhash
def self.enumerate_states
- states = [
- 'waiting_response',
- 'waiting_clarification',
- 'gone_postal',
- 'not_held',
- 'rejected', # this is called 'refused' in UK FOI law and the user interface, but 'rejected' internally for historic reasons
- 'successful',
- 'partially_successful',
- 'internal_review',
- 'error_message',
- 'requires_admin',
- 'user_withdrawn',
- 'attention_requested',
- 'vexatious',
- 'not_foi'
- ]
- if @@custom_states_loaded
- states += InfoRequest.theme_extra_states
- end
- states
+ warn %q([DEPRECATION] InfoRequest.enumerate_states will be removed in
+ 0.28. It has been replaced by InfoRequest::States#all).squish
+ State.all
end
# Subset of states accepted via the API
@@ -191,7 +181,7 @@ def prominence(opts = {})
end
def must_be_valid_state
- unless InfoRequest.enumerate_states.include?(described_state)
+ unless State.all.include?(described_state)
errors.add(:described_state, "is not a valid state")
end
end
@@ -253,6 +243,10 @@ def user_json_for_api
rescue MissingSourceFile, NameError
end
+ def self.custom_states_loaded
+ @@custom_states_loaded
+ end
+
OLD_AGE_IN_DAYS = 21.days
# If the URL name has changed, then all request: queries will break unless
diff --git a/app/models/info_request/state.rb b/app/models/info_request/state.rb
new file mode 100644
index 0000000000..5ce432c4dc
--- /dev/null
+++ b/app/models/info_request/state.rb
@@ -0,0 +1,43 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module State
+
+ def self.all
+ states = [
+ 'waiting_response',
+ 'waiting_clarification',
+ 'gone_postal',
+ 'not_held',
+ 'rejected', # this is called 'refused' in UK FOI law and the user interface, but 'rejected' internally for historic reasons
+ 'successful',
+ 'partially_successful',
+ 'internal_review',
+ 'error_message',
+ 'requires_admin',
+ 'user_withdrawn',
+ 'attention_requested',
+ 'vexatious',
+ 'not_foi'
+ ]
+ if InfoRequest.custom_states_loaded
+ states += InfoRequest.theme_extra_states
+ end
+ states
+ end
+
+ def self.phases
+ [ { name: _('Awaiting response'),
+ scope: :awaiting_response },
+ { name: _('Response received'),
+ scope: :response_received },
+ { name: _('Clarification needed'),
+ scope: :clarification_needed },
+ { name: _('Complete'),
+ scope: :complete },
+ { name: _('Status unknown'),
+ scope: :other }
+ ]
+ end
+
+ end
+end
diff --git a/app/models/info_request/state/awaiting_response_query.rb b/app/models/info_request/state/awaiting_response_query.rb
new file mode 100644
index 0000000000..5c2057506b
--- /dev/null
+++ b/app/models/info_request/state/awaiting_response_query.rb
@@ -0,0 +1,15 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module State
+ class AwaitingResponseQuery
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ @relation.where(described_state: 'waiting_response',
+ awaiting_description: false)
+ end
+ end
+ end
+end
diff --git a/app/models/info_request/state/clarification_needed_query.rb b/app/models/info_request/state/clarification_needed_query.rb
new file mode 100644
index 0000000000..4e3b83facd
--- /dev/null
+++ b/app/models/info_request/state/clarification_needed_query.rb
@@ -0,0 +1,15 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module State
+ class ClarificationNeededQuery
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ @relation.where(described_state: 'waiting_clarification',
+ awaiting_description: false)
+ end
+ end
+ end
+end
diff --git a/app/models/info_request/state/complete_query.rb b/app/models/info_request/state/complete_query.rb
new file mode 100644
index 0000000000..2fc6a0707b
--- /dev/null
+++ b/app/models/info_request/state/complete_query.rb
@@ -0,0 +1,19 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module State
+ class CompleteQuery
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ @relation.where(described_state: ['not_held',
+ 'rejected',
+ 'successful',
+ 'partially_successful',
+ 'user_withdrawn'],
+ awaiting_description: false)
+ end
+ end
+ end
+end
diff --git a/app/models/info_request/state/other_query.rb b/app/models/info_request/state/other_query.rb
new file mode 100644
index 0000000000..12f1a06f69
--- /dev/null
+++ b/app/models/info_request/state/other_query.rb
@@ -0,0 +1,21 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module State
+ class OtherQuery
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ @relation.where(described_state: ['gone_postal',
+ 'internal_review',
+ 'error_message',
+ 'requires_admin',
+ 'attention_requested',
+ 'vexatious',
+ 'not_foi'],
+ awaiting_description: false)
+ end
+ end
+ end
+end
diff --git a/app/models/info_request/state/response_received_query.rb b/app/models/info_request/state/response_received_query.rb
new file mode 100644
index 0000000000..a9d8a2aaf8
--- /dev/null
+++ b/app/models/info_request/state/response_received_query.rb
@@ -0,0 +1,14 @@
+# -*- encoding : utf-8 -*-
+class InfoRequest
+ module State
+ class ResponseReceivedQuery
+ def initialize(relation = InfoRequest)
+ @relation = relation
+ end
+
+ def call
+ @relation.where(awaiting_description: true)
+ end
+ end
+ end
+end
diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb
index 473fdea50a..9965af8b9c 100644
--- a/app/models/info_request_event.rb
+++ b/app/models/info_request_event.rb
@@ -71,7 +71,7 @@ class InfoRequestEvent < ActiveRecord::Base
validate :must_be_valid_state
def must_be_valid_state
- if described_state and !InfoRequest.enumerate_states.include?(described_state)
+ if described_state and !InfoRequest::State.all.include?(described_state)
errors.add(:described_state, "is not a valid state")
end
end
diff --git a/app/views/admin_request/edit.html.erb b/app/views/admin_request/edit.html.erb
index 51d344bfef..63a696ca3b 100644
--- a/app/views/admin_request/edit.html.erb
+++ b/app/views/admin_request/edit.html.erb
@@ -44,7 +44,7 @@
- <%= select( 'info_request', "described_state", InfoRequest.enumerate_states) %>
+ <%= select( 'info_request', "described_state", InfoRequest::State.all) %>
<%= select('info_request', "awaiting_description", [["Yes – needs state updating",true],["No – state is up to date",false]]) %>
(don't forget to change 'awaiting description' when you set described state)
diff --git a/app/views/alaveteli_pro/dashboard/_projects.html.erb b/app/views/alaveteli_pro/dashboard/_projects.html.erb
new file mode 100644
index 0000000000..0285f19fae
--- /dev/null
+++ b/app/views/alaveteli_pro/dashboard/_projects.html.erb
@@ -0,0 +1,30 @@
+
From fe590eeb6788f5ede00c6bdc478bcf5884ef39ad Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 6 Dec 2016 13:39:57 +0000
Subject: [PATCH 099/311] Add path for starting a new request.
---
app/views/alaveteli_pro/info_requests/_no_requests.html.erb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/views/alaveteli_pro/info_requests/_no_requests.html.erb b/app/views/alaveteli_pro/info_requests/_no_requests.html.erb
index 01f36ed4b9..8fbf827a9a 100644
--- a/app/views/alaveteli_pro/info_requests/_no_requests.html.erb
+++ b/app/views/alaveteli_pro/info_requests/_no_requests.html.erb
@@ -4,7 +4,7 @@
<%= _("Get started with your first request") %>
<%= _("From this screen you can get a helpful overview of your requests, and use quick actions to keep them up to date. Why not start your first one now?") %>
- Start new request
+ <%= link_to _("Start new request"), new_alaveteli_pro_info_request_path, :class => 'button' %>
\ No newline at end of file
diff --git a/app/views/alaveteli_pro/info_requests/_request_list.html.erb b/app/views/alaveteli_pro/info_requests/_request_list.html.erb
index 3e006ad7c3..a5ebbe7e1d 100644
--- a/app/views/alaveteli_pro/info_requests/_request_list.html.erb
+++ b/app/views/alaveteli_pro/info_requests/_request_list.html.erb
@@ -20,7 +20,11 @@
diff --git a/spec/models/request_filter_spec.rb b/spec/models/request_filter_spec.rb
index 365d3c1d81..5550207e63 100644
--- a/spec/models/request_filter_spec.rb
+++ b/spec/models/request_filter_spec.rb
@@ -22,6 +22,50 @@
request_filter.update_attributes(:order => 'created_at_asc')
expect(request_filter.order).to eq 'created_at_asc'
end
+
+ it 'does not assign an empty filter' do
+ request_filter = described_class.new
+ request_filter.update_attributes(:filter => '')
+ expect(request_filter.filter).to be nil
+ end
+
+ end
+
+ describe '#filter_label' do
+
+ def expect_label(label, filter)
+ request_filter = described_class.new
+ request_filter.update_attributes(:filter => filter)
+ expect(request_filter.filter_label).to eq label
+ end
+
+ it 'is "All requests" when the filter is empty' do
+ expect_label('All requests', '')
+ end
+
+ it 'is "Drafts" when the filter is "draft"' do
+ expect_label('Drafts', 'draft')
+ end
+
+ it 'is "Awaiting response" when the filter is "awaiting_response"' do
+ expect_label('Awaiting response', 'awaiting_response')
+ end
+
+ it 'is "Complete" when the filter is "complete"' do
+ expect_label('Complete', 'complete')
+ end
+
+ it 'is "Clarification needed" when the filter is "clarification_needed"' do
+ expect_label('Clarification needed', 'clarification_needed')
+ end
+
+ it 'is "Status unknown" when the filter is "other"' do
+ expect_label('Status unknown', 'other')
+ end
+
+ it 'is "Response received" when the filter is "response_received"' do
+ expect_label('Response received', 'response_received')
+ end
end
describe '#order_options' do
From 97ce4e805459d58488cf38027786bc301cbd8d5a Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 6 Dec 2016 15:27:06 +0000
Subject: [PATCH 104/311] Make order and search operate within filter.
---
app/views/alaveteli_pro/info_requests/_request_list.html.erb | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/views/alaveteli_pro/info_requests/_request_list.html.erb b/app/views/alaveteli_pro/info_requests/_request_list.html.erb
index 63f633b298..84b5c64ad9 100644
--- a/app/views/alaveteli_pro/info_requests/_request_list.html.erb
+++ b/app/views/alaveteli_pro/info_requests/_request_list.html.erb
@@ -14,6 +14,7 @@
<%= f.select :order, @request_filter.order_options %>
+ <%= f.hidden_field :filter %>
<% end %>
From 2c15dc4da16841022ec369cbb52611c27544140f Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 6 Dec 2016 15:29:02 +0000
Subject: [PATCH 105/311] Change "Status unknown" to "Other"
---
app/models/info_request/state.rb | 2 +-
spec/models/request_filter_spec.rb | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/models/info_request/state.rb b/app/models/info_request/state.rb
index 5ce432c4dc..10eccc1065 100644
--- a/app/models/info_request/state.rb
+++ b/app/models/info_request/state.rb
@@ -34,7 +34,7 @@ def self.phases
scope: :clarification_needed },
{ name: _('Complete'),
scope: :complete },
- { name: _('Status unknown'),
+ { name: _('Other'),
scope: :other }
]
end
diff --git a/spec/models/request_filter_spec.rb b/spec/models/request_filter_spec.rb
index 5550207e63..4c8e79b1e9 100644
--- a/spec/models/request_filter_spec.rb
+++ b/spec/models/request_filter_spec.rb
@@ -59,8 +59,8 @@ def expect_label(label, filter)
expect_label('Clarification needed', 'clarification_needed')
end
- it 'is "Status unknown" when the filter is "other"' do
- expect_label('Status unknown', 'other')
+ it 'is "Other" when the filter is "other"' do
+ expect_label('Other', 'other')
end
it 'is "Response received" when the filter is "response_received"' do
From 1f80be76ae1ba9294b6155ef278a03c3a42bb19f Mon Sep 17 00:00:00 2001
From: Louise Crow
Date: Tue, 6 Dec 2016 15:48:03 +0000
Subject: [PATCH 106/311] Extend search to request body and authority name
Actually includes the text of any outgoing message.
---
app/models/request_filter.rb | 31 +++++++++++++++++++++++++------
1 file changed, 25 insertions(+), 6 deletions(-)
diff --git a/app/models/request_filter.rb b/app/models/request_filter.rb
index c8632a5073..1ff53232ba 100644
--- a/app/models/request_filter.rb
+++ b/app/models/request_filter.rb
@@ -25,17 +25,36 @@ def filter_label
def results(user)
if filter == 'draft'
- info_requests = user.draft_info_requests
+ draft_info_request_results(user)
else
- info_requests = user.info_requests
+ info_request_results(user)
end
+ end
+
+ def draft_info_request_results(user)
+ info_requests = user.draft_info_requests
+ info_requests = info_requests
+ .includes(:public_body)
+ .where("title ILIKE :q
+ OR body ILIKE :q
+ OR public_bodies.name ILIKE :q",
+ q: "%#{ search }%")
+ info_requests.reorder("draft_info_requests.#{order_value}")
+ end
+
+ def info_request_results(user)
+ info_requests = user.info_requests
if !filter_value.blank?
info_requests = info_requests.send(filter_value)
end
- if search
- info_requests = info_requests.where("title ILIKE :q", q: "%#{ search }%")
- end
- info_requests.reorder(order_value)
+ info_requests = info_requests
+ .includes(:public_body)
+ .includes(:outgoing_messages)
+ .where("title ILIKE :q
+ OR outgoing_messages.body ILIKE :q
+ OR public_bodies.name ILIKE :q",
+ q: "%#{ search }%")
+ info_requests.reorder("info_requests.#{order_value}")
end
def persisted?
From e0109b3d27c994fd6027941666a76fa2f5cb1a40 Mon Sep 17 00:00:00 2001
From: Martin Wright
Date: Wed, 7 Dec 2016 10:00:18 +0000
Subject: [PATCH 107/311] Add a sumbit button to the request list search box
---
.../images/alaveteli-pro/search--light.png | Bin 0 -> 493 bytes
.../alaveteli_pro/_requests_layout.scss | 22 ++++++++++++++++++
.../info_requests/_request_list.html.erb | 5 ++++
3 files changed, 27 insertions(+)
create mode 100644 app/assets/images/alaveteli-pro/search--light.png
diff --git a/app/assets/images/alaveteli-pro/search--light.png b/app/assets/images/alaveteli-pro/search--light.png
new file mode 100644
index 0000000000000000000000000000000000000000..668484bf705ab0f5184567d78cce96f98bc43dad
GIT binary patch
literal 493
zcmVKnd7EY#>##p_UM1o4+WVOionE(EFQB~(;Jv@HJt*iV_2C1=T?KHW~w{`{WM%Ns4bx2
zO}ApPS>?d&nxp-Gzc$Rj10K-}i6 '', :method => 'get', :html => { :class => 'requests__filter_form' }) do |f| %>