discourse/spec/requests/admin/dashboard_controller_spec.rb
Martin Brennan dd0da69c15
DEV: Fix spec cleanup issues for upcoming changes (#39024)
Fixes two bugs in `clear_mocked_upcoming_change_metadata` /
`mock_upcoming_change_metadata`:

  1. .blank? vs .nil? — {}.blank? is true, so when the original metadata
     was an empty hash (no real upcoming changes loaded in test), the
     clear function returned early without restoring. Fixed by changing
     to .nil?.
  2. Nested mock overwrites original — The `permanent_upcoming_changes`
     describe block in upcoming_changes_spec.rb calls
     `mock_upcoming_change_metadata` a second time in its own before
block. This overwrote `@original_upcoming_changes_metadata` with the
     already-mocked state from the outer before. When clear ran after
     the last example (if it happened to be one from that nested block),
     it restored to the contaminated state instead of the true original.
     Fixed by changing = to ||= so the original is only captured on the
     first call.

Fixes spec failures like this we are seeing:

```
Error encountered while processing /admin/users/1695/revoke_moderation.json.
  NoMethodError: undefined method 'alpha_setting' for class SiteSetting
    ...(1 framework line(s) excluded)
    /__w/discourse/discourse/lib/upcoming_changes.rb:151:in 'Kernel#public_send'
    /__w/discourse/discourse/lib/upcoming_changes.rb:151:in 'UpcomingChanges.enabled_for_user?'
    /__w/discourse/discourse/app/models/user.rb:1971:in 'User#upcoming_change_enabled?'
    /__w/discourse/discourse/lib/upcoming_changes.rb:205:in 'block in UpcomingChanges.stats_for_user'
```
2026-04-01 14:15:02 +10:00

366 lines
11 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Admin::DashboardController do
fab!(:admin)
fab!(:moderator)
fab!(:user)
before do
AdminDashboardData.stubs(:fetch_cached_stats).returns(reports: [])
Jobs::CallDiscourseHub.any_instance.stubs(:execute).returns(true)
end
def populate_new_features(date1 = nil, date2 = nil)
sample_features = [
{
"id" => "1",
"emoji" => "🤾",
"title" => "Cool Beans",
"description" => "Now beans are included",
"created_at" => date1 || 40.minutes.ago,
},
{
"id" => "2",
"emoji" => "🙈",
"title" => "Fancy Legumes",
"description" => "Legumes too!",
"created_at" => date2 || 20.minutes.ago,
},
]
Discourse.redis.set("new_features", MultiJson.dump(sample_features))
end
describe "#index" do
shared_examples "version info present" do
it "returns discourse version info" do
get "/admin/dashboard.json"
expect(response.status).to eq(200)
expect(response.parsed_body["version_check"]).to be_present
end
end
shared_examples "version info absent" do
before { SiteSetting.version_checks = false }
it "does not return discourse version info" do
get "/admin/dashboard.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["version_check"]).not_to be_present
end
end
context "when logged in as an admin" do
before { sign_in(admin) }
context "when version checking is enabled" do
before { SiteSetting.version_checks = true }
include_examples "version info present"
end
context "when version checking is disabled" do
before { SiteSetting.version_checks = false }
include_examples "version info absent"
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
context "when version checking is enabled" do
before { SiteSetting.version_checks = true }
include_examples "version info present"
end
context "when version checking is disabled" do
before { SiteSetting.version_checks = false }
include_examples "version info absent"
end
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
it "denies access with a 404 response" do
get "/admin/dashboard.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
end
describe "#problems" do
before { ProblemCheck.stubs(:realtime).returns(stub(run_all: [])) }
context "when logged in as an admin" do
before { sign_in(admin) }
context "when there are no problems" do
it "returns an empty array" do
post "/admin/dashboard/problems.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["problems"].size).to eq(0)
end
end
context "when there are problems" do
before do
Fabricate(:admin_notice, subject: "problem", identifier: "foo")
Fabricate(:admin_notice, subject: "problem", identifier: "bar")
end
it "returns an array of strings" do
post "/admin/dashboard/problems.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["problems"].size).to eq(2)
end
end
end
context "when logged in as a moderator" do
before do
sign_in(moderator)
Fabricate(:admin_notice, subject: "problem", identifier: "foo")
Fabricate(:admin_notice, subject: "problem", identifier: "bar")
end
it "returns a list of problems" do
post "/admin/dashboard/problems.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["problems"].size).to eq(2)
end
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
it "denies access with a 404 response" do
post "/admin/dashboard/problems.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
end
describe "#new_features" do
after { DiscourseUpdates.clean_state }
context "when logged in as an admin" do
before { sign_in(admin) }
it "is empty by default" do
get "/admin/whats-new.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["new_features"]).to eq([])
end
it "fails gracefully for invalid JSON" do
Discourse.redis.set("new_features", "INVALID JSON")
get "/admin/whats-new.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["new_features"]).to eq([])
end
it "includes new features when available" do
populate_new_features
get "/admin/whats-new.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["new_features"].length).to eq(2)
expect(json["new_features"][0]["emoji"]).to eq("🙈")
expect(json["new_features"][0]["title"]).to eq("Fancy Legumes")
expect(json["has_unseen_features"]).to eq(true)
end
it "allows for forcing a refresh of new features, busting the cache" do
populate_new_features
get "/admin/whats-new.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["new_features"].length).to eq(2)
get "/admin/whats-new.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["new_features"].length).to eq(2)
DiscourseUpdates.stubs(:new_features_response_json).returns(
[
{
"id" => "3",
"emoji" => "🚀",
"title" => "Space platform launched!",
"description" => "Now to make it to the next planet unscathed...",
"created_at" => 1.minute.ago,
},
].to_json,
)
get "/admin/whats-new.json?force_refresh=true"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["new_features"].length).to eq(1)
expect(json["new_features"][0]["id"]).to eq("3")
end
it "passes unseen feature state" do
populate_new_features
DiscourseUpdates.mark_new_features_as_seen(admin.id)
get "/admin/whats-new.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["has_unseen_features"]).to eq(false)
end
it "sets/bumps the last viewed feature date for the admin" do
date1 = 30.minutes.ago
date2 = 20.minutes.ago
populate_new_features(date1, date2)
expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to eq(nil)
get "/admin/whats-new.json"
expect(response.status).to eq(200)
expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to be_within_one_second_of(
date2,
)
date2 = 10.minutes.ago
populate_new_features(date1, date2)
get "/admin/whats-new.json"
expect(response.status).to eq(200)
expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to be_within_one_second_of(
date2,
)
end
it "marks new features as seen" do
date1 = 30.minutes.ago
date2 = 20.minutes.ago
populate_new_features(date1, date2)
expect(DiscourseUpdates.new_features_last_seen(admin.id)).to eq(nil)
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(true)
get "/admin/whats-new.json"
expect(response.status).to eq(200)
expect(DiscourseUpdates.new_features_last_seen(admin.id)).not_to eq(nil)
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(false)
expect(DiscourseUpdates.new_features_last_seen(moderator.id)).to eq(nil)
expect(DiscourseUpdates.has_unseen_features?(moderator.id)).to eq(true)
end
it "doesn't error when there are no new features" do
get "/admin/whats-new.json"
expect(response.status).to eq(200)
end
context "when a permanent upcoming change exists and the feed is empty" do
before do
mock_upcoming_change_metadata(
{
enable_upload_debug_mode: {
impact: "other,developers",
status: :permanent,
impact_type: "other",
impact_role: "developers",
learn_more_url: "https://meta.discourse.org/t/-/1234",
},
},
)
UpcomingChanges.stubs(:image_exists?).returns(true)
UpcomingChanges.stubs(:image_data).returns(
{
url: "#{Discourse.base_url}/images/upcoming_changes/enable_upload_debug_mode.png",
width: 244,
height: 66,
file_path: file_from_fixtures("logo.png", "images").path,
},
)
end
it "includes the permanent upcoming change in the whats-new payload" do
freeze_time do
get "/admin/whats-new.json"
expect(response.status).to eq(200)
json = response.parsed_body
feature =
json["new_features"].find do |row|
row["upcoming_change_setting_name"] == "enable_upload_debug_mode"
end
expect(feature).to be_present
expect(feature["title"]).to eq(SiteSetting.humanized_names(:enable_upload_debug_mode))
expect(feature["description"]).to eq(SiteSetting.description(:enable_upload_debug_mode))
expect(feature["link"]).to eq("https://meta.discourse.org/t/-/1234")
expect(feature["screenshot_url"]).to eq(
"#{Discourse.base_url}/images/upcoming_changes/enable_upload_debug_mode.png",
)
expect(Time.parse(feature["created_at"])).to eq_time(Time.zone.now)
end
end
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
it "includes new features when available" do
populate_new_features
get "/admin/whats-new.json"
json = response.parsed_body
expect(json["new_features"].length).to eq(2)
expect(json["new_features"][0]["emoji"]).to eq("🙈")
expect(json["new_features"][0]["title"]).to eq("Fancy Legumes")
expect(json["has_unseen_features"]).to eq(true)
end
it "doesn't set last viewed feature date for moderators" do
populate_new_features
expect(DiscourseUpdates.get_last_viewed_feature_date(moderator.id)).to eq(nil)
get "/admin/whats-new.json"
expect(response.status).to eq(200)
expect(DiscourseUpdates.get_last_viewed_feature_date(moderator.id)).to eq(nil)
end
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
it "denies access with a 404 response" do
get "/admin/whats-new.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
end
end