21
1
Fork 0
mirror of https://github.com/Evolix/chexpire.git synced 2024-05-03 01:10:50 +02:00

Check processor with task to update/sync dates

This commit is contained in:
Colin Darie 2018-06-05 15:24:44 +02:00
parent 822da5c752
commit 142f0a6f1c
No known key found for this signature in database
GPG key ID: 4FB865FDBCA4BCC4
9 changed files with 238 additions and 6 deletions

View file

@ -52,6 +52,12 @@ class Check < ApplicationRecord
after_update :reset_notifications
after_save :enqueue_sync
scope :active, -> { where(active: true) }
scope :last_run_failed, -> {
where("(last_success_at IS NULL AND last_run_at IS NOT NULL)
OR (last_success_at <= DATE_SUB(last_run_at, INTERVAL 5 MINUTE))")
}
private
def domain_created_at_past

View file

@ -31,6 +31,9 @@ class Notification < ApplicationRecord
validates :delay, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
validates :recipient, presence: true
scope :active_check, -> { Check.active }
scope :check_last_run_failed, -> { Check.last_run_failed }
def pending!
self.sent_at = nil
super

View file

@ -0,0 +1,70 @@
class CheckProcessor
attr_reader :configuration
def initialize(configuration = nil)
@configuration = configuration || default_configuration
end
def sync_dates # rubocop:disable Metrics/MethodLength
%i[
resolve_last_run_failed
resolve_expire_short_term
resolve_expire_long_term
resolve_unknown_expiry
].each do |resolver|
public_send(resolver).find_each(batch_size: 100).each do |check|
process(check)
sleep configuration.interval
end
end
end
def resolve_last_run_failed
scope.last_run_failed
end
def resolve_expire_long_term
scope
.where("DATE(domain_expires_at) >= DATE_ADD(CURDATE(), INTERVAL ? DAY)",
configuration.long_term)
.where("DATEDIFF(domain_expires_at, CURDATE()) MOD ? = 0",
configuration.long_term_frequency)
end
def resolve_expire_short_term
scope.where("DATE(domain_expires_at) < DATE_ADD(CURDATE(), INTERVAL ? DAY)",
configuration.long_term)
end
def resolve_unknown_expiry
scope.where("domain_expires_at IS NULL")
end
private
def scope
Check
.active
.where("last_run_at IS NULL OR last_run_at < DATE_SUB(NOW(), INTERVAL 12 HOUR)")
end
def process(check)
case check.kind.to_sym
when :domain
WhoisSyncJob.perform_now(check.id)
else
fail ArgumentError, "Unsupported check kind `#{check.kind}`"
end
end
def default_configuration
config = Rails.configuration.chexpire.fetch("checks", {})
OpenStruct.new(
interval: config.fetch("interval") { 0.00 },
long_term: config.fetch("long_term") { 60 },
long_term_frequency: config.fetch("long_term_frequency") { 10 },
)
end
end

View file

@ -12,7 +12,7 @@ module Notifier
# Logical rules are in plain ruby inside processor
scope
.includes(check: :logs)
.where("checks.last_success_at <= DATE_SUB(checks.last_run_at, INTERVAL 5 MINUTE)")
.merge(Check.last_run_failed)
end
private
@ -20,7 +20,8 @@ module Notifier
def scope
Notification
.includes(:check)
.where(status: [:pending, :failed], checks: { active: true })
.where(status: [:pending, :failed])
.merge(Check.active)
.where.not(checks: { user: ignore_users })
end

View file

@ -3,6 +3,10 @@ default: &default
notifier:
interval: 0.00
failure_days: 3
checks:
interval: 0.5
long_term: 60
long_term_frequency: 10
development:
<<: *default

View file

@ -4,3 +4,7 @@ test:
notifier:
interval: 0.00
failure_days: 3
checks:
interval: 0.00
long_term: 60
long_term_frequency: 10

7
lib/tasks/checks.rake Normal file
View file

@ -0,0 +1,7 @@
namespace :checks do
desc "Refresh expiry dates for checks"
task sync_dates: :environment do
process = CheckProcessor.new
process.sync_dates
end
end

View file

@ -54,14 +54,18 @@ FactoryBot.define do
domain_expires_at 1.week.from_now
end
trait :expires_next_year do
domain_expires_at 1.year.from_now
end
trait :last_runs_failed do
last_run_at Time.now - 90.minutes
last_success_at 1.week.ago - 2.hours
last_run_at 3.days.ago - 90.minutes
last_success_at 7.days.ago - 2.hours
end
trait :last_run_succeed do
last_run_at 1.hour.ago
last_success_at 1.hour.ago
last_run_at 25.hour.ago
last_success_at 25.hour.ago
end
trait :inactive do

View file

@ -0,0 +1,133 @@
require "test_helper"
class CheckProcessorTest < ActiveSupport::TestCase
setup do
@processor = CheckProcessor.new
end
test "process WhoisSyncJob for domain checks" do
domain = "domain.fr"
check = create(:check, :domain, :nil_dates, domain: domain)
mock_system_command("whois", domain, stdout: file_fixture("whois/domain.fr.txt").read) do
@processor.send(:process, check)
end
check.reload
assert_equal Time.new(2019, 2, 17, 0, 0, 0, 0), check.domain_expires_at
end
test "raises an error for an unsupported check kind" do
check = build(:check)
check.stub :kind, :unknown do
assert_raises ArgumentError do
@processor.send(:process, check)
end
end
end
test "resolve_last_run_failed includes already and never succeeded" do
c1 = create(:check, :last_runs_failed)
c2 = create(:check, :last_run_succeed)
c3 = create(:check, last_run_at: 4.days.ago, last_success_at: nil)
checks = @processor.resolve_last_run_failed
assert_includes checks, c1
assert_not_includes checks, c2
assert_includes checks, c3
end
test "resolve_unknown_expiry" do
c1 = create(:check, :nil_dates)
c2 = create(:check)
checks = @processor.resolve_unknown_expiry
assert_includes checks, c1
assert_not_includes checks, c2
end
test "resolve_expire_short_term" do
c1 = create(:check, :expires_next_week)
c2 = create(:check, :expires_next_year)
checks = @processor.resolve_expire_short_term
assert_includes checks, c1
assert_not_includes checks, c2
end
test "resolve_expire_long_term returns checks with respect of frequency" do
c1 = create(:check, domain_expires_at: 380.days.from_now)
c2 = create(:check, domain_expires_at: 390.days.from_now)
c3 = create(:check, domain_expires_at: 391.days.from_now)
c4 = create(:check, domain_expires_at: 20.days.from_now)
checks = @processor.resolve_expire_long_term
assert_includes checks, c1
assert_includes checks, c2
assert_not_includes checks, c3
assert_not_includes checks, c4
end
test "resolvers does not include checks recently executed" do
c1 = create(:check, :expires_next_week)
c2 = create(:check, :expires_next_week, last_run_at: 4.hours.ago)
checks = @processor.resolve_expire_short_term
assert_includes checks, c1
assert_not_includes checks, c2
end
test "resolvers include checks never executed" do
c1 = create(:check, :expires_next_week, last_run_at: 4.days.ago)
checks = @processor.resolve_expire_short_term
assert_includes checks, c1
end
test "resolvers does not include inactive checks" do
c1 = create(:check, :expires_next_week)
c2 = create(:check, :expires_next_week, :inactive)
checks = @processor.resolve_expire_short_term
assert_includes checks, c1
assert_not_includes checks, c2
end
test "#sync_dates respects the interval configuration between sends" do
create_list(:check, 3, :expires_next_week)
configuration = Minitest::Mock.new
2.times do configuration.expect(:long_term, 60) end
configuration.expect(:long_term_frequency, 10)
3.times do
configuration.expect(:interval, 0.000001)
end
processor = CheckProcessor.new(configuration)
mock = Minitest::Mock.new
assert_stub = lambda { |actual_time|
assert_equal 0.000001, actual_time
mock
}
processor.stub :process, nil do
processor.stub :sleep, assert_stub do
processor.sync_dates
end
end
configuration.verify
mock.verify
end
end