mirror of
https://github.com/discourse/discourse.git
synced 2024-11-29 23:58:35 +08:00
098ab29d41
This commit introduces a new plugin API to register a group of stats that will be included in about.json and also conditionally in the site about UI at /about. The usage is like this: ```ruby register_about_stat_group("chat_messages", show_in_ui: true) do { last_day: 1, "7_days" => 10, "30_days" => 100, count: 1000, previous_30_days: 120 } end ``` In reality the stats will be generated any way the implementer chooses within the plugin. The `last_day`, `7_days`, `30_days,` and `count` keys must be present but apart from that additional stats may be added. Only those core 4 stat keys will be shown in the UI, but everything will be shown in about.json. The stat group name is used to prefix the stats in about.json like so: ```json "chat_messages_last_day": 2322, "chat_messages_7_days": 2322, "chat_messages_30_days": 2322, "chat_messages_count": 2322, ``` The `show_in_ui` option (default false) is used to determine whether the group of stats is shown on the site About page in the Site Statistics table. Some stats may be needed purely for reporting purposes and thus do not need to be shown in the UI to admins/users. An extension to the Site serializer, `displayed_about_plugin_stat_groups`, has been added so this can be inspected on the client-side.
777 lines
25 KiB
Ruby
777 lines
25 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe Plugin::Instance do
|
|
|
|
after do
|
|
DiscoursePluginRegistry.reset!
|
|
end
|
|
|
|
context "find_all" do
|
|
it "can find plugins correctly" do
|
|
plugins = Plugin::Instance.find_all("#{Rails.root}/spec/fixtures/plugins")
|
|
expect(plugins.count).to eq(5)
|
|
plugin = plugins[3]
|
|
|
|
expect(plugin.name).to eq("plugin-name")
|
|
expect(plugin.path).to eq("#{Rails.root}/spec/fixtures/plugins/my_plugin/plugin.rb")
|
|
end
|
|
|
|
it "does not blow up on missing directory" do
|
|
plugins = Plugin::Instance.find_all("#{Rails.root}/frank_zappa")
|
|
expect(plugins.count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "enabling/disabling" do
|
|
|
|
it "is enabled by default" do
|
|
expect(Plugin::Instance.new.enabled?).to eq(true)
|
|
end
|
|
|
|
context "with a plugin that extends things" do
|
|
|
|
class Trout
|
|
attr_accessor :data
|
|
end
|
|
|
|
class TroutSerializer < ApplicationSerializer
|
|
attribute :name
|
|
|
|
def name
|
|
"a trout"
|
|
end
|
|
end
|
|
class TroutJuniorSerializer < TroutSerializer
|
|
|
|
attribute :i_am_child
|
|
|
|
def name
|
|
"a trout jr"
|
|
end
|
|
|
|
def i_am_child
|
|
true
|
|
end
|
|
end
|
|
|
|
class TroutPlugin < Plugin::Instance
|
|
attr_accessor :enabled
|
|
def enabled?
|
|
@enabled
|
|
end
|
|
end
|
|
|
|
before do
|
|
@plugin = TroutPlugin.new
|
|
@trout = Trout.new
|
|
|
|
poison = TroutSerializer.new(@trout)
|
|
poison.attributes
|
|
|
|
poison = TroutJuniorSerializer.new(@trout)
|
|
poison.attributes
|
|
|
|
# New method
|
|
@plugin.add_to_class(:trout, :status?) { "evil" }
|
|
|
|
# DiscourseEvent
|
|
@hello_count = 0
|
|
@increase_count = -> { @hello_count += 1 }
|
|
@set = @plugin.on(:hello, &@increase_count)
|
|
|
|
# Serializer
|
|
@plugin.add_to_serializer(:trout, :scales) { 1024 }
|
|
|
|
@serializer = TroutSerializer.new(@trout)
|
|
@child_serializer = TroutJuniorSerializer.new(@trout)
|
|
end
|
|
|
|
after do
|
|
DiscourseEvent.off(:hello, &@set.first)
|
|
end
|
|
|
|
it "checks enabled/disabled functionality for extensions" do
|
|
# with an enabled plugin
|
|
@plugin.enabled = true
|
|
expect(@trout.status?).to eq("evil")
|
|
DiscourseEvent.trigger(:hello)
|
|
expect(@hello_count).to eq(1)
|
|
expect(@serializer.scales).to eq(1024)
|
|
expect(@serializer.include_scales?).to eq(true)
|
|
|
|
expect(@child_serializer.attributes[:scales]).to eq(1024)
|
|
|
|
# When a plugin is disabled
|
|
@plugin.enabled = false
|
|
expect(@trout.status?).to eq(nil)
|
|
DiscourseEvent.trigger(:hello)
|
|
expect(@hello_count).to eq(1)
|
|
expect(@serializer.scales).to eq(1024)
|
|
expect(@serializer.include_scales?).to eq(false)
|
|
expect(@serializer.name).to eq("a trout")
|
|
|
|
expect(@child_serializer.scales).to eq(1024)
|
|
expect(@child_serializer.include_scales?).to eq(false)
|
|
expect(@child_serializer.name).to eq("a trout jr")
|
|
end
|
|
|
|
it "only returns HTML if enabled" do
|
|
ctx = Trout.new
|
|
ctx.data = "hello"
|
|
|
|
@plugin.register_html_builder('test:html') { |c| "<div>#{c.data}</div>" }
|
|
@plugin.enabled = false
|
|
expect(DiscoursePluginRegistry.build_html('test:html', ctx)).to eq("")
|
|
@plugin.enabled = true
|
|
expect(DiscoursePluginRegistry.build_html('test:html', ctx)).to eq("<div>hello</div>")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "register asset" do
|
|
it "populates the DiscoursePluginRegistry" do
|
|
plugin = Plugin::Instance.new nil, "/tmp/test.rb"
|
|
plugin.register_asset("test.css")
|
|
plugin.register_asset("test2.css")
|
|
|
|
plugin.send :register_assets!
|
|
|
|
expect(DiscoursePluginRegistry.mobile_stylesheets[plugin.directory_name]).to be_nil
|
|
expect(DiscoursePluginRegistry.stylesheets[plugin.directory_name].count).to eq(2)
|
|
end
|
|
|
|
it "remaps vendored_core_pretty_text asset" do
|
|
plugin = Plugin::Instance.new nil, "/tmp/test.rb"
|
|
plugin.register_asset("moment.js", :vendored_core_pretty_text)
|
|
|
|
plugin.send :register_assets!
|
|
|
|
expect(DiscoursePluginRegistry.vendored_core_pretty_text.first).to eq("vendor/assets/javascripts/moment.js")
|
|
end
|
|
end
|
|
|
|
context "register service worker" do
|
|
it "populates the DiscoursePluginRegistry" do
|
|
plugin = Plugin::Instance.new nil, "/tmp/test.rb"
|
|
plugin.register_service_worker("test.js")
|
|
plugin.register_service_worker("test2.js")
|
|
|
|
plugin.send :register_service_workers!
|
|
|
|
expect(DiscoursePluginRegistry.service_workers.count).to eq(2)
|
|
end
|
|
end
|
|
|
|
context "#add_report" do
|
|
it "adds a report" do
|
|
plugin = Plugin::Instance.new nil, "/tmp/test.rb"
|
|
plugin.add_report("readers") {}
|
|
|
|
expect(Report.respond_to?(:report_readers)).to eq(true)
|
|
end
|
|
end
|
|
|
|
it 'patches the enabled? function for auth_providers if not defined' do
|
|
SimpleAuthenticator = Class.new(Auth::Authenticator) do
|
|
def name
|
|
"my_authenticator"
|
|
end
|
|
end
|
|
|
|
plugin = Plugin::Instance.new
|
|
|
|
# lets piggy back on another boolean setting, so we don't dirty our SiteSetting object
|
|
SiteSetting.enable_badges = false
|
|
|
|
# No enabled_site_setting
|
|
authenticator = SimpleAuthenticator.new
|
|
plugin.auth_provider(authenticator: authenticator)
|
|
plugin.notify_before_auth
|
|
expect(authenticator.enabled?).to eq(true)
|
|
|
|
# With enabled site setting
|
|
plugin = Plugin::Instance.new
|
|
authenticator = SimpleAuthenticator.new
|
|
plugin.auth_provider(enabled_setting: 'enable_badges', authenticator: authenticator)
|
|
plugin.notify_before_auth
|
|
expect(authenticator.enabled?).to eq(false)
|
|
|
|
# Defines own method
|
|
plugin = Plugin::Instance.new
|
|
|
|
SiteSetting.enable_badges = true
|
|
authenticator = Class.new(SimpleAuthenticator) do
|
|
def enabled?
|
|
false
|
|
end
|
|
end.new
|
|
plugin.auth_provider(enabled_setting: 'enable_badges', authenticator: authenticator)
|
|
plugin.notify_before_auth
|
|
expect(authenticator.enabled?).to eq(false)
|
|
end
|
|
|
|
context "activate!" do
|
|
before do
|
|
# lets piggy back on another boolean setting, so we don't dirty our SiteSetting object
|
|
SiteSetting.enable_badges = false
|
|
end
|
|
|
|
it "can activate plugins correctly" do
|
|
plugin = Plugin::Instance.new
|
|
plugin.path = "#{Rails.root}/spec/fixtures/plugins/my_plugin/plugin.rb"
|
|
junk_file = "#{plugin.auto_generated_path}/junk"
|
|
|
|
plugin.ensure_directory(junk_file)
|
|
File.open("#{plugin.auto_generated_path}/junk", "w") { |f| f.write("junk") }
|
|
plugin.activate!
|
|
|
|
# calls ensure_assets! make sure they are there
|
|
expect(plugin.assets.count).to eq(1)
|
|
plugin.assets.each do |a, opts|
|
|
expect(File.exist?(a)).to eq(true)
|
|
end
|
|
|
|
# ensure it cleans up all crap in autogenerated directory
|
|
expect(File.exist?(junk_file)).to eq(false)
|
|
end
|
|
|
|
it "registers auth providers correctly" do
|
|
plugin = Plugin::Instance.new
|
|
plugin.path = "#{Rails.root}/spec/fixtures/plugins/my_plugin/plugin.rb"
|
|
plugin.activate!
|
|
expect(DiscoursePluginRegistry.auth_providers.count).to eq(0)
|
|
plugin.notify_before_auth
|
|
expect(DiscoursePluginRegistry.auth_providers.count).to eq(1)
|
|
auth_provider = DiscoursePluginRegistry.auth_providers.to_a[0]
|
|
expect(auth_provider.authenticator.name).to eq('facebook')
|
|
end
|
|
|
|
it "finds all the custom assets" do
|
|
plugin = Plugin::Instance.new
|
|
plugin.path = "#{Rails.root}/spec/fixtures/plugins/my_plugin/plugin.rb"
|
|
|
|
plugin.register_asset("test.css")
|
|
plugin.register_asset("test2.scss")
|
|
plugin.register_asset("mobile.css", :mobile)
|
|
plugin.register_asset("desktop.css", :desktop)
|
|
plugin.register_asset("desktop2.css", :desktop)
|
|
|
|
plugin.register_asset("code.js")
|
|
|
|
plugin.register_asset("my_admin.js", :admin)
|
|
plugin.register_asset("my_admin2.js", :admin)
|
|
|
|
plugin.activate!
|
|
|
|
expect(DiscoursePluginRegistry.javascripts.count).to eq(2)
|
|
expect(DiscoursePluginRegistry.admin_javascripts.count).to eq(2)
|
|
expect(DiscoursePluginRegistry.desktop_stylesheets[plugin.directory_name].count).to eq(2)
|
|
expect(DiscoursePluginRegistry.stylesheets[plugin.directory_name].count).to eq(2)
|
|
expect(DiscoursePluginRegistry.mobile_stylesheets[plugin.directory_name].count).to eq(1)
|
|
end
|
|
end
|
|
|
|
context "serialized_current_user_fields" do
|
|
before do
|
|
DiscoursePluginRegistry.serialized_current_user_fields << "has_car"
|
|
end
|
|
|
|
after do
|
|
DiscoursePluginRegistry.serialized_current_user_fields.delete "has_car"
|
|
end
|
|
|
|
it "correctly serializes custom user fields" do
|
|
DiscoursePluginRegistry.serialized_current_user_fields << "has_car"
|
|
user = Fabricate(:user)
|
|
user.custom_fields["has_car"] = "true"
|
|
user.save!
|
|
|
|
payload = JSON.parse(CurrentUserSerializer.new(user, scope: Guardian.new(user)).to_json)
|
|
expect(payload["current_user"]["custom_fields"]["has_car"]).to eq("true")
|
|
|
|
payload = JSON.parse(UserSerializer.new(user, scope: Guardian.new(user)).to_json)
|
|
expect(payload["user"]["custom_fields"]["has_car"]).to eq("true")
|
|
|
|
UserCustomField.destroy_all
|
|
user.reload
|
|
|
|
payload = JSON.parse(CurrentUserSerializer.new(user, scope: Guardian.new(user)).to_json)
|
|
expect(payload["current_user"]["custom_fields"]).to eq({})
|
|
|
|
payload = JSON.parse(UserSerializer.new(user, scope: Guardian.new(user)).to_json)
|
|
expect(payload["user"]["custom_fields"]).to eq({})
|
|
end
|
|
end
|
|
|
|
context "register_color_scheme" do
|
|
it "can add a color scheme for the first time" do
|
|
plugin = Plugin::Instance.new nil, "/tmp/test.rb"
|
|
expect {
|
|
plugin.register_color_scheme("Purple", primary: 'EEE0E5')
|
|
plugin.notify_after_initialize
|
|
}.to change { ColorScheme.count }.by(1)
|
|
expect(ColorScheme.where(name: "Purple")).to be_present
|
|
end
|
|
|
|
it "doesn't add the same color scheme twice" do
|
|
Fabricate(:color_scheme, name: "Halloween")
|
|
plugin = Plugin::Instance.new nil, "/tmp/test.rb"
|
|
expect {
|
|
plugin.register_color_scheme("Halloween", primary: 'EEE0E5')
|
|
plugin.notify_after_initialize
|
|
}.to_not change { ColorScheme.count }
|
|
end
|
|
end
|
|
|
|
describe '.register_seedfu_fixtures' do
|
|
it "should add the new path to SeedFu's fixtures path" do
|
|
plugin = Plugin::Instance.new nil, "/tmp/test.rb"
|
|
plugin.register_seedfu_fixtures(['some_path'])
|
|
plugin.register_seedfu_fixtures('some_path2')
|
|
|
|
expect(SeedFu.fixture_paths).to include('some_path')
|
|
expect(SeedFu.fixture_paths).to include('some_path2')
|
|
end
|
|
end
|
|
|
|
describe '#add_model_callback' do
|
|
let(:metadata) do
|
|
metadata = Plugin::Metadata.new
|
|
metadata.name = 'test'
|
|
metadata
|
|
end
|
|
|
|
let(:plugin_instance) do
|
|
plugin = Plugin::Instance.new(nil, "/tmp/test.rb")
|
|
plugin.metadata = metadata
|
|
plugin
|
|
end
|
|
|
|
it 'should add the right callback' do
|
|
called = 0
|
|
|
|
plugin_instance.add_model_callback(User, :after_create) do
|
|
called += 1
|
|
end
|
|
|
|
user = Fabricate(:user)
|
|
|
|
expect(called).to eq(1)
|
|
|
|
user.update!(username: 'some_username')
|
|
|
|
expect(called).to eq(1)
|
|
end
|
|
|
|
it 'should add the right callback with options' do
|
|
called = 0
|
|
|
|
plugin_instance.add_model_callback(User, :after_commit, on: :create) do
|
|
called += 1
|
|
end
|
|
|
|
user = Fabricate(:user)
|
|
|
|
expect(called).to eq(1)
|
|
|
|
user.update!(username: 'some_username')
|
|
|
|
expect(called).to eq(1)
|
|
end
|
|
end
|
|
|
|
context "locales" do
|
|
let(:plugin_path) { "#{Rails.root}/spec/fixtures/plugins/custom_locales" }
|
|
let!(:plugin) { Plugin::Instance.new(nil, "#{plugin_path}/plugin.rb") }
|
|
let(:plural) do
|
|
{
|
|
keys: [:one, :few, :other],
|
|
rule: lambda do |n|
|
|
return :one if n == 1
|
|
return :few if n < 10
|
|
:other
|
|
end
|
|
}
|
|
end
|
|
|
|
def register_locale(locale, opts)
|
|
plugin.register_locale(locale, opts)
|
|
plugin.activate!
|
|
|
|
DiscoursePluginRegistry.locales[locale]
|
|
end
|
|
|
|
it "enables the registered locales only on activate" do
|
|
plugin.register_locale("foo_BAR", name: "Foo", nativeName: "Foo Bar", plural: plural)
|
|
plugin.register_locale("tup", name: "Tupi", nativeName: "Tupi", fallbackLocale: "pt_BR")
|
|
expect(DiscoursePluginRegistry.locales.count).to eq(0)
|
|
|
|
plugin.activate!
|
|
|
|
expect(DiscoursePluginRegistry.locales.count).to eq(2)
|
|
end
|
|
|
|
it "allows finding the locale by string and symbol" do
|
|
register_locale("foo_BAR", name: "Foo", nativeName: "Foo Bar", plural: plural)
|
|
|
|
expect(DiscoursePluginRegistry.locales).to have_key(:foo_BAR)
|
|
expect(DiscoursePluginRegistry.locales).to have_key('foo_BAR')
|
|
end
|
|
|
|
it "correctly registers a new locale" do
|
|
locale = register_locale("foo_BAR", name: "Foo", nativeName: "Foo Bar", plural: plural)
|
|
|
|
expect(DiscoursePluginRegistry.locales.count).to eq(1)
|
|
expect(DiscoursePluginRegistry.locales).to have_key(:foo_BAR)
|
|
|
|
expect(locale[:fallbackLocale]).to be_nil
|
|
expect(locale[:message_format]).to eq(["foo_BAR", "#{plugin_path}/lib/javascripts/locale/message_format/foo_BAR.js"])
|
|
expect(locale[:moment_js]).to eq(["foo_BAR", "#{plugin_path}/lib/javascripts/locale/moment_js/foo_BAR.js"])
|
|
expect(locale[:moment_js_timezones]).to eq(["foo_BAR", "#{plugin_path}/lib/javascripts/locale/moment_js_timezones/foo_BAR.js"])
|
|
expect(locale[:plural]).to eq(plural.with_indifferent_access)
|
|
|
|
expect(Rails.configuration.assets.precompile).to include("locales/foo_BAR.js")
|
|
|
|
expect(JsLocaleHelper.find_message_format_locale(["foo_BAR"], fallback_to_english: true)).to eq(locale[:message_format])
|
|
expect(JsLocaleHelper.find_moment_locale(["foo_BAR"])).to eq (locale[:moment_js])
|
|
expect(JsLocaleHelper.find_moment_locale(["foo_BAR"], timezone_names: true)).to eq (locale[:moment_js_timezones])
|
|
end
|
|
|
|
it "correctly registers a new locale using a fallback locale" do
|
|
locale = register_locale("tup", name: "Tupi", nativeName: "Tupi", fallbackLocale: "pt_BR")
|
|
|
|
expect(DiscoursePluginRegistry.locales.count).to eq(1)
|
|
expect(DiscoursePluginRegistry.locales).to have_key(:tup)
|
|
|
|
expect(locale[:fallbackLocale]).to eq("pt_BR")
|
|
expect(locale[:message_format]).to eq(["pt_BR", "#{Rails.root}/lib/javascripts/locale/pt_BR.js"])
|
|
expect(locale[:moment_js]).to eq(["pt-br", "#{Rails.root}/vendor/assets/javascripts/moment-locale/pt-br.js"])
|
|
expect(locale[:moment_js_timezones]).to eq(["pt", "#{Rails.root}/vendor/assets/javascripts/moment-timezone-names-locale/pt.js"])
|
|
expect(locale[:plural]).to be_nil
|
|
|
|
expect(Rails.configuration.assets.precompile).to include("locales/tup.js")
|
|
|
|
expect(JsLocaleHelper.find_message_format_locale(["tup"], fallback_to_english: true)).to eq(locale[:message_format])
|
|
expect(JsLocaleHelper.find_moment_locale(["tup"])).to eq (locale[:moment_js])
|
|
end
|
|
|
|
it "correctly registers a new locale when some files exist in core" do
|
|
locale = register_locale("tlh", name: "Klingon", nativeName: "tlhIngan Hol", plural: plural)
|
|
|
|
expect(DiscoursePluginRegistry.locales.count).to eq(1)
|
|
expect(DiscoursePluginRegistry.locales).to have_key(:tlh)
|
|
|
|
expect(locale[:fallbackLocale]).to be_nil
|
|
expect(locale[:message_format]).to eq(["tlh", "#{plugin_path}/lib/javascripts/locale/message_format/tlh.js"])
|
|
expect(locale[:moment_js]).to eq(["tlh", "#{Rails.root}/vendor/assets/javascripts/moment-locale/tlh.js"])
|
|
expect(locale[:plural]).to eq(plural.with_indifferent_access)
|
|
|
|
expect(Rails.configuration.assets.precompile).to include("locales/tlh.js")
|
|
|
|
expect(JsLocaleHelper.find_message_format_locale(["tlh"], fallback_to_english: true)).to eq(locale[:message_format])
|
|
expect(JsLocaleHelper.find_moment_locale(["tlh"])).to eq (locale[:moment_js])
|
|
end
|
|
|
|
it "does not register a new locale when the fallback locale does not exist" do
|
|
register_locale("bar", name: "Bar", nativeName: "Bar", fallbackLocale: "foo")
|
|
expect(DiscoursePluginRegistry.locales.count).to eq(0)
|
|
end
|
|
|
|
[
|
|
"config/locales/client.foo_BAR.yml",
|
|
"config/locales/server.foo_BAR.yml",
|
|
"lib/javascripts/locale/message_format/foo_BAR.js",
|
|
"lib/javascripts/locale/moment_js/foo_BAR.js",
|
|
"assets/locales/foo_BAR.js.erb"
|
|
].each do |path|
|
|
it "does not register a new locale when #{path} is missing" do
|
|
path = "#{plugin_path}/#{path}"
|
|
File.stubs('exist?').returns(false)
|
|
File.stubs('exist?').with(regexp_matches(/#{Regexp.quote(plugin_path)}.*/)).returns(true)
|
|
File.stubs('exist?').with(path).returns(false)
|
|
|
|
register_locale("foo_BAR", name: "Foo", nativeName: "Foo Bar", plural: plural)
|
|
expect(DiscoursePluginRegistry.locales.count).to eq(0)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#register_reviewable_types' do
|
|
it 'Overrides the existing Reviewable types adding new ones' do
|
|
current_types = Reviewable.types
|
|
new_type_class = Class
|
|
|
|
Plugin::Instance.new.register_reviewable_type new_type_class
|
|
|
|
expect(Reviewable.types).to match_array(current_types << new_type_class.name)
|
|
end
|
|
end
|
|
|
|
describe '#extend_list_method' do
|
|
it 'Overrides the existing list appending new elements' do
|
|
current_list = Reviewable.types
|
|
new_element = Class.name
|
|
|
|
Plugin::Instance.new.extend_list_method Reviewable, :types, [new_element]
|
|
|
|
expect(Reviewable.types).to match_array(current_list << new_element)
|
|
end
|
|
end
|
|
|
|
describe '#register_emoji' do
|
|
before do
|
|
Plugin::CustomEmoji.clear_cache
|
|
end
|
|
|
|
after do
|
|
Plugin::CustomEmoji.clear_cache
|
|
end
|
|
|
|
it 'allows to register an emoji' do
|
|
Plugin::Instance.new.register_emoji("foo", "/foo/bar.png")
|
|
|
|
custom_emoji = Emoji.custom.first
|
|
|
|
expect(custom_emoji.name).to eq("foo")
|
|
expect(custom_emoji.url).to eq("/foo/bar.png")
|
|
expect(custom_emoji.group).to eq(Emoji::DEFAULT_GROUP)
|
|
end
|
|
|
|
it 'allows to register an emoji with a group' do
|
|
Plugin::Instance.new.register_emoji("bar", "/baz/bar.png", "baz")
|
|
|
|
custom_emoji = Emoji.custom.first
|
|
|
|
expect(custom_emoji.name).to eq("bar")
|
|
expect(custom_emoji.url).to eq("/baz/bar.png")
|
|
expect(custom_emoji.group).to eq("baz")
|
|
end
|
|
end
|
|
|
|
describe '#replace_flags' do
|
|
after do
|
|
PostActionType.replace_flag_settings(nil)
|
|
ReviewableScore.reload_types
|
|
end
|
|
|
|
let(:original_flags) { PostActionType.flag_settings }
|
|
|
|
it 'adds a new flag' do
|
|
highest_flag_id = ReviewableScore.types.values.max
|
|
flag_name = :new_flag
|
|
|
|
subject.replace_flags(settings: original_flags) do |settings, next_flag_id|
|
|
settings.add(
|
|
next_flag_id,
|
|
flag_name
|
|
)
|
|
end
|
|
|
|
expect(PostActionType.flag_settings.flag_types.keys).to include(flag_name)
|
|
expect(PostActionType.flag_settings.flag_types.values.max).to eq(highest_flag_id + 1)
|
|
end
|
|
|
|
it 'adds a new score type after adding a new flag' do
|
|
highest_flag_id = ReviewableScore.types.values.max
|
|
new_score_type = :new_score_type
|
|
|
|
subject.replace_flags(settings: original_flags, score_type_names: [new_score_type]) do |settings, next_flag_id|
|
|
settings.add(
|
|
next_flag_id,
|
|
:new_flag
|
|
)
|
|
end
|
|
|
|
expect(PostActionType.flag_settings.flag_types.values.max).to eq(highest_flag_id + 1)
|
|
expect(ReviewableScore.types.keys).to include(new_score_type)
|
|
expect(ReviewableScore.types.values.max).to eq(highest_flag_id + 2)
|
|
end
|
|
end
|
|
|
|
describe '#add_api_key_scope' do
|
|
after { DiscoursePluginRegistry.reset! }
|
|
|
|
it 'adds a custom api key scope' do
|
|
actions = %w[admin/groups#create]
|
|
subject.add_api_key_scope(:groups, create: { actions: actions })
|
|
|
|
expect(ApiKeyScope.scope_mappings.dig(:groups, :create, :actions)).to contain_exactly(*actions)
|
|
end
|
|
end
|
|
|
|
describe '#add_directory_column' do
|
|
let!(:plugin) { Plugin::Instance.new }
|
|
|
|
before do
|
|
DirectoryItem.clear_plugin_queries
|
|
end
|
|
|
|
after do
|
|
DirectoryColumn.clear_plugin_directory_columns
|
|
end
|
|
|
|
describe "with valid column name" do
|
|
let(:column_name) { "random_c" }
|
|
|
|
before do
|
|
DB.exec("ALTER TABLE directory_items ADD COLUMN IF NOT EXISTS #{column_name} integer")
|
|
end
|
|
|
|
after do
|
|
DB.exec("ALTER TABLE directory_items DROP COLUMN IF EXISTS #{column_name}")
|
|
DiscourseEvent.all_off("before_directory_refresh")
|
|
end
|
|
|
|
it 'creates a directory column record when directory items are refreshed' do
|
|
plugin.add_directory_column(column_name, query: "SELECT COUNT(*) FROM users", icon: 'recycle')
|
|
expect(DirectoryColumn.find_by(name: column_name, icon: 'recycle', enabled: false)).not_to be_present
|
|
|
|
DirectoryItem.refresh!
|
|
expect(DirectoryColumn.find_by(name: column_name, icon: 'recycle', enabled: false)).to be_present
|
|
end
|
|
end
|
|
|
|
it 'errors when the column_name contains invalid characters' do
|
|
expect {
|
|
plugin.add_directory_column('Capital', query: "SELECT COUNT(*) FROM users", icon: 'recycle')
|
|
}.to raise_error(RuntimeError)
|
|
|
|
expect {
|
|
plugin.add_directory_column('has space', query: "SELECT COUNT(*) FROM users", icon: 'recycle')
|
|
}.to raise_error(RuntimeError)
|
|
|
|
expect {
|
|
plugin.add_directory_column('has_number_1', query: "SELECT COUNT(*) FROM users", icon: 'recycle')
|
|
}.to raise_error(RuntimeError)
|
|
end
|
|
end
|
|
|
|
describe '#register_site_categories_callback' do
|
|
fab!(:category) { Fabricate(:category) }
|
|
|
|
it 'adds a callback to the Site#categories' do
|
|
instance = Plugin::Instance.new
|
|
|
|
instance.register_site_categories_callback do |categories|
|
|
categories.each do |category|
|
|
category[:test_field] = "test"
|
|
end
|
|
end
|
|
|
|
site = Site.new(Guardian.new)
|
|
|
|
expect(site.categories.first[:test_field]).to eq("test")
|
|
ensure
|
|
Site.clear_cache
|
|
Site.categories_callbacks.clear
|
|
end
|
|
end
|
|
|
|
describe '#register_notification_consolidation_plan' do
|
|
let(:plugin) { Plugin::Instance.new }
|
|
fab!(:topic) { Fabricate(:topic) }
|
|
|
|
after do
|
|
DiscoursePluginRegistry.reset_register!(:notification_consolidation_plans)
|
|
end
|
|
|
|
it 'fails when the received object is not a consolidation plan' do
|
|
expect { plugin.register_notification_consolidation_plan(Object.new) }.to raise_error(ArgumentError)
|
|
end
|
|
|
|
it 'registers a consolidation plan and uses it' do
|
|
plan = Notifications::ConsolidateNotifications.new(
|
|
from: Notification.types[:code_review_commit_approved],
|
|
to: Notification.types[:code_review_commit_approved],
|
|
threshold: 1,
|
|
consolidation_window: 1.minute,
|
|
unconsolidated_query_blk: ->(notifications, _data) do
|
|
notifications.where("(data::json ->> 'consolidated') IS NULL")
|
|
end,
|
|
consolidated_query_blk: ->(notifications, _data) do
|
|
notifications.where("(data::json ->> 'consolidated') IS NOT NULL")
|
|
end
|
|
).set_mutations(
|
|
set_data_blk: ->(notification) do
|
|
notification.data_hash.merge(consolidated: true)
|
|
end
|
|
)
|
|
|
|
plugin.register_notification_consolidation_plan(plan)
|
|
|
|
create_notification!
|
|
create_notification!
|
|
|
|
expect(commit_approved_notifications.count).to eq(1)
|
|
consolidated_notification = commit_approved_notifications.last
|
|
expect(consolidated_notification.data_hash[:consolidated]).to eq(true)
|
|
end
|
|
|
|
def commit_approved_notifications
|
|
Notification.where(user: topic.user, notification_type: Notification.types[:code_review_commit_approved])
|
|
end
|
|
|
|
def create_notification!
|
|
Notification.consolidate_or_create!(
|
|
notification_type: Notification.types[:code_review_commit_approved],
|
|
topic_id: topic.id,
|
|
user: topic.user,
|
|
data: {}
|
|
)
|
|
end
|
|
end
|
|
|
|
describe '#register_email_unsubscriber' do
|
|
let(:plugin) { Plugin::Instance.new }
|
|
|
|
after do
|
|
DiscoursePluginRegistry.reset_register!(:email_unsubscribers)
|
|
end
|
|
|
|
it "doesn't let you override core unsubscribers" do
|
|
expect { plugin.register_email_unsubscriber(UnsubscribeKey::ALL_TYPE, Object) }.to raise_error(ArgumentError)
|
|
end
|
|
|
|
it "finds the plugin's custom unsubscriber" do
|
|
new_unsubscriber_type = 'new_type'
|
|
key = UnsubscribeKey.new(unsubscribe_key_type: new_unsubscriber_type)
|
|
CustomUnsubscriber = Class.new(EmailControllerHelper::BaseEmailUnsubscriber)
|
|
|
|
plugin.register_email_unsubscriber(new_unsubscriber_type, CustomUnsubscriber)
|
|
|
|
expect(UnsubscribeKey.get_unsubscribe_strategy_for(key).class).to eq(CustomUnsubscriber)
|
|
end
|
|
end
|
|
|
|
describe "#register_about_stat_group" do
|
|
let(:plugin) { Plugin::Instance.new }
|
|
|
|
after do
|
|
About.clear_plugin_stat_groups
|
|
end
|
|
|
|
it "registers an about stat group correctly" do
|
|
stats = { last_day: 1, "7_days" => 10, "30_days" => 100, count: 1000 }
|
|
plugin.register_about_stat_group("some_group", show_in_ui: true) do
|
|
stats
|
|
end
|
|
expect(About.new.plugin_stats.with_indifferent_access).to match(
|
|
hash_including(
|
|
some_group_last_day: 1,
|
|
some_group_7_days: 10,
|
|
some_group_30_days: 100,
|
|
some_group_count: 1000,
|
|
)
|
|
)
|
|
end
|
|
|
|
it "hides the stat group from the UI by default" do
|
|
stats = { last_day: 1, "7_days" => 10, "30_days" => 100, count: 1000 }
|
|
plugin.register_about_stat_group("some_group") do
|
|
stats
|
|
end
|
|
expect(About.displayed_plugin_stat_groups).to eq([])
|
|
end
|
|
end
|
|
end
|