Revision a36689ab
Added by Ohad Levy about 7 years ago
app/models/concerns/hostext/ui_notifications.rb | ||
---|---|---|
module Hostext
|
||
module UINotifications
|
||
extend ActiveSupport::Concern
|
||
included do
|
||
before_provision :provision_notification
|
||
before_destroy :remove_ui_notifications
|
||
end
|
||
|
||
def provision_notification
|
||
::UINotifications::Hosts::BuildCompleted.deliver!(self) if just_provisioned?
|
||
end
|
||
|
||
def remove_ui_notifications
|
||
::UINotifications::Hosts::Destroy.deliver!(self)
|
||
end
|
||
|
||
def just_provisioned?
|
||
!!previous_changes['installed_at']
|
||
end
|
||
end
|
||
end
|
app/models/host/managed.rb | ||
---|---|---|
# Define custom hook that can be called in model by magic methods (before, after, around)
|
||
define_model_callbacks :build, :only => :after
|
||
define_model_callbacks :provision, :only => :before
|
||
include Hostext::UINotifications
|
||
|
||
before_validation :refresh_build_status, :if => :build_changed?
|
||
|
||
... | ... | |
end
|
||
|
||
def build_hooks
|
||
return unless respond_to?(:old) && old && (build? != old.build?)
|
||
return if previous_changes['build'].nil?
|
||
if build?
|
||
run_callbacks :build do
|
||
logger.debug "custom hook after_build on #{name} will be executed if defined."
|
app/models/notification.rb | ||
---|---|---|
|
||
belongs_to :notification_blueprint
|
||
belongs_to :initiator, :class_name => User, :foreign_key => 'user_id'
|
||
belongs_to :subject, :polymorphic => true
|
||
has_many :notification_recipients, :dependent => :delete_all
|
||
has_many :recipients, :class_name => User, :through => :notification_recipients, :source => :user
|
||
store :actions, :accessors => [:links], :coder => JSON
|
||
|
||
validates :notification_blueprint, :presence => true
|
||
validates :initiator, :presence => true
|
||
... | ... | |
:in => [AUDIENCE_USER, AUDIENCE_GROUP, AUDIENCE_TAXONOMY,
|
||
AUDIENCE_GLOBAL, AUDIENCE_ADMIN]
|
||
}, :presence => true
|
||
validates :message, :presence => true
|
||
before_validation :set_custom_attributes
|
||
before_create :set_expiry, :set_notification_recipients,
|
||
:set_default_initiator
|
||
|
||
... | ... | |
when AUDIENCE_GLOBAL
|
||
User.reorder('').pluck(:id)
|
||
when AUDIENCE_TAXONOMY
|
||
notification_blueprint.subject.user_ids.uniq
|
||
subject.user_ids.uniq
|
||
when AUDIENCE_USER
|
||
[initiator.id]
|
||
when AUDIENCE_ADMIN
|
||
User.only_admin.reorder('').uniq.pluck(:id)
|
||
when AUDIENCE_GROUP
|
||
notification_blueprint.subject.all_users.uniq.map(&:id) # This needs to be rewritten in usergroups.
|
||
subject.all_users.uniq.map(&:id) # This needs to be rewritten in usergroups.
|
||
end
|
||
end
|
||
|
||
... | ... | |
subscribers = subscriber_ids
|
||
notification_recipients.build subscribers.map{|id| { :user_id => id}}
|
||
end
|
||
|
||
def set_custom_attributes
|
||
return unless notification_blueprint # let validation catch this.
|
||
self.actions = UINotifications::URLResolver.new(
|
||
subject,
|
||
notification_blueprint.actions
|
||
).actions if notification_blueprint.actions.any?
|
||
# copy notification message in case we didn't create a custom one.
|
||
self.message ||= UINotifications::StringParser.new(
|
||
notification_blueprint.message,
|
||
{subject: subject, initator: initiator}
|
||
).to_s
|
||
end
|
||
end
|
app/models/notification_blueprint.rb | ||
---|---|---|
# NotificationBlueprint of storing it.
|
||
class NotificationBlueprint < ActiveRecord::Base
|
||
has_many :notifications, :dependent => :destroy
|
||
belongs_to :subject, :polymorphic => true
|
||
store :actions, :accessors => [:links], :coder => JSON
|
||
|
||
validates :message, :presence => true
|
app/models/notification_recipient.rb | ||
---|---|---|
:id => id,
|
||
:seen => seen,
|
||
:level => notification_blueprint.level,
|
||
:text => notification_blueprint.message,
|
||
:subject => notification_blueprint.subject,
|
||
:text => notification.message,
|
||
:created_at => notification.created_at,
|
||
:group => notification_blueprint.group,
|
||
:actions => notification_blueprint.actions
|
||
:actions => notification.actions
|
||
}
|
||
end
|
||
|
app/services/ui_notifications.rb | ||
---|---|---|
# Namespace for Foreman UI notifications event handling
|
||
module UINotifications
|
||
class Base
|
||
attr_reader :subject
|
||
|
||
def self.deliver!(subject)
|
||
new(subject).deliver!
|
||
rescue => e
|
||
# Do not break actions using notifications even if there is a failure.
|
||
logger.warn("Failed to handle notifications - this is most likely a bug: #{e}")
|
||
end
|
||
|
||
def self.logger
|
||
@logger ||= Foreman::Logging.logger('notifications')
|
||
end
|
||
|
||
def initialize(subject)
|
||
raise(Foreman::Exception, 'must provide notification subject') if subject.nil?
|
||
@subject = subject
|
||
end
|
||
|
||
def deliver!
|
||
logger.debug("Notification event: #{event} - checking for notifications")
|
||
create
|
||
end
|
||
|
||
protected
|
||
|
||
def event
|
||
self.class.name.sub(/^\w+::/,'')
|
||
end
|
||
|
||
# Defaults to anonymous api admin, override in subclasses as needed.
|
||
def initiator
|
||
User.anonymous_api_admin
|
||
end
|
||
|
||
def logger
|
||
self.class.logger
|
||
end
|
||
end
|
||
end
|
app/services/ui_notifications/hosts.rb | ||
---|---|---|
module UINotifications
|
||
module Hosts
|
||
class Base < UINotifications::Base
|
||
def deliver!
|
||
if audience.nil? || initiator.nil?
|
||
logger.warn("Invalid owner for #{subject}, unable to send notifications")
|
||
# add notification for missing owner
|
||
UINotifications::Hosts::MissingOwner.deliver!(subject)
|
||
return false
|
||
end
|
||
super
|
||
end
|
||
|
||
protected
|
||
|
||
def audience
|
||
case subject.owner_type
|
||
when 'User'
|
||
::Notification::AUDIENCE_USER
|
||
when 'Usergroup'
|
||
::Notification::AUDIENCE_GROUP
|
||
end
|
||
end
|
||
|
||
def initiator
|
||
case subject.owner
|
||
when User
|
||
subject.owner
|
||
when Usergroup
|
||
# Usergroup, picking the first user, in theory can look in the audit
|
||
# log to see who set the host on built, but since its a group
|
||
# all of the users are going to get a notification.
|
||
subject.owner.all_users.first
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
app/services/ui_notifications/hosts/build_completed.rb | ||
---|---|---|
module UINotifications
|
||
module Hosts
|
||
class BuildCompleted < Base
|
||
private
|
||
|
||
def create
|
||
add_notification if update_notifications.zero?
|
||
end
|
||
|
||
def add_notification
|
||
::Notification.create!(
|
||
initiator: initiator,
|
||
subject: subject,
|
||
audience: audience,
|
||
notification_blueprint: blueprint
|
||
)
|
||
end
|
||
|
||
def update_notifications
|
||
blueprint.notifications.
|
||
where(subject: subject).
|
||
update_all(expired_at: blueprint.expired_at)
|
||
end
|
||
|
||
def blueprint
|
||
@blueprint ||= NotificationBlueprint.find_by(name: 'host_build_completed')
|
||
end
|
||
end
|
||
end
|
||
end
|
app/services/ui_notifications/hosts/destroy.rb | ||
---|---|---|
module UINotifications
|
||
module Hosts
|
||
class Destroy < Base
|
||
private
|
||
|
||
def create
|
||
# I'm defaulting to deleting older notifications as it may
|
||
# contain links to non existing actions.
|
||
delete_others
|
||
Notification.create!(
|
||
initiator: initiator,
|
||
audience: audience,
|
||
# note we do not store the subject, as the object is being deleted.
|
||
message: StringParser.new(blueprint.message, {subject: subject}),
|
||
notification_blueprint: blueprint
|
||
)
|
||
end
|
||
|
||
def delete_others
|
||
logger.debug("Removing all notifications for host: #{subject}")
|
||
Notification.where(subject: subject).destroy_all
|
||
end
|
||
|
||
def blueprint
|
||
@blueprint ||= NotificationBlueprint.find_by(name: 'host_destroyed')
|
||
end
|
||
end
|
||
end
|
||
end
|
app/services/ui_notifications/hosts/missing_owner.rb | ||
---|---|---|
module UINotifications
|
||
module Hosts
|
||
class MissingOwner < UINotifications::Base
|
||
private
|
||
def create
|
||
add_notification if update_notifications.zero?
|
||
end
|
||
|
||
def update_notifications
|
||
blueprint.notifications.
|
||
where(subject: subject).
|
||
update_all(expired_at: blueprint.expired_at)
|
||
end
|
||
|
||
def add_notification
|
||
Notification.create!(
|
||
initiator: initiator,
|
||
audience: ::Notification::AUDIENCE_ADMIN,
|
||
subject: subject,
|
||
notification_blueprint: blueprint
|
||
)
|
||
end
|
||
|
||
def blueprint
|
||
@blueprint ||= NotificationBlueprint.find_by(name: 'host_missing_owner')
|
||
end
|
||
end
|
||
end
|
||
end
|
app/services/ui_notifications/seed.rb | ||
---|---|---|
module UINotifications
|
||
# seeds UI notification blueprints that are supported by Foreman.
|
||
class Seed
|
||
attr_reader :attributes
|
||
|
||
def initialize(blueprint_attributes)
|
||
@attributes = blueprint_attributes
|
||
end
|
||
|
||
def configure
|
||
blueprint = NotificationBlueprint.find_by(name: attributes[:name])
|
||
if blueprint
|
||
blueprint.update_attributes!(attributes)
|
||
else
|
||
NotificationBlueprint.create!(attributes)
|
||
end
|
||
end
|
||
end
|
||
end
|
app/services/ui_notifications/string_parser.rb | ||
---|---|---|
module UINotifications
|
||
# class to convert blueprint and url title templates into strings
|
||
class StringParser
|
||
def initialize(template, options = {})
|
||
@template = template
|
||
@options = options
|
||
end
|
||
|
||
def to_s
|
||
template % options
|
||
end
|
||
|
||
private
|
||
attr_reader :template, :options
|
||
end
|
||
end
|
app/services/ui_notifications/url_resolver.rb | ||
---|---|---|
module UINotifications
|
||
# interpolates blueprint links for notifications
|
||
class URLResolver
|
||
# needed in order to resolve rails urls for notifications
|
||
include Rails.application.routes.url_helpers
|
||
def initialize(subject, actions = nil)
|
||
@subject = subject
|
||
@raw_actions = actions
|
||
end
|
||
|
||
def actions
|
||
return if raw_actions.try(:[], :links).nil?
|
||
links = raw_actions[:links].map do |link|
|
||
validate_title link
|
||
if link.has_key? :href
|
||
link
|
||
elsif link.has_key? :path_method
|
||
validate_link(link)
|
||
parse_link(link[:path_method], link[:title])
|
||
end
|
||
end
|
||
{links: links}
|
||
end
|
||
|
||
private
|
||
|
||
attr_reader :subject, :raw_actions
|
||
|
||
def parse_link path_method, title
|
||
{
|
||
href: path_for(path_method),
|
||
title: StringParser.new(title, {subject: subject}).to_s
|
||
}
|
||
end
|
||
|
||
def validate_title link
|
||
if link[:title].blank?
|
||
raise(Foreman::Exception, "Invalid link, must contain :title")
|
||
end
|
||
end
|
||
|
||
def validate_link link
|
||
path_method = link[:path_method]
|
||
unless path_method.to_s.match(/_path$/)
|
||
raise(Foreman::Exception, "Invalid path_method #{path_method}, must end with _path")
|
||
end
|
||
end
|
||
|
||
def path_for path_method
|
||
if collection_path?(path_method)
|
||
public_send(path_method)
|
||
else
|
||
public_send(path_method, subject)
|
||
end
|
||
end
|
||
|
||
def collection_path? path
|
||
path.to_s.sub(/_path$/,'').ends_with?('s')
|
||
end
|
||
end
|
||
end
|
config/application.rb | ||
---|---|---|
:ldap => {:enabled => false},
|
||
:permissions => {:enabled => false},
|
||
:sql => {:enabled => false},
|
||
:templates => {:enabled => true}
|
||
:templates => {:enabled => true},
|
||
:notifications => {:enabled => true}
|
||
))
|
||
|
||
config.logger = Foreman::Logging.logger('app')
|
config/settings.yaml.example | ||
---|---|---|
# :enabled: false
|
||
# :templates:
|
||
# :enabled: true
|
||
# :notifications:
|
||
# :enabled: true
|
db/migrate/20170226193446_move_subject_to_notifications.rb | ||
---|---|---|
class MoveSubjectToNotifications < ActiveRecord::Migration
|
||
def change
|
||
add_reference :notifications, :subject, polymorphic: true, index: true
|
||
remove_reference :notification_blueprints, :subject, polymorphic: true, index: true
|
||
end
|
||
end
|
db/migrate/20170306100129_add_message_to_notification.rb | ||
---|---|---|
class AddMessageToNotification < ActiveRecord::Migration
|
||
def change
|
||
add_column :notifications, :message, :string
|
||
add_column :notifications, :actions, :text
|
||
end
|
||
end
|
db/seeds.d/17-notification_blueprints.rb | ||
---|---|---|
blueprints = [
|
||
{
|
||
group: _('Hosts'),
|
||
name: 'host_build_completed',
|
||
message: _('%{subject} has been provisioned successfully'),
|
||
level: 'success',
|
||
actions:
|
||
{
|
||
links:
|
||
[
|
||
path_method: :host_path,
|
||
title: _('Details')
|
||
]
|
||
}
|
||
},
|
||
{
|
||
group: _('Hosts'),
|
||
name: 'host_destroyed',
|
||
message: _('%{subject} has been deleted successfully'),
|
||
level: 'info'
|
||
},
|
||
{
|
||
group: _('Hosts'),
|
||
name: 'host_missing_owner',
|
||
message: _('%{subject} has no owner set'),
|
||
level: 'warning',
|
||
actions:
|
||
{
|
||
links:
|
||
[
|
||
path_method: :edit_host_path,
|
||
title: _('Update host')
|
||
]
|
||
}
|
||
}
|
||
]
|
||
|
||
blueprints.each { |blueprint| UINotifications::Seed.new(blueprint).configure }
|
test/controllers/notification_recipients_controller_test.rb | ||
---|---|---|
assert_equal 0, response['total']
|
||
end
|
||
|
||
test "notification when host is destroyed" do
|
||
host = FactoryGirl.create(:host)
|
||
assert host.destroy
|
||
get :index, { :format => 'json' }, set_session_user
|
||
assert_response :success
|
||
response = ActiveSupport::JSON.decode(@response.body)
|
||
assert_equal 1, response['total']
|
||
assert_equal "#{host} has been deleted successfully", response['notifications'][0]["text"]
|
||
end
|
||
|
||
test "notification when host is built" do
|
||
host = FactoryGirl.create(:host, owner: User.current)
|
||
assert host.update_attribute(:build, true)
|
||
assert host.built
|
||
get :index, { :format => 'json' }, set_session_user
|
||
assert_response :success
|
||
response = ActiveSupport::JSON.decode(@response.body)
|
||
assert_equal 1, response['total']
|
||
assert_equal "#{host} has been provisioned successfully", response['notifications'][0]["text"]
|
||
end
|
||
|
||
test "notification when host has no owner" do
|
||
host = FactoryGirl.create(:host, :managed)
|
||
User.current = nil
|
||
assert host.update_attributes(owner_id: nil, owner_type: nil, build: true)
|
||
assert_nil host.owner
|
||
assert host.built
|
||
get :index, { :format => 'json' }, set_session_user
|
||
assert_response :success
|
||
response = ActiveSupport::JSON.decode(@response.body)
|
||
assert_equal 1, response['total']
|
||
assert_equal "#{host} has no owner set", response['notifications'][0]["text"]
|
||
end
|
||
|
||
private
|
||
|
||
def add_notification
|
test/factories/notification.rb | ||
---|---|---|
notification_blueprint
|
||
association :initiator, :factory => :user
|
||
audience 'user'
|
||
subject User.first
|
||
end
|
||
end
|
test/factories/notification_blueprint.rb | ||
---|---|---|
sequence(:group) { |n| "notification_blueprint_#{n}" }
|
||
sequence(:message) { |n| "message_#{n}" }
|
||
sequence(:name) { |n| "name_#{n}" }
|
||
subject User.first
|
||
level 'info'
|
||
expires_in 24.hours
|
||
end
|
test/integration_test_helper.rb | ||
---|---|---|
require 'database_cleaner'
|
||
require 'active_support_test_case_helper'
|
||
require 'minitest-optional_retry'
|
||
# load notification blueprint seeds
|
||
require File.join(Rails.root,'db','seeds.d','17-notification_blueprints.rb')
|
||
|
||
DatabaseCleaner.strategy = :transaction
|
||
|
test/models/notification_test.rb | ||
---|---|---|
test 'should be able to create notification' do
|
||
blueprint = FactoryGirl.create(
|
||
:notification_blueprint,
|
||
:message => 'this test just executed successfully',
|
||
:subject => nil
|
||
:message => 'this test just executed successfully'
|
||
)
|
||
notice = FactoryGirl.create(:notification,
|
||
:audience => 'global',
|
||
:notification_blueprint => blueprint)
|
||
assert notice.valid?
|
||
assert_equal blueprint.message, notice.notification_blueprint.message
|
||
assert_equal blueprint.message, notice.message
|
||
assert_equal User.all, notice.recipients
|
||
end
|
||
|
||
... | ... | |
group.users = FactoryGirl.create_list(:user,25)
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_GROUP)
|
||
notification.notification_blueprint.subject = group
|
||
notification.subject = group
|
||
assert group.all_users.any?
|
||
assert_equal group.all_users.map(&:id),
|
||
notification.subscriber_ids
|
||
... | ... | |
org.users = FactoryGirl.create_list(:user,25)
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_TAXONOMY)
|
||
notification.notification_blueprint.subject = org
|
||
notification.subject = org
|
||
assert org.user_ids.any?
|
||
assert_equal org.user_ids, notification.subscriber_ids
|
||
end
|
||
... | ... | |
loc.users = FactoryGirl.create_list(:user,25)
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_TAXONOMY)
|
||
notification.notification_blueprint.subject = loc
|
||
notification.subject = loc
|
||
assert loc.user_ids.any?
|
||
assert_equal loc.user_ids, notification.subscriber_ids
|
||
end
|
||
... | ... | |
assert_equal User.only_admin.reorder('').pluck(:id).sort,
|
||
notification.subscriber_ids.sort
|
||
end
|
||
|
||
test 'notification message should be stored' do
|
||
host = FactoryGirl.create(:host)
|
||
blueprint = FactoryGirl.create(
|
||
:notification_blueprint,
|
||
:message => "%{subject} has been lost",
|
||
:level => 'error'
|
||
)
|
||
notice = FactoryGirl.create(:notification,
|
||
:audience => 'global',
|
||
:subject => host,
|
||
:notification_blueprint => blueprint)
|
||
assert_equal "#{host} has been lost", notice.message
|
||
end
|
||
end
|
test/test_helper.rb | ||
---|---|---|
require 'facet_test_helper'
|
||
require 'active_support_test_case_helper'
|
||
|
||
# load notification blueprint seeds
|
||
require File.join(Rails.root,'db','seeds.d','17-notification_blueprints.rb')
|
||
|
||
Shoulda::Matchers.configure do |config|
|
||
config.integrate do |with|
|
||
with.test_framework :minitest_4
|
test/unit/ui_notifications/base_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class UINotificationsTest < ActiveSupport::TestCase
|
||
test 'notification should raise without a subject' do
|
||
assert_raise(Foreman::Exception) { UINotifications::Base.new(nil) }
|
||
end
|
||
|
||
test 'notification logger exists' do
|
||
assert_equal 'notifications', Foreman::Logging.logger('notifications').name
|
||
end
|
||
|
||
test 'event should default to class name' do
|
||
class Event < ::UINotifications::Base; end
|
||
assert_equal "Event", Event.new(Object.new).send(:event)
|
||
end
|
||
end
|
test/unit/ui_notifications/hosts/base_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class UINotificationsHostsTest < ActiveSupport::TestCase
|
||
test 'notification audience should be user' do
|
||
host.owner = FactoryGirl.build(:user)
|
||
assert_equal 'user', audience
|
||
end
|
||
|
||
test 'notification audience should be usergroup' do
|
||
host.owner = FactoryGirl.build(:usergroup)
|
||
assert_equal 'usergroup', audience
|
||
end
|
||
|
||
test 'notification audience should be nil if there is no owner' do
|
||
host.owner = nil
|
||
assert_nil audience
|
||
end
|
||
|
||
test 'deliver! should not run if audience is nil' do
|
||
host.owner = nil
|
||
assert !base.deliver!
|
||
end
|
||
|
||
private
|
||
|
||
def host
|
||
@host ||= FactoryGirl.build(:host, :managed)
|
||
end
|
||
|
||
def base
|
||
UINotifications::Hosts::Base.new(host)
|
||
end
|
||
|
||
def audience
|
||
base.send(:audience)
|
||
end
|
||
end
|
test/unit/ui_notifications/hosts/build_completed_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class UINotificationsHostsBuildCompletedTest < ActiveSupport::TestCase
|
||
test 'create new host build notification' do
|
||
host.update_attribute(:build, true)
|
||
assert_difference("blueprint.notifications.where(:subject => host).count", 1) do
|
||
assert host.built
|
||
end
|
||
end
|
||
|
||
test 'multiple build events should update current build notification' do
|
||
assert_difference("Notification.where(:subject => host).count", 1) do
|
||
host.built
|
||
host.setBuild
|
||
host.built
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
def host
|
||
@host ||= FactoryGirl.create(:host, :managed)
|
||
end
|
||
|
||
def blueprint
|
||
@blueprint ||= NotificationBlueprint.find_by(name: 'host_build_completed')
|
||
end
|
||
end
|
test/unit/ui_notifications/hosts/destroy_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class UINotificationsHostsDestroyTest < ActiveSupport::TestCase
|
||
test 'destroying a host should create a notification' do
|
||
assert_equal 0, Notification.where(:subject => host).count
|
||
host.destroy
|
||
# destory events do not store subject as its being deleted.
|
||
assert_equal 0, Notification.where(:subject => host).count
|
||
assert_equal 1, Notification.where(
|
||
notification_blueprint: blueprint,
|
||
message: "#{host} has been deleted successfully"
|
||
).count
|
||
end
|
||
|
||
test 'destorying host should remove other notification' do
|
||
assert_difference("Notification.where(:subject => host).count", 1) do
|
||
UINotifications::Hosts::BuildCompleted.deliver!(host)
|
||
end
|
||
UINotifications::Hosts::Destroy.deliver!(host)
|
||
assert_equal 0, Notification.where(:subject => host).count
|
||
assert_equal 1, blueprint.notifications.count
|
||
end
|
||
|
||
private
|
||
|
||
def host
|
||
@host ||= FactoryGirl.create(:host, :managed)
|
||
end
|
||
|
||
def blueprint
|
||
@blueprint ||= NotificationBlueprint.find_by(name: 'host_destroyed')
|
||
end
|
||
end
|
test/unit/ui_notifications/hosts/missing_owner_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class UINotificationsHostsMissingOwnerTest < ActiveSupport::TestCase
|
||
test 'add missing host owner notification' do
|
||
assert_difference("Notification.where(:subject => host).count", 1) do
|
||
UINotifications::Hosts::MissingOwner.deliver!(host)
|
||
end
|
||
end
|
||
|
||
test 'multiple build events should update current build notification' do
|
||
assert_difference("Notification.where(:subject => host).count", 1) do
|
||
UINotifications::Hosts::MissingOwner.deliver!(host)
|
||
UINotifications::Hosts::MissingOwner.deliver!(host)
|
||
end
|
||
end
|
||
private
|
||
|
||
def host
|
||
@host ||= FactoryGirl.create(:host, :managed)
|
||
end
|
||
|
||
def blueprint
|
||
@blueprint ||= NotificationBlueprint.find_by(name: 'host_missing_owner')
|
||
end
|
||
end
|
test/unit/ui_notifications/string_parser_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class UINotificationsTest < ActiveSupport::TestCase
|
||
test 'should parse a messsage with a subject' do
|
||
subject = FactoryGirl.create(:host)
|
||
template = "hello %{subject}"
|
||
options = {subject: subject}
|
||
resolver = UINotifications::StringParser.new(template, options)
|
||
assert_equal "hello #{subject}", resolver.to_s
|
||
end
|
||
|
||
test 'should parse a messsage with a subject twice' do
|
||
subject = FactoryGirl.create(:host)
|
||
template = "hello %{subject} / %{subject}"
|
||
options = {subject: subject}
|
||
resolver = UINotifications::StringParser.new(template, options)
|
||
assert_equal "hello #{subject} / #{subject}", resolver.to_s
|
||
end
|
||
end
|
test/unit/ui_notifications/url_resolver_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class UINotificationsTest < ActiveSupport::TestCase
|
||
test 'should parse a hard coded url' do
|
||
actions = {links: [ {href: '/static_path', title: 'some hard coded url'}] }
|
||
resolver = UINotifications::URLResolver.new(subject, actions)
|
||
assert_equal actions, resolver.actions
|
||
end
|
||
|
||
test 'should parse a dynamic url for a given subject' do
|
||
subject = FactoryGirl.create(:host)
|
||
actions = {links: [ {path_method: :host_path, title: 'link_to_host'}] }
|
||
resolver = UINotifications::URLResolver.new(subject, actions)
|
||
assert_equal resolver.actions, {links: [{href: "/hosts/#{subject}", title: 'link_to_host'}]}
|
||
end
|
||
|
||
test 'should parse a url title for a given subject' do
|
||
subject = FactoryGirl.create(:host)
|
||
actions = {links: [ {path_method: :edit_host_path, title: "edit %{subject}"}] }
|
||
resolver = UINotifications::URLResolver.new(subject, actions)
|
||
assert_equal resolver.actions, {links: [{href: "/hosts/#{subject}/edit", title: "edit #{subject}"}]}
|
||
end
|
||
|
||
test 'it should not accept path_method that does not edit with _path' do
|
||
actions = {links: [ {path_method: :somewhere, title: 'aha'} ] }
|
||
assert_raise(Foreman::Exception) { UINotifications::URLResolver.new(subject, actions).actions }
|
||
end
|
||
|
||
test 'it should not accept path_method that does not edit with _path' do
|
||
actions = {links: [ {path_method: :somewhere, title: 'aha'} ] }
|
||
assert_raise(Foreman::Exception) { UINotifications::URLResolver.new(subject, actions).actions }
|
||
end
|
||
|
||
test 'it should not accept empty titles' do
|
||
actions = {links: [ {href: '/somewhere'} ] }
|
||
assert_raise(Foreman::Exception) { UINotifications::URLResolver.new(subject, actions).actions }
|
||
end
|
||
|
||
test 'it should link to a collection url' do
|
||
actions = {links: [ {path_method: :bookmarks_path, title: 'bookmarks'} ] }
|
||
resolver = UINotifications::URLResolver.new(subject, actions)
|
||
assert_equal resolver.actions, {links: [{href: "/bookmarks", title: "bookmarks"}]}
|
||
end
|
||
end
|
Also available in: Unified diff
fixes #18681 - moves polymorphic subject into notification object
also adds host build, destroyed and missing owner UI notifications