discourse/spec/integrity/i18n_spec.rb
Alan Guo Xiang Tan 916495e0a1
DEV: Avoid leaking new site setting states in test environment (#21713)
What is the problem?

In the test environement, we were calling `SiteSetting.setting` directly
to introduce new site settings. However, this leads to changes in state of the SiteSettings
hash that is stored in memory as test runs. Changing or leaking states
when running tests is one of the major contributors of test flakiness.

An example of how this resulted in test flakiness is our `spec/integrity/i18n_spec.rb` spec file which
had a test case that would fail because a new "plugin_setting" site
setting was registered in another test case but the site setting did not
have translations for the site setting set.

What is the fix?

There are a couple of changes being introduced in this commit:

1. Make `SiteSetting.setting` a private method as it is not safe to be
   exposed as a public method of the `SiteSetting` class

2. Change test cases to use existing site settings in Discourse instead
   of creating custom site settings. Existing site settings are not
   removed often so we don't really need to dynamically add new site
   settings in test cases. Even if the site settings being used in test
   cases are removed, updating the test cases to rely on other site
   settings is a very easy change.

3. Set up a plugin instance in the test environment as a "fixture"
   instead of having each test create its own plugin instance.
2023-05-25 07:53:57 +08:00

138 lines
3.5 KiB
Ruby

# frozen_string_literal: true
def extract_locale(path)
path[/\.([^.]{2,})\.yml$/, 1]
end
def is_yaml_compatible?(english, translated)
english.each do |k, v|
if translated.has_key?(k)
if Hash === v
if Hash === translated[k]
return false unless is_yaml_compatible?(v, translated[k])
end
else
return false unless v.class == translated[k].class
end
end
end
true
end
def load_yaml(path)
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.1.0")
YAML.load_file(path, aliases: true)
else
YAML.load_file(path)
end
end
RSpec.describe "i18n integrity checks" do
it "has an i18n key for each Site Setting" do
SiteSetting.all_settings.each do |s|
next if s[:plugin] == SiteSetting::SAMPLE_TEST_PLUGIN.name
expect(s[:description]).not_to match(/translation missing/)
end
end
it "has an i18n key for each Badge description" do
Badge
.where(system: true)
.each do |b|
expect(b.long_description).to be_present
expect(b.description).to be_present
end
end
Dir["#{Rails.root}/config/locales/{client,server}.*.yml"].each do |path|
it "does not contain invalid interpolation keys for '#{path}'" do
matches = File.read(path).scan(/%\{([^a-zA-Z\s]+)\}|\{\{([^a-zA-Z\s]+)\}\}/)
matches.flatten!
matches.compact!
matches.uniq!
expect(matches).to eq([])
end
end
Dir["#{Rails.root}/config/locales/client.*.yml"].each do |path|
it "has valid client YAML for '#{path}'" do
yaml = load_yaml(path)
locale = extract_locale(path)
expect(yaml.keys).to eq([locale])
expect(yaml[locale]["js"]).to be
if !LocaleSiteSetting.fallback_locale(locale)
expect(yaml[locale]["admin_js"]).to be
expect(yaml[locale]["wizard_js"]).to be
end
end
end
Dir["#{Rails.root}/**/locale*/*.en.yml"].each do |english_path|
english_yaml = load_yaml(english_path)["en"]
context(english_path) do
it "has no duplicate keys" do
english_duplicates = DuplicateKeyFinder.new.find_duplicates(english_path)
expect(english_duplicates).to be_empty
end
end
Dir[english_path.sub(".en.yml", ".*.yml")].each do |path|
next if path[".en.yml"]
context(path) do
locale = extract_locale(path)
yaml = load_yaml(path)
it "has no duplicate keys" do
duplicates = DuplicateKeyFinder.new.find_duplicates(path)
expect(duplicates).to be_empty
end
it "does not overwrite another locale" do
expect(yaml.keys).to eq([locale])
end
unless path["transliterate"]
it "is compatible with english" do
expect(is_yaml_compatible?(english_yaml, yaml)).to eq(true)
end
end
end
end
end
end
RSpec.describe "fallbacks" do
before do
I18n.backend = I18n::Backend::DiscourseI18n.new
I18n.fallbacks = I18n::Backend::FallbackLocaleList.new
I18n.reload!
I18n.init_accelerator!
end
it "finds the fallback translation" do
I18n.backend.store_translations(:en, test: "en test")
I18n.with_locale("pl_PL") { expect(I18n.t("test")).to eq("en test") }
end
context "when in a multi-threaded environment" do
it "finds the fallback translation" do
I18n.backend.store_translations(:en, test: "en test")
thread = Thread.new { I18n.with_locale("pl_PL") { expect(I18n.t("test")).to eq("en test") } }
begin
thread.join
ensure
thread.exit
end
end
end
end