Hello world

First open source release of Campfire 🎉
This commit is contained in:
Kevin McConnell committed 2025-08-21 09:31:59 +01:00
commit df76a227dc
664 files changed
+36235

No files matched your search

View File
Whitespace-only changes.
+15
View File
@@ -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
+4
View File
@@ -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
+29
View File
@@ -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
+90
View File
@@ -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
+30
View File
@@ -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
+29
View File
@@ -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
+34
View File
@@ -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
+33
View File
@@ -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
+118
View File
@@ -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
+50
View File
@@ -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
+170
View File
@@ -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!&#x3c;&#x2f;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3e;&#x3c;&#x69;&#x6d;&#x67;&#x20;&#x73;&#x72;&#x63;&#x3d;&#x61;&#x20;&#x6f;&#x6e;&#x65;&#x72;&#x72;&#x6f;&#x72;&#x3d;&#x70;&#x72;&#x6f;&#x6d;&#x70;&#x74;&#x28;&#x31;&#x29;&#x3e;">
<meta property="og:description" content="Hello&#x3c;&#x2f;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3e;&#x3c;&#x69;&#x6d;&#x67;&#x20;&#x73;&#x72;&#x63;&#x3d;&#x61;&#x20;&#x6f;&#x6e;&#x65;&#x72;&#x72;&#x6f;&#x72;&#x3d;&#x70;&#x72;&#x6f;&#x6d;&#x70;&#x74;&#x28;&#x32;&#x29;&#x3e;</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
+79
View File
@@ -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
+37
View File
@@ -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
+21
View File
@@ -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
+14
View File
@@ -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
+41
View File
@@ -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
+23
View File
@@ -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
+37
View File
@@ -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
+56
View File
@@ -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