DEV: Refactor some services from chat

Extracted from https://github.com/discourse/discourse/pull/29129.

This patch makes the code more compliant with the upcoming service docs best practices.
This commit is contained in:
Loïc Guitaut 2024-10-18 17:09:23 +02:00 committed by Loïc Guitaut
parent 43a0ea876a
commit 08e9364573
24 changed files with 223 additions and 224 deletions

View File

@ -47,6 +47,10 @@ module Chat
private private
def fetch_channel(contract:)
::Chat::Channel.includes(:chatable).find_by(id: contract.channel_id)
end
def can_add_users_to_channel(guardian:, channel:) def can_add_users_to_channel(guardian:, channel:)
(guardian.user.admin? || channel.joined_by?(guardian.user)) && (guardian.user.admin? || channel.joined_by?(guardian.user)) &&
channel.direct_message_channel? && channel.chatable.group channel.direct_message_channel? && channel.chatable.group
@ -60,10 +64,6 @@ module Chat
) )
end end
def fetch_channel(contract:)
::Chat::Channel.includes(:chatable).find_by(id: contract.channel_id)
end
def upsert_memberships(channel:, target_users:) def upsert_memberships(channel:, target_users:)
always_level = ::Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always] always_level = ::Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always]

View File

@ -14,13 +14,13 @@ module Chat
class HandleCategoryUpdated class HandleCategoryUpdated
include Service::Base include Service::Base
policy :chat_enabled
contract do contract do
attribute :category_id, :integer attribute :category_id, :integer
validates :category_id, presence: true validates :category_id, presence: true
end end
step :assign_defaults step :assign_defaults
policy :chat_enabled
model :category model :category
model :category_channel_ids model :category_channel_ids
model :users model :users

View File

@ -21,13 +21,13 @@ module Chat
class HandleDestroyedGroup class HandleDestroyedGroup
include Service::Base include Service::Base
policy :chat_enabled
contract do contract do
attribute :destroyed_group_user_ids attribute :destroyed_group_user_ids, :array
validates :destroyed_group_user_ids, presence: true validates :destroyed_group_user_ids, presence: true
end end
step :assign_defaults step :assign_defaults
policy :chat_enabled
policy :not_everyone_allowed policy :not_everyone_allowed
model :scoped_users model :scoped_users
step :remove_users_outside_allowed_groups step :remove_users_outside_allowed_groups
@ -48,7 +48,7 @@ module Chat
!SiteSetting.chat_allowed_groups_map.include?(Group::AUTO_GROUPS[:everyone]) !SiteSetting.chat_allowed_groups_map.include?(Group::AUTO_GROUPS[:everyone])
end end
def fetch_scoped_users(destroyed_group_user_ids:) def fetch_scoped_users(contract:)
User User
.real .real
.activated .activated
@ -56,7 +56,7 @@ module Chat
.not_staged .not_staged
.includes(:group_users) .includes(:group_users)
.where("NOT admin AND NOT moderator") .where("NOT admin AND NOT moderator")
.where(id: destroyed_group_user_ids) .where(id: contract.destroyed_group_user_ids)
.joins(:user_chat_channel_memberships) .joins(:user_chat_channel_memberships)
.distinct .distinct
end end

View File

@ -20,13 +20,13 @@ module Chat
class HandleUserRemovedFromGroup class HandleUserRemovedFromGroup
include Service::Base include Service::Base
policy :chat_enabled
contract do contract do
attribute :user_id, :integer attribute :user_id, :integer
validates :user_id, presence: true validates :user_id, presence: true
end end
step :assign_defaults step :assign_defaults
policy :chat_enabled
policy :not_everyone_allowed policy :not_everyone_allowed
model :user model :user
policy :user_not_staff policy :user_not_staff

View File

@ -37,7 +37,7 @@ module Chat
validates :message_id, presence: true validates :message_id, presence: true
validates :channel_id, presence: true validates :channel_id, presence: true
validates :flag_type_id, inclusion: { in: ->(_flag_type) { ::ReviewableScore.types.values } } validates :flag_type_id, inclusion: { in: -> { ::ReviewableScore.types.values } }
end end
model :message model :message
policy :can_flag_message_in_channel policy :can_flag_message_in_channel

View File

@ -39,7 +39,6 @@ module Chat
}, },
allow_nil: true allow_nil: true
end end
model :channel model :channel
policy :can_view_channel policy :can_view_channel
model :membership, optional: true model :membership, optional: true

View File

@ -34,7 +34,7 @@ module Chat
private private
def clean_term(contract:) def clean_term(contract:)
context[:term] = contract.term&.downcase&.strip&.gsub(/^[@#]+/, "") contract.term = contract.term&.downcase&.strip&.gsub(/^[@#]+/, "")
end end
def fetch_memberships(guardian:) def fetch_memberships(guardian:)
@ -44,31 +44,31 @@ module Chat
def fetch_users(guardian:, contract:) def fetch_users(guardian:, contract:)
return unless contract.include_users return unless contract.include_users
return unless guardian.can_create_direct_message? return unless guardian.can_create_direct_message?
search_users(context, guardian) search_users(contract, guardian)
end end
def fetch_groups(guardian:, contract:) def fetch_groups(guardian:, contract:)
return unless contract.include_groups return unless contract.include_groups
return unless guardian.can_create_direct_message? return unless guardian.can_create_direct_message?
search_groups(context, guardian) search_groups(contract, guardian)
end end
def fetch_category_channels(guardian:, contract:) def fetch_category_channels(guardian:, contract:)
return unless contract.include_category_channels return unless contract.include_category_channels
return unless SiteSetting.enable_public_channels return unless SiteSetting.enable_public_channels
search_category_channels(context, guardian) search_category_channels(contract, guardian)
end end
def fetch_direct_message_channels(guardian:, contract:, users:) def fetch_direct_message_channels(guardian:, contract:, users:)
return unless contract.include_direct_message_channels return unless contract.include_direct_message_channels
return unless guardian.can_create_direct_message? return unless guardian.can_create_direct_message?
search_direct_message_channels(context, guardian, contract, users) search_direct_message_channels(guardian, contract, users)
end end
def search_users(context, guardian) def search_users(contract, guardian)
user_search = ::UserSearch.new(context.term, limit: SEARCH_RESULT_LIMIT) user_search = ::UserSearch.new(contract.term, limit: SEARCH_RESULT_LIMIT)
if context.term.blank? if contract.term.blank?
user_search = user_search.scoped_users user_search = user_search.scoped_users
else else
user_search = user_search.search user_search = user_search.search
@ -80,45 +80,45 @@ module Chat
user_search = user_search.real(allowed_bot_user_ids: allowed_bot_user_ids) user_search = user_search.real(allowed_bot_user_ids: allowed_bot_user_ids)
user_search = user_search.includes(:user_option) user_search = user_search.includes(:user_option)
if context.excluded_memberships_channel_id if contract.excluded_memberships_channel_id
user_search = user_search =
user_search.where( user_search.where(
"NOT EXISTS (SELECT 1 FROM user_chat_channel_memberships WHERE user_id = users.id AND chat_channel_id = ?)", "NOT EXISTS (SELECT 1 FROM user_chat_channel_memberships WHERE user_id = users.id AND chat_channel_id = ?)",
context.excluded_memberships_channel_id, contract.excluded_memberships_channel_id,
) )
end end
user_search user_search
end end
def search_groups(context, guardian) def search_groups(contract, guardian)
Group Group
.visible_groups(guardian.user) .visible_groups(guardian.user)
.includes(users: :user_option) .includes(users: :user_option)
.where( .where(
"groups.name ILIKE :term_like OR groups.full_name ILIKE :term_like", "groups.name ILIKE :term_like OR groups.full_name ILIKE :term_like",
term_like: "%#{context.term}%", term_like: "%#{contract.term}%",
) )
.limit(SEARCH_RESULT_LIMIT) .limit(SEARCH_RESULT_LIMIT)
end end
def search_category_channels(context, guardian) def search_category_channels(contract, guardian)
::Chat::ChannelFetcher.secured_public_channel_search( ::Chat::ChannelFetcher.secured_public_channel_search(
guardian, guardian,
status: :open, status: :open,
filter: context.term, filter: contract.term,
filter_on_category_name: false, filter_on_category_name: false,
match_filter_on_starts_with: false, match_filter_on_starts_with: false,
limit: SEARCH_RESULT_LIMIT, limit: SEARCH_RESULT_LIMIT,
) )
end end
def search_direct_message_channels(context, guardian, contract, users) def search_direct_message_channels(guardian, contract, users)
channels = channels =
::Chat::ChannelFetcher.secured_direct_message_channels_search( ::Chat::ChannelFetcher.secured_direct_message_channels_search(
guardian.user.id, guardian.user.id,
guardian, guardian,
filter: context.term, filter: contract.term,
match_filter_on_starts_with: false, match_filter_on_starts_with: false,
limit: SEARCH_RESULT_LIMIT, limit: SEARCH_RESULT_LIMIT,
) || [] ) || []

View File

@ -65,13 +65,11 @@ module Chat
end end
def update_channel(channel:, contract:) def update_channel(channel:, contract:)
channel.assign_attributes(contract.attributes) channel.update!(contract.attributes)
context[:threading_enabled_changed] = channel.threading_enabled_changed?
channel.save!
end end
def mark_all_threads_as_read_if_needed(channel:) def mark_all_threads_as_read_if_needed(channel:)
return if !(context.threading_enabled_changed && channel.threading_enabled) return unless channel.threading_enabled_previously_changed?(to: true)
Jobs.enqueue(Jobs::Chat::MarkAllChannelThreadsRead, channel_id: channel.id) Jobs.enqueue(Jobs::Chat::MarkAllChannelThreadsRead, channel_id: channel.id)
end end

View File

@ -17,7 +17,7 @@ module Chat
model :channel, :fetch_channel model :channel, :fetch_channel
contract do contract do
attribute :status attribute :status, :string
validates :status, inclusion: { in: Chat::Channel.editable_statuses.keys } validates :status, inclusion: { in: Chat::Channel.editable_statuses.keys }
end end
@ -30,13 +30,13 @@ module Chat
Chat::Channel.find_by(id: channel_id) Chat::Channel.find_by(id: channel_id)
end end
def check_channel_permission(guardian:, channel:, status:) def check_channel_permission(guardian:, channel:, contract:)
guardian.can_preview_chat_channel?(channel) && guardian.can_preview_chat_channel?(channel) &&
guardian.can_change_channel_status?(channel, status.to_sym) guardian.can_change_channel_status?(channel, contract.status.to_sym)
end end
def change_status(channel:, status:, guardian:) def change_status(channel:, contract:, guardian:)
channel.public_send("#{status}!", guardian.user) channel.public_send("#{contract.status}!", guardian.user)
end end
end end
end end

View File

@ -76,19 +76,19 @@ RSpec.describe Chat::Api::ChannelMessagesController do
describe "#create" do describe "#create" do
context "when force_thread param is given" do context "when force_thread param is given" do
it "removes it from params" do let!(:message) { Fabricate(:chat_message, chat_channel: channel) }
sign_in(current_user)
message_1 = Fabricate(:chat_message, chat_channel: channel) before { sign_in(current_user) }
it "ignores it" do
expect { expect {
post "/chat/#{channel.id}.json", post "/chat/#{channel.id}.json",
params: { params: {
in_reply_to_id: message_1.id, in_reply_to_id: message.id,
message: "test", message: "test",
force_thread: true, force_thread: true,
} }
}.to change { Chat::Thread.where(force: false).count }.by(1) }.not_to change { Chat::Thread.where(force: true).count }
end end
end end
end end

View File

@ -51,9 +51,7 @@ RSpec.describe Chat::AutoRemove::HandleCategoryUpdated do
updated_category.category_groups.delete_all updated_category.category_groups.delete_all
end end
it "sets the service result as successful" do it { is_expected.to run_successfully }
expect(result).to be_a_success
end
it "does not kick any users since the default permission is Everyone (full)" do it "does not kick any users since the default permission is Everyone (full)" do
expect { result }.not_to change { expect { result }.not_to change {
@ -90,9 +88,7 @@ RSpec.describe Chat::AutoRemove::HandleCategoryUpdated do
group_2.add(user_1) group_2.add(user_1)
end end
it "sets the service result as successful" do it { is_expected.to run_successfully }
expect(result).to be_a_success
end
it "kicks all regular users who are not in any groups with reply + see permissions" do it "kicks all regular users who are not in any groups with reply + see permissions" do
expect { result }.to change { expect { result }.to change {

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::AutoRemove::HandleDestroyedGroup do RSpec.describe Chat::AutoRemove::HandleDestroyedGroup do
describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of(:destroyed_group_user_ids) }
end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params) }
@ -45,9 +49,7 @@ RSpec.describe Chat::AutoRemove::HandleDestroyedGroup do
channel_1.add(admin_2) channel_1.add(admin_2)
end end
it "sets the service result as successful" do it { is_expected.to run_successfully }
expect(result).to be_a_success
end
it "removes the destroyed_group_user_ids from all public channels" do it "removes the destroyed_group_user_ids from all public channels" do
expect { result }.to change { expect { result }.to change {
@ -132,9 +134,7 @@ RSpec.describe Chat::AutoRemove::HandleDestroyedGroup do
channel_1.add(admin_2) channel_1.add(admin_2)
end end
it "sets the service result as successful" do it { is_expected.to run_successfully }
expect(result).to be_a_success
end
it "does not remove any memberships" do it "does not remove any memberships" do
expect { result }.not_to change { Chat::UserChatChannelMembership.count } expect { result }.not_to change { Chat::UserChatChannelMembership.count }
@ -196,9 +196,7 @@ RSpec.describe Chat::AutoRemove::HandleDestroyedGroup do
) )
end end
it "sets the service result as successful" do it { is_expected.to run_successfully }
expect(result).to be_a_success
end
it "removes the destroyed_group_user_ids from the channel" do it "removes the destroyed_group_user_ids from the channel" do
expect { result }.to change { expect { result }.to change {
@ -237,9 +235,7 @@ RSpec.describe Chat::AutoRemove::HandleDestroyedGroup do
context "when one of the users is not in any of the groups" do context "when one of the users is not in any of the groups" do
before { user_2.change_trust_level!(TrustLevel[3]) } before { user_2.change_trust_level!(TrustLevel[3]) }
it "sets the service result as successful" do it { is_expected.to run_successfully }
expect(result).to be_a_success
end
it "removes the destroyed_group_user_ids from the channel" do it "removes the destroyed_group_user_ids from the channel" do
expect { result }.to change { expect { result }.to change {

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::CreateDirectMessageChannel do RSpec.describe Chat::CreateDirectMessageChannel do
describe Chat::CreateDirectMessageChannel::Contract, type: :model do describe described_class::Contract, type: :model do
subject(:contract) { described_class.new(params) } subject(:contract) { described_class.new(params) }
let(:params) { { target_usernames: %w[lechuck elaine] } } let(:params) { { target_usernames: %w[lechuck elaine] } }
@ -45,10 +45,6 @@ RSpec.describe Chat::CreateDirectMessageChannel do
context "when all steps pass" do context "when all steps pass" do
it { is_expected.to run_successfully } it { is_expected.to run_successfully }
it "sets the service result as successful" do
expect(result).to be_a_success
end
it "updates user count" do it "updates user count" do
expect(result.channel.user_count).to eq(3) # current user + user_1 + user_2 expect(result.channel.user_count).to eq(3) # current user + user_1 + user_2
end end

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::CreateThread do RSpec.describe Chat::CreateThread do
describe Chat::CreateThread::Contract, type: :model do describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of :channel_id } it { is_expected.to validate_presence_of :channel_id }
it { is_expected.to validate_presence_of :original_message_id } it { is_expected.to validate_presence_of :original_message_id }
it { is_expected.to validate_length_of(:title).is_at_most(Chat::Thread::MAX_TITLE_LENGTH) }
end end
describe ".call" do describe ".call" do
@ -68,12 +69,6 @@ RSpec.describe Chat::CreateThread do
it { is_expected.to fail_a_contract } it { is_expected.to fail_a_contract }
end end
context "when title is too long" do
let(:title) { "a" * Chat::Thread::MAX_TITLE_LENGTH + "a" }
it { is_expected.to fail_a_contract }
end
context "when original message is not found" do context "when original message is not found" do
fab!(:channel_2) { Fabricate(:chat_channel, threading_enabled: true) } fab!(:channel_2) { Fabricate(:chat_channel, threading_enabled: true) }

View File

@ -2,8 +2,6 @@
RSpec.describe Chat::FlagMessage do RSpec.describe Chat::FlagMessage do
describe described_class::Contract, type: :model do describe described_class::Contract, type: :model do
subject(:contract) { described_class.new }
it { is_expected.to validate_presence_of(:channel_id) } it { is_expected.to validate_presence_of(:channel_id) }
it { is_expected.to validate_presence_of(:message_id) } it { is_expected.to validate_presence_of(:message_id) }
@ -26,9 +24,6 @@ RSpec.describe Chat::FlagMessage do
let(:message) { nil } let(:message) { nil }
let(:is_warning) { nil } let(:is_warning) { nil }
let(:take_action) { nil } let(:take_action) { nil }
before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] }
let(:params) do let(:params) do
{ {
guardian: guardian, guardian: guardian,
@ -41,23 +36,34 @@ RSpec.describe Chat::FlagMessage do
} }
end end
before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] }
context "when all steps pass" do context "when all steps pass" do
fab!(:current_user) { Fabricate(:admin) } fab!(:current_user) { Fabricate(:admin) }
let(:reviewable) { Reviewable.last }
it { is_expected.to run_successfully } it { is_expected.to run_successfully }
it "flags the message" do it "flags the message" do
expect { result }.to change { Reviewable.count }.by(1) expect { result }.to change { Reviewable.count }.by(1)
expect(reviewable).to have_attributes(
reviewable = Reviewable.last target: message_1,
expect(reviewable.target_type).to eq("ChatMessage") created_by: current_user,
expect(reviewable.created_by_id).to eq(current_user.id) target_created_by: message_1.user,
expect(reviewable.target_created_by_id).to eq(message_1.user.id) payload: {
expect(reviewable.target_id).to eq(message_1.id) "message_cooked" => message_1.cooked,
expect(reviewable.payload).to eq("message_cooked" => message_1.cooked) },
)
end end
end end
context "when contract is invalid" do
let(:channel_id) { nil }
it { is_expected.to fail_a_contract }
end
context "when channel is not found" do context "when channel is not found" do
before { params[:channel_id] = -999 } before { params[:channel_id] = -999 }

View File

@ -1,88 +1,93 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::InviteUsersToChannel do RSpec.describe Chat::InviteUsersToChannel do
subject(:result) { described_class.call(params) }
describe described_class::Contract, type: :model do describe described_class::Contract, type: :model do
subject(:contract) { described_class.new }
it { is_expected.to validate_presence_of :channel_id } it { is_expected.to validate_presence_of :channel_id }
it { is_expected.to validate_presence_of :user_ids } it { is_expected.to validate_presence_of :user_ids }
end end
fab!(:current_user) { Fabricate(:admin) } describe ".call" do
fab!(:user_1) { Fabricate(:user) } subject(:result) { described_class.call(**params, **dependencies) }
fab!(:user_2) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:group_1) { Fabricate(:group) }
let(:guardian) { current_user.guardian } fab!(:current_user) { Fabricate(:admin) }
let(:user_ids) { [user_1.id, user_2.id] } fab!(:user_1) { Fabricate(:user) }
let(:message_id) { nil } fab!(:user_2) { Fabricate(:user) }
let(:params) do fab!(:channel_1) { Fabricate(:chat_channel) }
{ guardian: guardian, channel_id: channel_1.id, message_id: message_id, user_ids: user_ids } fab!(:group_1) { Fabricate(:group) }
end
before do let(:guardian) { current_user.guardian }
group_1.add(user_1) let(:user_ids) { [user_1.id, user_2.id] }
SiteSetting.chat_allowed_groups = group_1.id let(:message_id) { nil }
end let(:params) { { channel_id: channel_1.id, message_id:, user_ids: } }
let(:dependencies) { { guardian: } }
context "when all steps pass" do before do
it { is_expected.to run_successfully } group_1.add(user_1)
SiteSetting.chat_allowed_groups = group_1.id
it "creates the notifications for allowed users" do
result
notification = user_1.reload.notifications.last
expect(notification.notification_type).to eq(::Notification.types[:chat_invitation])
notification = user_2.reload.notifications.last
expect(notification).to be_nil
end end
it "doesnt create notifications for suspended users" do context "when all steps pass" do
user_1.update!(suspended_till: 2.days.from_now, suspended_at: Time.now) it { is_expected.to run_successfully }
result it "creates the notifications for allowed users" do
notification = user_1.reload.notifications.last
expect(notification).to be_nil
end
it "doesnt create notifications for users with disabled chat" do
user_1.user_option.update!(chat_enabled: false)
result
notification = user_1.reload.notifications.last
expect(notification).to be_nil
end
context "when message id is provided" do
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
let(:message_id) { message_1.id }
it "sets the message id on the notification" do
result result
data = JSON.parse(user_1.reload.notifications.last.data) notification = user_1.reload.notifications.last
expect(data["chat_message_id"]).to eq(message_id) expect(notification.notification_type).to eq(::Notification.types[:chat_invitation])
notification = user_2.reload.notifications.last
expect(notification).to be_nil
end
it "doesnt create notifications for suspended users" do
user_1.update!(suspended_till: 2.days.from_now, suspended_at: Time.now)
result
notification = user_1.reload.notifications.last
expect(notification).to be_nil
end
it "doesnt create notifications for users with disabled chat" do
user_1.user_option.update!(chat_enabled: false)
result
notification = user_1.reload.notifications.last
expect(notification).to be_nil
end
context "when message id is provided" do
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
let(:message_id) { message_1.id }
it "sets the message id on the notification" do
result
data = JSON.parse(user_1.reload.notifications.last.data)
expect(data["chat_message_id"]).to eq(message_id)
end
end end
end end
end
context "when channel model is not found" do context "when contract is invalid" do
before { params[:channel_id] = -1 } let(:user_ids) { nil }
it { is_expected.to fail_to_find_a_model(:channel) } it { is_expected.to fail_a_contract }
end end
context "when current user can't view channel" do context "when channel model is not found" do
fab!(:current_user) { Fabricate(:user) } before { params[:channel_id] = -1 }
fab!(:channel_1) { Fabricate(:private_category_channel) }
it { is_expected.to fail_a_policy(:can_view_channel) } it { is_expected.to fail_to_find_a_model(:channel) }
end
context "when current user can't view channel" do
fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:private_category_channel) }
it { is_expected.to fail_a_policy(:can_view_channel) }
end
end end
end end

View File

@ -47,10 +47,10 @@ RSpec.describe Chat::SearchChatable do
it "cleans the term" do it "cleans the term" do
params[:term] = "#bob" params[:term] = "#bob"
expect(result.term).to eq("bob") expect(result.contract.term).to eq("bob")
params[:term] = "@bob" params[:term] = "@bob"
expect(result.term).to eq("bob") expect(result.contract.term).to eq("bob")
end end
it "fetches user memberships" do it "fetches user memberships" do

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::StopMessageStreaming do RSpec.describe Chat::StopMessageStreaming do
describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of(:message_id) }
end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params) }

View File

@ -1,24 +1,29 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::TrashMessage do RSpec.describe Chat::TrashMessage do
fab!(:current_user) { Fabricate(:user) } describe described_class::Contract, type: :model do
let!(:guardian) { Guardian.new(current_user) } it { is_expected.to validate_presence_of(:message_id) }
fab!(:message) { Fabricate(:chat_message, user: current_user) } it { is_expected.to validate_presence_of(:channel_id) }
end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(**params, **dependencies) }
fab!(:current_user) { Fabricate(:user) }
fab!(:message) { Fabricate(:chat_message, user: current_user) }
let(:guardian) { Guardian.new(current_user) }
let(:params) { { message_id: message.id, channel_id: } }
let(:dependencies) { { guardian: } }
let(:channel_id) { message.chat_channel_id }
context "when params are not valid" do context "when params are not valid" do
let(:params) { { guardian: guardian } } let(:params) { {} }
it { is_expected.to fail_a_contract } it { is_expected.to fail_a_contract }
end end
context "when params are valid" do context "when params are valid" do
let(:params) do
{ guardian: guardian, message_id: message.id, channel_id: message.chat_channel_id }
end
context "when the user does not have permission to delete" do context "when the user does not have permission to delete" do
before { message.update!(user: Fabricate(:admin)) } before { message.update!(user: Fabricate(:admin)) }
@ -26,9 +31,7 @@ RSpec.describe Chat::TrashMessage do
end end
context "when the channel does not match the message" do context "when the channel does not match the message" do
let(:params) do let(:channel_id) { -1 }
{ guardian: guardian, message_id: message.id, channel_id: Fabricate(:chat_channel).id }
end
it { is_expected.to fail_to_find_a_model(:message) } it { is_expected.to fail_to_find_a_model(:message) }
end end

View File

@ -1,26 +1,30 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::TrashMessages do RSpec.describe Chat::TrashMessages do
fab!(:current_user) { Fabricate(:user) } describe described_class::Contract, type: :model do
let!(:guardian) { Guardian.new(current_user) } it { is_expected.to validate_presence_of(:channel_id) }
fab!(:chat_channel) { Fabricate(:chat_channel) } it { is_expected.to allow_values([1], (1..200).to_a).for(:message_ids) }
fab!(:message1) { Fabricate(:chat_message, user: current_user, chat_channel: chat_channel) } it { is_expected.not_to allow_values([], (1..201).to_a).for(:message_ids) }
fab!(:message2) { Fabricate(:chat_message, user: current_user, chat_channel: chat_channel) } end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(**params, **dependencies) }
fab!(:current_user) { Fabricate(:user) }
fab!(:chat_channel) { Fabricate(:chat_channel) }
fab!(:message1) { Fabricate(:chat_message, user: current_user, chat_channel: chat_channel) }
fab!(:message2) { Fabricate(:chat_message, user: current_user, chat_channel: chat_channel) }
let(:guardian) { Guardian.new(current_user) }
let(:params) { { message_ids: [message1.id, message2.id], channel_id: chat_channel.id } }
let(:dependencies) { { guardian: } }
context "when params are not valid" do context "when params are not valid" do
let(:params) { { guardian: guardian } } let(:params) { {} }
it { is_expected.to fail_a_contract } it { is_expected.to fail_a_contract }
end end
context "when params are valid" do context "when params are valid" do
let(:params) do
{ guardian: guardian, message_ids: [message1.id, message2.id], channel_id: chat_channel.id }
end
context "when the user does not have permission to delete" do context "when the user does not have permission to delete" do
before { message1.update!(user: Fabricate(:admin)) } before { message1.update!(user: Fabricate(:admin)) }
@ -29,11 +33,7 @@ RSpec.describe Chat::TrashMessages do
context "when the channel does not match the message" do context "when the channel does not match the message" do
let(:params) do let(:params) do
{ { message_ids: [message1.id, message2.id], channel_id: Fabricate(:chat_channel).id }
guardian: guardian,
message_ids: [message1.id, message2.id],
channel_id: Fabricate(:chat_channel).id,
}
end end
it { is_expected.to fail_to_find_a_model(:messages) } it { is_expected.to fail_to_find_a_model(:messages) }

View File

@ -1,52 +1,57 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe(Chat::UpdateChannelStatus) do RSpec.describe(Chat::UpdateChannelStatus) do
subject(:result) do describe described_class::Contract, type: :model do
described_class.call(guardian: guardian, channel_id: channel.id, status: status) it do
is_expected.to validate_inclusion_of(:status).in_array(Chat::Channel.editable_statuses.keys)
end
end end
fab!(:channel) { Fabricate(:chat_channel) } describe ".call" do
fab!(:current_user) { Fabricate(:admin) } subject(:result) { described_class.call(**params, **dependencies) }
let(:guardian) { Guardian.new(current_user) } fab!(:channel) { Fabricate(:chat_channel) }
let(:status) { "open" } fab!(:current_user) { Fabricate(:admin) }
context "when no channel_id is given" do let(:params) { { channel_id:, status: } }
subject(:result) { described_class.call(guardian: guardian, status: status) } let(:dependencies) { { guardian: } }
let(:guardian) { Guardian.new(current_user) }
let(:status) { "open" }
let(:channel_id) { channel.id }
it { is_expected.to fail_to_find_a_model(:channel) } context "when no channel_id is given" do
end let(:channel_id) { nil }
context "when user is not allowed to change channel status" do it { is_expected.to fail_to_find_a_model(:channel) }
fab!(:current_user) { Fabricate(:user) } end
it { is_expected.to fail_a_policy(:check_channel_permission) } context "when user is not allowed to change channel status" do
end let!(:current_user) { Fabricate(:user) }
context "when status is not allowed" do it { is_expected.to fail_a_policy(:check_channel_permission) }
(Chat::Channel.statuses.keys - Chat::Channel.editable_statuses.keys).each do |na_status| end
context "when status is '#{na_status}'" do
let(:status) { na_status }
it { is_expected.to fail_a_contract } context "when contract is invalid" do
let(:status) { :invalid_status }
it { is_expected.to fail_a_contract }
end
context "when new status is the same than the existing one" do
let(:status) { channel.status }
it { is_expected.to fail_a_policy(:check_channel_permission) }
end
context "when everything's ok" do
let(:status) { "closed" }
it { is_expected.to run_successfully }
it "changes the status" do
result
expect(channel.reload).to be_closed
end end
end end
end end
context "when new status is the same than the existing one" do
let(:status) { channel.status }
it { is_expected.to fail_a_policy(:check_channel_permission) }
end
context "when status is allowed" do
let(:status) { "closed" }
it { is_expected.to run_successfully }
it "changes the status" do
result
expect(channel.reload).to be_closed
end
end
end end

View File

@ -1,10 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::UpdateThreadNotificationSettings do RSpec.describe Chat::UpdateThreadNotificationSettings do
describe Chat::UpdateThreadNotificationSettings::Contract, type: :model do describe described_class::Contract, type: :model do
let(:notification_levels) { Chat::UserChatThreadMembership.notification_levels.values }
it { is_expected.to validate_presence_of :channel_id } it { is_expected.to validate_presence_of :channel_id }
it { is_expected.to validate_presence_of :thread_id } it { is_expected.to validate_presence_of :thread_id }
it { is_expected.to validate_presence_of :notification_level } it { is_expected.to validate_presence_of :notification_level }
it { is_expected.to validate_inclusion_of(:notification_level).in_array(notification_levels) }
end end
describe ".call" do describe ".call" do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::UpdateThread do RSpec.describe Chat::UpdateThread do
describe Chat::UpdateThread::Contract, type: :model do describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of :thread_id } it { is_expected.to validate_presence_of :thread_id }
it { is_expected.to validate_length_of(:title).is_at_most(Chat::Thread::MAX_TITLE_LENGTH) }
end end
describe ".call" do describe ".call" do
@ -42,12 +43,6 @@ RSpec.describe Chat::UpdateThread do
it { is_expected.to fail_a_contract } it { is_expected.to fail_a_contract }
end end
context "when title is too long" do
let(:title) { "a" * Chat::Thread::MAX_TITLE_LENGTH + "a" }
it { is_expected.to fail_a_contract }
end
context "when thread is not found" do context "when thread is not found" do
before { thread.destroy! } before { thread.destroy! }

View File

@ -2,8 +2,6 @@
RSpec.describe Chat::UpsertDraft do RSpec.describe Chat::UpsertDraft do
describe described_class::Contract, type: :model do describe described_class::Contract, type: :model do
subject(:contract) { described_class.new(data: nil, channel_id: nil, thread_id: nil) }
it { is_expected.to validate_presence_of :channel_id } it { is_expected.to validate_presence_of :channel_id }
end end