mirror of
https://github.com/discourse/discourse.git
synced 2024-12-14 19:26:28 +08:00
243fcb6ffc
This commit introduces the `run_theme_migration` spec helper to allow theme developers to write RSpec tests for theme migrations. For example, this allows the following RSpec test to be written in themes: ``` RSpec.describe "0003-migrate-small-links-setting migration" do let!(:theme) { upload_theme_component } it "should set target property to `_blank` if previous target component is not valid or empty" do theme.theme_settings.create!( name: "small_links", theme: theme, data_type: ThemeSetting.types[:string], value: "some text, #|some text 2, #, invalid target", ) run_theme_migration(theme, "0003-migrate-small-links-setting") expect(theme.settings[:small_links].value).to eq( [ { "text" => "some text", "url" => "#", "target" => "_blank" }, { "text" => "some text 2", "url" => "#", "target" => "_blank" }, ], ) end end ``` This change is being introduced because we realised that writting just javascript tests for the migrations is insufficient since javascript tests do not ensure that the migrated theme settings can actually be successfully saved into the database. Hence, we are introduce this helper as a way for theme developers to write "end-to-end" migrations tests.
299 lines
8.9 KiB
Ruby
299 lines
8.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
GIT_INITIAL_BRANCH_SUPPORTED =
|
|
Gem::Version.new(`git --version`.match(/[\d\.]+/)[0]) >= Gem::Version.new("2.28.0")
|
|
|
|
module Helpers
|
|
extend ActiveSupport::Concern
|
|
|
|
def self.next_seq
|
|
@next_seq = (@next_seq || 0) + 1
|
|
end
|
|
|
|
def log_in(fabricator = nil)
|
|
user = Fabricate(fabricator || :user)
|
|
log_in_user(user)
|
|
user
|
|
end
|
|
|
|
def log_in_user(user)
|
|
cookie_jar = ActionDispatch::Request.new(request.env).cookie_jar
|
|
provider = Discourse.current_user_provider.new(request.env)
|
|
provider.log_on_user(user, session, cookie_jar)
|
|
provider
|
|
end
|
|
|
|
def log_out_user(provider)
|
|
provider.log_off_user(session, cookies)
|
|
end
|
|
|
|
def fixture_file(filename)
|
|
return "" if filename.blank?
|
|
file_path = File.expand_path(Rails.root + "spec/fixtures/" + filename)
|
|
File.read(file_path)
|
|
end
|
|
|
|
def build(*args)
|
|
Fabricate.build(*args)
|
|
end
|
|
|
|
def create_topic(args = {})
|
|
args[:title] ||= "This is my title #{Helpers.next_seq}"
|
|
user = args.delete(:user)
|
|
user = Fabricate(:user, refresh_auto_groups: true) if !user
|
|
guardian = Guardian.new(user)
|
|
args[:category] = args[:category].id if args[:category].is_a?(Category)
|
|
TopicCreator.create(user, guardian, args)
|
|
end
|
|
|
|
def create_post(args = {})
|
|
# Pretty much all the tests with `create_post` will fail without this
|
|
# since allow_uncategorized_topics is now false by default
|
|
SiteSetting.allow_uncategorized_topics = true unless args[:allow_uncategorized_topics] == false
|
|
|
|
args[:title] ||= "This is my title #{Helpers.next_seq}"
|
|
args[:raw] ||= "This is the raw body of my post, it is cool #{Helpers.next_seq}"
|
|
args[:topic_id] = args[:topic].id if args[:topic]
|
|
user = args.delete(:user) || Fabricate(:user, refresh_auto_groups: true)
|
|
args[:category] = args[:category].id if args[:category].is_a?(Category)
|
|
creator = PostCreator.new(user, args)
|
|
post = creator.create
|
|
|
|
raise StandardError.new(creator.errors.full_messages.join(" ")) if creator.errors.present?
|
|
|
|
post
|
|
end
|
|
|
|
def stub_guardian(user)
|
|
guardian = Guardian.new(user)
|
|
yield(guardian) if block_given?
|
|
Guardian.stubs(new: guardian).with(user, anything)
|
|
end
|
|
|
|
def wait_for(on_fail: nil, timeout: 1, &blk)
|
|
i = 0
|
|
result = false
|
|
while !result && i < timeout * 1000
|
|
result = blk.call
|
|
i += 1
|
|
sleep 0.001
|
|
end
|
|
|
|
on_fail&.call
|
|
expect(result).to eq(true)
|
|
end
|
|
|
|
def email(email_name)
|
|
fixture_file("emails/#{email_name}.eml")
|
|
end
|
|
|
|
def create_staff_only_tags(tag_names)
|
|
create_limited_tags("Staff Tags", Group::AUTO_GROUPS[:staff], tag_names)
|
|
end
|
|
|
|
def create_limited_tags(tag_group_name, group_id, tag_names)
|
|
tag_group = Fabricate(:tag_group, name: tag_group_name)
|
|
TagGroupPermission.where(
|
|
tag_group: tag_group,
|
|
group_id: Group::AUTO_GROUPS[:everyone],
|
|
permission_type: TagGroupPermission.permission_types[:full],
|
|
).update(permission_type: TagGroupPermission.permission_types[:readonly])
|
|
TagGroupPermission.create!(
|
|
tag_group: tag_group,
|
|
group_id: group_id,
|
|
permission_type: TagGroupPermission.permission_types[:full],
|
|
)
|
|
tag_names.each do |name|
|
|
tag_group.tags << (Tag.where(name: name).first || Fabricate(:tag, name: name))
|
|
end
|
|
end
|
|
|
|
def create_hidden_tags(tag_names)
|
|
tag_group = Fabricate(:tag_group, name: "Hidden Tags", permissions: { staff: :full })
|
|
tag_names.each do |name|
|
|
tag_group.tags << (Tag.where(name: name).first || Fabricate(:tag, name: name))
|
|
end
|
|
end
|
|
|
|
def sorted_tag_names(tag_records)
|
|
tag_records.map { |t| t.is_a?(String) ? t : t.name }.sort
|
|
end
|
|
|
|
def expect_same_tag_names(a, b)
|
|
expect(sorted_tag_names(a)).to eq(sorted_tag_names(b))
|
|
end
|
|
|
|
def capture_output(output_name)
|
|
if ENV["RAILS_ENABLE_TEST_STDOUT"]
|
|
yield
|
|
return
|
|
end
|
|
|
|
previous_output = output_name == :stdout ? $stdout : $stderr
|
|
|
|
io = StringIO.new
|
|
output_name == :stdout ? $stdout = io : $stderr = io
|
|
|
|
yield
|
|
io.string
|
|
ensure
|
|
output_name == :stdout ? $stdout = previous_output : $stderr = previous_output
|
|
end
|
|
|
|
def capture_stdout(&block)
|
|
capture_output(:stdout, &block)
|
|
end
|
|
|
|
def capture_stderr(&block)
|
|
capture_output(:stderr, &block)
|
|
end
|
|
|
|
def set_subfolder(new_root)
|
|
global_setting :relative_url_root, new_root
|
|
|
|
old_root = ActionController::Base.config.relative_url_root
|
|
ActionController::Base.config.relative_url_root = new_root
|
|
Rails.application.routes.stubs(:relative_url_root).returns(new_root)
|
|
|
|
before_next_spec { ActionController::Base.config.relative_url_root = old_root }
|
|
|
|
if RSpec.current_example.metadata[:type] == :system
|
|
Capybara.app.map("/") { run lambda { |env| [404, {}, [""]] } }
|
|
Capybara.app.map(new_root) { run Rails.application }
|
|
|
|
before_next_spec do
|
|
Capybara.app.map(new_root) { run lambda { |env| [404, {}, [""]] } }
|
|
Capybara.app.map("/") { run Rails.application }
|
|
end
|
|
end
|
|
end
|
|
|
|
def setup_git_repo(files)
|
|
repo_dir = Dir.mktmpdir
|
|
`cd #{repo_dir} && git init . #{"--initial-branch=main" if GIT_INITIAL_BRANCH_SUPPORTED}`
|
|
`cd #{repo_dir} && git config user.email 'someone@cool.com'`
|
|
`cd #{repo_dir} && git config user.name 'The Cool One'`
|
|
`cd #{repo_dir} && git config commit.gpgsign 'false'`
|
|
files.each do |name, data|
|
|
FileUtils.mkdir_p(Pathname.new("#{repo_dir}/#{name}").dirname)
|
|
File.write("#{repo_dir}/#{name}", data)
|
|
`cd #{repo_dir} && git add #{name}`
|
|
end
|
|
`cd #{repo_dir} && git commit -am 'first commit'`
|
|
repo_dir
|
|
end
|
|
|
|
def add_to_git_repo(repo_dir, files)
|
|
files.each do |name, data|
|
|
FileUtils.mkdir_p(Pathname.new("#{repo_dir}/#{name}").dirname)
|
|
File.write("#{repo_dir}/#{name}", data)
|
|
`cd #{repo_dir} && git add #{name}`
|
|
end
|
|
`cd #{repo_dir} && git commit -am 'add #{files.size} files'`
|
|
repo_dir
|
|
end
|
|
|
|
def stub_const(target, const, value)
|
|
old = target.const_get(const)
|
|
target.send(:remove_const, const)
|
|
target.const_set(const, value)
|
|
yield
|
|
ensure
|
|
target.send(:remove_const, const)
|
|
target.const_set(const, old)
|
|
end
|
|
|
|
def track_sql_queries
|
|
queries = []
|
|
callback = ->(*, payload) do
|
|
queries << payload.fetch(:sql) unless %w[CACHE SCHEMA].include?(payload.fetch(:name))
|
|
end
|
|
|
|
ActiveSupport::Notifications.subscribed(callback, "sql.active_record") { yield }
|
|
|
|
queries
|
|
end
|
|
|
|
def stub_ip_lookup(stub_addr, ips)
|
|
Addrinfo
|
|
.stubs(:getaddrinfo)
|
|
.with { |addr, _| addr == stub_addr }
|
|
.returns(
|
|
ips.map { |ip| Addrinfo.new([IPAddr.new(ip).ipv6? ? "AF_INET6" : "AF_INET", 80, nil, ip]) },
|
|
)
|
|
end
|
|
|
|
def with_search_indexer_enabled
|
|
SearchIndexer.enable
|
|
yield
|
|
ensure
|
|
SearchIndexer.disable
|
|
end
|
|
|
|
# Uploads a theme from a directory.
|
|
#
|
|
# @param set_theme_as_default [Boolean] Whether to set the uploaded theme as the default theme for the site. Defaults to true.
|
|
#
|
|
# @return [Theme] The uploaded theme model given by `models/theme.rb`.
|
|
#
|
|
# @example Upload a theme and set it as default
|
|
# upload_theme("/path/to/theme")
|
|
def upload_theme(set_theme_as_default: true)
|
|
theme = RemoteTheme.import_theme_from_directory(theme_dir_from_caller)
|
|
|
|
if theme.component
|
|
raise "Uploaded theme is a theme component, please use the `upload_theme_component` method instead."
|
|
end
|
|
|
|
theme.set_default! if set_theme_as_default
|
|
theme
|
|
end
|
|
|
|
# Uploads a theme component from a directory.
|
|
#
|
|
# @param parent_theme_id [Integer] The ID of the theme to add the theme component to. Defaults to `SiteSetting.default_theme_id`.
|
|
#
|
|
# @return [Theme] The uploaded theme model given by `models/theme.rb`.
|
|
#
|
|
# @example Upload a theme component
|
|
# upload_theme_component("/path/to/theme_component")
|
|
#
|
|
# @example Upload a theme component and add it to a specific theme
|
|
# upload_theme_component("/path/to/theme_component", parent_theme_id: 123)
|
|
def upload_theme_component(parent_theme_id: SiteSetting.default_theme_id)
|
|
theme = RemoteTheme.import_theme_from_directory(theme_dir_from_caller)
|
|
|
|
if !theme.component
|
|
raise "Uploaded theme is not a theme component, please use the `upload_theme` method instead."
|
|
end
|
|
|
|
Theme.find(parent_theme_id).child_themes << theme
|
|
theme
|
|
end
|
|
|
|
# Runs named migration for a given theme.
|
|
#
|
|
# @params [Theme] theme The theme to run the migration for.
|
|
# @params [String] migration_name The name of the migration to run.
|
|
#
|
|
# @return [nil]
|
|
#
|
|
# @example
|
|
# run_theme_migration(theme, "0001-migrate-some-settings")
|
|
def run_theme_migration(theme, migration_name)
|
|
migration_theme_field = theme.theme_fields.find_by(name: migration_name)
|
|
theme.migrate_settings(fields: [migration_theme_field], allow_out_of_sequence_migration: true)
|
|
nil
|
|
end
|
|
|
|
private
|
|
|
|
def theme_dir_from_caller
|
|
caller.each do |line|
|
|
if (split = line.split(%r{/spec/*/.+_spec.rb})).length > 1
|
|
return split.first
|
|
end
|
|
end
|
|
end
|
|
end
|