ChatMessages: Add attachments.
This commit is contained in:
parent
205313e541
commit
20baa2eaf0
|
@ -23,17 +23,28 @@ def create(actor, object, recipients) do
|
||||||
}, []}
|
}, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def chat_message(actor, recipient, content) do
|
def chat_message(actor, recipient, content, opts \\ []) do
|
||||||
{:ok,
|
basic = %{
|
||||||
%{
|
"id" => Utils.generate_object_id(),
|
||||||
"id" => Utils.generate_object_id(),
|
"actor" => actor.ap_id,
|
||||||
"actor" => actor.ap_id,
|
"type" => "ChatMessage",
|
||||||
"type" => "ChatMessage",
|
"to" => [recipient],
|
||||||
"to" => [recipient],
|
"content" => content,
|
||||||
"content" => content,
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
"emoji" => Emoji.Formatter.get_emoji_map(content)
|
||||||
"emoji" => Emoji.Formatter.get_emoji_map(content)
|
}
|
||||||
}, []}
|
|
||||||
|
case opts[:attachment] do
|
||||||
|
%Object{data: attachment_data} ->
|
||||||
|
{
|
||||||
|
:ok,
|
||||||
|
Map.put(basic, "attachment", attachment_data),
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:ok, basic, []}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
||||||
|
|
|
@ -63,11 +63,18 @@ def stringify_keys(%{__struct__: _} = object) do
|
||||||
|> stringify_keys
|
|> stringify_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
def stringify_keys(object) do
|
def stringify_keys(object) when is_map(object) do
|
||||||
object
|
object
|
||||||
|> Map.new(fn {key, val} -> {to_string(key), val} end)
|
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stringify_keys(object) when is_list(object) do
|
||||||
|
object
|
||||||
|
|> Enum.map(&stringify_keys/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def stringify_keys(object), do: object
|
||||||
|
|
||||||
def fetch_actor(object) do
|
def fetch_actor(object) do
|
||||||
with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do
|
with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do
|
||||||
User.get_or_fetch_by_ap_id(actor)
|
User.get_or_fetch_by_ap_id(actor)
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
embedded_schema do
|
||||||
|
field(:type, :string)
|
||||||
|
field(:mediaType, :string)
|
||||||
|
field(:name, :string)
|
||||||
|
|
||||||
|
embeds_many(:url, UrlObjectValidator)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
|> validate_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) do
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> fix_media_type()
|
||||||
|
|> fix_url()
|
||||||
|
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :mediaType, :name])
|
||||||
|
|> cast_embed(:url, required: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_media_type(data) do
|
||||||
|
data
|
||||||
|
|> Map.put_new("mediaType", data["mimeType"])
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_url(data) do
|
||||||
|
case data["url"] do
|
||||||
|
url when is_binary(url) ->
|
||||||
|
data
|
||||||
|
|> Map.put(
|
||||||
|
"url",
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"href" => url,
|
||||||
|
"type" => "Link",
|
||||||
|
"mediaType" => data["mediaType"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(cng) do
|
||||||
|
cng
|
||||||
|
|> validate_required([:mediaType, :url, :type])
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.Transmogrifier, only: [fix_emoji: 1]
|
import Pleroma.Web.ActivityPub.Transmogrifier, only: [fix_emoji: 1]
|
||||||
|
@ -22,6 +23,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
|
||||||
field(:actor, Types.ObjectID)
|
field(:actor, Types.ObjectID)
|
||||||
field(:published, Types.DateTime)
|
field(:published, Types.DateTime)
|
||||||
field(:emoji, :map, default: %{})
|
field(:emoji, :map, default: %{})
|
||||||
|
|
||||||
|
embeds_one(:attachment, AttachmentValidator)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(data) do
|
def cast_and_apply(data) do
|
||||||
|
@ -51,7 +54,8 @@ def changeset(struct, data) do
|
||||||
data = fix(data)
|
data = fix(data)
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, List.delete(__schema__(:fields), :attachment))
|
||||||
|
|> cast_embed(:attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
def validate_data(data_cng) do
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:type, :string)
|
||||||
|
field(:href, Types.Uri)
|
||||||
|
field(:mediaType, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
|> validate_required([:type, :href, :mediaType])
|
||||||
|
end
|
||||||
|
end
|
|
@ -236,7 +236,8 @@ def chat_message_create do
|
||||||
description: "POST body for creating an chat message",
|
description: "POST body for creating an chat message",
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
content: %Schema{type: :string, description: "The content of your message"}
|
content: %Schema{type: :string, description: "The content of your message"},
|
||||||
|
media_id: %Schema{type: :string, description: "The id of an upload"}
|
||||||
},
|
},
|
||||||
required: [:content],
|
required: [:content],
|
||||||
example: %{
|
example: %{
|
||||||
|
|
|
@ -17,7 +17,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
|
||||||
chat_id: %Schema{type: :string},
|
chat_id: %Schema{type: :string},
|
||||||
content: %Schema{type: :string},
|
content: %Schema{type: :string},
|
||||||
created_at: %Schema{type: :string, format: :"date-time"},
|
created_at: %Schema{type: :string, format: :"date-time"},
|
||||||
emojis: %Schema{type: :array}
|
emojis: %Schema{type: :array},
|
||||||
|
attachment: %Schema{type: :object, nullable: true}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
"account_id" => "someflakeid",
|
"account_id" => "someflakeid",
|
||||||
|
@ -32,7 +33,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
|
||||||
"url" => "https://dontbulling.me/emoji/Firefox.gif"
|
"url" => "https://dontbulling.me/emoji/Firefox.gif"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id" => "14"
|
"id" => "14",
|
||||||
|
"attachment" => nil
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,14 +25,16 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def post_chat_message(%User{} = user, %User{} = recipient, content) do
|
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
||||||
with :ok <- validate_chat_content_length(content),
|
with :ok <- validate_chat_content_length(content),
|
||||||
|
maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
||||||
{_, {:ok, chat_message_data, _meta}} <-
|
{_, {:ok, chat_message_data, _meta}} <-
|
||||||
{:build_object,
|
{:build_object,
|
||||||
Builder.chat_message(
|
Builder.chat_message(
|
||||||
user,
|
user,
|
||||||
recipient.ap_id,
|
recipient.ap_id,
|
||||||
content |> Formatter.html_escape("text/plain")
|
content |> Formatter.html_escape("text/plain"),
|
||||||
|
attachment: maybe_attachment
|
||||||
)},
|
)},
|
||||||
{_, {:ok, create_activity_data, _meta}} <-
|
{_, {:ok, create_activity_data, _meta}} <-
|
||||||
{:build_create_activity, Builder.create(user, chat_message_data, [recipient.ap_id])},
|
{:build_create_activity, Builder.create(user, chat_message_data, [recipient.ap_id])},
|
||||||
|
|
|
@ -36,14 +36,16 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
|
||||||
|
|
||||||
def post_chat_message(
|
def post_chat_message(
|
||||||
%{body_params: %{content: content}, assigns: %{user: %{id: user_id} = user}} = conn,
|
%{body_params: %{content: content} = params, assigns: %{user: %{id: user_id} = user}} =
|
||||||
|
conn,
|
||||||
%{
|
%{
|
||||||
id: id
|
id: id
|
||||||
}
|
}
|
||||||
) do
|
) do
|
||||||
with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
|
with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
|
||||||
%User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
|
%User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
|
||||||
{:ok, activity} <- CommonAPI.post_chat_message(user, recipient, content),
|
{:ok, activity} <-
|
||||||
|
CommonAPI.post_chat_message(user, recipient, content, media_id: params[:media_id]),
|
||||||
message <- Object.normalize(activity) do
|
message <- Object.normalize(activity) do
|
||||||
conn
|
conn
|
||||||
|> put_view(ChatMessageView)
|
|> put_view(ChatMessageView)
|
||||||
|
|
|
@ -23,7 +23,10 @@ def render(
|
||||||
chat_id: chat_id |> to_string(),
|
chat_id: chat_id |> to_string(),
|
||||||
account_id: User.get_cached_by_ap_id(chat_message["actor"]).id,
|
account_id: User.get_cached_by_ap_id(chat_message["actor"]).id,
|
||||||
created_at: Utils.to_masto_date(chat_message["published"]),
|
created_at: Utils.to_masto_date(chat_message["published"]),
|
||||||
emojis: StatusView.build_emojis(chat_message["emoji"])
|
emojis: StatusView.build_emojis(chat_message["emoji"]),
|
||||||
|
attachment:
|
||||||
|
chat_message["attachment"] &&
|
||||||
|
StatusView.render("attachment.json", attachment: chat_message["attachment"])
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,41 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Builder
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "attachments" do
|
||||||
|
test "it turns mastodon attachments into our attachments" do
|
||||||
|
attachment = %{
|
||||||
|
"url" =>
|
||||||
|
"http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
|
||||||
|
"type" => "Document",
|
||||||
|
"name" => nil,
|
||||||
|
"mediaType" => "image/jpeg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, attachment} =
|
||||||
|
AttachmentValidator.cast_and_validate(attachment)
|
||||||
|
|> Ecto.Changeset.apply_action(:insert)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
href:
|
||||||
|
"http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
|
||||||
|
type: "Link",
|
||||||
|
mediaType: "image/jpeg"
|
||||||
|
}
|
||||||
|
] = attachment.url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "chat message create activities" do
|
describe "chat message create activities" do
|
||||||
test "it is invalid if the object already exists" do
|
test "it is invalid if the object already exists" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -52,7 +79,28 @@ test "it is invalid if the object data has a different `to` or `actor` field" do
|
||||||
test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
|
test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
|
||||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||||
|
|
||||||
assert object == valid_chat_message
|
assert Map.put(valid_chat_message, "attachment", nil) == object
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validates for a basic object with an attachment", %{
|
||||||
|
valid_chat_message: valid_chat_message,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
valid_chat_message =
|
||||||
|
valid_chat_message
|
||||||
|
|> Map.put("attachment", attachment.data)
|
||||||
|
|
||||||
|
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||||
|
|
||||||
|
assert object["attachment"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "does not validate if the message is longer than the remote_limit", %{
|
test "does not validate if the message is longer than the remote_limit", %{
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do
|
defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
|
||||||
|
|
||||||
alias Pleroma.Chat
|
alias Pleroma.Chat
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
@ -49,6 +50,32 @@ test "it posts a message to the chat", %{conn: conn, user: user} do
|
||||||
assert result["content"] == "Hallo!!"
|
assert result["content"] == "Hallo!!"
|
||||||
assert result["chat_id"] == chat.id |> to_string()
|
assert result["chat_id"] == chat.id |> to_string()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works with an attachment", %{conn: conn, user: user} do
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{
|
||||||
|
"content" => "Hallo!!",
|
||||||
|
"media_id" => to_string(upload.id)
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert result["content"] == "Hallo!!"
|
||||||
|
assert result["chat_id"] == chat.id |> to_string()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET /api/v1/pleroma/chats/:id/messages" do
|
describe "GET /api/v1/pleroma/chats/:id/messages" do
|
||||||
|
|
|
@ -9,12 +9,21 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageViewTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.PleromaAPI.ChatMessageView
|
alias Pleroma.Web.PleromaAPI.ChatMessageView
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
test "it displays a chat message" do
|
test "it displays a chat message" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
recipient = insert(:user)
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
{:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:")
|
{:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:")
|
||||||
|
|
||||||
chat = Chat.get(user.id, recipient.ap_id)
|
chat = Chat.get(user.id, recipient.ap_id)
|
||||||
|
@ -30,7 +39,7 @@ test "it displays a chat message" do
|
||||||
assert chat_message[:created_at]
|
assert chat_message[:created_at]
|
||||||
assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
|
assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post_chat_message(recipient, user, "gkgkgk")
|
{:ok, activity} = CommonAPI.post_chat_message(recipient, user, "gkgkgk", media_id: upload.id)
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
|
@ -40,5 +49,6 @@ test "it displays a chat message" do
|
||||||
assert chat_message_two[:content] == "gkgkgk"
|
assert chat_message_two[:content] == "gkgkgk"
|
||||||
assert chat_message_two[:account_id] == recipient.id
|
assert chat_message_two[:account_id] == recipient.id
|
||||||
assert chat_message_two[:chat_id] == chat_message[:chat_id]
|
assert chat_message_two[:chat_id] == chat_message[:chat_id]
|
||||||
|
assert chat_message_two[:attachment]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue