diff options
Diffstat (limited to 'test')
25 files changed, 1609 insertions, 21 deletions
| diff --git a/test/chat/message_reference_test.exs b/test/chat/message_reference_test.exs new file mode 100644 index 000000000..aaa7c1ad4 --- /dev/null +++ b/test/chat/message_reference_test.exs @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Chat.MessageReferenceTest do +  use Pleroma.DataCase, async: true + +  alias Pleroma.Chat +  alias Pleroma.Chat.MessageReference +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "messages" do +    test "it returns the last message in a chat" do +      user = insert(:user) +      recipient = insert(:user) + +      {:ok, _message_1} = CommonAPI.post_chat_message(user, recipient, "hey") +      {:ok, _message_2} = CommonAPI.post_chat_message(recipient, user, "ho") + +      {:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id) + +      message = MessageReference.last_message_for_chat(chat) + +      assert message.object.data["content"] == "ho" +    end +  end +end diff --git a/test/chat_test.exs b/test/chat_test.exs new file mode 100644 index 000000000..332f2180a --- /dev/null +++ b/test/chat_test.exs @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ChatTest do +  use Pleroma.DataCase, async: true + +  alias Pleroma.Chat + +  import Pleroma.Factory + +  describe "creation and getting" do +    test "it only works if the recipient is a valid user (for now)" do +      user = insert(:user) + +      assert {:error, _chat} = Chat.bump_or_create(user.id, "http://some/nonexisting/account") +      assert {:error, _chat} = Chat.get_or_create(user.id, "http://some/nonexisting/account") +    end + +    test "it creates a chat for a user and recipient" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id) + +      assert chat.id +    end + +    test "it returns and bumps a chat for a user and recipient if it already exists" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id) +      {:ok, chat_two} = Chat.bump_or_create(user.id, other_user.ap_id) + +      assert chat.id == chat_two.id +    end + +    test "it returns a chat for a user and recipient if it already exists" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id) +      {:ok, chat_two} = Chat.get_or_create(user.id, other_user.ap_id) + +      assert chat.id == chat_two.id +    end + +    test "a returning chat will have an updated `update_at` field" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id) +      :timer.sleep(1500) +      {:ok, chat_two} = Chat.bump_or_create(user.id, other_user.ap_id) + +      assert chat.id == chat_two.id +      assert chat.updated_at != chat_two.updated_at +    end +  end +end diff --git a/test/fixtures/create-chat-message.json b/test/fixtures/create-chat-message.json new file mode 100644 index 000000000..9c23a1c9b --- /dev/null +++ b/test/fixtures/create-chat-message.json @@ -0,0 +1,31 @@ +{ +  "actor": "http://2hu.gensokyo/users/raymoo", +  "id": "http://2hu.gensokyo/objects/1", +  "object": { +    "attributedTo": "http://2hu.gensokyo/users/raymoo", +    "content": "You expected a cute girl? Too bad. <script>alert('XSS')</script>", +    "id": "http://2hu.gensokyo/objects/2", +    "published": "2020-02-12T14:08:20Z", +    "to": [ +      "http://2hu.gensokyo/users/marisa" +    ], +    "tag": [ +      { +        "icon": { +          "type": "Image", +          "url": "http://2hu.gensokyo/emoji/Firefox.gif" +        }, +        "id": "http://2hu.gensokyo/emoji/Firefox.gif", +        "name": ":firefox:", +        "type": "Emoji", +        "updated": "1970-01-01T00:00:00Z" +      } +    ], +    "type": "ChatMessage" +  }, +  "published": "2018-02-12T14:08:20Z", +  "to": [ +    "http://2hu.gensokyo/users/marisa" +  ], +  "type": "Create" +} diff --git a/test/migration_helper/notification_backfill_test.exs b/test/migration_helper/notification_backfill_test.exs new file mode 100644 index 000000000..2a62a2b00 --- /dev/null +++ b/test/migration_helper/notification_backfill_test.exs @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MigrationHelper.NotificationBackfillTest do +  use Pleroma.DataCase + +  alias Pleroma.Activity +  alias Pleroma.MigrationHelper.NotificationBackfill +  alias Pleroma.Notification +  alias Pleroma.Repo +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "fill_in_notification_types" do +    test "it fills in missing notification types" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, post} = CommonAPI.post(user, %{status: "yeah, @#{other_user.nickname}"}) +      {:ok, chat} = CommonAPI.post_chat_message(user, other_user, "yo") +      {:ok, react} = CommonAPI.react_with_emoji(post.id, other_user, "☕") +      {:ok, like} = CommonAPI.favorite(other_user, post.id) +      {:ok, react_2} = CommonAPI.react_with_emoji(post.id, other_user, "☕") + +      data = +        react_2.data +        |> Map.put("type", "EmojiReaction") + +      {:ok, react_2} = +        react_2 +        |> Activity.change(%{data: data}) +        |> Repo.update() + +      assert {5, nil} = Repo.update_all(Notification, set: [type: nil]) + +      NotificationBackfill.fill_in_notification_types() + +      assert %{type: "mention"} = +               Repo.get_by(Notification, user_id: other_user.id, activity_id: post.id) + +      assert %{type: "favourite"} = +               Repo.get_by(Notification, user_id: user.id, activity_id: like.id) + +      assert %{type: "pleroma:emoji_reaction"} = +               Repo.get_by(Notification, user_id: user.id, activity_id: react.id) + +      assert %{type: "pleroma:emoji_reaction"} = +               Repo.get_by(Notification, user_id: user.id, activity_id: react_2.id) + +      assert %{type: "pleroma:chat_mention"} = +               Repo.get_by(Notification, user_id: other_user.id, activity_id: chat.id) +    end +  end +end diff --git a/test/notification_test.exs b/test/notification_test.exs index 37c255fee..b9bbdceca 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -10,6 +10,7 @@ defmodule Pleroma.NotificationTest do    alias Pleroma.FollowingRelationship    alias Pleroma.Notification +  alias Pleroma.Repo    alias Pleroma.Tests.ObanHelpers    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub @@ -31,6 +32,7 @@ defmodule Pleroma.NotificationTest do        {:ok, [notification]} = Notification.create_notifications(activity)        assert notification.user_id == user.id +      assert notification.type == "pleroma:emoji_reaction"      end      test "notifies someone when they are directly addressed" do @@ -48,6 +50,7 @@ defmodule Pleroma.NotificationTest do        notified_ids = Enum.sort([notification.user_id, other_notification.user_id])        assert notified_ids == [other_user.id, third_user.id]        assert notification.activity_id == activity.id +      assert notification.type == "mention"        assert other_notification.activity_id == activity.id        assert [%Pleroma.Marker{unread_count: 2}] = @@ -335,9 +338,12 @@ defmodule Pleroma.NotificationTest do        # After request is accepted, the same notification is rendered with type "follow":        assert {:ok, _} = CommonAPI.accept_follow_request(user, followed_user) -      notification_id = notification.id -      assert [%{id: ^notification_id}] = Notification.for_user(followed_user) -      assert %{type: "follow"} = NotificationView.render("show.json", render_opts) +      notification = +        Repo.get(Notification, notification.id) +        |> Repo.preload(:activity) + +      assert %{type: "follow"} = +               NotificationView.render("show.json", notification: notification, for: followed_user)      end      test "it doesn't create a notification for follow-unfollow-follow chains" do diff --git a/test/upload_test.exs b/test/upload_test.exs index 060a940bb..2abf0edec 100644 --- a/test/upload_test.exs +++ b/test/upload_test.exs @@ -54,6 +54,7 @@ defmodule Pleroma.UploadTest do                  %{                    "name" => "image.jpg",                    "type" => "Document", +                  "mediaType" => "image/jpeg",                    "url" => [                      %{                        "href" => "http://localhost:4001/media/post-process-file.jpg", diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 7953eecf2..31224abe0 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -2,14 +2,264 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do    use Pleroma.DataCase    alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.ActivityPub.ObjectValidator +  alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  describe "attachments" do +    test "works with honkerific attachments" do +      attachment = %{ +        "mediaType" => "", +        "name" => "", +        "summary" => "298p3RG7j27tfsZ9RQ.jpg", +        "type" => "Document", +        "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg" +      } + +      assert {:ok, attachment} = +               AttachmentValidator.cast_and_validate(attachment) +               |> Ecto.Changeset.apply_action(:insert) + +      assert attachment.mediaType == "application/octet-stream" +    end + +    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 + +      assert attachment.mediaType == "image/jpeg" +    end + +    test "it handles our own uploads" do +      user = insert(:user) + +      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) + +      {:ok, attachment} = +        attachment.data +        |> AttachmentValidator.cast_and_validate() +        |> Ecto.Changeset.apply_action(:insert) + +      assert attachment.mediaType == "image/jpeg" +    end +  end + +  describe "chat message create activities" do +    test "it is invalid if the object already exists" do +      user = insert(:user) +      recipient = insert(:user) +      {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey") +      object = Object.normalize(activity, false) + +      {:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id]) + +      {:error, cng} = ObjectValidator.validate(create_data, []) + +      assert {:object, {"The object to create already exists", []}} in cng.errors +    end + +    test "it is invalid if the object data has a different `to` or `actor` field" do +      user = insert(:user) +      recipient = insert(:user) +      {:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey") + +      {:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id]) + +      {:error, cng} = ObjectValidator.validate(create_data, []) + +      assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors +      assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors +    end +  end + +  describe "chat messages" do +    setup do +      clear_config([:instance, :remote_limit]) +      user = insert(:user) +      recipient = insert(:user, local: false) + +      {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:") + +      %{user: user, recipient: recipient, valid_chat_message: valid_chat_message} +    end + +    test "let's through some basic html", %{user: user, recipient: recipient} do +      {:ok, valid_chat_message, _} = +        Builder.chat_message( +          user, +          recipient.ap_id, +          "hey <a href='https://example.org'>example</a> <script>alert('uguu')</script>" +        ) + +      assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) + +      assert object["content"] == +               "hey <a href=\"https://example.org\">example</a> alert('uguu')" +    end + +    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 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 + +    test "validates for a basic object with an attachment in an array", %{ +      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 + +    test "validates for a basic object with an attachment but without content", %{ +      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) +        |> Map.delete("content") + +      assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) + +      assert object["attachment"] +    end + +    test "does not validate if the message has no content", %{ +      valid_chat_message: valid_chat_message +    } do +      contentless = +        valid_chat_message +        |> Map.delete("content") + +      refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, [])) +    end + +    test "does not validate if the message is longer than the remote_limit", %{ +      valid_chat_message: valid_chat_message +    } do +      Pleroma.Config.put([:instance, :remote_limit], 2) +      refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, [])) +    end + +    test "does not validate if the recipient is blocking the actor", %{ +      valid_chat_message: valid_chat_message, +      user: user, +      recipient: recipient +    } do +      Pleroma.User.block(recipient, user) +      refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, [])) +    end + +    test "does not validate if the actor or the recipient is not in our system", %{ +      valid_chat_message: valid_chat_message +    } do +      chat_message = +        valid_chat_message +        |> Map.put("actor", "https://raymoo.com/raymoo") + +      {:error, _} = ObjectValidator.validate(chat_message, []) + +      chat_message = +        valid_chat_message +        |> Map.put("to", ["https://raymoo.com/raymoo"]) + +      {:error, _} = ObjectValidator.validate(chat_message, []) +    end + +    test "does not validate for a message with multiple recipients", %{ +      valid_chat_message: valid_chat_message, +      user: user, +      recipient: recipient +    } do +      chat_message = +        valid_chat_message +        |> Map.put("to", [user.ap_id, recipient.ap_id]) + +      assert {:error, _} = ObjectValidator.validate(chat_message, []) +    end + +    test "does not validate if it doesn't concern local users" do +      user = insert(:user, local: false) +      recipient = insert(:user, local: false) + +      {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey") +      assert {:error, _} = ObjectValidator.validate(valid_chat_message, []) +    end +  end +    describe "EmojiReacts" do      setup do        user = insert(:user) diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs index 834213182..c8911948e 100644 --- a/test/web/activity_pub/object_validators/types/object_id_test.exs +++ b/test/web/activity_pub/object_validators/types/object_id_test.exs @@ -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    alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID    use Pleroma.DataCase diff --git a/test/web/activity_pub/object_validators/types/safe_text_test.exs b/test/web/activity_pub/object_validators/types/safe_text_test.exs new file mode 100644 index 000000000..d4a574554 --- /dev/null +++ b/test/web/activity_pub/object_validators/types/safe_text_test.exs @@ -0,0 +1,30 @@ +# 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.Types.SafeTextTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.ObjectValidators.Types.SafeText + +  test "it lets normal text go through" do +    text = "hey how are you" +    assert {:ok, text} == SafeText.cast(text) +  end + +  test "it removes html tags from text" do +    text = "hey look xss <script>alert('foo')</script>" +    assert {:ok, "hey look xss alert('foo')"} == SafeText.cast(text) +  end + +  test "it keeps basic html tags" do +    text = "hey <a href='http://gensokyo.2hu'>look</a> xss <script>alert('foo')</script>" + +    assert {:ok, "hey <a href=\"http://gensokyo.2hu\">look</a> xss alert('foo')"} == +             SafeText.cast(text) +  end + +  test "errors for non-text" do +    assert :error == SafeText.cast(1) +  end +end diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs index 26557720b..8deb64501 100644 --- a/test/web/activity_pub/pipeline_test.exs +++ b/test/web/activity_pub/pipeline_test.exs @@ -33,7 +33,10 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do          {            Pleroma.Web.ActivityPub.SideEffects,            [], -          [handle: fn o, m -> {:ok, o, m} end] +          [ +            handle: fn o, m -> {:ok, o, m} end, +            handle_after_transaction: fn m -> m end +          ]          },          {            Pleroma.Web.Federator, @@ -71,7 +74,7 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do          {            Pleroma.Web.ActivityPub.SideEffects,            [], -          [handle: fn o, m -> {:ok, o, m} end] +          [handle: fn o, m -> {:ok, o, m} end, handle_after_transaction: fn m -> m end]          },          {            Pleroma.Web.Federator, @@ -110,7 +113,7 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do          {            Pleroma.Web.ActivityPub.SideEffects,            [], -          [handle: fn o, m -> {:ok, o, m} end] +          [handle: fn o, m -> {:ok, o, m} end, handle_after_transaction: fn m -> m end]          },          {            Pleroma.Web.Federator, diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index a80104ea7..6bbbaae87 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -7,6 +7,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do    use Pleroma.DataCase    alias Pleroma.Activity +  alias Pleroma.Chat +  alias Pleroma.Chat.MessageReference    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo @@ -20,6 +22,48 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do    import Pleroma.Factory    import Mock +  describe "handle_after_transaction" do +    test "it streams out notifications and streams" do +      author = insert(:user, local: true) +      recipient = insert(:user, local: true) + +      {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey") + +      {:ok, create_activity_data, _meta} = +        Builder.create(author, chat_message_data["id"], [recipient.ap_id]) + +      {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + +      {:ok, _create_activity, meta} = +        SideEffects.handle(create_activity, local: false, object_data: chat_message_data) + +      assert [notification] = meta[:notifications] + +      with_mocks([ +        { +          Pleroma.Web.Streamer, +          [], +          [ +            stream: fn _, _ -> nil end +          ] +        }, +        { +          Pleroma.Web.Push, +          [], +          [ +            send: fn _ -> nil end +          ] +        } +      ]) do +        SideEffects.handle_after_transaction(meta) + +        assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification)) +        assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_)) +        assert called(Pleroma.Web.Push.send(notification)) +      end +    end +  end +    describe "delete objects" do      setup do        user = insert(:user) @@ -290,6 +334,147 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do      end    end +  describe "creation of ChatMessages" do +    test "notifies the recipient" do +      author = insert(:user, local: false) +      recipient = insert(:user, local: true) + +      {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey") + +      {:ok, create_activity_data, _meta} = +        Builder.create(author, chat_message_data["id"], [recipient.ap_id]) + +      {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + +      {:ok, _create_activity, _meta} = +        SideEffects.handle(create_activity, local: false, object_data: chat_message_data) + +      assert Repo.get_by(Notification, user_id: recipient.id, activity_id: create_activity.id) +    end + +    test "it streams the created ChatMessage" do +      author = insert(:user, local: true) +      recipient = insert(:user, local: true) + +      {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey") + +      {:ok, create_activity_data, _meta} = +        Builder.create(author, chat_message_data["id"], [recipient.ap_id]) + +      {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + +      {:ok, _create_activity, meta} = +        SideEffects.handle(create_activity, local: false, object_data: chat_message_data) + +      assert [_, _] = meta[:streamables] +    end + +    test "it creates a Chat and MessageReferences for the local users and bumps the unread count, except for the author" do +      author = insert(:user, local: true) +      recipient = insert(:user, local: true) + +      {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey") + +      {:ok, create_activity_data, _meta} = +        Builder.create(author, chat_message_data["id"], [recipient.ap_id]) + +      {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + +      with_mocks([ +        { +          Pleroma.Web.Streamer, +          [], +          [ +            stream: fn _, _ -> nil end +          ] +        }, +        { +          Pleroma.Web.Push, +          [], +          [ +            send: fn _ -> nil end +          ] +        } +      ]) do +        {:ok, _create_activity, meta} = +          SideEffects.handle(create_activity, local: false, object_data: chat_message_data) + +        # The notification gets created +        assert [notification] = meta[:notifications] +        assert notification.activity_id == create_activity.id + +        # But it is not sent out +        refute called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification)) +        refute called(Pleroma.Web.Push.send(notification)) + +        # Same for the user chat stream +        assert [{topics, _}, _] = meta[:streamables] +        assert topics == ["user", "user:pleroma_chat"] +        refute called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_)) + +        chat = Chat.get(author.id, recipient.ap_id) + +        [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all() + +        assert cm_ref.object.data["content"] == "hey" +        assert cm_ref.unread == false + +        chat = Chat.get(recipient.id, author.ap_id) + +        [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all() + +        assert cm_ref.object.data["content"] == "hey" +        assert cm_ref.unread == true +      end +    end + +    test "it creates a Chat for the local users and bumps the unread count" do +      author = insert(:user, local: false) +      recipient = insert(:user, local: true) + +      {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey") + +      {:ok, create_activity_data, _meta} = +        Builder.create(author, chat_message_data["id"], [recipient.ap_id]) + +      {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + +      {:ok, _create_activity, _meta} = +        SideEffects.handle(create_activity, local: false, object_data: chat_message_data) + +      # An object is created +      assert Object.get_by_ap_id(chat_message_data["id"]) + +      # The remote user won't get a chat +      chat = Chat.get(author.id, recipient.ap_id) +      refute chat + +      # The local user will get a chat +      chat = Chat.get(recipient.id, author.ap_id) +      assert chat + +      author = insert(:user, local: true) +      recipient = insert(:user, local: true) + +      {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey") + +      {:ok, create_activity_data, _meta} = +        Builder.create(author, chat_message_data["id"], [recipient.ap_id]) + +      {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + +      {:ok, _create_activity, _meta} = +        SideEffects.handle(create_activity, local: false, object_data: chat_message_data) + +      # Both users are local and get the chat +      chat = Chat.get(author.id, recipient.ap_id) +      assert chat + +      chat = Chat.get(recipient.id, author.ap_id) +      assert chat +    end +  end +    describe "announce objects" do      setup do        poster = insert(:user) diff --git a/test/web/activity_pub/transmogrifier/chat_message_test.exs b/test/web/activity_pub/transmogrifier/chat_message_test.exs new file mode 100644 index 000000000..d6736dc3e --- /dev/null +++ b/test/web/activity_pub/transmogrifier/chat_message_test.exs @@ -0,0 +1,153 @@ +# 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.Transmogrifier.ChatMessageTest do +  use Pleroma.DataCase + +  import Pleroma.Factory + +  alias Pleroma.Activity +  alias Pleroma.Chat +  alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.Transmogrifier + +  describe "handle_incoming" do +    test "handles chonks with attachment" do +      data = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "actor" => "https://honk.tedunangst.com/u/tedu", +        "id" => "https://honk.tedunangst.com/u/tedu/honk/x6gt8X8PcyGkQcXxzg1T", +        "object" => %{ +          "attachment" => [ +            %{ +              "mediaType" => "image/jpeg", +              "name" => "298p3RG7j27tfsZ9RQ.jpg", +              "summary" => "298p3RG7j27tfsZ9RQ.jpg", +              "type" => "Document", +              "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg" +            } +          ], +          "attributedTo" => "https://honk.tedunangst.com/u/tedu", +          "content" => "", +          "id" => "https://honk.tedunangst.com/u/tedu/chonk/26L4wl5yCbn4dr4y1b", +          "published" => "2020-05-18T01:13:03Z", +          "to" => [ +            "https://dontbulling.me/users/lain" +          ], +          "type" => "ChatMessage" +        }, +        "published" => "2020-05-18T01:13:03Z", +        "to" => [ +          "https://dontbulling.me/users/lain" +        ], +        "type" => "Create" +      } + +      _user = insert(:user, ap_id: data["actor"]) +      _user = insert(:user, ap_id: hd(data["to"])) + +      assert {:ok, _activity} = Transmogrifier.handle_incoming(data) +    end + +    test "it rejects messages that don't contain content" do +      data = +        File.read!("test/fixtures/create-chat-message.json") +        |> Poison.decode!() + +      object = +        data["object"] +        |> Map.delete("content") + +      data = +        data +        |> Map.put("object", object) + +      _author = +        insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now()) + +      _recipient = +        insert(:user, +          ap_id: List.first(data["to"]), +          local: true, +          last_refreshed_at: DateTime.utc_now() +        ) + +      {:error, _} = Transmogrifier.handle_incoming(data) +    end + +    test "it rejects messages that don't concern local users" do +      data = +        File.read!("test/fixtures/create-chat-message.json") +        |> Poison.decode!() + +      _author = +        insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now()) + +      _recipient = +        insert(:user, +          ap_id: List.first(data["to"]), +          local: false, +          last_refreshed_at: DateTime.utc_now() +        ) + +      {:error, _} = Transmogrifier.handle_incoming(data) +    end + +    test "it rejects messages where the `to` field of activity and object don't match" do +      data = +        File.read!("test/fixtures/create-chat-message.json") +        |> Poison.decode!() + +      author = insert(:user, ap_id: data["actor"]) +      _recipient = insert(:user, ap_id: List.first(data["to"])) + +      data = +        data +        |> Map.put("to", author.ap_id) + +      assert match?({:error, _}, Transmogrifier.handle_incoming(data)) +      refute Object.get_by_ap_id(data["object"]["id"]) +    end + +    test "it fetches the actor if they aren't in our system" do +      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + +      data = +        File.read!("test/fixtures/create-chat-message.json") +        |> Poison.decode!() +        |> Map.put("actor", "http://mastodon.example.org/users/admin") +        |> put_in(["object", "actor"], "http://mastodon.example.org/users/admin") + +      _recipient = insert(:user, ap_id: List.first(data["to"]), local: true) + +      {:ok, %Activity{} = _activity} = Transmogrifier.handle_incoming(data) +    end + +    test "it inserts it and creates a chat" do +      data = +        File.read!("test/fixtures/create-chat-message.json") +        |> Poison.decode!() + +      author = +        insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now()) + +      recipient = insert(:user, ap_id: List.first(data["to"]), local: true) + +      {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data) +      assert activity.local == false + +      assert activity.actor == author.ap_id +      assert activity.recipients == [recipient.ap_id, author.ap_id] + +      %Object{} = object = Object.get_by_ap_id(activity.data["object"]) + +      assert object +      assert object.data["content"] == "You expected a cute girl? Too bad. alert('XSS')" +      assert match?(%{"firefox" => _}, object.data["emoji"]) + +      refute Chat.get(author.id, recipient.ap_id) +      assert Chat.get(recipient.id, author.ap_id) +    end +  end +end diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/web/activity_pub/transmogrifier/follow_handling_test.exs index 967389fae..06c39eed6 100644 --- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs @@ -5,6 +5,7 @@  defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do    use Pleroma.DataCase    alias Pleroma.Activity +  alias Pleroma.Notification    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.ActivityPub.Transmogrifier @@ -12,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do    import Pleroma.Factory    import Ecto.Query +  import Mock    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -57,9 +59,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do        activity = Repo.get(Activity, activity.id)        assert activity.data["state"] == "accept"        assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) + +      [notification] = Notification.for_user(user) +      assert notification.type == "follow"      end -    test "with locked accounts, it does not create a follow or an accept" do +    test "with locked accounts, it does create a Follow, but not an Accept" do        user = insert(:user, locked: true)        data = @@ -81,6 +86,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do          |> Repo.all()        assert Enum.empty?(accepts) + +      [notification] = Notification.for_user(user) +      assert notification.type == "follow_request"      end      test "it works for follow requests when you are already followed, creating a new accept activity" do @@ -144,6 +152,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do        assert activity.data["state"] == "reject"      end +    test "it rejects incoming follow requests if the following errors for some reason" do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-follow-activity.json") +        |> Poison.decode!() +        |> Map.put("object", user.ap_id) + +      with_mock Pleroma.User, [:passthrough], follow: fn _, _ -> {:error, :testing} end do +        {:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data) + +        %Activity{} = activity = Activity.get_by_ap_id(id) + +        assert activity.data["state"] == "reject" +      end +    end +      test "it works for incoming follow requests from hubzilla" do        user = insert(:user) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 2291f76dd..6bd26050e 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -5,7 +5,9 @@  defmodule Pleroma.Web.CommonAPITest do    use Pleroma.DataCase    alias Pleroma.Activity +  alias Pleroma.Chat    alias Pleroma.Conversation.Participation +  alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub @@ -23,6 +25,150 @@ defmodule Pleroma.Web.CommonAPITest do    setup do: clear_config([:instance, :limit])    setup do: clear_config([:instance, :max_pinned_statuses]) +  describe "posting chat messages" do +    setup do: clear_config([:instance, :chat_limit]) + +    test "it posts a chat message without content but with an attachment" do +      author = 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: author.ap_id) + +      with_mocks([ +        { +          Pleroma.Web.Streamer, +          [], +          [ +            stream: fn _, _ -> +              nil +            end +          ] +        }, +        { +          Pleroma.Web.Push, +          [], +          [ +            send: fn _ -> nil end +          ] +        } +      ]) do +        {:ok, activity} = +          CommonAPI.post_chat_message( +            author, +            recipient, +            nil, +            media_id: upload.id +          ) + +        notification = +          Notification.for_user_and_activity(recipient, activity) +          |> Repo.preload(:activity) + +        assert called(Pleroma.Web.Push.send(notification)) +        assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification)) +        assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_)) + +        assert activity +      end +    end + +    test "it adds html newlines" do +      author = insert(:user) +      recipient = insert(:user) + +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post_chat_message( +          author, +          recipient, +          "uguu\nuguuu" +        ) + +      assert other_user.ap_id not in activity.recipients + +      object = Object.normalize(activity, false) + +      assert object.data["content"] == "uguu<br/>uguuu" +    end + +    test "it linkifies" do +      author = insert(:user) +      recipient = insert(:user) + +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post_chat_message( +          author, +          recipient, +          "https://example.org is the site of @#{other_user.nickname} #2hu" +        ) + +      assert other_user.ap_id not in activity.recipients + +      object = Object.normalize(activity, false) + +      assert object.data["content"] == +               "<a href=\"https://example.org\" rel=\"ugc\">https://example.org</a> is the site of <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{ +                 other_user.id +               }\" href=\"#{other_user.ap_id}\" rel=\"ugc\">@<span>#{other_user.nickname}</span></a></span> <a class=\"hashtag\" data-tag=\"2hu\" href=\"http://localhost:4001/tag/2hu\">#2hu</a>" +    end + +    test "it posts a chat message" do +      author = insert(:user) +      recipient = insert(:user) + +      {:ok, activity} = +        CommonAPI.post_chat_message( +          author, +          recipient, +          "a test message <script>alert('uuu')</script> :firefox:" +        ) + +      assert activity.data["type"] == "Create" +      assert activity.local +      object = Object.normalize(activity) + +      assert object.data["type"] == "ChatMessage" +      assert object.data["to"] == [recipient.ap_id] + +      assert object.data["content"] == +               "a test message <script>alert('uuu')</script> :firefox:" + +      assert object.data["emoji"] == %{ +               "firefox" => "http://localhost:4001/emoji/Firefox.gif" +             } + +      assert Chat.get(author.id, recipient.ap_id) +      assert Chat.get(recipient.id, author.ap_id) + +      assert :ok == Pleroma.Web.Federator.perform(:publish, activity) +    end + +    test "it reject messages over the local limit" do +      Pleroma.Config.put([:instance, :chat_limit], 2) + +      author = insert(:user) +      recipient = insert(:user) + +      {:error, message} = +        CommonAPI.post_chat_message( +          author, +          recipient, +          "123" +        ) + +      assert message == :content_too_long +    end +  end +    describe "unblocking" do      test "it works even without an existing block activity" do        blocked = insert(:user) diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index e278d61f5..698c99711 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -54,6 +54,27 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do      assert response == expected_response    end +  test "by default, does not contain pleroma:chat_mention" do +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    other_user = insert(:user) + +    {:ok, _activity} = CommonAPI.post_chat_message(other_user, user, "hey") + +    result = +      conn +      |> get("/api/v1/notifications") +      |> json_response_and_validate_schema(200) + +    assert [] == result + +    result = +      conn +      |> get("/api/v1/notifications?include_types[]=pleroma:chat_mention") +      |> json_response_and_validate_schema(200) + +    assert [_] = result +  end +    test "getting a single notification" do      %{user: user, conn: conn} = oauth_access(["read:notifications"])      other_user = insert(:user) diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs index 84d46895e..0e025adca 100644 --- a/test/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/web/mastodon_api/controllers/search_controller_test.exs @@ -111,6 +111,15 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do                 %{"name" => "prone", "url" => "#{Web.base_url()}/tag/prone"},                 %{"name" => "AccidentProne", "url" => "#{Web.base_url()}/tag/AccidentProne"}               ] + +      results = +        conn +        |> get("/api/v2/search?#{URI.encode_query(%{q: "https://shpposter.club/users/shpuld"})}") +        |> json_response_and_validate_schema(200) + +      assert results["hashtags"] == [ +               %{"name" => "shpuld", "url" => "#{Web.base_url()}/tag/shpuld"} +             ]      end      test "excludes a blocked users from search results", %{conn: conn} do diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs index 4aa260663..d36bb1ae8 100644 --- a/test/web/mastodon_api/controllers/subscription_controller_test.exs +++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs @@ -58,7 +58,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        result =          conn          |> post("/api/v1/push/subscription", %{ -          "data" => %{"alerts" => %{"mention" => true, "test" => true}}, +          "data" => %{ +            "alerts" => %{"mention" => true, "test" => true, "pleroma:chat_mention" => true} +          },            "subscription" => @sub          })          |> json_response_and_validate_schema(200) @@ -66,7 +68,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        [subscription] = Pleroma.Repo.all(Subscription)        assert %{ -               "alerts" => %{"mention" => true}, +               "alerts" => %{"mention" => true, "pleroma:chat_mention" => true},                 "endpoint" => subscription.endpoint,                 "id" => to_string(subscription.id),                 "server_key" => @server_key diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index f91333e5c..044f088a4 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -72,6 +72,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          fields: []        },        pleroma: %{ +        ap_id: user.ap_id,          background_image: "https://example.com/images/asuka_hospital.png",          confirmation_pending: false,          tags: [], @@ -148,6 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          fields: []        },        pleroma: %{ +        ap_id: user.ap_id,          background_image: nil,          confirmation_pending: false,          tags: [], diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index f15be1df1..b2fa5b302 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -6,7 +6,10 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do    use Pleroma.DataCase    alias Pleroma.Activity +  alias Pleroma.Chat +  alias Pleroma.Chat.MessageReference    alias Pleroma.Notification +  alias Pleroma.Object    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.CommonAPI @@ -14,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.NotificationView    alias Pleroma.Web.MastodonAPI.StatusView +  alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView    import Pleroma.Factory    defp test_notifications_rendering(notifications, user, expected_result) do @@ -31,6 +35,30 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do      assert expected_result == result    end +  test "ChatMessage notification" do +    user = insert(:user) +    recipient = insert(:user) +    {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "what's up my dude") + +    {:ok, [notification]} = Notification.create_notifications(activity) + +    object = Object.normalize(activity) +    chat = Chat.get(recipient.id, user.ap_id) + +    cm_ref = MessageReference.for_chat_and_object(chat, object) + +    expected = %{ +      id: to_string(notification.id), +      pleroma: %{is_seen: false}, +      type: "pleroma:chat_mention", +      account: AccountView.render("show.json", %{user: user, for: recipient}), +      chat_message: MessageReferenceView.render("show.json", %{chat_message_reference: cm_ref}), +      created_at: Utils.to_masto_date(notification.inserted_at) +    } + +    test_notifications_rendering([notification], recipient, [expected]) +  end +    test "Mention notification" do      user = insert(:user)      mentioned_user = insert(:user) diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index 9bcc07b37..00925caad 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -145,7 +145,8 @@ defmodule Pleroma.Web.NodeInfoTest do        "shareable_emoji_packs",        "multifetch",        "pleroma_emoji_reactions", -      "pleroma:api/v1/notifications:include_types_filter" +      "pleroma:api/v1/notifications:include_types_filter", +      "pleroma_chat_messages"      ]      assert MapSet.subset?( diff --git a/test/web/pleroma_api/controllers/chat_controller_test.exs b/test/web/pleroma_api/controllers/chat_controller_test.exs new file mode 100644 index 000000000..82e16741d --- /dev/null +++ b/test/web/pleroma_api/controllers/chat_controller_test.exs @@ -0,0 +1,336 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  alias Pleroma.Chat +  alias Pleroma.Chat.MessageReference +  alias Pleroma.Object +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "POST /api/v1/pleroma/chats/:id/messages/:message_id/read" do +    setup do: oauth_access(["write:chats"]) + +    test "it marks one message as read", %{conn: conn, user: user} do +      other_user = insert(:user) + +      {:ok, create} = CommonAPI.post_chat_message(other_user, user, "sup") +      {:ok, _create} = CommonAPI.post_chat_message(other_user, user, "sup part 2") +      {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id) +      object = Object.normalize(create, false) +      cm_ref = MessageReference.for_chat_and_object(chat, object) + +      assert cm_ref.unread == true + +      result = +        conn +        |> post("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}/read") +        |> json_response_and_validate_schema(200) + +      assert result["unread"] == false + +      cm_ref = MessageReference.for_chat_and_object(chat, object) + +      assert cm_ref.unread == false +    end +  end + +  describe "POST /api/v1/pleroma/chats/:id/read" do +    setup do: oauth_access(["write:chats"]) + +    test "given a `last_read_id`, it marks everything until then as read", %{ +      conn: conn, +      user: user +    } do +      other_user = insert(:user) + +      {:ok, create} = CommonAPI.post_chat_message(other_user, user, "sup") +      {:ok, _create} = CommonAPI.post_chat_message(other_user, user, "sup part 2") +      {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id) +      object = Object.normalize(create, false) +      cm_ref = MessageReference.for_chat_and_object(chat, object) + +      assert cm_ref.unread == true + +      result = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/v1/pleroma/chats/#{chat.id}/read", %{"last_read_id" => cm_ref.id}) +        |> json_response_and_validate_schema(200) + +      assert result["unread"] == 1 + +      cm_ref = MessageReference.for_chat_and_object(chat, object) + +      assert cm_ref.unread == false +    end +  end + +  describe "POST /api/v1/pleroma/chats/:id/messages" do +    setup do: oauth_access(["write:chats"]) + +    test "it posts a message to the chat", %{conn: conn, user: user} do +      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!!"}) +        |> json_response_and_validate_schema(200) + +      assert result["content"] == "Hallo!!" +      assert result["chat_id"] == chat.id |> to_string() +    end + +    test "it fails if there is no content", %{conn: conn, user: user} do +      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") +        |> json_response_and_validate_schema(400) + +      assert result +    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", %{ +          "media_id" => to_string(upload.id) +        }) +        |> json_response_and_validate_schema(200) + +      assert result["attachment"] +    end +  end + +  describe "DELETE /api/v1/pleroma/chats/:id/messages/:message_id" do +    setup do: oauth_access(["write:chats"]) + +    test "it deletes a message from the chat", %{conn: conn, user: user} do +      recipient = insert(:user) + +      {:ok, message} = +        CommonAPI.post_chat_message(user, recipient, "Hello darkness my old friend") + +      {:ok, other_message} = CommonAPI.post_chat_message(recipient, user, "nico nico ni") + +      object = Object.normalize(message, false) + +      chat = Chat.get(user.id, recipient.ap_id) + +      cm_ref = MessageReference.for_chat_and_object(chat, object) + +      # Deleting your own message removes the message and the reference +      result = +        conn +        |> put_req_header("content-type", "application/json") +        |> delete("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}") +        |> json_response_and_validate_schema(200) + +      assert result["id"] == cm_ref.id +      refute MessageReference.get_by_id(cm_ref.id) +      assert %{data: %{"type" => "Tombstone"}} = Object.get_by_id(object.id) + +      # Deleting other people's messages just removes the reference +      object = Object.normalize(other_message, false) +      cm_ref = MessageReference.for_chat_and_object(chat, object) + +      result = +        conn +        |> put_req_header("content-type", "application/json") +        |> delete("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}") +        |> json_response_and_validate_schema(200) + +      assert result["id"] == cm_ref.id +      refute MessageReference.get_by_id(cm_ref.id) +      assert Object.get_by_id(object.id) +    end +  end + +  describe "GET /api/v1/pleroma/chats/:id/messages" do +    setup do: oauth_access(["read:chats"]) + +    test "it paginates", %{conn: conn, user: user} do +      recipient = insert(:user) + +      Enum.each(1..30, fn _ -> +        {:ok, _} = CommonAPI.post_chat_message(user, recipient, "hey") +      end) + +      chat = Chat.get(user.id, recipient.ap_id) + +      result = +        conn +        |> get("/api/v1/pleroma/chats/#{chat.id}/messages") +        |> json_response_and_validate_schema(200) + +      assert length(result) == 20 + +      result = +        conn +        |> get("/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}") +        |> json_response_and_validate_schema(200) + +      assert length(result) == 10 +    end + +    test "it returns the messages for a given chat", %{conn: conn, user: user} do +      other_user = insert(:user) +      third_user = insert(:user) + +      {:ok, _} = CommonAPI.post_chat_message(user, other_user, "hey") +      {:ok, _} = CommonAPI.post_chat_message(user, third_user, "hey") +      {:ok, _} = CommonAPI.post_chat_message(user, other_user, "how are you?") +      {:ok, _} = CommonAPI.post_chat_message(other_user, user, "fine, how about you?") + +      chat = Chat.get(user.id, other_user.ap_id) + +      result = +        conn +        |> get("/api/v1/pleroma/chats/#{chat.id}/messages") +        |> json_response_and_validate_schema(200) + +      result +      |> Enum.each(fn message -> +        assert message["chat_id"] == chat.id |> to_string() +      end) + +      assert length(result) == 3 + +      # Trying to get the chat of a different user +      result = +        conn +        |> assign(:user, other_user) +        |> get("/api/v1/pleroma/chats/#{chat.id}/messages") + +      assert result |> json_response(404) +    end +  end + +  describe "POST /api/v1/pleroma/chats/by-account-id/:id" do +    setup do: oauth_access(["write:chats"]) + +    test "it creates or returns a chat", %{conn: conn} do +      other_user = insert(:user) + +      result = +        conn +        |> post("/api/v1/pleroma/chats/by-account-id/#{other_user.id}") +        |> json_response_and_validate_schema(200) + +      assert result["id"] +    end +  end + +  describe "GET /api/v1/pleroma/chats/:id" do +    setup do: oauth_access(["read:chats"]) + +    test "it returns a chat", %{conn: conn, user: user} do +      other_user = insert(:user) + +      {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id) + +      result = +        conn +        |> get("/api/v1/pleroma/chats/#{chat.id}") +        |> json_response_and_validate_schema(200) + +      assert result["id"] == to_string(chat.id) +    end +  end + +  describe "GET /api/v1/pleroma/chats" do +    setup do: oauth_access(["read:chats"]) + +    test "it does not return chats with users you blocked", %{conn: conn, user: user} do +      recipient = insert(:user) + +      {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id) + +      result = +        conn +        |> get("/api/v1/pleroma/chats") +        |> json_response_and_validate_schema(200) + +      assert length(result) == 1 + +      User.block(user, recipient) + +      result = +        conn +        |> get("/api/v1/pleroma/chats") +        |> json_response_and_validate_schema(200) + +      assert length(result) == 0 +    end + +    test "it returns all chats", %{conn: conn, user: user} do +      Enum.each(1..30, fn _ -> +        recipient = insert(:user) +        {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id) +      end) + +      result = +        conn +        |> get("/api/v1/pleroma/chats") +        |> json_response_and_validate_schema(200) + +      assert length(result) == 30 +    end + +    test "it return a list of chats the current user is participating in, in descending order of updates", +         %{conn: conn, user: user} do +      har = insert(:user) +      jafnhar = insert(:user) +      tridi = insert(:user) + +      {:ok, chat_1} = Chat.get_or_create(user.id, har.ap_id) +      :timer.sleep(1000) +      {:ok, _chat_2} = Chat.get_or_create(user.id, jafnhar.ap_id) +      :timer.sleep(1000) +      {:ok, chat_3} = Chat.get_or_create(user.id, tridi.ap_id) +      :timer.sleep(1000) + +      # bump the second one +      {:ok, chat_2} = Chat.bump_or_create(user.id, jafnhar.ap_id) + +      result = +        conn +        |> get("/api/v1/pleroma/chats") +        |> json_response_and_validate_schema(200) + +      ids = Enum.map(result, & &1["id"]) + +      assert ids == [ +               chat_2.id |> to_string(), +               chat_3.id |> to_string(), +               chat_1.id |> to_string() +             ] +    end +  end +end diff --git a/test/web/pleroma_api/views/chat/message_reference_view_test.exs b/test/web/pleroma_api/views/chat/message_reference_view_test.exs new file mode 100644 index 000000000..e5b165255 --- /dev/null +++ b/test/web/pleroma_api/views/chat/message_reference_view_test.exs @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceViewTest do +  use Pleroma.DataCase + +  alias Pleroma.Chat +  alias Pleroma.Chat.MessageReference +  alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView + +  import Pleroma.Factory + +  test "it displays a chat message" do +    user = 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:") + +    chat = Chat.get(user.id, recipient.ap_id) + +    object = Object.normalize(activity) + +    cm_ref = MessageReference.for_chat_and_object(chat, object) + +    chat_message = MessageReferenceView.render("show.json", chat_message_reference: cm_ref) + +    assert chat_message[:id] == cm_ref.id +    assert chat_message[:content] == "kippis :firefox:" +    assert chat_message[:account_id] == user.id +    assert chat_message[:chat_id] +    assert chat_message[:created_at] +    assert chat_message[:unread] == false +    assert match?([%{shortcode: "firefox"}], chat_message[:emojis]) + +    {:ok, activity} = CommonAPI.post_chat_message(recipient, user, "gkgkgk", media_id: upload.id) + +    object = Object.normalize(activity) + +    cm_ref = MessageReference.for_chat_and_object(chat, object) + +    chat_message_two = MessageReferenceView.render("show.json", chat_message_reference: cm_ref) + +    assert chat_message_two[:id] == cm_ref.id +    assert chat_message_two[:content] == "gkgkgk" +    assert chat_message_two[:account_id] == recipient.id +    assert chat_message_two[:chat_id] == chat_message[:chat_id] +    assert chat_message_two[:attachment] +    assert chat_message_two[:unread] == true +  end +end diff --git a/test/web/pleroma_api/views/chat_view_test.exs b/test/web/pleroma_api/views/chat_view_test.exs new file mode 100644 index 000000000..14eecb1bd --- /dev/null +++ b/test/web/pleroma_api/views/chat_view_test.exs @@ -0,0 +1,48 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.ChatViewTest do +  use Pleroma.DataCase + +  alias Pleroma.Chat +  alias Pleroma.Chat.MessageReference +  alias Pleroma.Object +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView +  alias Pleroma.Web.PleromaAPI.ChatView + +  import Pleroma.Factory + +  test "it represents a chat" do +    user = insert(:user) +    recipient = insert(:user) + +    {:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id) + +    represented_chat = ChatView.render("show.json", chat: chat) + +    assert represented_chat == %{ +             id: "#{chat.id}", +             account: AccountView.render("show.json", user: recipient), +             unread: 0, +             last_message: nil, +             updated_at: Utils.to_masto_date(chat.updated_at) +           } + +    {:ok, chat_message_creation} = CommonAPI.post_chat_message(user, recipient, "hello") + +    chat_message = Object.normalize(chat_message_creation, false) + +    {:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id) + +    represented_chat = ChatView.render("show.json", chat: chat) + +    cm_ref = MessageReference.for_chat_and_object(chat, chat_message) + +    assert represented_chat[:last_message] == +             MessageReferenceView.render("show.json", chat_message_reference: cm_ref) +  end +end diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index a826b24c9..b48952b29 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -5,8 +5,10 @@  defmodule Pleroma.Web.Push.ImplTest do    use Pleroma.DataCase +  alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Push.Impl    alias Pleroma.Web.Push.Subscription @@ -60,7 +62,8 @@ defmodule Pleroma.Web.Push.ImplTest do      notif =        insert(:notification,          user: user, -        activity: activity +        activity: activity, +        type: "mention"        )      assert Impl.perform(notif) == {:ok, [:ok, :ok]} @@ -126,7 +129,7 @@ defmodule Pleroma.Web.Push.ImplTest do             ) ==               "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..." -    assert Impl.format_title(%{activity: activity}) == +    assert Impl.format_title(%{activity: activity, type: "mention"}) ==               "New Mention"    end @@ -136,9 +139,10 @@ defmodule Pleroma.Web.Push.ImplTest do      {:ok, _, _, activity} = CommonAPI.follow(user, other_user)      object = Object.normalize(activity, false) -    assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you" +    assert Impl.format_body(%{activity: activity, type: "follow"}, user, object) == +             "@Bob has followed you" -    assert Impl.format_title(%{activity: activity}) == +    assert Impl.format_title(%{activity: activity, type: "follow"}) ==               "New Follower"    end @@ -157,7 +161,7 @@ defmodule Pleroma.Web.Push.ImplTest do      assert Impl.format_body(%{activity: announce_activity}, user, object) ==               "@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..." -    assert Impl.format_title(%{activity: announce_activity}) == +    assert Impl.format_title(%{activity: announce_activity, type: "reblog"}) ==               "New Repeat"    end @@ -173,9 +177,10 @@ defmodule Pleroma.Web.Push.ImplTest do      {:ok, activity} = CommonAPI.favorite(user, activity.id)      object = Object.normalize(activity) -    assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post" +    assert Impl.format_body(%{activity: activity, type: "favourite"}, user, object) == +             "@Bob has favorited your post" -    assert Impl.format_title(%{activity: activity}) == +    assert Impl.format_title(%{activity: activity, type: "favourite"}) ==               "New Favorite"    end @@ -193,6 +198,46 @@ defmodule Pleroma.Web.Push.ImplTest do    end    describe "build_content/3" do +    test "builds content for chat messages" do +      user = insert(:user) +      recipient = insert(:user) + +      {:ok, chat} = CommonAPI.post_chat_message(user, recipient, "hey") +      object = Object.normalize(chat, false) +      [notification] = Notification.for_user(recipient) + +      res = Impl.build_content(notification, user, object) + +      assert res == %{ +               body: "@#{user.nickname}: hey", +               title: "New Chat Message" +             } +    end + +    test "builds content for chat messages with no content" do +      user = 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, chat} = CommonAPI.post_chat_message(user, recipient, nil, media_id: upload.id) +      object = Object.normalize(chat, false) +      [notification] = Notification.for_user(recipient) + +      res = Impl.build_content(notification, user, object) + +      assert res == %{ +               body: "@#{user.nickname}: (Attachment)", +               title: "New Chat Message" +             } +    end +      test "hides details for notifications when privacy option enabled" do        user = insert(:user, nickname: "Bob")        user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true}) @@ -218,7 +263,7 @@ defmodule Pleroma.Web.Push.ImplTest do            status: "<Lorem ipsum dolor sit amet."          }) -      notif = insert(:notification, user: user2, activity: activity) +      notif = insert(:notification, user: user2, activity: activity, type: "mention")        actor = User.get_cached_by_ap_id(notif.activity.data["actor"])        object = Object.normalize(activity) @@ -229,7 +274,7 @@ defmodule Pleroma.Web.Push.ImplTest do        {:ok, activity} = CommonAPI.favorite(user, activity.id) -      notif = insert(:notification, user: user2, activity: activity) +      notif = insert(:notification, user: user2, activity: activity, type: "favourite")        actor = User.get_cached_by_ap_id(notif.activity.data["actor"])        object = Object.normalize(activity) @@ -268,7 +313,7 @@ defmodule Pleroma.Web.Push.ImplTest do              "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."          }) -      notif = insert(:notification, user: user2, activity: activity) +      notif = insert(:notification, user: user2, activity: activity, type: "mention")        actor = User.get_cached_by_ap_id(notif.activity.data["actor"])        object = Object.normalize(activity) @@ -281,7 +326,7 @@ defmodule Pleroma.Web.Push.ImplTest do        {:ok, activity} = CommonAPI.favorite(user, activity.id) -      notif = insert(:notification, user: user2, activity: activity) +      notif = insert(:notification, user: user2, activity: activity, type: "favourite")        actor = User.get_cached_by_ap_id(notif.activity.data["actor"])        object = Object.normalize(activity) diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 3f012259a..245f6e63f 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -7,11 +7,15 @@ defmodule Pleroma.Web.StreamerTest do    import Pleroma.Factory +  alias Pleroma.Chat +  alias Pleroma.Chat.MessageReference    alias Pleroma.Conversation.Participation    alias Pleroma.List +  alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Streamer +  alias Pleroma.Web.StreamerView    @moduletag needs_streamer: true, capture_log: true @@ -145,6 +149,57 @@ defmodule Pleroma.Web.StreamerTest do        refute Streamer.filtered_by_user?(user, notify)      end +    test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} do +      other_user = insert(:user) + +      {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno") +      object = Object.normalize(create_activity, false) +      chat = Chat.get(user.id, other_user.ap_id) +      cm_ref = MessageReference.for_chat_and_object(chat, object) +      cm_ref = %{cm_ref | chat: chat, object: object} + +      Streamer.get_topic_and_add_socket("user:pleroma_chat", user) +      Streamer.stream("user:pleroma_chat", {user, cm_ref}) + +      text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) + +      assert text =~ "hey cirno" +      assert_receive {:text, ^text} +    end + +    test "it sends chat messages to the 'user' stream", %{user: user} do +      other_user = insert(:user) + +      {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno") +      object = Object.normalize(create_activity, false) +      chat = Chat.get(user.id, other_user.ap_id) +      cm_ref = MessageReference.for_chat_and_object(chat, object) +      cm_ref = %{cm_ref | chat: chat, object: object} + +      Streamer.get_topic_and_add_socket("user", user) +      Streamer.stream("user", {user, cm_ref}) + +      text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) + +      assert text =~ "hey cirno" +      assert_receive {:text, ^text} +    end + +    test "it sends chat message notifications to the 'user:notification' stream", %{user: user} do +      other_user = insert(:user) + +      {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey") + +      notify = +        Repo.get_by(Pleroma.Notification, user_id: user.id, activity_id: create_activity.id) +        |> Repo.preload(:activity) + +      Streamer.get_topic_and_add_socket("user:notification", user) +      Streamer.stream("user:notification", notify) +      assert_receive {:render_with_user, _, _, ^notify} +      refute Streamer.filtered_by_user?(user, notify) +    end +      test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{        user: user      } do | 
