DEV: Refactor UpdateUserLastRead a little

We’re now using `contract` as the first step and validations for
mandatory parameters have been added.

To simplify specs a bit, we only assert the service contract is run as
expected without testing each validation case. We’re now testing the
contract itself in isolation.
This commit is contained in:
Loïc Guitaut
2023-02-13 15:14:10 +01:00
committed by Loïc Guitaut
parent 60ad836313
commit 5f4623ba47
2 changed files with 93 additions and 90 deletions

View File

@ -17,9 +17,9 @@ module Chat
# @param [Guardian] guardian # @param [Guardian] guardian
# @return [Chat::Service::Base::Context] # @return [Chat::Service::Base::Context]
contract
model :membership, :fetch_active_membership model :membership, :fetch_active_membership
policy :invalid_access policy :invalid_access
contract
policy :ensure_message_id_recency policy :ensure_message_id_recency
policy :ensure_message_exists policy :ensure_message_exists
step :update_last_read_message_id step :update_last_read_message_id
@ -31,6 +31,8 @@ module Chat
attribute :message_id, :integer attribute :message_id, :integer
attribute :user_id, :integer attribute :user_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
validates :message_id, :user_id, :channel_id, presence: true
end end
private private

View File

@ -1,118 +1,119 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe(Chat::Service::UpdateUserLastRead) do RSpec.describe Chat::Service::UpdateUserLastRead do
subject(:result) { described_class.call(params) } describe Chat::Service::UpdateUserLastRead::Contract, type: :model do
it { is_expected.to validate_presence_of :user_id }
fab!(:current_user) { Fabricate(:user) } it { is_expected.to validate_presence_of :channel_id }
fab!(:channel) { Fabricate(:chat_channel) } it { is_expected.to validate_presence_of :message_id }
fab!(:membership) do
Fabricate(:user_chat_channel_membership, user: current_user, chat_channel: channel)
end
fab!(:message_1) { Fabricate(:chat_message, chat_channel: membership.chat_channel) }
let(:guardian) { Guardian.new(current_user) }
let(:params) do
{
guardian: guardian,
user_id: current_user.id,
channel_id: channel.id,
message_id: message_1.id,
}
end end
context "when channel_id is not provided" do describe ".call" do
before { params.delete(:channel_id) } subject(:result) { described_class.call(params) }
it { is_expected.to fail_to_find_a_model(:membership) } fab!(:current_user) { Fabricate(:user) }
end fab!(:channel) { Fabricate(:chat_channel) }
context "when user_id is not provided" do
before { params.delete(:user_id) }
it { is_expected.to fail_to_find_a_model(:membership) }
end
context "when user has no membership" do
before { membership.destroy! }
it { is_expected.to fail_to_find_a_model(:membership) }
end
context "when user can’t access the channel" do
fab!(:membership) do fab!(:membership) do
Fabricate( Fabricate(:user_chat_channel_membership, user: current_user, chat_channel: channel)
:user_chat_channel_membership, end
user: current_user, fab!(:message_1) { Fabricate(:chat_message, chat_channel: membership.chat_channel) }
chat_channel: Fabricate(:private_category_channel),
) let(:guardian) { Guardian.new(current_user) }
let(:params) do
{
guardian: guardian,
user_id: current_user.id,
channel_id: channel.id,
message_id: message_1.id,
}
end end
before { params[:channel_id] = membership.chat_channel.id } context "when params are not valid" do
before { params.delete(:user_id) }
it { is_expected.to fail_a_policy(:invalid_access) } it { is_expected.to fail_a_contract }
end
context "when message_id is older than membership's last_read_message_id" do
before do
params[:message_id] = -2
membership.update!(last_read_message_id: -1)
end end
it { is_expected.to fail_a_policy(:ensure_message_id_recency) } context "when params are valid" do
end context "when user has no membership" do
before { membership.destroy! }
context "when message doesn’t exist" do it { is_expected.to fail_to_find_a_model(:membership) }
before do end
params[:message_id] = 2
membership.update!(last_read_message_id: 1)
end
it { is_expected.to fail_a_policy(:ensure_message_exists) } context "when user can’t access the channel" do
end fab!(:membership) do
Fabricate(
:user_chat_channel_membership,
user: current_user,
chat_channel: Fabricate(:private_category_channel),
)
end
context "when params are valid" do before { params[:channel_id] = membership.chat_channel.id }
before { Jobs.run_immediately! }
it "sets the service result as successful" do it { is_expected.to fail_a_policy(:invalid_access) }
expect(result).to be_a_success end
end
it "updates the last_read message id" do context "when message_id is older than membership's last_read_message_id" do
expect { result }.to change { membership.reload.last_read_message_id }.to(message_1.id) before do
end params[:message_id] = -2
membership.update!(last_read_message_id: -1)
end
it "marks existing notifications related to the message as read" do it { is_expected.to fail_a_policy(:ensure_message_id_recency) }
expect { end
notification =
context "when message doesn’t exist" do
before do
params[:message_id] = 2
membership.update!(last_read_message_id: 1)
end
it { is_expected.to fail_a_policy(:ensure_message_exists) }
end
context "when everything is fine" do
fab!(:notification) do
Fabricate( Fabricate(
:notification, :notification,
notification_type: Notification.types[:chat_mention], notification_type: Notification.types[:chat_mention],
user: current_user, user: current_user,
) )
end
# FIXME: we need a better way to create proper chat mention let(:messages) { MessageBus.track_publish { result } }
ChatMention.create!(notification: notification, user: current_user, chat_message: message_1)
}.to change {
Notification.where(
notification_type: Notification.types[:chat_mention],
user: current_user,
read: false,
).count
}.by(1)
expect { result }.to change { before do
Notification.where( Jobs.run_immediately!
notification_type: Notification.types[:chat_mention], ChatMention.create!(
user: current_user, notification: notification,
read: false, user: current_user,
).count chat_message: message_1,
}.by(-1) )
end end
it "publishes new last read to clients" do it "sets the service result as successful" do
messages = MessageBus.track_publish { result } expect(result).to be_a_success
end
expect(messages.map(&:channel)).to include("/chat/user-tracking-state/#{current_user.id}") it "updates the last_read message id" do
expect { result }.to change { membership.reload.last_read_message_id }.to(message_1.id)
end
it "marks existing notifications related to the message as read" do
expect { result }.to change {
Notification.where(
notification_type: Notification.types[:chat_mention],
user: current_user,
read: false,
).count
}.by(-1)
end
it "publishes new last read to clients" do
expect(messages.map(&:channel)).to include("/chat/user-tracking-state/#{current_user.id}")
end
end
end end
end end
end end