Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/controllers/discourse_solved/answer_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ def unaccept

def limit_accepts
return if current_user.staff?
run_rate_limiter =
DiscoursePluginRegistry.apply_modifier(
:solved_answers_controller_run_rate_limiter,
true,
current_user,
)
return if !run_rate_limiter
RateLimiter.new(nil, "accept-hr-#{current_user.id}", 20, 1.hour).performed!
RateLimiter.new(nil, "accept-min-#{current_user.id}", 4, 30.seconds).performed!
end
Expand Down
88 changes: 88 additions & 0 deletions spec/requests/answer_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

require "rails_helper"

describe DiscourseSolved::AnswerController do
fab!(:user)
fab!(:staff_user) { Fabricate(:admin) }
fab!(:category)
fab!(:topic) { Fabricate(:topic, category: category) }
fab!(:p) { Fabricate(:post, topic: topic) }
fab!(:solution_post) { Fabricate(:post, topic: topic) }

before do
SiteSetting.solved_enabled = true
SiteSetting.allow_solved_on_all_topics = true
category.custom_fields[DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD] = "true"
category.save_custom_fields

# Give permission to accept solutions
user.update!(trust_level: 1)

# Make user the topic creator so they can accept answers
topic.update!(user_id: user.id)
end

describe "#accept" do
context "with default rate limiting" do
it "applies rate limits to regular users" do
sign_in(user)

# Should be rate limited
RateLimiter.any_instance.expects(:performed!).raises(RateLimiter::LimitExceeded.new(60))
post "/solution/accept.json", params: { id: solution_post.id }
expect(response.status).to eq(429)
end

it "does not apply rate limits to staff" do
sign_in(staff_user)

post "/solution/accept.json", params: { id: solution_post.id }
expect(response.status).to eq(200)
end
end

context "with plugin modifier" do
it "allows plugins to bypass rate limiting" do
sign_in(user)
# Create a plugin instance and register a modifier
plugin_instance = Plugin::Instance.new
modifier_block = Proc.new { |_, _| false }
plugin_instance.register_modifier(
:solved_answers_controller_run_rate_limiter,
&modifier_block
)

post "/solution/accept.json", params: { id: solution_post.id }
expect(response.status).to eq(200)
post "/solution/accept.json", params: { id: solution_post.id }
expect(response.status).to eq(200)

# Unregister the modifier using DiscoursePluginRegistry
DiscoursePluginRegistry.unregister_modifier(
plugin_instance,
:solved_answers_controller_run_rate_limiter,
&modifier_block
)
end
end
end
describe "#unaccept" do
before do
# Setup an accepted solution
sign_in(user)
post "/solution/accept.json", params: { id: solution_post.id }
expect(response.status).to eq(200)
sign_out
end

it "applies rate limits to regular users" do
sign_in(user)

# Should be rate limited
RateLimiter.any_instance.expects(:performed!).raises(RateLimiter::LimitExceeded.new(60))
post "/solution/unaccept.json", params: { id: solution_post.id }
expect(response.status).to eq(429)
end
end
end