In commite51e03446, we started using the same code to show stats in the public area and in the admin area. However, in doing so we introduced a bug, since stats in the public area are only shown after a certain part of the process has finished, meaning the stats appearing on the page never change (in theory), so it's perfectly fine to cache them. However, in the admin area stats can be accessed while the process is still ongoing, so caching the stats will lead to the wrong results being displayed. We've thought about expiring the cache when new supports or ballot lines are added; however, that means the methods calculating the stats for the supporting phase would expire when supports are added/removed but the methods calculating the stats for the voting phase would expire when ballot lines are added/removed. It gets even more complex because the `headings` method calculates stats for both the supporting and the voting phases. So, since loading stats in the admin section is fast even without the cache because they only load very basic statistics, we're taking the simple approach of disabling the cache in this case, so everything works the same way it did before commite51e03446. Co-authored-by: Javi Martín <javim@elretirao.net>
271 lines
6.5 KiB
Ruby
271 lines
6.5 KiB
Ruby
require "rails_helper"
|
|
|
|
describe Statisticable do
|
|
before do
|
|
dummy_stats = Class.new do
|
|
include Statisticable
|
|
attr_accessor :total
|
|
stats_cache :total
|
|
|
|
def full_cache_key_for(key)
|
|
"dummy_stats/#{object_id}/#{key}"
|
|
end
|
|
|
|
def participants
|
|
User.all
|
|
end
|
|
alias_method :participants_from_original_table, :participants
|
|
|
|
def total_participants
|
|
User.count
|
|
end
|
|
|
|
def participation_date
|
|
Time.current
|
|
end
|
|
end
|
|
|
|
stub_const("DummyStats", dummy_stats)
|
|
end
|
|
|
|
let(:resource) { double(id: 1, class: double(table_name: "")) }
|
|
let(:stats) { DummyStats.new(resource) }
|
|
|
|
describe "#gender?" do
|
|
context "No participants" do
|
|
it "is false" do
|
|
expect(stats.gender?).to be false
|
|
end
|
|
end
|
|
|
|
context "All participants have no defined gender" do
|
|
before { create(:user, gender: nil) }
|
|
|
|
it "is false" do
|
|
expect(stats.gender?).to be false
|
|
end
|
|
end
|
|
|
|
context "There's a male participant" do
|
|
before { create(:user, gender: "male") }
|
|
|
|
it "is true" do
|
|
expect(stats.gender?).to be true
|
|
end
|
|
end
|
|
|
|
context "There's a female participant" do
|
|
before { create(:user, gender: "female") }
|
|
|
|
it "is true" do
|
|
expect(stats.gender?).to be true
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#age?" do
|
|
context "No participants" do
|
|
it "is false" do
|
|
expect(stats.age?).to be false
|
|
end
|
|
end
|
|
|
|
context "All participants have no defined age" do
|
|
before { create(:user, date_of_birth: nil) }
|
|
|
|
it "is false" do
|
|
expect(stats.age?).to be false
|
|
end
|
|
end
|
|
|
|
context "All participants have impossible ages" do
|
|
before do
|
|
create(:user, date_of_birth: 3.seconds.ago)
|
|
create(:user, date_of_birth: 3000.years.ago)
|
|
end
|
|
|
|
it "is false" do
|
|
expect(stats.age?).to be false
|
|
end
|
|
end
|
|
|
|
context "There's a participant with a defined age" do
|
|
before { create(:user, date_of_birth: 30.years.ago) }
|
|
|
|
it "is true" do
|
|
expect(stats.age?).to be true
|
|
end
|
|
end
|
|
|
|
context "Partipation took place a long time ago" do
|
|
before do
|
|
create(:user, date_of_birth: 2000.years.ago)
|
|
allow(stats).to receive(:participation_date).and_return(1900.years.ago)
|
|
end
|
|
|
|
it "is true" do
|
|
expect(stats.age?).to be true
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#geozone?" do
|
|
context "No participants" do
|
|
it "is false" do
|
|
expect(stats.geozone?).to be false
|
|
end
|
|
end
|
|
|
|
context "All participants have no defined geozone" do
|
|
before { create(:user, geozone: nil) }
|
|
|
|
it "is false" do
|
|
expect(stats.geozone?).to be false
|
|
end
|
|
end
|
|
|
|
context "There's a participant with a defined geozone" do
|
|
before { create(:user, geozone: create(:geozone)) }
|
|
|
|
it "is true" do
|
|
expect(stats.geozone?).to be true
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#total_no_demographic_data" do
|
|
it "returns users with no defined gender" do
|
|
create(:user, gender: nil)
|
|
|
|
expect(stats.total_no_demographic_data).to be 1
|
|
end
|
|
|
|
it "returns users with no defined age" do
|
|
create(:user, gender: "female", date_of_birth: nil)
|
|
|
|
expect(stats.total_no_demographic_data).to be 1
|
|
end
|
|
|
|
it "returns users with no defined geozone" do
|
|
create(:user, gender: "female", geozone: nil)
|
|
|
|
expect(stats.total_no_demographic_data).to be 1
|
|
end
|
|
|
|
it "returns users with no defined gender, age nor geozone" do
|
|
create(:user, gender: nil, date_of_birth: nil, geozone: nil)
|
|
|
|
expect(stats.total_no_demographic_data).to be 1
|
|
end
|
|
|
|
it "doesn't return users with defined gender, age and geozone" do
|
|
create(:user, gender: "male", date_of_birth: 20.years.ago, geozone: create(:geozone))
|
|
|
|
expect(stats.total_no_demographic_data).to be 0
|
|
end
|
|
end
|
|
|
|
describe "#stats_methods" do
|
|
it "includes total participants" do
|
|
expect(stats.stats_methods).to include(:total_participants)
|
|
end
|
|
|
|
context "no gender stats" do
|
|
before { allow(stats).to receive(:gender?).and_return(false) }
|
|
|
|
it "doesn't include gender methods" do
|
|
expect(stats.stats_methods).not_to include(:total_male_participants)
|
|
end
|
|
end
|
|
|
|
context "no age stats" do
|
|
before { allow(stats).to receive(:age?).and_return(false) }
|
|
|
|
it "doesn't include age methods" do
|
|
expect(stats.stats_methods).not_to include(:participants_by_age)
|
|
end
|
|
end
|
|
|
|
context "no geozone stats" do
|
|
before { allow(stats).to receive(:geozone?).and_return(false) }
|
|
|
|
it "doesn't include age methods" do
|
|
expect(stats.stats_methods).not_to include(:participants_by_geozone)
|
|
end
|
|
end
|
|
|
|
context "all gender, age and geozone stats" do
|
|
before do
|
|
allow(stats).to receive_messages(gender?: true, age?: true, geozone?: true)
|
|
end
|
|
|
|
it "includes all stats methods" do
|
|
expect(stats.stats_methods).to include(:total_male_participants)
|
|
expect(stats.stats_methods).to include(:participants_by_age)
|
|
expect(stats.stats_methods).to include(:participants_by_geozone)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#generate" do
|
|
it "drops the temporary table after finishing" do
|
|
stats.generate
|
|
|
|
expect { stats.send(:participants_class).first }.to raise_exception(ActiveRecord::StatementInvalid)
|
|
end
|
|
|
|
it "can be executed twice without errors" do
|
|
stats.generate
|
|
|
|
expect { stats.generate }.not_to raise_exception
|
|
end
|
|
|
|
it "can be executed twice in parallel since it uses a transaction" do
|
|
other_stats = DummyStats.new(stats.resource)
|
|
|
|
[stats, other_stats].map do |stat|
|
|
Thread.new { stat.generate }
|
|
end.each(&:join)
|
|
end
|
|
end
|
|
|
|
describe "cache", :with_cache do
|
|
it "expires the cache at the end of the day by default" do
|
|
time = Time.current
|
|
|
|
travel_to(time) do
|
|
stats.total = 6
|
|
|
|
expect(stats.total).to eq 6
|
|
|
|
stats.total = 7
|
|
|
|
expect(stats.total).to eq 6
|
|
end
|
|
|
|
travel_to(time.end_of_day) do
|
|
expect(stats.total).to eq 6
|
|
end
|
|
|
|
travel_to(time.end_of_day + 1.second) do
|
|
expect(stats.total).to eq 7
|
|
end
|
|
end
|
|
|
|
it "does not use the cache with cache: false" do
|
|
stats = DummyStats.new(resource, cache: false)
|
|
time = Time.current
|
|
|
|
travel_to(time) do
|
|
stats.total = 6
|
|
|
|
expect(stats.total).to eq 6
|
|
|
|
stats.total = 7
|
|
|
|
expect(stats.total).to eq 7
|
|
end
|
|
end
|
|
end
|
|
end
|