mirror of
https://github.com/discourse/discourse.git
synced 2025-06-03 02:48:28 +08:00
DEV: Move discourse-chat
to the core repo. (#18776)
As part of this move, we are also renaming `discourse-chat` to `chat`.
This commit is contained in:
@ -0,0 +1,31 @@
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"auto_join_users": { "type": "boolean" }
|
||||
},
|
||||
"properties": {
|
||||
"id": { "type": "number" },
|
||||
"chatable_type": { "type": "string" },
|
||||
"chatable_url": { "type": "string" },
|
||||
"title": { "type": "string" },
|
||||
"chatable_id": { "type": "number" },
|
||||
"last_message_sent_at": { "type": "string" },
|
||||
"status": { "type": "string" },
|
||||
"chatable": {
|
||||
"type": "object",
|
||||
"required": ["id", "name", "slug", "color"]
|
||||
},
|
||||
"current_user_membership": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"last_read_message_id": { "type": ["number", "null"] },
|
||||
"muted": { "type": "boolean" },
|
||||
"unread_count": { "type": "number" },
|
||||
"unread_mentions": { "type": "number" },
|
||||
"desktop_notification_level": { "type": "string" },
|
||||
"mobile_notification_level": { "type": "string" },
|
||||
"following": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"chat_channel_id",
|
||||
"last_read_message_id",
|
||||
"muted",
|
||||
"desktop_notification_level",
|
||||
"mobile_notification_level",
|
||||
"following"
|
||||
],
|
||||
"properties": {
|
||||
"chat_channel_id": { "type": "number" },
|
||||
"last_read_message_id": { "type": ["number", "null"] },
|
||||
"muted": { "type": "boolean" },
|
||||
"desktop_notification_level": { "type": "string" },
|
||||
"mobile_notification_level": { "type": "string" },
|
||||
"following": { "type": "boolean" },
|
||||
"unread_count": { "type": "number" },
|
||||
"unread_mentions": { "type": "number" },
|
||||
"user": {
|
||||
"type": ["object", "null"],
|
||||
"required": ["id", "name", "avatar_template", "username"],
|
||||
"properties": {
|
||||
"id": { "type": "number" },
|
||||
"name": { "type": "string" },
|
||||
"avatar_template": { "type": "string" },
|
||||
"username": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
plugins/chat/spec/support/api_schema_matcher.rb
Normal file
16
plugins/chat/spec/support/api_schema_matcher.rb
Normal file
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec::Matchers.define :match_response_schema do |schema|
|
||||
match do |object|
|
||||
schema_directory = "#{Dir.pwd}/plugins/chat/spec/support/api/schemas"
|
||||
schema_path = "#{schema_directory}/#{schema}.json"
|
||||
|
||||
begin
|
||||
JSON::Validator.validate!(schema_path, object, strict: true)
|
||||
rescue JSON::Schema::ValidationError => e
|
||||
puts "-- Printing response body after validation error\n"
|
||||
pp object
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
19
plugins/chat/spec/support/chat_helper.rb
Normal file
19
plugins/chat/spec/support/chat_helper.rb
Normal file
@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ChatHelper
|
||||
def self.make_messages!(chatable, users, count)
|
||||
users = [users] unless Array === users
|
||||
raise ArgumentError if users.length <= 0
|
||||
|
||||
chatable = Fabricate(:category) unless chatable
|
||||
chat_channel = Fabricate(:chat_channel, chatable: chatable)
|
||||
|
||||
count.times do |n|
|
||||
ChatMessage.new(
|
||||
chat_channel: chat_channel,
|
||||
user: users[n % users.length],
|
||||
message: "Chat message for test #{n}",
|
||||
).save!
|
||||
end
|
||||
end
|
||||
end
|
39
plugins/chat/spec/support/examples/channel_access_example.rb
Normal file
39
plugins/chat/spec/support/examples/channel_access_example.rb
Normal file
@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples "channel access example" do |verb, endpoint|
|
||||
endpoint ||= ".json"
|
||||
|
||||
context "when channel is not found" do
|
||||
before { sign_in(Fabricate(:admin)) }
|
||||
|
||||
it "returns a 404" do
|
||||
public_send(verb, "/chat/api/chat_channels/-999#{endpoint}")
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
context "with anonymous user" do
|
||||
fab!(:chat_channel) { Fabricate(:category_channel) }
|
||||
|
||||
it "returns a 403" do
|
||||
public_send(verb, "/chat/api/chat_channels/#{chat_channel.id}#{endpoint}")
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
|
||||
context "when channel can’t be seen by current user" do
|
||||
fab!(:chatable) { Fabricate(:private_category, group: Fabricate(:group)) }
|
||||
fab!(:chat_channel) { Fabricate(:category_channel, chatable: chatable) }
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
fab!(:membership) do
|
||||
Fabricate(:user_chat_channel_membership, user: user, chat_channel: chat_channel)
|
||||
end
|
||||
|
||||
before { sign_in(user) }
|
||||
|
||||
it "returns a 403" do
|
||||
public_send(verb, "/chat/api/chat_channels/#{chat_channel.id}#{endpoint}")
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
end
|
346
plugins/chat/spec/support/examples/chat_channel_model.rb
Normal file
346
plugins/chat/spec/support/examples/chat_channel_model.rb
Normal file
@ -0,0 +1,346 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples "a chat channel model" do
|
||||
fab!(:user1) { Fabricate(:user) }
|
||||
fab!(:user2) { Fabricate(:user) }
|
||||
fab!(:staff) { Fabricate(:user, admin: true) }
|
||||
fab!(:group) { Fabricate(:group) }
|
||||
fab!(:private_category) { Fabricate(:private_category, group: group) }
|
||||
fab!(:private_category_channel) { Fabricate(:category_channel, chatable: private_category) }
|
||||
fab!(:direct_message_channel) do
|
||||
Fabricate(:dm_channel, chatable: Fabricate(:direct_message_channel, users: [user1, user2]))
|
||||
end
|
||||
|
||||
it { is_expected.to belong_to(:chatable) }
|
||||
it { is_expected.to belong_to(:direct_message_channel).with_foreign_key(:chatable_id) }
|
||||
it { is_expected.to have_many(:chat_messages) }
|
||||
it { is_expected.to have_many(:user_chat_channel_memberships) }
|
||||
it { is_expected.to have_one(:chat_channel_archive) }
|
||||
it do
|
||||
is_expected.to define_enum_for(:status).with_values(
|
||||
open: 0,
|
||||
read_only: 1,
|
||||
closed: 2,
|
||||
archived: 3,
|
||||
).without_scopes
|
||||
end
|
||||
|
||||
describe "Validations" do
|
||||
it { is_expected.to validate_presence_of(:name).allow_nil }
|
||||
it do
|
||||
is_expected.to validate_length_of(:name).is_at_most(
|
||||
SiteSetting.max_topic_title_length,
|
||||
).allow_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe ".public_channels" do
|
||||
context "when a category used as chatable is destroyed" do
|
||||
fab!(:category_channel_1) { Fabricate(:chat_channel, chatable: Fabricate(:category)) }
|
||||
fab!(:category_channel_2) { Fabricate(:chat_channel, chatable: Fabricate(:category)) }
|
||||
|
||||
before { category_channel_1.chatable.destroy! }
|
||||
|
||||
it "doesn’t list the channel" do
|
||||
ids = ChatChannel.public_channels.pluck(:chatable_id)
|
||||
expect(ids).to_not include(category_channel_1.chatable_id)
|
||||
expect(ids).to include(category_channel_2.chatable_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#closed!" do
|
||||
before { private_category_channel.update!(status: :open) }
|
||||
|
||||
it "does nothing if user is not staff" do
|
||||
private_category_channel.closed!(user1)
|
||||
expect(private_category_channel.reload.open?).to eq(true)
|
||||
end
|
||||
|
||||
it "closes the channel, logs a staff action, and sends an event" do
|
||||
events = []
|
||||
messages =
|
||||
MessageBus.track_publish do
|
||||
events = DiscourseEvent.track_events { private_category_channel.closed!(staff) }
|
||||
end
|
||||
|
||||
expect(events).to include(
|
||||
event_name: :chat_channel_status_change,
|
||||
params: [{ channel: private_category_channel, old_status: "open", new_status: "closed" }],
|
||||
)
|
||||
expect(messages.first.channel).to eq("/chat/channel-status")
|
||||
expect(messages.first.data).to eq(
|
||||
{ chat_channel_id: private_category_channel.id, status: "closed" },
|
||||
)
|
||||
expect(private_category_channel.reload.closed?).to eq(true)
|
||||
|
||||
expect(
|
||||
UserHistory.exists?(
|
||||
acting_user_id: staff.id,
|
||||
action: UserHistory.actions[:custom_staff],
|
||||
custom_type: "chat_channel_status_change",
|
||||
new_value: :closed,
|
||||
previous_value: :open,
|
||||
),
|
||||
).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#open!" do
|
||||
before { private_category_channel.update!(status: :closed) }
|
||||
|
||||
it "does nothing if user is not staff" do
|
||||
private_category_channel.open!(user1)
|
||||
expect(private_category_channel.reload.closed?).to eq(true)
|
||||
end
|
||||
|
||||
it "does nothing if the channel is archived" do
|
||||
private_category_channel.update!(status: :archived)
|
||||
private_category_channel.open!(staff)
|
||||
expect(private_category_channel.reload.archived?).to eq(true)
|
||||
end
|
||||
|
||||
it "opens the channel, logs a staff action, and sends an event" do
|
||||
events = []
|
||||
messages =
|
||||
MessageBus.track_publish do
|
||||
events = DiscourseEvent.track_events { private_category_channel.open!(staff) }
|
||||
end
|
||||
|
||||
expect(events).to include(
|
||||
event_name: :chat_channel_status_change,
|
||||
params: [{ channel: private_category_channel, old_status: "closed", new_status: "open" }],
|
||||
)
|
||||
expect(messages.first.channel).to eq("/chat/channel-status")
|
||||
expect(messages.first.data).to eq(
|
||||
{ chat_channel_id: private_category_channel.id, status: "open" },
|
||||
)
|
||||
expect(private_category_channel.reload.open?).to eq(true)
|
||||
|
||||
expect(
|
||||
UserHistory.exists?(
|
||||
acting_user_id: staff.id,
|
||||
action: UserHistory.actions[:custom_staff],
|
||||
custom_type: "chat_channel_status_change",
|
||||
new_value: :open,
|
||||
previous_value: :closed,
|
||||
),
|
||||
).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#read_only!" do
|
||||
before { private_category_channel.update!(status: :open) }
|
||||
|
||||
it "does nothing if user is not staff" do
|
||||
private_category_channel.read_only!(user1)
|
||||
expect(private_category_channel.reload.open?).to eq(true)
|
||||
end
|
||||
|
||||
it "marks the channel read_only, logs a staff action, and sends an event" do
|
||||
events = []
|
||||
messages =
|
||||
MessageBus.track_publish do
|
||||
events = DiscourseEvent.track_events { private_category_channel.read_only!(staff) }
|
||||
end
|
||||
|
||||
expect(events).to include(
|
||||
event_name: :chat_channel_status_change,
|
||||
params: [
|
||||
{ channel: private_category_channel, old_status: "open", new_status: "read_only" },
|
||||
],
|
||||
)
|
||||
expect(messages.first.channel).to eq("/chat/channel-status")
|
||||
expect(messages.first.data).to eq(
|
||||
{ chat_channel_id: private_category_channel.id, status: "read_only" },
|
||||
)
|
||||
expect(private_category_channel.reload.read_only?).to eq(true)
|
||||
|
||||
expect(
|
||||
UserHistory.exists?(
|
||||
acting_user_id: staff.id,
|
||||
action: UserHistory.actions[:custom_staff],
|
||||
custom_type: "chat_channel_status_change",
|
||||
new_value: :read_only,
|
||||
previous_value: :open,
|
||||
),
|
||||
).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#archived!" do
|
||||
before { private_category_channel.update!(status: :read_only) }
|
||||
|
||||
it "does nothing if user is not staff" do
|
||||
private_category_channel.archived!(user1)
|
||||
expect(private_category_channel.reload.read_only?).to eq(true)
|
||||
end
|
||||
|
||||
it "does nothing if already archived" do
|
||||
private_category_channel.update!(status: :archived)
|
||||
private_category_channel.archived!(user1)
|
||||
expect(private_category_channel.reload.archived?).to eq(true)
|
||||
end
|
||||
|
||||
it "does nothing if the channel is not already readonly" do
|
||||
private_category_channel.update!(status: :open)
|
||||
private_category_channel.archived!(staff)
|
||||
expect(private_category_channel.reload.open?).to eq(true)
|
||||
private_category_channel.update!(status: :read_only)
|
||||
private_category_channel.archived!(staff)
|
||||
expect(private_category_channel.reload.archived?).to eq(true)
|
||||
end
|
||||
|
||||
it "marks the channel archived, logs a staff action, and sends an event" do
|
||||
events = []
|
||||
messages =
|
||||
MessageBus.track_publish do
|
||||
events = DiscourseEvent.track_events { private_category_channel.archived!(staff) }
|
||||
end
|
||||
|
||||
expect(events).to include(
|
||||
event_name: :chat_channel_status_change,
|
||||
params: [
|
||||
{ channel: private_category_channel, old_status: "read_only", new_status: "archived" },
|
||||
],
|
||||
)
|
||||
expect(messages.first.channel).to eq("/chat/channel-status")
|
||||
expect(messages.first.data).to eq(
|
||||
{ chat_channel_id: private_category_channel.id, status: "archived" },
|
||||
)
|
||||
expect(private_category_channel.reload.archived?).to eq(true)
|
||||
|
||||
expect(
|
||||
UserHistory.exists?(
|
||||
acting_user_id: staff.id,
|
||||
action: UserHistory.actions[:custom_staff],
|
||||
custom_type: "chat_channel_status_change",
|
||||
new_value: :archived,
|
||||
previous_value: :read_only,
|
||||
),
|
||||
).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#add" do
|
||||
before { group.add(user1) }
|
||||
|
||||
it "creates a membership for the user and enqueues a job to update the count" do
|
||||
initial_count = private_category_channel.user_count
|
||||
|
||||
membership = private_category_channel.add(user1)
|
||||
private_category_channel.reload
|
||||
|
||||
expect(membership.following).to eq(true)
|
||||
expect(membership.user).to eq(user1)
|
||||
expect(membership.chat_channel).to eq(private_category_channel)
|
||||
expect(private_category_channel.user_count_stale).to eq(true)
|
||||
expect_job_enqueued(
|
||||
job: :update_channel_user_count,
|
||||
args: {
|
||||
chat_channel_id: private_category_channel.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "updates an existing membership for the user and enqueues a job to update the count" do
|
||||
membership =
|
||||
UserChatChannelMembership.create!(
|
||||
chat_channel: private_category_channel,
|
||||
user: user1,
|
||||
following: false,
|
||||
)
|
||||
|
||||
private_category_channel.add(user1)
|
||||
private_category_channel.reload
|
||||
|
||||
expect(membership.reload.following).to eq(true)
|
||||
expect(private_category_channel.user_count_stale).to eq(true)
|
||||
expect_job_enqueued(
|
||||
job: :update_channel_user_count,
|
||||
args: {
|
||||
chat_channel_id: private_category_channel.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does nothing if the user is already a member" do
|
||||
membership =
|
||||
UserChatChannelMembership.create!(
|
||||
chat_channel: private_category_channel,
|
||||
user: user1,
|
||||
following: true,
|
||||
)
|
||||
|
||||
expect(private_category_channel.user_count_stale).to eq(false)
|
||||
expect_not_enqueued_with(
|
||||
job: :update_channel_user_count,
|
||||
args: {
|
||||
chat_channel_id: private_category_channel.id,
|
||||
},
|
||||
) { private_category_channel.add(user1) }
|
||||
end
|
||||
|
||||
it "does not recalculate user count if it's already been marked as stale" do
|
||||
private_category_channel.update!(user_count_stale: true)
|
||||
expect_not_enqueued_with(
|
||||
job: :update_channel_user_count,
|
||||
args: {
|
||||
chat_channel_id: private_category_channel.id,
|
||||
},
|
||||
) { private_category_channel.add(user1) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#remove" do
|
||||
before do
|
||||
group.add(user1)
|
||||
@membership = private_category_channel.add(user1)
|
||||
private_category_channel.reload
|
||||
private_category_channel.update!(user_count_stale: false)
|
||||
end
|
||||
|
||||
it "updates the membership for the user and decreases the count" do
|
||||
membership = private_category_channel.remove(user1)
|
||||
private_category_channel.reload
|
||||
|
||||
expect(@membership.reload.following).to eq(false)
|
||||
expect(private_category_channel.user_count_stale).to eq(true)
|
||||
expect_job_enqueued(
|
||||
job: :update_channel_user_count,
|
||||
args: {
|
||||
chat_channel_id: private_category_channel.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "returns nil if the user doesn't have a membership" do
|
||||
expect(private_category_channel.remove(user2)).to eq(nil)
|
||||
end
|
||||
|
||||
it "does nothing if the user is not following the channel" do
|
||||
@membership.update!(following: false)
|
||||
|
||||
private_category_channel.remove(user1)
|
||||
private_category_channel.reload
|
||||
|
||||
expect(private_category_channel.user_count_stale).to eq(false)
|
||||
expect_job_enqueued(
|
||||
job: :update_channel_user_count,
|
||||
args: {
|
||||
chat_channel_id: private_category_channel.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not recalculate user count if it's already been marked as stale" do
|
||||
private_category_channel.update!(user_count_stale: true)
|
||||
expect_not_enqueued_with(
|
||||
job: :update_channel_user_count,
|
||||
args: {
|
||||
chat_channel_id: private_category_channel.id,
|
||||
},
|
||||
) { private_category_channel.remove(user1) }
|
||||
end
|
||||
end
|
||||
end
|
24
plugins/chat/spec/support/examples/chatable_model.rb
Normal file
24
plugins/chat/spec/support/examples/chatable_model.rb
Normal file
@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples "a chatable model" do
|
||||
describe "#chat_channel" do
|
||||
subject(:chat_channel) { chatable.chat_channel }
|
||||
|
||||
it "returns a new chat channel model" do
|
||||
expect(chat_channel).to have_attributes persisted?: false,
|
||||
class: channel_class,
|
||||
chatable: chatable
|
||||
end
|
||||
end
|
||||
|
||||
describe "#create_chat_channel!" do
|
||||
subject(:create_chat_channel) { chatable.create_chat_channel!(name: name) }
|
||||
|
||||
let(:name) { "a custom name" }
|
||||
|
||||
it "creates a proper chat channel" do
|
||||
expect { create_chat_channel }.to change { channel_class.count }.by(1)
|
||||
expect(channel_class.last).to have_attributes chatable: chatable, name: name
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user