From 1b62dd06b80bd72fa264caa91b3dd960156e872e Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 5 Feb 2016 15:16:12 +1100 Subject: [PATCH] Add products cache integrity checker --- Gemfile | 1 + Gemfile.lock | 2 + .../products_cache_integrity_checker_job.rb | 38 +++++++++++++++++++ config/schedule.rb | 4 ++ lib/tasks/cache.rake | 16 ++++++++ ...oducts_cache_integrity_checker_job_spec.rb | 26 +++++++++++++ 6 files changed, 87 insertions(+) create mode 100644 app/jobs/products_cache_integrity_checker_job.rb create mode 100644 lib/tasks/cache.rake create mode 100644 spec/jobs/products_cache_integrity_checker_job_spec.rb diff --git a/Gemfile b/Gemfile index 300b17ffa3..3690b194b8 100644 --- a/Gemfile +++ b/Gemfile @@ -55,6 +55,7 @@ gem 'figaro' gem 'blockenspiel' gem 'acts-as-taggable-on', '~> 3.4' gem 'paper_trail', '~> 3.0.8' +gem 'diffy' gem 'wicked_pdf' gem 'wkhtmltopdf-binary' diff --git a/Gemfile.lock b/Gemfile.lock index 75763fb4a0..fc93330817 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -248,6 +248,7 @@ GEM devise-encryptable (0.1.2) devise (>= 2.1.0) diff-lcs (1.2.4) + diffy (3.1.0) em-websocket (0.5.0) eventmachine (>= 0.12.9) http_parser.rb (~> 0.5.3) @@ -668,6 +669,7 @@ DEPENDENCIES debugger-linecache deface! delayed_job_active_record + diffy factory_girl_rails figaro foreigner diff --git a/app/jobs/products_cache_integrity_checker_job.rb b/app/jobs/products_cache_integrity_checker_job.rb new file mode 100644 index 0000000000..7bcf86baeb --- /dev/null +++ b/app/jobs/products_cache_integrity_checker_job.rb @@ -0,0 +1,38 @@ +require 'open_food_network/products_renderer' + +ProductsCacheIntegrityCheckerJob = Struct.new(:distributor_id, :order_cycle_id) do + def perform + if diff.any? + Bugsnag.notify RuntimeError.new("Products JSON differs from cached version"), diff: diff.to_s(:color) + end + end + + + private + + def diff + @diff ||= Diffy::Diff.new pretty(cached_json), pretty(rendered_json) + end + + def pretty(json) + JSON.pretty_generate JSON.parse json + end + + def cached_json + Rails.cache.read("products-json-#{distributor_id}-#{order_cycle_id}") || {}.to_json + end + + def rendered_json + OpenFoodNetwork::ProductsRenderer.new(distributor, order_cycle).products_json + rescue OpenFoodNetwork::ProductsRenderer::NoProducts + nil + end + + def distributor + Enterprise.find distributor_id + end + + def order_cycle + OrderCycle.find order_cycle_id + end +end diff --git a/config/schedule.rb b/config/schedule.rb index a09ca55dce..1846a6358d 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -7,6 +7,10 @@ env "MAILTO", "rohan@rohanmitchell.com" # If we use -e with a file containing specs, rspec interprets it and filters out our examples job_type :run_file, "cd :path; :environment_variable=:environment bundle exec script/rails runner :task :output" +every 1.hour do + rake 'openfoodnetwork:cache:check_products_cache_integrity' +end + every 1.day, at: '12:05am' do run_file "lib/open_food_network/integrity_checker.rb" end diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake new file mode 100644 index 0000000000..2eefb8a49f --- /dev/null +++ b/lib/tasks/cache.rake @@ -0,0 +1,16 @@ +namespace :openfoodnetwork do + namespace :cache do + desc 'check the integrity of the products cache' + task :check_products_cache_integrity => :environment do + exchanges = Exchange. + outgoing. + joins(:order_cycle). + merge(OrderCycle.dated). + merge(OrderCycle.not_closed) + + exchanges.each do |exchange| + Delayed::Job.enqueue ProductsCacheIntegrityCheckerJob.new(exchange.receiver, exchange.order_cycle), priority: 20 + end + end + end +end diff --git a/spec/jobs/products_cache_integrity_checker_job_spec.rb b/spec/jobs/products_cache_integrity_checker_job_spec.rb new file mode 100644 index 0000000000..1770f9bf84 --- /dev/null +++ b/spec/jobs/products_cache_integrity_checker_job_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' +require 'open_food_network/products_renderer' + +describe ProductsCacheIntegrityCheckerJob do + describe "reporting on differences between the products cache and the current products" do + let(:distributor) { create(:distributor_enterprise) } + let(:order_cycle) { create(:simple_order_cycle) } + let(:job) { ProductsCacheIntegrityCheckerJob.new distributor.id, order_cycle.id } + + before do + Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", "[1, 2, 3]\n" + OpenFoodNetwork::ProductsRenderer.stub(:new) { double(:pr, products_json: "[1, 3]\n") } + end + + it "reports errors" do + expect(Bugsnag).to receive(:notify) + run_job job + end + + it "deals with nil cached_json" do + Rails.cache.clear + expect(Bugsnag).to receive(:notify) + run_job job + end + end +end