mirror of
https://github.com/discourse/discourse.git
synced 2025-10-03 17:21:20 +08:00
DEV: Refactor Chat::ListChannelMessages service a bit
- improve the contract a little - use `model` where possible - extract message existence logic to a dedicated policy, allowing easier testing. - remove unused code - refactor specs to follow current guidelines/best practices
This commit is contained in:
parent
8337b49fe8
commit
3fbb2954cb
8 changed files with 295 additions and 333 deletions
|
@ -85,6 +85,10 @@ module Service
|
||||||
return super if args.present?
|
return super if args.present?
|
||||||
store[method_name]
|
store[method_name]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def respond_to_missing?(name, include_all)
|
||||||
|
store.key?(name) || super
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Internal module to define available steps as DSL
|
# Internal module to define available steps as DSL
|
||||||
|
|
|
@ -4,6 +4,8 @@ module Chat
|
||||||
class MessagesSerializer < ::ApplicationSerializer
|
class MessagesSerializer < ::ApplicationSerializer
|
||||||
attributes :messages, :tracking, :meta
|
attributes :messages, :tracking, :meta
|
||||||
|
|
||||||
|
delegate :metadata, to: :object, private: true
|
||||||
|
|
||||||
def initialize(object, opts)
|
def initialize(object, opts)
|
||||||
super(object, opts)
|
super(object, opts)
|
||||||
@opts = opts
|
@opts = opts
|
||||||
|
@ -30,9 +32,9 @@ module Chat
|
||||||
|
|
||||||
def meta
|
def meta
|
||||||
{
|
{
|
||||||
target_message_id: object.target_message_id,
|
target_message_id: metadata[:target_message]&.id,
|
||||||
can_load_more_future: object.can_load_more_future,
|
can_load_more_future: metadata[:can_load_more_future],
|
||||||
can_load_more_past: object.can_load_more_past,
|
can_load_more_past: metadata[:can_load_more_past],
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Chat::Channel::Policy::MessageExistence < Service::PolicyBase
|
||||||
|
delegate :channel, :target_message_id, to: :context
|
||||||
|
|
||||||
|
def call
|
||||||
|
return true if target_message_id.blank?
|
||||||
|
return false if target_message.blank?
|
||||||
|
return true if !target_message.trashed?
|
||||||
|
if target_message.trashed? && target_message.user_id == guardian.user.id || guardian.is_staff?
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
context[:target_message_id] = nil
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def reason
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def target_message
|
||||||
|
@target_message ||=
|
||||||
|
Chat::Message.with_deleted.find_by(id: target_message_id, chat_channel: channel)
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,7 +5,7 @@ module Chat
|
||||||
# or fetching paginated messages from last read.
|
# or fetching paginated messages from last read.
|
||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# Chat::ListChannelMessages.call(params: { channel_id: 2, **optional_params }, guardian: guardian)
|
# Chat::ListChannelMessages.call(params: { channel_id: 2, **optional_params }, guardian:)
|
||||||
#
|
#
|
||||||
class ListChannelMessages
|
class ListChannelMessages
|
||||||
include Service::Base
|
include Service::Base
|
||||||
|
@ -30,8 +30,10 @@ module Chat
|
||||||
validates :channel_id, presence: true
|
validates :channel_id, presence: true
|
||||||
validates :page_size,
|
validates :page_size,
|
||||||
numericality: {
|
numericality: {
|
||||||
less_than_or_equal_to: ::Chat::MessagesQuery::MAX_PAGE_SIZE,
|
less_than_or_equal_to: Chat::MessagesQuery::MAX_PAGE_SIZE,
|
||||||
|
greater_than_or_equal_to: 1,
|
||||||
only_integer: true,
|
only_integer: true,
|
||||||
|
only_numeric: true,
|
||||||
},
|
},
|
||||||
allow_nil: true
|
allow_nil: true
|
||||||
validates :direction,
|
validates :direction,
|
||||||
|
@ -39,19 +41,21 @@ module Chat
|
||||||
in: Chat::MessagesQuery::VALID_DIRECTIONS,
|
in: Chat::MessagesQuery::VALID_DIRECTIONS,
|
||||||
},
|
},
|
||||||
allow_nil: true
|
allow_nil: true
|
||||||
|
|
||||||
|
after_validation { self.page_size ||= Chat::MessagesQuery::MAX_PAGE_SIZE }
|
||||||
end
|
end
|
||||||
|
|
||||||
model :channel
|
model :channel
|
||||||
policy :can_view_channel
|
policy :can_view_channel
|
||||||
model :membership, optional: true
|
model :membership, optional: true
|
||||||
step :enabled_threads?
|
model :target_message_id, optional: true
|
||||||
step :determine_target_message_id
|
policy :target_message_exists, class_name: Chat::Channel::Policy::MessageExistence
|
||||||
policy :target_message_exists
|
model :metadata, optional: true
|
||||||
step :fetch_messages
|
model :messages, optional: true
|
||||||
step :fetch_thread_ids
|
model :thread_ids, optional: true
|
||||||
step :fetch_tracking
|
model :tracking, optional: true
|
||||||
step :fetch_thread_participants
|
model :thread_participants, optional: true
|
||||||
step :fetch_thread_memberships
|
model :thread_memberships, optional: true
|
||||||
step :update_membership_last_viewed_at
|
step :update_membership_last_viewed_at
|
||||||
step :update_user_last_channel
|
step :update_user_last_channel
|
||||||
|
|
||||||
|
@ -65,107 +69,57 @@ module Chat
|
||||||
channel.membership_for(guardian.user)
|
channel.membership_for(guardian.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def enabled_threads?(channel:)
|
|
||||||
context[:enabled_threads] = channel.threading_enabled
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_view_channel(guardian:, channel:)
|
def can_view_channel(guardian:, channel:)
|
||||||
guardian.can_preview_chat_channel?(channel)
|
guardian.can_preview_chat_channel?(channel)
|
||||||
end
|
end
|
||||||
|
|
||||||
def determine_target_message_id(params:, membership:)
|
def fetch_target_message_id(params:, membership:)
|
||||||
if params.fetch_from_last_read
|
return params.target_message_id unless params.fetch_from_last_read
|
||||||
context[:target_message_id] = membership&.last_read_message_id
|
membership&.last_read_message_id
|
||||||
else
|
|
||||||
context[:target_message_id] = params.target_message_id
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def target_message_exists(channel:, guardian:)
|
def fetch_metadata(channel:, guardian:, target_message_id:, params:)
|
||||||
return true if context.target_message_id.blank?
|
::Chat::MessagesQuery.call(
|
||||||
|
channel:,
|
||||||
target_message =
|
guardian:,
|
||||||
Chat::Message.with_deleted.find_by(id: context.target_message_id, chat_channel: channel)
|
target_message_id:,
|
||||||
return false if target_message.blank?
|
include_thread_messages: !channel.threading_enabled?,
|
||||||
|
**params.slice(:page_size, :direction, :target_date),
|
||||||
return true if !target_message.trashed?
|
|
||||||
if target_message.trashed? && target_message.user_id == guardian.user.id || guardian.is_staff?
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
context[:target_message_id] = nil
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_messages(channel:, params:, guardian:, enabled_threads:, target_message_id:)
|
|
||||||
messages_data =
|
|
||||||
::Chat::MessagesQuery.call(
|
|
||||||
channel:,
|
|
||||||
guardian:,
|
|
||||||
target_message_id:,
|
|
||||||
include_thread_messages: !enabled_threads,
|
|
||||||
page_size: params.page_size || Chat::MessagesQuery::MAX_PAGE_SIZE,
|
|
||||||
direction: params.direction,
|
|
||||||
target_date: params.target_date,
|
|
||||||
)
|
|
||||||
|
|
||||||
context[:can_load_more_past] = messages_data[:can_load_more_past]
|
|
||||||
context[:can_load_more_future] = messages_data[:can_load_more_future]
|
|
||||||
context[:target_message_id] = messages_data[:target_message_id]
|
|
||||||
|
|
||||||
messages_data[:target_message] = (
|
|
||||||
if messages_data[:target_message]&.thread_reply? &&
|
|
||||||
(enabled_threads || messages_data[:target_message].thread&.force)
|
|
||||||
[]
|
|
||||||
else
|
|
||||||
[messages_data[:target_message]]
|
|
||||||
end
|
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
context[:messages] = [
|
def fetch_messages(metadata:)
|
||||||
messages_data[:messages],
|
[
|
||||||
messages_data[:past_messages]&.reverse,
|
metadata[:messages],
|
||||||
messages_data[:target_message],
|
metadata[:past_messages]&.reverse,
|
||||||
messages_data[:future_messages],
|
(metadata[:target_message] unless metadata[:target_message]&.thread_reply?),
|
||||||
|
metadata[:future_messages],
|
||||||
].flatten.compact
|
].flatten.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_tracking(guardian:)
|
|
||||||
context[:tracking] = {}
|
|
||||||
|
|
||||||
return if !context.thread_ids.present?
|
|
||||||
|
|
||||||
context[:tracking] = ::Chat::TrackingStateReportQuery.call(
|
|
||||||
guardian: guardian,
|
|
||||||
thread_ids: context.thread_ids,
|
|
||||||
include_threads: true,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_thread_ids(messages:)
|
def fetch_thread_ids(messages:)
|
||||||
context[:thread_ids] = messages.map(&:thread_id).compact.uniq
|
messages.filter_map(&:thread_id).uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_thread_participants(messages:)
|
def fetch_tracking(guardian:, thread_ids:)
|
||||||
return if context.thread_ids.empty?
|
::Chat::TrackingStateReportQuery.(guardian:, thread_ids:, include_threads: true)
|
||||||
|
|
||||||
context[:thread_participants] = ::Chat::ThreadParticipantQuery.call(
|
|
||||||
thread_ids: context.thread_ids,
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_thread_memberships(guardian:)
|
def fetch_thread_participants(messages:, thread_ids:)
|
||||||
return if context.thread_ids.empty?
|
return if thread_ids.blank?
|
||||||
|
|
||||||
context[:thread_memberships] = ::Chat::UserChatThreadMembership.where(
|
::Chat::ThreadParticipantQuery.(thread_ids:)
|
||||||
thread_id: context.thread_ids,
|
|
||||||
user_id: guardian.user.id,
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_membership_last_viewed_at(guardian:)
|
def fetch_thread_memberships(guardian:, thread_ids:)
|
||||||
|
return if thread_ids.blank?
|
||||||
|
|
||||||
|
::Chat::UserChatThreadMembership.where(thread_id: thread_ids, user_id: guardian.user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_membership_last_viewed_at(guardian:, membership:)
|
||||||
Scheduler::Defer.later "Chat::ListChannelMessages - defer update_membership_last_viewed_at" do
|
Scheduler::Defer.later "Chat::ListChannelMessages - defer update_membership_last_viewed_at" do
|
||||||
context.membership&.update!(last_viewed_at: Time.zone.now)
|
membership&.update!(last_viewed_at: Time.zone.now)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ module Chat
|
||||||
model :membership, optional: true
|
model :membership, optional: true
|
||||||
step :determine_target_message_id
|
step :determine_target_message_id
|
||||||
policy :target_message_exists
|
policy :target_message_exists
|
||||||
|
model :metadata, optional: true
|
||||||
model :messages, optional: true
|
model :messages, optional: true
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -82,28 +83,26 @@ module Chat
|
||||||
target_message.user_id == guardian.user.id || guardian.is_staff?
|
target_message.user_id == guardian.user.id || guardian.is_staff?
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_messages(thread:, guardian:, params:)
|
def fetch_metadata(thread:, guardian:, params:)
|
||||||
messages_data =
|
::Chat::MessagesQuery.call(
|
||||||
::Chat::MessagesQuery.call(
|
guardian:,
|
||||||
channel: thread.channel,
|
channel: thread.channel,
|
||||||
guardian: guardian,
|
target_message_id: context.target_message_id,
|
||||||
target_message_id: context.target_message_id,
|
thread_id: thread.id,
|
||||||
thread_id: thread.id,
|
page_size: params.page_size,
|
||||||
page_size: params.page_size,
|
direction: params.direction,
|
||||||
direction: params.direction,
|
target_date: params.target_date,
|
||||||
target_date: params.target_date,
|
include_target_message_id:
|
||||||
include_target_message_id:
|
params.fetch_from_first_message || params.fetch_from_last_message,
|
||||||
params.fetch_from_first_message || params.fetch_from_last_message,
|
)
|
||||||
)
|
end
|
||||||
|
|
||||||
context[:can_load_more_past] = messages_data[:can_load_more_past]
|
|
||||||
context[:can_load_more_future] = messages_data[:can_load_more_future]
|
|
||||||
|
|
||||||
|
def fetch_messages(metadata:)
|
||||||
[
|
[
|
||||||
messages_data[:messages],
|
metadata[:messages],
|
||||||
messages_data[:past_messages]&.reverse,
|
metadata[:past_messages]&.reverse,
|
||||||
messages_data[:target_message],
|
metadata[:target_message],
|
||||||
messages_data[:future_messages],
|
metadata[:future_messages],
|
||||||
].flatten.compact
|
].flatten.compact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Chat::Channel::Policy::MessageExistence do
|
||||||
|
subject(:policy) { described_class.new(context) }
|
||||||
|
|
||||||
|
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
|
||||||
|
fab!(:channel) { Fabricate(:chat_channel) }
|
||||||
|
|
||||||
|
let(:guardian) { user.guardian }
|
||||||
|
let(:context) { Service::Base::Context.build(channel:, guardian:, target_message_id:) }
|
||||||
|
|
||||||
|
describe "#call" do
|
||||||
|
subject(:result) { policy.call }
|
||||||
|
|
||||||
|
context "when 'target_message_id' is not provided" do
|
||||||
|
let(:target_message_id) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when 'target_message_id' is provided" do
|
||||||
|
context "when target message does not exist" do
|
||||||
|
let(:target_message_id) { -1 }
|
||||||
|
|
||||||
|
it { is_expected.to be false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when target message exists" do
|
||||||
|
fab!(:message) { Fabricate(:chat_message, chat_channel: channel, user:) }
|
||||||
|
|
||||||
|
let(:target_message_id) { message.id }
|
||||||
|
|
||||||
|
context "when target message is not trashed" do
|
||||||
|
it { is_expected.to be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when target message is trashed" do
|
||||||
|
before { message.trash! }
|
||||||
|
|
||||||
|
context "when target message’s user is the same as the guardian" do
|
||||||
|
it { is_expected.to be true }
|
||||||
|
|
||||||
|
it "does not set 'target_message_id' to nil" do
|
||||||
|
expect { result }.not_to change { context.target_message_id }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when target message’s user is different than the guardian" do
|
||||||
|
fab!(:other_user) { Fabricate(:user, refresh_auto_groups: true) }
|
||||||
|
|
||||||
|
before { message.update!(user: other_user) }
|
||||||
|
|
||||||
|
context "when guardian is staff" do
|
||||||
|
let(:guardian) { Discourse.system_user.guardian }
|
||||||
|
|
||||||
|
it { is_expected.to be true }
|
||||||
|
|
||||||
|
it "does not set 'target_message_id' to nil" do
|
||||||
|
expect { result }.not_to change { context.target_message_id }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when guardian is not staff" do
|
||||||
|
it { is_expected.to be true }
|
||||||
|
|
||||||
|
it "sets 'target_message_id' to nil" do
|
||||||
|
expect { result }.to change { context.target_message_id }.to(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,267 +1,168 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe Chat::ListChannelMessages do
|
RSpec.describe Chat::ListChannelMessages do
|
||||||
subject(:result) { described_class.call(params:, **dependencies) }
|
describe described_class::Contract, type: :model do
|
||||||
|
it { is_expected.to validate_presence_of(:channel_id) }
|
||||||
|
it { is_expected.to allow_values(1, Chat::MessagesQuery::MAX_PAGE_SIZE, nil).for(:page_size) }
|
||||||
|
it do
|
||||||
|
is_expected.not_to allow_values(0, Chat::MessagesQuery::MAX_PAGE_SIZE + 1).for(:page_size)
|
||||||
|
end
|
||||||
|
it do
|
||||||
|
is_expected.to validate_inclusion_of(:direction).in_array(
|
||||||
|
Chat::MessagesQuery::VALID_DIRECTIONS,
|
||||||
|
).allow_nil
|
||||||
|
end
|
||||||
|
|
||||||
fab!(:user)
|
describe "#page_size" do
|
||||||
fab!(:channel) { Fabricate(:chat_channel) }
|
let(:contract) { described_class.new }
|
||||||
|
|
||||||
let(:guardian) { Guardian.new(user) }
|
context "when page_size is not set" do
|
||||||
let(:channel_id) { channel.id }
|
it "defaults to MAX_PAGE_SIZE" do
|
||||||
let(:optional_params) { {} }
|
contract.validate
|
||||||
let(:params) { { channel_id: }.merge(optional_params) }
|
expect(contract.page_size).to eq(Chat::MessagesQuery::MAX_PAGE_SIZE)
|
||||||
let(:dependencies) { { guardian: } }
|
end
|
||||||
|
end
|
||||||
|
|
||||||
before { channel.add(user) }
|
context "when page_size is set to nil" do
|
||||||
|
before { contract.page_size = nil }
|
||||||
|
|
||||||
context "when contract" do
|
it "defaults to MAX_PAGE_SIZE" do
|
||||||
context "when channel_id is not present" do
|
contract.validate
|
||||||
|
expect(contract.page_size).to eq(Chat::MessagesQuery::MAX_PAGE_SIZE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when page_size is set" do
|
||||||
|
before { contract.page_size = 5 }
|
||||||
|
|
||||||
|
it "does not change the value" do
|
||||||
|
contract.validate
|
||||||
|
expect(contract.page_size).to eq(5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".call" do
|
||||||
|
subject(:result) { described_class.call(params:, **dependencies) }
|
||||||
|
|
||||||
|
fab!(:user)
|
||||||
|
fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) }
|
||||||
|
|
||||||
|
let(:guardian) { Guardian.new(user) }
|
||||||
|
let(:channel_id) { channel.id }
|
||||||
|
let(:optional_params) { {} }
|
||||||
|
let(:params) { { channel_id:, **optional_params } }
|
||||||
|
let(:dependencies) { { guardian: } }
|
||||||
|
|
||||||
|
before { channel.add(user) }
|
||||||
|
|
||||||
|
context "when data is not valid" do
|
||||||
let(:channel_id) { nil }
|
let(:channel_id) { nil }
|
||||||
|
|
||||||
it { is_expected.to fail_a_contract }
|
it { is_expected.to fail_a_contract }
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context "when fetch_channel" do
|
|
||||||
context "when channel doesn’t exist" do
|
context "when channel doesn’t exist" do
|
||||||
let(:channel_id) { -1 }
|
let(:channel_id) { -1 }
|
||||||
|
|
||||||
it { is_expected.to fail_to_find_a_model(:channel) }
|
it { is_expected.to fail_to_find_a_model(:channel) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when channel exists" do
|
|
||||||
it { is_expected.to run_successfully }
|
|
||||||
|
|
||||||
it "finds the correct channel" do
|
|
||||||
expect(result.channel).to eq(channel)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when fetch_eventual_membership" do
|
|
||||||
context "when user has membership" do
|
|
||||||
it { is_expected.to run_successfully }
|
|
||||||
|
|
||||||
it "finds the correct membership" do
|
|
||||||
expect(result.membership).to eq(channel.membership_for(user))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when user has no membership" do
|
|
||||||
before { channel.membership_for(user).destroy! }
|
|
||||||
|
|
||||||
it { is_expected.to run_successfully }
|
|
||||||
|
|
||||||
it "finds no membership" do
|
|
||||||
expect(result.membership).to be_blank
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when enabled_threads?" do
|
|
||||||
context "when channel threading is disabled" do
|
|
||||||
before { channel.update!(threading_enabled: false) }
|
|
||||||
|
|
||||||
it "marks threads as disabled" do
|
|
||||||
expect(result.enabled_threads).to eq(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when channel and site setting are enabling threading" do
|
|
||||||
before { channel.update!(threading_enabled: true) }
|
|
||||||
|
|
||||||
it "marks threads as enabled" do
|
|
||||||
expect(result.enabled_threads).to eq(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when determine_target_message_id" do
|
|
||||||
context "when fetch_from_last_read is true" do
|
|
||||||
let(:optional_params) { { fetch_from_last_read: true } }
|
|
||||||
|
|
||||||
before do
|
|
||||||
channel.add(user)
|
|
||||||
channel.membership_for(user).update!(last_read_message_id: 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "sets target_message_id to last_read_message_id" do
|
|
||||||
expect(result.target_message_id).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when target_message_exists" do
|
|
||||||
context "when no target_message_id is given" do
|
|
||||||
it { is_expected.to run_successfully }
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when target message is not found" do
|
context "when target message is not found" do
|
||||||
let(:optional_params) { { target_message_id: -1 } }
|
let(:optional_params) { { target_message_id: -1 } }
|
||||||
|
|
||||||
it { is_expected.to fail_a_policy(:target_message_exists) }
|
it { is_expected.to fail_a_policy(:target_message_exists) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when target message is found" do
|
context "when everything is ok" do
|
||||||
fab!(:target_message) { Fabricate(:chat_message, chat_channel: channel) }
|
fab!(:thread) { Fabricate(:chat_thread, channel:) }
|
||||||
let(:optional_params) { { target_message_id: target_message.id } }
|
fab!(:messages) do
|
||||||
|
[thread.original_message, *Fabricate.times(20, :chat_message, chat_channel: channel)]
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:tracking) { double }
|
||||||
|
let(:thread_ids) { [thread.id] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
thread.add(user)
|
||||||
|
allow(Chat::TrackingStateReportQuery).to receive(:call).with(
|
||||||
|
guardian:,
|
||||||
|
thread_ids:,
|
||||||
|
include_threads: true,
|
||||||
|
).and_return(tracking)
|
||||||
|
end
|
||||||
|
|
||||||
it { is_expected.to run_successfully }
|
it { is_expected.to run_successfully }
|
||||||
end
|
|
||||||
|
|
||||||
context "when target message is trashed" do
|
it "finds the correct channel" do
|
||||||
fab!(:target_message) { Fabricate(:chat_message, chat_channel: channel) }
|
expect(result.channel).to eq(channel)
|
||||||
let(:optional_params) { { target_message_id: target_message.id } }
|
end
|
||||||
|
|
||||||
before { target_message.trash! }
|
it "finds the correct membership" do
|
||||||
|
expect(result.membership).to eq(channel.membership_for(user))
|
||||||
|
end
|
||||||
|
|
||||||
context "when user is regular" do
|
context "when fetch_from_last_read is true" do
|
||||||
it "nullifies target_message_id" do
|
let(:optional_params) { { fetch_from_last_read: true } }
|
||||||
expect(result.target_message_id).to be_blank
|
|
||||||
|
before do
|
||||||
|
channel.add(user)
|
||||||
|
channel.membership_for(user).update!(last_read_message: messages.second)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets target_message to last_read_message_id" do
|
||||||
|
expect(result.metadata[:target_message]).to eq(messages.second)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when user is the message creator" do
|
|
||||||
fab!(:target_message) { Fabricate(:chat_message, chat_channel: channel, user: user) }
|
|
||||||
|
|
||||||
it { is_expected.to run_successfully }
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when user is admin" do
|
|
||||||
fab!(:user) { Fabricate(:admin) }
|
|
||||||
|
|
||||||
it { is_expected.to run_successfully }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when fetch_messages" do
|
|
||||||
context "with no params" do
|
|
||||||
fab!(:messages) { Fabricate.times(20, :chat_message, chat_channel: channel) }
|
|
||||||
|
|
||||||
it { is_expected.to be_a_success }
|
|
||||||
|
|
||||||
it "returns messages" do
|
it "returns messages" do
|
||||||
expect(result.can_load_more_past).to eq(false)
|
expect(result).to have_attributes(
|
||||||
expect(result.can_load_more_future).to eq(false)
|
metadata: a_hash_including(can_load_more_future: false, can_load_more_past: false),
|
||||||
expect(result.messages).to contain_exactly(*messages)
|
messages:,
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when target_date is provided" do
|
|
||||||
fab!(:past_message) do
|
|
||||||
Fabricate(:chat_message, chat_channel: channel, created_at: 3.days.ago)
|
|
||||||
end
|
|
||||||
fab!(:future_message) do
|
|
||||||
Fabricate(:chat_message, chat_channel: channel, created_at: 1.days.ago)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:optional_params) { { target_date: 2.days.ago } }
|
|
||||||
|
|
||||||
it { is_expected.to be_a_success }
|
|
||||||
|
|
||||||
it "includes past and future messages" do
|
|
||||||
expect(result.messages).to eq([past_message, future_message])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when fetch_tracking" do
|
|
||||||
context "when threads are disabled" do
|
|
||||||
fab!(:thread_1) { Fabricate(:chat_thread, channel: channel) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
channel.update!(threading_enabled: false)
|
|
||||||
thread_1.add(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.to be_a_success }
|
|
||||||
|
|
||||||
it "returns tracking" do
|
|
||||||
Fabricate(:chat_message, chat_channel: channel, thread: thread_1)
|
|
||||||
|
|
||||||
expect(result.tracking.thread_tracking).to eq(
|
|
||||||
{
|
|
||||||
thread_1.id => {
|
|
||||||
channel_id: channel.id,
|
|
||||||
mention_count: 0,
|
|
||||||
unread_count: 0,
|
|
||||||
watched_threads_unread_count: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when thread is forced" do
|
context "when target_date is provided" do
|
||||||
before { thread_1.update!(force: true) }
|
fab!(:past_message) do
|
||||||
|
Fabricate(:chat_message, chat_channel: channel, created_at: 3.days.ago)
|
||||||
|
end
|
||||||
|
fab!(:future_message) do
|
||||||
|
Fabricate(:chat_message, chat_channel: channel, created_at: 1.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
it { is_expected.to be_a_success }
|
let(:optional_params) { { target_date: 2.days.ago } }
|
||||||
|
|
||||||
it "returns tracking" do
|
it "includes past and future messages" do
|
||||||
Fabricate(:chat_message, chat_channel: channel, thread: thread_1)
|
expect(result.messages).to include(past_message, future_message)
|
||||||
|
|
||||||
expect(result.tracking.thread_tracking).to eq(
|
|
||||||
{
|
|
||||||
thread_1.id => {
|
|
||||||
channel_id: channel.id,
|
|
||||||
mention_count: 0,
|
|
||||||
unread_count: 1,
|
|
||||||
watched_threads_unread_count: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context "when threads are enabled" do
|
|
||||||
fab!(:thread_1) { Fabricate(:chat_thread, channel: channel) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
channel.update!(threading_enabled: true)
|
|
||||||
thread_1.add(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.to be_a_success }
|
|
||||||
|
|
||||||
it "returns tracking" do
|
it "returns tracking" do
|
||||||
Fabricate(:chat_message, chat_channel: channel, thread: thread_1)
|
expect(result.tracking).to eq(tracking)
|
||||||
|
end
|
||||||
|
|
||||||
expect(result.tracking.channel_tracking).to eq({})
|
it "updates the last viewed at" do
|
||||||
expect(result.tracking.thread_tracking).to eq(
|
expect { result }.to change { channel.membership_for(user).last_viewed_at }.to be_within(
|
||||||
{
|
1.second,
|
||||||
thread_1.id => {
|
).of(Time.zone.now)
|
||||||
channel_id: channel.id,
|
end
|
||||||
mention_count: 0,
|
|
||||||
unread_count: 1,
|
it "updates the custom field" do
|
||||||
watched_threads_unread_count: 0,
|
expect { result }.to change { user.custom_fields[Chat::LAST_CHAT_CHANNEL_ID] }.from(nil).to(
|
||||||
},
|
channel.id,
|
||||||
},
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when update_membership_last_viewed_at" do
|
context "when custom field was already set" do
|
||||||
it "updates the last viewed at" do
|
let(:field) { UserCustomField.find_by(name: Chat::LAST_CHAT_CHANNEL_ID, user_id: user.id) }
|
||||||
expect { result }.to change { channel.membership_for(user).last_viewed_at }.to be_within(
|
|
||||||
1.second,
|
|
||||||
).of(Time.zone.now)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when update_user_last_channel" do
|
before { user.upsert_custom_fields(::Chat::LAST_CHAT_CHANNEL_ID => channel.id) }
|
||||||
it "updates the custom field" do
|
|
||||||
expect { result }.to change { user.custom_fields[Chat::LAST_CHAT_CHANNEL_ID] }.from(nil).to(
|
|
||||||
channel.id,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn’t update the custom field when it was already set to this value" do
|
it "doesn’t update the custom field" do
|
||||||
user.upsert_custom_fields(::Chat::LAST_CHAT_CHANNEL_ID => channel.id)
|
expect { result }.to_not change { field.reload.updated_at }
|
||||||
field = UserCustomField.find_by(name: Chat::LAST_CHAT_CHANNEL_ID, user_id: user.id)
|
end
|
||||||
|
end
|
||||||
expect { result }.to_not change { field.reload.updated_at }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -149,9 +149,10 @@ RSpec.describe Chat::ListChannelThreadMessages do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns messages" do
|
it "returns messages" do
|
||||||
expect(result.can_load_more_past).to eq(false)
|
expect(result).to have_attributes(
|
||||||
expect(result.can_load_more_future).to eq(false)
|
metadata: a_hash_including(can_load_more_future: false, can_load_more_past: false),
|
||||||
expect(result.messages).to contain_exactly(thread.original_message, *messages)
|
messages: [thread.original_message, *messages],
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue