mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-26 07:36:53 +09:00
Hello world
First open source release of Campfire 🎉
This commit is contained in:
commit
df76a227dc
664 files changed
+36235
No files matched your search
@@ -0,0 +1,9 @@
|
||||
require "test_helper"
|
||||
|
||||
WebMock.disable!
|
||||
|
||||
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
||||
driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ]
|
||||
|
||||
include SystemTestHelper
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
require "test_helper"
|
||||
|
||||
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
|
||||
test "connects with valid user_id cookie" do
|
||||
cookies.signed[:session_token] = sessions(:david_safari).token
|
||||
|
||||
connect
|
||||
|
||||
assert_equal users(:david), connection.current_user
|
||||
end
|
||||
|
||||
test "rejects connection with missing user_id cookie" do
|
||||
assert_reject_connection { connect }
|
||||
end
|
||||
|
||||
test "rejects connection with invalid user_id cookie" do
|
||||
cookies.signed[:session_token] = -1
|
||||
|
||||
assert_reject_connection { connect }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
require "test_helper"
|
||||
|
||||
class PresenceChannelTest < ActionCable::Channel::TestCase
|
||||
setup do
|
||||
stub_connection(current_user: users(:david))
|
||||
end
|
||||
|
||||
test "subscribes" do
|
||||
room = users(:david).rooms.first
|
||||
|
||||
subscribe room_id: room.id
|
||||
|
||||
assert subscription.confirmed?
|
||||
assert_has_stream_for room
|
||||
end
|
||||
|
||||
test "rejects subscription to a room that the user is not a member of" do
|
||||
subscribe room_id: Rooms::Closed.create!(name: "New Room", creator: users(:david)).id
|
||||
|
||||
assert subscription.rejected?
|
||||
end
|
||||
|
||||
test "rejects subscription to non-existent room" do
|
||||
subscribe room_id: -1
|
||||
|
||||
assert subscription.rejected?
|
||||
end
|
||||
|
||||
test "rejects subscription without a room" do
|
||||
subscribe room_id: -1
|
||||
|
||||
assert subscription.rejected?
|
||||
end
|
||||
|
||||
test "subscribing marks the membership as connected" do
|
||||
membership = users(:david).memberships.first
|
||||
|
||||
assert_changes -> { membership.reload.connected? }, from: false, to: true do
|
||||
subscribe room_id: membership.room_id
|
||||
end
|
||||
end
|
||||
|
||||
test "unsubscribing marks the membership as disconnected" do
|
||||
membership = users(:david).memberships.first
|
||||
subscribe room_id: membership.room_id
|
||||
|
||||
assert_changes -> { membership.reload.connected? }, from: true, to: false do
|
||||
unsubscribe
|
||||
end
|
||||
end
|
||||
end
|
||||
Whitespace-only changes.
@@ -0,0 +1,14 @@
|
||||
require "test_helper"
|
||||
|
||||
class Accounts::Bots::KeysControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "update" do
|
||||
assert_changes -> { users(:bender).reload.bot_token } do
|
||||
put account_bot_key_url(users(:bender))
|
||||
assert_redirected_to account_bots_url
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
require "test_helper"
|
||||
|
||||
class Accounts::BotsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "index" do
|
||||
get account_bots_url
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "create" do
|
||||
get new_account_bot_url
|
||||
assert_response :ok
|
||||
|
||||
post account_bots_url, params: { user: { name: "Bender's Friend" } }
|
||||
assert_redirected_to account_bots_url
|
||||
assert_equal "Bender's Friend", User.bot.last.name
|
||||
end
|
||||
|
||||
test "update" do
|
||||
get edit_account_bot_url(users(:bender))
|
||||
assert_response :ok
|
||||
|
||||
put account_bot_url(users(:bender)), params: { user: { name: "Bender's New Friend" } }
|
||||
assert_redirected_to account_bots_url
|
||||
assert_equal "Bender's New Friend", users(:bender).reload.name
|
||||
end
|
||||
|
||||
test "destroy" do
|
||||
assert_difference -> { User.active_bots.count }, -1 do
|
||||
delete account_bot_url(users(:bender))
|
||||
end
|
||||
|
||||
assert users(:bender).reload.deactivated?
|
||||
end
|
||||
|
||||
test "remove webhook" do
|
||||
assert_difference -> { Webhook.count }, -1 do
|
||||
put account_bot_url(users(:bender)), params: { user: { name: "Bender's New Friend", webook_url: "" } }
|
||||
assert_redirected_to account_bots_url
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
require "test_helper"
|
||||
|
||||
class Accounts::CustomStylesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "edit" do
|
||||
get edit_account_custom_styles_url
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "update" do
|
||||
assert users(:david).administrator?
|
||||
|
||||
put account_custom_styles_url, params: { account: { custom_styles: ":root { --color-text: red; }" } }
|
||||
|
||||
assert_redirected_to edit_account_custom_styles_url
|
||||
assert_equal accounts(:signal).custom_styles, ":root { --color-text: red; }"
|
||||
end
|
||||
|
||||
test "non-admins cannot update" do
|
||||
sign_in :kevin
|
||||
assert users(:kevin).member?
|
||||
|
||||
put account_custom_styles_url, params: { account: { custom_styles: ":root { --color-text: red; }" } }
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
require "test_helper"
|
||||
|
||||
class Accounts::JoinCodesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "create new join code" do
|
||||
assert_changes -> { accounts(:signal).reload.join_code } do
|
||||
post account_join_code_url
|
||||
assert_redirected_to edit_account_url
|
||||
end
|
||||
end
|
||||
|
||||
test "only administrators can create new join codes" do
|
||||
sign_in :jz
|
||||
post account_join_code_url
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
require "test_helper"
|
||||
require "vips"
|
||||
|
||||
class Accounts::LogosControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "show stock" do
|
||||
get account_logo_url
|
||||
assert_valid_png_response size: 512
|
||||
end
|
||||
|
||||
test "show stock small size" do
|
||||
get account_logo_url(size: :small)
|
||||
assert_valid_png_response size: 192
|
||||
end
|
||||
|
||||
test "show custom" do
|
||||
accounts(:signal).update! logo: fixture_file_upload("moon.jpg", "image/jpeg")
|
||||
|
||||
get account_logo_url
|
||||
assert_valid_png_response size: 512
|
||||
end
|
||||
|
||||
test "show custom small size" do
|
||||
accounts(:signal).update! logo: fixture_file_upload("moon.jpg", "image/jpeg")
|
||||
|
||||
get account_logo_url(size: :small)
|
||||
assert_valid_png_response size: 192
|
||||
end
|
||||
|
||||
test "destroy" do
|
||||
accounts(:signal).update! logo: fixture_file_upload("moon.jpg", "image/jpeg")
|
||||
|
||||
delete account_logo_url
|
||||
assert_redirected_to edit_account_url
|
||||
assert_not accounts(:signal).reload.logo.attached?
|
||||
end
|
||||
|
||||
private
|
||||
def assert_valid_png_response(size:)
|
||||
assert_equal @response.headers["content-type"], "image/png"
|
||||
|
||||
image = ::Vips::Image.new_from_buffer(@response.body, "")
|
||||
assert_equal size, image.width
|
||||
assert_equal size, image.height
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
require "test_helper"
|
||||
|
||||
class Accounts::UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "update" do
|
||||
assert users(:david).administrator?
|
||||
|
||||
put account_user_url(users(:david)), params: { user: { role: "administrator" } }
|
||||
|
||||
assert_redirected_to edit_account_url
|
||||
assert users(:david).reload.administrator?
|
||||
end
|
||||
|
||||
test "destroy" do
|
||||
assert_difference -> { User.active.count }, -1 do
|
||||
delete account_user_url(users(:david))
|
||||
end
|
||||
|
||||
assert_redirected_to edit_account_url
|
||||
assert_nil User.active.find_by(id: users(:david).id)
|
||||
end
|
||||
|
||||
test "non-admins cannot perform actions" do
|
||||
sign_in :kevin
|
||||
|
||||
put account_user_url(users(:david)), params: { user: { role: "administrator" } }
|
||||
assert_response :forbidden
|
||||
|
||||
delete account_user_url(users(:david))
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
require "test_helper"
|
||||
|
||||
class AccountsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "edit" do
|
||||
get edit_account_url
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "update" do
|
||||
assert users(:david).administrator?
|
||||
|
||||
put account_url, params: { account: { name: "Different" } }
|
||||
|
||||
assert_redirected_to edit_account_url
|
||||
assert_equal accounts(:signal).name, "Different"
|
||||
end
|
||||
|
||||
test "non-admins cannot update" do
|
||||
sign_in :kevin
|
||||
assert users(:kevin).member?
|
||||
|
||||
put account_url, params: { account: { name: "Different" } }
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
require "test_helper"
|
||||
|
||||
class Autocompletable::UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "search returns matching users" do
|
||||
get autocompletable_users_url(format: :json), params: { query: "da" }
|
||||
|
||||
assert_response :success
|
||||
assert_equal "David", response.parsed_body.first["name"]
|
||||
end
|
||||
|
||||
test "search results escape HTML in names" do
|
||||
users(:david).update!(name: "David <script>alert(123)</script>")
|
||||
|
||||
get autocompletable_users_url(format: :json), params: { query: "da" }
|
||||
|
||||
assert_response :success
|
||||
assert_equal "David <script>alert(123)</script>", response.parsed_body.first["name"]
|
||||
end
|
||||
|
||||
test "room search returns matching users" do
|
||||
get autocompletable_users_url(room_id: rooms(:hq).id, format: :json), params: { query: "da" }
|
||||
|
||||
assert_response :success
|
||||
assert_equal "David", response.parsed_body.first["name"]
|
||||
end
|
||||
|
||||
test "room search is scoped by membership" do
|
||||
sign_in :kevin
|
||||
|
||||
assert_not_includes users(:kevin).rooms, rooms(:watercooler)
|
||||
|
||||
assert_raises ActiveRecord::RecordNotFound do
|
||||
get autocompletable_users_url(room_id: rooms(:watercooler).id, format: :json), params: { query: "da" }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
require "test_helper"
|
||||
|
||||
class FirstRunsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
Account.destroy_all
|
||||
User.destroy_all
|
||||
Room.destroy_all
|
||||
end
|
||||
|
||||
test "new is permitted when no other users exit" do
|
||||
get first_run_url
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "new is not permitted when account exist" do
|
||||
Account.create!(name: "Chat")
|
||||
|
||||
get first_run_url
|
||||
assert_redirected_to root_url
|
||||
end
|
||||
|
||||
test "create" do
|
||||
assert_difference -> { Room.count }, 1 do
|
||||
assert_difference -> { User.count }, 1 do
|
||||
post first_run_url, params: { account: { name: "37signals" }, user: { name: "New Person", email_address: "new@37signals.com", password: "secret123456" } }
|
||||
end
|
||||
end
|
||||
|
||||
assert_redirected_to root_url
|
||||
|
||||
assert parsed_cookies.signed[:session_token]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
require "test_helper"
|
||||
|
||||
class Messages::BoostsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
@message = messages(:first)
|
||||
end
|
||||
|
||||
test "create" do
|
||||
assert_turbo_stream_broadcasts [ @message.room, :messages ], count: 1 do
|
||||
assert_difference -> { @message.boosts.count }, 1 do
|
||||
post message_boosts_url(@message, format: :turbo_stream), params: { boost: { content: "Morning!" } }
|
||||
assert_redirected_to message_boosts_url(@message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "destroy" do
|
||||
assert_turbo_stream_broadcasts [ @message.room, :messages ], count: 1 do
|
||||
assert_difference -> { @message.boosts.count }, -1 do
|
||||
delete message_boost_url(@message, boosts(:first), format: :turbo_stream)
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
require "test_helper"
|
||||
|
||||
class Messages::ByBotsControlleTest < 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 "denied index" do
|
||||
get room_messages_url(@room, bot_key: users(:bender).bot_key, format: :json)
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,152 @@
|
||||
require "test_helper"
|
||||
|
||||
class MessagesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
host! "once.campfire.test"
|
||||
|
||||
sign_in :david
|
||||
@room = rooms(:watercooler)
|
||||
@messages = @room.messages.ordered.to_a
|
||||
end
|
||||
|
||||
test "index returns the last page by default" do
|
||||
get room_messages_url(@room)
|
||||
|
||||
assert_response :success
|
||||
ensure_messages_present @messages.last
|
||||
end
|
||||
|
||||
test "index returns a page before the specified message" do
|
||||
get room_messages_url(@room, before: @messages.third)
|
||||
|
||||
assert_response :success
|
||||
ensure_messages_present @messages.first, @messages.second
|
||||
ensure_messages_not_present @messages.third, @messages.fourth, @messages.fifth
|
||||
end
|
||||
|
||||
test "index returns a page after the specified message" do
|
||||
get room_messages_url(@room, after: @messages.third)
|
||||
|
||||
assert_response :success
|
||||
ensure_messages_present @messages.fourth, @messages.fifth
|
||||
ensure_messages_not_present @messages.first, @messages.second, @messages.third
|
||||
end
|
||||
|
||||
test "index returns no_content when there are no messages" do
|
||||
@room.messages.destroy_all
|
||||
|
||||
get room_messages_url(@room)
|
||||
|
||||
assert_response :no_content
|
||||
end
|
||||
|
||||
test "get renders a single message belonging to the user" do
|
||||
message = @room.messages.where(creator: users(:david)).first
|
||||
|
||||
get room_message_url(@room, message)
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "creating a message broadcasts the message to the room" do
|
||||
post room_messages_url(@room, format: :turbo_stream), params: { message: { body: "New one", client_message_id: 999 } }
|
||||
|
||||
assert_rendered_turbo_stream_broadcast @room, :messages, action: "append", target: [ @room, :messages ] do
|
||||
assert_select ".message__body", text: /New one/
|
||||
assert_copy_link_button room_at_message_url(@room, Message.last, host: "once.campfire.test")
|
||||
end
|
||||
end
|
||||
|
||||
test "creating a message broadcasts unread room" do
|
||||
assert_broadcasts "unread_rooms", 1 do
|
||||
post room_messages_url(@room, format: :turbo_stream), params: { message: { body: "New one", client_message_id: 999 } }
|
||||
end
|
||||
end
|
||||
|
||||
test "update updates a message belonging to the user" do
|
||||
message = @room.messages.where(creator: users(:david)).first
|
||||
|
||||
Turbo::StreamsChannel.expects(:broadcast_replace_to).once
|
||||
put room_message_url(@room, message), params: { message: { body: "Updated body" } }
|
||||
|
||||
assert_redirected_to room_message_url(@room, message)
|
||||
assert_equal "Updated body", message.reload.plain_text_body
|
||||
end
|
||||
|
||||
test "admin updates a message belonging to another user" do
|
||||
message = @room.messages.where(creator: users(:jason)).first
|
||||
|
||||
Turbo::StreamsChannel.expects(:broadcast_replace_to).once
|
||||
put room_message_url(@room, message), params: { message: { body: "Updated body" } }
|
||||
|
||||
assert_redirected_to room_message_url(@room, message)
|
||||
assert_equal "Updated body", message.reload.plain_text_body
|
||||
end
|
||||
|
||||
test "destroy destroys a message belonging to the user" do
|
||||
message = @room.messages.where(creator: users(:david)).first
|
||||
|
||||
assert_difference -> { Message.count }, -1 do
|
||||
Turbo::StreamsChannel.expects(:broadcast_remove_to).once
|
||||
delete room_message_url(@room, message, format: :turbo_stream)
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "admin destroy destroys a message belonging to another user" do
|
||||
assert users(:david).administrator?
|
||||
message = @room.messages.where(creator: users(:jason)).first
|
||||
|
||||
assert_difference -> { Message.count }, -1 do
|
||||
Turbo::StreamsChannel.expects(:broadcast_remove_to).once
|
||||
delete room_message_url(@room, message, format: :turbo_stream)
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
test "ensure non-admin can't update a message belonging to another user" do
|
||||
sign_in :jz
|
||||
assert_not users(:jz).administrator?
|
||||
|
||||
room = rooms(:designers)
|
||||
message = room.messages.where(creator: users(:jason)).first
|
||||
|
||||
put room_message_url(room, message), params: { message: { body: "Updated body" } }
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
test "ensure non-admin can't destroy a message belonging to another user" do
|
||||
sign_in :jz
|
||||
assert_not users(:jz).administrator?
|
||||
|
||||
room = rooms(:designers)
|
||||
message = room.messages.where(creator: users(:jason)).first
|
||||
|
||||
delete room_message_url(room, message, format: :turbo_stream)
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
test "mentioning a bot triggers a webhook" do
|
||||
WebMock.stub_request(:post, webhooks(:bender).url).to_return(status: 200)
|
||||
|
||||
assert_enqueued_jobs 1, only: Bot::WebhookJob do
|
||||
post room_messages_url(@room, format: :turbo_stream), params: { message: {
|
||||
body: "<div>Hey #{mention_attachment_for(:bender)}</div>", client_message_id: 999 } }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def ensure_messages_present(*messages, count: 1)
|
||||
messages.each do |message|
|
||||
assert_select "#" + dom_id(message), count:
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_messages_not_present(*messages)
|
||||
ensure_messages_present *messages, count: 0
|
||||
end
|
||||
|
||||
def assert_copy_link_button(url)
|
||||
assert_select ".btn[title='Copy link'][data-copy-to-clipboard-content-value='#{url}']"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
require "test_helper"
|
||||
|
||||
class QrCodeControllerTest < ActionDispatch::IntegrationTest
|
||||
test "show renders a QR code as a cacheable SVG image" do
|
||||
id = Base64.urlsafe_encode64("http://example.com")
|
||||
|
||||
get qr_code_path(id)
|
||||
|
||||
assert_response :success
|
||||
assert_includes response.content_type, "image/svg+xml"
|
||||
|
||||
assert_equal 1.year, response.cache_control[:max_age].to_i
|
||||
assert response.cache_control[:public]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
require "test_helper"
|
||||
|
||||
class Rooms::ClosedsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "show redirects to get general show" do
|
||||
get rooms_open_url(users(:david).rooms.closeds.last)
|
||||
assert_redirected_to room_url(users(:david).rooms.closeds.last)
|
||||
end
|
||||
|
||||
test "new" do
|
||||
get new_rooms_closed_url
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "create" do
|
||||
assert_turbo_stream_broadcasts [ users(:david), :rooms ], count: 1 do
|
||||
assert_turbo_stream_broadcasts [ users(:kevin), :rooms ], count: 1 do
|
||||
assert_turbo_stream_broadcasts [ users(:jason), :rooms ], count: 1 do
|
||||
post rooms_closeds_url, params: { room: { name: "My New Room" }, user_ids: [ users(:david).id, users(:kevin).id, users(:jason).id ] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
new_room = Room.last
|
||||
assert_equal new_room.memberships.count, 3
|
||||
assert_redirected_to room_url(Room.last)
|
||||
end
|
||||
|
||||
test "update with membership revisions" do
|
||||
assert_difference -> { rooms(:designers).reload.users.count }, -1 do
|
||||
put rooms_closed_url(rooms(:designers)), params: {
|
||||
room: { name: "New Name" }, user_ids: rooms(:designers).users.without(users(:jason)).collect(&:id)
|
||||
}
|
||||
end
|
||||
|
||||
assert_redirected_to room_url(rooms(:designers))
|
||||
assert rooms(:designers).reload.name, "New Name"
|
||||
end
|
||||
|
||||
test "update an open room to be closed" do
|
||||
put rooms_closed_url(rooms(:pets)), params: { room: { name: "Doesn't matter" }, user_ids: [ users(:david).id, users(:jason).id ] }
|
||||
assert_equal rooms(:pets).memberships.count, 2
|
||||
end
|
||||
|
||||
test "only admins or creators can update" do
|
||||
sign_in :jz
|
||||
|
||||
assert_turbo_stream_broadcasts :rooms, count: 0 do
|
||||
put rooms_closed_url(rooms(:designers)), params: { room: { name: "New Name" } }
|
||||
end
|
||||
|
||||
assert_response :forbidden
|
||||
assert rooms(:designers).reload.name, "Designers"
|
||||
end
|
||||
|
||||
test "remove yourself" do
|
||||
assert_difference -> { users(:david).rooms.count }, -1 do
|
||||
put rooms_closed_url(rooms(:designers), params: { room: { name: "Designers" }, user_ids: [ users(:jason).id, users(:jz).id ] })
|
||||
|
||||
assert_redirected_to room_url(rooms(:designers))
|
||||
follow_redirect!
|
||||
assert_redirected_to root_url
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
require "test_helper"
|
||||
|
||||
class Rooms::DirectsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "create" do
|
||||
post rooms_directs_url, params: { user_ids: [ users(:jz).id ] }
|
||||
|
||||
room = Room.last
|
||||
assert_redirected_to room_url(room)
|
||||
assert room.users.include?(users(:david))
|
||||
assert room.users.include?(users(:jz))
|
||||
end
|
||||
|
||||
test "create only once per user set" do
|
||||
assert_difference -> { Room.all.count }, +1 do
|
||||
post rooms_directs_url, params: { user_ids: [ users(:jz).id ] }
|
||||
post rooms_directs_url, params: { user_ids: [ users(:jz).id ] }
|
||||
end
|
||||
end
|
||||
|
||||
test "destroy only allowed for all room users" do
|
||||
sign_in :kevin
|
||||
|
||||
assert_difference -> { Room.count }, -1 do
|
||||
delete rooms_direct_url(rooms(:david_and_kevin))
|
||||
assert_redirected_to root_url
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
require "test_helper"
|
||||
|
||||
class Rooms::InvolvementsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "show" do
|
||||
get room_involvement_url(rooms(:designers))
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "update involvement sends turbo update when becoming visible and when going invisible" do
|
||||
assert_turbo_stream_broadcasts [ users(:david), :rooms ], count: 1 do
|
||||
assert_changes -> { memberships(:david_watercooler).reload.involvement }, from: "everything", to: "invisible" do
|
||||
put room_involvement_url(rooms(:watercooler)), params: { involvement: "invisible" }
|
||||
assert_redirected_to room_involvement_url(rooms(:watercooler))
|
||||
end
|
||||
end
|
||||
|
||||
assert_turbo_stream_broadcasts [ users(:david), :rooms ], count: 2 do
|
||||
assert_changes -> { memberships(:david_watercooler).reload.involvement }, from: "invisible", to: "everything" do
|
||||
put room_involvement_url(rooms(:watercooler)), params: { involvement: "everything" }
|
||||
assert_redirected_to room_involvement_url(rooms(:watercooler))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "updating involvement does not send turbo update changing visible states" do
|
||||
assert_no_turbo_stream_broadcasts [ users(:david), :rooms ] do
|
||||
assert_changes -> { memberships(:david_watercooler).reload.involvement }, from: "everything", to: "mentions" do
|
||||
put room_involvement_url(rooms(:watercooler)), params: { involvement: "mentions" }
|
||||
assert_redirected_to room_involvement_url(rooms(:watercooler))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "updating involvement does not send turbo update for direct rooms" do
|
||||
assert_no_turbo_stream_broadcasts [ users(:david), :rooms ] do
|
||||
assert_changes -> { memberships(:david_david_and_jason).reload.involvement }, from: "everything", to: "nothing" do
|
||||
put room_involvement_url(rooms(:david_and_jason)), params: { involvement: "nothing" }
|
||||
assert_redirected_to room_involvement_url(rooms(:david_and_jason))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
require "test_helper"
|
||||
|
||||
class Rooms::OpensControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "show redirects to get general show" do
|
||||
get rooms_open_url(users(:david).rooms.opens.last)
|
||||
assert_redirected_to room_url(users(:david).rooms.opens.last)
|
||||
end
|
||||
|
||||
test "new" do
|
||||
get new_rooms_open_url
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "create" do
|
||||
assert_turbo_stream_broadcasts :rooms, count: 1 do
|
||||
post rooms_opens_url, params: { room: { name: "My New Room" } }
|
||||
end
|
||||
|
||||
assert_equal Room.last.memberships.count, User.count
|
||||
assert_redirected_to room_url(Room.last)
|
||||
end
|
||||
|
||||
test "only admins or creators can update" do
|
||||
sign_in :jz
|
||||
|
||||
assert_turbo_stream_broadcasts :rooms, count: 0 do
|
||||
put rooms_open_url(rooms(:hq)), params: { room: { name: "New Name" } }
|
||||
end
|
||||
|
||||
assert_response :forbidden
|
||||
assert rooms(:hq).reload.name, "HQ"
|
||||
end
|
||||
|
||||
test "update" do
|
||||
assert_turbo_stream_broadcasts :rooms, count: 1 do
|
||||
put rooms_open_url(rooms(:pets)), params: { room: { name: "New Name" } }
|
||||
end
|
||||
|
||||
assert_redirected_to room_url(rooms(:pets))
|
||||
assert rooms(:pets).reload.name, "New Name"
|
||||
end
|
||||
|
||||
test "update a closed room to be open" do
|
||||
put rooms_open_url(rooms(:designers)), params: { room: { name: "Doesn't matter" } }
|
||||
assert_equal rooms(:designers).memberships.count, User.count
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
require "test_helper"
|
||||
|
||||
class Rooms::RefreshesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "refresh includes new messages since the last known" do
|
||||
travel_to 1.day.ago do
|
||||
@old_message = rooms(:hq).messages.create!(creator: users(:jason), body: "Old message", client_message_id: "old")
|
||||
end
|
||||
|
||||
travel_to 1.minute.ago do
|
||||
@new_message = rooms(:hq).messages.create!(creator: users(:jason), body: "New message", client_message_id: "new")
|
||||
@old_message.touch
|
||||
end
|
||||
|
||||
get room_refresh_url(rooms(:hq), format: :turbo_stream), params: { since: 10.minutes.ago.to_fs(:epoch) }
|
||||
|
||||
assert_response :success
|
||||
|
||||
assert_select "turbo-stream[action='append']" do
|
||||
assert_select "#" + dom_id(@new_message)
|
||||
assert_select "template", count: 1
|
||||
end
|
||||
|
||||
assert_select "turbo-stream[action='replace']" do
|
||||
assert_select "#" + dom_id(@old_message)
|
||||
assert_select "template", count: 1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
require "test_helper"
|
||||
|
||||
class RoomsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "index redirects to the user's last room" do
|
||||
get rooms_url
|
||||
assert_redirected_to room_url(users(:david).rooms.last)
|
||||
end
|
||||
|
||||
test "show" do
|
||||
get room_url(users(:david).rooms.last)
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "shows records the last room visited in a cookie" do
|
||||
get room_url(users(:david).rooms.last)
|
||||
assert response.cookies[:last_room] = users(:david).rooms.last.id
|
||||
end
|
||||
|
||||
test "destroy" do
|
||||
assert_turbo_stream_broadcasts :rooms, count: 1 do
|
||||
assert_difference -> { Room.count }, -1 do
|
||||
delete room_url(rooms(:designers))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "destroy only allowed for creators or those who can administer" do
|
||||
sign_in :jz
|
||||
|
||||
assert_no_difference -> { Room.count } do
|
||||
delete room_url(rooms(:designers))
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
rooms(:designers).update! creator: users(:jz)
|
||||
|
||||
assert_difference -> { Room.count }, -1 do
|
||||
delete room_url(rooms(:designers))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
require "test_helper"
|
||||
|
||||
class SearchesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
@message = rooms(:designers).messages.create! body: "Hello world!", client_message_id: "search", creator: users(:david)
|
||||
end
|
||||
|
||||
test "index initial view" do
|
||||
get searches_url
|
||||
|
||||
assert_response :success
|
||||
assert_select ".message", count: 0
|
||||
end
|
||||
|
||||
test "finding reachable messages" do
|
||||
get searches_url, params: { q: "hello" }
|
||||
|
||||
assert_response :success
|
||||
assert_select ".message", text: /Hello world!/
|
||||
end
|
||||
|
||||
test "unreachable messages are not found" do
|
||||
memberships(:david_designers).destroy!
|
||||
|
||||
get searches_url, params: { q: "hello" }
|
||||
|
||||
assert_response :success
|
||||
assert_select ".message", count: 0
|
||||
end
|
||||
|
||||
test "create saves the search term" do
|
||||
assert_difference -> { users(:david).searches.count }, +1 do
|
||||
post searches_url, params: { q: "hello" }
|
||||
end
|
||||
|
||||
assert_redirected_to searches_url(q: "hello")
|
||||
assert users(:david).searches.exists?(query: "hello")
|
||||
end
|
||||
|
||||
test "clear search history" do
|
||||
assert users(:david).searches.any?
|
||||
|
||||
delete clear_searches_url
|
||||
|
||||
assert users(:david).searches.none?
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
require "test_helper"
|
||||
|
||||
class Sessions::TransfersControllerTest < ActionDispatch::IntegrationTest
|
||||
test "show renders when not signed in" do
|
||||
get session_transfer_url("some-token")
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "update establishes a session when the code is valid" do
|
||||
user = users(:david)
|
||||
|
||||
put session_transfer_url(user.transfer_id)
|
||||
|
||||
assert_redirected_to root_url
|
||||
assert parsed_cookies.signed[:session_token]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
require "test_helper"
|
||||
|
||||
class SessionsControllerTest < ActionDispatch::IntegrationTest
|
||||
ALLOWED_BROWSER = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15"
|
||||
DISALLOWED_BROWSER = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0"
|
||||
|
||||
test "new" do
|
||||
get new_session_url
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "new redirects to first run when no users exist" do
|
||||
User.destroy_all
|
||||
|
||||
get new_session_url
|
||||
|
||||
assert_redirected_to first_run_url
|
||||
end
|
||||
|
||||
test "new denied with incompatible browser" do
|
||||
get new_session_url, env: { "HTTP_USER_AGENT" => DISALLOWED_BROWSER }
|
||||
assert_select "h1", /Upgrade to a supported web browser/
|
||||
end
|
||||
|
||||
test "new allowed with compatible browser" do
|
||||
get new_session_url, env: { "HTTP_USER_AGENT" => ALLOWED_BROWSER }
|
||||
assert_select "h1", text: /Upgrade to a supported web browser/, count: 0
|
||||
end
|
||||
|
||||
test "create with valid credentials" do
|
||||
post session_url, params: { email_address: "david@37signals.com", password: "secret123456" }
|
||||
|
||||
assert_redirected_to root_url
|
||||
assert parsed_cookies.signed[:session_token]
|
||||
end
|
||||
|
||||
test "create with invalid credentials" do
|
||||
post session_url, params: { email_address: "david@37signals.com", password: "wrong" }
|
||||
|
||||
assert_response :unauthorized
|
||||
assert_nil parsed_cookies.signed[:session_token]
|
||||
end
|
||||
|
||||
test "destroy" do
|
||||
sign_in :david
|
||||
|
||||
delete session_url
|
||||
|
||||
assert_redirected_to root_url
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
|
||||
test "destroy removes the push subscription for the device" do
|
||||
sign_in :david
|
||||
|
||||
assert_difference -> { users(:david).push_subscriptions.count }, -1 do
|
||||
delete session_url, params: { push_subscription_endpoint: push_subscriptions(:david_chrome).endpoint }
|
||||
end
|
||||
|
||||
assert_redirected_to root_url
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
require "test_helper"
|
||||
|
||||
class UnfurlLinksControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "create" do
|
||||
stub_successful_request
|
||||
|
||||
post unfurl_link_url, params: { url: "https://www.example.com" }
|
||||
assert_response :success
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
assert_equal "Hey!", json_response["title"]
|
||||
assert_equal "https://example.com", json_response["url"]
|
||||
assert_equal "https://example.com/image.png", json_response["image"]
|
||||
assert_equal "desc..", json_response["description"]
|
||||
end
|
||||
|
||||
test "create with missing opengraph meta tags" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 200, body: "<html><head></head></html>", headers: {})
|
||||
|
||||
post unfurl_link_url, params: { url: "https://www.example.com" }
|
||||
assert_response :no_content
|
||||
end
|
||||
|
||||
test "create with a missing URL" do
|
||||
assert_raise ActionController::ParameterMissing do
|
||||
post unfurl_link_url, params: { url: "" }
|
||||
assert_response :bad_request
|
||||
end
|
||||
end
|
||||
|
||||
test "create for twitter.com" do
|
||||
stub_successful_request url: "https://fxtwitter.com/dhh/status/834146806594433025"
|
||||
|
||||
post unfurl_link_url, params: { url: "https://twitter.com/dhh/status/834146806594433025" }
|
||||
assert_response :success
|
||||
assert_equal "Hey!", JSON.parse(response.body)["title"]
|
||||
end
|
||||
|
||||
test "create for x.com" do
|
||||
stub_successful_request url: "https://fxtwitter.com/dhh/status/834146806594433025"
|
||||
|
||||
post unfurl_link_url, params: { url: "https://x.com/dhh/status/834146806594433025" }
|
||||
assert_response :success
|
||||
assert_equal "Hey!", JSON.parse(response.body)["title"]
|
||||
end
|
||||
|
||||
private
|
||||
def stub_successful_request(url: "https://www.example.com/")
|
||||
WebMock.stub_request(:get, url).to_return(
|
||||
status: 200,
|
||||
body: "<html><head><meta property=\"og:url\" content=\"https://example.com\"><meta property=\"og:title\" content=\"Hey!\"><meta property=\"og:description\" content=\"desc..\"><meta property=\"og:image\" content=\"https://example.com/image.png\"></head></html>",
|
||||
headers: { content_type: "text/html" }
|
||||
)
|
||||
|
||||
WebMock.stub_request(:head, "https://example.com/image.png").to_return(
|
||||
status: 200,
|
||||
headers: { content_type: "image/png" }
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
require "test_helper"
|
||||
|
||||
class Users::AvatarsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "show initials" do
|
||||
get user_avatar_url(users(:kevin).avatar_token)
|
||||
assert_select "text", text: "K"
|
||||
end
|
||||
|
||||
test "show image" do
|
||||
users(:kevin).update! avatar: fixture_file_upload("moon.jpg", "image/jpeg")
|
||||
get user_avatar_url(users(:kevin).avatar_token)
|
||||
|
||||
assert_response :success
|
||||
assert_equal "image/webp", @response.content_type
|
||||
end
|
||||
|
||||
test "show image with invalid token responds 404" do
|
||||
get user_avatar_url("not-a-valid-token")
|
||||
|
||||
assert_response :not_found
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
require "test_helper"
|
||||
|
||||
class Users::ProfilesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "show" do
|
||||
get user_profile_url
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "update" do
|
||||
put user_profile_url, params: { user: { name: "John Doe", bio: "Acrobat" } }
|
||||
|
||||
assert_redirected_to user_profile_url
|
||||
assert_equal "John Doe", users(:david).reload.name
|
||||
assert_equal "Acrobat", users(:david).bio
|
||||
assert_equal "david@37signals.com", users(:david).email_address
|
||||
end
|
||||
|
||||
test "updates are limited to the current user" do
|
||||
put user_profile_url(users(:jason)), params: { user: { name: "John Doe" } }
|
||||
|
||||
assert_equal "Jason", users(:jason).reload.name
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
require "test_helper"
|
||||
|
||||
class Users::PushSubscriptionsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "create new push subscription" do
|
||||
subscription_params = { "endpoint" => "https://apple", "p256dh_key" => "123", "auth_key" => "456" }
|
||||
|
||||
post user_push_subscriptions_url,
|
||||
params: { push_subscription: subscription_params }, headers: { "HTTP_USER_AGENT" => "Mozilla/5.0" }
|
||||
|
||||
assert_response :ok
|
||||
|
||||
assert_equal subscription_params, users(:david).push_subscriptions.last.attributes.slice("endpoint", "p256dh_key", "auth_key")
|
||||
assert_equal "Mozilla/5.0", users(:david).push_subscriptions.last.user_agent
|
||||
end
|
||||
|
||||
test "touch existing subscription" do
|
||||
assert_no_difference -> { users(:david).push_subscriptions.count } do
|
||||
assert_changes -> { push_subscriptions(:david_chrome).reload.updated_at } do
|
||||
post user_push_subscriptions_url(params: {
|
||||
push_subscription: push_subscriptions(:david_chrome).attributes.slice("endpoint", "p256dh_key", "auth_key")
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "destroy a push subscription via dev mode" do
|
||||
assert_difference -> { Push::Subscription.count }, -1 do
|
||||
delete user_push_subscription_url(push_subscriptions(:david_chrome))
|
||||
assert_redirected_to user_push_subscriptions_url
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
require "test_helper"
|
||||
|
||||
class Users::SidebarsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "show" do
|
||||
get user_sidebar_url
|
||||
|
||||
users(:david).rooms.opens.each do |room|
|
||||
assert_match /#{room.name}/, @response.body
|
||||
end
|
||||
end
|
||||
|
||||
test "unread directs" do
|
||||
rooms(:david_and_jason).messages.create! client_message_id: 999, body: "Hello", creator: users(:jason)
|
||||
|
||||
get user_sidebar_url
|
||||
assert_select ".unread", count: users(:david).memberships.select { |m| m.room.direct? && m.unread? }.count
|
||||
end
|
||||
|
||||
|
||||
test "unread other" do
|
||||
rooms(:watercooler).messages.create! client_message_id: 999, body: "Hello", creator: users(:jason)
|
||||
|
||||
get user_sidebar_url
|
||||
assert_select ".unread", count: users(:david).memberships.reject { |m| m.room.direct? || !m.unread? }.count
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
require "test_helper"
|
||||
|
||||
class UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@join_code = accounts(:signal).join_code
|
||||
end
|
||||
|
||||
test "show" do
|
||||
sign_in :david
|
||||
get user_url(users(:david))
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "new" do
|
||||
get join_url(@join_code)
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "new does not allow a signed in user" do
|
||||
sign_in :david
|
||||
|
||||
get join_url(@join_code)
|
||||
assert_redirected_to root_url
|
||||
end
|
||||
|
||||
test "new requires a join code" do
|
||||
get join_url("not")
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "create" do
|
||||
assert_difference -> { User.count }, 1 do
|
||||
post join_url(@join_code), params: { user: { name: "New Person", email_address: "new@37signals.com", password: "secret123456" } }
|
||||
end
|
||||
|
||||
assert_redirected_to root_url
|
||||
|
||||
user = User.last
|
||||
assert_equal user.id, Session.find_by(token: parsed_cookies.signed[:session_token]).user.id
|
||||
assert_equal Rooms::Open.all, user.rooms
|
||||
end
|
||||
|
||||
test "creating a new user with an existing email address will redirect to login screen" do
|
||||
assert_no_difference -> { User.count } do
|
||||
post join_url(@join_code), params: { user: { name: "Another David", email_address: users(:david).email_address, password: "secret123456" } }
|
||||
end
|
||||
|
||||
assert_redirected_to new_session_url(email_address: users(:david).email_address)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
require "test_helper"
|
||||
|
||||
class WelcomeControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "redirects to the first created visible room the user has access to" do
|
||||
get root_url
|
||||
|
||||
assert_redirected_to room_url(users(:david).rooms.original)
|
||||
end
|
||||
|
||||
test "redirects to the last room visited, if we have one" do
|
||||
cookies[:last_room] = rooms(:watercooler).id
|
||||
|
||||
get root_url
|
||||
|
||||
assert_redirected_to room_url(rooms(:watercooler))
|
||||
end
|
||||
end
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
signal:
|
||||
name: 37signals
|
||||
join_code: CRMu-l8Ge-KB9B
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
first:
|
||||
record: first (Message)
|
||||
name: body
|
||||
body: First post!
|
||||
|
||||
second:
|
||||
record: second (Message)
|
||||
name: body
|
||||
body: Seconded.
|
||||
|
||||
third:
|
||||
record: third (Message)
|
||||
name: body
|
||||
body: Third time's a charm.
|
||||
|
||||
fourth:
|
||||
record: fourth (Message)
|
||||
name: body
|
||||
body: You know what the fellow said – in Italy, for thirty years under the Borgias, they had warfare, terror, murder and bloodshed, but they produced Michelangelo, Leonardo da Vinci and the Renaissance. In Switzerland, they had brotherly love, they had five hundred years of democracy and peace – and what did that produce? The cuckoo clock.
|
||||
|
||||
fifth:
|
||||
record: fifth (Message)
|
||||
name: body
|
||||
body: Champagne, if you are seeking the truth, is better than a lie detector.
|
||||
|
||||
sixth:
|
||||
record: sixth (Message)
|
||||
name: body
|
||||
body: The sense of unhappiness is so much easier to convey than that of happiness. In misery we seem aware of our own existence, even though it may be in the form of a monstrous egotism—this pain of mine is individual, this nerve that winces belongs to me and to no other. But happiness annihilates us—we lose our identity.
|
||||
|
||||
seventh:
|
||||
record: seventh (Message)
|
||||
name: body
|
||||
body: A story has no beginning or end—arbitrarily one chooses that moment of experience from which to look back or from which to look ahead.
|
||||
|
||||
eighth:
|
||||
record: eighth (Message)
|
||||
name: body
|
||||
body: Hate is a lack of imagination.
|
||||
|
||||
ninth:
|
||||
record: ninth (Message)
|
||||
name: body
|
||||
body: And innocence is a kind of insanity
|
||||
|
||||
tenth:
|
||||
record: tenth (Message)
|
||||
name: body
|
||||
body: I don't care a damn about men who are loyal to the people who pay them, to organizations...I don't think even my country means all that much. There are many countries in our blood, aren't there, but only one person. Would the world be in the mess it is if we were loyal to love and not to countries?
|
||||
|
||||
eleventh:
|
||||
record: eleventh (Message)
|
||||
name: body
|
||||
body: People who like quotes love meaningless generalizations
|
||||
|
||||
twelfth:
|
||||
record: twelfth (Message)
|
||||
name: body
|
||||
body: Pain is easy to write. In pain we're all happily individual. But what can one write about happiness?
|
||||
|
||||
thirteenth:
|
||||
record: thirteenth (Message)
|
||||
name: body
|
||||
body: When we are not sure, we are alive.
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
first:
|
||||
message: first
|
||||
booster: david
|
||||
content: Hello
|
||||
thirteenth:
|
||||
message: thirteenth
|
||||
booster: jason
|
||||
content: 💯
|
||||
Vendored
Whitespace-only changes.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 494 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 46 MiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
david_designers:
|
||||
room: designers
|
||||
user: david
|
||||
|
||||
jason_designers:
|
||||
room: designers
|
||||
user: jason
|
||||
involvement: everything
|
||||
|
||||
jz_designers:
|
||||
room: designers
|
||||
user: jz
|
||||
involvement: everything
|
||||
|
||||
kevin_designers:
|
||||
room: designers
|
||||
user: kevin
|
||||
|
||||
david_pets:
|
||||
room: pets
|
||||
user: david
|
||||
involvement: everything
|
||||
|
||||
jason_pets:
|
||||
room: pets
|
||||
user: jason
|
||||
involvement: everything
|
||||
|
||||
david_watercooler:
|
||||
room: watercooler
|
||||
user: david
|
||||
involvement: everything
|
||||
|
||||
jason_watercooler:
|
||||
room: watercooler
|
||||
user: jason
|
||||
involvement: everything
|
||||
|
||||
bender_watercooler:
|
||||
room: watercooler
|
||||
user: bender
|
||||
|
||||
david_hq:
|
||||
room: hq
|
||||
user: david
|
||||
involvement: everything
|
||||
|
||||
jason_hq:
|
||||
room: hq
|
||||
user: jason
|
||||
involvement: everything
|
||||
|
||||
jz_hq:
|
||||
room: hq
|
||||
user: jz
|
||||
involvement: everything
|
||||
|
||||
kevin_hq:
|
||||
room: hq
|
||||
user: kevin
|
||||
involvement: everything
|
||||
|
||||
david_david_and_jason:
|
||||
room: david_and_jason
|
||||
user: david
|
||||
involvement: everything
|
||||
|
||||
jason_david_and_jason:
|
||||
room: david_and_jason
|
||||
user: jason
|
||||
involvement: everything
|
||||
|
||||
david_david_and_kevin:
|
||||
room: david_and_kevin
|
||||
user: david
|
||||
involvement: everything
|
||||
|
||||
kevin_david_and_kevin:
|
||||
room: david_and_kevin
|
||||
user: kevin
|
||||
involvement: everything
|
||||
|
||||
bender_bender_and_kevin:
|
||||
room: bender_and_kevin
|
||||
user: bender
|
||||
involvement: everything
|
||||
|
||||
kevin_bender_and_kevin:
|
||||
room: bender_and_kevin
|
||||
user: kevin
|
||||
involvement: everything
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
first:
|
||||
room: designers
|
||||
creator: jason
|
||||
client_message_id: "0001"
|
||||
created_at: <%= 1.hour.ago %>
|
||||
|
||||
second:
|
||||
room: designers
|
||||
creator: david
|
||||
client_message_id: "0002"
|
||||
created_at: <%= 36.minutes.ago %>
|
||||
|
||||
third:
|
||||
room: designers
|
||||
creator: jz
|
||||
client_message_id: "0003"
|
||||
created_at: <%= 7.minutes.ago %>
|
||||
|
||||
fourth:
|
||||
room: watercooler
|
||||
creator: jz
|
||||
client_message_id: "0004"
|
||||
created_at: <%= 30.minutes.ago %>
|
||||
|
||||
fifth:
|
||||
room: watercooler
|
||||
creator: jason
|
||||
client_message_id: "0005"
|
||||
created_at: <%= 29.minutes.ago %>
|
||||
|
||||
sixth:
|
||||
room: watercooler
|
||||
creator: david
|
||||
client_message_id: "0006"
|
||||
created_at: <%= 27.minutes.ago %>
|
||||
|
||||
seventh:
|
||||
room: watercooler
|
||||
creator: jason
|
||||
client_message_id: "0007"
|
||||
created_at: <%= 26.minutes.ago %>
|
||||
|
||||
eighth:
|
||||
room: watercooler
|
||||
creator: jz
|
||||
client_message_id: "0008"
|
||||
created_at: <%= 25.minutes.ago %>
|
||||
|
||||
ninth:
|
||||
room: watercooler
|
||||
creator: jz
|
||||
client_message_id: "0009"
|
||||
created_at: <%= 24.minutes.ago %>
|
||||
|
||||
tenth:
|
||||
room: watercooler
|
||||
creator: david
|
||||
client_message_id: "0010"
|
||||
created_at: <%= 23.minutes.ago %>
|
||||
|
||||
eleventh:
|
||||
room: watercooler
|
||||
creator: jason
|
||||
client_message_id: "0011"
|
||||
created_at: <%= 22.minutes.ago %>
|
||||
|
||||
twelfth:
|
||||
room: watercooler
|
||||
creator: jason
|
||||
client_message_id: "0012"
|
||||
created_at: <%= 21.minutes.ago %>
|
||||
|
||||
thirteenth:
|
||||
room: watercooler
|
||||
creator: jz
|
||||
client_message_id: "0013"
|
||||
created_at: <%= 5.minutes.ago %>
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
david_chrome:
|
||||
user: david
|
||||
endpoint: https://fcm.googleapis.com/fcm/send/123
|
||||
p256dh_key: 123-RIXcMgkdjhRnFZaYjjGvo00dydRQbCpQTuXFjLaCPSE7ofxi19awgGc3Doqa1RmYQqsbQDfQTifFZgc
|
||||
auth_key: xxx2DtgvmLkevKRwoyahJl0efg
|
||||
user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
|
||||
|
||||
jason_chrome:
|
||||
user: jason
|
||||
endpoint: https://fcm.googleapis.com/fcm/send/567
|
||||
p256dh_key: 456-RIXcMgkdjhRnFZaYjjGvo00dydRQbCpQTuXFjLaCPSE7ofxi19awgGc3Doqa1RmYQqsbQDfQTifFZgc
|
||||
auth_key: xxx2DtgvmLkevKRwoyahJl0efg
|
||||
user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
|
||||
|
||||
jz_chrome:
|
||||
user: jz
|
||||
endpoint: https://fcm.googleapis.com/fcm/send/456
|
||||
p256dh_key: 456-RIXcMgkdjhRnFZaYjjGvo00dydRQbCpQTuXFjLaCPSE7ofxi19awgGc3Doqa1RmYQqsbQDfQTifFZgc
|
||||
auth_key: xxx2DtgvmLkevKRwoyahJl0efg
|
||||
user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
|
||||
|
||||
kevin_chrome:
|
||||
user: kevin
|
||||
endpoint: https://fcm.googleapis.com/fcm/send/789
|
||||
p256dh_key: 456-RIXcMgkdjhRnFZaYjjGvo00dydRQbCpQTuXFjLaCPSE7ofxi19awgGc3Doqa1RmYQqsbQDfQTifFZgc
|
||||
auth_key: xxx2DtgvmLkevKRwoyahJl0efg
|
||||
user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
pets:
|
||||
name: All Pets
|
||||
type: Rooms::Open
|
||||
creator: :david
|
||||
|
||||
hq:
|
||||
name: HQ
|
||||
type: Rooms::Open
|
||||
creator: :david
|
||||
|
||||
watercooler:
|
||||
name: All Talk
|
||||
type: Rooms::Closed
|
||||
creator: :david
|
||||
|
||||
designers:
|
||||
name: Designers
|
||||
type: Rooms::Closed
|
||||
creator: :david
|
||||
|
||||
david_and_jason:
|
||||
type: Rooms::Direct
|
||||
creator: :david
|
||||
|
||||
david_and_kevin:
|
||||
type: Rooms::Direct
|
||||
creator: :david
|
||||
|
||||
bender_and_kevin:
|
||||
type: Rooms::Direct
|
||||
creator: :kevin
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
david_pizza:
|
||||
user: david
|
||||
query: pizza
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
david_safari:
|
||||
user: david
|
||||
token: AxJs94fteQ5Autv2VrKsH68c
|
||||
user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) HEY/1.0.4 Chrome/83.0.4103.100 Electron/9.0.3 Safari/537.36"
|
||||
last_active_at: <%= 2.hours.ago %>
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
<% password_digest = BCrypt::Password.create("secret123456") %>
|
||||
|
||||
david:
|
||||
name: David
|
||||
email_address: david@37signals.com
|
||||
password_digest: <%= password_digest %>
|
||||
role: administrator
|
||||
|
||||
jason:
|
||||
name: Jason
|
||||
email_address: jason@37signals.com
|
||||
password_digest: <%= password_digest %>
|
||||
role: administrator
|
||||
|
||||
jz:
|
||||
name: JZ
|
||||
email_address: jz@37signals.com
|
||||
password_digest: <%= password_digest %>
|
||||
bio: Designer
|
||||
|
||||
kevin:
|
||||
name: Kevin
|
||||
email_address: kevin@37signals.com
|
||||
password_digest: <%= password_digest %>
|
||||
bio: Programmer
|
||||
|
||||
bender:
|
||||
name: Bender Bot
|
||||
bot_token: <%= User.generate_bot_token %>
|
||||
role: bot
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
bender:
|
||||
user: bender
|
||||
url: http://example.com/bender
|
||||
Whitespace-only changes.
@@ -0,0 +1,93 @@
|
||||
require "test_helper"
|
||||
|
||||
class ContentFiltersTest < ActionView::TestCase
|
||||
test "entire message contains an unfurled URL" do
|
||||
text = "https://basecamp.com/"
|
||||
message = Message.create! room: rooms(:pets), body: unfurled_message_body_for_basecamp(text), client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_not_equal message.body.body.to_html, filtered.to_html
|
||||
assert_match /<div><action-text-attachment/, filtered.to_html
|
||||
end
|
||||
|
||||
test "message includes additional text besides an unfurled URL" do
|
||||
text = "Hello https://basecamp.com/"
|
||||
message = Message.create! room: rooms(:pets), body: unfurled_message_body_for_basecamp(text), client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_equal message.body.body.to_html, filtered.to_html
|
||||
assert_match %r{<div>Hello https://basecamp\.com/<action-text-attachment}, filtered.to_html
|
||||
end
|
||||
|
||||
test "unfurled tweet without any image" do
|
||||
text = "<div>https://twitter.com/37signals/status/1750290547908952568<action-text-attachment content-type=\"application/vnd.actiontext.opengraph-embed\" url=\"https://pbs.twimg.com/profile_images/1671940407633010689/9P5gi6LF_200x200.jpg\" href=\"https://twitter.com/37signals/status/1750290547908952568\" filename=\"37signals (@37signals)\" caption=\"We're back up on all apps, everyone. Really sorry for the disruption to your day.\" content=\"<actiontext-opengraph-embed>\n <div class="og-embed">\n <div class="og-embed__content">\n <div class="og-embed__title">37signals (@37signals)</div>\n <div class="og-embed__description">We're back up on all apps, everyone. Really sorry for the disruption to your day.</div>\n </div>\n <div class="og-embed__image">\n <img src="https://pbs.twimg.com/profile_images/1671940407633010689/9P5gi6LF_200x200.jpg" class="image" alt="" />\n </div>\n </div>\n </actiontext-opengraph-embed>\"></action-text-attachment></div>"
|
||||
message = Message.create! room: rooms(:pets), body: unfurled_message_body_for_basecamp(text), client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::StyleUnfurledTwitterAvatars.apply(message.body.body)
|
||||
assert_match %r{<div class="cf-twitter-avatar">}, filtered.to_html
|
||||
end
|
||||
|
||||
test "unfurled tweet containing an image" do
|
||||
text = "<div>https://twitter.com/dhh/status/1748445489648050505<action-text-attachment content-type=\"application/vnd.actiontext.opengraph-embed\" url=\"https://pbs.twimg.com/media/GEO5l04bsAA9f6H.jpg\" href=\"https://twitter.com/dhh/status/1748445489648050505\" filename=\"DHH (@dhh)\" caption=\"We pay homage to the glorious MIT License with the ONCE license. May all our future legalese be as succinct!\" content=\"<actiontext-opengraph-embed>\n <div class="og-embed">\n <div class="og-embed__content">\n <div class="og-embed__title">DHH (@dhh)</div>\n <div class="og-embed__description">We pay homage to the glorious MIT License with the ONCE license. May all our future legalese be as succinct!</div>\n </div>\n <div class="og-embed__image">\n <img src="https://pbs.twimg.com/media/GEO5l04bsAA9f6H.jpg" class="image" alt="" />\n </div>\n </div>\n </actiontext-opengraph-embed>\"></action-text-attachment></div>"
|
||||
message = Message.create! room: rooms(:pets), body: unfurled_message_body_for_basecamp(text), client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::StyleUnfurledTwitterAvatars.apply(message.body.body)
|
||||
assert_no_match %r{<div class="cf-twitter-avatar">}, filtered.to_html
|
||||
end
|
||||
|
||||
test "entire message contains an unfurled URL from x.com but unfurls to twitter.com" do
|
||||
text = "https://x.com/dhh/status/1752476663303323939"
|
||||
message = Message.create! room: rooms(:pets), body: unfurled_message_body_for_twitter(text), client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_not_equal message.body.body.to_html, filtered.to_html
|
||||
assert_match /<div><action-text-attachment/, filtered.to_html
|
||||
end
|
||||
|
||||
test "entire message contains an unfurled URL from x.com with query params" do
|
||||
text = "https://x.com/dhh/status/1752476663303323939?s=20"
|
||||
message = Message.create! room: rooms(:pets), body: unfurled_message_body_for_twitter(text), client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_not_equal message.body.body.to_html, filtered.to_html
|
||||
assert_match /<div><action-text-attachment/, filtered.to_html
|
||||
end
|
||||
|
||||
test "message contains a forbidden tag" do
|
||||
exploit_image_tag = 'Hello <img src="https://ssecurityrise.com/tests/billionlaughs-cache.svg">World'
|
||||
message = Message.create! room: rooms(:pets), body: exploit_image_tag, client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_equal "Hello World", filtered.to_html
|
||||
end
|
||||
|
||||
test "message with a mention attachment" do
|
||||
message = Message.create! room: rooms(:pets), body: "<div>Hey #{mention_attachment_for(:david)}</div>", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
expected = /<action-text-attachment sgid="#{users(:david).attachable_sgid}" content-type="application\/vnd\.campfire\.mention" content="(.*?)"><\/action-text-attachment>/m
|
||||
|
||||
assert_match expected, filtered.to_html
|
||||
end
|
||||
|
||||
private
|
||||
def unfurled_message_body_for_basecamp(text)
|
||||
"<div>#{text}#{unfurled_link_trix_attachment_for_basecamp}</div>"
|
||||
end
|
||||
|
||||
def unfurled_link_trix_attachment_for_basecamp
|
||||
<<~BASECAMP
|
||||
<action-text-attachment content-type=\"application/vnd.actiontext.opengraph-embed\" url=\"https://basecamp.com/assets/general/opengraph.png\" href=\"https://basecamp.com/\" filename=\"Project management software, online collaboration\" caption=\"Trusted by millions, Basecamp puts everything you need to get work done in one place. It’s the calm, organized way to manage projects, work with clients, and communicate company-wide.\" content=\"<actiontext-opengraph-embed>\n <div class="og-embed">\n <div class="og-embed__content">\n <div class="og-embed__title">Project management software, online collaboration</div>\n <div class="og-embed__description">Trusted by millions, Basecamp puts everything you need to get work done in one place. It’s the calm, organized way to manage projects, work with clients, and communicate company-wide.</div>\n </div>\n <div class="og-embed__image">\n <img src="https://basecamp.com/assets/general/opengraph.png" class="image" alt="" />\n </div>\n </div>\n </actiontext-opengraph-embed>\"></action-text-attachment>
|
||||
BASECAMP
|
||||
end
|
||||
|
||||
def unfurled_message_body_for_twitter(text)
|
||||
"<div>#{text}#{unfurled_link_trix_attachment_for_twitter}</div>"
|
||||
end
|
||||
|
||||
def unfurled_link_trix_attachment_for_twitter
|
||||
<<~TWEET
|
||||
<action-text-attachment content-type=\"application/vnd.actiontext.opengraph-embed\" url=\"https://pbs.twimg.com/ext_tw_video_thumb/1752476502791503873/pu/img/WEAqUgarUxWjPNHD.jpg\" href=\"https://twitter.com/dhh/status/1752476663303323939\" filename=\"DHH (@dhh)\" caption=\"We're playing with adding easy extension points to ONCE/Campfire. Here's one experiment for allowing any type of CSS to be easily added.\" content=\"<actiontext-opengraph-embed>\n <div class="og-embed">\n <div class="og-embed__content">\n <div class="og-embed__title">DHH (@dhh)</div>\n <div class="og-embed__description">We're playing with adding easy extension points to ONCE/Campfire. Here's one experiment for allowing any type of CSS to be easily added.</div>\n </div>\n <div class="og-embed__image">\n <img src="https://pbs.twimg.com/ext_tw_video_thumb/1752476502791503873/pu/img/WEAqUgarUxWjPNHD.jpg" class="image" alt="" />\n </div>\n </div>\n </actiontext-opengraph-embed>\"><figure class=\"attachment attachment--content attachment--og\">\n \n <div class=\"og-embed gap\">\n <div class=\"og-embed__content\">\n <div class=\"og-embed__title\">\n <a href=\"https://twitter.com/dhh/status/1752476663303323939\">DHH (@dhh)</a>\n </div>\n <div class=\"og-embed__description\">We're playing with adding easy extension points to ONCE/Campfire. Here's one experiment for allowing any type of CSS to be easily added.</div>\n </div>\n <div class=\"og-embed__image\">\n <img src=\"https://pbs.twimg.com/ext_tw_video_thumb/1752476502791503873/pu/img/WEAqUgarUxWjPNHD.jpg\" class=\"image center\" alt=\"\">\n </div>\n </div>\n \n</figure></action-text-attachment>
|
||||
TWEET
|
||||
end
|
||||
end
|
||||
Whitespace-only changes.
@@ -0,0 +1,15 @@
|
||||
require "test_helper"
|
||||
|
||||
class Account::JoinableTest < ActiveSupport::TestCase
|
||||
test "new accounts get a joinable code" do
|
||||
Account.destroy_all
|
||||
account = Account.create!(name: "Chat")
|
||||
assert_match /\w{4}-\w{4}-\w{4}/, account.join_code
|
||||
end
|
||||
|
||||
test "accounts can reset join code" do
|
||||
assert_changes -> { accounts(:signal).reload.join_code } do
|
||||
accounts(:signal).reset_join_code
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
require "test_helper"
|
||||
|
||||
class AccountTest < ActiveSupport::TestCase
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
require "test_helper"
|
||||
|
||||
class ActionTextAttachmentTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@user = users(:david)
|
||||
end
|
||||
|
||||
test "lookup user attachable with invalid sgid" do
|
||||
message, signature = @user.attachable_sgid.split("--")
|
||||
|
||||
html = %Q(<action-text-attachment sgid="#{message}--invalid"></action-text-attachment>)
|
||||
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
|
||||
|
||||
attachment = ActionText::Attachment.from_node(node)
|
||||
assert_equal @user, attachment.attachable
|
||||
end
|
||||
|
||||
test "lookup attachable with nil sgid" do
|
||||
html = %Q(<action-text-attachment></action-text-attachment>)
|
||||
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
|
||||
|
||||
attachment = ActionText::Attachment.from_node(node)
|
||||
assert_kind_of ActionText::Attachables::MissingAttachable, attachment.attachable
|
||||
end
|
||||
|
||||
test "lookup invalid sgid for an attachable requiring a valid sgid" do
|
||||
# Make room instance attachable for testing purposes
|
||||
room = rooms(:pets).tap { |r| r.extend ActionText::Attachable }
|
||||
|
||||
message, signature = rooms(:pets).attachable_sgid.split("--")
|
||||
|
||||
html = %Q(<action-text-attachment sgid="#{message}--invalid"></action-text-attachment>)
|
||||
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
|
||||
|
||||
attachment = ActionText::Attachment.from_node(node)
|
||||
assert_kind_of ActionText::Attachables::MissingAttachable, attachment.attachable
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
require "test_helper"
|
||||
|
||||
class FirstRunTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
Account.destroy_all
|
||||
Room.destroy_all
|
||||
User.destroy_all
|
||||
end
|
||||
|
||||
test "creating makes first user an administrator" do
|
||||
user = create_first_run_user
|
||||
assert user.administrator?
|
||||
end
|
||||
|
||||
test "first user has access to first room" do
|
||||
user = create_first_run_user
|
||||
assert user.rooms.one?
|
||||
end
|
||||
|
||||
test "first room is an open room" do
|
||||
create_first_run_user
|
||||
assert Room.first.open?
|
||||
end
|
||||
|
||||
private
|
||||
def create_first_run_user
|
||||
FirstRun.create!({ name: "User", email_address: "user@example.com", password: "secret123456" })
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
require "test_helper"
|
||||
|
||||
class MembershipTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@membership = memberships(:david_watercooler)
|
||||
end
|
||||
|
||||
test "connected scope" do
|
||||
@membership.connected
|
||||
assert Membership.connected.exists?(@membership.id)
|
||||
|
||||
@membership.disconnected
|
||||
assert_not Membership.connected.exists?(@membership.id)
|
||||
|
||||
travel_to Membership::Connectable::CONNECTION_TTL.from_now + 1
|
||||
assert_not Membership.connected.exists?(@membership.id)
|
||||
end
|
||||
|
||||
test "disconnected scope" do
|
||||
@membership.disconnected
|
||||
assert Membership.disconnected.exists?(@membership.id)
|
||||
|
||||
@membership.connected
|
||||
assert_not Membership.disconnected.exists?(@membership.id)
|
||||
|
||||
travel_to Membership::Connectable::CONNECTION_TTL.from_now + 1
|
||||
assert Membership.disconnected.exists?(@membership.id)
|
||||
end
|
||||
|
||||
test "connected? is false when connection is stale" do
|
||||
@membership.connected
|
||||
travel_to Membership::Connectable::CONNECTION_TTL.from_now + 1
|
||||
assert_not @membership.connected?
|
||||
end
|
||||
|
||||
test "connecting" do
|
||||
@membership.connected
|
||||
assert @membership.connected?
|
||||
assert_equal 1, @membership.connections
|
||||
|
||||
@membership.connected
|
||||
assert_equal 2, @membership.connections
|
||||
end
|
||||
|
||||
test "connecting resets stale connection count" do
|
||||
2.times { @membership.connected }
|
||||
assert_equal 2, @membership.connections
|
||||
|
||||
travel_to Membership::Connectable::CONNECTION_TTL.from_now + 1
|
||||
@membership.connected
|
||||
assert_equal 1, @membership.connections
|
||||
end
|
||||
|
||||
test "disconnecting" do
|
||||
2.times { @membership.connected }
|
||||
|
||||
@membership.disconnected
|
||||
assert @membership.connected?
|
||||
assert_equal 1, @membership.connections
|
||||
|
||||
@membership.disconnected
|
||||
assert_not @membership.connected?
|
||||
assert_equal 0, @membership.connections
|
||||
end
|
||||
|
||||
test "disconnecting resets stale connection count" do
|
||||
2.times { @membership.connected }
|
||||
assert_equal 2, @membership.connections
|
||||
|
||||
travel_to Membership::Connectable::CONNECTION_TTL.from_now + 1
|
||||
@membership.disconnected
|
||||
assert_equal 0, @membership.connections
|
||||
end
|
||||
|
||||
test "refreshing the connection" do
|
||||
@membership.connected
|
||||
|
||||
travel_to Membership::Connectable::CONNECTION_TTL.from_now + 1
|
||||
assert_not @membership.connected?
|
||||
|
||||
@membership.refresh_connection
|
||||
assert @membership.connected?
|
||||
end
|
||||
|
||||
test "removing a membership resets the user's connections" do
|
||||
@membership.user.expects :reset_remote_connections
|
||||
|
||||
@membership.destroy
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
require "test_helper"
|
||||
|
||||
class Message::AttachmentTest < ActiveSupport::TestCase
|
||||
include ActiveJob::TestHelper
|
||||
include ActionDispatch::TestProcess
|
||||
|
||||
test "creating a message creates image thumbnail" do
|
||||
message = create_attachment_message("moon.jpg", "image/jpeg")
|
||||
assert message.attachment.representation(:thumb).image.present?
|
||||
end
|
||||
|
||||
test "creating a message creates video preview" do
|
||||
message = create_attachment_message("alpha-centuri.mov", "video/quicktime")
|
||||
assert message.reload.attachment.preview(format: :webp).image.attached?
|
||||
end
|
||||
|
||||
test "creating a blank message with attachment will use filename as plain text body" do
|
||||
message = create_attachment_message("moon.jpg", "image/jpeg")
|
||||
assert_equal message.plain_text_body, "moon.jpg"
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def create_attachment_message(file, content_type)
|
||||
rooms(:hq).messages.create_with_attachment! \
|
||||
creator: users(:david),
|
||||
client_message_id: "message",
|
||||
attachment: fixture_file_upload(file, content_type)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
require "test_helper"
|
||||
|
||||
class Message::SearchableTest < ActiveSupport::TestCase
|
||||
test "message body is indexed and searchable" do
|
||||
message = rooms(:designers).messages.create! body: "My hovercraft is full of eels", client_message_id: "earth", creator: users(:david)
|
||||
assert_equal [ message ], rooms(:designers).messages.search("eel")
|
||||
|
||||
message.update! body: "My hovercraft is full of sharks"
|
||||
assert_equal [ message ], rooms(:designers).messages.search("sharks")
|
||||
|
||||
message.destroy!
|
||||
assert_equal [], rooms(:designers).messages.search("sharks")
|
||||
end
|
||||
|
||||
test "search results are returned in message order" do
|
||||
messages = [ "first cat", "second cat", "third cat", "cat cat cat" ].map do |body|
|
||||
rooms(:designers).messages.create! body: body, client_message_id: body, creator: users(:david)
|
||||
end
|
||||
|
||||
assert_equal messages, rooms(:designers).messages.search("cat")
|
||||
end
|
||||
|
||||
test "rich text body is converted to plain text for indexing" do
|
||||
message = rooms(:designers).messages.create! body: "<span>My hovercraft is full of eels</span>", client_message_id: "earth", creator: users(:david)
|
||||
|
||||
assert_equal [], rooms(:designers).messages.search("span")
|
||||
assert_equal [ message ], rooms(:designers).messages.search("eel")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
require "test_helper"
|
||||
|
||||
class MessageTest < ActiveSupport::TestCase
|
||||
include ActionCable::TestHelper, ActiveJob::TestHelper
|
||||
|
||||
test "creating a message enqueues to push later" do
|
||||
assert_enqueued_jobs 1, only: [ Room::PushMessageJob ] do
|
||||
create_new_message_in rooms(:designers)
|
||||
end
|
||||
end
|
||||
|
||||
test "all emoji" do
|
||||
assert Message.new(body: "😄🤘").plain_text_body.all_emoji?
|
||||
assert_not Message.new(body: "Haha! 😄🤘").plain_text_body.all_emoji?
|
||||
assert_not Message.new(body: "🔥\nmultiple lines\n💯").plain_text_body.all_emoji?
|
||||
assert_not Message.new(body: "🔥 💯").plain_text_body.all_emoji?
|
||||
end
|
||||
|
||||
test "mentionees" do
|
||||
message = Message.new room: rooms(:pets), body: "<div>Hey #{mention_attachment_for(:david)}</div>", creator: users(:jason), client_message_id: "earth"
|
||||
assert_equal [ users(:david) ], message.mentionees
|
||||
|
||||
message_with_duplicate_mentions = Message.new room: rooms(:pets), body: "<div>Hey #{mention_attachment_for(:david)} #{mention_attachment_for(:david)}</div>", creator: users(:jason), client_message_id: "earth"
|
||||
assert_equal [ users(:david) ], message.mentionees
|
||||
|
||||
message_mentioning_a_non_member = Message.new room: rooms(:pets), body: "<div>Hey #{mention_attachment_for(:kevin)}</div>", creator: users(:jason), client_message_id: "earth"
|
||||
assert_equal [], message_mentioning_a_non_member.mentionees
|
||||
end
|
||||
|
||||
private
|
||||
def create_new_message_in(room)
|
||||
room.messages.create!(creator: users(:jason), body: "Hello", client_message_id: "123")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
require "test_helper"
|
||||
|
||||
class Opengraph::DocumentTest < ActiveSupport::TestCase
|
||||
test "extract opengraph tags using property attribute" do
|
||||
document = Opengraph::Document.new("<html><head><meta property=\"og:url\" content=\"https://example.com\"><meta property=\"og:title\" content=\"Hey!\"><meta property=\"og:description\" content=\"desc..\"><meta property=\"og:image\" content=\"https://example.com/image.png\"></head></html>")
|
||||
attributes = document.opengraph_attributes
|
||||
|
||||
assert_equal "https://example.com", attributes[:url]
|
||||
assert_equal "Hey!", attributes[:title]
|
||||
assert_equal "desc..", attributes[:description]
|
||||
assert_equal "https://example.com/image.png", attributes[:image]
|
||||
end
|
||||
|
||||
test "extract opengraph tags using name attribute" do
|
||||
document = Opengraph::Document.new("<html><head><meta name=\"og:url\" content=\"https://example.com\"><meta name=\"og:title\" content=\"Hey!\"><meta name=\"og:description\" content=\"desc..\"><meta name=\"og:image\" content=\"https://example.com/image.png\"></head></html>")
|
||||
attributes = document.opengraph_attributes
|
||||
|
||||
assert_equal "https://example.com", attributes[:url]
|
||||
assert_equal "Hey!", attributes[:title]
|
||||
assert_equal "desc..", attributes[:description]
|
||||
assert_equal "https://example.com/image.png", attributes[:image]
|
||||
end
|
||||
|
||||
test "document containing missing meta encoding tag and non-UTF8 characters" do
|
||||
document = Opengraph::Document.new("<html><head><meta name=\"og:url\" content=\"https://example.com\"><meta name=\"og:title\" content=\"Hey!\"><meta name=\"og:description\" content=\"Hello â\u0080\u0099World\"><meta name=\"og:image\" content=\"https://example.com/image.png\"></head></html>")
|
||||
attributes = document.opengraph_attributes
|
||||
|
||||
assert_equal "https://example.com", attributes[:url]
|
||||
assert_equal "Hey!", attributes[:title]
|
||||
assert_equal "Hello World", attributes[:description]
|
||||
assert_equal "https://example.com/image.png", attributes[:image]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,118 @@
|
||||
require "test_helper"
|
||||
require "restricted_http/private_network_guard"
|
||||
|
||||
class Opengraph::FetchTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@fetch = Opengraph::Fetch.new
|
||||
@url = URI.parse("https://www.example.com")
|
||||
end
|
||||
|
||||
test "#fetch_document fetches valid HTML" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 200, body: "<body>ok<body>", headers: { content_type: "text/html" })
|
||||
|
||||
assert_equal "<body>ok<body>", @fetch.fetch_document(@url)
|
||||
end
|
||||
|
||||
test "#fetch_document discards other content types" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 200, body: "I'm not HTML!", headers: { content_type: "text/plain" })
|
||||
|
||||
assert_nil @fetch.fetch_document(@url)
|
||||
end
|
||||
|
||||
test "#fetch_document follows redirects" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 302, headers: { location: "https://www.other.com/" })
|
||||
|
||||
WebMock.stub_request(:get, "https://www.other.com/")
|
||||
.to_return(status: 200, body: "<body>ok<body>", headers: { content_type: "text/html" })
|
||||
|
||||
assert_equal "<body>ok<body>", @fetch.fetch_document(@url)
|
||||
end
|
||||
|
||||
test "#fetch_document does not follow redirects to private networks" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 302, headers: { location: "https://www.other.com/" })
|
||||
|
||||
WebMock.stub_request(:get, "https://www.other.com/")
|
||||
.to_return(status: 200, body: "<body>ok<body>", headers: { content_type: "text/html" })
|
||||
Resolv.stubs(:getaddress).with("www.other.com").returns("127.0.0.1")
|
||||
|
||||
assert_raises RestrictedHTTP::Violation do
|
||||
@fetch.fetch_document(@url, ip: "1.2.3.4")
|
||||
end
|
||||
end
|
||||
|
||||
test "#fetch_document resolves hostnames once to avoid DNS rebinding" do
|
||||
# Allow but interrupt a real connection to demonstrate that we connect
|
||||
# to a resolved IP, not a hostname to re-resolve.
|
||||
WebMock.disable_net_connect! allow: [ @url.host ]
|
||||
Resolv.stubs(:getaddress).with(@url.host).returns("1.2.3.4", "127.0.0.1")
|
||||
TCPSocket.expects(:open).with(@url.host, 443, nil, nil).never
|
||||
TCPSocket.expects(:open).with("1.2.3.4", 443, nil, nil).throws(:dns_not_rebound)
|
||||
|
||||
assert_throws :dns_not_rebound do
|
||||
@fetch.fetch_document(@url)
|
||||
end
|
||||
end
|
||||
|
||||
test "#fetch_document resolves redirect location hostnames once to avoid DNS rebinding" do
|
||||
# Stub the initial URL to redirect to a DNS-rebound location
|
||||
WebMock.stub_request(:get, "https://www.other.com/")
|
||||
.to_return(status: 302, headers: { location: @url.to_s })
|
||||
|
||||
# Allow but interrupt a real connection to demonstrate that we connect
|
||||
# to a resolved IP, not a hostname to re-resolve.
|
||||
WebMock.disable_net_connect! allow: [ @url.host ]
|
||||
Resolv.stubs(:getaddress).with(@url.host).returns("1.2.3.4", "127.0.0.1")
|
||||
TCPSocket.expects(:open).with(@url.host, 443, nil, nil).never
|
||||
TCPSocket.expects(:open).with("1.2.3.4", 443, nil, nil).throws(:dns_not_rebound)
|
||||
|
||||
assert_throws :dns_not_rebound do
|
||||
@fetch.fetch_document(URI.parse("https://www.other.com/"), ip: "1.2.3.4")
|
||||
end
|
||||
end
|
||||
|
||||
test "#fetch_document is empty following redirects that never finish" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 302, headers: { location: "https://www.example.com/" })
|
||||
|
||||
assert_raises Opengraph::Fetch::TooManyRedirectsError do
|
||||
@fetch.fetch_document(@url)
|
||||
end
|
||||
end
|
||||
|
||||
test "#fetch_document ignores large responses" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 200, body: "too large", headers: { content_length: 1.gigabyte, content_type: "text/html" })
|
||||
|
||||
assert_nil @fetch.fetch_document(@url)
|
||||
end
|
||||
|
||||
test "#fetch_document ignores large responses that were missing their content length" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 200, body: large_body_content, headers: { content_type: "text/html" })
|
||||
|
||||
assert_nil @fetch.fetch_document(@url)
|
||||
end
|
||||
|
||||
test "#fetch_document ignores large responses that were lying about their content length" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 200, body: large_body_content, headers: { content_length: 1.megabyte, content_type: "text/html" })
|
||||
|
||||
assert_nil @fetch.fetch_document(@url)
|
||||
end
|
||||
|
||||
test "fetch content type" do
|
||||
WebMock.stub_request(:head, "https://example.com/image.png").to_return(status: 200, headers: { content_type: "image/png" })
|
||||
|
||||
url = URI.parse("https://example.com/image.png")
|
||||
assert_equal "image/png", @fetch.fetch_content_type(url)
|
||||
end
|
||||
|
||||
private
|
||||
def large_body_content
|
||||
"x" * (Opengraph::Fetch::MAX_BODY_SIZE + 1)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
require "test_helper"
|
||||
require "restricted_http/private_network_guard"
|
||||
|
||||
class Opengraph::LocationTest < ActiveSupport::TestCase
|
||||
test "url validations" do
|
||||
assert Opengraph::Location.new("https://www.example.com").valid?
|
||||
assert Opengraph::Location.new("http://www.example.com").valid?
|
||||
|
||||
assert_not Opengraph::Location.new("~/etc/password").valid?
|
||||
assert_not Opengraph::Location.new("ftp://speedtest.tele2.net").valid?
|
||||
assert_not Opengraph::Location.new("httpfake").valid?
|
||||
assert_not Opengraph::Location.new(" foo").valid?
|
||||
assert_not Opengraph::Location.new("https/incorrect").valid?
|
||||
end
|
||||
|
||||
test "private network urls" do
|
||||
Resolv.stubs(:getaddress).with("www.example.com").returns("172.16.0.0")
|
||||
|
||||
location = Opengraph::Location.new("https://www.example.com")
|
||||
assert_not location.valid?
|
||||
assert_equal [ "is not public" ], location.errors[:url]
|
||||
end
|
||||
|
||||
test "avoid reading file urls when expecting HTML" do
|
||||
large_file = Opengraph::Location.new("https://www.example.com/100gb.zip")
|
||||
|
||||
assert_nil Opengraph::Location.new("http://www.example.com/video.mp4").read_html
|
||||
assert_nil Opengraph::Location.new("http://www.example.com/archive.tar").read_html
|
||||
assert_nil Opengraph::Location.new("https://www.example.com/large.heic").read_html
|
||||
assert_nil Opengraph::Location.new("https://www.example.com/image.jpeg").read_html
|
||||
assert_nil Opengraph::Location.new("https://www.example.com/malware.exe").read_html
|
||||
assert_nil Opengraph::Location.new("https://www.example.com/massiveOS.iso").read_html
|
||||
end
|
||||
|
||||
test "read valid HTML" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 200, body: "<body>ok<body>", headers: { content_type: "text/html" })
|
||||
|
||||
location = Opengraph::Location.new("https://www.example.com")
|
||||
assert_equal "<body>ok<body>", location.read_html
|
||||
end
|
||||
|
||||
test "read ignores invalid responses" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/")
|
||||
.to_return(status: 200, body: "too large", headers: { content_length: 1.gigabyte, content_type: "text/html" })
|
||||
|
||||
location = Opengraph::Location.new("https://www.example.com")
|
||||
assert_nil location.read_html
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,170 @@
|
||||
require "test_helper"
|
||||
|
||||
class Opengraph::MetadataTest < ActiveSupport::TestCase
|
||||
test "successful fetch" do
|
||||
body = <<~HTML
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:url" content="https://example.com">
|
||||
<meta property="og:title" content="Hey!">
|
||||
<meta property="og:description" content="Hello">
|
||||
<meta property="og:image" content="https://example.com/image.png">
|
||||
</head>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 200, body: body, headers: { content_type: "text/html" })
|
||||
WebMock.stub_request(:head, "https://example.com/image.png").to_return(status: 200, headers: { content_type: "image/png" })
|
||||
|
||||
metadata = Opengraph::Metadata.from_url("https://www.example.com")
|
||||
assert metadata.valid?
|
||||
|
||||
assert_equal "https://example.com", metadata.url
|
||||
assert_equal "Hey!", metadata.title
|
||||
assert_equal "Hello", metadata.description
|
||||
assert_equal "https://example.com/image.png", metadata.image
|
||||
end
|
||||
|
||||
test "missing opengraph meta tags" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 200, body: "<html><head></head></html>", headers: { content_type: "text/html" })
|
||||
opengraph = Opengraph::Metadata.from_url("https://www.example.com")
|
||||
|
||||
assert_not opengraph.valid?
|
||||
assert_equal [ "Title can't be blank", "Description can't be blank" ], opengraph.errors.full_messages
|
||||
end
|
||||
|
||||
test "URL uses the provided value if the returned value is missing" do
|
||||
body = <<~HTML
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:title" content="Hey!">
|
||||
<meta property="og:description" content="Hello">
|
||||
<meta property="og:image" content="https://example.com/image.png">
|
||||
</head>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 200, body: body, headers: { content_type: "text/html" })
|
||||
WebMock.stub_request(:head, "https://example.com/image.png").to_return(status: 200, headers: { content_type: "image/png" })
|
||||
|
||||
metadata = Opengraph::Metadata.from_url("https://www.example.com")
|
||||
|
||||
assert metadata.valid?
|
||||
assert_equal "https://www.example.com", metadata.url
|
||||
end
|
||||
|
||||
test "URL uses the provided value if the returned value is invalid" do
|
||||
body = <<~HTML
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:url" content="/foo">
|
||||
<meta property="og:title" content="Hey!">
|
||||
<meta property="og:description" content="Hello">
|
||||
<meta property="og:image" content="https://example.com/image.png">
|
||||
</head>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
WebMock.stub_request(:get, "https://www.example.com/foo").to_return(status: 200, body: body, headers: { content_type: "text/html" })
|
||||
WebMock.stub_request(:head, "https://example.com/image.png").to_return(status: 200, headers: { content_type: "image/png" })
|
||||
|
||||
metadata = Opengraph::Metadata.from_url("https://www.example.com/foo")
|
||||
|
||||
assert metadata.valid?
|
||||
assert_equal "https://www.example.com/foo", metadata.url
|
||||
end
|
||||
|
||||
test "missing response body" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 403, body: "", headers: { content_type: "text/html" })
|
||||
assert_not Opengraph::Metadata.from_url("https://www.example.com").valid?
|
||||
end
|
||||
|
||||
test "non html response" do
|
||||
WebMock.stub_request(:get, "https://www.example.com/image").to_return(status: 200, body: "[blob]", headers: { content_type: "image/jpeg" })
|
||||
assert_not Opengraph::Metadata.from_url("https://www.example.com/image").valid?
|
||||
end
|
||||
|
||||
test "relative and invalid image URLs are ignored" do
|
||||
body = <<~HTML
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:url" content="https://example.com">
|
||||
<meta property="og:title" content="Hey!">
|
||||
<meta property="og:description" content="Hello">
|
||||
<meta property="og:image" content="%s">
|
||||
</head>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
[ "/image.png", "foo", "https/incorrect", "~/etc/password" ].each do |invalid_image_url|
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 200, body: body % invalid_image_url, headers: { content_type: "text/html" })
|
||||
opengraph = Opengraph::Metadata.from_url("https://www.example.com")
|
||||
|
||||
assert opengraph.valid?
|
||||
assert_nil opengraph.image
|
||||
end
|
||||
end
|
||||
|
||||
test "sanitize title and description" do
|
||||
body = <<~HTML
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:title" content="Hey!<script>alert('hi')</script>">
|
||||
<meta property="og:description" content="Hello<script>alert('hi')</script>">
|
||||
<meta property="og:image" content="https://example.com/image.png">
|
||||
</head>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 200, body: body, headers: { content_type: "text/html" })
|
||||
WebMock.stub_request(:head, "https://example.com/image.png").to_return(status: 200, headers: { content_type: "image/png" })
|
||||
|
||||
metadata = Opengraph::Metadata.from_url("https://www.example.com")
|
||||
|
||||
assert metadata.valid?
|
||||
assert_equal "Hey!", metadata.title
|
||||
assert_equal "Hello", metadata.description
|
||||
end
|
||||
|
||||
test "remove encoded tags from title and description" do
|
||||
body = <<~HTML
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:title" content="Hey!</script><img src=a onerror=prompt(1)>">
|
||||
<meta property="og:description" content="Hello</script><img src=a onerror=prompt(2)></script>">
|
||||
<meta property="og:image" content="https://example.com/image.png">
|
||||
</head>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 200, body: body, headers: { content_type: "text/html" })
|
||||
WebMock.stub_request(:head, "https://example.com/image.png").to_return(status: 200, headers: { content_type: "image/png" })
|
||||
|
||||
metadata = Opengraph::Metadata.from_url("https://www.example.com")
|
||||
|
||||
assert metadata.valid?
|
||||
assert_equal "Hey!", metadata.title
|
||||
assert_equal "Hello", metadata.description
|
||||
end
|
||||
|
||||
test "does not allow SVG content type for preview image" do
|
||||
body = <<~HTML
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:url" content="https://example.com">
|
||||
<meta property="og:title" content="Hey!">
|
||||
<meta property="og:description" content="Hello">
|
||||
<meta property="og:image" content="https://example.com/image.svg">
|
||||
</head>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
WebMock.stub_request(:get, "https://www.example.com/").to_return(status: 200, body: body, headers: { content_type: "text/html" })
|
||||
WebMock.stub_request(:head, "https://example.com/image.svg").to_return(status: 200, headers: { content_type: "image/svg+xml" })
|
||||
|
||||
metadata = Opengraph::Metadata.from_url("https://www.example.com")
|
||||
assert metadata.valid?
|
||||
|
||||
assert_nil metadata.image
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
require "test_helper"
|
||||
|
||||
class Room::PushTest < ActiveSupport::TestCase
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
test "deliver new message to other room users with push subscriptions" do
|
||||
task_count = Push::Subscription.count - users(:david).push_subscriptions.count
|
||||
perform_enqueued_jobs only: Room::PushMessageJob do
|
||||
WebPush.expects(:payload_send).times(task_count)
|
||||
rooms(:hq).messages.create! body: "This is from earth", client_message_id: "earth", creator: users(:david)
|
||||
end
|
||||
wait_for_web_push_delivery_pool_tasks(task_count)
|
||||
end
|
||||
|
||||
test "notifies subscribed users" do
|
||||
perform_enqueued_jobs only: Room::PushMessageJob do
|
||||
WebPush.expects(:payload_send).times(2)
|
||||
rooms(:designers).messages.create! body: "This is from earth", client_message_id: "earth", creator: users(:david)
|
||||
end
|
||||
wait_for_web_push_delivery_pool_tasks(2)
|
||||
|
||||
perform_enqueued_jobs only: Room::PushMessageJob do
|
||||
WebPush.expects(:payload_send).times(3)
|
||||
rooms(:designers).messages.create! body: "Hey #{mention_attachment_for(:kevin)}", client_message_id: "earth", creator: users(:david)
|
||||
end
|
||||
wait_for_web_push_delivery_pool_tasks(5)
|
||||
end
|
||||
|
||||
test "does not notify for connected rooms" do
|
||||
memberships(:kevin_designers).connected
|
||||
|
||||
perform_enqueued_jobs only: Room::PushMessageJob do
|
||||
WebPush.expects(:payload_send).times(2)
|
||||
rooms(:designers).messages.create! body: "Hey @kevin", client_message_id: "earth", creator: users(:david)
|
||||
end
|
||||
wait_for_web_push_delivery_pool_tasks(2)
|
||||
end
|
||||
|
||||
test "does not notify for invisible rooms" do
|
||||
memberships(:kevin_designers).update! involvement: "invisible"
|
||||
|
||||
perform_enqueued_jobs only: Room::PushMessageJob do
|
||||
WebPush.expects(:payload_send).times(2)
|
||||
rooms(:designers).messages.create! body: "Hey @kevin", client_message_id: "earth", creator: users(:david)
|
||||
end
|
||||
wait_for_web_push_delivery_pool_tasks(2)
|
||||
end
|
||||
|
||||
test "destroys invalid subscriptions" do
|
||||
memberships(:kevin_designers).update! involvement: "invisible"
|
||||
|
||||
assert_difference -> { Push::Subscription.count }, -2 do
|
||||
perform_enqueued_jobs only: Room::PushMessageJob do
|
||||
WebPush.expects(:payload_send).times(2).raises(WebPush::ExpiredSubscription.new(Struct.new(:body).new, "example.com"))
|
||||
rooms(:designers).messages.create! body: "Hey @kevin", client_message_id: "earth", creator: users(:david)
|
||||
end
|
||||
wait_for_web_push_delivery_pool_tasks(2)
|
||||
wait_for_invalidation_pool_tasks(2)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def wait_for_web_push_delivery_pool_tasks(count)
|
||||
wait_for_pool_tasks(Rails.configuration.x.web_push_pool.delivery_pool, count)
|
||||
end
|
||||
|
||||
def wait_for_invalidation_pool_tasks(count)
|
||||
wait_for_pool_tasks(Rails.configuration.x.web_push_pool.invalidation_pool, count)
|
||||
end
|
||||
|
||||
def wait_for_pool_tasks(pool, count)
|
||||
start = Time.now
|
||||
timeout = 0.2
|
||||
while pool.completed_task_count < count
|
||||
raise "Timeout waiting for pool tasks to complete" if Time.now - start > timeout
|
||||
sleep timeout / 10.0
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
require "test_helper"
|
||||
|
||||
class RoomTest < ActiveSupport::TestCase
|
||||
test "grant membership to user" do
|
||||
rooms(:watercooler).memberships.grant_to(users(:kevin))
|
||||
assert rooms(:watercooler).users.include?(users(:kevin))
|
||||
end
|
||||
|
||||
test "revoke membership from user" do
|
||||
rooms(:watercooler).memberships.revoke_from(users(:david))
|
||||
assert_not rooms(:watercooler).users.include?(users(:david))
|
||||
end
|
||||
|
||||
test "revise memberships" do
|
||||
rooms(:watercooler).memberships.revise(granted: users(:kevin), revoked: users(:david))
|
||||
assert rooms(:watercooler).users.include?(users(:kevin))
|
||||
assert_not rooms(:watercooler).users.include?(users(:david))
|
||||
end
|
||||
|
||||
test "create for users by giving them immediate membership" do
|
||||
room = Rooms::Closed.create_for({ name: "Hello!", creator: users(:david) }, users: [ users(:kevin), users(:david) ])
|
||||
assert room.users.include?(users(:kevin))
|
||||
assert room.users.include?(users(:david))
|
||||
end
|
||||
|
||||
test "type" do
|
||||
assert Rooms::Open.new.open?
|
||||
assert_not Rooms::Open.new.direct?
|
||||
assert Rooms::Direct.new.direct?
|
||||
assert Rooms::Closed.new.closed?
|
||||
end
|
||||
|
||||
test "default involvement for new users" do
|
||||
room = Rooms::Closed.create_for({ name: "Hello!", creator: users(:david) }, users: [ users(:kevin), users(:david) ])
|
||||
assert room.memberships.all? { |m| m.involved_in_mentions? }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
require "test_helper"
|
||||
|
||||
class Rooms::DirectTest < ActiveSupport::TestCase
|
||||
test "create room for same users" do
|
||||
room = Rooms::Direct.find_or_create_for([ users(:david), users(:kevin) ])
|
||||
assert room.users.include?(users(:david))
|
||||
assert room.users.include?(users(:kevin))
|
||||
assert_not room.users.include?(users(:jason))
|
||||
end
|
||||
|
||||
test "only one room will exist for the same users" do
|
||||
room1 = Rooms::Direct.find_or_create_for([ users(:david), users(:kevin) ])
|
||||
room2 = Rooms::Direct.find_or_create_for([ users(:kevin), users(:david) ])
|
||||
assert_equal room1, room2
|
||||
end
|
||||
|
||||
test "default involvement for new users" do
|
||||
room = Rooms::Direct.find_or_create_for([ users(:david), users(:kevin) ])
|
||||
assert room.memberships.all? { |m| m.involved_in_everything? }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
require "test_helper"
|
||||
|
||||
class Rooms::OpenTest < ActiveSupport::TestCase
|
||||
test "grants access to all users after creation" do
|
||||
room = Rooms::Open.create!(name: "My open room with everyone!", creator: users(:david))
|
||||
assert_equal User.count, room.users.count
|
||||
end
|
||||
|
||||
test "grants access to all users after becoming open" do
|
||||
room = rooms(:watercooler).becomes!(Rooms::Open)
|
||||
room.save!
|
||||
assert_equal User.count, room.users.count
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
require "test_helper"
|
||||
|
||||
class User::BotTest < ActiveSupport::TestCase
|
||||
test "create bot" do
|
||||
token = "5M0aLYwQyBXOXa5Wsz6NZb11EE4tW2"
|
||||
SecureRandom.stubs(:alphanumeric).returns(token)
|
||||
|
||||
uuid = "3574925f-479d-44f8-82b7-fc039af5367c"
|
||||
Random.stubs(:uuid).returns(uuid)
|
||||
|
||||
bot = User.create_bot!(name: "Bender")
|
||||
assert_equal "#{bot.id}-#{token}", bot.bot_key
|
||||
end
|
||||
|
||||
test "reset bot key" do
|
||||
first_token = "5M0aLYwQyBXOXa5Wsz6NZb11EE4tW2"
|
||||
SecureRandom.stubs(:alphanumeric).returns(first_token)
|
||||
|
||||
bot = User.create_bot!(name: "Bender")
|
||||
assert_equal "#{bot.id}-#{first_token}", bot.bot_key
|
||||
|
||||
second_token = "R4kme9anwWRuz3sSoBXiB8Li8ioZPP"
|
||||
SecureRandom.stubs(:alphanumeric).returns(second_token)
|
||||
|
||||
bot.reset_bot_key
|
||||
assert_equal "#{bot.id}-#{second_token}", bot.bot_key
|
||||
end
|
||||
|
||||
test "authenticate" do
|
||||
bot = User.create_bot!(name: "Bender")
|
||||
assert User.authenticate_bot(bot.bot_key)
|
||||
end
|
||||
|
||||
test "deliver message by webhook" do
|
||||
WebMock.stub_request(:post, webhooks(:bender).url).to_return(status: 200)
|
||||
|
||||
perform_enqueued_jobs only: Bot::WebhookJob do
|
||||
users(:bender).deliver_webhook_later(messages(:first))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
require "test_helper"
|
||||
|
||||
class User::RoleTest < ActiveSupport::TestCase
|
||||
test "creating subsequent users makes them members" do
|
||||
assert User.create!(name: "User", email_address: "user@example.com", password: "secret123456").member?
|
||||
end
|
||||
|
||||
test "can_administer?" do
|
||||
assert User.new(role: :administrator).can_administer?
|
||||
|
||||
assert_not User.new(role: :member).can_administer?
|
||||
assert_not User.new.can_administer?
|
||||
end
|
||||
|
||||
test "can administer a record" do
|
||||
member = User.new(role: :member)
|
||||
assert member.can_administer?(Room.new(creator: member))
|
||||
|
||||
another_member = User.new(role: :member)
|
||||
assert another_member.can_administer?(Room.new(creator: member))
|
||||
assert_not another_member.can_administer?(rooms(:designers))
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
require "test_helper"
|
||||
|
||||
class UserTest < ActiveSupport::TestCase
|
||||
test "user does not prevent very long passwords" do
|
||||
users(:david).update(password: "secret" * 50)
|
||||
assert users(:david).valid?
|
||||
end
|
||||
|
||||
test "creating users grants membership to the open rooms" do
|
||||
assert_difference -> { Membership.count }, +Rooms::Open.count do
|
||||
create_new_user
|
||||
end
|
||||
end
|
||||
|
||||
test "deactivating a user deletes push subscriptions, searches, memberships for non-direct rooms, and changes their email address" do
|
||||
assert_difference -> { Membership.count }, -users(:david).memberships.without_direct_rooms.count do
|
||||
assert_difference -> { Push::Subscription.count }, -users(:david).push_subscriptions.count do
|
||||
assert_difference -> { Search.count }, -users(:david).searches.count do
|
||||
SecureRandom.stubs(:uuid).returns("2e7de450-cf04-4fa8-9b02-ff5ab2d733e7")
|
||||
users(:david).deactivate
|
||||
assert_equal "david-deactivated-2e7de450-cf04-4fa8-9b02-ff5ab2d733e7@37signals.com", users(:david).reload.email_address
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "deactivating a user deletes their sessions" do
|
||||
assert_changes -> { users(:david).sessions.count }, from: 1, to: 0 do
|
||||
users(:david).deactivate
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def create_new_user
|
||||
User.create!(name: "User", email_address: "user@example.com", password: "secret123456")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
require "test_helper"
|
||||
|
||||
class WebhookTest < ActiveSupport::TestCase
|
||||
test "payload" do
|
||||
message = messages(:first)
|
||||
message_path = Rails.application.routes.url_helpers.room_at_message_path(message.room, message)
|
||||
bot_messages_path = Rails.application.routes.url_helpers.room_bot_messages_path(message.room, users(:bender).bot_key)
|
||||
|
||||
WebMock.stub_request(:post, webhooks(:bender).url).
|
||||
with(body: hash_including(
|
||||
user: { id: message.creator.id, name: message.creator.name },
|
||||
room: { id: message.room.id, name: message.room.name, path: bot_messages_path },
|
||||
message: { id: message.id, body: { html: "First post!", plain: "First post!" }, path: message_path },
|
||||
))
|
||||
|
||||
response = webhooks(:bender).deliver(messages(:first))
|
||||
assert_equal 200, response.code.to_i
|
||||
end
|
||||
|
||||
test "delivery" do
|
||||
WebMock.stub_request(:post, webhooks(:bender).url).to_return(status: 200, body: "", headers: {})
|
||||
response = webhooks(:bender).deliver(messages(:first))
|
||||
assert_equal 200, response.code.to_i
|
||||
end
|
||||
|
||||
test "delivery with OK text reply" do
|
||||
WebMock.stub_request(:post, webhooks(:bender).url).to_return(status: 200, body: "Hello back!", headers: { "Content-Type" => "text/plain" })
|
||||
response = webhooks(:bender).deliver(messages(:first))
|
||||
|
||||
reply_message = Message.last
|
||||
assert_equal "Hello back!", reply_message.body.to_plain_text
|
||||
end
|
||||
|
||||
test "delivery with OK attachment reply" do
|
||||
WebMock.stub_request(:post, webhooks(:bender).url).to_return(status: 200, body: file_fixture("moon.jpg"), headers: { "Content-Type" => "image/jpeg" })
|
||||
response = webhooks(:bender).deliver(messages(:first))
|
||||
|
||||
reply_message = Message.last
|
||||
assert reply_message.attachment.present?
|
||||
end
|
||||
|
||||
test "delivery with error reply" do
|
||||
assert_no_difference -> { Message.count } do
|
||||
WebMock.stub_request(:post, webhooks(:bender).url).to_return(status: 500, body: "Internal Error!", headers: {})
|
||||
response = webhooks(:bender).deliver(messages(:first))
|
||||
end
|
||||
end
|
||||
|
||||
test "delivery that times out" do
|
||||
Webhook.any_instance.stubs(:post).raises(Net::OpenTimeout)
|
||||
response = webhooks(:bender).deliver(messages(:first))
|
||||
|
||||
reply_message = Message.last
|
||||
assert_equal "Failed to respond within 7 seconds", reply_message.body.to_plain_text
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
import ws from 'k6/ws';
|
||||
import http from 'k6/http';
|
||||
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
|
||||
import { SharedArray } from 'k6/data';
|
||||
|
||||
const turboSignedStreamNames = [
|
||||
"InJvb21zIg==--54acd827f0a7db144c75316a9fc488c0a949f9635b1e47956ce1bd9d1cf2c41d",
|
||||
"IloybGtPaTh2WTJGdGNHWnBjbVV2VW05dmJYTTZPa05zYjNObFpDOHg6bWVzc2FnZXMi--84f0f3dde5d23eb0fdb410746c2fb76813a4ddff1e2798aac4be0c3d969702ba",
|
||||
"IloybGtPaTh2WTJGdGNHWnBjbVV2VlhObGNpOHg6cm9vbXMi--df547a679cd41f7531b53d9e48f9883c02481a4da0d862453106441d8546d084"
|
||||
];
|
||||
|
||||
const dummyCookies = new SharedArray('another data name', function () {
|
||||
// Load CSV file and parse it using Papa Parse
|
||||
return papaparse.parse(open('cookies.txt'), { header: false }).data;
|
||||
});
|
||||
|
||||
const host = __ENV.HOST == "localhost" ? "host.docker.internal" : __ENV.HOST;
|
||||
const port = __ENV.PORT ? `:${__ENV.PORT}` : "";
|
||||
const users = parseInt(__ENV.USERS)
|
||||
|
||||
export const options = {
|
||||
discardResponseBodies: true,
|
||||
scenarios: {
|
||||
sockets: {
|
||||
executor: 'constant-arrival-rate',
|
||||
duration: '60s',
|
||||
rate: Math.ceil(users / 3.0),
|
||||
timeUnit: '1s',
|
||||
preAllocatedVUs: users,
|
||||
env: { SCENARIO: 'sockets' },
|
||||
gracefulStop: "0s"
|
||||
},
|
||||
messages: {
|
||||
executor: 'shared-iterations',
|
||||
iterations: 1,
|
||||
vus: 1,
|
||||
startTime: '30s',
|
||||
env: { SCENARIO: 'messages' },
|
||||
gracefulStop: "0s"
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function() {
|
||||
if (__ENV.SCENARIO == 'sockets') {
|
||||
sockets();
|
||||
} else if (__ENV.SCENARIO == 'messages') {
|
||||
messages();
|
||||
}
|
||||
}
|
||||
|
||||
export function sockets() {
|
||||
const cookie = dummyCookies[Math.floor(Math.random() * parseInt(__ENV.USERS))][0];
|
||||
const url = `ws://${host}${port}/cable`;
|
||||
const params = {
|
||||
headers: { 'Origin': `http://localhost`, 'Cookie': `session_token=${cookie}` }
|
||||
};
|
||||
|
||||
ws.connect(url, params, function(socket) {
|
||||
socket.on('open', function open() {
|
||||
// Subscribe to an ActionCable channel
|
||||
socket.send(JSON.stringify({ command: 'subscribe', identifier: '{"channel":"PresenceChannel", "room_id":1}' }));
|
||||
socket.send(JSON.stringify({ command: 'subscribe', identifier: '{"channel":"UnreadRoomsChannel"}' }));
|
||||
socket.send(JSON.stringify({ command: 'subscribe', identifier: '{"channel":"HeartbeatChannel"}' }));
|
||||
turboSignedStreamNames.forEach((signedStreamName) => {
|
||||
socket.send(JSON.stringify({ command: 'subscribe', identifier: `{"channel":"Turbo::StreamsChannel", "signed_stream_name":"${signedStreamName}"}` }));
|
||||
});
|
||||
|
||||
// Handle incoming messages
|
||||
socket.on('message', function(message) {
|
||||
if (message.includes("confirm_subscription")) {
|
||||
console.log("Subscription confirmed");
|
||||
} else if (message.includes("append")) {
|
||||
console.log("Message received");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('error', function(e) {
|
||||
if (e.error() != 'websocket: close sent') {
|
||||
console.log('An unexpected error occurred: ', e.error());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function messages() {
|
||||
const cookie = `session_token=${dummyCookies[0][0]}`;
|
||||
|
||||
const response = http.get(`http://${host}${port}/rooms/1`, { headers: { "Cookie": cookie }, responseType: "text" });
|
||||
const csrfToken = response.body.match(/<meta name="csrf-token" content="([^"]*)"/i)[1];
|
||||
|
||||
const postHeaders = {
|
||||
"Cookie": cookie,
|
||||
"Accept": "text/vnd.turbo-stream.html, text/html, application/xhtml+xml"
|
||||
}
|
||||
|
||||
const payload = {
|
||||
"message[body]": "Hello from k6",
|
||||
"message[client_message_id]": Math.random().toString(36),
|
||||
"authenticity_token": csrfToken
|
||||
};
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
http.post(`http://${host}${port}/rooms/1/messages`, payload, { headers: postHeaders, responseType: "text" });
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,8 @@
|
||||
key_generator = ActiveSupport::KeyGenerator.new("dummy", iterations: 1000)
|
||||
signed_cookie_secret = key_generator.generate_key("signed cookie")
|
||||
signed_cookie_verifier = ActiveSupport::MessageVerifier.new(signed_cookie_secret, digest: "SHA1", serializer: ActiveSupport::MessageEncryptor::NullSerializer)
|
||||
|
||||
(1..10000).each do |id|
|
||||
token = "a" * 19 + id.to_s.rjust(5, "0")
|
||||
puts signed_cookie_verifier.generate("\"#{token}\"", expires_in: 20.years, purpose: "cookie.session_token")
|
||||
end
|
||||
Whitespace-only changes.
@@ -0,0 +1,99 @@
|
||||
require "application_system_test_case"
|
||||
|
||||
class BoostingMessagesTest < ApplicationSystemTestCase
|
||||
setup do
|
||||
sign_in "kevin@37signals.com"
|
||||
join_room rooms(:designers)
|
||||
end
|
||||
|
||||
test "boosting a message" do
|
||||
within_message messages(:third) do
|
||||
reveal_message_actions
|
||||
fill_in_boost_input "Good morning"
|
||||
click_on "Submit"
|
||||
assert_boost_text "Good morning"
|
||||
end
|
||||
end
|
||||
|
||||
test "deleting a boost" do
|
||||
using_session("David") do
|
||||
sign_in "david@37signals.com"
|
||||
join_room rooms(:designers)
|
||||
|
||||
within "#" + dom_id(boosts(:first)) do
|
||||
find("span", text: "Hello").click
|
||||
assert_selector "button", text: "Delete this boost", wait: 5
|
||||
click_on "Delete this boost"
|
||||
end
|
||||
|
||||
assert_no_text "Hello"
|
||||
end
|
||||
end
|
||||
|
||||
test "message update preserves the input state" do
|
||||
within_message messages(:third) do
|
||||
assert_message_text "Third time's a charm."
|
||||
reveal_message_actions
|
||||
fill_in_boost_input "Hey!"
|
||||
end
|
||||
|
||||
using_session("JZ") do
|
||||
sign_in "jz@37signals.com"
|
||||
join_room rooms(:designers)
|
||||
|
||||
within_message messages(:third) do
|
||||
reveal_message_actions
|
||||
find(".message__edit-btn").click
|
||||
|
||||
fill_in_rich_text_area "message_body", with: "Redacted!"
|
||||
click_on "Save changes"
|
||||
end
|
||||
end
|
||||
|
||||
within_message messages(:third) do
|
||||
assert_message_text "Redacted!"
|
||||
assert_boost_input_value "Hey!"
|
||||
end
|
||||
end
|
||||
|
||||
test "boost by another user preserves the input state" do
|
||||
within_message messages(:third) do
|
||||
assert_message_text "Third time's a charm."
|
||||
reveal_message_actions
|
||||
fill_in_boost_input "Hey!"
|
||||
end
|
||||
|
||||
using_session("David") do
|
||||
sign_in "david@37signals.com"
|
||||
join_room rooms(:designers)
|
||||
|
||||
within_message messages(:third) do
|
||||
reveal_message_actions
|
||||
fill_in_boost_input "Morning"
|
||||
click_on "Submit"
|
||||
assert_boost_text "Morning"
|
||||
end
|
||||
end
|
||||
|
||||
perform_enqueued_jobs
|
||||
|
||||
within_message messages(:third) do
|
||||
assert_boost_text "Morning"
|
||||
assert_boost_input_value "Hey!"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def fill_in_boost_input(text)
|
||||
click_on "New boost"
|
||||
fill_in "boost[content]", with: text
|
||||
end
|
||||
|
||||
def assert_boost_input_value(text)
|
||||
assert page.has_field? "boost[content]", with: text
|
||||
end
|
||||
|
||||
def assert_boost_text(text, **options)
|
||||
assert_selector ".boost", text: text, **options
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
require "application_system_test_case"
|
||||
|
||||
class SendingMessagesTest < ApplicationSystemTestCase
|
||||
setup do
|
||||
sign_in "jz@37signals.com"
|
||||
join_room rooms(:designers)
|
||||
end
|
||||
|
||||
test "sending messages between two users" do
|
||||
using_session("Kevin") do
|
||||
sign_in "kevin@37signals.com"
|
||||
join_room rooms(:designers)
|
||||
end
|
||||
|
||||
join_room rooms(:designers)
|
||||
send_message "Is this thing on?"
|
||||
|
||||
using_session("Kevin") do
|
||||
join_room rooms(:designers)
|
||||
assert_message_text "Is this thing on?"
|
||||
|
||||
send_message "👍👍"
|
||||
end
|
||||
|
||||
join_room rooms(:designers)
|
||||
assert_message_text "👍👍"
|
||||
end
|
||||
|
||||
test "editing messages" do
|
||||
using_session("Kevin") do
|
||||
sign_in "kevin@37signals.com"
|
||||
join_room rooms(:designers)
|
||||
end
|
||||
|
||||
within_message messages(:third) do
|
||||
reveal_message_actions
|
||||
find(".message__edit-btn").click
|
||||
fill_in_rich_text_area "message_body", with: "Redacted!"
|
||||
click_on "Save changes"
|
||||
end
|
||||
|
||||
using_session("Kevin") do
|
||||
join_room rooms(:designers)
|
||||
|
||||
assert_message_text "Redacted!"
|
||||
end
|
||||
end
|
||||
|
||||
test "deleting messages" do
|
||||
using_session("Kevin") do
|
||||
sign_in "kevin@37signals.com"
|
||||
join_room rooms(:designers)
|
||||
|
||||
assert_message_text "Third time's a charm."
|
||||
end
|
||||
|
||||
within_message messages(:third) do
|
||||
reveal_message_actions
|
||||
find(".message__edit-btn").click
|
||||
|
||||
accept_confirm do
|
||||
click_on "Delete message"
|
||||
end
|
||||
end
|
||||
|
||||
using_session("Kevin") do
|
||||
assert_message_text "Third time's a charm.", count: 0
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
require "application_system_test_case"
|
||||
|
||||
class UnreadRoomsTest < ApplicationSystemTestCase
|
||||
setup do
|
||||
sign_in "jz@37signals.com"
|
||||
end
|
||||
|
||||
test "sending messages between two users" do
|
||||
designers_room = rooms(:designers)
|
||||
hq_room = rooms(:hq)
|
||||
|
||||
join_room hq_room
|
||||
assert_room_read hq_room
|
||||
|
||||
using_session("Kevin") do
|
||||
sign_in "kevin@37signals.com"
|
||||
join_room designers_room
|
||||
send_message("Hello!!")
|
||||
send_message("Talking to myself?")
|
||||
end
|
||||
|
||||
assert_room_unread designers_room
|
||||
|
||||
join_room designers_room
|
||||
assert_room_read designers_room
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
ENV["RAILS_ENV"] ||= "test"
|
||||
require_relative "../config/environment"
|
||||
|
||||
require "rails/test_help"
|
||||
require "minitest/unit"
|
||||
require "mocha/minitest"
|
||||
require "webmock/minitest"
|
||||
|
||||
WebMock.enable!
|
||||
|
||||
class ActiveSupport::TestCase
|
||||
# FIXME: Why isn't this included in ActiveSupport::TestCase by default?
|
||||
include ActiveJob::TestHelper, Turbo::Broadcastable::TestHelper
|
||||
|
||||
# FIXME: sqlite3 isn't correctly creating the additional databases per core
|
||||
# parallelize(workers: :number_of_processors)
|
||||
|
||||
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
|
||||
fixtures :all
|
||||
|
||||
include SessionTestHelper, MentionTestHelper, TurboTestHelper
|
||||
|
||||
setup do
|
||||
ActionCable.server.pubsub.clear
|
||||
|
||||
Rails.configuration.tap do |config|
|
||||
config.x.web_push_pool.shutdown
|
||||
config.x.web_push_pool = WebPush::Pool.new \
|
||||
invalid_subscription_handler: config.x.web_push_pool.invalid_subscription_handler
|
||||
end
|
||||
|
||||
WebMock.disable_net_connect!
|
||||
end
|
||||
|
||||
teardown do
|
||||
WebMock.reset!
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
module MentionTestHelper
|
||||
def mention_attachment_for(name)
|
||||
user = users(name)
|
||||
attachment_body = ApplicationController.render partial: "users/mention", locals: { user: user }
|
||||
"<action-text-attachment sgid=\"#{user.attachable_sgid}\" content-type=\"application/vnd.campfire.mention\" content=\"#{attachment_body.gsub('"', '"')}\"></action-text-attachment>"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
module SessionTestHelper
|
||||
def parsed_cookies
|
||||
ActionDispatch::Cookies::CookieJar.build(request, cookies.to_hash)
|
||||
end
|
||||
|
||||
def sign_in(user)
|
||||
user = users(user) unless user.is_a? User
|
||||
post session_url, params: { email_address: user.email_address, password: "secret123456" }
|
||||
assert cookies[:session_token].present?
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
module SystemTestHelper
|
||||
def sign_in(email_address, password = "secret123456")
|
||||
visit root_url
|
||||
|
||||
fill_in "email_address", with: email_address
|
||||
fill_in "password", with: password
|
||||
|
||||
click_on "log_in"
|
||||
assert_selector "a.btn", text: "Designers"
|
||||
end
|
||||
|
||||
def wait_for_cable_connection
|
||||
assert_selector "turbo-cable-stream-source[connected]", count: 3, visible: false
|
||||
end
|
||||
|
||||
def join_room(room)
|
||||
visit room_url(room)
|
||||
wait_for_cable_connection
|
||||
dismiss_pwa_install_prompt
|
||||
end
|
||||
|
||||
def send_message(message)
|
||||
fill_in_rich_text_area "message_body", with: message
|
||||
click_on "send"
|
||||
end
|
||||
|
||||
def within_message(message, &block)
|
||||
within "#" + dom_id(message), &block
|
||||
end
|
||||
|
||||
def assert_message_text(text, **options)
|
||||
assert_selector ".message__body", text: text, **options
|
||||
end
|
||||
|
||||
def assert_room_read(room)
|
||||
assert_selector ".rooms a", class: "!unread", text: "#{room.name}", wait: 5
|
||||
end
|
||||
|
||||
def assert_room_unread(room)
|
||||
assert_selector ".rooms a", class: "unread", text: "#{room.name}", wait: 5
|
||||
end
|
||||
|
||||
def reveal_message_actions
|
||||
find(".message__options-btn").click
|
||||
rescue Capybara::ElementNotFound
|
||||
find(".message__options-btn", visible: false).hover.click
|
||||
ensure
|
||||
assert_selector ".message__boost-btn", visible: true
|
||||
end
|
||||
|
||||
def dismiss_pwa_install_prompt
|
||||
if page.has_css?("[data-pwa-install-target~='dialog']", visible: :visible, wait: 5)
|
||||
click_on("Close")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
module TurboTestHelper
|
||||
def assert_rendered_turbo_stream_broadcast(*streambles, action:, target:, &block)
|
||||
streams = find_broadcasts_for(*streambles)
|
||||
target = ActionView::RecordIdentifier.dom_id(*target)
|
||||
assert_select Nokogiri::HTML.fragment(streams), %(turbo-stream[action="#{action}"][target="#{target}"]), &block
|
||||
end
|
||||
|
||||
private
|
||||
def find_broadcasts_for(*streambles)
|
||||
broadcasting = streambles.collect do |streamble|
|
||||
streamble.try(:to_gid_param) || streamble
|
||||
end.join(":")
|
||||
|
||||
broadcasts = ActionCable.server.pubsub.broadcasts(broadcasting)
|
||||
broadcasts.collect { |b| JSON.parse(b) }.join("\n\n")
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user