discourse/spec/lib/discourse_updates_spec.rb
Martin Brennan 2ef9d6ac47
FEATURE: Allow admins to force refresh "What's new?" (#29911)
Sometimes changes to "What's new?" feed items are made or the feed items are
removed altogether, and the polling interval to check for new features is 1 day.

This is quite long, so this commit introduces a "Check for updates"
button for admins to click on the "What's new?" page which will bust
the cache for the feed and check again at the new features endpoint.
This is limited to 5 times per minute to avoid rapid sending of
requests.
2024-11-27 09:40:55 +10:00

331 lines
11 KiB
Ruby

# frozen_string_literal: true
RSpec.describe DiscourseUpdates do
def stub_data(latest, missing, critical, updated_at)
DiscourseUpdates.latest_version = latest
DiscourseUpdates.missing_versions_count = missing
DiscourseUpdates.critical_updates_available = critical
DiscourseUpdates.updated_at = updated_at
end
subject(:version) { DiscourseUpdates.check_version }
context "when version check was done at the current installed version" do
before { DiscourseUpdates.last_installed_version = Discourse::VERSION::STRING }
context "when a good version check request happened recently" do
context "when server is up-to-date" do
let(:time) { 12.hours.ago }
before { stub_data(Discourse::VERSION::STRING, 0, false, time) }
it "returns all the version fields" do
expect(version.latest_version).to eq(Discourse::VERSION::STRING)
expect(version.missing_versions_count).to eq(0)
expect(version.critical_updates).to eq(false)
expect(version.installed_version).to eq(Discourse::VERSION::STRING)
expect(version.stale_data).to eq(false)
end
it "returns the timestamp of the last version check" do
expect(version.updated_at).to be_within_one_second_of(time)
end
end
context "when server is not up-to-date" do
let(:time) { 12.hours.ago }
before { stub_data("0.9.0", 2, false, time) }
it "returns all the version fields" do
expect(version.latest_version).to eq("0.9.0")
expect(version.missing_versions_count).to eq(2)
expect(version.critical_updates).to eq(false)
expect(version.installed_version).to eq(Discourse::VERSION::STRING)
end
it "returns the timestamp of the last version check" do
expect(version.updated_at).to be_within_one_second_of(time)
end
end
end
context "when a version check has never been performed" do
before { stub_data(nil, nil, false, nil) }
it "returns the installed version" do
expect(version.installed_version).to eq(Discourse::VERSION::STRING)
end
it "indicates that version check has not been performed" do
expect(version.updated_at).to eq(nil)
expect(version.stale_data).to eq(true)
end
it "does not return latest version info" do
expect(version.latest_version).to eq(nil)
expect(version.missing_versions_count).to eq(nil)
expect(version.critical_updates).to eq(nil)
end
it "queues a version check" do
expect_enqueued_with(job: :call_discourse_hub) { version }
end
end
# These cases should never happen anymore, but keep the specs to be sure
# they're handled in a sane way.
context "with old version check data" do
shared_examples "queue version check and report that version is ok" do
it "queues a version check" do
expect_enqueued_with(job: :call_discourse_hub) { version }
end
it "reports 0 missing versions" do
expect(version.missing_versions_count).to eq(0)
end
it "reports that a version check will be run soon" do
expect(version.version_check_pending).to eq(true)
end
end
context "when installed is latest" do
before { stub_data(Discourse::VERSION::STRING, 1, false, 8.hours.ago) }
include_examples "queue version check and report that version is ok"
end
context "when installed does not match latest version, but missing_versions_count is 0" do
before { stub_data("0.10.10.123", 0, false, 8.hours.ago) }
include_examples "queue version check and report that version is ok"
end
end
end
context "when version check was done at a different installed version" do
before { DiscourseUpdates.last_installed_version = "0.9.1" }
shared_examples "when last_installed_version is old" do
it "queues a version check" do
expect_enqueued_with(job: :call_discourse_hub) { version }
end
it "reports 0 missing versions" do
expect(version.missing_versions_count).to eq(0)
end
it "reports that a version check will be run soon" do
expect(version.version_check_pending).to eq(true)
end
end
context "when missing_versions_count is 0" do
before { stub_data("0.9.7", 0, false, 8.hours.ago) }
include_examples "when last_installed_version is old"
end
context "when missing_versions_count is not 0" do
before { stub_data("0.9.7", 1, false, 8.hours.ago) }
include_examples "when last_installed_version is old"
end
end
describe "new features" do
fab!(:admin)
fab!(:admin2) { Fabricate(:admin) }
let!(:last_item_date) { 5.minutes.ago }
let!(:sample_features) do
[
{
"emoji" => "🤾",
"title" => "Super Fruits",
"description" => "Taste explosion!",
"created_at" => 40.minutes.ago,
},
{
"emoji" => "🙈",
"title" => "Fancy Legumes",
"description" => "Magic legumes!",
"created_at" => 15.minutes.ago,
},
{
"emoji" => "🤾",
"title" => "Quality Veggies",
"description" => "Green goodness!",
"created_at" => last_item_date,
},
]
end
before(:each) do
Discourse.redis.del "new_features_last_seen_user_#{admin.id}"
Discourse.redis.del "new_features_last_seen_user_#{admin2.id}"
Discourse.redis.set("new_features", MultiJson.dump(sample_features))
end
after { DiscourseUpdates.clean_state }
it "returns all items on the first run" do
result = DiscourseUpdates.new_features
expect(result.length).to eq(3)
expect(result[2]["title"]).to eq("Super Fruits")
end
it "correctly marks unseen items by user" do
DiscourseUpdates.stubs(:new_features_last_seen).with(admin.id).returns(10.minutes.ago)
DiscourseUpdates.stubs(:new_features_last_seen).with(admin2.id).returns(30.minutes.ago)
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(true)
expect(DiscourseUpdates.has_unseen_features?(admin2.id)).to eq(true)
end
it "can mark features as seen for a given user" do
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to be_truthy
DiscourseUpdates.mark_new_features_as_seen(admin.id)
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(false)
# doesn't affect another user
expect(DiscourseUpdates.has_unseen_features?(admin2.id)).to eq(true)
end
it "correctly sees newly added features as unseen" do
DiscourseUpdates.mark_new_features_as_seen(admin.id)
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(false)
expect(DiscourseUpdates.new_features_last_seen(admin.id)).to be_within(1.second).of(
last_item_date,
)
updated_features = [
{ "emoji" => "🤾", "title" => "Brand New Item", "created_at" => 2.minutes.ago },
]
updated_features += sample_features
Discourse.redis.set("new_features", MultiJson.dump(updated_features))
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(true)
end
it "correctly shows features by Discourse version" do
features_with_versions = [
{ "emoji" => "🤾", "title" => "Bells", "created_at" => 2.days.ago },
{
"emoji" => "🙈",
"title" => "Whistles",
"created_at" => 120.minutes.ago,
:discourse_version => "2.6.0.beta1",
},
{
"emoji" => "🙈",
"title" => "Confetti",
"created_at" => 15.minutes.ago,
:discourse_version => "2.7.0.beta2",
},
{
"emoji" => "🤾",
"title" => "Not shown yet",
"created_at" => 10.minutes.ago,
:discourse_version => "2.7.0.beta5",
},
{
"emoji" => "🤾",
"title" => "Not shown yet (beta < stable)",
"created_at" => 10.minutes.ago,
:discourse_version => "2.7.0",
},
{
"emoji" => "🤾",
"title" => "Ignore invalid version",
"created_at" => 10.minutes.ago,
:discourse_version => "invalid-version",
},
]
Discourse.redis.set("new_features", MultiJson.dump(features_with_versions))
DiscourseUpdates.last_installed_version = "2.7.0.beta2"
result = DiscourseUpdates.new_features
expect(result.length).to eq(3)
expect(result[0]["title"]).to eq("Confetti")
expect(result[1]["title"]).to eq("Whistles")
expect(result[2]["title"]).to eq("Bells")
end
it "correctly shows features with correct boolean experimental site settings" do
features_with_versions = [
{
"emoji" => "🤾",
"title" => "Bells",
"created_at" => 2.days.ago,
"experiment_setting" => "enable_mobile_theme",
},
{
"emoji" => "🙈",
"title" => "Whistles",
"created_at" => 3.days.ago,
"experiment_setting" => "default_theme_id",
},
{
"emoji" => "🙈",
"title" => "Confetti",
"created_at" => 4.days.ago,
"experiment_setting" => "wrong value",
},
]
Discourse.redis.set("new_features", MultiJson.dump(features_with_versions))
DiscourseUpdates.last_installed_version = "2.7.0.beta2"
result = DiscourseUpdates.new_features
expect(result.length).to eq(3)
expect(result[0]["experiment_setting"]).to eq("enable_mobile_theme")
expect(result[1]["experiment_setting"]).to be_nil
expect(result[2]["experiment_setting"]).to be_nil
end
it "correctly shows features when related plugins are installed" do
Discourse.stubs(:plugins_by_name).returns({ "discourse-ai" => true })
features_with_versions = [
{
"emoji" => "🤾",
"title" => "Bells",
"created_at" => 2.days.ago,
"plugin_name" => "discourse-ai",
},
{ "emoji" => "🙈", "title" => "Whistles", "created_at" => 3.days.ago, "plugin_name" => "" },
{
"emoji" => "🙈",
"title" => "Confetti",
"created_at" => 4.days.ago,
"plugin_name" => "uninstalled-plugin",
},
]
Discourse.redis.set("new_features", MultiJson.dump(features_with_versions))
DiscourseUpdates.last_installed_version = "2.7.0.beta2"
result = DiscourseUpdates.new_features
expect(result.length).to eq(2)
expect(result[0]["title"]).to eq("Bells")
expect(result[1]["title"]).to eq("Whistles")
end
it "correctly refetches features if force_refresh is used" do
DiscourseUpdates.expects(:update_new_features).once
result = DiscourseUpdates.new_features
expect(result.length).to eq(3)
result = DiscourseUpdates.new_features(force_refresh: true)
expect(result.length).to eq(3)
end
end
describe "#get_last_viewed_feature_date" do
fab!(:user)
it "returns an ActiveSupport::TimeWithZone object" do
time = Time.zone.parse("2022-12-13T21:33:59Z")
DiscourseUpdates.bump_last_viewed_feature_date(user.id, time)
expect(DiscourseUpdates.get_last_viewed_feature_date(user.id)).to eq(time)
end
end
end