Files
once-campfire/test/controllers/messages/by_bots_controller_test.rb
T
Ronald Lokers 3ca1dcbf77 Allow bots to update and destroy their own messages
Bots can only create. A lifecycle notification — an alert that fires and then
resolves, a deploy that starts and finishes, a backup that runs — therefore has
to post a second message, and the room becomes an append-only log of states
rather than a view of the current one.

Adds PATCH and DELETE inside the existing bot_key scope, routed to
Messages::ByBotsController. The body is read the way create reads it, so
updating a message is the same request shape as posting one.

No new authorization: both actions already run through ensure_can_administer,
and can_administer? grants access only to a record the user created, so a bot
key reaches that bot's own messages and no others. set_room narrows it again by
looking the room up through the bot's own memberships. A leaked bot key gains
what it could already do by posting: write to rooms that bot belongs to.

update answers head :ok rather than the redirect, which meant extracting the
update and its broadcast into update_message — calling super and then head
would double render, since the parent redirects inside the action. destroy
needs no split, because the parent renders implicitly like create does.
2026-08-11 13:33:01 +02:00

229 lines
7.6 KiB
Ruby

require "test_helper"
class Messages::ByBotsControllerTest < ActionDispatch::IntegrationTest
setup do
@room = rooms(:watercooler)
end
test "create" do
assert_difference -> { Message.count }, +1 do
post room_bot_messages_url(@room, users(:bender).bot_key), params: +"Hello Bot World!"
assert_equal "Hello Bot World!", Message.last.plain_text_body
end
end
test "create with UTF-8 content" do
assert_difference -> { Message.count }, +1 do
post room_bot_messages_url(@room, users(:bender).bot_key), params: +"Hello 👋!"
assert_equal "Hello 👋!", Message.last.plain_text_body
end
end
test "create file" do
assert_difference -> { Message.count }, +1 do
post room_bot_messages_url(@room, users(:bender).bot_key), params: { attachment: fixture_file_upload("moon.jpg", "image/jpeg") }
assert Message.last.attachment.present?
end
end
test "create does not trigger a webhook to the sending bot if it mentions itself" do
body = "<div>Hey #{mention_attachment_for(:bender)}</div>"
assert_no_enqueued_jobs only: Bot::WebhookJob do
post room_bot_messages_url(@room, users(:bender).bot_key), params: body
end
end
test "create does not trigger a webhook to the sending bot in a direct room" do
assert_no_enqueued_jobs only: Bot::WebhookJob do
post room_bot_messages_url(rooms(:bender_and_kevin), users(:bender).bot_key), params: +"Talking to myself again!"
end
end
test "create without a body or attachment" do
assert_no_difference -> { Message.count } do
post room_bot_messages_url(@room, users(:bender).bot_key)
assert_response :unprocessable_content
post room_bot_messages_url(@room, users(:bender).bot_key), params: +" "
assert_response :unprocessable_content
end
end
test "create can't be abused to post messages as any user" do
user = users(:kevin)
bot_key = "#{user.id}-"
assert_no_difference -> { Message.count } do
post room_bot_messages_url(rooms(:bender_and_kevin), bot_key), params: "Hello 👋!"
end
assert_response :redirect
end
test "index returns the room's messages in the order they were sent" do
get room_bot_messages_url(@room, users(:bender).bot_key)
assert_response :success
json = JSON.parse(response.body)
assert_equal @room.messages.ordered.map(&:id), json.map { it["id"] }
end
test "index includes message details" do
post room_bot_messages_url(@room, users(:bender).bot_key), params: +"Hello from Bender!"
get room_bot_messages_url(@room, users(:bender).bot_key)
assert_response :success
message = Message.last
json_message = JSON.parse(response.body).last
assert_equal message.id, json_message["id"]
assert_equal "Hello from Bender!", json_message["body"]["plain_text"]
assert_includes json_message["body"]["html"], "Hello from Bender!"
assert_equal message.created_at.utc.iso8601(3), json_message["created_at"]
assert_equal users(:bender).id, json_message["creator"]["id"]
assert_equal "Bender Bot", json_message["creator"]["name"]
assert_equal "bot", json_message["creator"]["role"]
assert_equal @room.id, json_message["room"]["id"]
assert_equal room_message_url(@room, message), json_message["url"]
end
test "index pages through older messages with the Link header" do
(Message::PAGE_SIZE - @room.messages.count + 1).times do |i|
@room.messages.create!(body: "Filler #{i}", creator: users(:jason), client_message_id: "filler-#{i}")
end
get room_bot_messages_url(@room, users(:bender).bot_key)
assert_response :success
json = JSON.parse(response.body)
assert_equal Message::PAGE_SIZE, json.size
assert_equal "41", response.headers["X-Total-Count"]
assert_not_includes json.map { it["id"] }, messages(:fourth).id
get response.headers["Link"][/<(.*)>/, 1]
assert_response :success
json = JSON.parse(response.body)
assert_equal [ messages(:fourth).id ], json.map { it["id"] }
assert_nil response.headers["Link"]
end
test "index pages newer messages with after" do
get room_bot_messages_url(@room, users(:bender).bot_key, after: messages(:tenth).id)
assert_response :success
json = JSON.parse(response.body)
assert_equal %i[ eleventh twelfth thirteenth ].map { messages(it).id }, json.map { it["id"] }
assert_nil response.headers["Link"]
end
test "index in a room with no messages" do
get room_bot_messages_url(rooms(:bender_and_kevin), users(:bender).bot_key)
assert_response :success
assert_equal [], JSON.parse(response.body)
assert_equal "0", response.headers["X-Total-Count"]
assert_nil response.headers["Link"]
end
test "index requires a valid bot key" do
get room_bot_messages_url(@room, "invalid-bot-key")
assert_response :redirect
end
test "index is not found for a room the bot is not a member of" do
get room_bot_messages_url(rooms(:designers), users(:bender).bot_key)
assert_response :not_found
end
test "create is not found for a room the bot is not a member of" do
assert_no_difference -> { Message.count } do
post room_bot_messages_url(rooms(:designers), users(:bender).bot_key), params: +"Hello!"
end
assert_response :not_found
end
test "regular messages index remains denied for bots" do
get room_messages_url(@room, bot_key: users(:bender).bot_key)
assert_response :forbidden
end
test "update" do
message = post_bot_message "Deploying..."
assert_no_difference -> { Message.count } do
patch room_bot_message_url(@room, users(:bender).bot_key, message), params: +"Deployed."
end
assert_response :ok
assert_equal "Deployed.", message.reload.plain_text_body
end
test "update with UTF-8 content" do
message = post_bot_message "Deploying..."
patch room_bot_message_url(@room, users(:bender).bot_key, message), params: +"Deployed 🚀!"
assert_response :ok
assert_equal "Deployed 🚀!", message.reload.plain_text_body
end
test "update can't touch a message the bot did not create" do
message = messages(:fourth)
original = message.plain_text_body
patch room_bot_message_url(@room, users(:bender).bot_key, message), params: +"Hijacked!"
assert_response :forbidden
assert_equal original, message.reload.plain_text_body
end
test "update is not found for a room the bot is not a member of" do
message = messages(:first)
original = message.plain_text_body
patch room_bot_message_url(rooms(:designers), users(:bender).bot_key, message), params: +"Hijacked!"
assert_response :not_found
assert_equal original, message.reload.plain_text_body
end
test "update can't be abused to edit messages as any user" do
message = messages(:fourth)
bot_key = "#{users(:jz).id}-"
original = message.plain_text_body
patch room_bot_message_url(@room, bot_key, message), params: +"Hijacked!"
assert_response :redirect
assert_equal original, message.reload.plain_text_body
end
test "destroy" do
message = post_bot_message "Deploying..."
assert_difference -> { Message.count }, -1 do
delete room_bot_message_url(@room, users(:bender).bot_key, message)
end
assert_response :no_content
end
test "destroy can't touch a message the bot did not create" do
message = messages(:fourth)
assert_no_difference -> { Message.count } do
delete room_bot_message_url(@room, users(:bender).bot_key, message)
end
assert_response :forbidden
end
private
def post_bot_message(body)
post room_bot_messages_url(@room, users(:bender).bot_key), params: +body
Message.last
end
end